@open-mercato/core 0.7.1-develop.7113.1.9d83dca10c → 0.7.1-develop.7121.1.734c263bda

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.
Files changed (95) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/helpers/integration/crmFixtures.js.map +1 -1
  3. package/dist/modules/communication_channels/api/post/channels/[id]/test-send/route.js +9 -2
  4. package/dist/modules/communication_channels/api/post/channels/[id]/test-send/route.js.map +2 -2
  5. package/dist/modules/communication_channels/lib/outbound-recipient.js +5 -1
  6. package/dist/modules/communication_channels/lib/outbound-recipient.js.map +2 -2
  7. package/dist/modules/communication_channels/lib/test-seed.js +5 -0
  8. package/dist/modules/communication_channels/lib/test-seed.js.map +2 -2
  9. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealClosure.js +1 -1
  10. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealClosure.js.map +1 -1
  11. package/dist/modules/customers/backend/customers/deals/page.js +4 -3
  12. package/dist/modules/customers/backend/customers/deals/page.js.map +2 -2
  13. package/dist/modules/customers/backend/customers/deals/pipeline/components/QuickDealDialog.js +2 -2
  14. package/dist/modules/customers/backend/customers/deals/pipeline/components/QuickDealDialog.js.map +2 -2
  15. package/dist/modules/customers/backend/customers/deals/pipeline/components/StatusFilterPopover.js +1 -1
  16. package/dist/modules/customers/backend/customers/deals/pipeline/components/StatusFilterPopover.js.map +1 -1
  17. package/dist/modules/customers/backend/customers/deals/pipeline/page.js +1 -1
  18. package/dist/modules/customers/backend/customers/deals/pipeline/page.js.map +2 -2
  19. package/dist/modules/customers/cli.js +5 -5
  20. package/dist/modules/customers/cli.js.map +2 -2
  21. package/dist/modules/customers/commands/deals.js.map +2 -2
  22. package/dist/modules/customers/components/detail/pipelineStageUtils.js +1 -0
  23. package/dist/modules/customers/components/detail/pipelineStageUtils.js.map +2 -2
  24. package/dist/modules/customers/components/linking/adapters/dealAdapter.js +2 -2
  25. package/dist/modules/customers/components/linking/adapters/dealAdapter.js.map +2 -2
  26. package/dist/modules/customers/lib/closureStage.js.map +2 -2
  27. package/dist/modules/customers/lib/dealStatus.js +6 -4
  28. package/dist/modules/customers/lib/dealStatus.js.map +2 -2
  29. package/dist/modules/customers/lib/dealsSummaryQueries.js +3 -3
  30. package/dist/modules/customers/lib/dealsSummaryQueries.js.map +1 -1
  31. package/dist/modules/customers/migrations/Migration20260824180000_deal_status_lost.js +30 -0
  32. package/dist/modules/customers/migrations/Migration20260824180000_deal_status_lost.js.map +7 -0
  33. package/dist/modules/dashboards/widgets/dashboard/pipeline-summary/config.js.map +1 -1
  34. package/dist/modules/eudr/notifications.js +5 -0
  35. package/dist/modules/eudr/notifications.js.map +2 -2
  36. package/dist/modules/query_index/lib/search-tokens.js +59 -28
  37. package/dist/modules/query_index/lib/search-tokens.js.map +2 -2
  38. package/dist/modules/sales/commands/documents.js +146 -99
  39. package/dist/modules/sales/commands/documents.js.map +2 -2
  40. package/dist/modules/sales/commands/returns.js +1 -30
  41. package/dist/modules/sales/commands/returns.js.map +2 -2
  42. package/dist/modules/sales/commands/shared.js +1 -4
  43. package/dist/modules/sales/commands/shared.js.map +2 -2
  44. package/dist/modules/sales/components/documents/SalesOrderDraftLines.js +2 -1
  45. package/dist/modules/sales/components/documents/SalesOrderDraftLines.js.map +2 -2
  46. package/dist/modules/sales/data/validators.js +3 -0
  47. package/dist/modules/sales/data/validators.js.map +2 -2
  48. package/dist/modules/sales/lib/calculations.js +14 -2
  49. package/dist/modules/sales/lib/calculations.js.map +2 -2
  50. package/dist/modules/sales/lib/json.js +8 -0
  51. package/dist/modules/sales/lib/json.js.map +7 -0
  52. package/dist/modules/sales/lib/lineSnapshots.js +66 -0
  53. package/dist/modules/sales/lib/lineSnapshots.js.map +7 -0
  54. package/dist/modules/warranty_claims/notifications.js +5 -0
  55. package/dist/modules/warranty_claims/notifications.js.map +2 -2
  56. package/dist/modules/wms/notifications.js +2 -0
  57. package/dist/modules/wms/notifications.js.map +2 -2
  58. package/package.json +7 -7
  59. package/src/helpers/integration/crmFixtures.ts +1 -1
  60. package/src/modules/communication_channels/api/post/channels/[id]/test-send/route.ts +21 -9
  61. package/src/modules/communication_channels/lib/adapter.ts +11 -0
  62. package/src/modules/communication_channels/lib/outbound-recipient.ts +14 -1
  63. package/src/modules/communication_channels/lib/test-seed.ts +19 -0
  64. package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealClosure.ts +1 -1
  65. package/src/modules/customers/backend/customers/deals/page.tsx +4 -3
  66. package/src/modules/customers/backend/customers/deals/pipeline/components/QuickDealDialog.tsx +5 -5
  67. package/src/modules/customers/backend/customers/deals/pipeline/components/StatusFilterPopover.tsx +3 -3
  68. package/src/modules/customers/backend/customers/deals/pipeline/page.tsx +2 -2
  69. package/src/modules/customers/cli.ts +5 -5
  70. package/src/modules/customers/commands/deals.ts +4 -3
  71. package/src/modules/customers/components/detail/pipelineStageUtils.ts +1 -0
  72. package/src/modules/customers/components/linking/adapters/dealAdapter.tsx +2 -2
  73. package/src/modules/customers/i18n/de.json +1 -1
  74. package/src/modules/customers/i18n/en.json +1 -1
  75. package/src/modules/customers/i18n/es.json +1 -1
  76. package/src/modules/customers/i18n/ko.json +1 -1
  77. package/src/modules/customers/i18n/pl.json +1 -1
  78. package/src/modules/customers/lib/closureStage.ts +2 -1
  79. package/src/modules/customers/lib/dealStatus.ts +23 -12
  80. package/src/modules/customers/lib/dealsSummaryQueries.ts +3 -3
  81. package/src/modules/customers/migrations/Migration20260824180000_deal_status_lost.ts +55 -0
  82. package/src/modules/dashboards/widgets/dashboard/pipeline-summary/config.ts +1 -1
  83. package/src/modules/eudr/notifications.ts +5 -0
  84. package/src/modules/query_index/lib/search-tokens.ts +100 -43
  85. package/src/modules/sales/commands/documents.ts +185 -105
  86. package/src/modules/sales/commands/returns.ts +1 -31
  87. package/src/modules/sales/commands/shared.ts +1 -4
  88. package/src/modules/sales/components/documents/SalesOrderDraftLines.tsx +12 -1
  89. package/src/modules/sales/data/validators.ts +3 -0
  90. package/src/modules/sales/lib/calculations.ts +33 -7
  91. package/src/modules/sales/lib/json.ts +4 -0
  92. package/src/modules/sales/lib/lineSnapshots.ts +98 -0
  93. package/src/modules/sales/lib/types.ts +20 -0
  94. package/src/modules/warranty_claims/notifications.ts +5 -0
  95. package/src/modules/wms/notifications.ts +2 -0
@@ -11,6 +11,7 @@ import { SalesDocumentNumberGenerator } from "../services/salesDocumentNumberGen
11
11
  import { cloneJson, deriveLineNetFromGross, ensureOrganizationScope, ensureSameScope, ensureTenantScope, extractUndoPayload, toNumericString, enforceSalesDocumentOptimisticLock, SALES_RESOURCE_KIND_ORDER, SALES_RESOURCE_KIND_RETURN } from "./shared.js";
12
12
  import { resolveRedoSnapshot } from "@open-mercato/shared/lib/commands/redo";
13
13
  import { SalesOrder, SalesOrderAdjustment, SalesOrderLine, SalesReturn, SalesReturnLine } from "../data/entities.js";
14
+ import { mapOrderLineEntityToSnapshot } from "../lib/lineSnapshots.js";
14
15
  import { loadShippedQuantityByLine } from "../lib/shipments/snapshots.js";
15
16
  import { computeAvailableReturnQuantity } from "../lib/returnQuantity.js";
16
17
  import {
@@ -71,36 +72,6 @@ function applyOrderTotals(order, totals, lineCount) {
71
72
  order.totalsSnapshot = cloneJson(totals);
72
73
  order.lineItemCount = lineCount;
73
74
  }
74
- function mapOrderLineEntityToSnapshot(line) {
75
- return {
76
- id: line.id,
77
- lineNumber: line.lineNumber,
78
- kind: line.kind,
79
- productId: line.productId ?? null,
80
- productVariantId: line.productVariantId ?? null,
81
- name: line.name ?? null,
82
- description: line.description ?? null,
83
- comment: line.comment ?? null,
84
- quantity: toNumeric(line.quantity),
85
- quantityUnit: line.quantityUnit ?? null,
86
- normalizedQuantity: toNumeric(line.normalizedQuantity ?? line.quantity),
87
- normalizedUnit: line.normalizedUnit ?? line.quantityUnit ?? null,
88
- uomSnapshot: line.uomSnapshot ? cloneJson(line.uomSnapshot) : null,
89
- currencyCode: line.currencyCode,
90
- unitPriceNet: toNumeric(line.unitPriceNet),
91
- unitPriceGross: toNumeric(line.unitPriceGross),
92
- discountAmount: toNumeric(line.discountAmount),
93
- discountPercent: toNumeric(line.discountPercent),
94
- taxRate: toNumeric(line.taxRate),
95
- taxAmount: toNumeric(line.taxAmount),
96
- totalNetAmount: toNumeric(line.totalNetAmount),
97
- totalGrossAmount: toNumeric(line.totalGrossAmount),
98
- configuration: line.configuration ? cloneJson(line.configuration) : null,
99
- promotionCode: line.promotionCode ?? null,
100
- metadata: line.metadata ? cloneJson(line.metadata) : null,
101
- customFieldSetId: line.customFieldSetId ?? null
102
- };
103
- }
104
75
  function mapOrderAdjustmentToDraft(adjustment) {
105
76
  return {
106
77
  id: adjustment.id,
@@ -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 { 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 { 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\nfunction mapOrderLineEntityToSnapshot(line: SalesOrderLine): SalesLineSnapshot {\n return {\n id: line.id,\n lineNumber: line.lineNumber,\n kind: line.kind,\n productId: line.productId ?? null,\n productVariantId: line.productVariantId ?? null,\n name: line.name ?? null,\n description: line.description ?? null,\n comment: line.comment ?? null,\n quantity: toNumeric(line.quantity),\n quantityUnit: line.quantityUnit ?? null,\n normalizedQuantity: toNumeric(line.normalizedQuantity ?? line.quantity),\n normalizedUnit: line.normalizedUnit ?? line.quantityUnit ?? null,\n uomSnapshot: line.uomSnapshot ? cloneJson(line.uomSnapshot) : null,\n currencyCode: line.currencyCode,\n unitPriceNet: toNumeric(line.unitPriceNet),\n unitPriceGross: toNumeric(line.unitPriceGross),\n discountAmount: toNumeric(line.discountAmount),\n discountPercent: toNumeric(line.discountPercent),\n taxRate: toNumeric(line.taxRate),\n taxAmount: toNumeric(line.taxAmount),\n totalNetAmount: toNumeric(line.totalNetAmount),\n totalGrossAmount: toNumeric(line.totalGrossAmount),\n configuration: line.configuration ? cloneJson(line.configuration) : null,\n promotionCode: line.promotionCode ?? null,\n metadata: line.metadata ? cloneJson(line.metadata) : null,\n customFieldSetId: line.customFieldSetId ?? 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 })\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 })\n applyOrderTotals(order, calculation.totals, calculation.lines.length)\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 */\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\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('sales.returns.orderMissing')\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 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 })\n applyOrderTotals(order, calculation.totals, calculation.lines.length)\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 })\n applyOrderTotals(order, calculation.totals, calculation.lines.length)\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 throw notFound('sales.returns.orderMissing')\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 { 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\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 })\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 })\n applyOrderTotals(order, calculation.totals, calculation.lines.length)\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 */\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\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('sales.returns.orderMissing')\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 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 })\n applyOrderTotals(order, calculation.totals, calculation.lines.length)\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 })\n applyOrderTotals(order, calculation.totals, calculation.lines.length)\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 throw notFound('sales.returns.orderMissing')\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,10 +16,7 @@ async function enforceSalesDocumentOptimisticLock(ctx, document, resourceKind) {
16
16
  request: ctx.request ?? null
17
17
  });
18
18
  }
19
- function cloneJson(value) {
20
- if (value === null || value === void 0) return value;
21
- return JSON.parse(JSON.stringify(value));
22
- }
19
+ import { cloneJson } from "../lib/json.js";
23
20
  function toNumericString(value) {
24
21
  if (value === void 0 || value === null) return null;
25
22
  return value.toString();
@@ -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 function cloneJson<T>(value: T): T {\n if (value === null || value === undefined) return value\n return JSON.parse(JSON.stringify(value)) as T\n}\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;AAEO,SAAS,UAAa,OAAa;AACxC,MAAI,UAAU,QAAQ,UAAU,OAAW,QAAO;AAClD,SAAO,KAAK,MAAM,KAAK,UAAU,KAAK,CAAC;AACzC;AAEO,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\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;",
6
6
  "names": []
7
7
  }
@@ -21,6 +21,7 @@ function createSalesOrderLineDraft(payload, id = draftId()) {
21
21
  const totalGross = normalizeNumber(payload.totalGrossAmount, unitPriceGross * quantity);
22
22
  const metadata = payload.metadata && typeof payload.metadata === "object" ? payload.metadata : null;
23
23
  const catalogSnapshot = payload.catalogSnapshot && typeof payload.catalogSnapshot === "object" ? payload.catalogSnapshot : null;
24
+ const draftDiscountAmount = payload.discountAmountBasis === "line" ? normalizeNumber(payload.discountAmount, 0) : normalizeNumber(payload.discountAmount, 0) * quantity;
24
25
  return {
25
26
  id,
26
27
  payload: { ...payload },
@@ -36,7 +37,7 @@ function createSalesOrderLineDraft(payload, id = draftId()) {
36
37
  currencyCode: typeof payload.currencyCode === "string" ? payload.currencyCode : null,
37
38
  unitPriceNet,
38
39
  unitPriceGross,
39
- discountAmount: normalizeNumber(payload.discountAmount, 0) * quantity,
40
+ discountAmount: draftDiscountAmount,
40
41
  discountPercent: normalizeNumber(payload.discountPercent, 0),
41
42
  taxRate,
42
43
  totalNet,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/sales/components/documents/SalesOrderDraftLines.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport { Plus } from 'lucide-react'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport { EmptyState } from '@open-mercato/ui/primitives/empty-state'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { useLocale, useT } from '@open-mercato/shared/lib/i18n/context'\nimport { LineItemDialog } from './LineItemDialog'\nimport type { SalesLineRecord } from './lineItemTypes'\nimport { formatMoney, normalizeNumber } from './lineItemUtils'\n\nexport type SalesOrderLineDraft = {\n id: string\n payload: Record<string, unknown>\n record: SalesLineRecord\n}\n\ntype SalesOrderDraftLinesProps = {\n currencyCode: string | null | undefined\n organizationId: string | null\n tenantId: string | null\n lines: SalesOrderLineDraft[]\n error?: string | null\n onChange: (lines: SalesOrderLineDraft[]) => void\n}\n\nfunction draftId(): string {\n return typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'\n ? crypto.randomUUID()\n : `draft-${Date.now()}-${Math.round(performance.now())}`\n}\n\nexport function createSalesOrderLineDraft(\n payload: Record<string, unknown>,\n id = draftId(),\n): SalesOrderLineDraft {\n const quantity = normalizeNumber(payload.quantity, 0)\n const unitPriceNet = normalizeNumber(payload.unitPriceNet, 0)\n const unitPriceGross = normalizeNumber(payload.unitPriceGross, unitPriceNet)\n const taxRate = normalizeNumber(payload.taxRate, 0)\n const totalNet = normalizeNumber(payload.totalNetAmount, unitPriceNet * quantity)\n const totalGross = normalizeNumber(payload.totalGrossAmount, unitPriceGross * quantity)\n const metadata = payload.metadata && typeof payload.metadata === 'object'\n ? payload.metadata as Record<string, unknown>\n : null\n const catalogSnapshot = payload.catalogSnapshot && typeof payload.catalogSnapshot === 'object'\n ? payload.catalogSnapshot as Record<string, unknown>\n : null\n\n return {\n id,\n payload: { ...payload },\n record: {\n id,\n name: typeof payload.name === 'string' ? payload.name : null,\n productId: typeof payload.productId === 'string' ? payload.productId : null,\n productVariantId: typeof payload.productVariantId === 'string' ? payload.productVariantId : null,\n quantity,\n quantityUnit: typeof payload.quantityUnit === 'string' ? payload.quantityUnit : null,\n normalizedQuantity: normalizeNumber(payload.normalizedQuantity, quantity),\n normalizedUnit: typeof payload.normalizedUnit === 'string' ? payload.normalizedUnit : null,\n currencyCode: typeof payload.currencyCode === 'string' ? payload.currencyCode : null,\n unitPriceNet,\n unitPriceGross,\n discountAmount: normalizeNumber(payload.discountAmount, 0) * quantity,\n discountPercent: normalizeNumber(payload.discountPercent, 0),\n taxRate,\n totalNet,\n totalGross,\n priceMode: payload.priceMode === 'net' ? 'net' : 'gross',\n uomSnapshot: null,\n metadata,\n catalogSnapshot,\n customFieldSetId: typeof payload.customFieldSetId === 'string' ? payload.customFieldSetId : null,\n customFields: payload.customFields && typeof payload.customFields === 'object'\n ? payload.customFields as Record<string, unknown>\n : null,\n status: null,\n statusEntryId: typeof payload.statusEntryId === 'string' ? payload.statusEntryId : null,\n },\n }\n}\n\nexport function SalesOrderDraftLines({\n currencyCode,\n organizationId,\n tenantId,\n lines,\n error,\n onChange,\n}: SalesOrderDraftLinesProps) {\n const t = useT()\n const locale = useLocale()\n const [dialogOpen, setDialogOpen] = React.useState(false)\n const [editing, setEditing] = React.useState<SalesOrderLineDraft | null>(null)\n\n const columns = React.useMemo<ColumnDef<SalesOrderLineDraft>[]>(() => [\n {\n id: 'name',\n header: t('sales.documents.items.table.product', 'Product'),\n cell: ({ row }) => row.original.record.name ?? t('sales.documents.items.untitled', 'Untitled'),\n },\n {\n id: 'quantity',\n header: t('sales.documents.items.table.quantity', 'Qty'),\n cell: ({ row }) => row.original.record.quantityUnit\n ? `${row.original.record.quantity} ${row.original.record.quantityUnit}`\n : row.original.record.quantity,\n },\n {\n id: 'unitPrice',\n header: t('sales.documents.items.table.unit', 'Unit price'),\n cell: ({ row }) => formatMoney(row.original.record.unitPriceGross, row.original.record.currencyCode ?? currencyCode ?? undefined, locale),\n },\n {\n id: 'total',\n header: t('sales.documents.items.table.total', 'Total'),\n cell: ({ row }) => formatMoney(row.original.record.totalGross, row.original.record.currencyCode ?? currencyCode ?? undefined, locale),\n },\n ], [currencyCode, locale, t])\n\n const openCreate = React.useCallback(() => {\n setEditing(null)\n setDialogOpen(true)\n }, [])\n\n const rowActions = React.useCallback((line: SalesOrderLineDraft) => (\n <RowActions\n items={[\n {\n id: 'edit',\n label: t('ui.actions.edit', 'Edit'),\n onSelect: () => {\n setEditing(line)\n setDialogOpen(true)\n },\n },\n {\n id: 'delete',\n label: t('ui.actions.delete', 'Delete'),\n destructive: true,\n onSelect: () => onChange(lines.filter((candidate) => candidate.id !== line.id)),\n },\n ]}\n />\n ), [lines, onChange, t])\n\n return (\n <div className=\"space-y-3\">\n <div className=\"flex items-center justify-between gap-4\">\n <div>\n <h3 className=\"text-sm font-medium\">{t('sales.orders.form.lines', 'Line Items')}</h3>\n <p className=\"text-sm text-muted-foreground\">\n {t('sales.orders.linesRequired', 'Add at least one line item before creating the order.')}\n </p>\n </div>\n <Button type=\"button\" variant=\"outline\" onClick={openCreate}>\n <Plus className=\"h-4 w-4\" aria-hidden=\"true\" />\n {t('sales.documents.items.add', 'Add item')}\n </Button>\n </div>\n {error ? <p className=\"text-sm text-status-error-text\" role=\"alert\">{error}</p> : null}\n <DataTable\n columns={columns}\n data={lines}\n embedded\n disableRowClick\n rowActions={rowActions}\n emptyState={(\n <EmptyState\n size=\"sm\"\n title={t('sales.documents.items.empty', 'No items yet.')}\n description={t('sales.orders.linesRequired', 'Add at least one line item before creating the order.')}\n actions={<Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={openCreate}>{t('sales.documents.items.add', 'Add item')}</Button>}\n />\n )}\n />\n <LineItemDialog\n open={dialogOpen}\n onOpenChange={(open) => {\n setDialogOpen(open)\n if (!open) setEditing(null)\n }}\n kind=\"order\"\n currencyCode={currencyCode}\n organizationId={organizationId}\n tenantId={tenantId}\n initialLine={editing?.record ?? null}\n onDraftSaved={(payload, lineId) => {\n const draft = createSalesOrderLineDraft(payload, lineId ?? undefined)\n onChange(lineId\n ? lines.map((line) => line.id === lineId ? draft : line)\n : [...lines, draft])\n }}\n />\n </div>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport { Plus } from 'lucide-react'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport { EmptyState } from '@open-mercato/ui/primitives/empty-state'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { useLocale, useT } from '@open-mercato/shared/lib/i18n/context'\nimport { LineItemDialog } from './LineItemDialog'\nimport type { SalesLineRecord } from './lineItemTypes'\nimport { formatMoney, normalizeNumber } from './lineItemUtils'\n\nexport type SalesOrderLineDraft = {\n id: string\n payload: Record<string, unknown>\n record: SalesLineRecord\n}\n\ntype SalesOrderDraftLinesProps = {\n currencyCode: string | null | undefined\n organizationId: string | null\n tenantId: string | null\n lines: SalesOrderLineDraft[]\n error?: string | null\n onChange: (lines: SalesOrderLineDraft[]) => void\n}\n\nfunction draftId(): string {\n return typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'\n ? crypto.randomUUID()\n : `draft-${Date.now()}-${Math.round(performance.now())}`\n}\n\nexport function createSalesOrderLineDraft(\n payload: Record<string, unknown>,\n id = draftId(),\n): SalesOrderLineDraft {\n const quantity = normalizeNumber(payload.quantity, 0)\n const unitPriceNet = normalizeNumber(payload.unitPriceNet, 0)\n const unitPriceGross = normalizeNumber(payload.unitPriceGross, unitPriceNet)\n const taxRate = normalizeNumber(payload.taxRate, 0)\n const totalNet = normalizeNumber(payload.totalNetAmount, unitPriceNet * quantity)\n const totalGross = normalizeNumber(payload.totalGrossAmount, unitPriceGross * quantity)\n const metadata = payload.metadata && typeof payload.metadata === 'object'\n ? payload.metadata as Record<string, unknown>\n : null\n const catalogSnapshot = payload.catalogSnapshot && typeof payload.catalogSnapshot === 'object'\n ? payload.catalogSnapshot as Record<string, unknown>\n : null\n // `payload` is forwarded verbatim to the create API, where a supplied\n // `discountAmount` is read per unit unless the caller says otherwise, while\n // `record` below drives the grid, which shows the discount for the whole\n // line. The multiplication is therefore the conversion between the two, not a\n // second copy of the per-unit misreading the calculation engine used to hold\n // \u2014 but it has to stop when the caller has explicitly said the amount is\n // already a line total, or the grid would show the discount quantity times\n // over while the persisted order shows it once.\n const draftDiscountAmount = payload.discountAmountBasis === 'line'\n ? normalizeNumber(payload.discountAmount, 0)\n : normalizeNumber(payload.discountAmount, 0) * quantity\n\n return {\n id,\n payload: { ...payload },\n record: {\n id,\n name: typeof payload.name === 'string' ? payload.name : null,\n productId: typeof payload.productId === 'string' ? payload.productId : null,\n productVariantId: typeof payload.productVariantId === 'string' ? payload.productVariantId : null,\n quantity,\n quantityUnit: typeof payload.quantityUnit === 'string' ? payload.quantityUnit : null,\n normalizedQuantity: normalizeNumber(payload.normalizedQuantity, quantity),\n normalizedUnit: typeof payload.normalizedUnit === 'string' ? payload.normalizedUnit : null,\n currencyCode: typeof payload.currencyCode === 'string' ? payload.currencyCode : null,\n unitPriceNet,\n unitPriceGross,\n discountAmount: draftDiscountAmount,\n discountPercent: normalizeNumber(payload.discountPercent, 0),\n taxRate,\n totalNet,\n totalGross,\n priceMode: payload.priceMode === 'net' ? 'net' : 'gross',\n uomSnapshot: null,\n metadata,\n catalogSnapshot,\n customFieldSetId: typeof payload.customFieldSetId === 'string' ? payload.customFieldSetId : null,\n customFields: payload.customFields && typeof payload.customFields === 'object'\n ? payload.customFields as Record<string, unknown>\n : null,\n status: null,\n statusEntryId: typeof payload.statusEntryId === 'string' ? payload.statusEntryId : null,\n },\n }\n}\n\nexport function SalesOrderDraftLines({\n currencyCode,\n organizationId,\n tenantId,\n lines,\n error,\n onChange,\n}: SalesOrderDraftLinesProps) {\n const t = useT()\n const locale = useLocale()\n const [dialogOpen, setDialogOpen] = React.useState(false)\n const [editing, setEditing] = React.useState<SalesOrderLineDraft | null>(null)\n\n const columns = React.useMemo<ColumnDef<SalesOrderLineDraft>[]>(() => [\n {\n id: 'name',\n header: t('sales.documents.items.table.product', 'Product'),\n cell: ({ row }) => row.original.record.name ?? t('sales.documents.items.untitled', 'Untitled'),\n },\n {\n id: 'quantity',\n header: t('sales.documents.items.table.quantity', 'Qty'),\n cell: ({ row }) => row.original.record.quantityUnit\n ? `${row.original.record.quantity} ${row.original.record.quantityUnit}`\n : row.original.record.quantity,\n },\n {\n id: 'unitPrice',\n header: t('sales.documents.items.table.unit', 'Unit price'),\n cell: ({ row }) => formatMoney(row.original.record.unitPriceGross, row.original.record.currencyCode ?? currencyCode ?? undefined, locale),\n },\n {\n id: 'total',\n header: t('sales.documents.items.table.total', 'Total'),\n cell: ({ row }) => formatMoney(row.original.record.totalGross, row.original.record.currencyCode ?? currencyCode ?? undefined, locale),\n },\n ], [currencyCode, locale, t])\n\n const openCreate = React.useCallback(() => {\n setEditing(null)\n setDialogOpen(true)\n }, [])\n\n const rowActions = React.useCallback((line: SalesOrderLineDraft) => (\n <RowActions\n items={[\n {\n id: 'edit',\n label: t('ui.actions.edit', 'Edit'),\n onSelect: () => {\n setEditing(line)\n setDialogOpen(true)\n },\n },\n {\n id: 'delete',\n label: t('ui.actions.delete', 'Delete'),\n destructive: true,\n onSelect: () => onChange(lines.filter((candidate) => candidate.id !== line.id)),\n },\n ]}\n />\n ), [lines, onChange, t])\n\n return (\n <div className=\"space-y-3\">\n <div className=\"flex items-center justify-between gap-4\">\n <div>\n <h3 className=\"text-sm font-medium\">{t('sales.orders.form.lines', 'Line Items')}</h3>\n <p className=\"text-sm text-muted-foreground\">\n {t('sales.orders.linesRequired', 'Add at least one line item before creating the order.')}\n </p>\n </div>\n <Button type=\"button\" variant=\"outline\" onClick={openCreate}>\n <Plus className=\"h-4 w-4\" aria-hidden=\"true\" />\n {t('sales.documents.items.add', 'Add item')}\n </Button>\n </div>\n {error ? <p className=\"text-sm text-status-error-text\" role=\"alert\">{error}</p> : null}\n <DataTable\n columns={columns}\n data={lines}\n embedded\n disableRowClick\n rowActions={rowActions}\n emptyState={(\n <EmptyState\n size=\"sm\"\n title={t('sales.documents.items.empty', 'No items yet.')}\n description={t('sales.orders.linesRequired', 'Add at least one line item before creating the order.')}\n actions={<Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={openCreate}>{t('sales.documents.items.add', 'Add item')}</Button>}\n />\n )}\n />\n <LineItemDialog\n open={dialogOpen}\n onOpenChange={(open) => {\n setDialogOpen(open)\n if (!open) setEditing(null)\n }}\n kind=\"order\"\n currencyCode={currencyCode}\n organizationId={organizationId}\n tenantId={tenantId}\n initialLine={editing?.record ?? null}\n onDraftSaved={(payload, lineId) => {\n const draft = createSalesOrderLineDraft(payload, lineId ?? undefined)\n onChange(lineId\n ? lines.map((line) => line.id === lineId ? draft : line)\n : [...lines, draft])\n }}\n />\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -216,6 +216,9 @@ const linePricingSchema = z.object({
216
216
  priceMode: z.enum(["net", "gross"]).optional(),
217
217
  taxRateId: uuid().optional(),
218
218
  discountAmount: decimal({ min: 0 }).optional(),
219
+ // How to read a supplied `discountAmount`. Omitting it reproduces the meaning
220
+ // the API has always documented — per unit — so no existing caller changes.
221
+ discountAmountBasis: z.enum(["unit", "line"]).optional(),
219
222
  discountPercent: percentage().optional(),
220
223
  taxRate: percentage().optional(),
221
224
  taxAmount: decimal({ min: 0 }).optional(),