@open-mercato/core 0.10.1-develop.7775.1.2a1f553826 → 0.10.1-develop.7776.1.3032ea0741

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.
@@ -76,6 +76,7 @@ import {
76
76
  resolveSuppliedOrderPaymentLedgerFields,
77
77
  amountsModeSchema,
78
78
  orderLineCreateSchema,
79
+ orderLineBulkUpsertSchema,
79
80
  orderHeaderTotalsSchema,
80
81
  orderAdjustmentCreateSchema,
81
82
  invoiceCreateSchema,
@@ -5656,21 +5657,28 @@ async function assertShippedOrderLineChangeAllowed(existingSnapshot, nextSnapsho
5656
5657
  )
5657
5658
  });
5658
5659
  }
5659
- async function assertShippedOrderLineEditable(em, order, existingSnapshot, parsed) {
5660
+ async function assertShippedOrderLineEditableAgainst(shippedQuantityByLine, existingSnapshot, parsed) {
5660
5661
  const lineId = existingSnapshot?.id;
5661
5662
  if (!existingSnapshot || !lineId) return;
5662
- const shippedByLine = await loadShippedQuantityByLine(em, order.id, {
5663
- tenantId: order.tenantId,
5664
- organizationId: order.organizationId
5665
- });
5666
- const shippedQuantity = shippedByLine.get(lineId) ?? 0;
5667
5663
  await assertShippedOrderLineChangeAllowed(
5668
5664
  existingSnapshot,
5669
5665
  parsed,
5670
- shippedQuantity,
5666
+ shippedQuantityByLine.get(lineId) ?? 0,
5671
5667
  false
5672
5668
  );
5673
5669
  }
5670
+ async function assertShippedOrderLineEditable(em, order, existingSnapshot, parsed) {
5671
+ if (!existingSnapshot?.id) return;
5672
+ const shippedQuantityByLine = await loadShippedQuantityByLine(em, order.id, {
5673
+ tenantId: order.tenantId,
5674
+ organizationId: order.organizationId
5675
+ });
5676
+ await assertShippedOrderLineEditableAgainst(
5677
+ shippedQuantityByLine,
5678
+ existingSnapshot,
5679
+ parsed
5680
+ );
5681
+ }
5674
5682
  async function assertShippedOrderGraphRestorable(em, order, existingLines, snapshot) {
5675
5683
  const shippedByLine = await loadShippedQuantityByLine(em, order.id, {
5676
5684
  tenantId: order.tenantId,
@@ -5707,6 +5715,140 @@ async function assertOrderAcceptsNewLine(order, existingSnapshot) {
5707
5715
  )
5708
5716
  });
5709
5717
  }
5718
+ function createTaxCalculationServiceResolver(container) {
5719
+ let resolved = false;
5720
+ let service = null;
5721
+ return () => {
5722
+ if (resolved) return service;
5723
+ resolved = true;
5724
+ try {
5725
+ service = container.resolve(
5726
+ "taxCalculationService"
5727
+ );
5728
+ } catch {
5729
+ service = null;
5730
+ }
5731
+ return service;
5732
+ };
5733
+ }
5734
+ async function buildUpsertedOrderLineSnapshot(dependencies, parsed, existingSnapshot, fallbackLineNumber) {
5735
+ const { em, order, uomResolver, resolveTaxCalculationService } = dependencies;
5736
+ const priceMode = parsed.priceMode === "gross" ? "gross" : parsed.priceMode === "net" ? "net" : null;
5737
+ let unitPriceNet = parsed.unitPriceNet ?? existingSnapshot?.unitPriceNet ?? null;
5738
+ let unitPriceGross = parsed.unitPriceGross ?? existingSnapshot?.unitPriceGross ?? null;
5739
+ let taxRate = parsed.taxRate ?? existingSnapshot?.taxRate ?? null;
5740
+ const unitPriceSupplied = unitPriceNet !== null || unitPriceGross !== null;
5741
+ if (priceMode && (unitPriceNet === null || unitPriceGross === null)) {
5742
+ const taxService = resolveTaxCalculationService();
5743
+ if (taxService) {
5744
+ const taxResult = await taxService.calculateUnitAmounts({
5745
+ amount: priceMode === "gross" ? unitPriceGross ?? unitPriceNet ?? 0 : unitPriceNet ?? unitPriceGross ?? 0,
5746
+ mode: priceMode,
5747
+ organizationId: parsed.organizationId,
5748
+ tenantId: parsed.tenantId,
5749
+ taxRateId: parsed.taxRateId ?? void 0,
5750
+ taxRate: taxRate ?? void 0
5751
+ });
5752
+ unitPriceNet = unitPriceNet ?? taxResult.netAmount;
5753
+ unitPriceGross = unitPriceGross ?? taxResult.grossAmount;
5754
+ taxRate = taxResult.taxRate ?? taxRate;
5755
+ }
5756
+ }
5757
+ const metadata = typeof parsed.metadata === "object" && parsed.metadata ? { ...parsed.metadata } : existingSnapshot?.metadata ? cloneJson(existingSnapshot.metadata) : {};
5758
+ if (parsed.priceId) metadata.priceId = parsed.priceId;
5759
+ if (priceMode) metadata.priceMode = priceMode;
5760
+ const statusEntryId = parsed.statusEntryId ?? existingSnapshot?.statusEntryId ?? null;
5761
+ const lineId = parsed.id ?? existingSnapshot?.id ?? randomUUID();
5762
+ const lineUomInput = {
5763
+ productId: parsed.productId ?? existingSnapshot?.productId ?? null,
5764
+ productVariantId: parsed.productVariantId ?? existingSnapshot?.productVariantId ?? null,
5765
+ quantity: parsed.quantity ?? existingSnapshot?.quantity ?? 0,
5766
+ quantityUnit: parsed.quantityUnit ?? existingSnapshot?.quantityUnit ?? null,
5767
+ normalizedQuantity: existingSnapshot?.normalizedQuantity ?? null,
5768
+ normalizedUnit: existingSnapshot?.normalizedUnit ?? null,
5769
+ uomSnapshot: existingSnapshot?.uomSnapshot ?? null
5770
+ };
5771
+ let normalizedUom = await normalizeLineUom({
5772
+ em,
5773
+ resolver: uomResolver,
5774
+ organizationId: order.organizationId,
5775
+ tenantId: order.tenantId,
5776
+ line: {
5777
+ ...lineUomInput,
5778
+ unitPriceNet: unitPriceNet ?? 0,
5779
+ unitPriceGross: unitPriceGross ?? unitPriceNet ?? 0
5780
+ }
5781
+ });
5782
+ const convertedPrices = convertLineUnitPricesOnUnitChange({
5783
+ existingSnapshot,
5784
+ nextQuantityUnit: normalizedUom.quantityUnit,
5785
+ nextUomSnapshot: normalizedUom.uomSnapshot,
5786
+ unitPriceNet,
5787
+ unitPriceGross
5788
+ });
5789
+ if (convertedPrices.didConvert) {
5790
+ unitPriceNet = convertedPrices.unitPriceNet;
5791
+ unitPriceGross = convertedPrices.unitPriceGross;
5792
+ normalizedUom = await normalizeLineUom({
5793
+ em,
5794
+ resolver: uomResolver,
5795
+ organizationId: order.organizationId,
5796
+ tenantId: order.tenantId,
5797
+ line: {
5798
+ ...lineUomInput,
5799
+ unitPriceNet: unitPriceNet ?? 0,
5800
+ unitPriceGross: unitPriceGross ?? unitPriceNet ?? 0
5801
+ }
5802
+ });
5803
+ }
5804
+ const updatedSnapshot = {
5805
+ id: lineId,
5806
+ lineNumber: parsed.lineNumber ?? existingSnapshot?.lineNumber ?? fallbackLineNumber,
5807
+ kind: parsed.kind ?? existingSnapshot?.kind ?? "product",
5808
+ productId: parsed.productId ?? existingSnapshot?.productId ?? null,
5809
+ productVariantId: parsed.productVariantId ?? existingSnapshot?.productVariantId ?? null,
5810
+ name: parsed.name ?? existingSnapshot?.name ?? null,
5811
+ description: parsed.description ?? existingSnapshot?.description ?? null,
5812
+ comment: parsed.comment ?? existingSnapshot?.comment ?? null,
5813
+ quantity: normalizedUom.quantity,
5814
+ quantityUnit: normalizedUom.quantityUnit,
5815
+ normalizedQuantity: normalizedUom.normalizedQuantity,
5816
+ normalizedUnit: normalizedUom.normalizedUnit,
5817
+ uomSnapshot: normalizedUom.uomSnapshot ? cloneJson(normalizedUom.uomSnapshot) : null,
5818
+ currencyCode: parsed.currencyCode ?? existingSnapshot?.currencyCode ?? order.currencyCode,
5819
+ unitPriceNet: unitPriceNet ?? 0,
5820
+ unitPriceGross: unitPriceGross ?? unitPriceNet ?? 0,
5821
+ ...resolveUpsertDiscountFields(
5822
+ parsed.discountAmount,
5823
+ parsed.discountAmountBasis,
5824
+ existingSnapshot
5825
+ ),
5826
+ discountPercent: parsed.discountPercent ?? existingSnapshot?.discountPercent ?? 0,
5827
+ taxRate: taxRate ?? 0,
5828
+ totalNetAmount: parsed.totalNetAmount ?? existingSnapshot?.totalNetAmount ?? null,
5829
+ ...resolveUpsertTotalsOrigin(parsed.totalNetAmount, existingSnapshot),
5830
+ configuration: parsed.configuration ?? existingSnapshot?.configuration ?? null,
5831
+ promotionCode: parsed.promotionCode ?? existingSnapshot?.promotionCode ?? null,
5832
+ metadata,
5833
+ customFieldSetId: parsed.customFieldSetId ?? existingSnapshot?.customFieldSetId ?? null,
5834
+ customFields: parsed.customFields && typeof parsed.customFields === "object" ? cloneJson(parsed.customFields) : existingSnapshot?.customFields ?? null,
5835
+ amountsMode: order.totalsMode ?? "computed",
5836
+ statusEntryId,
5837
+ catalogSnapshot: parsed.catalogSnapshot ?? existingSnapshot?.catalogSnapshot ?? null,
5838
+ promotionSnapshot: parsed.promotionSnapshot ?? existingSnapshot?.promotionSnapshot ?? null
5839
+ };
5840
+ Object.assign(
5841
+ updatedSnapshot,
5842
+ resolveUpsertCalculatedAmounts(parsed, updatedSnapshot, existingSnapshot)
5843
+ );
5844
+ if (isExternalAmountsMode(order.totalsMode ?? "computed")) {
5845
+ await assertExternalLineComplete(
5846
+ { ...updatedSnapshot, unitPriceNet: unitPriceSupplied ? unitPriceNet : null },
5847
+ updatedSnapshot.lineNumber ?? 1
5848
+ );
5849
+ }
5850
+ return updatedSnapshot;
5851
+ }
5710
5852
  const orderLineUpsertCommand = {
5711
5853
  id: "sales.orders.lines.upsert",
5712
5854
  async prepare(input) {
@@ -5734,131 +5876,21 @@ const orderLineUpsertCommand = {
5734
5876
  const suppliedTotals = isExternalAmountsMode(totalsMode) ? buildExternalHeaderTotals(parsed.orderTotals) : null;
5735
5877
  await assertOrderAcceptsNewLine(order, existingSnapshot);
5736
5878
  await assertShippedOrderLineEditable(em, order, existingSnapshot, parsed);
5737
- const priceMode = parsed.priceMode === "gross" ? "gross" : parsed.priceMode === "net" ? "net" : null;
5738
- let unitPriceNet = parsed.unitPriceNet ?? existingSnapshot?.unitPriceNet ?? null;
5739
- let unitPriceGross = parsed.unitPriceGross ?? existingSnapshot?.unitPriceGross ?? null;
5740
- let taxRate = parsed.taxRate ?? existingSnapshot?.taxRate ?? null;
5741
- const unitPriceSupplied = unitPriceNet !== null || unitPriceGross !== null;
5742
- if (priceMode && (unitPriceNet === null || unitPriceGross === null)) {
5743
- let taxService = null;
5744
- try {
5745
- taxService = ctx.container.resolve(
5746
- "taxCalculationService"
5747
- );
5748
- } catch {
5749
- taxService = null;
5750
- }
5751
- if (taxService) {
5752
- const taxResult = await taxService.calculateUnitAmounts({
5753
- amount: priceMode === "gross" ? unitPriceGross ?? unitPriceNet ?? 0 : unitPriceNet ?? unitPriceGross ?? 0,
5754
- mode: priceMode,
5755
- organizationId: parsed.organizationId,
5756
- tenantId: parsed.tenantId,
5757
- taxRateId: parsed.taxRateId ?? void 0,
5758
- taxRate: taxRate ?? void 0
5759
- });
5760
- unitPriceNet = unitPriceNet ?? taxResult.netAmount;
5761
- unitPriceGross = unitPriceGross ?? taxResult.grossAmount;
5762
- taxRate = taxResult.taxRate ?? taxRate;
5763
- }
5764
- }
5765
- const metadata = typeof parsed.metadata === "object" && parsed.metadata ? { ...parsed.metadata } : existingSnapshot?.metadata ? cloneJson(existingSnapshot.metadata) : {};
5766
- if (parsed.priceId) metadata.priceId = parsed.priceId;
5767
- if (priceMode) metadata.priceMode = priceMode;
5768
- const statusEntryId = parsed.statusEntryId ?? existingSnapshot?.statusEntryId ?? null;
5769
- const lineId = parsed.id ?? existingSnapshot?.id ?? randomUUID();
5770
- const lineUomInput = {
5771
- productId: parsed.productId ?? existingSnapshot?.productId ?? null,
5772
- productVariantId: parsed.productVariantId ?? existingSnapshot?.productVariantId ?? null,
5773
- quantity: parsed.quantity ?? existingSnapshot?.quantity ?? 0,
5774
- quantityUnit: parsed.quantityUnit ?? existingSnapshot?.quantityUnit ?? null,
5775
- normalizedQuantity: existingSnapshot?.normalizedQuantity ?? null,
5776
- normalizedUnit: existingSnapshot?.normalizedUnit ?? null,
5777
- uomSnapshot: existingSnapshot?.uomSnapshot ?? null
5778
- };
5779
5879
  const uomResolver = createUomResolver();
5780
- let normalizedUom = await normalizeLineUom({
5781
- em,
5782
- resolver: uomResolver,
5783
- organizationId: order.organizationId,
5784
- tenantId: order.tenantId,
5785
- line: {
5786
- ...lineUomInput,
5787
- unitPriceNet: unitPriceNet ?? 0,
5788
- unitPriceGross: unitPriceGross ?? unitPriceNet ?? 0
5789
- }
5790
- });
5791
- const convertedPrices = convertLineUnitPricesOnUnitChange({
5792
- existingSnapshot,
5793
- nextQuantityUnit: normalizedUom.quantityUnit,
5794
- nextUomSnapshot: normalizedUom.uomSnapshot,
5795
- unitPriceNet,
5796
- unitPriceGross
5797
- });
5798
- if (convertedPrices.didConvert) {
5799
- unitPriceNet = convertedPrices.unitPriceNet;
5800
- unitPriceGross = convertedPrices.unitPriceGross;
5801
- normalizedUom = await normalizeLineUom({
5880
+ const updatedSnapshot = await buildUpsertedOrderLineSnapshot(
5881
+ {
5802
5882
  em,
5803
- resolver: uomResolver,
5804
- organizationId: order.organizationId,
5805
- tenantId: order.tenantId,
5806
- line: {
5807
- ...lineUomInput,
5808
- unitPriceNet: unitPriceNet ?? 0,
5809
- unitPriceGross: unitPriceGross ?? unitPriceNet ?? 0
5810
- }
5811
- });
5812
- }
5813
- const updatedSnapshot = {
5814
- id: lineId,
5815
- lineNumber: parsed.lineNumber ?? existingSnapshot?.lineNumber ?? lineSnapshots.length + 1,
5816
- kind: parsed.kind ?? existingSnapshot?.kind ?? "product",
5817
- productId: parsed.productId ?? existingSnapshot?.productId ?? null,
5818
- productVariantId: parsed.productVariantId ?? existingSnapshot?.productVariantId ?? null,
5819
- name: parsed.name ?? existingSnapshot?.name ?? null,
5820
- description: parsed.description ?? existingSnapshot?.description ?? null,
5821
- comment: parsed.comment ?? existingSnapshot?.comment ?? null,
5822
- quantity: normalizedUom.quantity,
5823
- quantityUnit: normalizedUom.quantityUnit,
5824
- normalizedQuantity: normalizedUom.normalizedQuantity,
5825
- normalizedUnit: normalizedUom.normalizedUnit,
5826
- uomSnapshot: normalizedUom.uomSnapshot ? cloneJson(normalizedUom.uomSnapshot) : null,
5827
- currencyCode: parsed.currencyCode ?? existingSnapshot?.currencyCode ?? order.currencyCode,
5828
- unitPriceNet: unitPriceNet ?? 0,
5829
- unitPriceGross: unitPriceGross ?? unitPriceNet ?? 0,
5830
- ...resolveUpsertDiscountFields(
5831
- parsed.discountAmount,
5832
- parsed.discountAmountBasis,
5833
- existingSnapshot
5834
- ),
5835
- discountPercent: parsed.discountPercent ?? existingSnapshot?.discountPercent ?? 0,
5836
- taxRate: taxRate ?? 0,
5837
- totalNetAmount: parsed.totalNetAmount ?? existingSnapshot?.totalNetAmount ?? null,
5838
- ...resolveUpsertTotalsOrigin(parsed.totalNetAmount, existingSnapshot),
5839
- configuration: parsed.configuration ?? existingSnapshot?.configuration ?? null,
5840
- promotionCode: parsed.promotionCode ?? existingSnapshot?.promotionCode ?? null,
5841
- metadata,
5842
- customFieldSetId: parsed.customFieldSetId ?? existingSnapshot?.customFieldSetId ?? null,
5843
- customFields: parsed.customFields && typeof parsed.customFields === "object" ? cloneJson(parsed.customFields) : existingSnapshot?.customFields ?? null,
5844
- amountsMode: totalsMode
5845
- };
5846
- Object.assign(
5847
- updatedSnapshot,
5848
- resolveUpsertCalculatedAmounts(parsed, updatedSnapshot, existingSnapshot)
5883
+ order,
5884
+ uomResolver,
5885
+ resolveTaxCalculationService: createTaxCalculationServiceResolver(
5886
+ ctx.container
5887
+ )
5888
+ },
5889
+ parsed,
5890
+ existingSnapshot,
5891
+ lineSnapshots.length + 1
5849
5892
  );
5850
- if (isExternalAmountsMode(totalsMode)) {
5851
- await assertExternalLineComplete(
5852
- {
5853
- ...updatedSnapshot,
5854
- unitPriceNet: unitPriceSupplied ? unitPriceNet : null
5855
- },
5856
- updatedSnapshot.lineNumber ?? 1
5857
- );
5858
- }
5859
- updatedSnapshot.statusEntryId = statusEntryId;
5860
- updatedSnapshot.catalogSnapshot = parsed.catalogSnapshot ?? existingSnapshot?.catalogSnapshot ?? null;
5861
- updatedSnapshot.promotionSnapshot = parsed.promotionSnapshot ?? existingSnapshot?.promotionSnapshot ?? null;
5893
+ const lineId = updatedSnapshot.id;
5862
5894
  let nextLines = parsed.id ? lineSnapshots.map(
5863
5895
  (line) => line.id === parsed.id ? updatedSnapshot : line
5864
5896
  ) : [...lineSnapshots, updatedSnapshot];
@@ -6069,6 +6101,200 @@ const orderLineDeleteCommand = {
6069
6101
  await undoOrderGraph(ctx, before, payload?.after);
6070
6102
  }
6071
6103
  };
6104
+ function placeOrderLine(lines, line, targetPosition) {
6105
+ const currentIndex = lines.findIndex((entry) => entry.id === line.id);
6106
+ const remaining = currentIndex === -1 ? [...lines] : [...lines.slice(0, currentIndex), ...lines.slice(currentIndex + 1)];
6107
+ if (targetPosition == null) {
6108
+ if (currentIndex === -1) {
6109
+ remaining.push(line);
6110
+ return remaining;
6111
+ }
6112
+ remaining.splice(currentIndex, 0, line);
6113
+ return remaining;
6114
+ }
6115
+ const index = Math.min(Math.max(targetPosition - 1, 0), remaining.length);
6116
+ remaining.splice(index, 0, line);
6117
+ return remaining;
6118
+ }
6119
+ const orderLineBulkUpsertCommand = {
6120
+ id: "sales.orders.lines.upsert_many",
6121
+ async prepare(input) {
6122
+ return markLockedGraphWrite(input);
6123
+ },
6124
+ async execute(input, ctx) {
6125
+ const { translate } = await resolveTranslations();
6126
+ const parsed = orderLineBulkUpsertSchema.parse(
6127
+ input?.body ?? {}
6128
+ );
6129
+ return writeLockedOrderGraph(input, ctx, parsed.orderId, "Sales order not found", async (em, order) => {
6130
+ const totalsMode = order.totalsMode ?? "computed";
6131
+ await assertUniformAmountsMode(totalsMode, parsed.lines);
6132
+ if (isExternalAmountsMode(totalsMode)) {
6133
+ await requireOrderTotalsForExternalWrite(parsed.orderTotals);
6134
+ }
6135
+ const suppliedTotals = isExternalAmountsMode(totalsMode) ? buildExternalHeaderTotals(parsed.orderTotals) : null;
6136
+ const [existingLines, adjustments] = await Promise.all([
6137
+ em.find(SalesOrderLine, { order }, { orderBy: { lineNumber: "asc" } }),
6138
+ em.find(
6139
+ SalesOrderAdjustment,
6140
+ { order },
6141
+ { orderBy: { position: "asc" } }
6142
+ )
6143
+ ]);
6144
+ const existingSnapshotsById = new Map(
6145
+ existingLines.map((line) => [line.id, mapOrderLineEntityToSnapshot(line)])
6146
+ );
6147
+ const deleteIds = new Set(parsed.deleteIds);
6148
+ const referencedIds = [
6149
+ ...deleteIds,
6150
+ ...parsed.lines.flatMap((line) => line.id ? [line.id] : [])
6151
+ ];
6152
+ for (const id of referencedIds) {
6153
+ if (existingSnapshotsById.has(id)) continue;
6154
+ throw notFound(
6155
+ translate(
6156
+ "sales.documents.detail.error",
6157
+ "Document not found or inaccessible."
6158
+ )
6159
+ );
6160
+ }
6161
+ if (deleteIds.size) {
6162
+ const shippedItems = await em.find(SalesShipmentItem, {
6163
+ orderLine: { $in: [...deleteIds] },
6164
+ shipment: { deletedAt: null }
6165
+ });
6166
+ if (shippedItems.length) {
6167
+ throw new CrudHttpError(409, {
6168
+ error: translate(
6169
+ "sales.documents.items.errorDeleteShipped",
6170
+ "Cannot delete a line that has shipped items."
6171
+ )
6172
+ });
6173
+ }
6174
+ }
6175
+ const editsExistingLine = parsed.lines.some(
6176
+ (line) => line.id != null && existingSnapshotsById.has(line.id)
6177
+ );
6178
+ const shippedQuantityByLine = editsExistingLine ? await loadShippedQuantityByLine(em, order.id, {
6179
+ tenantId: order.tenantId,
6180
+ organizationId: order.organizationId
6181
+ }) : /* @__PURE__ */ new Map();
6182
+ const dependencies = {
6183
+ em,
6184
+ order,
6185
+ uomResolver: createUomResolver(),
6186
+ resolveTaxCalculationService: createTaxCalculationServiceResolver(
6187
+ ctx.container
6188
+ )
6189
+ };
6190
+ let nextLines = [...existingSnapshotsById.values()].filter(
6191
+ (line) => !deleteIds.has(line.id)
6192
+ );
6193
+ const lineIds = [];
6194
+ for (const entry of parsed.lines) {
6195
+ const existingSnapshot = entry.id ? existingSnapshotsById.get(entry.id) ?? null : null;
6196
+ await assertOrderAcceptsNewLine(order, existingSnapshot);
6197
+ await assertShippedOrderLineEditableAgainst(
6198
+ shippedQuantityByLine,
6199
+ existingSnapshot,
6200
+ entry
6201
+ );
6202
+ const upserted = await buildUpsertedOrderLineSnapshot(
6203
+ dependencies,
6204
+ {
6205
+ ...entry,
6206
+ organizationId: parsed.organizationId,
6207
+ tenantId: parsed.tenantId
6208
+ },
6209
+ existingSnapshot,
6210
+ nextLines.length + 1
6211
+ );
6212
+ nextLines = placeOrderLine(nextLines, upserted, entry.lineNumber ?? null);
6213
+ lineIds.push(upserted.id);
6214
+ }
6215
+ if (!nextLines.length) {
6216
+ throw new CrudHttpError(409, {
6217
+ error: translate(
6218
+ "sales.documents.items.errorDeleteLast",
6219
+ "An order must contain at least one line item."
6220
+ )
6221
+ });
6222
+ }
6223
+ const sourceInputs = nextLines.map((line, index) => ({
6224
+ ...line,
6225
+ statusEntryId: line.statusEntryId ?? null,
6226
+ catalogSnapshot: line.catalogSnapshot ?? null,
6227
+ promotionSnapshot: line.promotionSnapshot ?? null,
6228
+ organizationId: order.organizationId,
6229
+ tenantId: order.tenantId,
6230
+ orderId: order.id,
6231
+ lineNumber: index + 1
6232
+ }));
6233
+ const calcLines = sourceInputs.map(
6234
+ (line, index) => createLineSnapshotFromInput(line, line.lineNumber ?? index + 1)
6235
+ );
6236
+ const adjustmentDrafts = adjustments.map(mapOrderAdjustmentToDraft);
6237
+ const salesCalculationService = ctx.container.resolve("salesCalculationService");
6238
+ const calculationContext = buildCalculationContext({
6239
+ tenantId: order.tenantId,
6240
+ organizationId: order.organizationId,
6241
+ currencyCode: order.currencyCode,
6242
+ shippingSnapshot: order.shippingMethodSnapshot,
6243
+ paymentSnapshot: order.paymentMethodSnapshot,
6244
+ shippingMethodId: order.shippingMethodId ?? null,
6245
+ paymentMethodId: order.paymentMethodId ?? null,
6246
+ shippingMethodCode: order.shippingMethodCode ?? null,
6247
+ paymentMethodCode: order.paymentMethodCode ?? null
6248
+ });
6249
+ const calculation = await salesCalculationService.calculateDocumentTotals({
6250
+ documentKind: "order",
6251
+ lines: calcLines,
6252
+ adjustments: adjustmentDrafts,
6253
+ context: calculationContext,
6254
+ existingTotals: resolveExistingPaymentTotals(order),
6255
+ totalsMode,
6256
+ suppliedTotals
6257
+ });
6258
+ await applyOrderLineResults({
6259
+ em,
6260
+ order,
6261
+ calculation,
6262
+ sourceLines: sourceInputs,
6263
+ existingLines
6264
+ });
6265
+ applyOrderTotals(order, calculation.totals, calculation.lines.length);
6266
+ return { result: { orderId: order.id, lineIds }, calculation };
6267
+ });
6268
+ },
6269
+ captureAfter: async (_input, result, ctx) => captureLockedOrderGraphAfter(ctx, result, result.orderId),
6270
+ buildLog: async ({ result, snapshots }) => {
6271
+ const before = resolveLockedOrderGraphBefore(result, snapshots.before);
6272
+ const after = snapshots.after;
6273
+ if (!after) return null;
6274
+ const { translate } = await resolveTranslations();
6275
+ return {
6276
+ actionLabel: translate(
6277
+ "sales.audit.orders.lines.upsert_many",
6278
+ "Update order lines"
6279
+ ),
6280
+ resourceKind: "sales.order",
6281
+ resourceId: result.orderId,
6282
+ tenantId: after.order.tenantId,
6283
+ organizationId: after.order.organizationId,
6284
+ snapshotBefore: before ?? null,
6285
+ snapshotAfter: after,
6286
+ payload: {
6287
+ undo: { before, after }
6288
+ }
6289
+ };
6290
+ },
6291
+ undo: async ({ logEntry, ctx }) => {
6292
+ const payload = extractUndoPayload(logEntry);
6293
+ const before = payload?.before;
6294
+ if (!before) return;
6295
+ await undoOrderGraph(ctx, before, payload?.after);
6296
+ }
6297
+ };
6072
6298
  const quoteLineUpsertCommand = {
6073
6299
  id: "sales.quotes.lines.upsert",
6074
6300
  async prepare(input) {
@@ -8002,6 +8228,7 @@ registerCommand(updateOrderCommand);
8002
8228
  registerCommand(createOrderCommand);
8003
8229
  registerCommand(deleteOrderCommand);
8004
8230
  registerCommand(orderLineUpsertCommand);
8231
+ registerCommand(orderLineBulkUpsertCommand);
8005
8232
  registerCommand(orderLineDeleteCommand);
8006
8233
  registerCommand(quoteLineUpsertCommand);
8007
8234
  registerCommand(quoteLineDeleteCommand);