@open-mercato/core 0.10.1-develop.7727.1.0b89c4f800 → 0.10.1-develop.7736.1.88e07906bf
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/dist/modules/attachments/lib/ocrService.js +8 -0
- package/dist/modules/attachments/lib/ocrService.js.map +2 -2
- package/dist/modules/auth/api/logout.js +3 -1
- package/dist/modules/auth/api/logout.js.map +2 -2
- package/dist/modules/sales/commands/documents.js +1190 -1408
- package/dist/modules/sales/commands/documents.js.map +2 -2
- package/dist/modules/sales/commands/returns.js +34 -11
- package/dist/modules/sales/commands/returns.js.map +2 -2
- package/dist/modules/sales/commands/shared.js +5 -0
- package/dist/modules/sales/commands/shared.js.map +2 -2
- package/dist/modules/sales/commands/shipments.js +9 -0
- package/dist/modules/sales/commands/shipments.js.map +2 -2
- package/dist/modules/staff/api/timesheets/time-projects/route.js +6 -2
- package/dist/modules/staff/api/timesheets/time-projects/route.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/attachments/lib/ocrService.ts +8 -0
- package/src/modules/auth/api/logout.ts +3 -1
- package/src/modules/sales/commands/documents.ts +1501 -1626
- package/src/modules/sales/commands/returns.ts +41 -14
- package/src/modules/sales/commands/shared.ts +40 -0
- package/src/modules/sales/commands/shipments.ts +10 -0
- package/src/modules/staff/api/timesheets/time-projects/route.ts +6 -2
|
@@ -10,7 +10,7 @@ import { findOneWithDecryption, findWithDecryption } from "@open-mercato/shared/
|
|
|
10
10
|
import { SalesDocumentNumberGenerator } from "../services/salesDocumentNumberGenerator.js";
|
|
11
11
|
import { isExternalAmountsMode } from "../lib/calculations.js";
|
|
12
12
|
import { readPersistedHeaderTotals } from "../lib/externalAmounts.js";
|
|
13
|
-
import { cloneJson, deriveLineNetFromGross, ensureOrganizationScope, ensureSameScope, ensureTenantScope, extractUndoPayload, toNumericString, enforceSalesDocumentOptimisticLock, SALES_RESOURCE_KIND_ORDER, SALES_RESOURCE_KIND_RETURN } from "./shared.js";
|
|
13
|
+
import { cloneJson, deriveLineNetFromGross, ensureOrganizationScope, ensureSameScope, ensureTenantScope, extractUndoPayload, toNumericString, enforceSalesDocumentOptimisticLock, lockSalesDocumentRow, SALES_RESOURCE_KIND_ORDER, SALES_RESOURCE_KIND_RETURN } from "./shared.js";
|
|
14
14
|
import { resolveRedoSnapshot } from "@open-mercato/shared/lib/commands/redo";
|
|
15
15
|
import { SalesOrder, SalesOrderAdjustment, SalesOrderLine, SalesReturn, SalesReturnLine } from "../data/entities.js";
|
|
16
16
|
import { mapOrderLineEntityToSnapshot } from "../lib/lineSnapshots.js";
|
|
@@ -209,20 +209,28 @@ async function loadReturnHeaderSnapshot(em, id) {
|
|
|
209
209
|
returnedAt: header.returnedAt ? header.returnedAt.toISOString() : null
|
|
210
210
|
};
|
|
211
211
|
}
|
|
212
|
-
async function
|
|
213
|
-
|
|
212
|
+
async function lockReturnOrder(em, orderId, scope) {
|
|
213
|
+
if (!await lockSalesDocumentRow(em, "sales_orders", orderId, scope)) return null;
|
|
214
|
+
return findOneWithDecryption(
|
|
214
215
|
em,
|
|
215
216
|
SalesOrder,
|
|
216
|
-
{ id:
|
|
217
|
-
{},
|
|
218
|
-
|
|
217
|
+
{ id: orderId, deletedAt: null, ...scope },
|
|
218
|
+
{ refresh: true },
|
|
219
|
+
scope
|
|
219
220
|
);
|
|
220
|
-
|
|
221
|
+
}
|
|
222
|
+
async function reverseReturnEffects(em, salesCalculationService, snapshot) {
|
|
223
|
+
let order = null;
|
|
221
224
|
let lines = [];
|
|
222
225
|
await withAtomicFlush(
|
|
223
226
|
em,
|
|
224
227
|
[
|
|
225
228
|
async () => {
|
|
229
|
+
order = await lockReturnOrder(em, snapshot.orderId, {
|
|
230
|
+
tenantId: snapshot.tenantId,
|
|
231
|
+
organizationId: snapshot.organizationId
|
|
232
|
+
});
|
|
233
|
+
if (!order) return;
|
|
226
234
|
lines = await findWithDecryption(
|
|
227
235
|
em,
|
|
228
236
|
SalesOrderLine,
|
|
@@ -241,6 +249,7 @@ async function reverseReturnEffects(em, salesCalculationService, snapshot) {
|
|
|
241
249
|
});
|
|
242
250
|
},
|
|
243
251
|
async () => {
|
|
252
|
+
if (!order) return;
|
|
244
253
|
if (snapshot.adjustmentIds.length) {
|
|
245
254
|
const adjustments = await findWithDecryption(
|
|
246
255
|
em,
|
|
@@ -300,17 +309,24 @@ async function restoreReturnEffects(em, salesCalculationService, snapshot) {
|
|
|
300
309
|
em,
|
|
301
310
|
[
|
|
302
311
|
async () => {
|
|
303
|
-
const
|
|
312
|
+
const existingOrder = await findOneWithDecryption(
|
|
304
313
|
em,
|
|
305
314
|
SalesOrder,
|
|
306
315
|
{ id: snapshot.orderId, deletedAt: null },
|
|
307
316
|
{},
|
|
308
317
|
{ tenantId: snapshot.tenantId, organizationId: snapshot.organizationId }
|
|
309
318
|
);
|
|
319
|
+
if (!existingOrder) {
|
|
320
|
+
throw notFound(translate("sales.returns.orderMissing", "Order not found."));
|
|
321
|
+
}
|
|
322
|
+
ensureSameScope(existingOrder, snapshot.organizationId, snapshot.tenantId);
|
|
323
|
+
const order = await lockReturnOrder(em, existingOrder.id, {
|
|
324
|
+
tenantId: snapshot.tenantId,
|
|
325
|
+
organizationId: snapshot.organizationId
|
|
326
|
+
});
|
|
310
327
|
if (!order) {
|
|
311
328
|
throw notFound(translate("sales.returns.orderMissing", "Order not found."));
|
|
312
329
|
}
|
|
313
|
-
ensureSameScope(order, snapshot.organizationId, snapshot.tenantId);
|
|
314
330
|
const orderLines = await findWithDecryption(
|
|
315
331
|
em,
|
|
316
332
|
SalesOrderLine,
|
|
@@ -476,17 +492,24 @@ const createReturnCommand = {
|
|
|
476
492
|
}
|
|
477
493
|
const salesCalculationService = ctx.container.resolve("salesCalculationService");
|
|
478
494
|
const { header, createdLines, order } = await em.transactional(async (tx) => {
|
|
479
|
-
const
|
|
495
|
+
const existingOrder = await findOneWithDecryption(
|
|
480
496
|
tx,
|
|
481
497
|
SalesOrder,
|
|
482
498
|
{ id: input.orderId, deletedAt: null },
|
|
483
499
|
{},
|
|
484
500
|
{ tenantId: input.tenantId, organizationId: input.organizationId }
|
|
485
501
|
);
|
|
502
|
+
if (!existingOrder) {
|
|
503
|
+
throw notFound(translate("sales.returns.orderMissing", "Order not found."));
|
|
504
|
+
}
|
|
505
|
+
ensureSameScope(existingOrder, input.organizationId, input.tenantId);
|
|
506
|
+
const order2 = await lockReturnOrder(tx, existingOrder.id, {
|
|
507
|
+
tenantId: input.tenantId,
|
|
508
|
+
organizationId: input.organizationId
|
|
509
|
+
});
|
|
486
510
|
if (!order2) {
|
|
487
511
|
throw notFound(translate("sales.returns.orderMissing", "Order not found."));
|
|
488
512
|
}
|
|
489
|
-
ensureSameScope(order2, input.organizationId, input.tenantId);
|
|
490
513
|
await enforceSalesDocumentOptimisticLock(ctx, order2, SALES_RESOURCE_KIND_ORDER);
|
|
491
514
|
const orderLines = await findWithDecryption(
|
|
492
515
|
tx,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/sales/commands/returns.ts"],
|
|
4
|
-
"sourcesContent": ["import { randomUUID } from 'crypto'\nimport { registerCommand, type CommandHandler } from '@open-mercato/shared/lib/commands'\nimport { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'\nimport { LockMode } from '@mikro-orm/core'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { CrudHttpError, notFound } from '@open-mercato/shared/lib/crud/errors'\nimport { invalidateCrudCache } from '@open-mercato/shared/lib/crud/cache'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { emitCrudSideEffects } from '@open-mercato/shared/lib/commands/helpers'\nimport type { CrudEventsConfig } from '@open-mercato/shared/lib/crud/types'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { SalesDocumentNumberGenerator } from '../services/salesDocumentNumberGenerator'\nimport type { SalesCalculationService } from '../services/salesCalculationService'\nimport type { SalesAdjustmentDraft, SalesLineSnapshot, SalesDocumentCalculationResult } from '../lib/types'\nimport { isExternalAmountsMode } from '../lib/calculations'\nimport { readPersistedHeaderTotals } from '../lib/externalAmounts'\nimport { cloneJson, deriveLineNetFromGross, ensureOrganizationScope, ensureSameScope, ensureTenantScope, extractUndoPayload, toNumericString, enforceSalesDocumentOptimisticLock, SALES_RESOURCE_KIND_ORDER, SALES_RESOURCE_KIND_RETURN } from './shared'\nimport { resolveRedoSnapshot } from '@open-mercato/shared/lib/commands/redo'\nimport { SalesOrder, SalesOrderAdjustment, SalesOrderLine, SalesReturn, SalesReturnLine } from '../data/entities'\nimport { mapOrderLineEntityToSnapshot } from '../lib/lineSnapshots'\nimport { loadShippedQuantityByLine } from '../lib/shipments/snapshots'\nimport { computeAvailableReturnQuantity } from '../lib/returnQuantity'\nimport {\n returnCreateSchema,\n returnUpdateSchema,\n returnDeleteSchema,\n type ReturnCreateInput,\n type ReturnUpdateInput,\n type ReturnDeleteInput,\n} from '../data/validators'\nimport { E } from '#generated/entities.ids.generated'\n\ntype ReturnLineInput = { orderLineId: string; quantity: number }\n\ntype ReturnSnapshot = {\n id: string\n orderId: string\n organizationId: string\n tenantId: string\n returnNumber: string\n returnedAt: string | null\n reason: string | null\n notes: string | null\n lines: Array<{\n id: string\n orderLineId: string\n quantityReturned: number\n unitPriceNet: number\n unitPriceGross: number\n totalNetAmount: number\n totalGrossAmount: number\n }>\n adjustmentIds: string[]\n}\n\ntype ReturnUndoPayload = {\n after?: ReturnSnapshot | null\n}\n\nconst returnCrudEvents: CrudEventsConfig = {\n module: 'sales',\n entity: 'return',\n persistent: true,\n buildPayload: (ctx) => ({\n id: ctx.identifiers.id,\n organizationId: ctx.identifiers.organizationId,\n tenantId: ctx.identifiers.tenantId,\n }),\n}\n\ntype OrderCacheRecord = Pick<SalesOrder, 'id' | 'organizationId' | 'tenantId'>\n\n/**\n * Return mutations update the order aggregate, which is also the cache resource\n * used by the order-lines and order-adjustments routes. Invalidate it after\n * each committed return lifecycle change so reloads receive its fresh\n * `updatedAt` optimistic-lock token.\n */\nasync function invalidateOrderCache(\n container: Parameters<typeof invalidateCrudCache>[0],\n order: OrderCacheRecord,\n fallbackTenant: string | null,\n): Promise<void> {\n await invalidateCrudCache(\n container,\n SALES_RESOURCE_KIND_ORDER,\n { id: order.id, organizationId: order.organizationId, tenantId: order.tenantId },\n fallbackTenant,\n 'updated',\n )\n}\n\nfunction toNumeric(value: unknown): number {\n if (typeof value === 'number' && Number.isFinite(value)) return value\n if (typeof value === 'string' && value.trim().length) {\n const parsed = Number(value)\n if (Number.isFinite(parsed)) return parsed\n }\n return 0\n}\n\nfunction round(value: number): number {\n return Math.round((value + Number.EPSILON) * 1e4) / 1e4\n}\n\n/**\n * Payment totals live on the order, not in the line/adjustment math a return\n * recalculates. Every `calculateDocumentTotals` call that writes order totals\n * back to the order MUST seed these so `buildBaseDocumentResult` preserves the\n * recorded `paidTotalAmount` / `refundedTotalAmount` instead of defaulting them\n * to 0 \u2014 otherwise creating/undoing/redoing a return silently zeroes the paid\n * amount and the order's outstanding balance goes wrong on any paid order\n * (#3756). Mirrors `resolveExistingPaymentTotals` in `commands/documents.ts`.\n */\nfunction resolveExistingPaymentTotals(order: SalesOrder): { paidTotalAmount: number; refundedTotalAmount: number } {\n return {\n paidTotalAmount: toNumeric(order.paidTotalAmount),\n refundedTotalAmount: toNumeric(order.refundedTotalAmount),\n }\n}\n\nfunction applyOrderTotals(order: SalesOrder, totals: SalesDocumentCalculationResult['totals'], lineCount: number): void {\n order.subtotalNetAmount = toNumericString(totals.subtotalNetAmount) ?? '0'\n order.subtotalGrossAmount = toNumericString(totals.subtotalGrossAmount) ?? '0'\n order.discountTotalAmount = toNumericString(totals.discountTotalAmount) ?? '0'\n order.taxTotalAmount = toNumericString(totals.taxTotalAmount) ?? '0'\n order.shippingNetAmount = toNumericString(totals.shippingNetAmount) ?? '0'\n order.shippingGrossAmount = toNumericString(totals.shippingGrossAmount) ?? '0'\n order.surchargeTotalAmount = toNumericString(totals.surchargeTotalAmount) ?? '0'\n order.grandTotalNetAmount = toNumericString(totals.grandTotalNetAmount) ?? '0'\n order.grandTotalGrossAmount = toNumericString(totals.grandTotalGrossAmount) ?? '0'\n order.paidTotalAmount = toNumericString(totals.paidTotalAmount) ?? '0'\n order.refundedTotalAmount = toNumericString(totals.refundedTotalAmount) ?? '0'\n order.outstandingAmount = toNumericString(totals.outstandingAmount) ?? '0'\n order.totalsSnapshot = cloneJson(totals)\n order.lineItemCount = lineCount\n}\n\n// Deliberate, not an omission: an external order's header belongs to the source\n// system, which issues its own credit document. The return itself is still recorded.\nfunction applyOrderTotalsUnlessExternal(\n order: SalesOrder,\n calculation: SalesDocumentCalculationResult,\n): void {\n if (isExternalAmountsMode(order.totalsMode ?? 'computed')) return\n applyOrderTotals(order, calculation.totals, calculation.lines.length)\n}\n\nfunction resolveOrderCalculationMode(order: SalesOrder) {\n const totalsMode = order.totalsMode ?? 'computed'\n return {\n totalsMode,\n suppliedTotals: isExternalAmountsMode(totalsMode) ? readPersistedHeaderTotals(order) : null,\n }\n}\n\nfunction mapOrderAdjustmentToDraft(adjustment: SalesOrderAdjustment): SalesAdjustmentDraft {\n return {\n id: adjustment.id,\n scope: adjustment.scope ?? 'order',\n kind: adjustment.kind,\n code: adjustment.code ?? null,\n label: adjustment.label ?? null,\n calculatorKey: adjustment.calculatorKey ?? null,\n promotionId: adjustment.promotionId ?? null,\n rate: toNumeric(adjustment.rate),\n amountNet: toNumeric(adjustment.amountNet),\n amountGross: toNumeric(adjustment.amountGross),\n currencyCode: adjustment.currencyCode ?? null,\n metadata: adjustment.metadata ? cloneJson(adjustment.metadata) : null,\n position: adjustment.position ?? 0,\n }\n}\n\nfunction buildCalculationContext(order: SalesOrder) {\n return {\n tenantId: order.tenantId,\n organizationId: order.organizationId,\n currencyCode: order.currencyCode,\n metadata: {\n shippingMethod: order.shippingMethodSnapshot\n ? cloneJson(order.shippingMethodSnapshot as Record<string, unknown>)\n : null,\n paymentMethod: order.paymentMethodSnapshot ? cloneJson(order.paymentMethodSnapshot as Record<string, unknown>) : null,\n },\n }\n}\n\n/**\n * Recalculates order totals (including line-scoped return adjustments) from lines and\n * adjustments. Do not merge this into GET /api/sales/orders responses: provider\n * calculators re-emit shipping/payment fees on top of already-persisted adjustments\n * and make list vs detail totals diverge (#5438). Persist via commands instead.\n */\nexport async function recalculateOrderTotalsForDisplay(\n em: EntityManager,\n container: { resolve: (key: string) => unknown },\n orderId: string,\n scope: { tenantId: string; organizationId: string },\n): Promise<SalesDocumentCalculationResult['totals'] | null> {\n const order = await findOneWithDecryption(\n em,\n SalesOrder,\n { id: orderId, deletedAt: null },\n {},\n scope,\n )\n if (!order) return null\n const [orderLines, adjustments] = await Promise.all([\n findWithDecryption(em, SalesOrderLine, { order: order.id, deletedAt: null }, {}, scope),\n findWithDecryption(\n em,\n SalesOrderAdjustment,\n { order: order.id, deletedAt: null },\n { orderBy: { position: 'asc' } },\n scope,\n ),\n ])\n const lineSnapshots: SalesLineSnapshot[] = orderLines.map(mapOrderLineEntityToSnapshot)\n const adjustmentDrafts: SalesAdjustmentDraft[] = adjustments.map(mapOrderAdjustmentToDraft)\n const salesCalculationService = container.resolve('salesCalculationService') as SalesCalculationService\n const calculation = await salesCalculationService.calculateDocumentTotals({\n documentKind: 'order',\n lines: lineSnapshots,\n adjustments: adjustmentDrafts,\n context: buildCalculationContext(order),\n existingTotals: resolveExistingPaymentTotals(order),\n ...resolveOrderCalculationMode(order),\n })\n return calculation.totals\n}\n\nexport async function loadReturnSnapshot(em: EntityManager, id: string): Promise<ReturnSnapshot | null> {\n const header = await findOneWithDecryption(\n em,\n SalesReturn,\n { id, deletedAt: null },\n { populate: ['order'] },\n {},\n )\n if (!header || !header.order) return null\n const orderId = typeof header.order === 'string' ? header.order : header.order.id\n const lines = await findWithDecryption(\n em,\n SalesReturnLine,\n { salesReturn: header.id, deletedAt: null },\n { populate: ['orderLine'] },\n { tenantId: header.tenantId, organizationId: header.organizationId },\n )\n const adjustmentIds: string[] = []\n const adjustments = await findWithDecryption(\n em,\n SalesOrderAdjustment,\n { order: orderId, kind: 'return', deletedAt: null },\n {},\n { tenantId: header.tenantId, organizationId: header.organizationId },\n )\n adjustments.forEach((adj) => {\n const meta = adj.metadata as Record<string, unknown> | null | undefined\n if (meta && meta.returnId === header.id) adjustmentIds.push(adj.id)\n })\n\n return {\n id: header.id,\n orderId,\n organizationId: header.organizationId,\n tenantId: header.tenantId,\n returnNumber: header.returnNumber,\n returnedAt: header.returnedAt ? header.returnedAt.toISOString() : null,\n reason: header.reason ?? null,\n notes: header.notes ?? null,\n lines: lines.map((line) => ({\n id: line.id,\n orderLineId: typeof line.orderLine === 'string' ? line.orderLine : line.orderLine?.id ?? null,\n quantityReturned: toNumeric(line.quantityReturned),\n unitPriceNet: toNumeric(line.unitPriceNet),\n unitPriceGross: toNumeric(line.unitPriceGross),\n totalNetAmount: toNumeric(line.totalNetAmount),\n totalGrossAmount: toNumeric(line.totalGrossAmount),\n })),\n adjustmentIds,\n }\n}\n\ntype ReturnHeaderSnapshot = {\n id: string\n orderId: string\n organizationId: string\n tenantId: string\n reason: string | null\n notes: string | null\n returnedAt: string | null\n}\n\ntype ReturnHeaderUndoPayload = {\n before?: ReturnHeaderSnapshot | null\n after?: ReturnHeaderSnapshot | null\n}\n\ntype ReturnDeleteUndoPayload = {\n before?: ReturnSnapshot | null\n}\n\nasync function loadReturnHeaderSnapshot(em: EntityManager, id: string): Promise<ReturnHeaderSnapshot | null> {\n const header = await findOneWithDecryption(em, SalesReturn, { id, deletedAt: null }, { populate: ['order'] }, {})\n if (!header || !header.order) return null\n const orderId = typeof header.order === 'string' ? header.order : header.order.id\n return {\n id: header.id,\n orderId,\n organizationId: header.organizationId,\n tenantId: header.tenantId,\n reason: header.reason ?? null,\n notes: header.notes ?? null,\n returnedAt: header.returnedAt ? header.returnedAt.toISOString() : null,\n }\n}\n\n/**\n * Reverse the order-level effects of a return: restore each order line's\n * `returnedQuantity`, drop the return's line-scoped credit adjustments, remove\n * the return header + lines, and recalculate the order totals. Shared by the\n * create command's undo and the delete command's execute \u2014 both need the exact\n * same teardown. No-op when the order is gone.\n *\n * The line reversals, adjustment/return removals, and the order-total recompute\n * interleave queries on the same EntityManager with scalar mutations, so they\n * run inside an atomic flush to avoid lost updates and partial commits\n * (SPEC-018): the per-phase flush boundary persists the line `returnedQuantity`\n * reversals before the adjustment/header/return-line lookups in the next phase\n * run any query, which under MikroORM v7 would otherwise silently discard the\n * pending scalar changes on the managed lines.\n */\nasync function reverseReturnEffects(\n em: EntityManager,\n salesCalculationService: SalesCalculationService,\n snapshot: ReturnSnapshot,\n): Promise<void> {\n const order = await findOneWithDecryption(\n em,\n SalesOrder,\n { id: snapshot.orderId, deletedAt: null },\n {},\n { tenantId: snapshot.tenantId, organizationId: snapshot.organizationId },\n )\n if (!order) return\n\n let lines: SalesOrderLine[] = []\n await withAtomicFlush(\n em,\n [\n async () => {\n lines = await findWithDecryption(\n em,\n SalesOrderLine,\n { order: order.id, deletedAt: null },\n {},\n { tenantId: snapshot.tenantId, organizationId: snapshot.organizationId },\n )\n const lineMap = new Map(lines.map((line) => [line.id, line]))\n snapshot.lines.forEach((entry) => {\n const line = lineMap.get(entry.orderLineId)\n if (!line) return\n const next = Math.max(0, toNumeric(line.returnedQuantity) - entry.quantityReturned)\n line.returnedQuantity = next.toString()\n line.updatedAt = new Date()\n em.persist(line)\n })\n },\n async () => {\n if (snapshot.adjustmentIds.length) {\n const adjustments = await findWithDecryption(\n em,\n SalesOrderAdjustment,\n { id: { $in: snapshot.adjustmentIds }, deletedAt: null },\n {},\n { tenantId: snapshot.tenantId, organizationId: snapshot.organizationId },\n )\n adjustments.forEach((adj) => em.remove(adj))\n }\n\n const header = await findOneWithDecryption(\n em,\n SalesReturn,\n { id: snapshot.id, deletedAt: null },\n {},\n { tenantId: snapshot.tenantId, organizationId: snapshot.organizationId },\n )\n const returnLines = await findWithDecryption(\n em,\n SalesReturnLine,\n { salesReturn: snapshot.id, deletedAt: null },\n {},\n { tenantId: snapshot.tenantId, organizationId: snapshot.organizationId },\n )\n returnLines.forEach((line) => em.remove(line))\n if (header) em.remove(header)\n\n const existingAdjustments = await findWithDecryption(\n em,\n SalesOrderAdjustment,\n { order: order.id, deletedAt: null },\n { orderBy: { position: 'asc' } },\n { tenantId: snapshot.tenantId, organizationId: snapshot.organizationId },\n )\n const lineSnapshots: SalesLineSnapshot[] = lines.map(mapOrderLineEntityToSnapshot)\n const adjustmentDrafts: SalesAdjustmentDraft[] = existingAdjustments.map(mapOrderAdjustmentToDraft)\n const calculation = await salesCalculationService.calculateDocumentTotals({\n documentKind: 'order',\n lines: lineSnapshots,\n adjustments: adjustmentDrafts,\n context: buildCalculationContext(order),\n existingTotals: resolveExistingPaymentTotals(order),\n ...resolveOrderCalculationMode(order),\n })\n applyOrderTotalsUnlessExternal(order, calculation)\n order.updatedAt = new Date()\n em.persist(order)\n },\n ],\n { transaction: true },\n )\n}\n\n/**\n * Re-apply a return from a snapshot: recreate the return header + lines and the\n * line-scoped credit adjustments, bump each order line's `returnedQuantity`, and\n * recalculate the order totals. Shared by the create command's redo and the\n * delete command's undo. Returns the recreated return lines so callers can emit\n * index side effects. Throws a 404 when the order is gone.\n *\n * The order may have changed since the snapshot was taken (another return\n * consumed the shipped quantity, a shipment was removed, an order line was\n * deleted), so the snapshot quantities are re-checked against the currently\n * returnable quantity \u2014 the same rule `sales.returns.create` enforces \u2014 under\n * the order-line lock and before any write. A stale restore is refused with a\n * 409 instead of returning and crediting the same shipped units twice.\n */\nasync function restoreReturnEffects(\n em: EntityManager,\n salesCalculationService: SalesCalculationService,\n snapshot: ReturnSnapshot,\n): Promise<SalesReturnLine[]> {\n const returnId = snapshot.id\n const createdLines: SalesReturnLine[] = []\n const { translate } = await resolveTranslations()\n\n await withAtomicFlush(\n em,\n [\n async () => {\n const order = await findOneWithDecryption(\n em,\n SalesOrder,\n { id: snapshot.orderId, deletedAt: null },\n {},\n { tenantId: snapshot.tenantId, organizationId: snapshot.organizationId },\n )\n if (!order) {\n throw notFound(translate('sales.returns.orderMissing', 'Order not found.'))\n }\n ensureSameScope(order, snapshot.organizationId, snapshot.tenantId)\n\n const orderLines = await findWithDecryption(\n em,\n SalesOrderLine,\n { order: order.id, deletedAt: null },\n { lockMode: LockMode.PESSIMISTIC_WRITE },\n { tenantId: snapshot.tenantId, organizationId: snapshot.organizationId },\n )\n const lineMap = new Map(orderLines.map((line) => [line.id, line]))\n\n const shippedByLine = await loadShippedQuantityByLine(em, order.id, {\n tenantId: snapshot.tenantId,\n organizationId: snapshot.organizationId,\n })\n const restoredByLine = new Map<string, { line: SalesOrderLine; quantity: number }>()\n snapshot.lines.forEach((lineSnapshot) => {\n const line = lineSnapshot.orderLineId ? lineMap.get(lineSnapshot.orderLineId) : undefined\n if (!line) {\n throw new CrudHttpError(409, {\n error: translate('sales.returns.restoreLineMissing', 'This return cannot be restored: one of its order lines no longer exists.'),\n })\n }\n const restored = restoredByLine.get(line.id)\n restoredByLine.set(line.id, { line, quantity: (restored?.quantity ?? 0) + lineSnapshot.quantityReturned })\n })\n restoredByLine.forEach(({ line, quantity }, orderLineId) => {\n const available = computeAvailableReturnQuantity({\n quantity: toNumeric(line.quantity),\n returnedQuantity: toNumeric(line.returnedQuantity),\n shippedQuantity: shippedByLine.get(orderLineId) ?? 0,\n })\n if (quantity - 1e-6 > available) {\n throw new CrudHttpError(409, {\n error: translate('sales.returns.restoreQuantityUnavailable', 'This return cannot be restored: the order no longer has enough shipped quantity left to return.'),\n })\n }\n })\n\n const existingAdjustments = await findWithDecryption(\n em,\n SalesOrderAdjustment,\n { order: order.id, deletedAt: null },\n { orderBy: { position: 'asc' } },\n { tenantId: snapshot.tenantId, organizationId: snapshot.organizationId },\n )\n const positionStart = existingAdjustments.reduce((acc, adj) => Math.max(acc, adj.position ?? 0), 0) + 1\n\n const restoredHeader =\n (await findOneWithDecryption(\n em,\n SalesReturn,\n { id: snapshot.id },\n {},\n { tenantId: snapshot.tenantId, organizationId: snapshot.organizationId },\n )) ??\n em.create(SalesReturn, {\n id: snapshot.id,\n order,\n organizationId: snapshot.organizationId,\n tenantId: snapshot.tenantId,\n returnNumber: snapshot.returnNumber,\n reason: snapshot.reason ?? null,\n notes: snapshot.notes ?? null,\n returnedAt: snapshot.returnedAt ? new Date(snapshot.returnedAt) : new Date(),\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n restoredHeader.order = order\n restoredHeader.deletedAt = null\n restoredHeader.organizationId = snapshot.organizationId\n restoredHeader.tenantId = snapshot.tenantId\n restoredHeader.returnNumber = snapshot.returnNumber\n restoredHeader.reason = snapshot.reason ?? null\n restoredHeader.notes = snapshot.notes ?? null\n restoredHeader.returnedAt = snapshot.returnedAt ? new Date(snapshot.returnedAt) : new Date()\n restoredHeader.updatedAt = new Date()\n em.persist(restoredHeader)\n\n const createdAdjustments: SalesOrderAdjustment[] = []\n snapshot.lines.forEach((lineSnapshot, index) => {\n const line = lineMap.get(lineSnapshot.orderLineId)\n if (!line) return\n const totalNet = lineSnapshot.totalNetAmount\n const totalGross = lineSnapshot.totalGrossAmount\n const adjustmentId = snapshot.adjustmentIds[index] ?? randomUUID()\n\n const returnLine = em.create(SalesReturnLine, {\n id: lineSnapshot.id,\n salesReturn: restoredHeader,\n orderLine: em.getReference(SalesOrderLine, line.id),\n organizationId: snapshot.organizationId,\n tenantId: snapshot.tenantId,\n quantityReturned: lineSnapshot.quantityReturned.toString(),\n unitPriceNet: lineSnapshot.unitPriceNet.toString(),\n unitPriceGross: lineSnapshot.unitPriceGross.toString(),\n totalNetAmount: totalNet.toString(),\n totalGrossAmount: totalGross.toString(),\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n createdLines.push(returnLine)\n em.persist(returnLine)\n\n const adjustment = em.create(SalesOrderAdjustment, {\n id: adjustmentId,\n order,\n orderLine: em.getReference(SalesOrderLine, line.id),\n organizationId: snapshot.organizationId,\n tenantId: snapshot.tenantId,\n scope: 'line',\n kind: 'return',\n rate: '0',\n amountNet: totalNet.toString(),\n amountGross: totalGross.toString(),\n currencyCode: order.currencyCode,\n metadata: { returnId, returnLineId: lineSnapshot.id },\n position: positionStart + index,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n createdAdjustments.push(adjustment)\n em.persist(adjustment)\n\n line.returnedQuantity = (toNumeric(line.returnedQuantity) + lineSnapshot.quantityReturned).toString()\n line.updatedAt = new Date()\n em.persist(line)\n })\n\n const lineSnapshots: SalesLineSnapshot[] = orderLines.map(mapOrderLineEntityToSnapshot)\n const adjustmentDrafts: SalesAdjustmentDraft[] = [...existingAdjustments, ...createdAdjustments].map(\n mapOrderAdjustmentToDraft,\n )\n const calculation = await salesCalculationService.calculateDocumentTotals({\n documentKind: 'order',\n lines: lineSnapshots,\n adjustments: adjustmentDrafts,\n context: buildCalculationContext(order),\n existingTotals: resolveExistingPaymentTotals(order),\n ...resolveOrderCalculationMode(order),\n })\n applyOrderTotalsUnlessExternal(order, calculation)\n order.updatedAt = new Date()\n em.persist(order)\n },\n ],\n { transaction: true },\n )\n\n return createdLines\n}\n\nfunction normalizeLinesInput(lines: ReturnCreateInput['lines']): ReturnLineInput[] {\n const seen = new Set<string>()\n const result: ReturnLineInput[] = []\n for (const line of lines) {\n const orderLineId = line.orderLineId\n if (!orderLineId || seen.has(orderLineId)) continue\n const quantity = toNumeric(line.quantity)\n if (!Number.isFinite(quantity) || quantity <= 0) continue\n seen.add(orderLineId)\n result.push({ orderLineId, quantity })\n }\n return result\n}\n\nconst createReturnCommand: CommandHandler<ReturnCreateInput, { returnId: string }> = {\n id: 'sales.returns.create',\n async execute(rawInput, ctx) {\n const input = returnCreateSchema.parse(rawInput ?? {})\n ensureTenantScope(ctx, input.tenantId)\n ensureOrganizationScope(ctx, input.organizationId)\n\n const { translate } = await resolveTranslations()\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n\n const requested = normalizeLinesInput(input.lines)\n if (!requested.length) {\n throw new CrudHttpError(400, { error: translate('sales.returns.linesRequired', 'Select at least one line to return.') })\n }\n\n const salesCalculationService = ctx.container.resolve<SalesCalculationService>('salesCalculationService')\n const { header, createdLines, order } = await em.transactional(async (tx) => {\n const order = await findOneWithDecryption(\n tx,\n SalesOrder,\n { id: input.orderId, deletedAt: null },\n {},\n { tenantId: input.tenantId, organizationId: input.organizationId },\n )\n if (!order) {\n throw notFound(translate('sales.returns.orderMissing', 'Order not found.'))\n }\n ensureSameScope(order, input.organizationId, input.tenantId)\n await enforceSalesDocumentOptimisticLock(ctx, order, SALES_RESOURCE_KIND_ORDER)\n\n const orderLines = await findWithDecryption(\n tx,\n SalesOrderLine,\n { order: order.id, deletedAt: null },\n { lockMode: LockMode.PESSIMISTIC_WRITE },\n { tenantId: input.tenantId, organizationId: input.organizationId },\n )\n const lineMap = new Map(orderLines.map((line) => [line.id, line]))\n\n const shippedByLine = await loadShippedQuantityByLine(tx, order.id, {\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n })\n\n requested.forEach(({ orderLineId, quantity }) => {\n const line = lineMap.get(orderLineId)\n if (!line) {\n throw notFound(translate('sales.returns.lineMissing', 'Order line not found.'))\n }\n const available = computeAvailableReturnQuantity({\n quantity: toNumeric(line.quantity),\n returnedQuantity: toNumeric(line.returnedQuantity),\n shippedQuantity: shippedByLine.get(orderLineId) ?? 0,\n })\n if (quantity - 1e-6 > available) {\n throw new CrudHttpError(400, { error: translate('sales.returns.quantityExceedsShipped', 'Cannot return more than the shipped quantity. Ship the items before recording a return.') })\n }\n })\n\n const existingAdjustments = await findWithDecryption(\n tx,\n SalesOrderAdjustment,\n { order: order.id, deletedAt: null },\n { orderBy: { position: 'asc' } },\n { tenantId: input.tenantId, organizationId: input.organizationId },\n )\n const positionStart = existingAdjustments.reduce((acc, adj) => Math.max(acc, adj.position ?? 0), 0) + 1\n\n const numberGenerator = new SalesDocumentNumberGenerator(tx)\n const generated = await numberGenerator.generate({\n kind: 'return',\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n })\n const returnId = randomUUID()\n const entity = tx.create(SalesReturn, {\n id: returnId,\n order,\n organizationId: input.organizationId,\n tenantId: input.tenantId,\n returnNumber: generated.number,\n reason: input.reason ?? null,\n notes: input.notes ?? null,\n returnedAt: input.returnedAt ?? new Date(),\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n tx.persist(entity)\n\n const createdAdjustments: SalesOrderAdjustment[] = []\n const createdReturnLines: SalesReturnLine[] = []\n requested.forEach((lineInput, index) => {\n const line = lineMap.get(lineInput.orderLineId)\n if (!line) return\n const quantity = lineInput.quantity\n const lineQuantity = Math.max(toNumeric(line.quantity), 0)\n // `total_net_amount = 0` while `total_gross_amount > 0` is not a representable\n // priced state (gross = net * (1 + taxRate) \u21D2 net = 0 \u21D2 gross = 0). When a line\n // carries a positive gross but a zeroed/missing net, reconstruct the net from the\n // line's gross and tax rate so the return credits both sides and the order's net\n // grand total moves in lockstep with gross (#3036). A genuinely free line\n // (gross = 0, e.g. a 100% discount / comp) keeps net 0, so the return is not\n // over-credited at the discount-ignoring unit price (#3521).\n const lineTotalNet = deriveLineNetFromGross(line.totalNetAmount, line.totalGrossAmount, line.taxRate)\n const unitNet = lineQuantity > 0 ? lineTotalNet / lineQuantity : toNumeric(line.unitPriceNet)\n const unitGross = lineQuantity > 0 ? toNumeric(line.totalGrossAmount) / lineQuantity : toNumeric(line.unitPriceGross)\n const totalNet = -round(Math.max(unitNet, 0) * quantity)\n const totalGross = -round(Math.max(unitGross, 0) * quantity)\n\n const returnLineId = randomUUID()\n const returnLine = tx.create(SalesReturnLine, {\n id: returnLineId,\n salesReturn: entity,\n orderLine: tx.getReference(SalesOrderLine, line.id),\n organizationId: input.organizationId,\n tenantId: input.tenantId,\n quantityReturned: quantity.toString(),\n unitPriceNet: round(unitNet).toString(),\n unitPriceGross: round(unitGross).toString(),\n totalNetAmount: totalNet.toString(),\n totalGrossAmount: totalGross.toString(),\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n createdReturnLines.push(returnLine)\n tx.persist(returnLine)\n\n const adjustment = tx.create(SalesOrderAdjustment, {\n id: randomUUID(),\n order,\n orderLine: tx.getReference(SalesOrderLine, line.id),\n organizationId: input.organizationId,\n tenantId: input.tenantId,\n scope: 'line',\n kind: 'return',\n rate: '0',\n amountNet: totalNet.toString(),\n amountGross: totalGross.toString(),\n currencyCode: order.currencyCode,\n metadata: { returnId, returnLineId },\n position: positionStart + index,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n createdAdjustments.push(adjustment)\n tx.persist(adjustment)\n\n line.returnedQuantity = (toNumeric(line.returnedQuantity) + quantity).toString()\n line.updatedAt = new Date()\n tx.persist(line)\n })\n\n const lineSnapshots: SalesLineSnapshot[] = orderLines.map(mapOrderLineEntityToSnapshot)\n const adjustmentDrafts: SalesAdjustmentDraft[] = [...existingAdjustments, ...createdAdjustments].map(mapOrderAdjustmentToDraft)\n const calculation = await salesCalculationService.calculateDocumentTotals({\n documentKind: 'order',\n lines: lineSnapshots,\n adjustments: adjustmentDrafts,\n context: buildCalculationContext(order),\n existingTotals: resolveExistingPaymentTotals(order),\n ...resolveOrderCalculationMode(order),\n })\n applyOrderTotalsUnlessExternal(order, calculation)\n order.updatedAt = new Date()\n tx.persist(order)\n\n await tx.flush()\n\n return { header: entity, createdLines: createdReturnLines, order }\n })\n\n await invalidateOrderCache(ctx.container, order, ctx.auth?.tenantId ?? null)\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({\n dataEngine,\n action: 'created',\n entity: header,\n identifiers: { id: header.id, organizationId: header.organizationId, tenantId: header.tenantId },\n indexer: { entityType: E.sales.sales_return },\n events: returnCrudEvents,\n })\n\n if (createdLines.length) {\n await Promise.all(\n createdLines.map((line) =>\n emitCrudSideEffects({\n dataEngine,\n action: 'created',\n entity: line,\n identifiers: { id: line.id, organizationId: line.organizationId, tenantId: line.tenantId },\n indexer: { entityType: E.sales.sales_return_line },\n }),\n ),\n )\n }\n\n return { returnId: header.id }\n },\n captureAfter: async (_input, result, ctx) => {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n return loadReturnSnapshot(em, result.returnId)\n },\n buildLog: async ({ result, snapshots }) => {\n const after = snapshots.after as ReturnSnapshot | undefined\n if (!after) return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('sales.audit.returns.create', 'Create return'),\n resourceKind: 'sales.return',\n resourceId: result.returnId,\n parentResourceKind: 'sales.order',\n parentResourceId: after.orderId ?? null,\n tenantId: after.tenantId,\n organizationId: after.organizationId,\n snapshotAfter: after,\n payload: {\n undo: { after } satisfies ReturnUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<ReturnUndoPayload>(logEntry)\n const after = payload?.after\n if (!after) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const salesCalculationService = ctx.container.resolve<SalesCalculationService>('salesCalculationService')\n await reverseReturnEffects(em, salesCalculationService, after)\n await invalidateOrderCache(ctx.container, {\n id: after.orderId,\n organizationId: after.organizationId,\n tenantId: after.tenantId,\n }, ctx.auth?.tenantId ?? null)\n },\n redo: async ({ ctx, logEntry }) => {\n const after = resolveRedoSnapshot<ReturnSnapshot>(logEntry)\n if (!after || !after.id) {\n throw new CrudHttpError(400, { error: '[internal] redo snapshot unavailable for sales.returns.create' })\n }\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const salesCalculationService = ctx.container.resolve<SalesCalculationService>('salesCalculationService')\n\n const createdLines = await restoreReturnEffects(em, salesCalculationService, after)\n\n const header = await findOneWithDecryption(\n em,\n SalesReturn,\n { id: after.id, deletedAt: null },\n {},\n { tenantId: after.tenantId, organizationId: after.organizationId },\n )\n if (!header) {\n const { translate } = await resolveTranslations()\n throw notFound(translate('sales.returns.orderMissing', 'Order not found.'))\n }\n\n await invalidateOrderCache(ctx.container, {\n id: after.orderId,\n organizationId: after.organizationId,\n tenantId: after.tenantId,\n }, ctx.auth?.tenantId ?? null)\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({\n dataEngine,\n action: 'created',\n entity: header,\n identifiers: { id: header.id, organizationId: header.organizationId, tenantId: header.tenantId },\n indexer: { entityType: E.sales.sales_return },\n events: returnCrudEvents,\n })\n\n if (createdLines.length) {\n await Promise.all(\n createdLines.map((line) =>\n emitCrudSideEffects({\n dataEngine,\n action: 'created',\n entity: line,\n identifiers: { id: line.id, organizationId: line.organizationId, tenantId: line.tenantId },\n indexer: { entityType: E.sales.sales_return_line },\n }),\n ),\n )\n }\n\n return { returnId: header.id }\n },\n}\n\nconst updateReturnCommand: CommandHandler<ReturnUpdateInput, { returnId: string }> = {\n id: 'sales.returns.update',\n async prepare(rawInput, ctx) {\n const parsed = returnUpdateSchema.parse(rawInput ?? {})\n const em = ctx.container.resolve('em') as EntityManager\n const snapshot = await loadReturnHeaderSnapshot(em, parsed.id)\n if (snapshot) {\n ensureTenantScope(ctx, snapshot.tenantId)\n ensureOrganizationScope(ctx, snapshot.organizationId)\n }\n return snapshot ? { before: snapshot } : {}\n },\n async execute(rawInput, ctx) {\n const input = returnUpdateSchema.parse(rawInput ?? {})\n ensureTenantScope(ctx, input.tenantId)\n ensureOrganizationScope(ctx, input.organizationId)\n const { translate } = await resolveTranslations()\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n\n const header = await em.transactional(async (tx) => {\n const entity = await findOneWithDecryption(\n tx,\n SalesReturn,\n { id: input.id, deletedAt: null },\n { populate: ['order'] },\n { tenantId: input.tenantId, organizationId: input.organizationId },\n )\n if (!entity || !entity.order) {\n throw notFound(translate('sales.returns.notFound', 'Return not found.'))\n }\n ensureSameScope(entity, input.organizationId, input.tenantId)\n const orderId = typeof entity.order === 'string' ? entity.order : entity.order.id\n if (input.orderId !== orderId) {\n throw new CrudHttpError(400, { error: translate('sales.returns.orderMismatch', 'Return does not belong to this order.') })\n }\n // Lock on the return's own version \u2014 editing header fields (reason / notes /\n // returnedAt) only touches the return, not the order totals.\n await enforceSalesDocumentOptimisticLock(ctx, entity, SALES_RESOURCE_KIND_RETURN)\n\n if (input.reason !== undefined) entity.reason = input.reason.length ? input.reason : null\n if (input.notes !== undefined) entity.notes = input.notes.length ? input.notes : null\n if (input.returnedAt !== undefined) entity.returnedAt = input.returnedAt ?? null\n entity.updatedAt = new Date()\n tx.persist(entity)\n await tx.flush()\n return entity\n })\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({\n dataEngine,\n action: 'updated',\n entity: header,\n identifiers: { id: header.id, organizationId: header.organizationId, tenantId: header.tenantId },\n indexer: { entityType: E.sales.sales_return },\n events: returnCrudEvents,\n })\n\n return { returnId: header.id }\n },\n captureAfter: async (_input, result, ctx) => {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n return loadReturnHeaderSnapshot(em, result.returnId)\n },\n buildLog: async ({ snapshots, result }) => {\n const { translate } = await resolveTranslations()\n const before = snapshots.before as ReturnHeaderSnapshot | undefined\n const after = snapshots.after as ReturnHeaderSnapshot | undefined\n return {\n actionLabel: translate('sales.audit.returns.update', 'Update return'),\n resourceKind: 'sales.return',\n resourceId: result.returnId,\n parentResourceKind: 'sales.order',\n parentResourceId: after?.orderId ?? before?.orderId ?? null,\n tenantId: after?.tenantId ?? before?.tenantId ?? null,\n organizationId: after?.organizationId ?? before?.organizationId ?? null,\n snapshotBefore: before ?? null,\n snapshotAfter: after ?? null,\n payload: {\n undo: { before, after } satisfies ReturnHeaderUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<ReturnHeaderUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n await em.transactional(async (tx) => {\n const entity = await findOneWithDecryption(\n tx,\n SalesReturn,\n { id: before.id, deletedAt: null },\n {},\n { tenantId: before.tenantId, organizationId: before.organizationId },\n )\n if (!entity) return\n entity.reason = before.reason\n entity.notes = before.notes\n entity.returnedAt = before.returnedAt ? new Date(before.returnedAt) : null\n entity.updatedAt = new Date()\n tx.persist(entity)\n await tx.flush()\n })\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n const restored = await findOneWithDecryption(\n em,\n SalesReturn,\n { id: before.id, deletedAt: null },\n {},\n { tenantId: before.tenantId, organizationId: before.organizationId },\n )\n if (restored) {\n await emitCrudSideEffects({\n dataEngine,\n action: 'updated',\n entity: restored,\n identifiers: { id: restored.id, organizationId: restored.organizationId, tenantId: restored.tenantId },\n indexer: { entityType: E.sales.sales_return },\n events: returnCrudEvents,\n })\n }\n },\n}\n\nconst deleteReturnCommand: CommandHandler<ReturnDeleteInput, { returnId: string }> = {\n id: 'sales.returns.delete',\n async prepare(rawInput, ctx) {\n const parsed = returnDeleteSchema.parse(rawInput ?? {})\n const em = ctx.container.resolve('em') as EntityManager\n const snapshot = await loadReturnSnapshot(em, parsed.id)\n if (snapshot) {\n ensureTenantScope(ctx, snapshot.tenantId)\n ensureOrganizationScope(ctx, snapshot.organizationId)\n }\n return snapshot ? { before: snapshot } : {}\n },\n async execute(rawInput, ctx) {\n const input = returnDeleteSchema.parse(rawInput ?? {})\n ensureTenantScope(ctx, input.tenantId)\n ensureOrganizationScope(ctx, input.organizationId)\n const { translate } = await resolveTranslations()\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const salesCalculationService = ctx.container.resolve<SalesCalculationService>('salesCalculationService')\n\n const snapshot = await loadReturnSnapshot(em, input.id)\n if (!snapshot) {\n throw notFound(translate('sales.returns.notFound', 'Return not found.'))\n }\n ensureSameScope(snapshot, input.organizationId, input.tenantId)\n if (input.orderId !== snapshot.orderId) {\n throw new CrudHttpError(400, { error: translate('sales.returns.orderMismatch', 'Return does not belong to this order.') })\n }\n\n const header = await findOneWithDecryption(\n em,\n SalesReturn,\n { id: input.id, deletedAt: null },\n {},\n { tenantId: input.tenantId, organizationId: input.organizationId },\n )\n if (!header) {\n throw notFound(translate('sales.returns.notFound', 'Return not found.'))\n }\n ensureSameScope(header, input.organizationId, input.tenantId)\n // Lock on the return's own version, captured before any mutation.\n await enforceSalesDocumentOptimisticLock(ctx, header, SALES_RESOURCE_KIND_RETURN)\n\n await reverseReturnEffects(em, salesCalculationService, snapshot)\n await invalidateOrderCache(ctx.container, {\n id: snapshot.orderId,\n organizationId: snapshot.organizationId,\n tenantId: snapshot.tenantId,\n }, ctx.auth?.tenantId ?? null)\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({\n dataEngine,\n action: 'deleted',\n entity: header,\n identifiers: { id: snapshot.id, organizationId: snapshot.organizationId, tenantId: snapshot.tenantId },\n indexer: { entityType: E.sales.sales_return },\n events: returnCrudEvents,\n })\n\n if (snapshot.lines.length) {\n await Promise.all(\n snapshot.lines.map((line) =>\n emitCrudSideEffects({\n dataEngine,\n action: 'deleted',\n entity: { id: line.id, organizationId: snapshot.organizationId, tenantId: snapshot.tenantId },\n identifiers: { id: line.id, organizationId: snapshot.organizationId, tenantId: snapshot.tenantId },\n indexer: { entityType: E.sales.sales_return_line },\n }),\n ),\n )\n }\n\n return { returnId: snapshot.id }\n },\n buildLog: async ({ snapshots, result }) => {\n const before = snapshots.before as ReturnSnapshot | undefined\n if (!before) return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('sales.audit.returns.delete', 'Delete return'),\n resourceKind: 'sales.return',\n resourceId: result.returnId,\n parentResourceKind: 'sales.order',\n parentResourceId: before.orderId ?? null,\n tenantId: before.tenantId,\n organizationId: before.organizationId,\n snapshotBefore: before,\n payload: {\n undo: { before } satisfies ReturnDeleteUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<ReturnDeleteUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const salesCalculationService = ctx.container.resolve<SalesCalculationService>('salesCalculationService')\n\n const createdLines = await restoreReturnEffects(em, salesCalculationService, before)\n\n const header = await findOneWithDecryption(\n em,\n SalesReturn,\n { id: before.id, deletedAt: null },\n {},\n { tenantId: before.tenantId, organizationId: before.organizationId },\n )\n if (!header) return\n\n await invalidateOrderCache(ctx.container, {\n id: before.orderId,\n organizationId: before.organizationId,\n tenantId: before.tenantId,\n }, ctx.auth?.tenantId ?? null)\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({\n dataEngine,\n action: 'created',\n entity: header,\n identifiers: { id: header.id, organizationId: header.organizationId, tenantId: header.tenantId },\n indexer: { entityType: E.sales.sales_return },\n events: returnCrudEvents,\n })\n\n if (createdLines.length) {\n await Promise.all(\n createdLines.map((line) =>\n emitCrudSideEffects({\n dataEngine,\n action: 'created',\n entity: line,\n identifiers: { id: line.id, organizationId: line.organizationId, tenantId: line.tenantId },\n indexer: { entityType: E.sales.sales_return_line },\n }),\n ),\n )\n }\n },\n}\n\nregisterCommand(createReturnCommand)\nregisterCommand(updateReturnCommand)\nregisterCommand(deleteReturnCommand)\n\nexport const returnCommands = [createReturnCommand, updateReturnCommand, deleteReturnCommand]\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import { randomUUID } from 'crypto'\nimport { registerCommand, type CommandHandler } from '@open-mercato/shared/lib/commands'\nimport { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'\nimport { LockMode } from '@mikro-orm/core'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { CrudHttpError, notFound } from '@open-mercato/shared/lib/crud/errors'\nimport { invalidateCrudCache } from '@open-mercato/shared/lib/crud/cache'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { emitCrudSideEffects } from '@open-mercato/shared/lib/commands/helpers'\nimport type { CrudEventsConfig } from '@open-mercato/shared/lib/crud/types'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { SalesDocumentNumberGenerator } from '../services/salesDocumentNumberGenerator'\nimport type { SalesCalculationService } from '../services/salesCalculationService'\nimport type { SalesAdjustmentDraft, SalesLineSnapshot, SalesDocumentCalculationResult } from '../lib/types'\nimport { isExternalAmountsMode } from '../lib/calculations'\nimport { readPersistedHeaderTotals } from '../lib/externalAmounts'\nimport { cloneJson, deriveLineNetFromGross, ensureOrganizationScope, ensureSameScope, ensureTenantScope, extractUndoPayload, toNumericString, enforceSalesDocumentOptimisticLock, lockSalesDocumentRow, SALES_RESOURCE_KIND_ORDER, SALES_RESOURCE_KIND_RETURN } from './shared'\nimport { resolveRedoSnapshot } from '@open-mercato/shared/lib/commands/redo'\nimport { SalesOrder, SalesOrderAdjustment, SalesOrderLine, SalesReturn, SalesReturnLine } from '../data/entities'\nimport { mapOrderLineEntityToSnapshot } from '../lib/lineSnapshots'\nimport { loadShippedQuantityByLine } from '../lib/shipments/snapshots'\nimport { computeAvailableReturnQuantity } from '../lib/returnQuantity'\nimport {\n returnCreateSchema,\n returnUpdateSchema,\n returnDeleteSchema,\n type ReturnCreateInput,\n type ReturnUpdateInput,\n type ReturnDeleteInput,\n} from '../data/validators'\nimport { E } from '#generated/entities.ids.generated'\n\ntype ReturnLineInput = { orderLineId: string; quantity: number }\n\ntype ReturnSnapshot = {\n id: string\n orderId: string\n organizationId: string\n tenantId: string\n returnNumber: string\n returnedAt: string | null\n reason: string | null\n notes: string | null\n lines: Array<{\n id: string\n orderLineId: string\n quantityReturned: number\n unitPriceNet: number\n unitPriceGross: number\n totalNetAmount: number\n totalGrossAmount: number\n }>\n adjustmentIds: string[]\n}\n\ntype ReturnUndoPayload = {\n after?: ReturnSnapshot | null\n}\n\nconst returnCrudEvents: CrudEventsConfig = {\n module: 'sales',\n entity: 'return',\n persistent: true,\n buildPayload: (ctx) => ({\n id: ctx.identifiers.id,\n organizationId: ctx.identifiers.organizationId,\n tenantId: ctx.identifiers.tenantId,\n }),\n}\n\ntype OrderCacheRecord = Pick<SalesOrder, 'id' | 'organizationId' | 'tenantId'>\n\n/**\n * Return mutations update the order aggregate, which is also the cache resource\n * used by the order-lines and order-adjustments routes. Invalidate it after\n * each committed return lifecycle change so reloads receive its fresh\n * `updatedAt` optimistic-lock token.\n */\nasync function invalidateOrderCache(\n container: Parameters<typeof invalidateCrudCache>[0],\n order: OrderCacheRecord,\n fallbackTenant: string | null,\n): Promise<void> {\n await invalidateCrudCache(\n container,\n SALES_RESOURCE_KIND_ORDER,\n { id: order.id, organizationId: order.organizationId, tenantId: order.tenantId },\n fallbackTenant,\n 'updated',\n )\n}\n\nfunction toNumeric(value: unknown): number {\n if (typeof value === 'number' && Number.isFinite(value)) return value\n if (typeof value === 'string' && value.trim().length) {\n const parsed = Number(value)\n if (Number.isFinite(parsed)) return parsed\n }\n return 0\n}\n\nfunction round(value: number): number {\n return Math.round((value + Number.EPSILON) * 1e4) / 1e4\n}\n\n/**\n * Payment totals live on the order, not in the line/adjustment math a return\n * recalculates. Every `calculateDocumentTotals` call that writes order totals\n * back to the order MUST seed these so `buildBaseDocumentResult` preserves the\n * recorded `paidTotalAmount` / `refundedTotalAmount` instead of defaulting them\n * to 0 \u2014 otherwise creating/undoing/redoing a return silently zeroes the paid\n * amount and the order's outstanding balance goes wrong on any paid order\n * (#3756). Mirrors `resolveExistingPaymentTotals` in `commands/documents.ts`.\n */\nfunction resolveExistingPaymentTotals(order: SalesOrder): { paidTotalAmount: number; refundedTotalAmount: number } {\n return {\n paidTotalAmount: toNumeric(order.paidTotalAmount),\n refundedTotalAmount: toNumeric(order.refundedTotalAmount),\n }\n}\n\nfunction applyOrderTotals(order: SalesOrder, totals: SalesDocumentCalculationResult['totals'], lineCount: number): void {\n order.subtotalNetAmount = toNumericString(totals.subtotalNetAmount) ?? '0'\n order.subtotalGrossAmount = toNumericString(totals.subtotalGrossAmount) ?? '0'\n order.discountTotalAmount = toNumericString(totals.discountTotalAmount) ?? '0'\n order.taxTotalAmount = toNumericString(totals.taxTotalAmount) ?? '0'\n order.shippingNetAmount = toNumericString(totals.shippingNetAmount) ?? '0'\n order.shippingGrossAmount = toNumericString(totals.shippingGrossAmount) ?? '0'\n order.surchargeTotalAmount = toNumericString(totals.surchargeTotalAmount) ?? '0'\n order.grandTotalNetAmount = toNumericString(totals.grandTotalNetAmount) ?? '0'\n order.grandTotalGrossAmount = toNumericString(totals.grandTotalGrossAmount) ?? '0'\n order.paidTotalAmount = toNumericString(totals.paidTotalAmount) ?? '0'\n order.refundedTotalAmount = toNumericString(totals.refundedTotalAmount) ?? '0'\n order.outstandingAmount = toNumericString(totals.outstandingAmount) ?? '0'\n order.totalsSnapshot = cloneJson(totals)\n order.lineItemCount = lineCount\n}\n\n// Deliberate, not an omission: an external order's header belongs to the source\n// system, which issues its own credit document. The return itself is still recorded.\nfunction applyOrderTotalsUnlessExternal(\n order: SalesOrder,\n calculation: SalesDocumentCalculationResult,\n): void {\n if (isExternalAmountsMode(order.totalsMode ?? 'computed')) return\n applyOrderTotals(order, calculation.totals, calculation.lines.length)\n}\n\nfunction resolveOrderCalculationMode(order: SalesOrder) {\n const totalsMode = order.totalsMode ?? 'computed'\n return {\n totalsMode,\n suppliedTotals: isExternalAmountsMode(totalsMode) ? readPersistedHeaderTotals(order) : null,\n }\n}\n\nfunction mapOrderAdjustmentToDraft(adjustment: SalesOrderAdjustment): SalesAdjustmentDraft {\n return {\n id: adjustment.id,\n scope: adjustment.scope ?? 'order',\n kind: adjustment.kind,\n code: adjustment.code ?? null,\n label: adjustment.label ?? null,\n calculatorKey: adjustment.calculatorKey ?? null,\n promotionId: adjustment.promotionId ?? null,\n rate: toNumeric(adjustment.rate),\n amountNet: toNumeric(adjustment.amountNet),\n amountGross: toNumeric(adjustment.amountGross),\n currencyCode: adjustment.currencyCode ?? null,\n metadata: adjustment.metadata ? cloneJson(adjustment.metadata) : null,\n position: adjustment.position ?? 0,\n }\n}\n\nfunction buildCalculationContext(order: SalesOrder) {\n return {\n tenantId: order.tenantId,\n organizationId: order.organizationId,\n currencyCode: order.currencyCode,\n metadata: {\n shippingMethod: order.shippingMethodSnapshot\n ? cloneJson(order.shippingMethodSnapshot as Record<string, unknown>)\n : null,\n paymentMethod: order.paymentMethodSnapshot ? cloneJson(order.paymentMethodSnapshot as Record<string, unknown>) : null,\n },\n }\n}\n\n/**\n * Recalculates order totals (including line-scoped return adjustments) from lines and\n * adjustments. Do not merge this into GET /api/sales/orders responses: provider\n * calculators re-emit shipping/payment fees on top of already-persisted adjustments\n * and make list vs detail totals diverge (#5438). Persist via commands instead.\n */\nexport async function recalculateOrderTotalsForDisplay(\n em: EntityManager,\n container: { resolve: (key: string) => unknown },\n orderId: string,\n scope: { tenantId: string; organizationId: string },\n): Promise<SalesDocumentCalculationResult['totals'] | null> {\n const order = await findOneWithDecryption(\n em,\n SalesOrder,\n { id: orderId, deletedAt: null },\n {},\n scope,\n )\n if (!order) return null\n const [orderLines, adjustments] = await Promise.all([\n findWithDecryption(em, SalesOrderLine, { order: order.id, deletedAt: null }, {}, scope),\n findWithDecryption(\n em,\n SalesOrderAdjustment,\n { order: order.id, deletedAt: null },\n { orderBy: { position: 'asc' } },\n scope,\n ),\n ])\n const lineSnapshots: SalesLineSnapshot[] = orderLines.map(mapOrderLineEntityToSnapshot)\n const adjustmentDrafts: SalesAdjustmentDraft[] = adjustments.map(mapOrderAdjustmentToDraft)\n const salesCalculationService = container.resolve('salesCalculationService') as SalesCalculationService\n const calculation = await salesCalculationService.calculateDocumentTotals({\n documentKind: 'order',\n lines: lineSnapshots,\n adjustments: adjustmentDrafts,\n context: buildCalculationContext(order),\n existingTotals: resolveExistingPaymentTotals(order),\n ...resolveOrderCalculationMode(order),\n })\n return calculation.totals\n}\n\nexport async function loadReturnSnapshot(em: EntityManager, id: string): Promise<ReturnSnapshot | null> {\n const header = await findOneWithDecryption(\n em,\n SalesReturn,\n { id, deletedAt: null },\n { populate: ['order'] },\n {},\n )\n if (!header || !header.order) return null\n const orderId = typeof header.order === 'string' ? header.order : header.order.id\n const lines = await findWithDecryption(\n em,\n SalesReturnLine,\n { salesReturn: header.id, deletedAt: null },\n { populate: ['orderLine'] },\n { tenantId: header.tenantId, organizationId: header.organizationId },\n )\n const adjustmentIds: string[] = []\n const adjustments = await findWithDecryption(\n em,\n SalesOrderAdjustment,\n { order: orderId, kind: 'return', deletedAt: null },\n {},\n { tenantId: header.tenantId, organizationId: header.organizationId },\n )\n adjustments.forEach((adj) => {\n const meta = adj.metadata as Record<string, unknown> | null | undefined\n if (meta && meta.returnId === header.id) adjustmentIds.push(adj.id)\n })\n\n return {\n id: header.id,\n orderId,\n organizationId: header.organizationId,\n tenantId: header.tenantId,\n returnNumber: header.returnNumber,\n returnedAt: header.returnedAt ? header.returnedAt.toISOString() : null,\n reason: header.reason ?? null,\n notes: header.notes ?? null,\n lines: lines.map((line) => ({\n id: line.id,\n orderLineId: typeof line.orderLine === 'string' ? line.orderLine : line.orderLine?.id ?? null,\n quantityReturned: toNumeric(line.quantityReturned),\n unitPriceNet: toNumeric(line.unitPriceNet),\n unitPriceGross: toNumeric(line.unitPriceGross),\n totalNetAmount: toNumeric(line.totalNetAmount),\n totalGrossAmount: toNumeric(line.totalGrossAmount),\n })),\n adjustmentIds,\n }\n}\n\ntype ReturnHeaderSnapshot = {\n id: string\n orderId: string\n organizationId: string\n tenantId: string\n reason: string | null\n notes: string | null\n returnedAt: string | null\n}\n\ntype ReturnHeaderUndoPayload = {\n before?: ReturnHeaderSnapshot | null\n after?: ReturnHeaderSnapshot | null\n}\n\ntype ReturnDeleteUndoPayload = {\n before?: ReturnSnapshot | null\n}\n\nasync function loadReturnHeaderSnapshot(em: EntityManager, id: string): Promise<ReturnHeaderSnapshot | null> {\n const header = await findOneWithDecryption(em, SalesReturn, { id, deletedAt: null }, { populate: ['order'] }, {})\n if (!header || !header.order) return null\n const orderId = typeof header.order === 'string' ? header.order : header.order.id\n return {\n id: header.id,\n orderId,\n organizationId: header.organizationId,\n tenantId: header.tenantId,\n reason: header.reason ?? null,\n notes: header.notes ?? null,\n returnedAt: header.returnedAt ? header.returnedAt.toISOString() : null,\n }\n}\n\nasync function lockReturnOrder(\n em: EntityManager,\n orderId: string,\n scope: { tenantId: string; organizationId: string },\n): Promise<SalesOrder | null> {\n if (!(await lockSalesDocumentRow(em, 'sales_orders', orderId, scope))) return null\n return findOneWithDecryption(\n em,\n SalesOrder,\n { id: orderId, deletedAt: null, ...scope },\n { refresh: true },\n scope,\n )\n}\n\n/**\n * Reverse the order-level effects of a return: restore each order line's\n * `returnedQuantity`, drop the return's line-scoped credit adjustments, remove\n * the return header + lines, and recalculate the order totals. Shared by the\n * create command's undo and the delete command's execute \u2014 both need the exact\n * same teardown. No-op when the order is gone.\n *\n * The line reversals, adjustment/return removals, and the order-total recompute\n * interleave queries on the same EntityManager with scalar mutations, so they\n * run inside an atomic flush to avoid lost updates and partial commits\n * (SPEC-018): the per-phase flush boundary persists the line `returnedQuantity`\n * reversals before the adjustment/header/return-line lookups in the next phase\n * run any query, which under MikroORM v7 would otherwise silently discard the\n * pending scalar changes on the managed lines.\n */\nasync function reverseReturnEffects(\n em: EntityManager,\n salesCalculationService: SalesCalculationService,\n snapshot: ReturnSnapshot,\n): Promise<void> {\n let order: SalesOrder | null = null\n let lines: SalesOrderLine[] = []\n await withAtomicFlush(\n em,\n [\n async () => {\n order = await lockReturnOrder(em, snapshot.orderId, {\n tenantId: snapshot.tenantId,\n organizationId: snapshot.organizationId,\n })\n if (!order) return\n lines = await findWithDecryption(\n em,\n SalesOrderLine,\n { order: order.id, deletedAt: null },\n {},\n { tenantId: snapshot.tenantId, organizationId: snapshot.organizationId },\n )\n const lineMap = new Map(lines.map((line) => [line.id, line]))\n snapshot.lines.forEach((entry) => {\n const line = lineMap.get(entry.orderLineId)\n if (!line) return\n const next = Math.max(0, toNumeric(line.returnedQuantity) - entry.quantityReturned)\n line.returnedQuantity = next.toString()\n line.updatedAt = new Date()\n em.persist(line)\n })\n },\n async () => {\n if (!order) return\n if (snapshot.adjustmentIds.length) {\n const adjustments = await findWithDecryption(\n em,\n SalesOrderAdjustment,\n { id: { $in: snapshot.adjustmentIds }, deletedAt: null },\n {},\n { tenantId: snapshot.tenantId, organizationId: snapshot.organizationId },\n )\n adjustments.forEach((adj) => em.remove(adj))\n }\n\n const header = await findOneWithDecryption(\n em,\n SalesReturn,\n { id: snapshot.id, deletedAt: null },\n {},\n { tenantId: snapshot.tenantId, organizationId: snapshot.organizationId },\n )\n const returnLines = await findWithDecryption(\n em,\n SalesReturnLine,\n { salesReturn: snapshot.id, deletedAt: null },\n {},\n { tenantId: snapshot.tenantId, organizationId: snapshot.organizationId },\n )\n returnLines.forEach((line) => em.remove(line))\n if (header) em.remove(header)\n\n const existingAdjustments = await findWithDecryption(\n em,\n SalesOrderAdjustment,\n { order: order.id, deletedAt: null },\n { orderBy: { position: 'asc' } },\n { tenantId: snapshot.tenantId, organizationId: snapshot.organizationId },\n )\n const lineSnapshots: SalesLineSnapshot[] = lines.map(mapOrderLineEntityToSnapshot)\n const adjustmentDrafts: SalesAdjustmentDraft[] = existingAdjustments.map(mapOrderAdjustmentToDraft)\n const calculation = await salesCalculationService.calculateDocumentTotals({\n documentKind: 'order',\n lines: lineSnapshots,\n adjustments: adjustmentDrafts,\n context: buildCalculationContext(order),\n existingTotals: resolveExistingPaymentTotals(order),\n ...resolveOrderCalculationMode(order),\n })\n applyOrderTotalsUnlessExternal(order, calculation)\n order.updatedAt = new Date()\n em.persist(order)\n },\n ],\n { transaction: true },\n )\n}\n\n/**\n * Re-apply a return from a snapshot: recreate the return header + lines and the\n * line-scoped credit adjustments, bump each order line's `returnedQuantity`, and\n * recalculate the order totals. Shared by the create command's redo and the\n * delete command's undo. Returns the recreated return lines so callers can emit\n * index side effects. Throws a 404 when the order is gone.\n *\n * The order may have changed since the snapshot was taken (another return\n * consumed the shipped quantity, a shipment was removed, an order line was\n * deleted), so the snapshot quantities are re-checked against the currently\n * returnable quantity \u2014 the same rule `sales.returns.create` enforces \u2014 under\n * the order-line lock and before any write. A stale restore is refused with a\n * 409 instead of returning and crediting the same shipped units twice.\n */\nasync function restoreReturnEffects(\n em: EntityManager,\n salesCalculationService: SalesCalculationService,\n snapshot: ReturnSnapshot,\n): Promise<SalesReturnLine[]> {\n const returnId = snapshot.id\n const createdLines: SalesReturnLine[] = []\n const { translate } = await resolveTranslations()\n\n await withAtomicFlush(\n em,\n [\n async () => {\n const existingOrder = await findOneWithDecryption(\n em,\n SalesOrder,\n { id: snapshot.orderId, deletedAt: null },\n {},\n { tenantId: snapshot.tenantId, organizationId: snapshot.organizationId },\n )\n if (!existingOrder) {\n throw notFound(translate('sales.returns.orderMissing', 'Order not found.'))\n }\n ensureSameScope(existingOrder, snapshot.organizationId, snapshot.tenantId)\n const order = await lockReturnOrder(em, existingOrder.id, {\n tenantId: snapshot.tenantId,\n organizationId: snapshot.organizationId,\n })\n if (!order) {\n throw notFound(translate('sales.returns.orderMissing', 'Order not found.'))\n }\n\n const orderLines = await findWithDecryption(\n em,\n SalesOrderLine,\n { order: order.id, deletedAt: null },\n { lockMode: LockMode.PESSIMISTIC_WRITE },\n { tenantId: snapshot.tenantId, organizationId: snapshot.organizationId },\n )\n const lineMap = new Map(orderLines.map((line) => [line.id, line]))\n\n const shippedByLine = await loadShippedQuantityByLine(em, order.id, {\n tenantId: snapshot.tenantId,\n organizationId: snapshot.organizationId,\n })\n const restoredByLine = new Map<string, { line: SalesOrderLine; quantity: number }>()\n snapshot.lines.forEach((lineSnapshot) => {\n const line = lineSnapshot.orderLineId ? lineMap.get(lineSnapshot.orderLineId) : undefined\n if (!line) {\n throw new CrudHttpError(409, {\n error: translate('sales.returns.restoreLineMissing', 'This return cannot be restored: one of its order lines no longer exists.'),\n })\n }\n const restored = restoredByLine.get(line.id)\n restoredByLine.set(line.id, { line, quantity: (restored?.quantity ?? 0) + lineSnapshot.quantityReturned })\n })\n restoredByLine.forEach(({ line, quantity }, orderLineId) => {\n const available = computeAvailableReturnQuantity({\n quantity: toNumeric(line.quantity),\n returnedQuantity: toNumeric(line.returnedQuantity),\n shippedQuantity: shippedByLine.get(orderLineId) ?? 0,\n })\n if (quantity - 1e-6 > available) {\n throw new CrudHttpError(409, {\n error: translate('sales.returns.restoreQuantityUnavailable', 'This return cannot be restored: the order no longer has enough shipped quantity left to return.'),\n })\n }\n })\n\n const existingAdjustments = await findWithDecryption(\n em,\n SalesOrderAdjustment,\n { order: order.id, deletedAt: null },\n { orderBy: { position: 'asc' } },\n { tenantId: snapshot.tenantId, organizationId: snapshot.organizationId },\n )\n const positionStart = existingAdjustments.reduce((acc, adj) => Math.max(acc, adj.position ?? 0), 0) + 1\n\n const restoredHeader =\n (await findOneWithDecryption(\n em,\n SalesReturn,\n { id: snapshot.id },\n {},\n { tenantId: snapshot.tenantId, organizationId: snapshot.organizationId },\n )) ??\n em.create(SalesReturn, {\n id: snapshot.id,\n order,\n organizationId: snapshot.organizationId,\n tenantId: snapshot.tenantId,\n returnNumber: snapshot.returnNumber,\n reason: snapshot.reason ?? null,\n notes: snapshot.notes ?? null,\n returnedAt: snapshot.returnedAt ? new Date(snapshot.returnedAt) : new Date(),\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n restoredHeader.order = order\n restoredHeader.deletedAt = null\n restoredHeader.organizationId = snapshot.organizationId\n restoredHeader.tenantId = snapshot.tenantId\n restoredHeader.returnNumber = snapshot.returnNumber\n restoredHeader.reason = snapshot.reason ?? null\n restoredHeader.notes = snapshot.notes ?? null\n restoredHeader.returnedAt = snapshot.returnedAt ? new Date(snapshot.returnedAt) : new Date()\n restoredHeader.updatedAt = new Date()\n em.persist(restoredHeader)\n\n const createdAdjustments: SalesOrderAdjustment[] = []\n snapshot.lines.forEach((lineSnapshot, index) => {\n const line = lineMap.get(lineSnapshot.orderLineId)\n if (!line) return\n const totalNet = lineSnapshot.totalNetAmount\n const totalGross = lineSnapshot.totalGrossAmount\n const adjustmentId = snapshot.adjustmentIds[index] ?? randomUUID()\n\n const returnLine = em.create(SalesReturnLine, {\n id: lineSnapshot.id,\n salesReturn: restoredHeader,\n orderLine: em.getReference(SalesOrderLine, line.id),\n organizationId: snapshot.organizationId,\n tenantId: snapshot.tenantId,\n quantityReturned: lineSnapshot.quantityReturned.toString(),\n unitPriceNet: lineSnapshot.unitPriceNet.toString(),\n unitPriceGross: lineSnapshot.unitPriceGross.toString(),\n totalNetAmount: totalNet.toString(),\n totalGrossAmount: totalGross.toString(),\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n createdLines.push(returnLine)\n em.persist(returnLine)\n\n const adjustment = em.create(SalesOrderAdjustment, {\n id: adjustmentId,\n order,\n orderLine: em.getReference(SalesOrderLine, line.id),\n organizationId: snapshot.organizationId,\n tenantId: snapshot.tenantId,\n scope: 'line',\n kind: 'return',\n rate: '0',\n amountNet: totalNet.toString(),\n amountGross: totalGross.toString(),\n currencyCode: order.currencyCode,\n metadata: { returnId, returnLineId: lineSnapshot.id },\n position: positionStart + index,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n createdAdjustments.push(adjustment)\n em.persist(adjustment)\n\n line.returnedQuantity = (toNumeric(line.returnedQuantity) + lineSnapshot.quantityReturned).toString()\n line.updatedAt = new Date()\n em.persist(line)\n })\n\n const lineSnapshots: SalesLineSnapshot[] = orderLines.map(mapOrderLineEntityToSnapshot)\n const adjustmentDrafts: SalesAdjustmentDraft[] = [...existingAdjustments, ...createdAdjustments].map(\n mapOrderAdjustmentToDraft,\n )\n const calculation = await salesCalculationService.calculateDocumentTotals({\n documentKind: 'order',\n lines: lineSnapshots,\n adjustments: adjustmentDrafts,\n context: buildCalculationContext(order),\n existingTotals: resolveExistingPaymentTotals(order),\n ...resolveOrderCalculationMode(order),\n })\n applyOrderTotalsUnlessExternal(order, calculation)\n order.updatedAt = new Date()\n em.persist(order)\n },\n ],\n { transaction: true },\n )\n\n return createdLines\n}\n\nfunction normalizeLinesInput(lines: ReturnCreateInput['lines']): ReturnLineInput[] {\n const seen = new Set<string>()\n const result: ReturnLineInput[] = []\n for (const line of lines) {\n const orderLineId = line.orderLineId\n if (!orderLineId || seen.has(orderLineId)) continue\n const quantity = toNumeric(line.quantity)\n if (!Number.isFinite(quantity) || quantity <= 0) continue\n seen.add(orderLineId)\n result.push({ orderLineId, quantity })\n }\n return result\n}\n\nconst createReturnCommand: CommandHandler<ReturnCreateInput, { returnId: string }> = {\n id: 'sales.returns.create',\n async execute(rawInput, ctx) {\n const input = returnCreateSchema.parse(rawInput ?? {})\n ensureTenantScope(ctx, input.tenantId)\n ensureOrganizationScope(ctx, input.organizationId)\n\n const { translate } = await resolveTranslations()\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n\n const requested = normalizeLinesInput(input.lines)\n if (!requested.length) {\n throw new CrudHttpError(400, { error: translate('sales.returns.linesRequired', 'Select at least one line to return.') })\n }\n\n const salesCalculationService = ctx.container.resolve<SalesCalculationService>('salesCalculationService')\n const { header, createdLines, order } = await em.transactional(async (tx) => {\n const existingOrder = await findOneWithDecryption(\n tx,\n SalesOrder,\n { id: input.orderId, deletedAt: null },\n {},\n { tenantId: input.tenantId, organizationId: input.organizationId },\n )\n if (!existingOrder) {\n throw notFound(translate('sales.returns.orderMissing', 'Order not found.'))\n }\n ensureSameScope(existingOrder, input.organizationId, input.tenantId)\n const order = await lockReturnOrder(tx, existingOrder.id, {\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n })\n if (!order) {\n throw notFound(translate('sales.returns.orderMissing', 'Order not found.'))\n }\n await enforceSalesDocumentOptimisticLock(ctx, order, SALES_RESOURCE_KIND_ORDER)\n\n const orderLines = await findWithDecryption(\n tx,\n SalesOrderLine,\n { order: order.id, deletedAt: null },\n { lockMode: LockMode.PESSIMISTIC_WRITE },\n { tenantId: input.tenantId, organizationId: input.organizationId },\n )\n const lineMap = new Map(orderLines.map((line) => [line.id, line]))\n\n const shippedByLine = await loadShippedQuantityByLine(tx, order.id, {\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n })\n\n requested.forEach(({ orderLineId, quantity }) => {\n const line = lineMap.get(orderLineId)\n if (!line) {\n throw notFound(translate('sales.returns.lineMissing', 'Order line not found.'))\n }\n const available = computeAvailableReturnQuantity({\n quantity: toNumeric(line.quantity),\n returnedQuantity: toNumeric(line.returnedQuantity),\n shippedQuantity: shippedByLine.get(orderLineId) ?? 0,\n })\n if (quantity - 1e-6 > available) {\n throw new CrudHttpError(400, { error: translate('sales.returns.quantityExceedsShipped', 'Cannot return more than the shipped quantity. Ship the items before recording a return.') })\n }\n })\n\n const existingAdjustments = await findWithDecryption(\n tx,\n SalesOrderAdjustment,\n { order: order.id, deletedAt: null },\n { orderBy: { position: 'asc' } },\n { tenantId: input.tenantId, organizationId: input.organizationId },\n )\n const positionStart = existingAdjustments.reduce((acc, adj) => Math.max(acc, adj.position ?? 0), 0) + 1\n\n const numberGenerator = new SalesDocumentNumberGenerator(tx)\n const generated = await numberGenerator.generate({\n kind: 'return',\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n })\n const returnId = randomUUID()\n const entity = tx.create(SalesReturn, {\n id: returnId,\n order,\n organizationId: input.organizationId,\n tenantId: input.tenantId,\n returnNumber: generated.number,\n reason: input.reason ?? null,\n notes: input.notes ?? null,\n returnedAt: input.returnedAt ?? new Date(),\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n tx.persist(entity)\n\n const createdAdjustments: SalesOrderAdjustment[] = []\n const createdReturnLines: SalesReturnLine[] = []\n requested.forEach((lineInput, index) => {\n const line = lineMap.get(lineInput.orderLineId)\n if (!line) return\n const quantity = lineInput.quantity\n const lineQuantity = Math.max(toNumeric(line.quantity), 0)\n // `total_net_amount = 0` while `total_gross_amount > 0` is not a representable\n // priced state (gross = net * (1 + taxRate) \u21D2 net = 0 \u21D2 gross = 0). When a line\n // carries a positive gross but a zeroed/missing net, reconstruct the net from the\n // line's gross and tax rate so the return credits both sides and the order's net\n // grand total moves in lockstep with gross (#3036). A genuinely free line\n // (gross = 0, e.g. a 100% discount / comp) keeps net 0, so the return is not\n // over-credited at the discount-ignoring unit price (#3521).\n const lineTotalNet = deriveLineNetFromGross(line.totalNetAmount, line.totalGrossAmount, line.taxRate)\n const unitNet = lineQuantity > 0 ? lineTotalNet / lineQuantity : toNumeric(line.unitPriceNet)\n const unitGross = lineQuantity > 0 ? toNumeric(line.totalGrossAmount) / lineQuantity : toNumeric(line.unitPriceGross)\n const totalNet = -round(Math.max(unitNet, 0) * quantity)\n const totalGross = -round(Math.max(unitGross, 0) * quantity)\n\n const returnLineId = randomUUID()\n const returnLine = tx.create(SalesReturnLine, {\n id: returnLineId,\n salesReturn: entity,\n orderLine: tx.getReference(SalesOrderLine, line.id),\n organizationId: input.organizationId,\n tenantId: input.tenantId,\n quantityReturned: quantity.toString(),\n unitPriceNet: round(unitNet).toString(),\n unitPriceGross: round(unitGross).toString(),\n totalNetAmount: totalNet.toString(),\n totalGrossAmount: totalGross.toString(),\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n createdReturnLines.push(returnLine)\n tx.persist(returnLine)\n\n const adjustment = tx.create(SalesOrderAdjustment, {\n id: randomUUID(),\n order,\n orderLine: tx.getReference(SalesOrderLine, line.id),\n organizationId: input.organizationId,\n tenantId: input.tenantId,\n scope: 'line',\n kind: 'return',\n rate: '0',\n amountNet: totalNet.toString(),\n amountGross: totalGross.toString(),\n currencyCode: order.currencyCode,\n metadata: { returnId, returnLineId },\n position: positionStart + index,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n createdAdjustments.push(adjustment)\n tx.persist(adjustment)\n\n line.returnedQuantity = (toNumeric(line.returnedQuantity) + quantity).toString()\n line.updatedAt = new Date()\n tx.persist(line)\n })\n\n const lineSnapshots: SalesLineSnapshot[] = orderLines.map(mapOrderLineEntityToSnapshot)\n const adjustmentDrafts: SalesAdjustmentDraft[] = [...existingAdjustments, ...createdAdjustments].map(mapOrderAdjustmentToDraft)\n const calculation = await salesCalculationService.calculateDocumentTotals({\n documentKind: 'order',\n lines: lineSnapshots,\n adjustments: adjustmentDrafts,\n context: buildCalculationContext(order),\n existingTotals: resolveExistingPaymentTotals(order),\n ...resolveOrderCalculationMode(order),\n })\n applyOrderTotalsUnlessExternal(order, calculation)\n order.updatedAt = new Date()\n tx.persist(order)\n\n await tx.flush()\n\n return { header: entity, createdLines: createdReturnLines, order }\n })\n\n await invalidateOrderCache(ctx.container, order, ctx.auth?.tenantId ?? null)\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({\n dataEngine,\n action: 'created',\n entity: header,\n identifiers: { id: header.id, organizationId: header.organizationId, tenantId: header.tenantId },\n indexer: { entityType: E.sales.sales_return },\n events: returnCrudEvents,\n })\n\n if (createdLines.length) {\n await Promise.all(\n createdLines.map((line) =>\n emitCrudSideEffects({\n dataEngine,\n action: 'created',\n entity: line,\n identifiers: { id: line.id, organizationId: line.organizationId, tenantId: line.tenantId },\n indexer: { entityType: E.sales.sales_return_line },\n }),\n ),\n )\n }\n\n return { returnId: header.id }\n },\n captureAfter: async (_input, result, ctx) => {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n return loadReturnSnapshot(em, result.returnId)\n },\n buildLog: async ({ result, snapshots }) => {\n const after = snapshots.after as ReturnSnapshot | undefined\n if (!after) return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('sales.audit.returns.create', 'Create return'),\n resourceKind: 'sales.return',\n resourceId: result.returnId,\n parentResourceKind: 'sales.order',\n parentResourceId: after.orderId ?? null,\n tenantId: after.tenantId,\n organizationId: after.organizationId,\n snapshotAfter: after,\n payload: {\n undo: { after } satisfies ReturnUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<ReturnUndoPayload>(logEntry)\n const after = payload?.after\n if (!after) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const salesCalculationService = ctx.container.resolve<SalesCalculationService>('salesCalculationService')\n await reverseReturnEffects(em, salesCalculationService, after)\n await invalidateOrderCache(ctx.container, {\n id: after.orderId,\n organizationId: after.organizationId,\n tenantId: after.tenantId,\n }, ctx.auth?.tenantId ?? null)\n },\n redo: async ({ ctx, logEntry }) => {\n const after = resolveRedoSnapshot<ReturnSnapshot>(logEntry)\n if (!after || !after.id) {\n throw new CrudHttpError(400, { error: '[internal] redo snapshot unavailable for sales.returns.create' })\n }\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const salesCalculationService = ctx.container.resolve<SalesCalculationService>('salesCalculationService')\n\n const createdLines = await restoreReturnEffects(em, salesCalculationService, after)\n\n const header = await findOneWithDecryption(\n em,\n SalesReturn,\n { id: after.id, deletedAt: null },\n {},\n { tenantId: after.tenantId, organizationId: after.organizationId },\n )\n if (!header) {\n const { translate } = await resolveTranslations()\n throw notFound(translate('sales.returns.orderMissing', 'Order not found.'))\n }\n\n await invalidateOrderCache(ctx.container, {\n id: after.orderId,\n organizationId: after.organizationId,\n tenantId: after.tenantId,\n }, ctx.auth?.tenantId ?? null)\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({\n dataEngine,\n action: 'created',\n entity: header,\n identifiers: { id: header.id, organizationId: header.organizationId, tenantId: header.tenantId },\n indexer: { entityType: E.sales.sales_return },\n events: returnCrudEvents,\n })\n\n if (createdLines.length) {\n await Promise.all(\n createdLines.map((line) =>\n emitCrudSideEffects({\n dataEngine,\n action: 'created',\n entity: line,\n identifiers: { id: line.id, organizationId: line.organizationId, tenantId: line.tenantId },\n indexer: { entityType: E.sales.sales_return_line },\n }),\n ),\n )\n }\n\n return { returnId: header.id }\n },\n}\n\nconst updateReturnCommand: CommandHandler<ReturnUpdateInput, { returnId: string }> = {\n id: 'sales.returns.update',\n async prepare(rawInput, ctx) {\n const parsed = returnUpdateSchema.parse(rawInput ?? {})\n const em = ctx.container.resolve('em') as EntityManager\n const snapshot = await loadReturnHeaderSnapshot(em, parsed.id)\n if (snapshot) {\n ensureTenantScope(ctx, snapshot.tenantId)\n ensureOrganizationScope(ctx, snapshot.organizationId)\n }\n return snapshot ? { before: snapshot } : {}\n },\n async execute(rawInput, ctx) {\n const input = returnUpdateSchema.parse(rawInput ?? {})\n ensureTenantScope(ctx, input.tenantId)\n ensureOrganizationScope(ctx, input.organizationId)\n const { translate } = await resolveTranslations()\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n\n const header = await em.transactional(async (tx) => {\n const entity = await findOneWithDecryption(\n tx,\n SalesReturn,\n { id: input.id, deletedAt: null },\n { populate: ['order'] },\n { tenantId: input.tenantId, organizationId: input.organizationId },\n )\n if (!entity || !entity.order) {\n throw notFound(translate('sales.returns.notFound', 'Return not found.'))\n }\n ensureSameScope(entity, input.organizationId, input.tenantId)\n const orderId = typeof entity.order === 'string' ? entity.order : entity.order.id\n if (input.orderId !== orderId) {\n throw new CrudHttpError(400, { error: translate('sales.returns.orderMismatch', 'Return does not belong to this order.') })\n }\n // Lock on the return's own version \u2014 editing header fields (reason / notes /\n // returnedAt) only touches the return, not the order totals.\n await enforceSalesDocumentOptimisticLock(ctx, entity, SALES_RESOURCE_KIND_RETURN)\n\n if (input.reason !== undefined) entity.reason = input.reason.length ? input.reason : null\n if (input.notes !== undefined) entity.notes = input.notes.length ? input.notes : null\n if (input.returnedAt !== undefined) entity.returnedAt = input.returnedAt ?? null\n entity.updatedAt = new Date()\n tx.persist(entity)\n await tx.flush()\n return entity\n })\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({\n dataEngine,\n action: 'updated',\n entity: header,\n identifiers: { id: header.id, organizationId: header.organizationId, tenantId: header.tenantId },\n indexer: { entityType: E.sales.sales_return },\n events: returnCrudEvents,\n })\n\n return { returnId: header.id }\n },\n captureAfter: async (_input, result, ctx) => {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n return loadReturnHeaderSnapshot(em, result.returnId)\n },\n buildLog: async ({ snapshots, result }) => {\n const { translate } = await resolveTranslations()\n const before = snapshots.before as ReturnHeaderSnapshot | undefined\n const after = snapshots.after as ReturnHeaderSnapshot | undefined\n return {\n actionLabel: translate('sales.audit.returns.update', 'Update return'),\n resourceKind: 'sales.return',\n resourceId: result.returnId,\n parentResourceKind: 'sales.order',\n parentResourceId: after?.orderId ?? before?.orderId ?? null,\n tenantId: after?.tenantId ?? before?.tenantId ?? null,\n organizationId: after?.organizationId ?? before?.organizationId ?? null,\n snapshotBefore: before ?? null,\n snapshotAfter: after ?? null,\n payload: {\n undo: { before, after } satisfies ReturnHeaderUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<ReturnHeaderUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n await em.transactional(async (tx) => {\n const entity = await findOneWithDecryption(\n tx,\n SalesReturn,\n { id: before.id, deletedAt: null },\n {},\n { tenantId: before.tenantId, organizationId: before.organizationId },\n )\n if (!entity) return\n entity.reason = before.reason\n entity.notes = before.notes\n entity.returnedAt = before.returnedAt ? new Date(before.returnedAt) : null\n entity.updatedAt = new Date()\n tx.persist(entity)\n await tx.flush()\n })\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n const restored = await findOneWithDecryption(\n em,\n SalesReturn,\n { id: before.id, deletedAt: null },\n {},\n { tenantId: before.tenantId, organizationId: before.organizationId },\n )\n if (restored) {\n await emitCrudSideEffects({\n dataEngine,\n action: 'updated',\n entity: restored,\n identifiers: { id: restored.id, organizationId: restored.organizationId, tenantId: restored.tenantId },\n indexer: { entityType: E.sales.sales_return },\n events: returnCrudEvents,\n })\n }\n },\n}\n\nconst deleteReturnCommand: CommandHandler<ReturnDeleteInput, { returnId: string }> = {\n id: 'sales.returns.delete',\n async prepare(rawInput, ctx) {\n const parsed = returnDeleteSchema.parse(rawInput ?? {})\n const em = ctx.container.resolve('em') as EntityManager\n const snapshot = await loadReturnSnapshot(em, parsed.id)\n if (snapshot) {\n ensureTenantScope(ctx, snapshot.tenantId)\n ensureOrganizationScope(ctx, snapshot.organizationId)\n }\n return snapshot ? { before: snapshot } : {}\n },\n async execute(rawInput, ctx) {\n const input = returnDeleteSchema.parse(rawInput ?? {})\n ensureTenantScope(ctx, input.tenantId)\n ensureOrganizationScope(ctx, input.organizationId)\n const { translate } = await resolveTranslations()\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const salesCalculationService = ctx.container.resolve<SalesCalculationService>('salesCalculationService')\n\n const snapshot = await loadReturnSnapshot(em, input.id)\n if (!snapshot) {\n throw notFound(translate('sales.returns.notFound', 'Return not found.'))\n }\n ensureSameScope(snapshot, input.organizationId, input.tenantId)\n if (input.orderId !== snapshot.orderId) {\n throw new CrudHttpError(400, { error: translate('sales.returns.orderMismatch', 'Return does not belong to this order.') })\n }\n\n const header = await findOneWithDecryption(\n em,\n SalesReturn,\n { id: input.id, deletedAt: null },\n {},\n { tenantId: input.tenantId, organizationId: input.organizationId },\n )\n if (!header) {\n throw notFound(translate('sales.returns.notFound', 'Return not found.'))\n }\n ensureSameScope(header, input.organizationId, input.tenantId)\n // Lock on the return's own version, captured before any mutation.\n await enforceSalesDocumentOptimisticLock(ctx, header, SALES_RESOURCE_KIND_RETURN)\n\n await reverseReturnEffects(em, salesCalculationService, snapshot)\n await invalidateOrderCache(ctx.container, {\n id: snapshot.orderId,\n organizationId: snapshot.organizationId,\n tenantId: snapshot.tenantId,\n }, ctx.auth?.tenantId ?? null)\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({\n dataEngine,\n action: 'deleted',\n entity: header,\n identifiers: { id: snapshot.id, organizationId: snapshot.organizationId, tenantId: snapshot.tenantId },\n indexer: { entityType: E.sales.sales_return },\n events: returnCrudEvents,\n })\n\n if (snapshot.lines.length) {\n await Promise.all(\n snapshot.lines.map((line) =>\n emitCrudSideEffects({\n dataEngine,\n action: 'deleted',\n entity: { id: line.id, organizationId: snapshot.organizationId, tenantId: snapshot.tenantId },\n identifiers: { id: line.id, organizationId: snapshot.organizationId, tenantId: snapshot.tenantId },\n indexer: { entityType: E.sales.sales_return_line },\n }),\n ),\n )\n }\n\n return { returnId: snapshot.id }\n },\n buildLog: async ({ snapshots, result }) => {\n const before = snapshots.before as ReturnSnapshot | undefined\n if (!before) return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('sales.audit.returns.delete', 'Delete return'),\n resourceKind: 'sales.return',\n resourceId: result.returnId,\n parentResourceKind: 'sales.order',\n parentResourceId: before.orderId ?? null,\n tenantId: before.tenantId,\n organizationId: before.organizationId,\n snapshotBefore: before,\n payload: {\n undo: { before } satisfies ReturnDeleteUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<ReturnDeleteUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const salesCalculationService = ctx.container.resolve<SalesCalculationService>('salesCalculationService')\n\n const createdLines = await restoreReturnEffects(em, salesCalculationService, before)\n\n const header = await findOneWithDecryption(\n em,\n SalesReturn,\n { id: before.id, deletedAt: null },\n {},\n { tenantId: before.tenantId, organizationId: before.organizationId },\n )\n if (!header) return\n\n await invalidateOrderCache(ctx.container, {\n id: before.orderId,\n organizationId: before.organizationId,\n tenantId: before.tenantId,\n }, ctx.auth?.tenantId ?? null)\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({\n dataEngine,\n action: 'created',\n entity: header,\n identifiers: { id: header.id, organizationId: header.organizationId, tenantId: header.tenantId },\n indexer: { entityType: E.sales.sales_return },\n events: returnCrudEvents,\n })\n\n if (createdLines.length) {\n await Promise.all(\n createdLines.map((line) =>\n emitCrudSideEffects({\n dataEngine,\n action: 'created',\n entity: line,\n identifiers: { id: line.id, organizationId: line.organizationId, tenantId: line.tenantId },\n indexer: { entityType: E.sales.sales_return_line },\n }),\n ),\n )\n }\n },\n}\n\nregisterCommand(createReturnCommand)\nregisterCommand(updateReturnCommand)\nregisterCommand(deleteReturnCommand)\n\nexport const returnCommands = [createReturnCommand, updateReturnCommand, deleteReturnCommand]\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["order"]
|
|
7
7
|
}
|
|
@@ -16,6 +16,10 @@ async function enforceSalesDocumentOptimisticLock(ctx, document, resourceKind) {
|
|
|
16
16
|
request: ctx.request ?? null
|
|
17
17
|
});
|
|
18
18
|
}
|
|
19
|
+
async function lockSalesDocumentRow(em, table, id, scope) {
|
|
20
|
+
const locked = await em.getKysely().selectFrom(table).select("id").where("id", "=", id).where("tenant_id", "=", scope.tenantId).where("organization_id", "=", scope.organizationId).where("deleted_at", "is", null).forNoKeyUpdate().executeTakeFirst();
|
|
21
|
+
return Boolean(locked);
|
|
22
|
+
}
|
|
19
23
|
import { cloneJson } from "../lib/json.js";
|
|
20
24
|
function toNumericString(value) {
|
|
21
25
|
if (value === void 0 || value === null) return null;
|
|
@@ -72,6 +76,7 @@ export {
|
|
|
72
76
|
ensureSameScope,
|
|
73
77
|
ensureTenantScope,
|
|
74
78
|
extractUndoPayload,
|
|
79
|
+
lockSalesDocumentRow,
|
|
75
80
|
reconcileLinePersistedTotals,
|
|
76
81
|
requireScopedEntity,
|
|
77
82
|
toNumericString
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/sales/commands/shared.ts"],
|
|
4
|
-
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { notFound } from '@open-mercato/shared/lib/crud/errors'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nexport { assertFound } from '@open-mercato/shared/lib/crud/errors'\nexport { ensureOrganizationScope, ensureSameScope, ensureTenantScope } from '@open-mercato/shared/lib/commands/scope'\nexport { extractUndoPayload } from '@open-mercato/shared/lib/commands/undo'\n\n/** Resource kinds used by the document-aggregate optimistic-lock check. */\nexport const SALES_RESOURCE_KIND_ORDER = 'sales.order'\nexport const SALES_RESOURCE_KIND_QUOTE = 'sales.quote'\nexport const SALES_RESOURCE_KIND_RETURN = 'sales.return'\n\n/**\n * Enforce the document-aggregate OSS optimistic lock for a sales sub-resource\n * command (lines, adjustments, shipments, payments, returns, quote\n * conversion). The client sends the parent order/quote's expected `updated_at`\n * via the optimistic-lock extension header; this compares it against the\n * already-loaded document and throws the structured 409 on mismatch.\n *\n * The parent document is the consistency boundary: sub-resource mutations\n * recalculate document totals (or transition the document), which dirties the\n * parent so its `updated_at` advances on flush \u2014 meaning concurrent sub-edits\n * observe each other and conflict. Call this AFTER loading + scope-checking the\n * document and BEFORE mutating, so `document.updatedAt` is the pre-mutation\n * version.\n *\n * Strictly additive: when the client sends no header the check is a no-op, so\n * existing API consumers are unaffected. Respects `OM_OPTIMISTIC_LOCK`.\n *\n * Routes through the async DI-aware seam `enforceCommandOptimisticLockWithGuards`\n * (Phase 0 / S1): the OSS `updated_at` floor runs first (identical behavior when\n * `record_locks` is disabled \u2014 floor only), then the optional enterprise\n * `record_locks` enrichment is awaited so the aggregate check observes the\n * action-log diff when the resource is enabled. Callers MUST `await` it.\n */\nexport async function enforceSalesDocumentOptimisticLock(\n ctx: CommandRuntimeContext,\n document: { id: string; updatedAt?: Date | string | null } | null | undefined,\n resourceKind: string,\n): Promise<void> {\n if (!document) return\n await enforceCommandOptimisticLockWithGuards(ctx.container, {\n resourceKind,\n resourceId: document.id,\n current: document.updatedAt ?? null,\n request: ctx.request ?? null,\n })\n}\n\nexport { cloneJson } from '../lib/json'\n\nexport function toNumericString(value: number | null | undefined): string | null {\n if (value === undefined || value === null) return null\n return value.toString()\n}\n\n/** Numeric scale of the `total_net_amount` / `total_gross_amount` line columns. */\nconst LINE_AMOUNT_SCALE = 4\n\nfunction parseLineAmount(value: number | string | null | undefined): number {\n if (typeof value === 'number') return Number.isFinite(value) ? value : 0\n if (typeof value === 'string' && value.trim().length) {\n const parsed = Number(value)\n if (!Number.isNaN(parsed)) return parsed\n }\n return 0\n}\n\nfunction roundLineAmount(value: number): number {\n const factor = 10 ** LINE_AMOUNT_SCALE\n return Math.round((value + Number.EPSILON) * factor) / factor\n}\n\n/**\n * Derive a sales line's net total from its gross total and tax rate.\n *\n * `total_net_amount = 0` while `total_gross_amount > 0` is not a representable\n * priced state: `gross = net * (1 + taxRate)`, so `net = 0 \u21D2 gross = 0`. When a\n * line carries a positive gross but a missing/zero net (legacy rows, optional\n * pass-through inputs, or invoice/credit-memo copy of a zeroed order line), the\n * net is reconstructed from gross and the line's tax rate. `taxRate` is a\n * percentage (e.g. `23` \u21D2 `0.23` fraction), matching the stored column and\n * `taxCalculationService`. Returns the existing net unchanged when the\n * invariant already holds. See issues #3521 / #3036.\n */\nexport function deriveLineNetFromGross(\n net: number | string | null | undefined,\n gross: number | string | null | undefined,\n taxRate: number | string | null | undefined,\n): number {\n const netValue = parseLineAmount(net)\n const grossValue = parseLineAmount(gross)\n if (grossValue > 0 && netValue <= 0) {\n const rate = parseLineAmount(taxRate)\n const fraction = rate > 0 ? rate / 100 : 0\n return roundLineAmount(fraction > 0 ? grossValue / (1 + fraction) : grossValue)\n }\n return netValue\n}\n\ntype LinePersistedTotals = {\n totalNetAmount?: number | string | null\n totalGrossAmount?: number | string | null\n taxRate?: number | string | null\n}\n\n/**\n * Enforce the `gross > 0 \u21D2 net > 0` invariant on a line-entity create payload\n * right before persistence. Returns the payload unchanged when the net total is\n * already positive or gross is non-positive; otherwise fills the net total in\n * from gross / taxRate via {@link deriveLineNetFromGross}. Applied at every\n * sales line persistence site so the skew that froze return net totals (#3036)\n * cannot be stored at the source. Idempotent and non-destructive: it only ever\n * raises a zero/missing net to its derived value.\n */\nexport function reconcileLinePersistedTotals<T extends LinePersistedTotals>(payload: T): T {\n const gross = parseLineAmount(payload.totalGrossAmount)\n const net = parseLineAmount(payload.totalNetAmount)\n if (gross <= 0 || net > 0) return payload\n const derivedNet = toNumericString(deriveLineNetFromGross(net, gross, payload.taxRate))\n if (derivedNet == null) return payload\n return { ...payload, totalNetAmount: derivedNet } as T\n}\n\nexport async function requireScopedEntity<T extends { id: string; deletedAt?: Date | null }>(\n em: EntityManager,\n entityClass: { new (): T },\n id: string,\n message: string,\n scope: { organizationId: string | null; tenantId: string | null } = { organizationId: null, tenantId: null },\n): Promise<T> {\n const where: Record<string, unknown> = { id, deletedAt: null }\n if (scope.organizationId) where.organizationId = scope.organizationId\n if (scope.tenantId) where.tenantId = scope.tenantId\n const entity = await findOneWithDecryption(em, entityClass, where, {}, scope)\n if (!entity) throw notFound(message)\n return entity\n}\n"],
|
|
5
|
-
"mappings": "AACA,SAAS,gBAAgB;AACzB,SAAS,6BAA6B;AAEtC,SAAS,8CAA8C;AACvD,SAAS,mBAAmB;AAC5B,SAAS,yBAAyB,iBAAiB,yBAAyB;AAC5E,SAAS,0BAA0B;AAG5B,MAAM,4BAA4B;AAClC,MAAM,4BAA4B;AAClC,MAAM,6BAA6B;AAyB1C,eAAsB,mCACpB,KACA,UACA,cACe;AACf,MAAI,CAAC,SAAU;AACf,QAAM,uCAAuC,IAAI,WAAW;AAAA,IAC1D;AAAA,IACA,YAAY,SAAS;AAAA,IACrB,SAAS,SAAS,aAAa;AAAA,IAC/B,SAAS,IAAI,WAAW;AAAA,EAC1B,CAAC;AACH;AAEA,SAAS,iBAAiB;AAEnB,SAAS,gBAAgB,OAAiD;AAC/E,MAAI,UAAU,UAAa,UAAU,KAAM,QAAO;AAClD,SAAO,MAAM,SAAS;AACxB;AAGA,MAAM,oBAAoB;AAE1B,SAAS,gBAAgB,OAAmD;AAC1E,MAAI,OAAO,UAAU,SAAU,QAAO,OAAO,SAAS,KAAK,IAAI,QAAQ;AACvE,MAAI,OAAO,UAAU,YAAY,MAAM,KAAK,EAAE,QAAQ;AACpD,UAAM,SAAS,OAAO,KAAK;AAC3B,QAAI,CAAC,OAAO,MAAM,MAAM,EAAG,QAAO;AAAA,EACpC;AACA,SAAO;AACT;AAEA,SAAS,gBAAgB,OAAuB;AAC9C,QAAM,SAAS,MAAM;AACrB,SAAO,KAAK,OAAO,QAAQ,OAAO,WAAW,MAAM,IAAI;AACzD;AAcO,SAAS,uBACd,KACA,OACA,SACQ;AACR,QAAM,WAAW,gBAAgB,GAAG;AACpC,QAAM,aAAa,gBAAgB,KAAK;AACxC,MAAI,aAAa,KAAK,YAAY,GAAG;AACnC,UAAM,OAAO,gBAAgB,OAAO;AACpC,UAAM,WAAW,OAAO,IAAI,OAAO,MAAM;AACzC,WAAO,gBAAgB,WAAW,IAAI,cAAc,IAAI,YAAY,UAAU;AAAA,EAChF;AACA,SAAO;AACT;AAiBO,SAAS,6BAA4D,SAAe;AACzF,QAAM,QAAQ,gBAAgB,QAAQ,gBAAgB;AACtD,QAAM,MAAM,gBAAgB,QAAQ,cAAc;AAClD,MAAI,SAAS,KAAK,MAAM,EAAG,QAAO;AAClC,QAAM,aAAa,gBAAgB,uBAAuB,KAAK,OAAO,QAAQ,OAAO,CAAC;AACtF,MAAI,cAAc,KAAM,QAAO;AAC/B,SAAO,EAAE,GAAG,SAAS,gBAAgB,WAAW;AAClD;AAEA,eAAsB,oBACpB,IACA,aACA,IACA,SACA,QAAoE,EAAE,gBAAgB,MAAM,UAAU,KAAK,GAC/F;AACZ,QAAM,QAAiC,EAAE,IAAI,WAAW,KAAK;AAC7D,MAAI,MAAM,eAAgB,OAAM,iBAAiB,MAAM;AACvD,MAAI,MAAM,SAAU,OAAM,WAAW,MAAM;AAC3C,QAAM,SAAS,MAAM,sBAAsB,IAAI,aAAa,OAAO,CAAC,GAAG,KAAK;AAC5E,MAAI,CAAC,OAAQ,OAAM,SAAS,OAAO;AACnC,SAAO;AACT;",
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { notFound } from '@open-mercato/shared/lib/crud/errors'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nexport { assertFound } from '@open-mercato/shared/lib/crud/errors'\nexport { ensureOrganizationScope, ensureSameScope, ensureTenantScope } from '@open-mercato/shared/lib/commands/scope'\nexport { extractUndoPayload } from '@open-mercato/shared/lib/commands/undo'\n\n/** Resource kinds used by the document-aggregate optimistic-lock check. */\nexport const SALES_RESOURCE_KIND_ORDER = 'sales.order'\nexport const SALES_RESOURCE_KIND_QUOTE = 'sales.quote'\nexport const SALES_RESOURCE_KIND_RETURN = 'sales.return'\n\n/**\n * Enforce the document-aggregate OSS optimistic lock for a sales sub-resource\n * command (lines, adjustments, shipments, payments, returns, quote\n * conversion). The client sends the parent order/quote's expected `updated_at`\n * via the optimistic-lock extension header; this compares it against the\n * already-loaded document and throws the structured 409 on mismatch.\n *\n * The parent document is the consistency boundary: sub-resource mutations\n * recalculate document totals (or transition the document), which dirties the\n * parent so its `updated_at` advances on flush \u2014 meaning concurrent sub-edits\n * observe each other and conflict. Call this AFTER loading + scope-checking the\n * document and BEFORE mutating, so `document.updatedAt` is the pre-mutation\n * version.\n *\n * Strictly additive: when the client sends no header the check is a no-op, so\n * existing API consumers are unaffected. Respects `OM_OPTIMISTIC_LOCK`.\n *\n * Routes through the async DI-aware seam `enforceCommandOptimisticLockWithGuards`\n * (Phase 0 / S1): the OSS `updated_at` floor runs first (identical behavior when\n * `record_locks` is disabled \u2014 floor only), then the optional enterprise\n * `record_locks` enrichment is awaited so the aggregate check observes the\n * action-log diff when the resource is enabled. Callers MUST `await` it.\n */\nexport async function enforceSalesDocumentOptimisticLock(\n ctx: CommandRuntimeContext,\n document: { id: string; updatedAt?: Date | string | null } | null | undefined,\n resourceKind: string,\n): Promise<void> {\n if (!document) return\n await enforceCommandOptimisticLockWithGuards(ctx.container, {\n resourceKind,\n resourceId: document.id,\n current: document.updatedAt ?? null,\n request: ctx.request ?? null,\n })\n}\n\ntype SalesDocumentLockTable = 'sales_orders' | 'sales_quotes'\ntype SalesDocumentLockRow = {\n id: string\n tenant_id: string\n organization_id: string\n deleted_at: Date | null\n}\ntype SalesDocumentLockDatabase = Record<SalesDocumentLockTable, SalesDocumentLockRow>\n\n/**\n * Take the parent-document row lock that serializes every write deriving the\n * order/quote header (totals, line count, paid amounts) or the document's child\n * rows (lines, adjustments, returned/fulfilled quantities) inside the caller's\n * transaction. Acquire it BEFORE reading the child rows the header is\n * recalculated from, and before locking those child rows, so all writers use the\n * same parent-then-children lock order (#6463).\n *\n * `FOR NO KEY UPDATE` conflicts with other writers of the same document, including\n * the `FOR UPDATE` locks taken by payments and undo, while still allowing the\n * `KEY SHARE` locks that foreign-key inserts (for example shipments) take on the\n * parent row. Returns false when no live row matches the scoped id.\n */\nexport async function lockSalesDocumentRow(\n em: EntityManager,\n table: SalesDocumentLockTable,\n id: string,\n scope: { tenantId: string; organizationId: string },\n): Promise<boolean> {\n const locked = await em.getKysely<SalesDocumentLockDatabase>()\n .selectFrom(table)\n .select('id')\n .where('id', '=', id)\n .where('tenant_id', '=', scope.tenantId)\n .where('organization_id', '=', scope.organizationId)\n .where('deleted_at', 'is', null)\n .forNoKeyUpdate()\n .executeTakeFirst()\n return Boolean(locked)\n}\n\nexport { cloneJson } from '../lib/json'\n\nexport function toNumericString(value: number | null | undefined): string | null {\n if (value === undefined || value === null) return null\n return value.toString()\n}\n\n/** Numeric scale of the `total_net_amount` / `total_gross_amount` line columns. */\nconst LINE_AMOUNT_SCALE = 4\n\nfunction parseLineAmount(value: number | string | null | undefined): number {\n if (typeof value === 'number') return Number.isFinite(value) ? value : 0\n if (typeof value === 'string' && value.trim().length) {\n const parsed = Number(value)\n if (!Number.isNaN(parsed)) return parsed\n }\n return 0\n}\n\nfunction roundLineAmount(value: number): number {\n const factor = 10 ** LINE_AMOUNT_SCALE\n return Math.round((value + Number.EPSILON) * factor) / factor\n}\n\n/**\n * Derive a sales line's net total from its gross total and tax rate.\n *\n * `total_net_amount = 0` while `total_gross_amount > 0` is not a representable\n * priced state: `gross = net * (1 + taxRate)`, so `net = 0 \u21D2 gross = 0`. When a\n * line carries a positive gross but a missing/zero net (legacy rows, optional\n * pass-through inputs, or invoice/credit-memo copy of a zeroed order line), the\n * net is reconstructed from gross and the line's tax rate. `taxRate` is a\n * percentage (e.g. `23` \u21D2 `0.23` fraction), matching the stored column and\n * `taxCalculationService`. Returns the existing net unchanged when the\n * invariant already holds. See issues #3521 / #3036.\n */\nexport function deriveLineNetFromGross(\n net: number | string | null | undefined,\n gross: number | string | null | undefined,\n taxRate: number | string | null | undefined,\n): number {\n const netValue = parseLineAmount(net)\n const grossValue = parseLineAmount(gross)\n if (grossValue > 0 && netValue <= 0) {\n const rate = parseLineAmount(taxRate)\n const fraction = rate > 0 ? rate / 100 : 0\n return roundLineAmount(fraction > 0 ? grossValue / (1 + fraction) : grossValue)\n }\n return netValue\n}\n\ntype LinePersistedTotals = {\n totalNetAmount?: number | string | null\n totalGrossAmount?: number | string | null\n taxRate?: number | string | null\n}\n\n/**\n * Enforce the `gross > 0 \u21D2 net > 0` invariant on a line-entity create payload\n * right before persistence. Returns the payload unchanged when the net total is\n * already positive or gross is non-positive; otherwise fills the net total in\n * from gross / taxRate via {@link deriveLineNetFromGross}. Applied at every\n * sales line persistence site so the skew that froze return net totals (#3036)\n * cannot be stored at the source. Idempotent and non-destructive: it only ever\n * raises a zero/missing net to its derived value.\n */\nexport function reconcileLinePersistedTotals<T extends LinePersistedTotals>(payload: T): T {\n const gross = parseLineAmount(payload.totalGrossAmount)\n const net = parseLineAmount(payload.totalNetAmount)\n if (gross <= 0 || net > 0) return payload\n const derivedNet = toNumericString(deriveLineNetFromGross(net, gross, payload.taxRate))\n if (derivedNet == null) return payload\n return { ...payload, totalNetAmount: derivedNet } as T\n}\n\nexport async function requireScopedEntity<T extends { id: string; deletedAt?: Date | null }>(\n em: EntityManager,\n entityClass: { new (): T },\n id: string,\n message: string,\n scope: { organizationId: string | null; tenantId: string | null } = { organizationId: null, tenantId: null },\n): Promise<T> {\n const where: Record<string, unknown> = { id, deletedAt: null }\n if (scope.organizationId) where.organizationId = scope.organizationId\n if (scope.tenantId) where.tenantId = scope.tenantId\n const entity = await findOneWithDecryption(em, entityClass, where, {}, scope)\n if (!entity) throw notFound(message)\n return entity\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,gBAAgB;AACzB,SAAS,6BAA6B;AAEtC,SAAS,8CAA8C;AACvD,SAAS,mBAAmB;AAC5B,SAAS,yBAAyB,iBAAiB,yBAAyB;AAC5E,SAAS,0BAA0B;AAG5B,MAAM,4BAA4B;AAClC,MAAM,4BAA4B;AAClC,MAAM,6BAA6B;AAyB1C,eAAsB,mCACpB,KACA,UACA,cACe;AACf,MAAI,CAAC,SAAU;AACf,QAAM,uCAAuC,IAAI,WAAW;AAAA,IAC1D;AAAA,IACA,YAAY,SAAS;AAAA,IACrB,SAAS,SAAS,aAAa;AAAA,IAC/B,SAAS,IAAI,WAAW;AAAA,EAC1B,CAAC;AACH;AAwBA,eAAsB,qBACpB,IACA,OACA,IACA,OACkB;AAClB,QAAM,SAAS,MAAM,GAAG,UAAqC,EAC1D,WAAW,KAAK,EAChB,OAAO,IAAI,EACX,MAAM,MAAM,KAAK,EAAE,EACnB,MAAM,aAAa,KAAK,MAAM,QAAQ,EACtC,MAAM,mBAAmB,KAAK,MAAM,cAAc,EAClD,MAAM,cAAc,MAAM,IAAI,EAC9B,eAAe,EACf,iBAAiB;AACpB,SAAO,QAAQ,MAAM;AACvB;AAEA,SAAS,iBAAiB;AAEnB,SAAS,gBAAgB,OAAiD;AAC/E,MAAI,UAAU,UAAa,UAAU,KAAM,QAAO;AAClD,SAAO,MAAM,SAAS;AACxB;AAGA,MAAM,oBAAoB;AAE1B,SAAS,gBAAgB,OAAmD;AAC1E,MAAI,OAAO,UAAU,SAAU,QAAO,OAAO,SAAS,KAAK,IAAI,QAAQ;AACvE,MAAI,OAAO,UAAU,YAAY,MAAM,KAAK,EAAE,QAAQ;AACpD,UAAM,SAAS,OAAO,KAAK;AAC3B,QAAI,CAAC,OAAO,MAAM,MAAM,EAAG,QAAO;AAAA,EACpC;AACA,SAAO;AACT;AAEA,SAAS,gBAAgB,OAAuB;AAC9C,QAAM,SAAS,MAAM;AACrB,SAAO,KAAK,OAAO,QAAQ,OAAO,WAAW,MAAM,IAAI;AACzD;AAcO,SAAS,uBACd,KACA,OACA,SACQ;AACR,QAAM,WAAW,gBAAgB,GAAG;AACpC,QAAM,aAAa,gBAAgB,KAAK;AACxC,MAAI,aAAa,KAAK,YAAY,GAAG;AACnC,UAAM,OAAO,gBAAgB,OAAO;AACpC,UAAM,WAAW,OAAO,IAAI,OAAO,MAAM;AACzC,WAAO,gBAAgB,WAAW,IAAI,cAAc,IAAI,YAAY,UAAU;AAAA,EAChF;AACA,SAAO;AACT;AAiBO,SAAS,6BAA4D,SAAe;AACzF,QAAM,QAAQ,gBAAgB,QAAQ,gBAAgB;AACtD,QAAM,MAAM,gBAAgB,QAAQ,cAAc;AAClD,MAAI,SAAS,KAAK,MAAM,EAAG,QAAO;AAClC,QAAM,aAAa,gBAAgB,uBAAuB,KAAK,OAAO,QAAQ,OAAO,CAAC;AACtF,MAAI,cAAc,KAAM,QAAO;AAC/B,SAAO,EAAE,GAAG,SAAS,gBAAgB,WAAW;AAClD;AAEA,eAAsB,oBACpB,IACA,aACA,IACA,SACA,QAAoE,EAAE,gBAAgB,MAAM,UAAU,KAAK,GAC/F;AACZ,QAAM,QAAiC,EAAE,IAAI,WAAW,KAAK;AAC7D,MAAI,MAAM,eAAgB,OAAM,iBAAiB,MAAM;AACvD,MAAI,MAAM,SAAU,OAAM,WAAW,MAAM;AAC3C,QAAM,SAAS,MAAM,sBAAsB,IAAI,aAAa,OAAO,CAAC,GAAG,KAAK;AAC5E,MAAI,CAAC,OAAQ,OAAM,SAAS,OAAO;AACnC,SAAO;AACT;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
ensureTenantScope,
|
|
26
26
|
extractUndoPayload,
|
|
27
27
|
enforceSalesDocumentOptimisticLock,
|
|
28
|
+
lockSalesDocumentRow,
|
|
28
29
|
SALES_RESOURCE_KIND_ORDER
|
|
29
30
|
} from "./shared.js";
|
|
30
31
|
import { resolveDictionaryEntryValue } from "../lib/dictionaries.js";
|
|
@@ -237,8 +238,15 @@ async function deleteShipmentWithItems(em, shipment) {
|
|
|
237
238
|
em.remove(shipment);
|
|
238
239
|
await em.flush();
|
|
239
240
|
}
|
|
241
|
+
async function lockOrderBeforeLines(em, order) {
|
|
242
|
+
await lockSalesDocumentRow(em, "sales_orders", order.id, {
|
|
243
|
+
tenantId: order.tenantId,
|
|
244
|
+
organizationId: order.organizationId
|
|
245
|
+
});
|
|
246
|
+
}
|
|
240
247
|
async function recomputeFulfilledQuantities(em, order) {
|
|
241
248
|
const scope = { tenantId: order.tenantId, organizationId: order.organizationId };
|
|
249
|
+
await lockOrderBeforeLines(em, order);
|
|
242
250
|
const shipments = await findWithDecryption(em, SalesShipment, { order, deletedAt: null }, {}, scope);
|
|
243
251
|
const shipmentIds = shipments.map((entry) => entry.id);
|
|
244
252
|
const shipmentItems = shipmentIds.length ? await findWithDecryption(em, SalesShipmentItem, { shipment: { $in: shipmentIds } }, {}, scope) : [];
|
|
@@ -281,6 +289,7 @@ async function validateShipmentItems(params) {
|
|
|
281
289
|
throw new CrudHttpError(400, { error: translate("sales.shipments.items_required", "Add at least one line to ship.") });
|
|
282
290
|
}
|
|
283
291
|
const scope = { tenantId: order.tenantId, organizationId: order.organizationId };
|
|
292
|
+
if (lockOrderLines) await lockOrderBeforeLines(em, order);
|
|
284
293
|
const orderLines = await findWithDecryption(
|
|
285
294
|
em,
|
|
286
295
|
SalesOrderLine,
|