@open-mercato/core 0.10.1-develop.7727.1.0b89c4f800 → 0.10.1-develop.7736.1.88e07906bf
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/dist/modules/attachments/lib/ocrService.js +8 -0
- package/dist/modules/attachments/lib/ocrService.js.map +2 -2
- package/dist/modules/auth/api/logout.js +3 -1
- package/dist/modules/auth/api/logout.js.map +2 -2
- package/dist/modules/sales/commands/documents.js +1190 -1408
- package/dist/modules/sales/commands/documents.js.map +2 -2
- package/dist/modules/sales/commands/returns.js +34 -11
- package/dist/modules/sales/commands/returns.js.map +2 -2
- package/dist/modules/sales/commands/shared.js +5 -0
- package/dist/modules/sales/commands/shared.js.map +2 -2
- package/dist/modules/sales/commands/shipments.js +9 -0
- package/dist/modules/sales/commands/shipments.js.map +2 -2
- package/dist/modules/staff/api/timesheets/time-projects/route.js +6 -2
- package/dist/modules/staff/api/timesheets/time-projects/route.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/attachments/lib/ocrService.ts +8 -0
- package/src/modules/auth/api/logout.ts +3 -1
- package/src/modules/sales/commands/documents.ts +1501 -1626
- package/src/modules/sales/commands/returns.ts +41 -14
- package/src/modules/sales/commands/shared.ts +40 -0
- package/src/modules/sales/commands/shipments.ts +10 -0
- package/src/modules/staff/api/timesheets/time-projects/route.ts +6 -2
|
@@ -92,6 +92,7 @@ import {
|
|
|
92
92
|
reconcileLinePersistedTotals,
|
|
93
93
|
deriveLineNetFromGross,
|
|
94
94
|
enforceSalesDocumentOptimisticLock,
|
|
95
|
+
lockSalesDocumentRow,
|
|
95
96
|
SALES_RESOURCE_KIND_ORDER,
|
|
96
97
|
SALES_RESOURCE_KIND_QUOTE
|
|
97
98
|
} from "./shared.js";
|
|
@@ -525,19 +526,26 @@ async function applyDocumentUpdate({
|
|
|
525
526
|
entity.quoteNumber = input.quoteNumber;
|
|
526
527
|
}
|
|
527
528
|
if (input.customerEntityId !== void 0) {
|
|
528
|
-
|
|
529
|
+
const nextCustomerEntityId = input.customerEntityId ?? null;
|
|
530
|
+
const customerChanged = (nextCustomerEntityId?.toLowerCase() ?? null) !== (entity.customerEntityId?.toLowerCase() ?? null);
|
|
531
|
+
const snapshotContactId = input.customerContactId !== void 0 ? input.customerContactId : customerChanged ? null : entity.customerContactId ?? null;
|
|
532
|
+
if (customerChanged) {
|
|
533
|
+
entity.customerEntityId = nextCustomerEntityId;
|
|
534
|
+
}
|
|
529
535
|
entity.customerSnapshot = await resolveCustomerSnapshot(
|
|
530
536
|
em,
|
|
531
537
|
organizationId,
|
|
532
538
|
tenantId,
|
|
533
|
-
|
|
534
|
-
|
|
539
|
+
entity.customerEntityId ?? null,
|
|
540
|
+
snapshotContactId
|
|
535
541
|
);
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
542
|
+
if (customerChanged) {
|
|
543
|
+
entity.customerContactId = input.customerContactId ?? null;
|
|
544
|
+
entity.billingAddressId = null;
|
|
545
|
+
entity.shippingAddressId = null;
|
|
546
|
+
entity.billingAddressSnapshot = null;
|
|
547
|
+
entity.shippingAddressSnapshot = null;
|
|
548
|
+
}
|
|
541
549
|
}
|
|
542
550
|
if (input.customerContactId !== void 0) {
|
|
543
551
|
entity.customerContactId = input.customerContactId ?? null;
|
|
@@ -3924,6 +3932,165 @@ const deleteQuoteCommand = {
|
|
|
3924
3932
|
await undoQuoteGraph(ctx, before, payload?.after);
|
|
3925
3933
|
}
|
|
3926
3934
|
};
|
|
3935
|
+
const lockedGraphWriteInputs = /* @__PURE__ */ new WeakSet();
|
|
3936
|
+
const lockedOrderGraphSnapshots = /* @__PURE__ */ new WeakMap();
|
|
3937
|
+
const lockedQuoteGraphSnapshots = /* @__PURE__ */ new WeakMap();
|
|
3938
|
+
function resolveRequestDecryptionScope(ctx) {
|
|
3939
|
+
return {
|
|
3940
|
+
tenantId: ctx.auth?.tenantId ?? void 0,
|
|
3941
|
+
organizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? void 0
|
|
3942
|
+
};
|
|
3943
|
+
}
|
|
3944
|
+
async function lockOrderForTotalsWrite(em, ctx, orderId, missingOrderMessage) {
|
|
3945
|
+
const existingOrder = await findOneWithDecryption(
|
|
3946
|
+
em,
|
|
3947
|
+
SalesOrder,
|
|
3948
|
+
{ id: orderId, deletedAt: null },
|
|
3949
|
+
{},
|
|
3950
|
+
resolveRequestDecryptionScope(ctx)
|
|
3951
|
+
);
|
|
3952
|
+
if (!existingOrder) throw notFound(missingOrderMessage);
|
|
3953
|
+
ensureOrderScope(ctx, existingOrder.organizationId, existingOrder.tenantId);
|
|
3954
|
+
const scope = {
|
|
3955
|
+
tenantId: existingOrder.tenantId,
|
|
3956
|
+
organizationId: existingOrder.organizationId
|
|
3957
|
+
};
|
|
3958
|
+
if (!await lockSalesDocumentRow(em, "sales_orders", orderId, scope)) {
|
|
3959
|
+
throw notFound(missingOrderMessage);
|
|
3960
|
+
}
|
|
3961
|
+
const order = await findOneWithDecryption(
|
|
3962
|
+
em,
|
|
3963
|
+
SalesOrder,
|
|
3964
|
+
{ id: orderId, deletedAt: null, ...scope },
|
|
3965
|
+
{ refresh: true },
|
|
3966
|
+
scope
|
|
3967
|
+
);
|
|
3968
|
+
if (!order) throw notFound(missingOrderMessage);
|
|
3969
|
+
await enforceSalesDocumentOptimisticLock(ctx, order, SALES_RESOURCE_KIND_ORDER);
|
|
3970
|
+
return order;
|
|
3971
|
+
}
|
|
3972
|
+
async function lockQuoteForTotalsWrite(em, ctx, quoteId, missingQuoteMessage) {
|
|
3973
|
+
const existingQuote = await findOneWithDecryption(
|
|
3974
|
+
em,
|
|
3975
|
+
SalesQuote,
|
|
3976
|
+
{ id: quoteId, deletedAt: null },
|
|
3977
|
+
{},
|
|
3978
|
+
resolveRequestDecryptionScope(ctx)
|
|
3979
|
+
);
|
|
3980
|
+
if (!existingQuote) throw notFound(missingQuoteMessage);
|
|
3981
|
+
ensureQuoteScope(ctx, existingQuote.organizationId, existingQuote.tenantId);
|
|
3982
|
+
const scope = {
|
|
3983
|
+
tenantId: existingQuote.tenantId,
|
|
3984
|
+
organizationId: existingQuote.organizationId
|
|
3985
|
+
};
|
|
3986
|
+
if (!await lockSalesDocumentRow(em, "sales_quotes", quoteId, scope)) {
|
|
3987
|
+
throw notFound(missingQuoteMessage);
|
|
3988
|
+
}
|
|
3989
|
+
const quote = await findOneWithDecryption(
|
|
3990
|
+
em,
|
|
3991
|
+
SalesQuote,
|
|
3992
|
+
{ id: quoteId, deletedAt: null, ...scope },
|
|
3993
|
+
{ refresh: true },
|
|
3994
|
+
scope
|
|
3995
|
+
);
|
|
3996
|
+
if (!quote) throw notFound(missingQuoteMessage);
|
|
3997
|
+
await enforceSalesDocumentOptimisticLock(ctx, quote, SALES_RESOURCE_KIND_QUOTE);
|
|
3998
|
+
return quote;
|
|
3999
|
+
}
|
|
4000
|
+
function buildTotalsCalculatedPayload(documentKind, document, calculation) {
|
|
4001
|
+
return {
|
|
4002
|
+
documentKind,
|
|
4003
|
+
documentId: document.id,
|
|
4004
|
+
organizationId: document.organizationId,
|
|
4005
|
+
tenantId: document.tenantId,
|
|
4006
|
+
customerId: document.customerEntityId ?? null,
|
|
4007
|
+
totals: calculation.totals,
|
|
4008
|
+
lineCount: calculation.lines.length
|
|
4009
|
+
};
|
|
4010
|
+
}
|
|
4011
|
+
async function emitCommittedTotalsCalculated(ctx, payload) {
|
|
4012
|
+
if (!payload) return;
|
|
4013
|
+
let eventBus = null;
|
|
4014
|
+
try {
|
|
4015
|
+
eventBus = ctx.container.resolve("eventBus");
|
|
4016
|
+
} catch {
|
|
4017
|
+
eventBus = null;
|
|
4018
|
+
}
|
|
4019
|
+
await emitTotalsCalculated(eventBus, payload);
|
|
4020
|
+
}
|
|
4021
|
+
const lockedOrderGraphKind = {
|
|
4022
|
+
documentKind: "order",
|
|
4023
|
+
lock: lockOrderForTotalsWrite,
|
|
4024
|
+
loadSnapshot: loadOrderSnapshot,
|
|
4025
|
+
snapshots: lockedOrderGraphSnapshots
|
|
4026
|
+
};
|
|
4027
|
+
const lockedQuoteGraphKind = {
|
|
4028
|
+
documentKind: "quote",
|
|
4029
|
+
lock: lockQuoteForTotalsWrite,
|
|
4030
|
+
loadSnapshot: loadQuoteSnapshot,
|
|
4031
|
+
snapshots: lockedQuoteGraphSnapshots
|
|
4032
|
+
};
|
|
4033
|
+
function markLockedGraphWrite(input) {
|
|
4034
|
+
lockedGraphWriteInputs.add(input);
|
|
4035
|
+
return {};
|
|
4036
|
+
}
|
|
4037
|
+
async function writeLockedDocumentGraph(kind, input, ctx, documentId, missingMessage, write) {
|
|
4038
|
+
const em = ctx.container.resolve("em").fork();
|
|
4039
|
+
const captureSnapshots = lockedGraphWriteInputs.has(input);
|
|
4040
|
+
let written;
|
|
4041
|
+
let totalsEvent;
|
|
4042
|
+
let before = null;
|
|
4043
|
+
let after = null;
|
|
4044
|
+
await withAtomicFlush(
|
|
4045
|
+
em,
|
|
4046
|
+
[
|
|
4047
|
+
async () => {
|
|
4048
|
+
const document = await kind.lock(em, ctx, documentId, missingMessage);
|
|
4049
|
+
if (captureSnapshots) before = await kind.loadSnapshot(em, document.id);
|
|
4050
|
+
written = await write(em, document);
|
|
4051
|
+
totalsEvent = buildTotalsCalculatedPayload(
|
|
4052
|
+
kind.documentKind,
|
|
4053
|
+
document,
|
|
4054
|
+
written.calculation
|
|
4055
|
+
);
|
|
4056
|
+
},
|
|
4057
|
+
...captureSnapshots ? [
|
|
4058
|
+
async () => {
|
|
4059
|
+
em.clear();
|
|
4060
|
+
after = await kind.loadSnapshot(em, documentId);
|
|
4061
|
+
}
|
|
4062
|
+
] : []
|
|
4063
|
+
],
|
|
4064
|
+
{ transaction: true }
|
|
4065
|
+
);
|
|
4066
|
+
if (captureSnapshots) kind.snapshots.set(written.result, { before, after });
|
|
4067
|
+
await emitCommittedTotalsCalculated(ctx, totalsEvent);
|
|
4068
|
+
return written.result;
|
|
4069
|
+
}
|
|
4070
|
+
function writeLockedOrderGraph(input, ctx, orderId, missingOrderMessage, write) {
|
|
4071
|
+
return writeLockedDocumentGraph(lockedOrderGraphKind, input, ctx, orderId, missingOrderMessage, write);
|
|
4072
|
+
}
|
|
4073
|
+
function writeLockedQuoteGraph(input, ctx, quoteId, missingQuoteMessage, write) {
|
|
4074
|
+
return writeLockedDocumentGraph(lockedQuoteGraphKind, input, ctx, quoteId, missingQuoteMessage, write);
|
|
4075
|
+
}
|
|
4076
|
+
function resolveLockedOrderGraphBefore(result, preparedBefore) {
|
|
4077
|
+
return lockedOrderGraphSnapshots.get(result)?.before ?? preparedBefore;
|
|
4078
|
+
}
|
|
4079
|
+
function resolveLockedQuoteGraphBefore(result, preparedBefore) {
|
|
4080
|
+
return lockedQuoteGraphSnapshots.get(result)?.before ?? preparedBefore;
|
|
4081
|
+
}
|
|
4082
|
+
async function captureLockedOrderGraphAfter(ctx, result, orderId) {
|
|
4083
|
+
const locked = lockedOrderGraphSnapshots.get(result);
|
|
4084
|
+
if (locked) return locked.after;
|
|
4085
|
+
const em = ctx.container.resolve("em").fork();
|
|
4086
|
+
return loadOrderSnapshot(em, orderId);
|
|
4087
|
+
}
|
|
4088
|
+
async function captureLockedQuoteGraphAfter(ctx, result, quoteId) {
|
|
4089
|
+
const locked = lockedQuoteGraphSnapshots.get(result);
|
|
4090
|
+
if (locked) return locked.after;
|
|
4091
|
+
const em = ctx.container.resolve("em").fork();
|
|
4092
|
+
return loadQuoteSnapshot(em, quoteId);
|
|
4093
|
+
}
|
|
3927
4094
|
const updateQuoteCommand = {
|
|
3928
4095
|
id: "sales.quotes.update",
|
|
3929
4096
|
async prepare(input, ctx) {
|
|
@@ -3944,20 +4111,15 @@ const updateQuoteCommand = {
|
|
|
3944
4111
|
await assertAmountsModeUnsupportedOnQuote([parsed]);
|
|
3945
4112
|
documentEditSchema.parse(rawInput ?? {});
|
|
3946
4113
|
const em = ctx.container.resolve("em").fork();
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
deletedAt: null
|
|
3950
|
-
});
|
|
3951
|
-
if (!quote)
|
|
3952
|
-
throw notFound("Sales quote not found");
|
|
3953
|
-
ensureQuoteScope(ctx, quote.organizationId, quote.tenantId);
|
|
3954
|
-
await enforceSalesDocumentOptimisticLock(ctx, quote, SALES_RESOURCE_KIND_QUOTE);
|
|
3955
|
-
const shouldInvalidateSentToken = (quote.status ?? null) === "sent";
|
|
4114
|
+
let quote;
|
|
4115
|
+
let totalsEvent = null;
|
|
3956
4116
|
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;
|
|
3957
4117
|
await withAtomicFlush(
|
|
3958
4118
|
em,
|
|
3959
4119
|
[
|
|
3960
4120
|
async () => {
|
|
4121
|
+
quote = await lockQuoteForTotalsWrite(em, ctx, parsed.id, "Sales quote not found");
|
|
4122
|
+
const shouldInvalidateSentToken = (quote.status ?? null) === "sent";
|
|
3961
4123
|
await applyDocumentUpdate({
|
|
3962
4124
|
kind: "quote",
|
|
3963
4125
|
entity: quote,
|
|
@@ -4055,27 +4217,14 @@ const updateQuoteCommand = {
|
|
|
4055
4217
|
adjustmentInputs
|
|
4056
4218
|
);
|
|
4057
4219
|
applyQuoteTotals(quote, calculation.totals, calculation.lines.length);
|
|
4058
|
-
|
|
4059
|
-
try {
|
|
4060
|
-
eventBus = ctx.container.resolve("eventBus");
|
|
4061
|
-
} catch {
|
|
4062
|
-
eventBus = null;
|
|
4063
|
-
}
|
|
4064
|
-
await emitTotalsCalculated(eventBus, {
|
|
4065
|
-
documentKind: "quote",
|
|
4066
|
-
documentId: quote.id,
|
|
4067
|
-
organizationId: quote.organizationId,
|
|
4068
|
-
tenantId: quote.tenantId,
|
|
4069
|
-
customerId: quote.customerEntityId ?? null,
|
|
4070
|
-
totals: calculation.totals,
|
|
4071
|
-
lineCount: calculation.lines.length
|
|
4072
|
-
});
|
|
4220
|
+
totalsEvent = buildTotalsCalculatedPayload("quote", quote, calculation);
|
|
4073
4221
|
}
|
|
4074
4222
|
quote.updatedAt = /* @__PURE__ */ new Date();
|
|
4075
4223
|
}
|
|
4076
4224
|
],
|
|
4077
4225
|
{ transaction: true }
|
|
4078
4226
|
);
|
|
4227
|
+
await emitCommittedTotalsCalculated(ctx, totalsEvent);
|
|
4079
4228
|
const dataEngine = ctx.container.resolve("dataEngine");
|
|
4080
4229
|
await emitCrudSideEffects({
|
|
4081
4230
|
dataEngine,
|
|
@@ -4193,44 +4342,42 @@ const updateOrderCommand = {
|
|
|
4193
4342
|
async execute(rawInput, ctx) {
|
|
4194
4343
|
const parsed = documentUpdateSchema.parse(rawInput ?? {});
|
|
4195
4344
|
const em = ctx.container.resolve("em").fork();
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
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
|
-
}
|
|
4213
|
-
await enforceSalesDocumentOptimisticLock(ctx, order, SALES_RESOURCE_KIND_ORDER);
|
|
4214
|
-
const previousStatus = normalizeStatusValue(order.status);
|
|
4215
|
-
if (parsed.statusEntryId && !isCancelledOrderStatus(previousStatus)) {
|
|
4216
|
-
const requestedStatus = normalizeStatusValue(
|
|
4217
|
-
await resolveDictionaryEntryValue(em, parsed.statusEntryId, {
|
|
4218
|
-
tenantId: order.tenantId
|
|
4219
|
-
})
|
|
4220
|
-
);
|
|
4221
|
-
if (isCancelledOrderStatus(requestedStatus)) {
|
|
4222
|
-
await assertOrderCanBeCancelled(em, order);
|
|
4223
|
-
}
|
|
4224
|
-
}
|
|
4345
|
+
let order;
|
|
4346
|
+
let previousStatus = null;
|
|
4347
|
+
let nextTotalsMode = "computed";
|
|
4348
|
+
let totalsModeChanged = false;
|
|
4349
|
+
let externalHeaderSupplied = false;
|
|
4350
|
+
let suppliedTotals = null;
|
|
4225
4351
|
let statusChangeNote = null;
|
|
4226
|
-
|
|
4227
|
-
|
|
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;
|
|
4352
|
+
let totalsEvent = null;
|
|
4353
|
+
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;
|
|
4230
4354
|
await withAtomicFlush(
|
|
4231
4355
|
em,
|
|
4232
4356
|
[
|
|
4233
4357
|
async () => {
|
|
4358
|
+
order = await lockOrderForTotalsWrite(em, ctx, parsed.id, "Sales order not found");
|
|
4359
|
+
const currentTotalsMode = order.totalsMode ?? "computed";
|
|
4360
|
+
nextTotalsMode = parsed.totalsMode ?? currentTotalsMode;
|
|
4361
|
+
totalsModeChanged = nextTotalsMode !== currentTotalsMode;
|
|
4362
|
+
externalHeaderSupplied = isExternalAmountsMode(nextTotalsMode) && hasAnySuppliedHeaderTotal(parsed);
|
|
4363
|
+
if (!totalsModeChanged && !externalHeaderSupplied) {
|
|
4364
|
+
documentEditSchema.parse(rawInput ?? {});
|
|
4365
|
+
}
|
|
4366
|
+
if (isExternalAmountsMode(nextTotalsMode) && (totalsModeChanged || externalHeaderSupplied)) {
|
|
4367
|
+
await requireOrderTotalsForExternalWrite(parsed);
|
|
4368
|
+
}
|
|
4369
|
+
suppliedTotals = isExternalAmountsMode(nextTotalsMode) ? externalHeaderSupplied ? buildExternalHeaderTotals(parsed) : readPersistedHeaderTotals(order) : null;
|
|
4370
|
+
previousStatus = normalizeStatusValue(order.status);
|
|
4371
|
+
if (parsed.statusEntryId && !isCancelledOrderStatus(previousStatus)) {
|
|
4372
|
+
const requestedStatus = normalizeStatusValue(
|
|
4373
|
+
await resolveDictionaryEntryValue(em, parsed.statusEntryId, {
|
|
4374
|
+
tenantId: order.tenantId
|
|
4375
|
+
})
|
|
4376
|
+
);
|
|
4377
|
+
if (isCancelledOrderStatus(requestedStatus)) {
|
|
4378
|
+
await assertOrderCanBeCancelled(em, order);
|
|
4379
|
+
}
|
|
4380
|
+
}
|
|
4234
4381
|
await applyDocumentUpdate({
|
|
4235
4382
|
kind: "order",
|
|
4236
4383
|
entity: order,
|
|
@@ -4245,7 +4392,7 @@ const updateOrderCommand = {
|
|
|
4245
4392
|
// `em.find` lines/adjustments or appendOrderStatusChangeNote queries
|
|
4246
4393
|
// reset the changeset (see SPEC-018).
|
|
4247
4394
|
async () => {
|
|
4248
|
-
if (shouldRecalculateTotals) {
|
|
4395
|
+
if (shouldRecalculateTotals || totalsModeChanged || externalHeaderSupplied) {
|
|
4249
4396
|
const [existingLines, adjustments] = await Promise.all([
|
|
4250
4397
|
em.find(
|
|
4251
4398
|
SalesOrderLine,
|
|
@@ -4334,21 +4481,7 @@ const updateOrderCommand = {
|
|
|
4334
4481
|
order.totalsMode = nextTotalsMode;
|
|
4335
4482
|
}
|
|
4336
4483
|
applyOrderTotals(order, calculation.totals, calculation.lines.length);
|
|
4337
|
-
|
|
4338
|
-
try {
|
|
4339
|
-
eventBus = ctx.container.resolve("eventBus");
|
|
4340
|
-
} catch {
|
|
4341
|
-
eventBus = null;
|
|
4342
|
-
}
|
|
4343
|
-
await emitTotalsCalculated(eventBus, {
|
|
4344
|
-
documentKind: "order",
|
|
4345
|
-
documentId: order.id,
|
|
4346
|
-
organizationId: order.organizationId,
|
|
4347
|
-
tenantId: order.tenantId,
|
|
4348
|
-
customerId: order.customerEntityId ?? null,
|
|
4349
|
-
totals: calculation.totals,
|
|
4350
|
-
lineCount: calculation.lines.length
|
|
4351
|
-
});
|
|
4484
|
+
totalsEvent = buildTotalsCalculatedPayload("order", order, calculation);
|
|
4352
4485
|
}
|
|
4353
4486
|
statusChangeNote = await appendOrderStatusChangeNote({
|
|
4354
4487
|
em,
|
|
@@ -4361,6 +4494,7 @@ const updateOrderCommand = {
|
|
|
4361
4494
|
],
|
|
4362
4495
|
{ transaction: true }
|
|
4363
4496
|
);
|
|
4497
|
+
await emitCommittedTotalsCalculated(ctx, totalsEvent);
|
|
4364
4498
|
emitOrderLifecycleEventsForTransition({ order, previousStatus });
|
|
4365
4499
|
const dataEngine = ctx.container.resolve("dataEngine");
|
|
4366
4500
|
await emitCrudSideEffects({
|
|
@@ -5575,115 +5709,75 @@ async function assertOrderAcceptsNewLine(order, existingSnapshot) {
|
|
|
5575
5709
|
}
|
|
5576
5710
|
const orderLineUpsertCommand = {
|
|
5577
5711
|
id: "sales.orders.lines.upsert",
|
|
5578
|
-
async prepare(input
|
|
5579
|
-
|
|
5580
|
-
const orderId = typeof raw.orderId === "string" ? raw.orderId : null;
|
|
5581
|
-
if (!orderId) return {};
|
|
5582
|
-
const em = ctx.container.resolve("em");
|
|
5583
|
-
const snapshot = await loadOrderSnapshot(em, orderId);
|
|
5584
|
-
if (snapshot)
|
|
5585
|
-
ensureOrderScope(
|
|
5586
|
-
ctx,
|
|
5587
|
-
snapshot.order.organizationId,
|
|
5588
|
-
snapshot.order.tenantId
|
|
5589
|
-
);
|
|
5590
|
-
return snapshot ? { before: snapshot } : {};
|
|
5712
|
+
async prepare(input) {
|
|
5713
|
+
return markLockedGraphWrite(input);
|
|
5591
5714
|
},
|
|
5592
5715
|
async execute(input, ctx) {
|
|
5593
5716
|
const rawBody = input?.body ?? {};
|
|
5594
5717
|
const parsed = orderLineUpsertSchema.parse(rawBody);
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
|
|
5599
|
-
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
)
|
|
5611
|
-
]);
|
|
5612
|
-
const lineSnapshots = existingLines.map(mapOrderLineEntityToSnapshot);
|
|
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;
|
|
5620
|
-
await assertOrderAcceptsNewLine(order, existingSnapshot);
|
|
5621
|
-
await assertShippedOrderLineEditable(em, order, existingSnapshot, parsed);
|
|
5622
|
-
const priceMode = parsed.priceMode === "gross" ? "gross" : parsed.priceMode === "net" ? "net" : null;
|
|
5623
|
-
let unitPriceNet = parsed.unitPriceNet ?? existingSnapshot?.unitPriceNet ?? null;
|
|
5624
|
-
let unitPriceGross = parsed.unitPriceGross ?? existingSnapshot?.unitPriceGross ?? null;
|
|
5625
|
-
let taxRate = parsed.taxRate ?? existingSnapshot?.taxRate ?? null;
|
|
5626
|
-
const unitPriceSupplied = unitPriceNet !== null || unitPriceGross !== null;
|
|
5627
|
-
if (priceMode && (unitPriceNet === null || unitPriceGross === null)) {
|
|
5628
|
-
let taxService = null;
|
|
5629
|
-
try {
|
|
5630
|
-
taxService = ctx.container.resolve(
|
|
5631
|
-
"taxCalculationService"
|
|
5632
|
-
);
|
|
5633
|
-
} catch {
|
|
5634
|
-
taxService = null;
|
|
5635
|
-
}
|
|
5636
|
-
if (taxService) {
|
|
5637
|
-
const taxResult = await taxService.calculateUnitAmounts({
|
|
5638
|
-
amount: priceMode === "gross" ? unitPriceGross ?? unitPriceNet ?? 0 : unitPriceNet ?? unitPriceGross ?? 0,
|
|
5639
|
-
mode: priceMode,
|
|
5640
|
-
organizationId: parsed.organizationId,
|
|
5641
|
-
tenantId: parsed.tenantId,
|
|
5642
|
-
taxRateId: parsed.taxRateId ?? void 0,
|
|
5643
|
-
taxRate: taxRate ?? void 0
|
|
5644
|
-
});
|
|
5645
|
-
unitPriceNet = unitPriceNet ?? taxResult.netAmount;
|
|
5646
|
-
unitPriceGross = unitPriceGross ?? taxResult.grossAmount;
|
|
5647
|
-
taxRate = taxResult.taxRate ?? taxRate;
|
|
5718
|
+
return writeLockedOrderGraph(input, ctx, parsed.orderId, "Sales order not found", async (em, order) => {
|
|
5719
|
+
const [existingLines, adjustments] = await Promise.all([
|
|
5720
|
+
em.find(SalesOrderLine, { order }, { orderBy: { lineNumber: "asc" } }),
|
|
5721
|
+
em.find(
|
|
5722
|
+
SalesOrderAdjustment,
|
|
5723
|
+
{ order },
|
|
5724
|
+
{ orderBy: { position: "asc" } }
|
|
5725
|
+
)
|
|
5726
|
+
]);
|
|
5727
|
+
const lineSnapshots = existingLines.map(mapOrderLineEntityToSnapshot);
|
|
5728
|
+
const existingSnapshot = parsed.id ? lineSnapshots.find((line) => line.id === parsed.id) ?? null : null;
|
|
5729
|
+
const totalsMode = order.totalsMode ?? "computed";
|
|
5730
|
+
await assertUniformAmountsMode(totalsMode, [parsed]);
|
|
5731
|
+
if (isExternalAmountsMode(totalsMode)) {
|
|
5732
|
+
await requireOrderTotalsForExternalWrite(parsed.orderTotals);
|
|
5648
5733
|
}
|
|
5649
|
-
|
|
5650
|
-
|
|
5651
|
-
|
|
5652
|
-
|
|
5653
|
-
|
|
5654
|
-
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
|
|
5664
|
-
|
|
5665
|
-
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5734
|
+
const suppliedTotals = isExternalAmountsMode(totalsMode) ? buildExternalHeaderTotals(parsed.orderTotals) : null;
|
|
5735
|
+
await assertOrderAcceptsNewLine(order, existingSnapshot);
|
|
5736
|
+
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
|
+
}
|
|
5674
5764
|
}
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
|
|
5686
|
-
|
|
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
|
+
const uomResolver = createUomResolver();
|
|
5780
|
+
let normalizedUom = await normalizeLineUom({
|
|
5687
5781
|
em,
|
|
5688
5782
|
resolver: uomResolver,
|
|
5689
5783
|
organizationId: order.organizationId,
|
|
@@ -5694,134 +5788,130 @@ const orderLineUpsertCommand = {
|
|
|
5694
5788
|
unitPriceGross: unitPriceGross ?? unitPriceNet ?? 0
|
|
5695
5789
|
}
|
|
5696
5790
|
});
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
|
|
5707
|
-
|
|
5708
|
-
|
|
5709
|
-
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
|
|
5725
|
-
|
|
5726
|
-
|
|
5727
|
-
|
|
5728
|
-
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
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({
|
|
5802
|
+
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)
|
|
5742
5849
|
);
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
|
|
5752
|
-
|
|
5753
|
-
|
|
5754
|
-
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
5768
|
-
|
|
5769
|
-
|
|
5770
|
-
|
|
5771
|
-
|
|
5772
|
-
|
|
5773
|
-
|
|
5774
|
-
|
|
5775
|
-
|
|
5776
|
-
|
|
5777
|
-
|
|
5778
|
-
|
|
5779
|
-
|
|
5780
|
-
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
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;
|
|
5862
|
+
let nextLines = parsed.id ? lineSnapshots.map(
|
|
5863
|
+
(line) => line.id === parsed.id ? updatedSnapshot : line
|
|
5864
|
+
) : [...lineSnapshots, updatedSnapshot];
|
|
5865
|
+
nextLines = nextLines.sort((a, b) => (a.lineNumber ?? 0) - (b.lineNumber ?? 0)).map((line, index) => ({ ...line, lineNumber: index + 1 }));
|
|
5866
|
+
const sourceInputs = nextLines.map((line, index) => ({
|
|
5867
|
+
...line,
|
|
5868
|
+
statusEntryId: line.statusEntryId ?? null,
|
|
5869
|
+
catalogSnapshot: line.catalogSnapshot ?? null,
|
|
5870
|
+
promotionSnapshot: line.promotionSnapshot ?? null,
|
|
5871
|
+
organizationId: order.organizationId,
|
|
5872
|
+
tenantId: order.tenantId,
|
|
5873
|
+
orderId: order.id,
|
|
5874
|
+
lineNumber: line.lineNumber ?? index + 1
|
|
5875
|
+
}));
|
|
5876
|
+
const calcLines = sourceInputs.map(
|
|
5877
|
+
(line, index) => createLineSnapshotFromInput(line, line.lineNumber ?? index + 1)
|
|
5878
|
+
);
|
|
5879
|
+
const adjustmentDrafts = adjustments.map(mapOrderAdjustmentToDraft);
|
|
5880
|
+
const salesCalculationService = ctx.container.resolve("salesCalculationService");
|
|
5881
|
+
const calculationContext = buildCalculationContext({
|
|
5882
|
+
tenantId: order.tenantId,
|
|
5883
|
+
organizationId: order.organizationId,
|
|
5884
|
+
currencyCode: order.currencyCode,
|
|
5885
|
+
shippingSnapshot: order.shippingMethodSnapshot,
|
|
5886
|
+
paymentSnapshot: order.paymentMethodSnapshot,
|
|
5887
|
+
shippingMethodId: order.shippingMethodId ?? null,
|
|
5888
|
+
paymentMethodId: order.paymentMethodId ?? null,
|
|
5889
|
+
shippingMethodCode: order.shippingMethodCode ?? null,
|
|
5890
|
+
paymentMethodCode: order.paymentMethodCode ?? null
|
|
5891
|
+
});
|
|
5892
|
+
const calculation = await salesCalculationService.calculateDocumentTotals({
|
|
5893
|
+
documentKind: "order",
|
|
5894
|
+
lines: calcLines,
|
|
5895
|
+
adjustments: adjustmentDrafts,
|
|
5896
|
+
context: calculationContext,
|
|
5897
|
+
existingTotals: resolveExistingPaymentTotals(order),
|
|
5898
|
+
totalsMode,
|
|
5899
|
+
suppliedTotals
|
|
5900
|
+
});
|
|
5901
|
+
await applyOrderLineResults({
|
|
5902
|
+
em,
|
|
5903
|
+
order,
|
|
5904
|
+
calculation,
|
|
5905
|
+
sourceLines: sourceInputs,
|
|
5906
|
+
existingLines
|
|
5907
|
+
});
|
|
5908
|
+
applyOrderTotals(order, calculation.totals, calculation.lines.length);
|
|
5909
|
+
return { result: { orderId: order.id, lineId }, calculation };
|
|
5785
5910
|
});
|
|
5786
|
-
let eventBus = null;
|
|
5787
|
-
try {
|
|
5788
|
-
eventBus = ctx.container.resolve("eventBus");
|
|
5789
|
-
} catch {
|
|
5790
|
-
eventBus = null;
|
|
5791
|
-
}
|
|
5792
|
-
await withAtomicFlush(
|
|
5793
|
-
em,
|
|
5794
|
-
[
|
|
5795
|
-
async () => {
|
|
5796
|
-
await applyOrderLineResults({
|
|
5797
|
-
em,
|
|
5798
|
-
order,
|
|
5799
|
-
calculation,
|
|
5800
|
-
sourceLines: sourceInputs,
|
|
5801
|
-
existingLines
|
|
5802
|
-
});
|
|
5803
|
-
applyOrderTotals(order, calculation.totals, calculation.lines.length);
|
|
5804
|
-
await emitTotalsCalculated(eventBus, {
|
|
5805
|
-
documentKind: "order",
|
|
5806
|
-
documentId: order.id,
|
|
5807
|
-
organizationId: order.organizationId,
|
|
5808
|
-
tenantId: order.tenantId,
|
|
5809
|
-
customerId: order.customerEntityId ?? null,
|
|
5810
|
-
totals: calculation.totals,
|
|
5811
|
-
lineCount: calculation.lines.length
|
|
5812
|
-
});
|
|
5813
|
-
}
|
|
5814
|
-
],
|
|
5815
|
-
{ transaction: true }
|
|
5816
|
-
);
|
|
5817
|
-
return { orderId: order.id, lineId };
|
|
5818
|
-
},
|
|
5819
|
-
captureAfter: async (_input, result, ctx) => {
|
|
5820
|
-
const em = ctx.container.resolve("em").fork();
|
|
5821
|
-
return loadOrderSnapshot(em, result.orderId);
|
|
5822
5911
|
},
|
|
5912
|
+
captureAfter: async (_input, result, ctx) => captureLockedOrderGraphAfter(ctx, result, result.orderId),
|
|
5823
5913
|
buildLog: async ({ result, snapshots }) => {
|
|
5824
|
-
const before = snapshots.before;
|
|
5914
|
+
const before = resolveLockedOrderGraphBefore(result, snapshots.before);
|
|
5825
5915
|
const after = snapshots.after;
|
|
5826
5916
|
if (!after) return null;
|
|
5827
5917
|
const { translate } = await resolveTranslations();
|
|
@@ -5850,153 +5940,109 @@ const orderLineUpsertCommand = {
|
|
|
5850
5940
|
};
|
|
5851
5941
|
const orderLineDeleteCommand = {
|
|
5852
5942
|
id: "sales.orders.lines.delete",
|
|
5853
|
-
async prepare(input
|
|
5854
|
-
|
|
5855
|
-
const orderId = typeof raw.orderId === "string" ? raw.orderId : null;
|
|
5856
|
-
if (!orderId) return {};
|
|
5857
|
-
const em = ctx.container.resolve("em");
|
|
5858
|
-
const snapshot = await loadOrderSnapshot(em, orderId);
|
|
5859
|
-
if (snapshot)
|
|
5860
|
-
ensureOrderScope(
|
|
5861
|
-
ctx,
|
|
5862
|
-
snapshot.order.organizationId,
|
|
5863
|
-
snapshot.order.tenantId
|
|
5864
|
-
);
|
|
5865
|
-
return snapshot ? { before: snapshot } : {};
|
|
5943
|
+
async prepare(input) {
|
|
5944
|
+
return markLockedGraphWrite(input);
|
|
5866
5945
|
},
|
|
5867
5946
|
async execute(input, ctx) {
|
|
5868
5947
|
const { translate } = await resolveTranslations();
|
|
5869
5948
|
const parsed = orderLineDeleteSchema.parse(
|
|
5870
5949
|
input?.body ?? {}
|
|
5871
5950
|
);
|
|
5872
|
-
|
|
5873
|
-
|
|
5874
|
-
|
|
5875
|
-
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
"sales.documents.detail.error",
|
|
5880
|
-
"Document not found or inaccessible."
|
|
5881
|
-
));
|
|
5882
|
-
ensureOrderScope(ctx, order.organizationId, order.tenantId);
|
|
5883
|
-
await enforceSalesDocumentOptimisticLock(ctx, order, SALES_RESOURCE_KIND_ORDER);
|
|
5884
|
-
const shipmentCount = await em.count(SalesShipmentItem, {
|
|
5885
|
-
orderLine: parsed.id,
|
|
5886
|
-
shipment: { deletedAt: null }
|
|
5887
|
-
});
|
|
5888
|
-
if (shipmentCount > 0) {
|
|
5889
|
-
throw new CrudHttpError(409, {
|
|
5890
|
-
error: translate(
|
|
5891
|
-
"sales.documents.items.errorDeleteShipped",
|
|
5892
|
-
"Cannot delete a line that has shipped items."
|
|
5893
|
-
)
|
|
5951
|
+
return writeLockedOrderGraph(input, ctx, parsed.orderId, translate(
|
|
5952
|
+
"sales.documents.detail.error",
|
|
5953
|
+
"Document not found or inaccessible."
|
|
5954
|
+
), async (em, order) => {
|
|
5955
|
+
const shipmentCount = await em.count(SalesShipmentItem, {
|
|
5956
|
+
orderLine: parsed.id,
|
|
5957
|
+
shipment: { deletedAt: null }
|
|
5894
5958
|
});
|
|
5895
|
-
|
|
5896
|
-
|
|
5897
|
-
|
|
5898
|
-
|
|
5899
|
-
|
|
5900
|
-
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
|
|
5905
|
-
|
|
5906
|
-
|
|
5907
|
-
|
|
5908
|
-
|
|
5909
|
-
|
|
5910
|
-
|
|
5911
|
-
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
|
|
5915
|
-
|
|
5916
|
-
"sales.documents.
|
|
5917
|
-
"
|
|
5918
|
-
)
|
|
5959
|
+
if (shipmentCount > 0) {
|
|
5960
|
+
throw new CrudHttpError(409, {
|
|
5961
|
+
error: translate(
|
|
5962
|
+
"sales.documents.items.errorDeleteShipped",
|
|
5963
|
+
"Cannot delete a line that has shipped items."
|
|
5964
|
+
)
|
|
5965
|
+
});
|
|
5966
|
+
}
|
|
5967
|
+
const existingLines = await em.find(
|
|
5968
|
+
SalesOrderLine,
|
|
5969
|
+
{ order },
|
|
5970
|
+
{ orderBy: { lineNumber: "asc" } }
|
|
5971
|
+
);
|
|
5972
|
+
const adjustments = await em.find(
|
|
5973
|
+
SalesOrderAdjustment,
|
|
5974
|
+
{ order },
|
|
5975
|
+
{ orderBy: { position: "asc" } }
|
|
5976
|
+
);
|
|
5977
|
+
const filtered = existingLines.filter((line) => line.id !== parsed.id);
|
|
5978
|
+
if (filtered.length === existingLines.length) {
|
|
5979
|
+
throw notFound(translate(
|
|
5980
|
+
"sales.documents.detail.error",
|
|
5981
|
+
"Document not found or inaccessible."
|
|
5982
|
+
));
|
|
5983
|
+
}
|
|
5984
|
+
if (filtered.length === 0) {
|
|
5985
|
+
throw new CrudHttpError(409, {
|
|
5986
|
+
error: translate(
|
|
5987
|
+
"sales.documents.items.errorDeleteLast",
|
|
5988
|
+
"An order must contain at least one line item."
|
|
5989
|
+
)
|
|
5990
|
+
});
|
|
5991
|
+
}
|
|
5992
|
+
const totalsMode = order.totalsMode ?? "computed";
|
|
5993
|
+
if (isExternalAmountsMode(totalsMode)) {
|
|
5994
|
+
await requireOrderTotalsForExternalWrite(parsed.orderTotals);
|
|
5995
|
+
}
|
|
5996
|
+
const suppliedTotals = isExternalAmountsMode(totalsMode) ? buildExternalHeaderTotals(parsed.orderTotals) : null;
|
|
5997
|
+
const sourceInputs = filtered.map((line, index) => ({
|
|
5998
|
+
...mapOrderLineEntityToSnapshot(line),
|
|
5999
|
+
statusEntryId: line.statusEntryId ?? null,
|
|
6000
|
+
catalogSnapshot: line.catalogSnapshot ? cloneJson(line.catalogSnapshot) : null,
|
|
6001
|
+
promotionSnapshot: line.promotionSnapshot ? cloneJson(line.promotionSnapshot) : null,
|
|
6002
|
+
organizationId: order.organizationId,
|
|
6003
|
+
tenantId: order.tenantId,
|
|
6004
|
+
orderId: order.id,
|
|
6005
|
+
lineNumber: index + 1
|
|
6006
|
+
}));
|
|
6007
|
+
const calcLines = sourceInputs.map(
|
|
6008
|
+
(line, index) => createLineSnapshotFromInput(line, line.lineNumber ?? index + 1)
|
|
6009
|
+
);
|
|
6010
|
+
const adjustmentDrafts = adjustments.map(mapOrderAdjustmentToDraft);
|
|
6011
|
+
const salesCalculationService = ctx.container.resolve("salesCalculationService");
|
|
6012
|
+
const calculationContext = buildCalculationContext({
|
|
6013
|
+
tenantId: order.tenantId,
|
|
6014
|
+
organizationId: order.organizationId,
|
|
6015
|
+
currencyCode: order.currencyCode,
|
|
6016
|
+
shippingSnapshot: order.shippingMethodSnapshot,
|
|
6017
|
+
paymentSnapshot: order.paymentMethodSnapshot,
|
|
6018
|
+
shippingMethodId: order.shippingMethodId ?? null,
|
|
6019
|
+
paymentMethodId: order.paymentMethodId ?? null,
|
|
6020
|
+
shippingMethodCode: order.shippingMethodCode ?? null,
|
|
6021
|
+
paymentMethodCode: order.paymentMethodCode ?? null
|
|
5919
6022
|
});
|
|
5920
|
-
|
|
5921
|
-
|
|
5922
|
-
|
|
5923
|
-
|
|
5924
|
-
|
|
5925
|
-
|
|
5926
|
-
|
|
5927
|
-
|
|
5928
|
-
|
|
5929
|
-
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
|
|
5934
|
-
|
|
5935
|
-
|
|
5936
|
-
|
|
5937
|
-
|
|
5938
|
-
);
|
|
5939
|
-
const adjustmentDrafts = adjustments.map(mapOrderAdjustmentToDraft);
|
|
5940
|
-
const salesCalculationService = ctx.container.resolve("salesCalculationService");
|
|
5941
|
-
const calculationContext = buildCalculationContext({
|
|
5942
|
-
tenantId: order.tenantId,
|
|
5943
|
-
organizationId: order.organizationId,
|
|
5944
|
-
currencyCode: order.currencyCode,
|
|
5945
|
-
shippingSnapshot: order.shippingMethodSnapshot,
|
|
5946
|
-
paymentSnapshot: order.paymentMethodSnapshot,
|
|
5947
|
-
shippingMethodId: order.shippingMethodId ?? null,
|
|
5948
|
-
paymentMethodId: order.paymentMethodId ?? null,
|
|
5949
|
-
shippingMethodCode: order.shippingMethodCode ?? null,
|
|
5950
|
-
paymentMethodCode: order.paymentMethodCode ?? null
|
|
5951
|
-
});
|
|
5952
|
-
const calculation = await salesCalculationService.calculateDocumentTotals({
|
|
5953
|
-
documentKind: "order",
|
|
5954
|
-
lines: calcLines,
|
|
5955
|
-
adjustments: adjustmentDrafts,
|
|
5956
|
-
context: calculationContext,
|
|
5957
|
-
existingTotals: resolveExistingPaymentTotals(order),
|
|
5958
|
-
totalsMode,
|
|
5959
|
-
suppliedTotals
|
|
6023
|
+
const calculation = await salesCalculationService.calculateDocumentTotals({
|
|
6024
|
+
documentKind: "order",
|
|
6025
|
+
lines: calcLines,
|
|
6026
|
+
adjustments: adjustmentDrafts,
|
|
6027
|
+
context: calculationContext,
|
|
6028
|
+
existingTotals: resolveExistingPaymentTotals(order),
|
|
6029
|
+
totalsMode,
|
|
6030
|
+
suppliedTotals
|
|
6031
|
+
});
|
|
6032
|
+
await applyOrderLineResults({
|
|
6033
|
+
em,
|
|
6034
|
+
order,
|
|
6035
|
+
calculation,
|
|
6036
|
+
sourceLines: sourceInputs,
|
|
6037
|
+
existingLines
|
|
6038
|
+
});
|
|
6039
|
+
applyOrderTotals(order, calculation.totals, calculation.lines.length);
|
|
6040
|
+
return { result: { orderId: order.id, lineId: parsed.id }, calculation };
|
|
5960
6041
|
});
|
|
5961
|
-
let eventBus = null;
|
|
5962
|
-
try {
|
|
5963
|
-
eventBus = ctx.container.resolve("eventBus");
|
|
5964
|
-
} catch {
|
|
5965
|
-
eventBus = null;
|
|
5966
|
-
}
|
|
5967
|
-
await withAtomicFlush(
|
|
5968
|
-
em,
|
|
5969
|
-
[
|
|
5970
|
-
async () => {
|
|
5971
|
-
await applyOrderLineResults({
|
|
5972
|
-
em,
|
|
5973
|
-
order,
|
|
5974
|
-
calculation,
|
|
5975
|
-
sourceLines: sourceInputs,
|
|
5976
|
-
existingLines
|
|
5977
|
-
});
|
|
5978
|
-
applyOrderTotals(order, calculation.totals, calculation.lines.length);
|
|
5979
|
-
await emitTotalsCalculated(eventBus, {
|
|
5980
|
-
documentKind: "order",
|
|
5981
|
-
documentId: order.id,
|
|
5982
|
-
organizationId: order.organizationId,
|
|
5983
|
-
tenantId: order.tenantId,
|
|
5984
|
-
customerId: order.customerEntityId ?? null,
|
|
5985
|
-
totals: calculation.totals,
|
|
5986
|
-
lineCount: calculation.lines.length
|
|
5987
|
-
});
|
|
5988
|
-
}
|
|
5989
|
-
],
|
|
5990
|
-
{ transaction: true }
|
|
5991
|
-
);
|
|
5992
|
-
return { orderId: order.id, lineId: parsed.id };
|
|
5993
|
-
},
|
|
5994
|
-
captureAfter: async (_input, result, ctx) => {
|
|
5995
|
-
const em = ctx.container.resolve("em").fork();
|
|
5996
|
-
return loadOrderSnapshot(em, result.orderId);
|
|
5997
6042
|
},
|
|
6043
|
+
captureAfter: async (_input, result, ctx) => captureLockedOrderGraphAfter(ctx, result, result.orderId),
|
|
5998
6044
|
buildLog: async ({ result, snapshots }) => {
|
|
5999
|
-
const before = snapshots.before;
|
|
6045
|
+
const before = resolveLockedOrderGraphBefore(result, snapshots.before);
|
|
6000
6046
|
const after = snapshots.after;
|
|
6001
6047
|
if (!after) return null;
|
|
6002
6048
|
const { translate } = await resolveTranslations();
|
|
@@ -6025,107 +6071,67 @@ const orderLineDeleteCommand = {
|
|
|
6025
6071
|
};
|
|
6026
6072
|
const quoteLineUpsertCommand = {
|
|
6027
6073
|
id: "sales.quotes.lines.upsert",
|
|
6028
|
-
async prepare(input
|
|
6029
|
-
|
|
6030
|
-
const quoteId = typeof raw.quoteId === "string" ? raw.quoteId : null;
|
|
6031
|
-
if (!quoteId) return {};
|
|
6032
|
-
const em = ctx.container.resolve("em");
|
|
6033
|
-
const snapshot = await loadQuoteSnapshot(em, quoteId);
|
|
6034
|
-
if (snapshot)
|
|
6035
|
-
ensureQuoteScope(
|
|
6036
|
-
ctx,
|
|
6037
|
-
snapshot.quote.organizationId,
|
|
6038
|
-
snapshot.quote.tenantId
|
|
6039
|
-
);
|
|
6040
|
-
return snapshot ? { before: snapshot } : {};
|
|
6074
|
+
async prepare(input) {
|
|
6075
|
+
return markLockedGraphWrite(input);
|
|
6041
6076
|
},
|
|
6042
6077
|
async execute(input, ctx) {
|
|
6043
6078
|
const rawBody = input?.body ?? {};
|
|
6044
6079
|
const parsed = quoteLineUpsertSchema.parse(rawBody);
|
|
6045
6080
|
await assertAmountsModeUnsupportedOnQuote([parsed]);
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
|
|
6051
|
-
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
|
|
6056
|
-
|
|
6057
|
-
|
|
6058
|
-
|
|
6059
|
-
|
|
6060
|
-
|
|
6061
|
-
)
|
|
6062
|
-
|
|
6063
|
-
|
|
6064
|
-
|
|
6065
|
-
|
|
6066
|
-
|
|
6067
|
-
|
|
6068
|
-
|
|
6069
|
-
|
|
6070
|
-
|
|
6071
|
-
|
|
6072
|
-
|
|
6073
|
-
|
|
6074
|
-
|
|
6075
|
-
|
|
6076
|
-
|
|
6077
|
-
|
|
6078
|
-
|
|
6079
|
-
|
|
6080
|
-
|
|
6081
|
-
|
|
6082
|
-
|
|
6083
|
-
tenantId: parsed.tenantId,
|
|
6084
|
-
taxRateId: parsed.taxRateId ?? void 0,
|
|
6085
|
-
taxRate: taxRate ?? void 0
|
|
6086
|
-
});
|
|
6087
|
-
unitPriceNet = unitPriceNet ?? taxResult.netAmount;
|
|
6088
|
-
unitPriceGross = unitPriceGross ?? taxResult.grossAmount;
|
|
6089
|
-
taxRate = taxResult.taxRate ?? taxRate;
|
|
6090
|
-
}
|
|
6091
|
-
}
|
|
6092
|
-
const metadata = typeof parsed.metadata === "object" && parsed.metadata ? { ...parsed.metadata } : existingSnapshot?.metadata ? cloneJson(existingSnapshot.metadata) : {};
|
|
6093
|
-
if (parsed.priceId) metadata.priceId = parsed.priceId;
|
|
6094
|
-
if (priceMode) metadata.priceMode = priceMode;
|
|
6095
|
-
const statusEntryId = parsed.statusEntryId ?? existingSnapshot?.statusEntryId ?? null;
|
|
6096
|
-
const lineId = parsed.id ?? existingSnapshot?.id ?? randomUUID();
|
|
6097
|
-
const lineUomInput = {
|
|
6098
|
-
productId: parsed.productId ?? existingSnapshot?.productId ?? null,
|
|
6099
|
-
productVariantId: parsed.productVariantId ?? existingSnapshot?.productVariantId ?? null,
|
|
6100
|
-
quantity: parsed.quantity ?? existingSnapshot?.quantity ?? 0,
|
|
6101
|
-
quantityUnit: parsed.quantityUnit ?? existingSnapshot?.quantityUnit ?? null,
|
|
6102
|
-
normalizedQuantity: existingSnapshot?.normalizedQuantity ?? null,
|
|
6103
|
-
normalizedUnit: existingSnapshot?.normalizedUnit ?? null,
|
|
6104
|
-
uomSnapshot: existingSnapshot?.uomSnapshot ?? null
|
|
6105
|
-
};
|
|
6106
|
-
const uomResolver = createUomResolver();
|
|
6107
|
-
let normalizedUom = await normalizeLineUom({
|
|
6108
|
-
em,
|
|
6109
|
-
resolver: uomResolver,
|
|
6110
|
-
organizationId: quote.organizationId,
|
|
6111
|
-
tenantId: quote.tenantId,
|
|
6112
|
-
line: {
|
|
6113
|
-
...lineUomInput,
|
|
6114
|
-
unitPriceNet: unitPriceNet ?? 0,
|
|
6115
|
-
unitPriceGross: unitPriceGross ?? unitPriceNet ?? 0
|
|
6081
|
+
return writeLockedQuoteGraph(input, ctx, parsed.quoteId, "Sales quote not found", async (em, quote) => {
|
|
6082
|
+
const [existingLines, adjustments] = await Promise.all([
|
|
6083
|
+
em.find(SalesQuoteLine, { quote }, { orderBy: { lineNumber: "asc" } }),
|
|
6084
|
+
em.find(
|
|
6085
|
+
SalesQuoteAdjustment,
|
|
6086
|
+
{ quote },
|
|
6087
|
+
{ orderBy: { position: "asc" } }
|
|
6088
|
+
)
|
|
6089
|
+
]);
|
|
6090
|
+
const lineSnapshots = existingLines.map(mapQuoteLineEntityToSnapshot);
|
|
6091
|
+
const existingSnapshot = parsed.id ? lineSnapshots.find((line) => line.id === parsed.id) ?? null : null;
|
|
6092
|
+
const priceMode = parsed.priceMode === "gross" ? "gross" : parsed.priceMode === "net" ? "net" : null;
|
|
6093
|
+
let unitPriceNet = parsed.unitPriceNet ?? existingSnapshot?.unitPriceNet ?? null;
|
|
6094
|
+
let unitPriceGross = parsed.unitPriceGross ?? existingSnapshot?.unitPriceGross ?? null;
|
|
6095
|
+
let taxRate = parsed.taxRate ?? existingSnapshot?.taxRate ?? null;
|
|
6096
|
+
if (priceMode && (unitPriceNet === null || unitPriceGross === null)) {
|
|
6097
|
+
let taxService = null;
|
|
6098
|
+
try {
|
|
6099
|
+
taxService = ctx.container.resolve(
|
|
6100
|
+
"taxCalculationService"
|
|
6101
|
+
);
|
|
6102
|
+
} catch {
|
|
6103
|
+
taxService = null;
|
|
6104
|
+
}
|
|
6105
|
+
if (taxService) {
|
|
6106
|
+
const taxResult = await taxService.calculateUnitAmounts({
|
|
6107
|
+
amount: priceMode === "gross" ? unitPriceGross ?? unitPriceNet ?? 0 : unitPriceNet ?? unitPriceGross ?? 0,
|
|
6108
|
+
mode: priceMode,
|
|
6109
|
+
organizationId: parsed.organizationId,
|
|
6110
|
+
tenantId: parsed.tenantId,
|
|
6111
|
+
taxRateId: parsed.taxRateId ?? void 0,
|
|
6112
|
+
taxRate: taxRate ?? void 0
|
|
6113
|
+
});
|
|
6114
|
+
unitPriceNet = unitPriceNet ?? taxResult.netAmount;
|
|
6115
|
+
unitPriceGross = unitPriceGross ?? taxResult.grossAmount;
|
|
6116
|
+
taxRate = taxResult.taxRate ?? taxRate;
|
|
6117
|
+
}
|
|
6116
6118
|
}
|
|
6117
|
-
|
|
6118
|
-
|
|
6119
|
-
|
|
6120
|
-
|
|
6121
|
-
|
|
6122
|
-
|
|
6123
|
-
|
|
6124
|
-
|
|
6125
|
-
|
|
6126
|
-
|
|
6127
|
-
|
|
6128
|
-
|
|
6119
|
+
const metadata = typeof parsed.metadata === "object" && parsed.metadata ? { ...parsed.metadata } : existingSnapshot?.metadata ? cloneJson(existingSnapshot.metadata) : {};
|
|
6120
|
+
if (parsed.priceId) metadata.priceId = parsed.priceId;
|
|
6121
|
+
if (priceMode) metadata.priceMode = priceMode;
|
|
6122
|
+
const statusEntryId = parsed.statusEntryId ?? existingSnapshot?.statusEntryId ?? null;
|
|
6123
|
+
const lineId = parsed.id ?? existingSnapshot?.id ?? randomUUID();
|
|
6124
|
+
const lineUomInput = {
|
|
6125
|
+
productId: parsed.productId ?? existingSnapshot?.productId ?? null,
|
|
6126
|
+
productVariantId: parsed.productVariantId ?? existingSnapshot?.productVariantId ?? null,
|
|
6127
|
+
quantity: parsed.quantity ?? existingSnapshot?.quantity ?? 0,
|
|
6128
|
+
quantityUnit: parsed.quantityUnit ?? existingSnapshot?.quantityUnit ?? null,
|
|
6129
|
+
normalizedQuantity: existingSnapshot?.normalizedQuantity ?? null,
|
|
6130
|
+
normalizedUnit: existingSnapshot?.normalizedUnit ?? null,
|
|
6131
|
+
uomSnapshot: existingSnapshot?.uomSnapshot ?? null
|
|
6132
|
+
};
|
|
6133
|
+
const uomResolver = createUomResolver();
|
|
6134
|
+
let normalizedUom = await normalizeLineUom({
|
|
6129
6135
|
em,
|
|
6130
6136
|
resolver: uomResolver,
|
|
6131
6137
|
organizationId: quote.organizationId,
|
|
@@ -6136,121 +6142,117 @@ const quoteLineUpsertCommand = {
|
|
|
6136
6142
|
unitPriceGross: unitPriceGross ?? unitPriceNet ?? 0
|
|
6137
6143
|
}
|
|
6138
6144
|
});
|
|
6139
|
-
|
|
6140
|
-
|
|
6141
|
-
|
|
6142
|
-
|
|
6143
|
-
|
|
6144
|
-
|
|
6145
|
-
|
|
6146
|
-
|
|
6147
|
-
|
|
6148
|
-
|
|
6149
|
-
|
|
6150
|
-
|
|
6151
|
-
|
|
6152
|
-
|
|
6153
|
-
|
|
6154
|
-
|
|
6155
|
-
|
|
6156
|
-
|
|
6157
|
-
|
|
6158
|
-
|
|
6159
|
-
|
|
6160
|
-
|
|
6161
|
-
|
|
6162
|
-
|
|
6163
|
-
|
|
6164
|
-
|
|
6165
|
-
|
|
6166
|
-
|
|
6167
|
-
|
|
6168
|
-
|
|
6169
|
-
|
|
6170
|
-
|
|
6171
|
-
|
|
6172
|
-
|
|
6173
|
-
|
|
6174
|
-
|
|
6175
|
-
|
|
6176
|
-
|
|
6177
|
-
|
|
6178
|
-
|
|
6179
|
-
|
|
6180
|
-
|
|
6181
|
-
|
|
6182
|
-
|
|
6183
|
-
|
|
6184
|
-
|
|
6185
|
-
|
|
6186
|
-
|
|
6187
|
-
|
|
6188
|
-
|
|
6189
|
-
|
|
6190
|
-
|
|
6191
|
-
|
|
6192
|
-
|
|
6193
|
-
|
|
6194
|
-
|
|
6195
|
-
|
|
6196
|
-
|
|
6197
|
-
|
|
6198
|
-
|
|
6199
|
-
|
|
6200
|
-
|
|
6201
|
-
|
|
6202
|
-
|
|
6203
|
-
|
|
6204
|
-
|
|
6205
|
-
|
|
6206
|
-
|
|
6207
|
-
|
|
6208
|
-
|
|
6209
|
-
|
|
6210
|
-
|
|
6211
|
-
|
|
6212
|
-
|
|
6213
|
-
|
|
6145
|
+
const convertedPrices = convertLineUnitPricesOnUnitChange({
|
|
6146
|
+
existingSnapshot,
|
|
6147
|
+
nextQuantityUnit: normalizedUom.quantityUnit,
|
|
6148
|
+
nextUomSnapshot: normalizedUom.uomSnapshot,
|
|
6149
|
+
unitPriceNet,
|
|
6150
|
+
unitPriceGross
|
|
6151
|
+
});
|
|
6152
|
+
if (convertedPrices.didConvert) {
|
|
6153
|
+
unitPriceNet = convertedPrices.unitPriceNet;
|
|
6154
|
+
unitPriceGross = convertedPrices.unitPriceGross;
|
|
6155
|
+
normalizedUom = await normalizeLineUom({
|
|
6156
|
+
em,
|
|
6157
|
+
resolver: uomResolver,
|
|
6158
|
+
organizationId: quote.organizationId,
|
|
6159
|
+
tenantId: quote.tenantId,
|
|
6160
|
+
line: {
|
|
6161
|
+
...lineUomInput,
|
|
6162
|
+
unitPriceNet: unitPriceNet ?? 0,
|
|
6163
|
+
unitPriceGross: unitPriceGross ?? unitPriceNet ?? 0
|
|
6164
|
+
}
|
|
6165
|
+
});
|
|
6166
|
+
}
|
|
6167
|
+
const updatedSnapshot = {
|
|
6168
|
+
id: lineId,
|
|
6169
|
+
lineNumber: parsed.lineNumber ?? existingSnapshot?.lineNumber ?? lineSnapshots.length + 1,
|
|
6170
|
+
kind: parsed.kind ?? existingSnapshot?.kind ?? "product",
|
|
6171
|
+
productId: parsed.productId ?? existingSnapshot?.productId ?? null,
|
|
6172
|
+
productVariantId: parsed.productVariantId ?? existingSnapshot?.productVariantId ?? null,
|
|
6173
|
+
name: parsed.name ?? existingSnapshot?.name ?? null,
|
|
6174
|
+
description: parsed.description ?? existingSnapshot?.description ?? null,
|
|
6175
|
+
comment: parsed.comment ?? existingSnapshot?.comment ?? null,
|
|
6176
|
+
quantity: normalizedUom.quantity,
|
|
6177
|
+
quantityUnit: normalizedUom.quantityUnit,
|
|
6178
|
+
normalizedQuantity: normalizedUom.normalizedQuantity,
|
|
6179
|
+
normalizedUnit: normalizedUom.normalizedUnit,
|
|
6180
|
+
uomSnapshot: normalizedUom.uomSnapshot ? cloneJson(normalizedUom.uomSnapshot) : null,
|
|
6181
|
+
currencyCode: parsed.currencyCode ?? existingSnapshot?.currencyCode ?? quote.currencyCode,
|
|
6182
|
+
unitPriceNet: unitPriceNet ?? 0,
|
|
6183
|
+
unitPriceGross: unitPriceGross ?? unitPriceNet ?? 0,
|
|
6184
|
+
...resolveUpsertDiscountFields(
|
|
6185
|
+
parsed.discountAmount,
|
|
6186
|
+
parsed.discountAmountBasis,
|
|
6187
|
+
existingSnapshot
|
|
6188
|
+
),
|
|
6189
|
+
discountPercent: parsed.discountPercent ?? existingSnapshot?.discountPercent ?? 0,
|
|
6190
|
+
taxRate: taxRate ?? 0,
|
|
6191
|
+
totalNetAmount: parsed.totalNetAmount ?? existingSnapshot?.totalNetAmount ?? null,
|
|
6192
|
+
...resolveUpsertTotalsOrigin(parsed.totalNetAmount, existingSnapshot),
|
|
6193
|
+
configuration: parsed.configuration ?? existingSnapshot?.configuration ?? null,
|
|
6194
|
+
promotionCode: parsed.promotionCode ?? existingSnapshot?.promotionCode ?? null,
|
|
6195
|
+
metadata,
|
|
6196
|
+
customFieldSetId: parsed.customFieldSetId ?? existingSnapshot?.customFieldSetId ?? null,
|
|
6197
|
+
customFields: parsed.customFields && typeof parsed.customFields === "object" ? cloneJson(parsed.customFields) : existingSnapshot?.customFields ?? null
|
|
6198
|
+
};
|
|
6199
|
+
Object.assign(
|
|
6200
|
+
updatedSnapshot,
|
|
6201
|
+
resolveUpsertCalculatedAmounts(parsed, updatedSnapshot, existingSnapshot)
|
|
6202
|
+
);
|
|
6203
|
+
updatedSnapshot.statusEntryId = statusEntryId;
|
|
6204
|
+
updatedSnapshot.catalogSnapshot = parsed.catalogSnapshot ?? existingSnapshot?.catalogSnapshot ?? null;
|
|
6205
|
+
updatedSnapshot.promotionSnapshot = parsed.promotionSnapshot ?? existingSnapshot?.promotionSnapshot ?? null;
|
|
6206
|
+
let nextLines = parsed.id ? lineSnapshots.map(
|
|
6207
|
+
(line) => line.id === parsed.id ? updatedSnapshot : line
|
|
6208
|
+
) : [...lineSnapshots, updatedSnapshot];
|
|
6209
|
+
nextLines = nextLines.sort((a, b) => (a.lineNumber ?? 0) - (b.lineNumber ?? 0)).map((line, index) => ({ ...line, lineNumber: index + 1 }));
|
|
6210
|
+
const sourceInputs = nextLines.map((line, index) => ({
|
|
6211
|
+
...line,
|
|
6212
|
+
statusEntryId: line.statusEntryId ?? null,
|
|
6213
|
+
catalogSnapshot: line.catalogSnapshot ?? null,
|
|
6214
|
+
promotionSnapshot: line.promotionSnapshot ?? null,
|
|
6215
|
+
organizationId: quote.organizationId,
|
|
6216
|
+
tenantId: quote.tenantId,
|
|
6217
|
+
quoteId: quote.id,
|
|
6218
|
+
lineNumber: line.lineNumber ?? index + 1
|
|
6219
|
+
}));
|
|
6220
|
+
const calcLines = sourceInputs.map(
|
|
6221
|
+
(line, index) => createLineSnapshotFromInput(line, line.lineNumber ?? index + 1)
|
|
6222
|
+
);
|
|
6223
|
+
const adjustmentDrafts = adjustments.map(mapQuoteAdjustmentToDraft);
|
|
6224
|
+
const salesCalculationService = ctx.container.resolve("salesCalculationService");
|
|
6225
|
+
const calculationContext = buildCalculationContext({
|
|
6226
|
+
tenantId: quote.tenantId,
|
|
6227
|
+
organizationId: quote.organizationId,
|
|
6228
|
+
currencyCode: quote.currencyCode,
|
|
6229
|
+
shippingSnapshot: quote.shippingMethodSnapshot,
|
|
6230
|
+
paymentSnapshot: quote.paymentMethodSnapshot,
|
|
6231
|
+
shippingMethodId: quote.shippingMethodId ?? null,
|
|
6232
|
+
paymentMethodId: quote.paymentMethodId ?? null,
|
|
6233
|
+
shippingMethodCode: quote.shippingMethodCode ?? null,
|
|
6234
|
+
paymentMethodCode: quote.paymentMethodCode ?? null
|
|
6235
|
+
});
|
|
6236
|
+
const calculation = await salesCalculationService.calculateDocumentTotals({
|
|
6237
|
+
documentKind: "quote",
|
|
6238
|
+
lines: calcLines,
|
|
6239
|
+
adjustments: adjustmentDrafts,
|
|
6240
|
+
context: calculationContext
|
|
6241
|
+
});
|
|
6242
|
+
await applyQuoteLineResults({
|
|
6243
|
+
em,
|
|
6244
|
+
quote,
|
|
6245
|
+
calculation,
|
|
6246
|
+
sourceLines: sourceInputs,
|
|
6247
|
+
existingLines
|
|
6248
|
+
});
|
|
6249
|
+
applyQuoteTotals(quote, calculation.totals, calculation.lines.length);
|
|
6250
|
+
return { result: { quoteId: quote.id, lineId }, calculation };
|
|
6214
6251
|
});
|
|
6215
|
-
let eventBus = null;
|
|
6216
|
-
try {
|
|
6217
|
-
eventBus = ctx.container.resolve("eventBus");
|
|
6218
|
-
} catch {
|
|
6219
|
-
eventBus = null;
|
|
6220
|
-
}
|
|
6221
|
-
await withAtomicFlush(
|
|
6222
|
-
em,
|
|
6223
|
-
[
|
|
6224
|
-
async () => {
|
|
6225
|
-
await applyQuoteLineResults({
|
|
6226
|
-
em,
|
|
6227
|
-
quote,
|
|
6228
|
-
calculation,
|
|
6229
|
-
sourceLines: sourceInputs,
|
|
6230
|
-
existingLines
|
|
6231
|
-
});
|
|
6232
|
-
applyQuoteTotals(quote, calculation.totals, calculation.lines.length);
|
|
6233
|
-
await emitTotalsCalculated(eventBus, {
|
|
6234
|
-
documentKind: "quote",
|
|
6235
|
-
documentId: quote.id,
|
|
6236
|
-
organizationId: quote.organizationId,
|
|
6237
|
-
tenantId: quote.tenantId,
|
|
6238
|
-
customerId: quote.customerEntityId ?? null,
|
|
6239
|
-
totals: calculation.totals,
|
|
6240
|
-
lineCount: calculation.lines.length
|
|
6241
|
-
});
|
|
6242
|
-
}
|
|
6243
|
-
],
|
|
6244
|
-
{ transaction: true }
|
|
6245
|
-
);
|
|
6246
|
-
return { quoteId: quote.id, lineId };
|
|
6247
|
-
},
|
|
6248
|
-
captureAfter: async (_input, result, ctx) => {
|
|
6249
|
-
const em = ctx.container.resolve("em").fork();
|
|
6250
|
-
return loadQuoteSnapshot(em, result.quoteId);
|
|
6251
6252
|
},
|
|
6253
|
+
captureAfter: async (_input, result, ctx) => captureLockedQuoteGraphAfter(ctx, result, result.quoteId),
|
|
6252
6254
|
buildLog: async ({ result, snapshots }) => {
|
|
6253
|
-
const before = snapshots.before;
|
|
6255
|
+
const before = resolveLockedQuoteGraphBefore(result, snapshots.before);
|
|
6254
6256
|
const after = snapshots.after;
|
|
6255
6257
|
if (!after) return null;
|
|
6256
6258
|
const { translate } = await resolveTranslations();
|
|
@@ -6279,118 +6281,74 @@ const quoteLineUpsertCommand = {
|
|
|
6279
6281
|
};
|
|
6280
6282
|
const quoteLineDeleteCommand = {
|
|
6281
6283
|
id: "sales.quotes.lines.delete",
|
|
6282
|
-
async prepare(input
|
|
6283
|
-
|
|
6284
|
-
const quoteId = typeof raw.quoteId === "string" ? raw.quoteId : null;
|
|
6285
|
-
if (!quoteId) return {};
|
|
6286
|
-
const em = ctx.container.resolve("em");
|
|
6287
|
-
const snapshot = await loadQuoteSnapshot(em, quoteId);
|
|
6288
|
-
if (snapshot)
|
|
6289
|
-
ensureQuoteScope(
|
|
6290
|
-
ctx,
|
|
6291
|
-
snapshot.quote.organizationId,
|
|
6292
|
-
snapshot.quote.tenantId
|
|
6293
|
-
);
|
|
6294
|
-
return snapshot ? { before: snapshot } : {};
|
|
6284
|
+
async prepare(input) {
|
|
6285
|
+
return markLockedGraphWrite(input);
|
|
6295
6286
|
},
|
|
6296
6287
|
async execute(input, ctx) {
|
|
6297
6288
|
const parsed = quoteLineDeleteSchema.parse(
|
|
6298
6289
|
input?.body ?? {}
|
|
6299
6290
|
);
|
|
6300
|
-
|
|
6301
|
-
|
|
6302
|
-
|
|
6303
|
-
|
|
6304
|
-
|
|
6305
|
-
|
|
6306
|
-
|
|
6307
|
-
|
|
6308
|
-
|
|
6309
|
-
|
|
6310
|
-
|
|
6311
|
-
|
|
6312
|
-
|
|
6313
|
-
|
|
6314
|
-
|
|
6315
|
-
|
|
6316
|
-
|
|
6317
|
-
|
|
6318
|
-
|
|
6319
|
-
|
|
6320
|
-
|
|
6321
|
-
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
|
|
6328
|
-
|
|
6329
|
-
|
|
6330
|
-
|
|
6331
|
-
|
|
6332
|
-
|
|
6333
|
-
|
|
6334
|
-
|
|
6335
|
-
|
|
6336
|
-
|
|
6337
|
-
|
|
6338
|
-
|
|
6339
|
-
|
|
6340
|
-
|
|
6341
|
-
|
|
6342
|
-
|
|
6343
|
-
|
|
6344
|
-
|
|
6345
|
-
|
|
6346
|
-
|
|
6347
|
-
|
|
6348
|
-
|
|
6349
|
-
|
|
6350
|
-
|
|
6351
|
-
|
|
6352
|
-
|
|
6353
|
-
|
|
6291
|
+
return writeLockedQuoteGraph(input, ctx, parsed.quoteId, "Sales quote not found", async (em, quote) => {
|
|
6292
|
+
const existingLines = await em.find(
|
|
6293
|
+
SalesQuoteLine,
|
|
6294
|
+
{ quote },
|
|
6295
|
+
{ orderBy: { lineNumber: "asc" } }
|
|
6296
|
+
);
|
|
6297
|
+
const adjustments = await em.find(
|
|
6298
|
+
SalesQuoteAdjustment,
|
|
6299
|
+
{ quote },
|
|
6300
|
+
{ orderBy: { position: "asc" } }
|
|
6301
|
+
);
|
|
6302
|
+
const filtered = existingLines.filter((line) => line.id !== parsed.id);
|
|
6303
|
+
if (filtered.length === existingLines.length) {
|
|
6304
|
+
throw notFound("Quote line not found");
|
|
6305
|
+
}
|
|
6306
|
+
const sourceInputs = filtered.map((line, index) => ({
|
|
6307
|
+
...mapQuoteLineEntityToSnapshot(line),
|
|
6308
|
+
statusEntryId: line.statusEntryId ?? null,
|
|
6309
|
+
catalogSnapshot: line.catalogSnapshot ? cloneJson(line.catalogSnapshot) : null,
|
|
6310
|
+
promotionSnapshot: line.promotionSnapshot ? cloneJson(line.promotionSnapshot) : null,
|
|
6311
|
+
organizationId: quote.organizationId,
|
|
6312
|
+
tenantId: quote.tenantId,
|
|
6313
|
+
quoteId: quote.id,
|
|
6314
|
+
lineNumber: index + 1
|
|
6315
|
+
}));
|
|
6316
|
+
const calcLines = sourceInputs.map(
|
|
6317
|
+
(line, index) => createLineSnapshotFromInput(line, line.lineNumber ?? index + 1)
|
|
6318
|
+
);
|
|
6319
|
+
const adjustmentDrafts = adjustments.map(mapQuoteAdjustmentToDraft);
|
|
6320
|
+
const salesCalculationService = ctx.container.resolve("salesCalculationService");
|
|
6321
|
+
const calculationContext = buildCalculationContext({
|
|
6322
|
+
tenantId: quote.tenantId,
|
|
6323
|
+
organizationId: quote.organizationId,
|
|
6324
|
+
currencyCode: quote.currencyCode,
|
|
6325
|
+
shippingSnapshot: quote.shippingMethodSnapshot,
|
|
6326
|
+
paymentSnapshot: quote.paymentMethodSnapshot,
|
|
6327
|
+
shippingMethodId: quote.shippingMethodId ?? null,
|
|
6328
|
+
paymentMethodId: quote.paymentMethodId ?? null,
|
|
6329
|
+
shippingMethodCode: quote.shippingMethodCode ?? null,
|
|
6330
|
+
paymentMethodCode: quote.paymentMethodCode ?? null
|
|
6331
|
+
});
|
|
6332
|
+
const calculation = await salesCalculationService.calculateDocumentTotals({
|
|
6333
|
+
documentKind: "quote",
|
|
6334
|
+
lines: calcLines,
|
|
6335
|
+
adjustments: adjustmentDrafts,
|
|
6336
|
+
context: calculationContext
|
|
6337
|
+
});
|
|
6338
|
+
await applyQuoteLineResults({
|
|
6339
|
+
em,
|
|
6340
|
+
quote,
|
|
6341
|
+
calculation,
|
|
6342
|
+
sourceLines: sourceInputs,
|
|
6343
|
+
existingLines
|
|
6344
|
+
});
|
|
6345
|
+
applyQuoteTotals(quote, calculation.totals, calculation.lines.length);
|
|
6346
|
+
return { result: { quoteId: quote.id, lineId: parsed.id }, calculation };
|
|
6354
6347
|
});
|
|
6355
|
-
let eventBus = null;
|
|
6356
|
-
try {
|
|
6357
|
-
eventBus = ctx.container.resolve("eventBus");
|
|
6358
|
-
} catch {
|
|
6359
|
-
eventBus = null;
|
|
6360
|
-
}
|
|
6361
|
-
await withAtomicFlush(
|
|
6362
|
-
em,
|
|
6363
|
-
[
|
|
6364
|
-
async () => {
|
|
6365
|
-
await applyQuoteLineResults({
|
|
6366
|
-
em,
|
|
6367
|
-
quote,
|
|
6368
|
-
calculation,
|
|
6369
|
-
sourceLines: sourceInputs,
|
|
6370
|
-
existingLines
|
|
6371
|
-
});
|
|
6372
|
-
applyQuoteTotals(quote, calculation.totals, calculation.lines.length);
|
|
6373
|
-
await emitTotalsCalculated(eventBus, {
|
|
6374
|
-
documentKind: "quote",
|
|
6375
|
-
documentId: quote.id,
|
|
6376
|
-
organizationId: quote.organizationId,
|
|
6377
|
-
tenantId: quote.tenantId,
|
|
6378
|
-
customerId: quote.customerEntityId ?? null,
|
|
6379
|
-
totals: calculation.totals,
|
|
6380
|
-
lineCount: calculation.lines.length
|
|
6381
|
-
});
|
|
6382
|
-
}
|
|
6383
|
-
],
|
|
6384
|
-
{ transaction: true }
|
|
6385
|
-
);
|
|
6386
|
-
return { quoteId: quote.id, lineId: parsed.id };
|
|
6387
|
-
},
|
|
6388
|
-
captureAfter: async (_input, result, ctx) => {
|
|
6389
|
-
const em = ctx.container.resolve("em").fork();
|
|
6390
|
-
return loadQuoteSnapshot(em, result.quoteId);
|
|
6391
6348
|
},
|
|
6349
|
+
captureAfter: async (_input, result, ctx) => captureLockedQuoteGraphAfter(ctx, result, result.quoteId),
|
|
6392
6350
|
buildLog: async ({ result, snapshots }) => {
|
|
6393
|
-
const before = snapshots.before;
|
|
6351
|
+
const before = resolveLockedQuoteGraphBefore(result, snapshots.before);
|
|
6394
6352
|
const after = snapshots.after;
|
|
6395
6353
|
if (!after) return null;
|
|
6396
6354
|
const { translate } = await resolveTranslations();
|
|
@@ -6419,229 +6377,185 @@ const quoteLineDeleteCommand = {
|
|
|
6419
6377
|
};
|
|
6420
6378
|
const orderAdjustmentUpsertCommand = {
|
|
6421
6379
|
id: "sales.orders.adjustments.upsert",
|
|
6422
|
-
async prepare(input
|
|
6423
|
-
|
|
6424
|
-
const orderId = typeof raw.orderId === "string" ? raw.orderId : null;
|
|
6425
|
-
if (!orderId) return {};
|
|
6426
|
-
const em = ctx.container.resolve("em");
|
|
6427
|
-
const snapshot = await loadOrderSnapshot(em, orderId);
|
|
6428
|
-
if (snapshot)
|
|
6429
|
-
ensureOrderScope(
|
|
6430
|
-
ctx,
|
|
6431
|
-
snapshot.order.organizationId,
|
|
6432
|
-
snapshot.order.tenantId
|
|
6433
|
-
);
|
|
6434
|
-
return snapshot ? { before: snapshot } : {};
|
|
6380
|
+
async prepare(input) {
|
|
6381
|
+
return markLockedGraphWrite(input);
|
|
6435
6382
|
},
|
|
6436
6383
|
async execute(input, ctx) {
|
|
6437
6384
|
const parsed = orderAdjustmentUpsertSchema.parse(
|
|
6438
6385
|
input?.body ?? {}
|
|
6439
6386
|
);
|
|
6440
|
-
|
|
6441
|
-
|
|
6442
|
-
|
|
6443
|
-
|
|
6444
|
-
|
|
6445
|
-
|
|
6446
|
-
throw notFound("Sales order not found");
|
|
6447
|
-
ensureOrderScope(ctx, order.organizationId, order.tenantId);
|
|
6448
|
-
await enforceSalesDocumentOptimisticLock(ctx, order, SALES_RESOURCE_KIND_ORDER);
|
|
6449
|
-
if (isExternalAmountsMode(order.totalsMode ?? "computed")) await refuseOnExternalOrder();
|
|
6450
|
-
if (parsed.scope === "line") {
|
|
6451
|
-
throw new CrudHttpError(400, {
|
|
6452
|
-
error: "Line-scoped adjustments are not supported yet."
|
|
6453
|
-
});
|
|
6454
|
-
}
|
|
6455
|
-
const [existingLines, existingAdjustments] = await Promise.all([
|
|
6456
|
-
em.find(SalesOrderLine, { order }, { orderBy: { lineNumber: "asc" } }),
|
|
6457
|
-
em.find(
|
|
6458
|
-
SalesOrderAdjustment,
|
|
6459
|
-
{ order },
|
|
6460
|
-
{ orderBy: { position: "asc" } }
|
|
6461
|
-
)
|
|
6462
|
-
]);
|
|
6463
|
-
const lineSnapshots = existingLines.map(mapOrderLineEntityToSnapshot);
|
|
6464
|
-
const adjustmentDrafts = existingAdjustments.map(mapOrderAdjustmentToDraft);
|
|
6465
|
-
const existingSnapshot = parsed.id ? adjustmentDrafts.find((adj) => adj.id === parsed.id) ?? null : null;
|
|
6466
|
-
const adjustmentId = parsed.id ?? existingSnapshot?.id ?? randomUUID();
|
|
6467
|
-
let metadata = typeof parsed.metadata === "object" && parsed.metadata ? cloneJson(parsed.metadata) : existingSnapshot?.metadata ? cloneJson(existingSnapshot.metadata) : null;
|
|
6468
|
-
const calculatorKey = parsed.calculatorKey ?? existingSnapshot?.calculatorKey ?? null;
|
|
6469
|
-
if (parsed.id && calculatorKey && (calculatorKey.startsWith("shipping-provider:") || calculatorKey.startsWith("payment-provider:"))) {
|
|
6470
|
-
metadata = { ...metadata ?? {}, manualOverride: true };
|
|
6471
|
-
}
|
|
6472
|
-
let nextAdjustments = parsed.id ? adjustmentDrafts.map(
|
|
6473
|
-
(adj) => adj.id === parsed.id ? {
|
|
6474
|
-
...adj,
|
|
6475
|
-
id: adjustmentId,
|
|
6476
|
-
scope: parsed.scope ?? adj.scope ?? "order",
|
|
6477
|
-
kind: parsed.kind ?? adj.kind ?? "custom",
|
|
6478
|
-
code: parsed.code ?? adj.code ?? null,
|
|
6479
|
-
label: parsed.label ?? adj.label ?? null,
|
|
6480
|
-
calculatorKey: parsed.calculatorKey ?? adj.calculatorKey ?? null,
|
|
6481
|
-
promotionId: parsed.promotionId ?? adj.promotionId ?? null,
|
|
6482
|
-
rate: parsed.rate ?? adj.rate ?? null,
|
|
6483
|
-
amountNet: parsed.amountNet ?? adj.amountNet ?? null,
|
|
6484
|
-
amountGross: parsed.amountGross ?? adj.amountGross ?? null,
|
|
6485
|
-
currencyCode: parsed.currencyCode ?? adj.currencyCode ?? order.currencyCode,
|
|
6486
|
-
metadata,
|
|
6487
|
-
customFields: parsed.customFields !== void 0 ? parsed.customFields : adj.customFields ?? null,
|
|
6488
|
-
position: parsed.position ?? adj.position ?? adjustmentDrafts.length
|
|
6489
|
-
} : adj
|
|
6490
|
-
) : [
|
|
6491
|
-
...adjustmentDrafts,
|
|
6492
|
-
{
|
|
6493
|
-
id: adjustmentId,
|
|
6494
|
-
scope: parsed.scope ?? "order",
|
|
6495
|
-
kind: parsed.kind ?? "custom",
|
|
6496
|
-
code: parsed.code ?? null,
|
|
6497
|
-
label: parsed.label ?? null,
|
|
6498
|
-
calculatorKey: parsed.calculatorKey ?? null,
|
|
6499
|
-
promotionId: parsed.promotionId ?? null,
|
|
6500
|
-
rate: parsed.rate ?? null,
|
|
6501
|
-
amountNet: parsed.amountNet ?? null,
|
|
6502
|
-
amountGross: parsed.amountGross ?? null,
|
|
6503
|
-
currencyCode: parsed.currencyCode ?? order.currencyCode,
|
|
6504
|
-
metadata,
|
|
6505
|
-
customFields: parsed.customFields ?? null,
|
|
6506
|
-
position: parsed.position ?? adjustmentDrafts.length
|
|
6387
|
+
return writeLockedOrderGraph(input, ctx, parsed.orderId, "Sales order not found", async (em, order) => {
|
|
6388
|
+
if (isExternalAmountsMode(order.totalsMode ?? "computed")) await refuseOnExternalOrder();
|
|
6389
|
+
if (parsed.scope === "line") {
|
|
6390
|
+
throw new CrudHttpError(400, {
|
|
6391
|
+
error: "Line-scoped adjustments are not supported yet."
|
|
6392
|
+
});
|
|
6507
6393
|
}
|
|
6508
|
-
|
|
6509
|
-
|
|
6510
|
-
|
|
6511
|
-
|
|
6512
|
-
|
|
6513
|
-
|
|
6514
|
-
|
|
6515
|
-
|
|
6516
|
-
|
|
6517
|
-
|
|
6518
|
-
|
|
6519
|
-
|
|
6520
|
-
|
|
6521
|
-
|
|
6522
|
-
|
|
6523
|
-
|
|
6524
|
-
|
|
6525
|
-
|
|
6526
|
-
|
|
6527
|
-
|
|
6528
|
-
|
|
6529
|
-
|
|
6530
|
-
|
|
6531
|
-
|
|
6532
|
-
|
|
6533
|
-
|
|
6534
|
-
|
|
6535
|
-
|
|
6536
|
-
|
|
6537
|
-
|
|
6538
|
-
|
|
6539
|
-
|
|
6540
|
-
|
|
6394
|
+
const [existingLines, existingAdjustments] = await Promise.all([
|
|
6395
|
+
em.find(SalesOrderLine, { order }, { orderBy: { lineNumber: "asc" } }),
|
|
6396
|
+
em.find(
|
|
6397
|
+
SalesOrderAdjustment,
|
|
6398
|
+
{ order },
|
|
6399
|
+
{ orderBy: { position: "asc" } }
|
|
6400
|
+
)
|
|
6401
|
+
]);
|
|
6402
|
+
const lineSnapshots = existingLines.map(mapOrderLineEntityToSnapshot);
|
|
6403
|
+
const adjustmentDrafts = existingAdjustments.map(mapOrderAdjustmentToDraft);
|
|
6404
|
+
const existingSnapshot = parsed.id ? adjustmentDrafts.find((adj) => adj.id === parsed.id) ?? null : null;
|
|
6405
|
+
const adjustmentId = parsed.id ?? existingSnapshot?.id ?? randomUUID();
|
|
6406
|
+
let metadata = typeof parsed.metadata === "object" && parsed.metadata ? cloneJson(parsed.metadata) : existingSnapshot?.metadata ? cloneJson(existingSnapshot.metadata) : null;
|
|
6407
|
+
const calculatorKey = parsed.calculatorKey ?? existingSnapshot?.calculatorKey ?? null;
|
|
6408
|
+
if (parsed.id && calculatorKey && (calculatorKey.startsWith("shipping-provider:") || calculatorKey.startsWith("payment-provider:"))) {
|
|
6409
|
+
metadata = { ...metadata ?? {}, manualOverride: true };
|
|
6410
|
+
}
|
|
6411
|
+
let nextAdjustments = parsed.id ? adjustmentDrafts.map(
|
|
6412
|
+
(adj) => adj.id === parsed.id ? {
|
|
6413
|
+
...adj,
|
|
6414
|
+
id: adjustmentId,
|
|
6415
|
+
scope: parsed.scope ?? adj.scope ?? "order",
|
|
6416
|
+
kind: parsed.kind ?? adj.kind ?? "custom",
|
|
6417
|
+
code: parsed.code ?? adj.code ?? null,
|
|
6418
|
+
label: parsed.label ?? adj.label ?? null,
|
|
6419
|
+
calculatorKey: parsed.calculatorKey ?? adj.calculatorKey ?? null,
|
|
6420
|
+
promotionId: parsed.promotionId ?? adj.promotionId ?? null,
|
|
6421
|
+
rate: parsed.rate ?? adj.rate ?? null,
|
|
6422
|
+
amountNet: parsed.amountNet ?? adj.amountNet ?? null,
|
|
6423
|
+
amountGross: parsed.amountGross ?? adj.amountGross ?? null,
|
|
6424
|
+
currencyCode: parsed.currencyCode ?? adj.currencyCode ?? order.currencyCode,
|
|
6425
|
+
metadata,
|
|
6426
|
+
customFields: parsed.customFields !== void 0 ? parsed.customFields : adj.customFields ?? null,
|
|
6427
|
+
position: parsed.position ?? adj.position ?? adjustmentDrafts.length
|
|
6428
|
+
} : adj
|
|
6429
|
+
) : [
|
|
6430
|
+
...adjustmentDrafts,
|
|
6431
|
+
{
|
|
6432
|
+
id: adjustmentId,
|
|
6433
|
+
scope: parsed.scope ?? "order",
|
|
6434
|
+
kind: parsed.kind ?? "custom",
|
|
6435
|
+
code: parsed.code ?? null,
|
|
6436
|
+
label: parsed.label ?? null,
|
|
6437
|
+
calculatorKey: parsed.calculatorKey ?? null,
|
|
6438
|
+
promotionId: parsed.promotionId ?? null,
|
|
6439
|
+
rate: parsed.rate ?? null,
|
|
6440
|
+
amountNet: parsed.amountNet ?? null,
|
|
6441
|
+
amountGross: parsed.amountGross ?? null,
|
|
6442
|
+
currencyCode: parsed.currencyCode ?? order.currencyCode,
|
|
6443
|
+
metadata,
|
|
6444
|
+
customFields: parsed.customFields ?? null,
|
|
6445
|
+
position: parsed.position ?? adjustmentDrafts.length
|
|
6446
|
+
}
|
|
6447
|
+
];
|
|
6448
|
+
nextAdjustments = nextAdjustments.sort((a, b) => (a.position ?? 0) - (b.position ?? 0)).map((adj, index) => ({ ...adj, position: adj.position ?? index }));
|
|
6449
|
+
const sourceLines = lineSnapshots.map((line, index) => ({
|
|
6450
|
+
...line,
|
|
6451
|
+
statusEntryId: line.statusEntryId ?? null,
|
|
6452
|
+
catalogSnapshot: line.catalogSnapshot ?? null,
|
|
6453
|
+
promotionSnapshot: line.promotionSnapshot ?? null,
|
|
6454
|
+
organizationId: order.organizationId,
|
|
6455
|
+
tenantId: order.tenantId,
|
|
6456
|
+
orderId: order.id,
|
|
6457
|
+
lineNumber: line.lineNumber ?? index + 1
|
|
6458
|
+
}));
|
|
6459
|
+
const calcLines = sourceLines.map(
|
|
6460
|
+
(line, index) => createLineSnapshotFromInput(line, line.lineNumber ?? index + 1)
|
|
6461
|
+
);
|
|
6462
|
+
const salesCalculationService = ctx.container.resolve("salesCalculationService");
|
|
6463
|
+
const calculationContext = buildCalculationContext({
|
|
6464
|
+
tenantId: order.tenantId,
|
|
6465
|
+
organizationId: order.organizationId,
|
|
6466
|
+
currencyCode: order.currencyCode,
|
|
6467
|
+
shippingSnapshot: order.shippingMethodSnapshot,
|
|
6468
|
+
paymentSnapshot: order.paymentMethodSnapshot,
|
|
6469
|
+
shippingMethodId: order.shippingMethodId ?? null,
|
|
6470
|
+
paymentMethodId: order.paymentMethodId ?? null,
|
|
6471
|
+
shippingMethodCode: order.shippingMethodCode ?? null,
|
|
6472
|
+
paymentMethodCode: order.paymentMethodCode ?? null
|
|
6473
|
+
});
|
|
6474
|
+
const effectiveAdjustment = nextAdjustments.find(
|
|
6475
|
+
(adj) => adj.id === adjustmentId
|
|
6541
6476
|
);
|
|
6542
|
-
|
|
6477
|
+
if (effectiveAdjustment?.kind === "return") {
|
|
6478
|
+
const baselineAdjustments = nextAdjustments.filter(
|
|
6479
|
+
(adj) => adj.id !== adjustmentId
|
|
6480
|
+
);
|
|
6481
|
+
const baselineCalculation = await salesCalculationService.calculateDocumentTotals({
|
|
6482
|
+
documentKind: "order",
|
|
6483
|
+
lines: calcLines,
|
|
6484
|
+
adjustments: baselineAdjustments,
|
|
6485
|
+
context: calculationContext,
|
|
6486
|
+
existingTotals: resolveExistingPaymentTotals(order)
|
|
6487
|
+
});
|
|
6488
|
+
const issues = validateReturnAdjustmentWithinRemaining({
|
|
6489
|
+
kind: "return",
|
|
6490
|
+
amountNet: effectiveAdjustment.amountNet ?? effectiveAdjustment.amountGross ?? 0,
|
|
6491
|
+
amountGross: effectiveAdjustment.amountGross ?? effectiveAdjustment.amountNet ?? 0,
|
|
6492
|
+
remainingNet: Number(
|
|
6493
|
+
baselineCalculation.totals?.grandTotalNetAmount ?? 0
|
|
6494
|
+
),
|
|
6495
|
+
remainingGross: Number(
|
|
6496
|
+
baselineCalculation.totals?.grandTotalGrossAmount ?? 0
|
|
6497
|
+
)
|
|
6498
|
+
});
|
|
6499
|
+
if (issues.length > 0) {
|
|
6500
|
+
const { translate } = await resolveTranslations();
|
|
6501
|
+
const grossIssue = issues.find((issue) => issue.path === "amountGross");
|
|
6502
|
+
const netIssue = issues.find((issue) => issue.path === "amountNet");
|
|
6503
|
+
const fieldErrors = {};
|
|
6504
|
+
if (grossIssue) {
|
|
6505
|
+
fieldErrors.amountGross = translate(
|
|
6506
|
+
"sales.adjustments.error.returnExceedsRemainingGrandTotalGross",
|
|
6507
|
+
RETURN_ADJUSTMENT_EXCEEDS_REMAINING_GROSS_MESSAGE
|
|
6508
|
+
);
|
|
6509
|
+
}
|
|
6510
|
+
if (netIssue) {
|
|
6511
|
+
fieldErrors.amountNet = translate(
|
|
6512
|
+
"sales.adjustments.error.returnExceedsRemainingGrandTotalNet",
|
|
6513
|
+
RETURN_ADJUSTMENT_EXCEEDS_REMAINING_NET_MESSAGE
|
|
6514
|
+
);
|
|
6515
|
+
}
|
|
6516
|
+
throw new CrudHttpError(400, {
|
|
6517
|
+
error: fieldErrors.amountGross ?? fieldErrors.amountNet ?? translate(
|
|
6518
|
+
"sales.adjustments.error.returnExceedsRemainingGrandTotalGross",
|
|
6519
|
+
RETURN_ADJUSTMENT_EXCEEDS_REMAINING_GROSS_MESSAGE
|
|
6520
|
+
),
|
|
6521
|
+
fieldErrors
|
|
6522
|
+
});
|
|
6523
|
+
}
|
|
6524
|
+
}
|
|
6525
|
+
const calculation = await salesCalculationService.calculateDocumentTotals({
|
|
6543
6526
|
documentKind: "order",
|
|
6544
6527
|
lines: calcLines,
|
|
6545
|
-
adjustments:
|
|
6528
|
+
adjustments: nextAdjustments,
|
|
6546
6529
|
context: calculationContext,
|
|
6547
6530
|
existingTotals: resolveExistingPaymentTotals(order)
|
|
6548
6531
|
});
|
|
6549
|
-
const
|
|
6550
|
-
|
|
6551
|
-
|
|
6552
|
-
|
|
6553
|
-
|
|
6554
|
-
|
|
6555
|
-
|
|
6556
|
-
|
|
6557
|
-
|
|
6558
|
-
|
|
6559
|
-
|
|
6560
|
-
|
|
6561
|
-
|
|
6562
|
-
|
|
6563
|
-
|
|
6564
|
-
|
|
6565
|
-
|
|
6566
|
-
|
|
6567
|
-
|
|
6568
|
-
|
|
6569
|
-
|
|
6570
|
-
|
|
6571
|
-
if (netIssue) {
|
|
6572
|
-
fieldErrors.amountNet = translate(
|
|
6573
|
-
"sales.adjustments.error.returnExceedsRemainingGrandTotalNet",
|
|
6574
|
-
RETURN_ADJUSTMENT_EXCEEDS_REMAINING_NET_MESSAGE
|
|
6575
|
-
);
|
|
6576
|
-
}
|
|
6577
|
-
throw new CrudHttpError(400, {
|
|
6578
|
-
error: fieldErrors.amountGross ?? fieldErrors.amountNet ?? translate(
|
|
6579
|
-
"sales.adjustments.error.returnExceedsRemainingGrandTotalGross",
|
|
6580
|
-
RETURN_ADJUSTMENT_EXCEEDS_REMAINING_GROSS_MESSAGE
|
|
6581
|
-
),
|
|
6582
|
-
fieldErrors
|
|
6583
|
-
});
|
|
6584
|
-
}
|
|
6585
|
-
}
|
|
6586
|
-
const calculation = await salesCalculationService.calculateDocumentTotals({
|
|
6587
|
-
documentKind: "order",
|
|
6588
|
-
lines: calcLines,
|
|
6589
|
-
adjustments: nextAdjustments,
|
|
6590
|
-
context: calculationContext,
|
|
6591
|
-
existingTotals: resolveExistingPaymentTotals(order)
|
|
6532
|
+
const adjustmentInputs = nextAdjustments.map((adj, index) => ({
|
|
6533
|
+
organizationId: order.organizationId,
|
|
6534
|
+
tenantId: order.tenantId,
|
|
6535
|
+
orderId: order.id,
|
|
6536
|
+
scope: adj.scope ?? "order",
|
|
6537
|
+
kind: adj.kind ?? "custom",
|
|
6538
|
+
code: adj.code ?? void 0,
|
|
6539
|
+
label: adj.label ?? void 0,
|
|
6540
|
+
calculatorKey: adj.calculatorKey ?? void 0,
|
|
6541
|
+
promotionId: adj.promotionId ?? void 0,
|
|
6542
|
+
rate: adj.rate ?? void 0,
|
|
6543
|
+
amountNet: adj.amountNet ?? void 0,
|
|
6544
|
+
amountGross: adj.amountGross ?? void 0,
|
|
6545
|
+
currencyCode: adj.currencyCode ?? order.currencyCode,
|
|
6546
|
+
metadata: adj.metadata ?? void 0,
|
|
6547
|
+
customFields: adj.customFields ?? void 0,
|
|
6548
|
+
position: adj.position ?? index
|
|
6549
|
+
}));
|
|
6550
|
+
await replaceOrderAdjustments(em, order, calculation, adjustmentInputs);
|
|
6551
|
+
applyOrderTotals(order, calculation.totals, calculation.lines.length);
|
|
6552
|
+
order.updatedAt = /* @__PURE__ */ new Date();
|
|
6553
|
+
return { result: { orderId: order.id, adjustmentId }, calculation };
|
|
6592
6554
|
});
|
|
6593
|
-
const adjustmentInputs = nextAdjustments.map((adj, index) => ({
|
|
6594
|
-
organizationId: order.organizationId,
|
|
6595
|
-
tenantId: order.tenantId,
|
|
6596
|
-
orderId: order.id,
|
|
6597
|
-
scope: adj.scope ?? "order",
|
|
6598
|
-
kind: adj.kind ?? "custom",
|
|
6599
|
-
code: adj.code ?? void 0,
|
|
6600
|
-
label: adj.label ?? void 0,
|
|
6601
|
-
calculatorKey: adj.calculatorKey ?? void 0,
|
|
6602
|
-
promotionId: adj.promotionId ?? void 0,
|
|
6603
|
-
rate: adj.rate ?? void 0,
|
|
6604
|
-
amountNet: adj.amountNet ?? void 0,
|
|
6605
|
-
amountGross: adj.amountGross ?? void 0,
|
|
6606
|
-
currencyCode: adj.currencyCode ?? order.currencyCode,
|
|
6607
|
-
metadata: adj.metadata ?? void 0,
|
|
6608
|
-
customFields: adj.customFields ?? void 0,
|
|
6609
|
-
position: adj.position ?? index
|
|
6610
|
-
}));
|
|
6611
|
-
let eventBus = null;
|
|
6612
|
-
try {
|
|
6613
|
-
eventBus = ctx.container.resolve("eventBus");
|
|
6614
|
-
} catch {
|
|
6615
|
-
eventBus = null;
|
|
6616
|
-
}
|
|
6617
|
-
await withAtomicFlush(
|
|
6618
|
-
em,
|
|
6619
|
-
[
|
|
6620
|
-
async () => {
|
|
6621
|
-
await replaceOrderAdjustments(em, order, calculation, adjustmentInputs);
|
|
6622
|
-
applyOrderTotals(order, calculation.totals, calculation.lines.length);
|
|
6623
|
-
order.updatedAt = /* @__PURE__ */ new Date();
|
|
6624
|
-
await emitTotalsCalculated(eventBus, {
|
|
6625
|
-
documentKind: "order",
|
|
6626
|
-
documentId: order.id,
|
|
6627
|
-
organizationId: order.organizationId,
|
|
6628
|
-
tenantId: order.tenantId,
|
|
6629
|
-
customerId: order.customerEntityId ?? null,
|
|
6630
|
-
totals: calculation.totals,
|
|
6631
|
-
lineCount: calculation.lines.length
|
|
6632
|
-
});
|
|
6633
|
-
}
|
|
6634
|
-
],
|
|
6635
|
-
{ transaction: true }
|
|
6636
|
-
);
|
|
6637
|
-
return { orderId: order.id, adjustmentId };
|
|
6638
|
-
},
|
|
6639
|
-
captureAfter: async (_input, result, ctx) => {
|
|
6640
|
-
const em = ctx.container.resolve("em").fork();
|
|
6641
|
-
return loadOrderSnapshot(em, result.orderId);
|
|
6642
6555
|
},
|
|
6556
|
+
captureAfter: async (_input, result, ctx) => captureLockedOrderGraphAfter(ctx, result, result.orderId),
|
|
6643
6557
|
buildLog: async ({ snapshots, result }) => {
|
|
6644
|
-
const before = snapshots.before;
|
|
6558
|
+
const before = resolveLockedOrderGraphBefore(result, snapshots.before);
|
|
6645
6559
|
const after = snapshots.after;
|
|
6646
6560
|
if (!after) return null;
|
|
6647
6561
|
const { translate } = await resolveTranslations();
|
|
@@ -6670,133 +6584,89 @@ const orderAdjustmentUpsertCommand = {
|
|
|
6670
6584
|
};
|
|
6671
6585
|
const orderAdjustmentDeleteCommand = {
|
|
6672
6586
|
id: "sales.orders.adjustments.delete",
|
|
6673
|
-
async prepare(input
|
|
6674
|
-
|
|
6675
|
-
const orderId = typeof raw.orderId === "string" ? raw.orderId : null;
|
|
6676
|
-
if (!orderId) return {};
|
|
6677
|
-
const em = ctx.container.resolve("em");
|
|
6678
|
-
const snapshot = await loadOrderSnapshot(em, orderId);
|
|
6679
|
-
if (snapshot)
|
|
6680
|
-
ensureOrderScope(
|
|
6681
|
-
ctx,
|
|
6682
|
-
snapshot.order.organizationId,
|
|
6683
|
-
snapshot.order.tenantId
|
|
6684
|
-
);
|
|
6685
|
-
return snapshot ? { before: snapshot } : {};
|
|
6587
|
+
async prepare(input) {
|
|
6588
|
+
return markLockedGraphWrite(input);
|
|
6686
6589
|
},
|
|
6687
6590
|
async execute(input, ctx) {
|
|
6688
|
-
const parsed = orderAdjustmentDeleteSchema.parse(
|
|
6689
|
-
input?.body ?? {}
|
|
6690
|
-
);
|
|
6691
|
-
|
|
6692
|
-
|
|
6693
|
-
|
|
6694
|
-
|
|
6695
|
-
|
|
6696
|
-
|
|
6697
|
-
|
|
6698
|
-
|
|
6699
|
-
|
|
6700
|
-
|
|
6701
|
-
|
|
6702
|
-
|
|
6703
|
-
|
|
6704
|
-
|
|
6705
|
-
|
|
6706
|
-
|
|
6707
|
-
|
|
6708
|
-
|
|
6709
|
-
|
|
6710
|
-
|
|
6711
|
-
|
|
6712
|
-
|
|
6713
|
-
|
|
6714
|
-
|
|
6715
|
-
|
|
6716
|
-
|
|
6717
|
-
|
|
6718
|
-
|
|
6719
|
-
|
|
6720
|
-
|
|
6721
|
-
|
|
6722
|
-
|
|
6723
|
-
|
|
6724
|
-
|
|
6725
|
-
|
|
6726
|
-
|
|
6727
|
-
|
|
6728
|
-
|
|
6729
|
-
|
|
6730
|
-
|
|
6731
|
-
|
|
6732
|
-
|
|
6733
|
-
|
|
6734
|
-
|
|
6735
|
-
|
|
6736
|
-
|
|
6737
|
-
|
|
6738
|
-
|
|
6739
|
-
|
|
6740
|
-
|
|
6741
|
-
|
|
6742
|
-
|
|
6743
|
-
|
|
6744
|
-
|
|
6745
|
-
|
|
6746
|
-
|
|
6747
|
-
|
|
6748
|
-
|
|
6749
|
-
|
|
6750
|
-
|
|
6751
|
-
|
|
6752
|
-
|
|
6753
|
-
|
|
6754
|
-
|
|
6755
|
-
|
|
6756
|
-
|
|
6757
|
-
|
|
6758
|
-
|
|
6759
|
-
|
|
6760
|
-
|
|
6761
|
-
|
|
6762
|
-
|
|
6763
|
-
metadata: adj.metadata ?? void 0,
|
|
6764
|
-
position: adj.position ?? index
|
|
6765
|
-
}));
|
|
6766
|
-
let eventBus = null;
|
|
6767
|
-
try {
|
|
6768
|
-
eventBus = ctx.container.resolve("eventBus");
|
|
6769
|
-
} catch {
|
|
6770
|
-
eventBus = null;
|
|
6771
|
-
}
|
|
6772
|
-
await withAtomicFlush(
|
|
6773
|
-
em,
|
|
6774
|
-
[
|
|
6775
|
-
async () => {
|
|
6776
|
-
await replaceOrderAdjustments(em, order, calculation, adjustmentInputs);
|
|
6777
|
-
applyOrderTotals(order, calculation.totals, calculation.lines.length);
|
|
6778
|
-
order.updatedAt = /* @__PURE__ */ new Date();
|
|
6779
|
-
await emitTotalsCalculated(eventBus, {
|
|
6780
|
-
documentKind: "order",
|
|
6781
|
-
documentId: order.id,
|
|
6782
|
-
organizationId: order.organizationId,
|
|
6783
|
-
tenantId: order.tenantId,
|
|
6784
|
-
customerId: order.customerEntityId ?? null,
|
|
6785
|
-
totals: calculation.totals,
|
|
6786
|
-
lineCount: calculation.lines.length
|
|
6787
|
-
});
|
|
6788
|
-
}
|
|
6789
|
-
],
|
|
6790
|
-
{ transaction: true }
|
|
6791
|
-
);
|
|
6792
|
-
return { orderId: order.id, adjustmentId: parsed.id };
|
|
6793
|
-
},
|
|
6794
|
-
captureAfter: async (_input, result, ctx) => {
|
|
6795
|
-
const em = ctx.container.resolve("em").fork();
|
|
6796
|
-
return loadOrderSnapshot(em, result.orderId);
|
|
6591
|
+
const parsed = orderAdjustmentDeleteSchema.parse(
|
|
6592
|
+
input?.body ?? {}
|
|
6593
|
+
);
|
|
6594
|
+
return writeLockedOrderGraph(input, ctx, parsed.orderId, "Sales order not found", async (em, order) => {
|
|
6595
|
+
if (isExternalAmountsMode(order.totalsMode ?? "computed")) await refuseOnExternalOrder();
|
|
6596
|
+
const [existingLines, adjustments] = await Promise.all([
|
|
6597
|
+
em.find(SalesOrderLine, { order }, { orderBy: { lineNumber: "asc" } }),
|
|
6598
|
+
em.find(
|
|
6599
|
+
SalesOrderAdjustment,
|
|
6600
|
+
{ order },
|
|
6601
|
+
{ orderBy: { position: "asc" } }
|
|
6602
|
+
)
|
|
6603
|
+
]);
|
|
6604
|
+
const filtered = adjustments.filter((adj) => adj.id !== parsed.id);
|
|
6605
|
+
if (filtered.length === adjustments.length) {
|
|
6606
|
+
throw notFound("Adjustment not found");
|
|
6607
|
+
}
|
|
6608
|
+
const lineSnapshots = existingLines.map(mapOrderLineEntityToSnapshot);
|
|
6609
|
+
const calcLines = lineSnapshots.map(
|
|
6610
|
+
(line, index) => createLineSnapshotFromInput(
|
|
6611
|
+
{
|
|
6612
|
+
...line,
|
|
6613
|
+
organizationId: order.organizationId,
|
|
6614
|
+
tenantId: order.tenantId,
|
|
6615
|
+
orderId: order.id,
|
|
6616
|
+
lineNumber: line.lineNumber ?? index + 1,
|
|
6617
|
+
statusEntryId: line.statusEntryId ?? null,
|
|
6618
|
+
catalogSnapshot: line.catalogSnapshot ?? null,
|
|
6619
|
+
promotionSnapshot: line.promotionSnapshot ?? null
|
|
6620
|
+
},
|
|
6621
|
+
line.lineNumber ?? index + 1
|
|
6622
|
+
)
|
|
6623
|
+
);
|
|
6624
|
+
const adjustmentDrafts = filtered.map(mapOrderAdjustmentToDraft);
|
|
6625
|
+
const salesCalculationService = ctx.container.resolve("salesCalculationService");
|
|
6626
|
+
const calculationContext = buildCalculationContext({
|
|
6627
|
+
tenantId: order.tenantId,
|
|
6628
|
+
organizationId: order.organizationId,
|
|
6629
|
+
currencyCode: order.currencyCode,
|
|
6630
|
+
shippingSnapshot: order.shippingMethodSnapshot,
|
|
6631
|
+
paymentSnapshot: order.paymentMethodSnapshot,
|
|
6632
|
+
shippingMethodId: order.shippingMethodId ?? null,
|
|
6633
|
+
paymentMethodId: order.paymentMethodId ?? null,
|
|
6634
|
+
shippingMethodCode: order.shippingMethodCode ?? null,
|
|
6635
|
+
paymentMethodCode: order.paymentMethodCode ?? null
|
|
6636
|
+
});
|
|
6637
|
+
const calculation = await salesCalculationService.calculateDocumentTotals({
|
|
6638
|
+
documentKind: "order",
|
|
6639
|
+
lines: calcLines,
|
|
6640
|
+
adjustments: adjustmentDrafts,
|
|
6641
|
+
context: calculationContext,
|
|
6642
|
+
existingTotals: resolveExistingPaymentTotals(order)
|
|
6643
|
+
});
|
|
6644
|
+
const adjustmentInputs = adjustmentDrafts.map((adj, index) => ({
|
|
6645
|
+
organizationId: order.organizationId,
|
|
6646
|
+
tenantId: order.tenantId,
|
|
6647
|
+
orderId: order.id,
|
|
6648
|
+
scope: adj.scope ?? "order",
|
|
6649
|
+
kind: adj.kind ?? "custom",
|
|
6650
|
+
code: adj.code ?? void 0,
|
|
6651
|
+
label: adj.label ?? void 0,
|
|
6652
|
+
calculatorKey: adj.calculatorKey ?? void 0,
|
|
6653
|
+
promotionId: adj.promotionId ?? void 0,
|
|
6654
|
+
rate: adj.rate ?? void 0,
|
|
6655
|
+
amountNet: adj.amountNet ?? void 0,
|
|
6656
|
+
amountGross: adj.amountGross ?? void 0,
|
|
6657
|
+
currencyCode: adj.currencyCode ?? order.currencyCode,
|
|
6658
|
+
metadata: adj.metadata ?? void 0,
|
|
6659
|
+
position: adj.position ?? index
|
|
6660
|
+
}));
|
|
6661
|
+
await replaceOrderAdjustments(em, order, calculation, adjustmentInputs);
|
|
6662
|
+
applyOrderTotals(order, calculation.totals, calculation.lines.length);
|
|
6663
|
+
order.updatedAt = /* @__PURE__ */ new Date();
|
|
6664
|
+
return { result: { orderId: order.id, adjustmentId: parsed.id }, calculation };
|
|
6665
|
+
});
|
|
6797
6666
|
},
|
|
6667
|
+
captureAfter: async (_input, result, ctx) => captureLockedOrderGraphAfter(ctx, result, result.orderId),
|
|
6798
6668
|
buildLog: async ({ snapshots, result }) => {
|
|
6799
|
-
const before = snapshots.before;
|
|
6669
|
+
const before = resolveLockedOrderGraphBefore(result, snapshots.before);
|
|
6800
6670
|
const after = snapshots.after;
|
|
6801
6671
|
if (!after) return null;
|
|
6802
6672
|
const { translate } = await resolveTranslations();
|
|
@@ -6825,226 +6695,182 @@ const orderAdjustmentDeleteCommand = {
|
|
|
6825
6695
|
};
|
|
6826
6696
|
const quoteAdjustmentUpsertCommand = {
|
|
6827
6697
|
id: "sales.quotes.adjustments.upsert",
|
|
6828
|
-
async prepare(input
|
|
6829
|
-
|
|
6830
|
-
const quoteId = typeof raw.quoteId === "string" ? raw.quoteId : null;
|
|
6831
|
-
if (!quoteId) return {};
|
|
6832
|
-
const em = ctx.container.resolve("em");
|
|
6833
|
-
const snapshot = await loadQuoteSnapshot(em, quoteId);
|
|
6834
|
-
if (snapshot)
|
|
6835
|
-
ensureQuoteScope(
|
|
6836
|
-
ctx,
|
|
6837
|
-
snapshot.quote.organizationId,
|
|
6838
|
-
snapshot.quote.tenantId
|
|
6839
|
-
);
|
|
6840
|
-
return snapshot ? { before: snapshot } : {};
|
|
6698
|
+
async prepare(input) {
|
|
6699
|
+
return markLockedGraphWrite(input);
|
|
6841
6700
|
},
|
|
6842
6701
|
async execute(input, ctx) {
|
|
6843
6702
|
const parsed = quoteAdjustmentUpsertSchema.parse(
|
|
6844
6703
|
input?.body ?? {}
|
|
6845
6704
|
);
|
|
6846
|
-
|
|
6847
|
-
|
|
6848
|
-
|
|
6849
|
-
|
|
6850
|
-
|
|
6851
|
-
if (!quote)
|
|
6852
|
-
throw notFound("Sales quote not found");
|
|
6853
|
-
ensureQuoteScope(ctx, quote.organizationId, quote.tenantId);
|
|
6854
|
-
await enforceSalesDocumentOptimisticLock(ctx, quote, SALES_RESOURCE_KIND_QUOTE);
|
|
6855
|
-
if (parsed.scope === "line") {
|
|
6856
|
-
throw new CrudHttpError(400, {
|
|
6857
|
-
error: "Line-scoped adjustments are not supported yet."
|
|
6858
|
-
});
|
|
6859
|
-
}
|
|
6860
|
-
const [existingLines, existingAdjustments] = await Promise.all([
|
|
6861
|
-
em.find(SalesQuoteLine, { quote }, { orderBy: { lineNumber: "asc" } }),
|
|
6862
|
-
em.find(
|
|
6863
|
-
SalesQuoteAdjustment,
|
|
6864
|
-
{ quote },
|
|
6865
|
-
{ orderBy: { position: "asc" } }
|
|
6866
|
-
)
|
|
6867
|
-
]);
|
|
6868
|
-
const lineSnapshots = existingLines.map(mapQuoteLineEntityToSnapshot);
|
|
6869
|
-
const adjustmentDrafts = existingAdjustments.map(mapQuoteAdjustmentToDraft);
|
|
6870
|
-
const existingSnapshot = parsed.id ? adjustmentDrafts.find((adj) => adj.id === parsed.id) ?? null : null;
|
|
6871
|
-
const adjustmentId = parsed.id ?? existingSnapshot?.id ?? randomUUID();
|
|
6872
|
-
let metadata = typeof parsed.metadata === "object" && parsed.metadata ? cloneJson(parsed.metadata) : existingSnapshot?.metadata ? cloneJson(existingSnapshot.metadata) : null;
|
|
6873
|
-
const calculatorKey = parsed.calculatorKey ?? existingSnapshot?.calculatorKey ?? null;
|
|
6874
|
-
if (parsed.id && calculatorKey && (calculatorKey.startsWith("shipping-provider:") || calculatorKey.startsWith("payment-provider:"))) {
|
|
6875
|
-
metadata = { ...metadata ?? {}, manualOverride: true };
|
|
6876
|
-
}
|
|
6877
|
-
let nextAdjustments = parsed.id ? adjustmentDrafts.map(
|
|
6878
|
-
(adj) => adj.id === parsed.id ? {
|
|
6879
|
-
...adj,
|
|
6880
|
-
id: adjustmentId,
|
|
6881
|
-
scope: parsed.scope ?? adj.scope ?? "order",
|
|
6882
|
-
kind: parsed.kind ?? adj.kind ?? "custom",
|
|
6883
|
-
code: parsed.code ?? adj.code ?? null,
|
|
6884
|
-
label: parsed.label ?? adj.label ?? null,
|
|
6885
|
-
calculatorKey: parsed.calculatorKey ?? adj.calculatorKey ?? null,
|
|
6886
|
-
promotionId: parsed.promotionId ?? adj.promotionId ?? null,
|
|
6887
|
-
rate: parsed.rate ?? adj.rate ?? null,
|
|
6888
|
-
amountNet: parsed.amountNet ?? adj.amountNet ?? null,
|
|
6889
|
-
amountGross: parsed.amountGross ?? adj.amountGross ?? null,
|
|
6890
|
-
currencyCode: parsed.currencyCode ?? adj.currencyCode ?? quote.currencyCode,
|
|
6891
|
-
metadata,
|
|
6892
|
-
customFields: parsed.customFields !== void 0 ? parsed.customFields : adj.customFields ?? null,
|
|
6893
|
-
position: parsed.position ?? adj.position ?? adjustmentDrafts.length
|
|
6894
|
-
} : adj
|
|
6895
|
-
) : [
|
|
6896
|
-
...adjustmentDrafts,
|
|
6897
|
-
{
|
|
6898
|
-
id: adjustmentId,
|
|
6899
|
-
scope: parsed.scope ?? "order",
|
|
6900
|
-
kind: parsed.kind ?? "custom",
|
|
6901
|
-
code: parsed.code ?? null,
|
|
6902
|
-
label: parsed.label ?? null,
|
|
6903
|
-
calculatorKey: parsed.calculatorKey ?? null,
|
|
6904
|
-
promotionId: parsed.promotionId ?? null,
|
|
6905
|
-
rate: parsed.rate ?? null,
|
|
6906
|
-
amountNet: parsed.amountNet ?? null,
|
|
6907
|
-
amountGross: parsed.amountGross ?? null,
|
|
6908
|
-
currencyCode: parsed.currencyCode ?? quote.currencyCode,
|
|
6909
|
-
metadata,
|
|
6910
|
-
customFields: parsed.customFields ?? null,
|
|
6911
|
-
position: parsed.position ?? adjustmentDrafts.length
|
|
6705
|
+
return writeLockedQuoteGraph(input, ctx, parsed.quoteId, "Sales quote not found", async (em, quote) => {
|
|
6706
|
+
if (parsed.scope === "line") {
|
|
6707
|
+
throw new CrudHttpError(400, {
|
|
6708
|
+
error: "Line-scoped adjustments are not supported yet."
|
|
6709
|
+
});
|
|
6912
6710
|
}
|
|
6913
|
-
|
|
6914
|
-
|
|
6915
|
-
|
|
6916
|
-
|
|
6917
|
-
|
|
6918
|
-
|
|
6919
|
-
promotionSnapshot: line.promotionSnapshot ?? null,
|
|
6920
|
-
organizationId: quote.organizationId,
|
|
6921
|
-
tenantId: quote.tenantId,
|
|
6922
|
-
quoteId: quote.id,
|
|
6923
|
-
lineNumber: line.lineNumber ?? index + 1
|
|
6924
|
-
}));
|
|
6925
|
-
const calcLines = sourceLines.map(
|
|
6926
|
-
(line, index) => createLineSnapshotFromInput(line, line.lineNumber ?? index + 1)
|
|
6927
|
-
);
|
|
6928
|
-
const salesCalculationService = ctx.container.resolve("salesCalculationService");
|
|
6929
|
-
const calculationContext = buildCalculationContext({
|
|
6930
|
-
tenantId: quote.tenantId,
|
|
6931
|
-
organizationId: quote.organizationId,
|
|
6932
|
-
currencyCode: quote.currencyCode,
|
|
6933
|
-
shippingSnapshot: quote.shippingMethodSnapshot,
|
|
6934
|
-
paymentSnapshot: quote.paymentMethodSnapshot,
|
|
6935
|
-
shippingMethodId: quote.shippingMethodId ?? null,
|
|
6936
|
-
paymentMethodId: quote.paymentMethodId ?? null,
|
|
6937
|
-
shippingMethodCode: quote.shippingMethodCode ?? null,
|
|
6938
|
-
paymentMethodCode: quote.paymentMethodCode ?? null
|
|
6939
|
-
});
|
|
6940
|
-
const effectiveAdjustment = nextAdjustments.find(
|
|
6941
|
-
(adj) => adj.id === adjustmentId
|
|
6942
|
-
);
|
|
6943
|
-
if (effectiveAdjustment?.kind === "return") {
|
|
6944
|
-
const baselineAdjustments = nextAdjustments.filter(
|
|
6945
|
-
(adj) => adj.id !== adjustmentId
|
|
6946
|
-
);
|
|
6947
|
-
const baselineCalculation = await salesCalculationService.calculateDocumentTotals({
|
|
6948
|
-
documentKind: "quote",
|
|
6949
|
-
lines: calcLines,
|
|
6950
|
-
adjustments: baselineAdjustments,
|
|
6951
|
-
context: calculationContext
|
|
6952
|
-
});
|
|
6953
|
-
const issues = validateReturnAdjustmentWithinRemaining({
|
|
6954
|
-
kind: "return",
|
|
6955
|
-
amountNet: effectiveAdjustment.amountNet ?? effectiveAdjustment.amountGross ?? 0,
|
|
6956
|
-
amountGross: effectiveAdjustment.amountGross ?? effectiveAdjustment.amountNet ?? 0,
|
|
6957
|
-
remainingNet: Number(
|
|
6958
|
-
baselineCalculation.totals?.grandTotalNetAmount ?? 0
|
|
6959
|
-
),
|
|
6960
|
-
remainingGross: Number(
|
|
6961
|
-
baselineCalculation.totals?.grandTotalGrossAmount ?? 0
|
|
6711
|
+
const [existingLines, existingAdjustments] = await Promise.all([
|
|
6712
|
+
em.find(SalesQuoteLine, { quote }, { orderBy: { lineNumber: "asc" } }),
|
|
6713
|
+
em.find(
|
|
6714
|
+
SalesQuoteAdjustment,
|
|
6715
|
+
{ quote },
|
|
6716
|
+
{ orderBy: { position: "asc" } }
|
|
6962
6717
|
)
|
|
6963
|
-
|
|
6964
|
-
|
|
6965
|
-
|
|
6966
|
-
|
|
6967
|
-
|
|
6968
|
-
|
|
6969
|
-
|
|
6970
|
-
|
|
6971
|
-
|
|
6972
|
-
|
|
6973
|
-
|
|
6974
|
-
|
|
6975
|
-
|
|
6976
|
-
|
|
6977
|
-
|
|
6978
|
-
|
|
6979
|
-
|
|
6718
|
+
]);
|
|
6719
|
+
const lineSnapshots = existingLines.map(mapQuoteLineEntityToSnapshot);
|
|
6720
|
+
const adjustmentDrafts = existingAdjustments.map(mapQuoteAdjustmentToDraft);
|
|
6721
|
+
const existingSnapshot = parsed.id ? adjustmentDrafts.find((adj) => adj.id === parsed.id) ?? null : null;
|
|
6722
|
+
const adjustmentId = parsed.id ?? existingSnapshot?.id ?? randomUUID();
|
|
6723
|
+
let metadata = typeof parsed.metadata === "object" && parsed.metadata ? cloneJson(parsed.metadata) : existingSnapshot?.metadata ? cloneJson(existingSnapshot.metadata) : null;
|
|
6724
|
+
const calculatorKey = parsed.calculatorKey ?? existingSnapshot?.calculatorKey ?? null;
|
|
6725
|
+
if (parsed.id && calculatorKey && (calculatorKey.startsWith("shipping-provider:") || calculatorKey.startsWith("payment-provider:"))) {
|
|
6726
|
+
metadata = { ...metadata ?? {}, manualOverride: true };
|
|
6727
|
+
}
|
|
6728
|
+
let nextAdjustments = parsed.id ? adjustmentDrafts.map(
|
|
6729
|
+
(adj) => adj.id === parsed.id ? {
|
|
6730
|
+
...adj,
|
|
6731
|
+
id: adjustmentId,
|
|
6732
|
+
scope: parsed.scope ?? adj.scope ?? "order",
|
|
6733
|
+
kind: parsed.kind ?? adj.kind ?? "custom",
|
|
6734
|
+
code: parsed.code ?? adj.code ?? null,
|
|
6735
|
+
label: parsed.label ?? adj.label ?? null,
|
|
6736
|
+
calculatorKey: parsed.calculatorKey ?? adj.calculatorKey ?? null,
|
|
6737
|
+
promotionId: parsed.promotionId ?? adj.promotionId ?? null,
|
|
6738
|
+
rate: parsed.rate ?? adj.rate ?? null,
|
|
6739
|
+
amountNet: parsed.amountNet ?? adj.amountNet ?? null,
|
|
6740
|
+
amountGross: parsed.amountGross ?? adj.amountGross ?? null,
|
|
6741
|
+
currencyCode: parsed.currencyCode ?? adj.currencyCode ?? quote.currencyCode,
|
|
6742
|
+
metadata,
|
|
6743
|
+
customFields: parsed.customFields !== void 0 ? parsed.customFields : adj.customFields ?? null,
|
|
6744
|
+
position: parsed.position ?? adj.position ?? adjustmentDrafts.length
|
|
6745
|
+
} : adj
|
|
6746
|
+
) : [
|
|
6747
|
+
...adjustmentDrafts,
|
|
6748
|
+
{
|
|
6749
|
+
id: adjustmentId,
|
|
6750
|
+
scope: parsed.scope ?? "order",
|
|
6751
|
+
kind: parsed.kind ?? "custom",
|
|
6752
|
+
code: parsed.code ?? null,
|
|
6753
|
+
label: parsed.label ?? null,
|
|
6754
|
+
calculatorKey: parsed.calculatorKey ?? null,
|
|
6755
|
+
promotionId: parsed.promotionId ?? null,
|
|
6756
|
+
rate: parsed.rate ?? null,
|
|
6757
|
+
amountNet: parsed.amountNet ?? null,
|
|
6758
|
+
amountGross: parsed.amountGross ?? null,
|
|
6759
|
+
currencyCode: parsed.currencyCode ?? quote.currencyCode,
|
|
6760
|
+
metadata,
|
|
6761
|
+
customFields: parsed.customFields ?? null,
|
|
6762
|
+
position: parsed.position ?? adjustmentDrafts.length
|
|
6980
6763
|
}
|
|
6981
|
-
|
|
6982
|
-
|
|
6983
|
-
|
|
6984
|
-
|
|
6764
|
+
];
|
|
6765
|
+
nextAdjustments = nextAdjustments.sort((a, b) => (a.position ?? 0) - (b.position ?? 0)).map((adj, index) => ({ ...adj, position: adj.position ?? index }));
|
|
6766
|
+
const sourceLines = lineSnapshots.map((line, index) => ({
|
|
6767
|
+
...line,
|
|
6768
|
+
statusEntryId: line.statusEntryId ?? null,
|
|
6769
|
+
catalogSnapshot: line.catalogSnapshot ?? null,
|
|
6770
|
+
promotionSnapshot: line.promotionSnapshot ?? null,
|
|
6771
|
+
organizationId: quote.organizationId,
|
|
6772
|
+
tenantId: quote.tenantId,
|
|
6773
|
+
quoteId: quote.id,
|
|
6774
|
+
lineNumber: line.lineNumber ?? index + 1
|
|
6775
|
+
}));
|
|
6776
|
+
const calcLines = sourceLines.map(
|
|
6777
|
+
(line, index) => createLineSnapshotFromInput(line, line.lineNumber ?? index + 1)
|
|
6778
|
+
);
|
|
6779
|
+
const salesCalculationService = ctx.container.resolve("salesCalculationService");
|
|
6780
|
+
const calculationContext = buildCalculationContext({
|
|
6781
|
+
tenantId: quote.tenantId,
|
|
6782
|
+
organizationId: quote.organizationId,
|
|
6783
|
+
currencyCode: quote.currencyCode,
|
|
6784
|
+
shippingSnapshot: quote.shippingMethodSnapshot,
|
|
6785
|
+
paymentSnapshot: quote.paymentMethodSnapshot,
|
|
6786
|
+
shippingMethodId: quote.shippingMethodId ?? null,
|
|
6787
|
+
paymentMethodId: quote.paymentMethodId ?? null,
|
|
6788
|
+
shippingMethodCode: quote.shippingMethodCode ?? null,
|
|
6789
|
+
paymentMethodCode: quote.paymentMethodCode ?? null
|
|
6790
|
+
});
|
|
6791
|
+
const effectiveAdjustment = nextAdjustments.find(
|
|
6792
|
+
(adj) => adj.id === adjustmentId
|
|
6793
|
+
);
|
|
6794
|
+
if (effectiveAdjustment?.kind === "return") {
|
|
6795
|
+
const baselineAdjustments = nextAdjustments.filter(
|
|
6796
|
+
(adj) => adj.id !== adjustmentId
|
|
6797
|
+
);
|
|
6798
|
+
const baselineCalculation = await salesCalculationService.calculateDocumentTotals({
|
|
6799
|
+
documentKind: "quote",
|
|
6800
|
+
lines: calcLines,
|
|
6801
|
+
adjustments: baselineAdjustments,
|
|
6802
|
+
context: calculationContext
|
|
6803
|
+
});
|
|
6804
|
+
const issues = validateReturnAdjustmentWithinRemaining({
|
|
6805
|
+
kind: "return",
|
|
6806
|
+
amountNet: effectiveAdjustment.amountNet ?? effectiveAdjustment.amountGross ?? 0,
|
|
6807
|
+
amountGross: effectiveAdjustment.amountGross ?? effectiveAdjustment.amountNet ?? 0,
|
|
6808
|
+
remainingNet: Number(
|
|
6809
|
+
baselineCalculation.totals?.grandTotalNetAmount ?? 0
|
|
6985
6810
|
),
|
|
6986
|
-
|
|
6811
|
+
remainingGross: Number(
|
|
6812
|
+
baselineCalculation.totals?.grandTotalGrossAmount ?? 0
|
|
6813
|
+
)
|
|
6987
6814
|
});
|
|
6988
|
-
|
|
6989
|
-
|
|
6990
|
-
|
|
6991
|
-
|
|
6992
|
-
|
|
6993
|
-
|
|
6994
|
-
|
|
6995
|
-
|
|
6996
|
-
|
|
6997
|
-
|
|
6998
|
-
|
|
6999
|
-
|
|
7000
|
-
|
|
7001
|
-
|
|
7002
|
-
|
|
7003
|
-
|
|
7004
|
-
|
|
7005
|
-
|
|
7006
|
-
|
|
7007
|
-
|
|
7008
|
-
|
|
7009
|
-
|
|
7010
|
-
|
|
7011
|
-
customFields: adj.customFields ?? void 0,
|
|
7012
|
-
position: adj.position ?? index
|
|
7013
|
-
}));
|
|
7014
|
-
let eventBus = null;
|
|
7015
|
-
try {
|
|
7016
|
-
eventBus = ctx.container.resolve("eventBus");
|
|
7017
|
-
} catch {
|
|
7018
|
-
eventBus = null;
|
|
7019
|
-
}
|
|
7020
|
-
await withAtomicFlush(
|
|
7021
|
-
em,
|
|
7022
|
-
[
|
|
7023
|
-
async () => {
|
|
7024
|
-
await replaceQuoteAdjustments(em, quote, calculation, adjustmentInputs);
|
|
7025
|
-
applyQuoteTotals(quote, calculation.totals, calculation.lines.length);
|
|
7026
|
-
quote.updatedAt = /* @__PURE__ */ new Date();
|
|
7027
|
-
await emitTotalsCalculated(eventBus, {
|
|
7028
|
-
documentKind: "quote",
|
|
7029
|
-
documentId: quote.id,
|
|
7030
|
-
organizationId: quote.organizationId,
|
|
7031
|
-
tenantId: quote.tenantId,
|
|
7032
|
-
customerId: quote.customerEntityId ?? null,
|
|
7033
|
-
totals: calculation.totals,
|
|
7034
|
-
lineCount: calculation.lines.length
|
|
6815
|
+
if (issues.length > 0) {
|
|
6816
|
+
const { translate } = await resolveTranslations();
|
|
6817
|
+
const grossIssue = issues.find((issue) => issue.path === "amountGross");
|
|
6818
|
+
const netIssue = issues.find((issue) => issue.path === "amountNet");
|
|
6819
|
+
const fieldErrors = {};
|
|
6820
|
+
if (grossIssue) {
|
|
6821
|
+
fieldErrors.amountGross = translate(
|
|
6822
|
+
"sales.adjustments.error.returnExceedsRemainingGrandTotalGross",
|
|
6823
|
+
RETURN_ADJUSTMENT_EXCEEDS_REMAINING_GROSS_MESSAGE
|
|
6824
|
+
);
|
|
6825
|
+
}
|
|
6826
|
+
if (netIssue) {
|
|
6827
|
+
fieldErrors.amountNet = translate(
|
|
6828
|
+
"sales.adjustments.error.returnExceedsRemainingGrandTotalNet",
|
|
6829
|
+
RETURN_ADJUSTMENT_EXCEEDS_REMAINING_NET_MESSAGE
|
|
6830
|
+
);
|
|
6831
|
+
}
|
|
6832
|
+
throw new CrudHttpError(400, {
|
|
6833
|
+
error: fieldErrors.amountGross ?? fieldErrors.amountNet ?? translate(
|
|
6834
|
+
"sales.adjustments.error.returnExceedsRemainingGrandTotalGross",
|
|
6835
|
+
RETURN_ADJUSTMENT_EXCEEDS_REMAINING_GROSS_MESSAGE
|
|
6836
|
+
),
|
|
6837
|
+
fieldErrors
|
|
7035
6838
|
});
|
|
7036
6839
|
}
|
|
7037
|
-
|
|
7038
|
-
|
|
7039
|
-
|
|
7040
|
-
|
|
7041
|
-
|
|
7042
|
-
|
|
7043
|
-
|
|
7044
|
-
|
|
6840
|
+
}
|
|
6841
|
+
const calculation = await salesCalculationService.calculateDocumentTotals({
|
|
6842
|
+
documentKind: "quote",
|
|
6843
|
+
lines: calcLines,
|
|
6844
|
+
adjustments: nextAdjustments,
|
|
6845
|
+
context: calculationContext
|
|
6846
|
+
});
|
|
6847
|
+
const adjustmentInputs = nextAdjustments.map((adj, index) => ({
|
|
6848
|
+
organizationId: quote.organizationId,
|
|
6849
|
+
tenantId: quote.tenantId,
|
|
6850
|
+
quoteId: quote.id,
|
|
6851
|
+
scope: adj.scope ?? "order",
|
|
6852
|
+
kind: adj.kind ?? "custom",
|
|
6853
|
+
code: adj.code ?? void 0,
|
|
6854
|
+
label: adj.label ?? void 0,
|
|
6855
|
+
calculatorKey: adj.calculatorKey ?? void 0,
|
|
6856
|
+
promotionId: adj.promotionId ?? void 0,
|
|
6857
|
+
rate: adj.rate ?? void 0,
|
|
6858
|
+
amountNet: adj.amountNet ?? void 0,
|
|
6859
|
+
amountGross: adj.amountGross ?? void 0,
|
|
6860
|
+
currencyCode: adj.currencyCode ?? quote.currencyCode,
|
|
6861
|
+
metadata: adj.metadata ?? void 0,
|
|
6862
|
+
customFields: adj.customFields ?? void 0,
|
|
6863
|
+
position: adj.position ?? index
|
|
6864
|
+
}));
|
|
6865
|
+
await replaceQuoteAdjustments(em, quote, calculation, adjustmentInputs);
|
|
6866
|
+
applyQuoteTotals(quote, calculation.totals, calculation.lines.length);
|
|
6867
|
+
quote.updatedAt = /* @__PURE__ */ new Date();
|
|
6868
|
+
return { result: { quoteId: quote.id, adjustmentId }, calculation };
|
|
6869
|
+
});
|
|
7045
6870
|
},
|
|
6871
|
+
captureAfter: async (_input, result, ctx) => captureLockedQuoteGraphAfter(ctx, result, result.quoteId),
|
|
7046
6872
|
buildLog: async ({ snapshots, result }) => {
|
|
7047
|
-
const before = snapshots.before;
|
|
6873
|
+
const before = resolveLockedQuoteGraphBefore(result, snapshots.before);
|
|
7048
6874
|
const after = snapshots.after;
|
|
7049
6875
|
if (!after) return null;
|
|
7050
6876
|
const { translate } = await resolveTranslations();
|
|
@@ -7073,131 +6899,87 @@ const quoteAdjustmentUpsertCommand = {
|
|
|
7073
6899
|
};
|
|
7074
6900
|
const quoteAdjustmentDeleteCommand = {
|
|
7075
6901
|
id: "sales.quotes.adjustments.delete",
|
|
7076
|
-
async prepare(input
|
|
7077
|
-
|
|
7078
|
-
const quoteId = typeof raw.quoteId === "string" ? raw.quoteId : null;
|
|
7079
|
-
if (!quoteId) return {};
|
|
7080
|
-
const em = ctx.container.resolve("em");
|
|
7081
|
-
const snapshot = await loadQuoteSnapshot(em, quoteId);
|
|
7082
|
-
if (snapshot)
|
|
7083
|
-
ensureQuoteScope(
|
|
7084
|
-
ctx,
|
|
7085
|
-
snapshot.quote.organizationId,
|
|
7086
|
-
snapshot.quote.tenantId
|
|
7087
|
-
);
|
|
7088
|
-
return snapshot ? { before: snapshot } : {};
|
|
6902
|
+
async prepare(input) {
|
|
6903
|
+
return markLockedGraphWrite(input);
|
|
7089
6904
|
},
|
|
7090
6905
|
async execute(input, ctx) {
|
|
7091
6906
|
const parsed = quoteAdjustmentDeleteSchema.parse(
|
|
7092
6907
|
input?.body ?? {}
|
|
7093
6908
|
);
|
|
7094
|
-
|
|
7095
|
-
|
|
7096
|
-
|
|
7097
|
-
|
|
7098
|
-
|
|
7099
|
-
|
|
7100
|
-
|
|
7101
|
-
|
|
7102
|
-
|
|
7103
|
-
|
|
7104
|
-
|
|
7105
|
-
|
|
7106
|
-
|
|
7107
|
-
|
|
7108
|
-
|
|
7109
|
-
|
|
7110
|
-
|
|
7111
|
-
|
|
7112
|
-
if (filtered.length === adjustments.length) {
|
|
7113
|
-
throw notFound("Adjustment not found");
|
|
7114
|
-
}
|
|
7115
|
-
const lineSnapshots = existingLines.map(mapQuoteLineEntityToSnapshot);
|
|
7116
|
-
const calcLines = lineSnapshots.map(
|
|
7117
|
-
(line, index) => createLineSnapshotFromInput(
|
|
7118
|
-
{
|
|
7119
|
-
...line,
|
|
7120
|
-
organizationId: quote.organizationId,
|
|
7121
|
-
tenantId: quote.tenantId,
|
|
7122
|
-
quoteId: quote.id,
|
|
7123
|
-
lineNumber: line.lineNumber ?? index + 1,
|
|
7124
|
-
statusEntryId: line.statusEntryId ?? null,
|
|
7125
|
-
catalogSnapshot: line.catalogSnapshot ?? null,
|
|
7126
|
-
promotionSnapshot: line.promotionSnapshot ?? null
|
|
7127
|
-
},
|
|
7128
|
-
line.lineNumber ?? index + 1
|
|
7129
|
-
)
|
|
7130
|
-
);
|
|
7131
|
-
const adjustmentDrafts = filtered.map(mapQuoteAdjustmentToDraft);
|
|
7132
|
-
const salesCalculationService = ctx.container.resolve("salesCalculationService");
|
|
7133
|
-
const calculationContext = buildCalculationContext({
|
|
7134
|
-
tenantId: quote.tenantId,
|
|
7135
|
-
organizationId: quote.organizationId,
|
|
7136
|
-
currencyCode: quote.currencyCode,
|
|
7137
|
-
shippingSnapshot: quote.shippingMethodSnapshot,
|
|
7138
|
-
paymentSnapshot: quote.paymentMethodSnapshot,
|
|
7139
|
-
shippingMethodId: quote.shippingMethodId ?? null,
|
|
7140
|
-
paymentMethodId: quote.paymentMethodId ?? null,
|
|
7141
|
-
shippingMethodCode: quote.shippingMethodCode ?? null,
|
|
7142
|
-
paymentMethodCode: quote.paymentMethodCode ?? null
|
|
7143
|
-
});
|
|
7144
|
-
const calculation = await salesCalculationService.calculateDocumentTotals({
|
|
7145
|
-
documentKind: "quote",
|
|
7146
|
-
lines: calcLines,
|
|
7147
|
-
adjustments: adjustmentDrafts,
|
|
7148
|
-
context: calculationContext
|
|
7149
|
-
});
|
|
7150
|
-
const adjustmentInputs = adjustmentDrafts.map((adj, index) => ({
|
|
7151
|
-
organizationId: quote.organizationId,
|
|
7152
|
-
tenantId: quote.tenantId,
|
|
7153
|
-
quoteId: quote.id,
|
|
7154
|
-
scope: adj.scope ?? "order",
|
|
7155
|
-
kind: adj.kind ?? "custom",
|
|
7156
|
-
code: adj.code ?? void 0,
|
|
7157
|
-
label: adj.label ?? void 0,
|
|
7158
|
-
calculatorKey: adj.calculatorKey ?? void 0,
|
|
7159
|
-
promotionId: adj.promotionId ?? void 0,
|
|
7160
|
-
rate: adj.rate ?? void 0,
|
|
7161
|
-
amountNet: adj.amountNet ?? void 0,
|
|
7162
|
-
amountGross: adj.amountGross ?? void 0,
|
|
7163
|
-
currencyCode: adj.currencyCode ?? quote.currencyCode,
|
|
7164
|
-
metadata: adj.metadata ?? void 0,
|
|
7165
|
-
position: adj.position ?? index
|
|
7166
|
-
}));
|
|
7167
|
-
let eventBus = null;
|
|
7168
|
-
try {
|
|
7169
|
-
eventBus = ctx.container.resolve("eventBus");
|
|
7170
|
-
} catch {
|
|
7171
|
-
eventBus = null;
|
|
7172
|
-
}
|
|
7173
|
-
await withAtomicFlush(
|
|
7174
|
-
em,
|
|
7175
|
-
[
|
|
7176
|
-
async () => {
|
|
7177
|
-
await replaceQuoteAdjustments(em, quote, calculation, adjustmentInputs);
|
|
7178
|
-
applyQuoteTotals(quote, calculation.totals, calculation.lines.length);
|
|
7179
|
-
quote.updatedAt = /* @__PURE__ */ new Date();
|
|
7180
|
-
await emitTotalsCalculated(eventBus, {
|
|
7181
|
-
documentKind: "quote",
|
|
7182
|
-
documentId: quote.id,
|
|
6909
|
+
return writeLockedQuoteGraph(input, ctx, parsed.quoteId, "Sales quote not found", async (em, quote) => {
|
|
6910
|
+
const [existingLines, adjustments] = await Promise.all([
|
|
6911
|
+
em.find(SalesQuoteLine, { quote }, { orderBy: { lineNumber: "asc" } }),
|
|
6912
|
+
em.find(
|
|
6913
|
+
SalesQuoteAdjustment,
|
|
6914
|
+
{ quote },
|
|
6915
|
+
{ orderBy: { position: "asc" } }
|
|
6916
|
+
)
|
|
6917
|
+
]);
|
|
6918
|
+
const filtered = adjustments.filter((adj) => adj.id !== parsed.id);
|
|
6919
|
+
if (filtered.length === adjustments.length) {
|
|
6920
|
+
throw notFound("Adjustment not found");
|
|
6921
|
+
}
|
|
6922
|
+
const lineSnapshots = existingLines.map(mapQuoteLineEntityToSnapshot);
|
|
6923
|
+
const calcLines = lineSnapshots.map(
|
|
6924
|
+
(line, index) => createLineSnapshotFromInput(
|
|
6925
|
+
{
|
|
6926
|
+
...line,
|
|
7183
6927
|
organizationId: quote.organizationId,
|
|
7184
6928
|
tenantId: quote.tenantId,
|
|
7185
|
-
|
|
7186
|
-
|
|
7187
|
-
|
|
7188
|
-
|
|
7189
|
-
|
|
7190
|
-
|
|
7191
|
-
|
|
7192
|
-
|
|
7193
|
-
|
|
7194
|
-
|
|
7195
|
-
|
|
7196
|
-
|
|
7197
|
-
|
|
6929
|
+
quoteId: quote.id,
|
|
6930
|
+
lineNumber: line.lineNumber ?? index + 1,
|
|
6931
|
+
statusEntryId: line.statusEntryId ?? null,
|
|
6932
|
+
catalogSnapshot: line.catalogSnapshot ?? null,
|
|
6933
|
+
promotionSnapshot: line.promotionSnapshot ?? null
|
|
6934
|
+
},
|
|
6935
|
+
line.lineNumber ?? index + 1
|
|
6936
|
+
)
|
|
6937
|
+
);
|
|
6938
|
+
const adjustmentDrafts = filtered.map(mapQuoteAdjustmentToDraft);
|
|
6939
|
+
const salesCalculationService = ctx.container.resolve("salesCalculationService");
|
|
6940
|
+
const calculationContext = buildCalculationContext({
|
|
6941
|
+
tenantId: quote.tenantId,
|
|
6942
|
+
organizationId: quote.organizationId,
|
|
6943
|
+
currencyCode: quote.currencyCode,
|
|
6944
|
+
shippingSnapshot: quote.shippingMethodSnapshot,
|
|
6945
|
+
paymentSnapshot: quote.paymentMethodSnapshot,
|
|
6946
|
+
shippingMethodId: quote.shippingMethodId ?? null,
|
|
6947
|
+
paymentMethodId: quote.paymentMethodId ?? null,
|
|
6948
|
+
shippingMethodCode: quote.shippingMethodCode ?? null,
|
|
6949
|
+
paymentMethodCode: quote.paymentMethodCode ?? null
|
|
6950
|
+
});
|
|
6951
|
+
const calculation = await salesCalculationService.calculateDocumentTotals({
|
|
6952
|
+
documentKind: "quote",
|
|
6953
|
+
lines: calcLines,
|
|
6954
|
+
adjustments: adjustmentDrafts,
|
|
6955
|
+
context: calculationContext
|
|
6956
|
+
});
|
|
6957
|
+
const adjustmentInputs = adjustmentDrafts.map((adj, index) => ({
|
|
6958
|
+
organizationId: quote.organizationId,
|
|
6959
|
+
tenantId: quote.tenantId,
|
|
6960
|
+
quoteId: quote.id,
|
|
6961
|
+
scope: adj.scope ?? "order",
|
|
6962
|
+
kind: adj.kind ?? "custom",
|
|
6963
|
+
code: adj.code ?? void 0,
|
|
6964
|
+
label: adj.label ?? void 0,
|
|
6965
|
+
calculatorKey: adj.calculatorKey ?? void 0,
|
|
6966
|
+
promotionId: adj.promotionId ?? void 0,
|
|
6967
|
+
rate: adj.rate ?? void 0,
|
|
6968
|
+
amountNet: adj.amountNet ?? void 0,
|
|
6969
|
+
amountGross: adj.amountGross ?? void 0,
|
|
6970
|
+
currencyCode: adj.currencyCode ?? quote.currencyCode,
|
|
6971
|
+
metadata: adj.metadata ?? void 0,
|
|
6972
|
+
position: adj.position ?? index
|
|
6973
|
+
}));
|
|
6974
|
+
await replaceQuoteAdjustments(em, quote, calculation, adjustmentInputs);
|
|
6975
|
+
applyQuoteTotals(quote, calculation.totals, calculation.lines.length);
|
|
6976
|
+
quote.updatedAt = /* @__PURE__ */ new Date();
|
|
6977
|
+
return { result: { quoteId: quote.id, adjustmentId: parsed.id }, calculation };
|
|
6978
|
+
});
|
|
7198
6979
|
},
|
|
6980
|
+
captureAfter: async (_input, result, ctx) => captureLockedQuoteGraphAfter(ctx, result, result.quoteId),
|
|
7199
6981
|
buildLog: async ({ snapshots, result }) => {
|
|
7200
|
-
const before = snapshots.before;
|
|
6982
|
+
const before = resolveLockedQuoteGraphBefore(result, snapshots.before);
|
|
7201
6983
|
const after = snapshots.after;
|
|
7202
6984
|
if (!after) return null;
|
|
7203
6985
|
const { translate } = await resolveTranslations();
|