@open-mercato/core 0.10.1-develop.7774.1.9a13ef7580 → 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.
- package/.turbo/turbo-build.log +1 -1
- package/dist/modules/cart/lib/promotionContext.js +3 -2
- package/dist/modules/cart/lib/promotionContext.js.map +2 -2
- package/dist/modules/promotions/lib/promotions-service.js +5 -1
- package/dist/modules/promotions/lib/promotions-service.js.map +2 -2
- package/dist/modules/promotions/services/promotion-cache.js +35 -2
- package/dist/modules/promotions/services/promotion-cache.js.map +3 -3
- package/dist/modules/sales/api/order-lines/batch/route.js +227 -0
- package/dist/modules/sales/api/order-lines/batch/route.js.map +7 -0
- package/dist/modules/sales/commands/documents.js +356 -129
- package/dist/modules/sales/commands/documents.js.map +2 -2
- package/dist/modules/sales/data/validators.js +53 -0
- package/dist/modules/sales/data/validators.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/cart/lib/promotionContext.ts +3 -7
- package/src/modules/promotions/lib/promotions-service.ts +5 -3
- package/src/modules/promotions/services/promotion-cache.ts +41 -4
- package/src/modules/sales/api/order-lines/batch/route.ts +312 -0
- package/src/modules/sales/commands/documents.ts +504 -179
- package/src/modules/sales/data/validators.ts +71 -0
- package/src/modules/sales/i18n/de.json +9 -0
- package/src/modules/sales/i18n/en.json +9 -0
- package/src/modules/sales/i18n/es.json +9 -0
- package/src/modules/sales/i18n/ko.json +9 -0
- package/src/modules/sales/i18n/pl.json +9 -0
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
5781
|
-
|
|
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
|
-
|
|
5804
|
-
|
|
5805
|
-
|
|
5806
|
-
|
|
5807
|
-
|
|
5808
|
-
|
|
5809
|
-
|
|
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
|
-
|
|
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);
|