@munchi_oy/cart-engine 0.1.2 → 0.1.4
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/dist/index.cjs +576 -63
- package/dist/index.d.cts +81 -5
- package/dist/index.d.ts +81 -5
- package/dist/index.js +581 -64
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
OrderRefundStatus,
|
|
9
9
|
OrderStatusEnum,
|
|
10
10
|
OrderTypePOS,
|
|
11
|
+
PosDiscountTargetType as PosDiscountTargetType2,
|
|
11
12
|
PosPaymentStatus,
|
|
12
13
|
ProviderEnum as Provider
|
|
13
14
|
} from "@munchi_oy/core";
|
|
@@ -16,7 +17,12 @@ import dayjs from "dayjs";
|
|
|
16
17
|
import lodash from "lodash";
|
|
17
18
|
|
|
18
19
|
// src/pricing/calculator.ts
|
|
19
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
CheckoutModelEnum,
|
|
22
|
+
DiscountScope,
|
|
23
|
+
KitchenStatus,
|
|
24
|
+
PosDiscountTargetType
|
|
25
|
+
} from "@munchi_oy/core";
|
|
20
26
|
|
|
21
27
|
// src/discount/calculator.ts
|
|
22
28
|
import { DiscountType } from "@munchi_oy/core";
|
|
@@ -77,7 +83,49 @@ function calculateSequentialDiscountTotal(discounts, initialAmount) {
|
|
|
77
83
|
};
|
|
78
84
|
}
|
|
79
85
|
|
|
86
|
+
// src/discount/modulo.ts
|
|
87
|
+
var allocateProportionalMinorUnits = withSafeIntegers(
|
|
88
|
+
(weights, totalAmount) => {
|
|
89
|
+
for (let i = 0; i < weights.length; i++) {
|
|
90
|
+
if (!Number.isInteger(weights[i])) {
|
|
91
|
+
throw new Error(
|
|
92
|
+
`[CartEngine SafeLock] Weight at index ${i} MUST be an integer. Received: ${weights[i]}`
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (weights.length === 0 || totalAmount <= 0) {
|
|
97
|
+
return new Array(weights.length).fill(0);
|
|
98
|
+
}
|
|
99
|
+
const totalWeight = weights.reduce((sum, weight) => sum + weight, 0);
|
|
100
|
+
if (totalWeight <= 0) {
|
|
101
|
+
return new Array(weights.length).fill(0);
|
|
102
|
+
}
|
|
103
|
+
const baseAllocations = weights.map(
|
|
104
|
+
(weight) => Math.floor(weight * totalAmount / totalWeight)
|
|
105
|
+
);
|
|
106
|
+
const remainderOrder = weights.map((weight, index) => ({
|
|
107
|
+
index,
|
|
108
|
+
remainder: weight * totalAmount % totalWeight
|
|
109
|
+
})).sort(
|
|
110
|
+
(left, right) => right.remainder - left.remainder || left.index - right.index
|
|
111
|
+
);
|
|
112
|
+
let remainingAmount = totalAmount - baseAllocations.reduce((sum, amount) => sum + amount, 0);
|
|
113
|
+
for (const { index } of remainderOrder) {
|
|
114
|
+
if (remainingAmount <= 0) break;
|
|
115
|
+
baseAllocations[index] = (baseAllocations[index] ?? 0) + 1;
|
|
116
|
+
remainingAmount -= 1;
|
|
117
|
+
}
|
|
118
|
+
return baseAllocations;
|
|
119
|
+
}
|
|
120
|
+
);
|
|
121
|
+
|
|
80
122
|
// src/pricing/calculator.ts
|
|
123
|
+
var EMPTY_SPLIT_PRICING_RESULT = {
|
|
124
|
+
targets: [],
|
|
125
|
+
totalGrossAmount: 0,
|
|
126
|
+
totalDiscountAmount: 0,
|
|
127
|
+
totalNetAmount: 0
|
|
128
|
+
};
|
|
81
129
|
var assertMinorUnit = (value, fieldName) => {
|
|
82
130
|
if (!Number.isInteger(value)) {
|
|
83
131
|
throw new Error(
|
|
@@ -85,6 +133,93 @@ var assertMinorUnit = (value, fieldName) => {
|
|
|
85
133
|
);
|
|
86
134
|
}
|
|
87
135
|
};
|
|
136
|
+
var sortDiscountsByCreatedAt = (discounts) => {
|
|
137
|
+
return [...discounts].sort(
|
|
138
|
+
(left, right) => new Date(left.createdAt).getTime() - new Date(right.createdAt).getTime()
|
|
139
|
+
);
|
|
140
|
+
};
|
|
141
|
+
var calculateTargetPricing = (id, grossAmount, discounts) => {
|
|
142
|
+
assertMinorUnit(grossAmount, `target gross amount (${id})`);
|
|
143
|
+
const { totalDiscountAmount, remainingAmount } = calculateSequentialDiscountTotal(discounts, grossAmount);
|
|
144
|
+
return {
|
|
145
|
+
id,
|
|
146
|
+
grossAmount,
|
|
147
|
+
discountAmount: totalDiscountAmount,
|
|
148
|
+
netAmount: remainingAmount
|
|
149
|
+
};
|
|
150
|
+
};
|
|
151
|
+
var sumAmounts = (targets, field) => {
|
|
152
|
+
return targets.reduce((sum, target) => sum + target[field], 0);
|
|
153
|
+
};
|
|
154
|
+
var buildSplitPricingResult = (targets) => {
|
|
155
|
+
return {
|
|
156
|
+
targets,
|
|
157
|
+
totalGrossAmount: sumAmounts(targets, "grossAmount"),
|
|
158
|
+
totalDiscountAmount: sumAmounts(targets, "discountAmount"),
|
|
159
|
+
totalNetAmount: sumAmounts(targets, "netAmount")
|
|
160
|
+
};
|
|
161
|
+
};
|
|
162
|
+
var filterTargetedDiscounts = (discounts, targetType, targetId) => {
|
|
163
|
+
return sortDiscountsByCreatedAt(
|
|
164
|
+
discounts.filter(
|
|
165
|
+
(discount) => discount.targetType === targetType && discount.targetId === targetId
|
|
166
|
+
)
|
|
167
|
+
);
|
|
168
|
+
};
|
|
169
|
+
var getSeatGrossAmount = (seat) => {
|
|
170
|
+
return seat.assignments.reduce((sum, assignment) => {
|
|
171
|
+
assertMinorUnit(assignment.amount, `seat assignment amount (${seat.id})`);
|
|
172
|
+
return sum + assignment.amount;
|
|
173
|
+
}, 0);
|
|
174
|
+
};
|
|
175
|
+
var getEqualSplitPartIds = (equalSplitState) => {
|
|
176
|
+
const partCount = Math.max(equalSplitState.partCount, equalSplitState.parts.length);
|
|
177
|
+
const ids = equalSplitState.parts.map((part) => part.id);
|
|
178
|
+
for (let index = ids.length; index < partCount; index += 1) {
|
|
179
|
+
ids.push(`__generated_equal_split_part_${index + 1}`);
|
|
180
|
+
}
|
|
181
|
+
return ids;
|
|
182
|
+
};
|
|
183
|
+
function calculateItemSplitSeatPricing(details) {
|
|
184
|
+
if (!details.tableService) {
|
|
185
|
+
return EMPTY_SPLIT_PRICING_RESULT;
|
|
186
|
+
}
|
|
187
|
+
const targets = details.tableService.itemSplitSeats.map(
|
|
188
|
+
(seat) => calculateTargetPricing(
|
|
189
|
+
seat.id,
|
|
190
|
+
getSeatGrossAmount(seat),
|
|
191
|
+
filterTargetedDiscounts(
|
|
192
|
+
details.discounts,
|
|
193
|
+
PosDiscountTargetType.ItemSplitSeat,
|
|
194
|
+
seat.id
|
|
195
|
+
)
|
|
196
|
+
)
|
|
197
|
+
);
|
|
198
|
+
return buildSplitPricingResult(targets);
|
|
199
|
+
}
|
|
200
|
+
function calculateEqualSplitPartPricing(details) {
|
|
201
|
+
assertMinorUnit(details.totalAmount, "details.totalAmount");
|
|
202
|
+
if (!details.equalSplitState || details.equalSplitState.partCount <= 0) {
|
|
203
|
+
return EMPTY_SPLIT_PRICING_RESULT;
|
|
204
|
+
}
|
|
205
|
+
const partIds = getEqualSplitPartIds(details.equalSplitState);
|
|
206
|
+
const grossAmounts = allocateProportionalMinorUnits(
|
|
207
|
+
new Array(partIds.length).fill(1),
|
|
208
|
+
details.totalAmount
|
|
209
|
+
);
|
|
210
|
+
const targets = partIds.map(
|
|
211
|
+
(partId, index) => calculateTargetPricing(
|
|
212
|
+
partId,
|
|
213
|
+
grossAmounts[index] ?? 0,
|
|
214
|
+
filterTargetedDiscounts(
|
|
215
|
+
details.discounts,
|
|
216
|
+
PosDiscountTargetType.EqualSplitPart,
|
|
217
|
+
partId
|
|
218
|
+
)
|
|
219
|
+
)
|
|
220
|
+
);
|
|
221
|
+
return buildSplitPricingResult(targets);
|
|
222
|
+
}
|
|
88
223
|
function calculateItemPrice(details) {
|
|
89
224
|
assertMinorUnit(details.basePriceInMinorUnit, "details.basePriceInMinorUnit");
|
|
90
225
|
assertMinorUnit(details.quantity, "details.quantity");
|
|
@@ -147,7 +282,9 @@ function calculateCartPriceSnapshot(details) {
|
|
|
147
282
|
(sum, item) => sum + (item.itemPrice.priceBreakdown.totalDiscounts.amount ?? 0),
|
|
148
283
|
0
|
|
149
284
|
);
|
|
150
|
-
const cartDiscounts = details.discounts.filter(
|
|
285
|
+
const cartDiscounts = details.discounts.filter(
|
|
286
|
+
(discount) => discount.scope === DiscountScope.Cart && (discount.targetType === null || discount.targetType === void 0 || discount.targetType === PosDiscountTargetType.Order)
|
|
287
|
+
).sort(
|
|
151
288
|
(left, right) => new Date(left.createdAt).getTime() - new Date(right.createdAt).getTime()
|
|
152
289
|
);
|
|
153
290
|
const grossAfterItemDiscounts = Math.max(
|
|
@@ -158,7 +295,21 @@ function calculateCartPriceSnapshot(details) {
|
|
|
158
295
|
cartDiscounts,
|
|
159
296
|
grossAfterItemDiscounts
|
|
160
297
|
);
|
|
161
|
-
const
|
|
298
|
+
const itemSplitSeatPricing = details.checkoutModel === CheckoutModelEnum.ItemSplit ? calculateItemSplitSeatPricing({
|
|
299
|
+
tableService: details.tableService,
|
|
300
|
+
discounts: details.discounts
|
|
301
|
+
}) : null;
|
|
302
|
+
const equalSplitPartPricing = details.checkoutModel === CheckoutModelEnum.EqualSplit ? calculateEqualSplitPartPricing({
|
|
303
|
+
totalAmount: cartDiscountCalculation.remainingAmount,
|
|
304
|
+
equalSplitState: details.equalSplitState,
|
|
305
|
+
discounts: details.discounts
|
|
306
|
+
}) : null;
|
|
307
|
+
const subtotalTargetedDiscountsAmount = itemSplitSeatPricing?.totalDiscountAmount ?? equalSplitPartPricing?.totalDiscountAmount ?? 0;
|
|
308
|
+
const totalAmount = Math.max(
|
|
309
|
+
0,
|
|
310
|
+
cartDiscountCalculation.remainingAmount - subtotalTargetedDiscountsAmount
|
|
311
|
+
);
|
|
312
|
+
const totalDiscountsAmount = subtotalItemDiscountsAmount + cartDiscountCalculation.totalDiscountAmount + subtotalTargetedDiscountsAmount;
|
|
162
313
|
const price = {
|
|
163
314
|
priceBreakdown: {
|
|
164
315
|
totalBeforeDiscounts: {
|
|
@@ -179,7 +330,7 @@ function calculateCartPriceSnapshot(details) {
|
|
|
179
330
|
}
|
|
180
331
|
},
|
|
181
332
|
total: {
|
|
182
|
-
amount:
|
|
333
|
+
amount: totalAmount,
|
|
183
334
|
currency: details.currency
|
|
184
335
|
}
|
|
185
336
|
};
|
|
@@ -188,52 +339,17 @@ function calculateCartPriceSnapshot(details) {
|
|
|
188
339
|
totalBeforeDiscountsAmount,
|
|
189
340
|
subtotalItemDiscountsAmount,
|
|
190
341
|
subtotalBasketDiscountsAmount: cartDiscountCalculation.totalDiscountAmount,
|
|
342
|
+
subtotalTargetedDiscountsAmount,
|
|
191
343
|
totalDiscountsAmount,
|
|
192
|
-
totalAmount
|
|
344
|
+
totalAmount,
|
|
345
|
+
itemSplitSeatPricing,
|
|
346
|
+
equalSplitPartPricing,
|
|
193
347
|
price
|
|
194
348
|
};
|
|
195
349
|
}
|
|
196
350
|
|
|
197
351
|
// src/tax/index.ts
|
|
198
352
|
import { KitchenStatus as KitchenStatus2 } from "@munchi_oy/core";
|
|
199
|
-
|
|
200
|
-
// src/discount/modulo.ts
|
|
201
|
-
var allocateProportionalMinorUnits = withSafeIntegers(
|
|
202
|
-
(weights, totalAmount) => {
|
|
203
|
-
for (let i = 0; i < weights.length; i++) {
|
|
204
|
-
if (!Number.isInteger(weights[i])) {
|
|
205
|
-
throw new Error(
|
|
206
|
-
`[CartEngine SafeLock] Weight at index ${i} MUST be an integer. Received: ${weights[i]}`
|
|
207
|
-
);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
if (weights.length === 0 || totalAmount <= 0) {
|
|
211
|
-
return new Array(weights.length).fill(0);
|
|
212
|
-
}
|
|
213
|
-
const totalWeight = weights.reduce((sum, weight) => sum + weight, 0);
|
|
214
|
-
if (totalWeight <= 0) {
|
|
215
|
-
return new Array(weights.length).fill(0);
|
|
216
|
-
}
|
|
217
|
-
const baseAllocations = weights.map(
|
|
218
|
-
(weight) => Math.floor(weight * totalAmount / totalWeight)
|
|
219
|
-
);
|
|
220
|
-
const remainderOrder = weights.map((weight, index) => ({
|
|
221
|
-
index,
|
|
222
|
-
remainder: weight * totalAmount % totalWeight
|
|
223
|
-
})).sort(
|
|
224
|
-
(left, right) => right.remainder - left.remainder || left.index - right.index
|
|
225
|
-
);
|
|
226
|
-
let remainingAmount = totalAmount - baseAllocations.reduce((sum, amount) => sum + amount, 0);
|
|
227
|
-
for (const { index } of remainderOrder) {
|
|
228
|
-
if (remainingAmount <= 0) break;
|
|
229
|
-
baseAllocations[index] = (baseAllocations[index] ?? 0) + 1;
|
|
230
|
-
remainingAmount -= 1;
|
|
231
|
-
}
|
|
232
|
-
return baseAllocations;
|
|
233
|
-
}
|
|
234
|
-
);
|
|
235
|
-
|
|
236
|
-
// src/tax/index.ts
|
|
237
353
|
function assertMinorUnit2(value, fieldName) {
|
|
238
354
|
if (!Number.isInteger(value)) {
|
|
239
355
|
throw new Error(
|
|
@@ -341,6 +457,76 @@ function calculateOrderTaxSummary(details) {
|
|
|
341
457
|
|
|
342
458
|
// src/cart/Cart.ts
|
|
343
459
|
var { isEqual } = lodash;
|
|
460
|
+
function cloneEqualSplitPart(part) {
|
|
461
|
+
return {
|
|
462
|
+
...part,
|
|
463
|
+
name: part.name ?? null,
|
|
464
|
+
loyaltyId: part.loyaltyId ?? null,
|
|
465
|
+
loyaltyUserName: part.loyaltyUserName ?? null,
|
|
466
|
+
appliedDiscountIds: [...part.appliedDiscountIds ?? []]
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
function cloneEqualSplitState(equalSplitState) {
|
|
470
|
+
if (!equalSplitState) {
|
|
471
|
+
return null;
|
|
472
|
+
}
|
|
473
|
+
return {
|
|
474
|
+
partCount: equalSplitState.partCount,
|
|
475
|
+
parts: (equalSplitState.parts ?? []).map(cloneEqualSplitPart)
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
function createEmptyEqualSplitState() {
|
|
479
|
+
return {
|
|
480
|
+
partCount: 0,
|
|
481
|
+
parts: []
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
function cloneItemSplitSeatAssignment(assignment) {
|
|
485
|
+
return {
|
|
486
|
+
...assignment,
|
|
487
|
+
appliedDiscountIds: [...assignment.appliedDiscountIds ?? []]
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
function cloneItemSplitSeat(seat) {
|
|
491
|
+
return {
|
|
492
|
+
...seat,
|
|
493
|
+
name: seat.name ?? null,
|
|
494
|
+
loyaltyId: seat.loyaltyId ?? null,
|
|
495
|
+
loyaltyUserName: seat.loyaltyUserName ?? null,
|
|
496
|
+
appliedDiscountIds: [...seat.appliedDiscountIds ?? []],
|
|
497
|
+
assignments: (seat.assignments ?? []).map(cloneItemSplitSeatAssignment)
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
function cloneTableService(tableService) {
|
|
501
|
+
if (!tableService) {
|
|
502
|
+
return null;
|
|
503
|
+
}
|
|
504
|
+
return {
|
|
505
|
+
name: tableService.name ?? null,
|
|
506
|
+
seats: tableService.seats,
|
|
507
|
+
itemSplitSeats: (tableService.itemSplitSeats ?? []).map(cloneItemSplitSeat)
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
function createEmptyTableService() {
|
|
511
|
+
return {
|
|
512
|
+
name: null,
|
|
513
|
+
seats: 0,
|
|
514
|
+
itemSplitSeats: []
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
function validateEqualSplitState(equalSplitState) {
|
|
518
|
+
if (!equalSplitState) {
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
if (!Number.isInteger(equalSplitState.partCount) || equalSplitState.partCount < 0) {
|
|
522
|
+
throw new Error("Equal split part count must be a non-negative integer");
|
|
523
|
+
}
|
|
524
|
+
if (equalSplitState.parts.length > equalSplitState.partCount) {
|
|
525
|
+
throw new Error(
|
|
526
|
+
"Equal split state cannot contain more parts than partCount"
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
344
530
|
var Cart = class _Cart {
|
|
345
531
|
id;
|
|
346
532
|
businessId;
|
|
@@ -365,6 +551,7 @@ var Cart = class _Cart {
|
|
|
365
551
|
_customer = null;
|
|
366
552
|
_refunds = [];
|
|
367
553
|
_tableService;
|
|
554
|
+
_equalSplitState;
|
|
368
555
|
_taxSummary;
|
|
369
556
|
_newlyCancelledItemIds;
|
|
370
557
|
_invoiceCompany;
|
|
@@ -390,7 +577,8 @@ var Cart = class _Cart {
|
|
|
390
577
|
this.status = OrderStatusEnum.Draft;
|
|
391
578
|
this.orderFormat = OrderFormat.Instant;
|
|
392
579
|
this._locatorType = LocatorType.Table;
|
|
393
|
-
this._tableService = options.tableService
|
|
580
|
+
this._tableService = cloneTableService(options.tableService);
|
|
581
|
+
this._equalSplitState = cloneEqualSplitState(options.equalSplitState);
|
|
394
582
|
this._newlyCancelledItemIds = /* @__PURE__ */ new Set();
|
|
395
583
|
this._staffId = options.staffId ?? null;
|
|
396
584
|
this._shiftId = options.shiftId ?? null;
|
|
@@ -404,6 +592,7 @@ var Cart = class _Cart {
|
|
|
404
592
|
orderNumber: options.orderNumber,
|
|
405
593
|
checkoutModel: options.checkoutModel ?? CheckoutModel.Full,
|
|
406
594
|
tableService: options.tableService ?? null,
|
|
595
|
+
equalSplitState: options.equalSplitState ?? null,
|
|
407
596
|
currency: options.currency,
|
|
408
597
|
business: options.business,
|
|
409
598
|
staffId: options.staffId,
|
|
@@ -418,6 +607,7 @@ var Cart = class _Cart {
|
|
|
418
607
|
orderNumber: orderDto.orderNumber,
|
|
419
608
|
checkoutModel: orderDto.checkoutModel,
|
|
420
609
|
tableService: orderDto.tableService ?? null,
|
|
610
|
+
equalSplitState: orderDto.equalSplitState ?? null,
|
|
421
611
|
currency: orderDto.currency,
|
|
422
612
|
business: orderDto.business,
|
|
423
613
|
staffId: orderDto.staffId ?? null,
|
|
@@ -439,7 +629,8 @@ var Cart = class _Cart {
|
|
|
439
629
|
cart._refunds = orderDto.refunds ? [...orderDto.refunds] : [];
|
|
440
630
|
cart._loyaltyTransactionIds = [...orderDto.loyaltyTransactionIds];
|
|
441
631
|
cart._loyaltyProgramId = orderDto.loyaltyProgramId ?? null;
|
|
442
|
-
cart._tableService = orderDto.tableService
|
|
632
|
+
cart._tableService = cloneTableService(orderDto.tableService);
|
|
633
|
+
cart._equalSplitState = cloneEqualSplitState(orderDto.equalSplitState);
|
|
443
634
|
cart._newlyCancelledItemIds = /* @__PURE__ */ new Set();
|
|
444
635
|
cart._taxSummary = orderDto.taxSummary;
|
|
445
636
|
cart._invoiceCompany = orderDto.invoiceCompany ?? null;
|
|
@@ -479,13 +670,22 @@ var Cart = class _Cart {
|
|
|
479
670
|
return this._tableService?.seats ?? null;
|
|
480
671
|
}
|
|
481
672
|
get tableService() {
|
|
482
|
-
return this.
|
|
673
|
+
return this.getTableService();
|
|
674
|
+
}
|
|
675
|
+
get equalSplitState() {
|
|
676
|
+
return this.getEqualSplitState();
|
|
677
|
+
}
|
|
678
|
+
get equalSplitPartCount() {
|
|
679
|
+
return this._equalSplitState?.partCount ?? null;
|
|
483
680
|
}
|
|
484
681
|
get price() {
|
|
485
682
|
return calculateCartPriceSnapshot({
|
|
486
683
|
items: this._items,
|
|
487
684
|
discounts: this._discounts,
|
|
488
|
-
currency: this.currency
|
|
685
|
+
currency: this.currency,
|
|
686
|
+
checkoutModel: this.checkoutModel,
|
|
687
|
+
tableService: this._tableService,
|
|
688
|
+
equalSplitState: this._equalSplitState
|
|
489
689
|
}).price;
|
|
490
690
|
}
|
|
491
691
|
setInvoiceCompany(company) {
|
|
@@ -588,21 +788,25 @@ var Cart = class _Cart {
|
|
|
588
788
|
this._newlyCancelledItemIds.clear();
|
|
589
789
|
}
|
|
590
790
|
applyDiscountToCart(discountData) {
|
|
591
|
-
this._discounts.push(
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
791
|
+
this._discounts.push(
|
|
792
|
+
this.createDiscountRecord(
|
|
793
|
+
discountData,
|
|
794
|
+
DiscountScope2.Cart,
|
|
795
|
+
null,
|
|
796
|
+
PosDiscountTargetType2.Order,
|
|
797
|
+
null
|
|
798
|
+
)
|
|
799
|
+
);
|
|
597
800
|
this.touch();
|
|
598
801
|
}
|
|
599
802
|
applyDiscountToItem(lineItemId, discountData) {
|
|
600
|
-
const newDiscount =
|
|
601
|
-
|
|
602
|
-
|
|
803
|
+
const newDiscount = this.createDiscountRecord(
|
|
804
|
+
discountData,
|
|
805
|
+
DiscountScope2.Item,
|
|
603
806
|
lineItemId,
|
|
604
|
-
|
|
605
|
-
|
|
807
|
+
PosDiscountTargetType2.LineItem,
|
|
808
|
+
lineItemId
|
|
809
|
+
);
|
|
606
810
|
const existingDiscountIndex = this._discounts.findIndex(
|
|
607
811
|
(discount) => discount.lineItemId === lineItemId && discount.scope === DiscountScope2.Item
|
|
608
812
|
);
|
|
@@ -633,6 +837,42 @@ var Cart = class _Cart {
|
|
|
633
837
|
});
|
|
634
838
|
this.touch();
|
|
635
839
|
}
|
|
840
|
+
applyDiscountToItemSplitSeat(seatId, discountData) {
|
|
841
|
+
this._discounts.push(
|
|
842
|
+
this.createDiscountRecord(
|
|
843
|
+
discountData,
|
|
844
|
+
DiscountScope2.Cart,
|
|
845
|
+
null,
|
|
846
|
+
PosDiscountTargetType2.ItemSplitSeat,
|
|
847
|
+
seatId
|
|
848
|
+
)
|
|
849
|
+
);
|
|
850
|
+
this.touch();
|
|
851
|
+
}
|
|
852
|
+
applyDiscountToItemSplitAssignment(assignmentId, discountData) {
|
|
853
|
+
this._discounts.push(
|
|
854
|
+
this.createDiscountRecord(
|
|
855
|
+
discountData,
|
|
856
|
+
DiscountScope2.Cart,
|
|
857
|
+
null,
|
|
858
|
+
PosDiscountTargetType2.ItemSplitAssignment,
|
|
859
|
+
assignmentId
|
|
860
|
+
)
|
|
861
|
+
);
|
|
862
|
+
this.touch();
|
|
863
|
+
}
|
|
864
|
+
applyDiscountToEqualSplitPart(partId, discountData) {
|
|
865
|
+
this._discounts.push(
|
|
866
|
+
this.createDiscountRecord(
|
|
867
|
+
discountData,
|
|
868
|
+
DiscountScope2.Cart,
|
|
869
|
+
null,
|
|
870
|
+
PosDiscountTargetType2.EqualSplitPart,
|
|
871
|
+
partId
|
|
872
|
+
)
|
|
873
|
+
);
|
|
874
|
+
this.touch();
|
|
875
|
+
}
|
|
636
876
|
removeItemDiscount(lineItemId) {
|
|
637
877
|
this._items = this._items.map((item) => {
|
|
638
878
|
if (item.lineItemId !== lineItemId) {
|
|
@@ -745,15 +985,277 @@ var Cart = class _Cart {
|
|
|
745
985
|
this.setCheckoutModel(checkoutModel);
|
|
746
986
|
}
|
|
747
987
|
setGuestCount(guestCount) {
|
|
748
|
-
if (
|
|
988
|
+
if (guestCount === null) {
|
|
989
|
+
if (this._tableService === null) {
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
992
|
+
this._tableService = null;
|
|
993
|
+
this.touch();
|
|
749
994
|
return;
|
|
750
995
|
}
|
|
751
|
-
this.
|
|
752
|
-
this.touch();
|
|
996
|
+
this.setSeatsCount(guestCount);
|
|
753
997
|
}
|
|
754
998
|
setSeatNumber(guestCount) {
|
|
755
999
|
this.setGuestCount(guestCount);
|
|
756
1000
|
}
|
|
1001
|
+
getTableService() {
|
|
1002
|
+
return cloneTableService(this._tableService);
|
|
1003
|
+
}
|
|
1004
|
+
setTableService(tableService) {
|
|
1005
|
+
const nextTableService = cloneTableService(tableService);
|
|
1006
|
+
if (isEqual(this._tableService, nextTableService)) {
|
|
1007
|
+
return this;
|
|
1008
|
+
}
|
|
1009
|
+
this._tableService = nextTableService;
|
|
1010
|
+
this.touch();
|
|
1011
|
+
return this;
|
|
1012
|
+
}
|
|
1013
|
+
clearTableService() {
|
|
1014
|
+
return this.setTableService(null);
|
|
1015
|
+
}
|
|
1016
|
+
getTableName() {
|
|
1017
|
+
return this._tableService?.name ?? null;
|
|
1018
|
+
}
|
|
1019
|
+
setTableName(name) {
|
|
1020
|
+
const tableService = this.getTableService() ?? createEmptyTableService();
|
|
1021
|
+
if (tableService.name === name) {
|
|
1022
|
+
return this;
|
|
1023
|
+
}
|
|
1024
|
+
tableService.name = name;
|
|
1025
|
+
return this.setTableService(tableService);
|
|
1026
|
+
}
|
|
1027
|
+
getSeatsCount() {
|
|
1028
|
+
return this._tableService?.seats ?? null;
|
|
1029
|
+
}
|
|
1030
|
+
setSeatsCount(seats) {
|
|
1031
|
+
if (!Number.isInteger(seats) || seats < 0) {
|
|
1032
|
+
throw new Error("Seats count must be a non-negative integer");
|
|
1033
|
+
}
|
|
1034
|
+
const tableService = this.getTableService() ?? createEmptyTableService();
|
|
1035
|
+
if (tableService.seats === seats) {
|
|
1036
|
+
return this;
|
|
1037
|
+
}
|
|
1038
|
+
tableService.seats = seats;
|
|
1039
|
+
return this.setTableService(tableService);
|
|
1040
|
+
}
|
|
1041
|
+
getItemSplitSeats() {
|
|
1042
|
+
return this._tableService ? this._tableService.itemSplitSeats.map(cloneItemSplitSeat) : [];
|
|
1043
|
+
}
|
|
1044
|
+
setItemSplitSeats(seats) {
|
|
1045
|
+
const tableService = this.getTableService() ?? createEmptyTableService();
|
|
1046
|
+
tableService.itemSplitSeats = seats.map(cloneItemSplitSeat);
|
|
1047
|
+
return this.setTableService(tableService);
|
|
1048
|
+
}
|
|
1049
|
+
clearItemSplitSeats() {
|
|
1050
|
+
return this.setItemSplitSeats([]);
|
|
1051
|
+
}
|
|
1052
|
+
addItemSplitSeat(seat) {
|
|
1053
|
+
if (this.getItemSplitSeatById(seat.id)) {
|
|
1054
|
+
return this;
|
|
1055
|
+
}
|
|
1056
|
+
const seats = this.getItemSplitSeats();
|
|
1057
|
+
seats.push(cloneItemSplitSeat(seat));
|
|
1058
|
+
return this.setItemSplitSeats(seats);
|
|
1059
|
+
}
|
|
1060
|
+
updateItemSplitSeat(seatId, updater) {
|
|
1061
|
+
const seats = this.getItemSplitSeats();
|
|
1062
|
+
const seatIndex = seats.findIndex((seat) => seat.id === seatId);
|
|
1063
|
+
if (seatIndex === -1 || !seats[seatIndex]) {
|
|
1064
|
+
return this;
|
|
1065
|
+
}
|
|
1066
|
+
seats[seatIndex] = cloneItemSplitSeat(updater(cloneItemSplitSeat(seats[seatIndex])));
|
|
1067
|
+
return this.setItemSplitSeats(seats);
|
|
1068
|
+
}
|
|
1069
|
+
removeItemSplitSeat(seatId) {
|
|
1070
|
+
const seats = this.getItemSplitSeats();
|
|
1071
|
+
const nextSeats = seats.filter((seat) => seat.id !== seatId);
|
|
1072
|
+
if (nextSeats.length === seats.length) {
|
|
1073
|
+
return this;
|
|
1074
|
+
}
|
|
1075
|
+
return this.setItemSplitSeats(nextSeats);
|
|
1076
|
+
}
|
|
1077
|
+
getItemSplitSeatById(seatId) {
|
|
1078
|
+
const seat = this._tableService?.itemSplitSeats.find(
|
|
1079
|
+
(itemSplitSeat) => itemSplitSeat.id === seatId
|
|
1080
|
+
);
|
|
1081
|
+
return seat ? cloneItemSplitSeat(seat) : null;
|
|
1082
|
+
}
|
|
1083
|
+
setItemSplitSeatAssignments(seatId, assignments) {
|
|
1084
|
+
return this.updateItemSplitSeat(seatId, (seat) => ({
|
|
1085
|
+
...seat,
|
|
1086
|
+
assignments: assignments.map(cloneItemSplitSeatAssignment)
|
|
1087
|
+
}));
|
|
1088
|
+
}
|
|
1089
|
+
addItemSplitSeatAssignment(seatId, assignment) {
|
|
1090
|
+
return this.updateItemSplitSeat(seatId, (seat) => ({
|
|
1091
|
+
...seat,
|
|
1092
|
+
assignments: [...seat.assignments, cloneItemSplitSeatAssignment(assignment)]
|
|
1093
|
+
}));
|
|
1094
|
+
}
|
|
1095
|
+
removeItemSplitSeatAssignmentsByLineItem(seatId, lineItemId) {
|
|
1096
|
+
return this.updateItemSplitSeat(seatId, (seat) => ({
|
|
1097
|
+
...seat,
|
|
1098
|
+
assignments: seat.assignments.filter(
|
|
1099
|
+
(assignment) => assignment.lineItemId !== lineItemId
|
|
1100
|
+
)
|
|
1101
|
+
}));
|
|
1102
|
+
}
|
|
1103
|
+
clearItemSplitSeatAssignments(seatId) {
|
|
1104
|
+
return this.setItemSplitSeatAssignments(seatId, []);
|
|
1105
|
+
}
|
|
1106
|
+
setItemSplitSeatLoyalty(seatId, loyaltyId, loyaltyUserName) {
|
|
1107
|
+
return this.updateItemSplitSeat(seatId, (seat) => ({
|
|
1108
|
+
...seat,
|
|
1109
|
+
loyaltyId,
|
|
1110
|
+
loyaltyUserName
|
|
1111
|
+
}));
|
|
1112
|
+
}
|
|
1113
|
+
clearItemSplitSeatLoyalty(seatId) {
|
|
1114
|
+
return this.setItemSplitSeatLoyalty(seatId, null, null);
|
|
1115
|
+
}
|
|
1116
|
+
setItemSplitSeatAppliedDiscountIds(seatId, appliedDiscountIds) {
|
|
1117
|
+
return this.updateItemSplitSeat(seatId, (seat) => ({
|
|
1118
|
+
...seat,
|
|
1119
|
+
appliedDiscountIds: [...appliedDiscountIds]
|
|
1120
|
+
}));
|
|
1121
|
+
}
|
|
1122
|
+
addItemSplitSeatAppliedDiscountId(seatId, discountId) {
|
|
1123
|
+
return this.updateItemSplitSeat(seatId, (seat) => {
|
|
1124
|
+
if (seat.appliedDiscountIds.includes(discountId)) {
|
|
1125
|
+
return seat;
|
|
1126
|
+
}
|
|
1127
|
+
return {
|
|
1128
|
+
...seat,
|
|
1129
|
+
appliedDiscountIds: [...seat.appliedDiscountIds, discountId]
|
|
1130
|
+
};
|
|
1131
|
+
});
|
|
1132
|
+
}
|
|
1133
|
+
removeItemSplitSeatAppliedDiscountId(seatId, discountId) {
|
|
1134
|
+
return this.updateItemSplitSeat(seatId, (seat) => ({
|
|
1135
|
+
...seat,
|
|
1136
|
+
appliedDiscountIds: seat.appliedDiscountIds.filter(
|
|
1137
|
+
(appliedDiscountId) => appliedDiscountId !== discountId
|
|
1138
|
+
)
|
|
1139
|
+
}));
|
|
1140
|
+
}
|
|
1141
|
+
clearItemSplitSeatAppliedDiscountIds(seatId) {
|
|
1142
|
+
return this.setItemSplitSeatAppliedDiscountIds(seatId, []);
|
|
1143
|
+
}
|
|
1144
|
+
getEqualSplitState() {
|
|
1145
|
+
return cloneEqualSplitState(this._equalSplitState);
|
|
1146
|
+
}
|
|
1147
|
+
setEqualSplitState(equalSplitState) {
|
|
1148
|
+
const nextEqualSplitState = cloneEqualSplitState(equalSplitState);
|
|
1149
|
+
validateEqualSplitState(nextEqualSplitState);
|
|
1150
|
+
if (isEqual(this._equalSplitState, nextEqualSplitState)) {
|
|
1151
|
+
return this;
|
|
1152
|
+
}
|
|
1153
|
+
this._equalSplitState = nextEqualSplitState;
|
|
1154
|
+
this.touch();
|
|
1155
|
+
return this;
|
|
1156
|
+
}
|
|
1157
|
+
clearEqualSplitState() {
|
|
1158
|
+
return this.setEqualSplitState(null);
|
|
1159
|
+
}
|
|
1160
|
+
getEqualSplitParts() {
|
|
1161
|
+
return this._equalSplitState ? this._equalSplitState.parts.map(cloneEqualSplitPart) : [];
|
|
1162
|
+
}
|
|
1163
|
+
setEqualSplitParts(parts) {
|
|
1164
|
+
if (parts.length === 0) {
|
|
1165
|
+
return this.clearEqualSplitState();
|
|
1166
|
+
}
|
|
1167
|
+
const equalSplitState = this.getEqualSplitState() ?? createEmptyEqualSplitState();
|
|
1168
|
+
equalSplitState.parts = parts.map(cloneEqualSplitPart);
|
|
1169
|
+
equalSplitState.partCount = parts.length;
|
|
1170
|
+
return this.setEqualSplitState(equalSplitState);
|
|
1171
|
+
}
|
|
1172
|
+
clearEqualSplitParts() {
|
|
1173
|
+
return this.clearEqualSplitState();
|
|
1174
|
+
}
|
|
1175
|
+
addEqualSplitPart(part) {
|
|
1176
|
+
if (this.getEqualSplitPartById(part.id)) {
|
|
1177
|
+
return this;
|
|
1178
|
+
}
|
|
1179
|
+
const parts = this.getEqualSplitParts();
|
|
1180
|
+
parts.push(cloneEqualSplitPart(part));
|
|
1181
|
+
return this.setEqualSplitParts(parts);
|
|
1182
|
+
}
|
|
1183
|
+
updateEqualSplitPart(partId, updater) {
|
|
1184
|
+
const parts = this.getEqualSplitParts();
|
|
1185
|
+
const partIndex = parts.findIndex((part) => part.id === partId);
|
|
1186
|
+
if (partIndex === -1 || !parts[partIndex]) {
|
|
1187
|
+
return this;
|
|
1188
|
+
}
|
|
1189
|
+
parts[partIndex] = cloneEqualSplitPart(updater(cloneEqualSplitPart(parts[partIndex])));
|
|
1190
|
+
return this.setEqualSplitParts(parts);
|
|
1191
|
+
}
|
|
1192
|
+
removeEqualSplitPart(partId) {
|
|
1193
|
+
const parts = this.getEqualSplitParts();
|
|
1194
|
+
const nextParts = parts.filter((part) => part.id !== partId);
|
|
1195
|
+
if (nextParts.length === parts.length) {
|
|
1196
|
+
return this;
|
|
1197
|
+
}
|
|
1198
|
+
return nextParts.length === 0 ? this.clearEqualSplitState() : this.setEqualSplitParts(nextParts);
|
|
1199
|
+
}
|
|
1200
|
+
getEqualSplitPartById(partId) {
|
|
1201
|
+
const part = this._equalSplitState?.parts.find(
|
|
1202
|
+
(equalSplitPart) => equalSplitPart.id === partId
|
|
1203
|
+
);
|
|
1204
|
+
return part ? cloneEqualSplitPart(part) : null;
|
|
1205
|
+
}
|
|
1206
|
+
getEqualSplitPartCount() {
|
|
1207
|
+
return this._equalSplitState?.partCount ?? null;
|
|
1208
|
+
}
|
|
1209
|
+
setEqualSplitPartCount(partCount) {
|
|
1210
|
+
if (partCount === null) {
|
|
1211
|
+
return this.clearEqualSplitState();
|
|
1212
|
+
}
|
|
1213
|
+
if (!Number.isInteger(partCount) || partCount <= 0) {
|
|
1214
|
+
throw new Error("Equal split part count must be a positive integer or null");
|
|
1215
|
+
}
|
|
1216
|
+
const equalSplitState = this.getEqualSplitState() ?? createEmptyEqualSplitState();
|
|
1217
|
+
if (equalSplitState.parts.length > partCount) {
|
|
1218
|
+
throw new Error(
|
|
1219
|
+
"Equal split part count cannot be less than persisted parts length"
|
|
1220
|
+
);
|
|
1221
|
+
}
|
|
1222
|
+
if (equalSplitState.partCount === partCount) {
|
|
1223
|
+
return this;
|
|
1224
|
+
}
|
|
1225
|
+
equalSplitState.partCount = partCount;
|
|
1226
|
+
return this.setEqualSplitState(equalSplitState);
|
|
1227
|
+
}
|
|
1228
|
+
clearEqualSplitPartCount() {
|
|
1229
|
+
return this.clearEqualSplitState();
|
|
1230
|
+
}
|
|
1231
|
+
setEqualSplitPartAppliedDiscountIds(partId, appliedDiscountIds) {
|
|
1232
|
+
return this.updateEqualSplitPart(partId, (part) => ({
|
|
1233
|
+
...part,
|
|
1234
|
+
appliedDiscountIds: [...appliedDiscountIds]
|
|
1235
|
+
}));
|
|
1236
|
+
}
|
|
1237
|
+
addEqualSplitPartAppliedDiscountId(partId, discountId) {
|
|
1238
|
+
return this.updateEqualSplitPart(partId, (part) => {
|
|
1239
|
+
if (part.appliedDiscountIds.includes(discountId)) {
|
|
1240
|
+
return part;
|
|
1241
|
+
}
|
|
1242
|
+
return {
|
|
1243
|
+
...part,
|
|
1244
|
+
appliedDiscountIds: [...part.appliedDiscountIds, discountId]
|
|
1245
|
+
};
|
|
1246
|
+
});
|
|
1247
|
+
}
|
|
1248
|
+
removeEqualSplitPartAppliedDiscountId(partId, discountId) {
|
|
1249
|
+
return this.updateEqualSplitPart(partId, (part) => ({
|
|
1250
|
+
...part,
|
|
1251
|
+
appliedDiscountIds: part.appliedDiscountIds.filter(
|
|
1252
|
+
(appliedDiscountId) => appliedDiscountId !== discountId
|
|
1253
|
+
)
|
|
1254
|
+
}));
|
|
1255
|
+
}
|
|
1256
|
+
clearEqualSplitPartAppliedDiscountIds(partId) {
|
|
1257
|
+
return this.setEqualSplitPartAppliedDiscountIds(partId, []);
|
|
1258
|
+
}
|
|
757
1259
|
updateOrderStatus(newStatus) {
|
|
758
1260
|
if (this.status === newStatus) {
|
|
759
1261
|
return;
|
|
@@ -812,7 +1314,8 @@ var Cart = class _Cart {
|
|
|
812
1314
|
locatorType: this._locatorType,
|
|
813
1315
|
spotNumber: this.spotNumber,
|
|
814
1316
|
comments: this.comments,
|
|
815
|
-
tableService: this._tableService,
|
|
1317
|
+
tableService: cloneTableService(this._tableService),
|
|
1318
|
+
equalSplitState: cloneEqualSplitState(this._equalSplitState),
|
|
816
1319
|
orderRefundStatus: this.orderRefundStatus,
|
|
817
1320
|
items: this._items,
|
|
818
1321
|
status: this.status,
|
|
@@ -841,6 +1344,7 @@ var Cart = class _Cart {
|
|
|
841
1344
|
orderNumber: this.orderNumber,
|
|
842
1345
|
checkoutModel: this.checkoutModel,
|
|
843
1346
|
tableService: this._tableService,
|
|
1347
|
+
equalSplitState: this._equalSplitState,
|
|
844
1348
|
currency: this.currency,
|
|
845
1349
|
business: this._business,
|
|
846
1350
|
staffId: this._staffId,
|
|
@@ -859,7 +1363,8 @@ var Cart = class _Cart {
|
|
|
859
1363
|
newCart._refunds = [...this._refunds];
|
|
860
1364
|
newCart._loyaltyProgramId = this._loyaltyProgramId;
|
|
861
1365
|
newCart._loyaltyTransactionIds = [...this._loyaltyTransactionIds];
|
|
862
|
-
newCart._tableService = this._tableService
|
|
1366
|
+
newCart._tableService = cloneTableService(this._tableService);
|
|
1367
|
+
newCart._equalSplitState = cloneEqualSplitState(this._equalSplitState);
|
|
863
1368
|
newCart._taxSummary = this._taxSummary;
|
|
864
1369
|
newCart._newlyCancelledItemIds = new Set(this._newlyCancelledItemIds);
|
|
865
1370
|
newCart._invoiceCompany = this._invoiceCompany ? { ...this._invoiceCompany } : null;
|
|
@@ -872,6 +1377,16 @@ var Cart = class _Cart {
|
|
|
872
1377
|
currency: this.currency
|
|
873
1378
|
});
|
|
874
1379
|
}
|
|
1380
|
+
createDiscountRecord(discountData, scope, lineItemId, targetType, targetId) {
|
|
1381
|
+
return {
|
|
1382
|
+
...discountData,
|
|
1383
|
+
scope,
|
|
1384
|
+
lineItemId,
|
|
1385
|
+
targetType,
|
|
1386
|
+
targetId,
|
|
1387
|
+
createdAt: dayjs().toDate().toISOString()
|
|
1388
|
+
};
|
|
1389
|
+
}
|
|
875
1390
|
touch() {
|
|
876
1391
|
this.updatedAt = dayjs().toDate();
|
|
877
1392
|
}
|
|
@@ -885,7 +1400,7 @@ var ApplyTo = /* @__PURE__ */ ((ApplyTo2) => {
|
|
|
885
1400
|
})(ApplyTo || {});
|
|
886
1401
|
|
|
887
1402
|
// src/version.ts
|
|
888
|
-
var VERSION = "0.1.
|
|
1403
|
+
var VERSION = "0.1.4";
|
|
889
1404
|
export {
|
|
890
1405
|
ApplyTo,
|
|
891
1406
|
Cart,
|
|
@@ -893,7 +1408,9 @@ export {
|
|
|
893
1408
|
allocateProportionalMinorUnits,
|
|
894
1409
|
calculateCartPriceSnapshot,
|
|
895
1410
|
calculateDiscountAmount,
|
|
1411
|
+
calculateEqualSplitPartPricing,
|
|
896
1412
|
calculateItemPrice,
|
|
1413
|
+
calculateItemSplitSeatPricing,
|
|
897
1414
|
calculateOrderTaxSummary,
|
|
898
1415
|
calculateSequentialDiscountTotal,
|
|
899
1416
|
splitTaxInclusiveAmount
|