fix(money,orders): wage/gram-debt parity, atomic document confirm, expert edit, made-to-order, QA fixes
- Rial wage and gram-debt arithmetic matched to the reference; order corrections keep debt in sync - Document confirm/un-confirm is a compare-and-swap (no double-applied cache under concurrency) - Settlement document and expert inputs validated (400 instead of 500) - Expert: edit / activate-deactivate own customers, adjust raw weights only - Made-to-order flow in the app, PreOrder.quantity migration - Order grids: customer column, sort keys; panel select search; pre-order quantity save - Rebuilt release artifacts Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
959a272227
commit
6514b05d18
@@ -0,0 +1,68 @@
|
||||
import { BadRequestException } from '@nestjs/common';
|
||||
import { PreOrderStatus } from '@prisma/client';
|
||||
import { AdminPreOrdersService } from './admin-preorders.service';
|
||||
|
||||
/** The quantity of a made-to-order request (legacy `sum`) is stored and shown, not a hard-coded 1. */
|
||||
describe('AdminPreOrdersService — quantity', () => {
|
||||
const row = {
|
||||
id: 'p1',
|
||||
status: PreOrderStatus.PLACED,
|
||||
colorName: 'طلایی',
|
||||
sizeSpec: '18',
|
||||
minaColor: null,
|
||||
stoneColor: null,
|
||||
quantity: 7,
|
||||
description: null,
|
||||
designImageUrl: null,
|
||||
estimatedWeightGrams: null,
|
||||
estimatedPriceRial: null,
|
||||
convertedOrderId: null,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
city: null,
|
||||
workshop: null,
|
||||
customer: { id: 'c1', firstName: 'علی', lastName: 'رضایی', fullName: 'علی رضایی', mobile: '0912' },
|
||||
};
|
||||
|
||||
function build() {
|
||||
const updates: any[] = [];
|
||||
const prisma = {
|
||||
preOrder: {
|
||||
findUnique: jest.fn(async () => row),
|
||||
update: jest.fn(async ({ data }: any) => (updates.push(data), { ...row, ...data })),
|
||||
},
|
||||
};
|
||||
return { service: new AdminPreOrdersService(prisma as never, {} as never), updates };
|
||||
}
|
||||
|
||||
it("shows the request's own quantity as `sum`", async () => {
|
||||
const { service } = build();
|
||||
expect(((await service.findOne('p1')) as any).sum).toBe(7);
|
||||
});
|
||||
|
||||
it('lets the panel correct the quantity', async () => {
|
||||
const { service, updates } = build();
|
||||
await service.update('p1', { sum: 12 });
|
||||
expect(updates[0].quantity).toBe(12);
|
||||
});
|
||||
|
||||
it('refuses a quantity that is not a whole number of at least one', async () => {
|
||||
const { service, updates } = build();
|
||||
for (const sum of [0, -2, 1.5, 'abc']) {
|
||||
await expect(service.update('p1', { sum })).rejects.toThrow(BadRequestException);
|
||||
}
|
||||
expect(updates).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('AdminPreOrdersService.list — sorting the «تعداد» column', () => {
|
||||
const { TableService } = jest.requireActual('../admin-table/table.service') as typeof import('../admin-table/table.service');
|
||||
|
||||
it('sorts on the stored quantity when the grid asks for its legacy key `sum`', async () => {
|
||||
const findMany = jest.fn(async () => []);
|
||||
const prisma = { preOrder: { findMany, count: jest.fn(async () => 0) } };
|
||||
const service = new AdminPreOrdersService(prisma as never, new TableService() as never);
|
||||
await service.list({ page: 1, limit: 10, order: '-sum' } as never);
|
||||
expect((findMany.mock.calls[0] as any)[0].orderBy).toEqual({ quantity: 'desc' });
|
||||
});
|
||||
});
|
||||
@@ -37,6 +37,7 @@ export class AdminPreOrdersService {
|
||||
'sizeSpec',
|
||||
'stoneColor',
|
||||
'minaColor',
|
||||
'quantity',
|
||||
'estimatedWeightGrams',
|
||||
'estimatedPriceRial',
|
||||
'customer.fullName',
|
||||
@@ -47,7 +48,10 @@ export class AdminPreOrdersService {
|
||||
async list(query: TableQueryDto) {
|
||||
this.table.assertNotExport(query);
|
||||
|
||||
const { where, orderBy, skip, take, page, limit } = this.table.build(query, {
|
||||
// The «تعداد» column is keyed `sum` (legacy name); the stored field is `quantity`.
|
||||
const sorted = query.order && /^[+-]?sum$/.test(query.order) ? { ...query, order: query.order.replace('sum', 'quantity') } : query;
|
||||
|
||||
const { where, orderBy, skip, take, page, limit } = this.table.build(sorted, {
|
||||
searchable: ['customer.fullName', 'customer.mobile'],
|
||||
allowed: AdminPreOrdersService.ALLOWED,
|
||||
defaultOrderBy: { createdAt: 'desc' },
|
||||
@@ -79,8 +83,8 @@ export class AdminPreOrdersService {
|
||||
size: p.sizeSpec,
|
||||
color_mina: p.minaColor,
|
||||
color_stone: p.stoneColor,
|
||||
// legacy 'sum' (quantity); a PreOrder models a single design request
|
||||
sum: 1,
|
||||
// legacy 'sum' — the pieces asked for
|
||||
sum: p.quantity,
|
||||
description: p.description,
|
||||
designImageUrl: p.designImageUrl,
|
||||
estimatedWeightGrams: p.estimatedWeightGrams,
|
||||
@@ -123,6 +127,11 @@ export class AdminPreOrdersService {
|
||||
if (data.color_mina !== undefined) patch.minaColor = data.color_mina as string;
|
||||
if (data.color_stone !== undefined) patch.stoneColor = data.color_stone as string;
|
||||
if (data.description !== undefined) patch.description = data.description as string;
|
||||
if (data.sum !== undefined) {
|
||||
const sum = Number(data.sum);
|
||||
if (!Number.isInteger(sum) || sum < 1) throw new BadRequestException('تعداد باید عدد صحیح و حداقل ۱ باشد');
|
||||
patch.quantity = sum;
|
||||
}
|
||||
if (data.estimatedWeightGrams !== undefined) {
|
||||
patch.estimatedWeightGrams = new Prisma.Decimal(data.estimatedWeightGrams as string);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user