@open-mercato/core 0.10.1-develop.7723.1.3b2ea82ea7 → 0.10.1-develop.7726.1.a447cc5525
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/generated/entities/sales_order/index.js +2 -0
- package/dist/generated/entities/sales_order/index.js.map +2 -2
- package/dist/generated/entities/sales_order_line/index.js +2 -0
- package/dist/generated/entities/sales_order_line/index.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +2 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/modules/planner/api/availability-date-specific.js +2 -7
- package/dist/modules/planner/api/availability-date-specific.js.map +2 -2
- package/dist/modules/planner/commands/availability-date-specific.js +14 -31
- package/dist/modules/planner/commands/availability-date-specific.js.map +2 -2
- package/dist/modules/planner/commands/availability-weekly.js +8 -22
- package/dist/modules/planner/commands/availability-weekly.js.map +2 -2
- package/dist/modules/planner/lib/availabilityMerge.js +10 -18
- package/dist/modules/planner/lib/availabilityMerge.js.map +2 -2
- package/dist/modules/planner/lib/availabilityTimezone.js +161 -0
- package/dist/modules/planner/lib/availabilityTimezone.js.map +7 -0
- package/dist/modules/sales/api/documents/factory.js +3 -0
- package/dist/modules/sales/api/documents/factory.js.map +2 -2
- package/dist/modules/sales/api/order-lines/route.js +2 -1
- package/dist/modules/sales/api/order-lines/route.js.map +2 -2
- package/dist/modules/sales/backend/sales/documents/[id]/page.js +83 -6
- package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
- package/dist/modules/sales/commands/documents.js +141 -18
- package/dist/modules/sales/commands/documents.js.map +2 -2
- package/dist/modules/sales/commands/returns.js +24 -7
- package/dist/modules/sales/commands/returns.js.map +2 -2
- package/dist/modules/sales/components/documents/AdjustmentsSection.js +17 -11
- package/dist/modules/sales/components/documents/AdjustmentsSection.js.map +2 -2
- package/dist/modules/sales/components/documents/DocumentTotals.js +21 -3
- package/dist/modules/sales/components/documents/DocumentTotals.js.map +2 -2
- package/dist/modules/sales/components/documents/ItemsSection.js +23 -12
- package/dist/modules/sales/components/documents/ItemsSection.js.map +2 -2
- package/dist/modules/sales/data/entities.js +10 -0
- package/dist/modules/sales/data/entities.js.map +2 -2
- package/dist/modules/sales/data/validators.js +9 -1
- package/dist/modules/sales/data/validators.js.map +2 -2
- package/dist/modules/sales/lib/calculations.js +85 -6
- package/dist/modules/sales/lib/calculations.js.map +2 -2
- package/dist/modules/sales/lib/externalAmounts.js +143 -0
- package/dist/modules/sales/lib/externalAmounts.js.map +7 -0
- package/dist/modules/sales/lib/lineSnapshots.js +6 -1
- package/dist/modules/sales/lib/lineSnapshots.js.map +2 -2
- package/dist/modules/sales/lib/makeSalesLineRoute.js +8 -2
- package/dist/modules/sales/lib/makeSalesLineRoute.js.map +2 -2
- package/dist/modules/sales/lib/providers/totals.js +3 -2
- package/dist/modules/sales/lib/providers/totals.js.map +2 -2
- package/dist/modules/sales/migrations/Migration20260910120000_sales_external_amounts_mode.js +19 -0
- package/dist/modules/sales/migrations/Migration20260910120000_sales_external_amounts_mode.js.map +7 -0
- package/generated/entities/sales_order/index.ts +1 -0
- package/generated/entities/sales_order_line/index.ts +1 -0
- package/generated/entity-fields-registry.ts +2 -0
- package/package.json +8 -7
- package/src/modules/planner/api/availability-date-specific.ts +5 -10
- package/src/modules/planner/commands/availability-date-specific.ts +14 -33
- package/src/modules/planner/commands/availability-weekly.ts +15 -24
- package/src/modules/planner/lib/availabilityMerge.ts +13 -19
- package/src/modules/planner/lib/availabilityTimezone.ts +242 -0
- package/src/modules/sales/api/documents/factory.ts +3 -0
- package/src/modules/sales/api/order-lines/route.ts +2 -1
- package/src/modules/sales/backend/sales/documents/[id]/page.tsx +96 -0
- package/src/modules/sales/commands/documents.ts +219 -44
- package/src/modules/sales/commands/returns.ts +27 -3
- package/src/modules/sales/components/documents/AdjustmentsSection.tsx +34 -25
- package/src/modules/sales/components/documents/DocumentTotals.tsx +26 -2
- package/src/modules/sales/components/documents/ItemsSection.tsx +27 -12
- package/src/modules/sales/data/entities.ts +14 -0
- package/src/modules/sales/data/validators.ts +10 -0
- package/src/modules/sales/i18n/de.json +12 -0
- package/src/modules/sales/i18n/en.json +12 -0
- package/src/modules/sales/i18n/es.json +12 -0
- package/src/modules/sales/i18n/ko.json +12 -0
- package/src/modules/sales/i18n/pl.json +12 -0
- package/src/modules/sales/lib/calculations.ts +99 -4
- package/src/modules/sales/lib/externalAmounts.ts +174 -0
- package/src/modules/sales/lib/lineSnapshots.ts +6 -1
- package/src/modules/sales/lib/makeSalesLineRoute.ts +8 -0
- package/src/modules/sales/lib/providers/totals.ts +5 -2
- package/src/modules/sales/lib/types.ts +19 -2
- package/src/modules/sales/migrations/.snapshot-open-mercato.json +33 -1
- package/src/modules/sales/migrations/Migration20260910120000_sales_external_amounts_mode.ts +25 -0
|
@@ -74,7 +74,9 @@ import {
|
|
|
74
74
|
orderCreateSchema,
|
|
75
75
|
ORDER_PAYMENT_LEDGER_WARNING_CODE,
|
|
76
76
|
resolveSuppliedOrderPaymentLedgerFields,
|
|
77
|
+
amountsModeSchema,
|
|
77
78
|
orderLineCreateSchema,
|
|
79
|
+
orderHeaderTotalsSchema,
|
|
78
80
|
orderAdjustmentCreateSchema,
|
|
79
81
|
invoiceCreateSchema,
|
|
80
82
|
invoiceUpdateSchema,
|
|
@@ -109,6 +111,18 @@ import {
|
|
|
109
111
|
resolveUpsertTotalsOrigin,
|
|
110
112
|
resolveUpsertCalculatedAmounts
|
|
111
113
|
} from "../lib/lineSnapshots.js";
|
|
114
|
+
import {
|
|
115
|
+
assertAmountsModeUnsupportedOnQuote,
|
|
116
|
+
assertExternalHeaderComplete,
|
|
117
|
+
assertExternalLineComplete,
|
|
118
|
+
assertUniformAmountsMode,
|
|
119
|
+
buildExternalHeaderTotals,
|
|
120
|
+
hasAnySuppliedHeaderTotal,
|
|
121
|
+
readPersistedHeaderTotals,
|
|
122
|
+
refuseOnExternalOrder,
|
|
123
|
+
requireOrderTotalsForExternalWrite
|
|
124
|
+
} from "../lib/externalAmounts.js";
|
|
125
|
+
import { isExternalAmountsMode } from "../lib/calculations.js";
|
|
112
126
|
import { loadShippedQuantityByLine } from "../lib/shipments/snapshots.js";
|
|
113
127
|
import { resolveDictionaryEntryValue, resolveCachedDictionaryEntryValue } from "../lib/dictionaries.js";
|
|
114
128
|
import { assertOrderCanBeCancelled, isCancelledOrderStatus } from "../lib/orderCancellationGuard.js";
|
|
@@ -151,7 +165,7 @@ const dateOnlySchema = z.string().trim().regex(/^\d{4}-\d{2}-\d{2}$/, { message:
|
|
|
151
165
|
});
|
|
152
166
|
const addressSnapshotSchema = z.record(z.string(), z.unknown()).nullable().optional();
|
|
153
167
|
const exchangeRateSchema = z.coerce.number().min(0);
|
|
154
|
-
const
|
|
168
|
+
const documentUpdateFieldsSchema = z.object({
|
|
155
169
|
id: z.string().uuid(),
|
|
156
170
|
customerEntityId: z.string().uuid().nullable().optional(),
|
|
157
171
|
customerContactId: z.string().uuid().nullable().optional(),
|
|
@@ -191,11 +205,20 @@ const documentUpdateSchema = z.object({
|
|
|
191
205
|
paymentMethodSnapshot: z.record(z.string(), z.unknown()).nullable().optional(),
|
|
192
206
|
tags: z.array(z.string().uuid()).optional(),
|
|
193
207
|
customFields: z.record(z.string(), z.unknown()).optional(),
|
|
194
|
-
customFieldSetId: z.string().uuid().nullable().optional()
|
|
195
|
-
|
|
196
|
-
|
|
208
|
+
customFieldSetId: z.string().uuid().nullable().optional(),
|
|
209
|
+
totalsMode: amountsModeSchema.optional(),
|
|
210
|
+
...orderHeaderTotalsSchema.shape
|
|
211
|
+
});
|
|
212
|
+
function carriesDocumentEdit(input) {
|
|
213
|
+
return typeof input.currencyCode === "string" || input.placedAt !== void 0 || input.expectedDeliveryAt !== void 0 || input.channelId !== void 0 || input.statusEntryId !== void 0 || input.exchangeRate !== void 0 || input.paymentStatusEntryId !== void 0 || input.fulfillmentStatusEntryId !== void 0 || input.shippingAddressId !== void 0 || input.billingAddressId !== void 0 || input.customerEntityId !== void 0 || input.customerContactId !== void 0 || input.customerSnapshot !== void 0 || input.metadata !== void 0 || input.customerReference !== void 0 || input.externalReference !== void 0 || input.comment !== void 0 || input.comments !== void 0 || input.internalNotes !== void 0 || input.orderNumber !== void 0 || input.quoteNumber !== void 0 || input.shippingAddressSnapshot !== void 0 || input.billingAddressSnapshot !== void 0 || input.shippingMethodId !== void 0 || input.shippingMethodCode !== void 0 || input.shippingMethodSnapshot !== void 0 || input.paymentMethodId !== void 0 || input.paymentMethodCode !== void 0 || input.paymentMethodSnapshot !== void 0 || input.tags !== void 0 || input.customFields !== void 0 || input.customFieldSetId !== void 0;
|
|
214
|
+
}
|
|
215
|
+
const documentUpdateSchema = documentUpdateFieldsSchema.refine(
|
|
216
|
+
(input) => carriesDocumentEdit(input) || input.totalsMode !== void 0 || hasAnySuppliedHeaderTotal(input),
|
|
197
217
|
{ message: "update_payload_empty" }
|
|
198
218
|
);
|
|
219
|
+
const documentEditSchema = documentUpdateFieldsSchema.refine(carriesDocumentEdit, {
|
|
220
|
+
message: "update_payload_empty"
|
|
221
|
+
});
|
|
199
222
|
function cloneJson(value) {
|
|
200
223
|
if (value === null || value === void 0) return value;
|
|
201
224
|
return JSON.parse(JSON.stringify(value));
|
|
@@ -1230,7 +1253,8 @@ async function loadOrderSnapshot(em, id) {
|
|
|
1230
1253
|
refundedTotalAmount: order.refundedTotalAmount,
|
|
1231
1254
|
outstandingAmount: order.outstandingAmount,
|
|
1232
1255
|
totalsSnapshot: order.totalsSnapshot ? cloneJson(order.totalsSnapshot) : null,
|
|
1233
|
-
lineItemCount: order.lineItemCount
|
|
1256
|
+
lineItemCount: order.lineItemCount,
|
|
1257
|
+
totalsMode: order.totalsMode ?? "computed"
|
|
1234
1258
|
},
|
|
1235
1259
|
lines: lines.map((line) => ({
|
|
1236
1260
|
id: line.id,
|
|
@@ -1262,6 +1286,7 @@ async function loadOrderSnapshot(em, id) {
|
|
|
1262
1286
|
taxAmount: line.taxAmount,
|
|
1263
1287
|
totalNetAmount: line.totalNetAmount,
|
|
1264
1288
|
totalGrossAmount: line.totalGrossAmount,
|
|
1289
|
+
amountsMode: line.amountsMode ?? "computed",
|
|
1265
1290
|
configuration: line.configuration ? cloneJson(line.configuration) : null,
|
|
1266
1291
|
promotionCode: line.promotionCode ?? null,
|
|
1267
1292
|
promotionSnapshot: line.promotionSnapshot ? cloneJson(line.promotionSnapshot) : null,
|
|
@@ -1991,7 +2016,8 @@ function createLineSnapshotFromInput(line, lineNumber) {
|
|
|
1991
2016
|
promotionCode: line.promotionCode ?? null,
|
|
1992
2017
|
metadata: line.metadata ? cloneJson(line.metadata) : null,
|
|
1993
2018
|
customFieldSetId: "customFieldSetId" in line ? line.customFieldSetId ?? null : null,
|
|
1994
|
-
customFields: "customFields" in line && line.customFields ? cloneJson(line.customFields) : null
|
|
2019
|
+
customFields: "customFields" in line && line.customFields ? cloneJson(line.customFields) : null,
|
|
2020
|
+
amountsMode: line.amountsMode ?? null
|
|
1995
2021
|
};
|
|
1996
2022
|
}
|
|
1997
2023
|
function createAdjustmentDraftFromInput(adjustment) {
|
|
@@ -2019,7 +2045,8 @@ function createAdjustmentDraftFromInput(adjustment) {
|
|
|
2019
2045
|
}
|
|
2020
2046
|
function convertLineCalculationToEntityInput(lineResult, sourceLine, document, index) {
|
|
2021
2047
|
const line = lineResult.line;
|
|
2022
|
-
|
|
2048
|
+
const persistTotals = isExternalAmountsMode(line.amountsMode) ? (payload) => payload : reconcileLinePersistedTotals;
|
|
2049
|
+
return persistTotals({
|
|
2023
2050
|
lineNumber: line.lineNumber ?? index + 1,
|
|
2024
2051
|
kind: line.kind ?? "product",
|
|
2025
2052
|
statusEntryId: sourceLine.statusEntryId ?? null,
|
|
@@ -2047,6 +2074,8 @@ function convertLineCalculationToEntityInput(lineResult, sourceLine, document, i
|
|
|
2047
2074
|
taxAmount: toNumericString(lineResult.taxAmount) ?? "0",
|
|
2048
2075
|
totalNetAmount: toNumericString(lineResult.netAmount) ?? "0",
|
|
2049
2076
|
totalGrossAmount: toNumericString(lineResult.grossAmount) ?? "0",
|
|
2077
|
+
// Conditional: this converter also serves quote lines, which have no column.
|
|
2078
|
+
...line.amountsMode ? { amountsMode: line.amountsMode } : {},
|
|
2050
2079
|
configuration: line.configuration ? cloneJson(line.configuration) : null,
|
|
2051
2080
|
promotionCode: line.promotionCode ?? null,
|
|
2052
2081
|
promotionSnapshot: sourceLine.promotionSnapshot ? cloneJson(sourceLine.promotionSnapshot) : null,
|
|
@@ -2426,6 +2455,19 @@ function applyOrderTotals(order, totals, lineCount) {
|
|
|
2426
2455
|
order.totalsSnapshot = cloneJson(totals);
|
|
2427
2456
|
order.lineItemCount = lineCount;
|
|
2428
2457
|
}
|
|
2458
|
+
function applyOrderLineAmountsFromCalculation(params) {
|
|
2459
|
+
const { em, lines, calculation, amountsMode } = params;
|
|
2460
|
+
lines.forEach((line, index) => {
|
|
2461
|
+
const result = calculation.lines[index];
|
|
2462
|
+
if (!result) return;
|
|
2463
|
+
line.amountsMode = amountsMode;
|
|
2464
|
+
line.discountAmount = toNumericString(result.discountAmount) ?? "0";
|
|
2465
|
+
line.taxAmount = toNumericString(result.taxAmount) ?? "0";
|
|
2466
|
+
line.totalNetAmount = toNumericString(result.netAmount) ?? "0";
|
|
2467
|
+
line.totalGrossAmount = toNumericString(result.grossAmount) ?? "0";
|
|
2468
|
+
em.persist(line);
|
|
2469
|
+
});
|
|
2470
|
+
}
|
|
2429
2471
|
function normalizePaymentTotal(value) {
|
|
2430
2472
|
if (typeof value === "number" && Number.isFinite(value))
|
|
2431
2473
|
return Math.max(value, 0);
|
|
@@ -2679,6 +2721,7 @@ function applyOrderSnapshot(order, snapshot) {
|
|
|
2679
2721
|
order.outstandingAmount = snapshot.outstandingAmount;
|
|
2680
2722
|
order.totalsSnapshot = snapshot.totalsSnapshot ? cloneJson(snapshot.totalsSnapshot) : null;
|
|
2681
2723
|
order.lineItemCount = snapshot.lineItemCount;
|
|
2724
|
+
order.totalsMode = snapshot.totalsMode ?? "computed";
|
|
2682
2725
|
}
|
|
2683
2726
|
async function assertQuoteGraphUndoCurrent(em, quoteId, expected) {
|
|
2684
2727
|
const current = await loadQuoteSnapshot(em, quoteId);
|
|
@@ -3202,6 +3245,7 @@ async function restoreOrderGraph(em, snapshot, verified = null) {
|
|
|
3202
3245
|
outstandingAmount: snapshot.order.outstandingAmount,
|
|
3203
3246
|
totalsSnapshot: snapshot.order.totalsSnapshot ? cloneJson(snapshot.order.totalsSnapshot) : null,
|
|
3204
3247
|
lineItemCount: snapshot.order.lineItemCount,
|
|
3248
|
+
totalsMode: snapshot.order.totalsMode ?? "computed",
|
|
3205
3249
|
createdAt: /* @__PURE__ */ new Date(),
|
|
3206
3250
|
updatedAt: /* @__PURE__ */ new Date()
|
|
3207
3251
|
});
|
|
@@ -3302,10 +3346,12 @@ async function restoreOrderGraph(em, snapshot, verified = null) {
|
|
|
3302
3346
|
discountPercent: line.discountPercent,
|
|
3303
3347
|
taxRate: line.taxRate,
|
|
3304
3348
|
taxAmount: line.taxAmount,
|
|
3305
|
-
|
|
3349
|
+
// Not repaired from gross: an external line's net is the caller's figure.
|
|
3350
|
+
totalNetAmount: isExternalAmountsMode(line.amountsMode) ? line.totalNetAmount : toNumericString(
|
|
3306
3351
|
deriveLineNetFromGross(line.totalNetAmount, line.totalGrossAmount, line.taxRate)
|
|
3307
3352
|
),
|
|
3308
3353
|
totalGrossAmount: line.totalGrossAmount,
|
|
3354
|
+
amountsMode: line.amountsMode ?? "computed",
|
|
3309
3355
|
configuration: line.configuration ? cloneJson(line.configuration) : null,
|
|
3310
3356
|
promotionCode: line.promotionCode ?? null,
|
|
3311
3357
|
promotionSnapshot: line.promotionSnapshot ? cloneJson(line.promotionSnapshot) : null,
|
|
@@ -3549,6 +3595,7 @@ const createQuoteCommand = {
|
|
|
3549
3595
|
createdAt: /* @__PURE__ */ new Date(),
|
|
3550
3596
|
updatedAt: /* @__PURE__ */ new Date()
|
|
3551
3597
|
});
|
|
3598
|
+
await assertAmountsModeUnsupportedOnQuote(parsed.lines ?? []);
|
|
3552
3599
|
const lineInputs = (parsed.lines ?? []).map(
|
|
3553
3600
|
(line, index) => quoteLineCreateSchema.parse({
|
|
3554
3601
|
...line,
|
|
@@ -3894,6 +3941,8 @@ const updateQuoteCommand = {
|
|
|
3894
3941
|
},
|
|
3895
3942
|
async execute(rawInput, ctx) {
|
|
3896
3943
|
const parsed = documentUpdateSchema.parse(rawInput ?? {});
|
|
3944
|
+
await assertAmountsModeUnsupportedOnQuote([parsed]);
|
|
3945
|
+
documentEditSchema.parse(rawInput ?? {});
|
|
3897
3946
|
const em = ctx.container.resolve("em").fork();
|
|
3898
3947
|
const quote = await findOneWithDecryption(em, SalesQuote, {
|
|
3899
3948
|
id: parsed.id,
|
|
@@ -4151,6 +4200,16 @@ const updateOrderCommand = {
|
|
|
4151
4200
|
if (!order)
|
|
4152
4201
|
throw notFound("Sales order not found");
|
|
4153
4202
|
ensureOrderScope(ctx, order.organizationId, order.tenantId);
|
|
4203
|
+
const currentTotalsMode = order.totalsMode ?? "computed";
|
|
4204
|
+
const nextTotalsMode = parsed.totalsMode ?? currentTotalsMode;
|
|
4205
|
+
const totalsModeChanged = nextTotalsMode !== currentTotalsMode;
|
|
4206
|
+
const externalHeaderSupplied = isExternalAmountsMode(nextTotalsMode) && hasAnySuppliedHeaderTotal(parsed);
|
|
4207
|
+
if (!totalsModeChanged && !externalHeaderSupplied) {
|
|
4208
|
+
documentEditSchema.parse(rawInput ?? {});
|
|
4209
|
+
}
|
|
4210
|
+
if (isExternalAmountsMode(nextTotalsMode) && (totalsModeChanged || externalHeaderSupplied)) {
|
|
4211
|
+
await requireOrderTotalsForExternalWrite(parsed);
|
|
4212
|
+
}
|
|
4154
4213
|
await enforceSalesDocumentOptimisticLock(ctx, order, SALES_RESOURCE_KIND_ORDER);
|
|
4155
4214
|
const previousStatus = normalizeStatusValue(order.status);
|
|
4156
4215
|
if (parsed.statusEntryId && !isCancelledOrderStatus(previousStatus)) {
|
|
@@ -4164,7 +4223,10 @@ const updateOrderCommand = {
|
|
|
4164
4223
|
}
|
|
4165
4224
|
}
|
|
4166
4225
|
let statusChangeNote = null;
|
|
4167
|
-
const
|
|
4226
|
+
const suppliedTotals = isExternalAmountsMode(
|
|
4227
|
+
nextTotalsMode
|
|
4228
|
+
) ? externalHeaderSupplied ? buildExternalHeaderTotals(parsed) : readPersistedHeaderTotals(order) : null;
|
|
4229
|
+
const shouldRecalculateTotals = parsed.shippingMethodId !== void 0 || parsed.shippingMethodSnapshot !== void 0 || parsed.shippingMethodCode !== void 0 || parsed.paymentMethodId !== void 0 || parsed.paymentMethodSnapshot !== void 0 || parsed.paymentMethodCode !== void 0 || parsed.currencyCode !== void 0 || totalsModeChanged || externalHeaderSupplied;
|
|
4168
4230
|
await withAtomicFlush(
|
|
4169
4231
|
em,
|
|
4170
4232
|
[
|
|
@@ -4197,10 +4259,13 @@ const updateOrderCommand = {
|
|
|
4197
4259
|
)
|
|
4198
4260
|
]);
|
|
4199
4261
|
const lineSnapshots = existingLines.map(mapOrderLineEntityToSnapshot);
|
|
4262
|
+
const leavingExternal = totalsModeChanged && !isExternalAmountsMode(nextTotalsMode);
|
|
4200
4263
|
const calcLines = lineSnapshots.map(
|
|
4201
4264
|
(line, index) => createLineSnapshotFromInput(
|
|
4202
4265
|
{
|
|
4203
4266
|
...line,
|
|
4267
|
+
...leavingExternal ? { taxAmount: null, totalGrossAmount: null } : {},
|
|
4268
|
+
amountsMode: nextTotalsMode,
|
|
4204
4269
|
organizationId: order.organizationId,
|
|
4205
4270
|
tenantId: order.tenantId,
|
|
4206
4271
|
orderId: order.id,
|
|
@@ -4232,7 +4297,9 @@ const updateOrderCommand = {
|
|
|
4232
4297
|
lines: calcLines,
|
|
4233
4298
|
adjustments: adjustmentDrafts,
|
|
4234
4299
|
context: calculationContext,
|
|
4235
|
-
existingTotals: resolveExistingPaymentTotals(order)
|
|
4300
|
+
existingTotals: resolveExistingPaymentTotals(order),
|
|
4301
|
+
totalsMode: nextTotalsMode,
|
|
4302
|
+
suppliedTotals
|
|
4236
4303
|
});
|
|
4237
4304
|
const adjustmentInputs = adjustmentDrafts.map((adj, index) => ({
|
|
4238
4305
|
organizationId: order.organizationId,
|
|
@@ -4257,6 +4324,15 @@ const updateOrderCommand = {
|
|
|
4257
4324
|
calculation,
|
|
4258
4325
|
adjustmentInputs
|
|
4259
4326
|
);
|
|
4327
|
+
if (totalsModeChanged) {
|
|
4328
|
+
applyOrderLineAmountsFromCalculation({
|
|
4329
|
+
em,
|
|
4330
|
+
lines: existingLines,
|
|
4331
|
+
calculation,
|
|
4332
|
+
amountsMode: nextTotalsMode
|
|
4333
|
+
});
|
|
4334
|
+
order.totalsMode = nextTotalsMode;
|
|
4335
|
+
}
|
|
4260
4336
|
applyOrderTotals(order, calculation.totals, calculation.lines.length);
|
|
4261
4337
|
let eventBus = null;
|
|
4262
4338
|
try {
|
|
@@ -4545,7 +4621,21 @@ const createOrderCommand = {
|
|
|
4545
4621
|
orderId: order.id
|
|
4546
4622
|
})
|
|
4547
4623
|
) : null;
|
|
4548
|
-
const
|
|
4624
|
+
const totalsMode = parsed.totalsMode ?? "computed";
|
|
4625
|
+
await assertUniformAmountsMode(totalsMode, normalizedLineInputs);
|
|
4626
|
+
const modedLineInputs = normalizedLineInputs.map((line) => ({
|
|
4627
|
+
...line,
|
|
4628
|
+
amountsMode: totalsMode
|
|
4629
|
+
}));
|
|
4630
|
+
if (isExternalAmountsMode(totalsMode)) {
|
|
4631
|
+
await assertExternalHeaderComplete(parsed);
|
|
4632
|
+
for (const [index, line] of modedLineInputs.entries()) {
|
|
4633
|
+
await assertExternalLineComplete(line, line.lineNumber ?? index + 1);
|
|
4634
|
+
}
|
|
4635
|
+
}
|
|
4636
|
+
order.totalsMode = totalsMode;
|
|
4637
|
+
const suppliedTotals = isExternalAmountsMode(totalsMode) ? buildExternalHeaderTotals(parsed) : null;
|
|
4638
|
+
const lineSnapshots = modedLineInputs.map(
|
|
4549
4639
|
(line, index) => createLineSnapshotFromInput(line, line.lineNumber ?? index + 1)
|
|
4550
4640
|
);
|
|
4551
4641
|
const adjustmentDrafts = adjustmentInputs ? adjustmentInputs.map((adj) => createAdjustmentDraftFromInput(adj)) : [];
|
|
@@ -4566,7 +4656,9 @@ const createOrderCommand = {
|
|
|
4566
4656
|
lines: lineSnapshots,
|
|
4567
4657
|
adjustments: adjustmentDrafts,
|
|
4568
4658
|
context: calculationContext,
|
|
4569
|
-
existingTotals: resolveExistingPaymentTotals(order)
|
|
4659
|
+
existingTotals: resolveExistingPaymentTotals(order),
|
|
4660
|
+
totalsMode,
|
|
4661
|
+
suppliedTotals
|
|
4570
4662
|
});
|
|
4571
4663
|
let eventBus = null;
|
|
4572
4664
|
try {
|
|
@@ -4579,7 +4671,7 @@ const createOrderCommand = {
|
|
|
4579
4671
|
[
|
|
4580
4672
|
async () => {
|
|
4581
4673
|
em.persist(order);
|
|
4582
|
-
await replaceOrderLines(em, order, calculation,
|
|
4674
|
+
await replaceOrderLines(em, order, calculation, modedLineInputs);
|
|
4583
4675
|
await replaceOrderAdjustments(
|
|
4584
4676
|
em,
|
|
4585
4677
|
order,
|
|
@@ -5315,11 +5407,13 @@ const convertQuoteToOrderCommand = {
|
|
|
5315
5407
|
}
|
|
5316
5408
|
};
|
|
5317
5409
|
const orderLineUpsertSchema = orderLineCreateSchema.extend({
|
|
5318
|
-
id: z.string().uuid().optional()
|
|
5410
|
+
id: z.string().uuid().optional(),
|
|
5411
|
+
orderTotals: orderHeaderTotalsSchema.optional()
|
|
5319
5412
|
});
|
|
5320
5413
|
const orderLineDeleteSchema = z.object({
|
|
5321
5414
|
id: z.string().uuid(),
|
|
5322
|
-
orderId: z.string().uuid()
|
|
5415
|
+
orderId: z.string().uuid(),
|
|
5416
|
+
orderTotals: orderHeaderTotalsSchema.optional()
|
|
5323
5417
|
});
|
|
5324
5418
|
const quoteLineUpsertSchema = quoteLineCreateSchema.extend({
|
|
5325
5419
|
id: z.string().uuid().optional()
|
|
@@ -5517,12 +5611,19 @@ const orderLineUpsertCommand = {
|
|
|
5517
5611
|
]);
|
|
5518
5612
|
const lineSnapshots = existingLines.map(mapOrderLineEntityToSnapshot);
|
|
5519
5613
|
const existingSnapshot = parsed.id ? lineSnapshots.find((line) => line.id === parsed.id) ?? null : null;
|
|
5614
|
+
const totalsMode = order.totalsMode ?? "computed";
|
|
5615
|
+
await assertUniformAmountsMode(totalsMode, [parsed]);
|
|
5616
|
+
if (isExternalAmountsMode(totalsMode)) {
|
|
5617
|
+
await requireOrderTotalsForExternalWrite(parsed.orderTotals);
|
|
5618
|
+
}
|
|
5619
|
+
const suppliedTotals = isExternalAmountsMode(totalsMode) ? buildExternalHeaderTotals(parsed.orderTotals) : null;
|
|
5520
5620
|
await assertOrderAcceptsNewLine(order, existingSnapshot);
|
|
5521
5621
|
await assertShippedOrderLineEditable(em, order, existingSnapshot, parsed);
|
|
5522
5622
|
const priceMode = parsed.priceMode === "gross" ? "gross" : parsed.priceMode === "net" ? "net" : null;
|
|
5523
5623
|
let unitPriceNet = parsed.unitPriceNet ?? existingSnapshot?.unitPriceNet ?? null;
|
|
5524
5624
|
let unitPriceGross = parsed.unitPriceGross ?? existingSnapshot?.unitPriceGross ?? null;
|
|
5525
5625
|
let taxRate = parsed.taxRate ?? existingSnapshot?.taxRate ?? null;
|
|
5626
|
+
const unitPriceSupplied = unitPriceNet !== null || unitPriceGross !== null;
|
|
5526
5627
|
if (priceMode && (unitPriceNet === null || unitPriceGross === null)) {
|
|
5527
5628
|
let taxService = null;
|
|
5528
5629
|
try {
|
|
@@ -5624,12 +5725,22 @@ const orderLineUpsertCommand = {
|
|
|
5624
5725
|
promotionCode: parsed.promotionCode ?? existingSnapshot?.promotionCode ?? null,
|
|
5625
5726
|
metadata,
|
|
5626
5727
|
customFieldSetId: parsed.customFieldSetId ?? existingSnapshot?.customFieldSetId ?? null,
|
|
5627
|
-
customFields: parsed.customFields && typeof parsed.customFields === "object" ? cloneJson(parsed.customFields) : existingSnapshot?.customFields ?? null
|
|
5728
|
+
customFields: parsed.customFields && typeof parsed.customFields === "object" ? cloneJson(parsed.customFields) : existingSnapshot?.customFields ?? null,
|
|
5729
|
+
amountsMode: totalsMode
|
|
5628
5730
|
};
|
|
5629
5731
|
Object.assign(
|
|
5630
5732
|
updatedSnapshot,
|
|
5631
5733
|
resolveUpsertCalculatedAmounts(parsed, updatedSnapshot, existingSnapshot)
|
|
5632
5734
|
);
|
|
5735
|
+
if (isExternalAmountsMode(totalsMode)) {
|
|
5736
|
+
await assertExternalLineComplete(
|
|
5737
|
+
{
|
|
5738
|
+
...updatedSnapshot,
|
|
5739
|
+
unitPriceNet: unitPriceSupplied ? unitPriceNet : null
|
|
5740
|
+
},
|
|
5741
|
+
updatedSnapshot.lineNumber ?? 1
|
|
5742
|
+
);
|
|
5743
|
+
}
|
|
5633
5744
|
updatedSnapshot.statusEntryId = statusEntryId;
|
|
5634
5745
|
updatedSnapshot.catalogSnapshot = parsed.catalogSnapshot ?? existingSnapshot?.catalogSnapshot ?? null;
|
|
5635
5746
|
updatedSnapshot.promotionSnapshot = parsed.promotionSnapshot ?? existingSnapshot?.promotionSnapshot ?? null;
|
|
@@ -5668,7 +5779,9 @@ const orderLineUpsertCommand = {
|
|
|
5668
5779
|
lines: calcLines,
|
|
5669
5780
|
adjustments: adjustmentDrafts,
|
|
5670
5781
|
context: calculationContext,
|
|
5671
|
-
existingTotals: resolveExistingPaymentTotals(order)
|
|
5782
|
+
existingTotals: resolveExistingPaymentTotals(order),
|
|
5783
|
+
totalsMode,
|
|
5784
|
+
suppliedTotals
|
|
5672
5785
|
});
|
|
5673
5786
|
let eventBus = null;
|
|
5674
5787
|
try {
|
|
@@ -5805,6 +5918,11 @@ const orderLineDeleteCommand = {
|
|
|
5805
5918
|
)
|
|
5806
5919
|
});
|
|
5807
5920
|
}
|
|
5921
|
+
const totalsMode = order.totalsMode ?? "computed";
|
|
5922
|
+
if (isExternalAmountsMode(totalsMode)) {
|
|
5923
|
+
await requireOrderTotalsForExternalWrite(parsed.orderTotals);
|
|
5924
|
+
}
|
|
5925
|
+
const suppliedTotals = isExternalAmountsMode(totalsMode) ? buildExternalHeaderTotals(parsed.orderTotals) : null;
|
|
5808
5926
|
const sourceInputs = filtered.map((line, index) => ({
|
|
5809
5927
|
...mapOrderLineEntityToSnapshot(line),
|
|
5810
5928
|
statusEntryId: line.statusEntryId ?? null,
|
|
@@ -5836,7 +5954,9 @@ const orderLineDeleteCommand = {
|
|
|
5836
5954
|
lines: calcLines,
|
|
5837
5955
|
adjustments: adjustmentDrafts,
|
|
5838
5956
|
context: calculationContext,
|
|
5839
|
-
existingTotals: resolveExistingPaymentTotals(order)
|
|
5957
|
+
existingTotals: resolveExistingPaymentTotals(order),
|
|
5958
|
+
totalsMode,
|
|
5959
|
+
suppliedTotals
|
|
5840
5960
|
});
|
|
5841
5961
|
let eventBus = null;
|
|
5842
5962
|
try {
|
|
@@ -5922,6 +6042,7 @@ const quoteLineUpsertCommand = {
|
|
|
5922
6042
|
async execute(input, ctx) {
|
|
5923
6043
|
const rawBody = input?.body ?? {};
|
|
5924
6044
|
const parsed = quoteLineUpsertSchema.parse(rawBody);
|
|
6045
|
+
await assertAmountsModeUnsupportedOnQuote([parsed]);
|
|
5925
6046
|
const em = ctx.container.resolve("em").fork();
|
|
5926
6047
|
const quote = await findOneWithDecryption(em, SalesQuote, {
|
|
5927
6048
|
id: parsed.quoteId,
|
|
@@ -6325,6 +6446,7 @@ const orderAdjustmentUpsertCommand = {
|
|
|
6325
6446
|
throw notFound("Sales order not found");
|
|
6326
6447
|
ensureOrderScope(ctx, order.organizationId, order.tenantId);
|
|
6327
6448
|
await enforceSalesDocumentOptimisticLock(ctx, order, SALES_RESOURCE_KIND_ORDER);
|
|
6449
|
+
if (isExternalAmountsMode(order.totalsMode ?? "computed")) await refuseOnExternalOrder();
|
|
6328
6450
|
if (parsed.scope === "line") {
|
|
6329
6451
|
throw new CrudHttpError(400, {
|
|
6330
6452
|
error: "Line-scoped adjustments are not supported yet."
|
|
@@ -6575,6 +6697,7 @@ const orderAdjustmentDeleteCommand = {
|
|
|
6575
6697
|
throw notFound("Sales order not found");
|
|
6576
6698
|
ensureOrderScope(ctx, order.organizationId, order.tenantId);
|
|
6577
6699
|
await enforceSalesDocumentOptimisticLock(ctx, order, SALES_RESOURCE_KIND_ORDER);
|
|
6700
|
+
if (isExternalAmountsMode(order.totalsMode ?? "computed")) await refuseOnExternalOrder();
|
|
6578
6701
|
const [existingLines, adjustments] = await Promise.all([
|
|
6579
6702
|
em.find(SalesOrderLine, { order }, { orderBy: { lineNumber: "asc" } }),
|
|
6580
6703
|
em.find(
|