@munchi_oy/cart-engine 0.1.0
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/README.md +204 -0
- package/dist/index.cjs +889 -0
- package/dist/index.d.cts +157 -0
- package/dist/index.d.ts +157 -0
- package/dist/index.js +852 -0
- package/package.json +60 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,889 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
ApplyTo: () => ApplyTo,
|
|
34
|
+
Cart: () => Cart,
|
|
35
|
+
VERSION: () => VERSION,
|
|
36
|
+
allocateProportionalMinorUnits: () => allocateProportionalMinorUnits,
|
|
37
|
+
calculateCartPriceSnapshot: () => calculateCartPriceSnapshot,
|
|
38
|
+
calculateDiscountAmount: () => calculateDiscountAmount,
|
|
39
|
+
calculateItemPrice: () => calculateItemPrice,
|
|
40
|
+
calculateOrderTaxSummary: () => calculateOrderTaxSummary,
|
|
41
|
+
calculateSequentialDiscountTotal: () => calculateSequentialDiscountTotal,
|
|
42
|
+
splitTaxInclusiveAmount: () => splitTaxInclusiveAmount
|
|
43
|
+
});
|
|
44
|
+
module.exports = __toCommonJS(index_exports);
|
|
45
|
+
|
|
46
|
+
// src/cart/Cart.ts
|
|
47
|
+
var import_core4 = require("@munchi_oy/core");
|
|
48
|
+
var import_cuid2 = require("@paralleldrive/cuid2");
|
|
49
|
+
var import_dayjs = __toESM(require("dayjs"), 1);
|
|
50
|
+
var import_lodash = __toESM(require("lodash"), 1);
|
|
51
|
+
|
|
52
|
+
// src/pricing/calculator.ts
|
|
53
|
+
var import_core2 = require("@munchi_oy/core");
|
|
54
|
+
|
|
55
|
+
// src/discount/calculator.ts
|
|
56
|
+
var import_core = require("@munchi_oy/core");
|
|
57
|
+
|
|
58
|
+
// src/utils/index.ts
|
|
59
|
+
var withSafeIntegers = (fn, ignoreIndexes = []) => {
|
|
60
|
+
return (...args) => {
|
|
61
|
+
args.forEach((arg, index) => {
|
|
62
|
+
if (typeof arg === "number" && !ignoreIndexes.includes(index) && !Number.isInteger(arg)) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
`[CartEngine SafeLock] Argument at position ${index} MUST be an integer (minor unit). Received: ${arg}`
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
return fn(...args);
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
function mergeDefinedValue(currentValue, nextValue, hasChanges) {
|
|
72
|
+
if (nextValue === void 0 || currentValue === nextValue) {
|
|
73
|
+
return [currentValue, hasChanges];
|
|
74
|
+
}
|
|
75
|
+
return [nextValue, true];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// src/discount/calculator.ts
|
|
79
|
+
var discountStrategies = {
|
|
80
|
+
[import_core.DiscountType.Fixed]: withSafeIntegers(
|
|
81
|
+
(value, totalAmount) => {
|
|
82
|
+
return Math.min(value, totalAmount);
|
|
83
|
+
}
|
|
84
|
+
),
|
|
85
|
+
[import_core.DiscountType.Percentage]: withSafeIntegers(
|
|
86
|
+
(value, totalAmount) => {
|
|
87
|
+
return Math.floor(totalAmount * value / 100);
|
|
88
|
+
},
|
|
89
|
+
[0]
|
|
90
|
+
),
|
|
91
|
+
default: () => 0
|
|
92
|
+
};
|
|
93
|
+
function calculateDiscountAmount(type, value, totalAmountBeforeDiscounts) {
|
|
94
|
+
const strategy = discountStrategies[type] ?? discountStrategies.default;
|
|
95
|
+
return strategy(value, totalAmountBeforeDiscounts);
|
|
96
|
+
}
|
|
97
|
+
function calculateSequentialDiscountTotal(discounts, initialAmount) {
|
|
98
|
+
let remainingAmount = initialAmount;
|
|
99
|
+
let totalDiscountAmount = 0;
|
|
100
|
+
for (const discount of discounts) {
|
|
101
|
+
const discountAmount = Math.min(
|
|
102
|
+
calculateDiscountAmount(discount.type, discount.value, remainingAmount),
|
|
103
|
+
remainingAmount
|
|
104
|
+
);
|
|
105
|
+
totalDiscountAmount += discountAmount;
|
|
106
|
+
remainingAmount -= discountAmount;
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
totalDiscountAmount,
|
|
110
|
+
remainingAmount
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// src/pricing/calculator.ts
|
|
115
|
+
var assertMinorUnit = (value, fieldName) => {
|
|
116
|
+
if (!Number.isInteger(value)) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
`[CartEngine SafeLock] ${fieldName} MUST be an integer (minor unit). Received: ${value}`
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
function calculateItemPrice(details) {
|
|
123
|
+
assertMinorUnit(details.basePriceInMinorUnit, "details.basePriceInMinorUnit");
|
|
124
|
+
assertMinorUnit(details.quantity, "details.quantity");
|
|
125
|
+
const totalOptionsPrice = details.options.reduce((sum, opt) => {
|
|
126
|
+
return sum + opt.suboptions.reduce((subSum, sub) => {
|
|
127
|
+
assertMinorUnit(sub.price.amount, "option.price.amount");
|
|
128
|
+
assertMinorUnit(sub.quantity, "option.quantity");
|
|
129
|
+
return subSum + sub.price.amount * sub.quantity;
|
|
130
|
+
}, 0);
|
|
131
|
+
}, 0);
|
|
132
|
+
const singleUnitPriceAmount = details.basePriceInMinorUnit + totalOptionsPrice;
|
|
133
|
+
const totalAmountBeforeDiscounts = singleUnitPriceAmount * details.quantity;
|
|
134
|
+
let discountAmount = 0;
|
|
135
|
+
if (details.discount) {
|
|
136
|
+
discountAmount = calculateDiscountAmount(
|
|
137
|
+
details.discount.type,
|
|
138
|
+
details.discount.value,
|
|
139
|
+
totalAmountBeforeDiscounts
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
const totalAfterDiscount = totalAmountBeforeDiscounts - discountAmount;
|
|
143
|
+
return {
|
|
144
|
+
unitPrice: { amount: singleUnitPriceAmount, currency: details.currency },
|
|
145
|
+
basePrice: {
|
|
146
|
+
amount: details.basePriceInMinorUnit,
|
|
147
|
+
currency: details.currency
|
|
148
|
+
},
|
|
149
|
+
vatPercentage: details.taxRate,
|
|
150
|
+
total: { amount: totalAfterDiscount, currency: details.currency },
|
|
151
|
+
priceBreakdown: {
|
|
152
|
+
totalBeforeDiscounts: {
|
|
153
|
+
amount: totalAmountBeforeDiscounts,
|
|
154
|
+
currency: details.currency
|
|
155
|
+
},
|
|
156
|
+
totalDiscounts: { amount: discountAmount, currency: details.currency },
|
|
157
|
+
subtotalBasketDiscounts: { amount: 0, currency: details.currency },
|
|
158
|
+
subtotalItemDiscounts: { amount: 0, currency: details.currency },
|
|
159
|
+
basePriceBeforeDiscounts: {
|
|
160
|
+
amount: details.basePriceInMinorUnit * details.quantity,
|
|
161
|
+
currency: details.currency
|
|
162
|
+
},
|
|
163
|
+
unitPriceBeforeDiscounts: {
|
|
164
|
+
amount: singleUnitPriceAmount,
|
|
165
|
+
currency: details.currency
|
|
166
|
+
},
|
|
167
|
+
subtotalOptionsBasketDiscounts: { amount: 0, currency: details.currency },
|
|
168
|
+
subtotalOptionsItemDiscounts: { amount: 0, currency: details.currency }
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
function calculateCartPriceSnapshot(details) {
|
|
173
|
+
const activeItems = details.items.filter(
|
|
174
|
+
(item) => item.kitchenStatus !== import_core2.KitchenStatus.Cancelled
|
|
175
|
+
);
|
|
176
|
+
const totalBeforeDiscountsAmount = activeItems.reduce(
|
|
177
|
+
(sum, item) => sum + (item.itemPrice.priceBreakdown.totalBeforeDiscounts.amount ?? 0),
|
|
178
|
+
0
|
|
179
|
+
);
|
|
180
|
+
const subtotalItemDiscountsAmount = activeItems.reduce(
|
|
181
|
+
(sum, item) => sum + (item.itemPrice.priceBreakdown.totalDiscounts.amount ?? 0),
|
|
182
|
+
0
|
|
183
|
+
);
|
|
184
|
+
const cartDiscounts = details.discounts.filter((discount) => discount.scope === import_core2.DiscountScope.Cart).sort(
|
|
185
|
+
(left, right) => new Date(left.createdAt).getTime() - new Date(right.createdAt).getTime()
|
|
186
|
+
);
|
|
187
|
+
const grossAfterItemDiscounts = Math.max(
|
|
188
|
+
0,
|
|
189
|
+
totalBeforeDiscountsAmount - subtotalItemDiscountsAmount
|
|
190
|
+
);
|
|
191
|
+
const cartDiscountCalculation = calculateSequentialDiscountTotal(
|
|
192
|
+
cartDiscounts,
|
|
193
|
+
grossAfterItemDiscounts
|
|
194
|
+
);
|
|
195
|
+
const totalDiscountsAmount = subtotalItemDiscountsAmount + cartDiscountCalculation.totalDiscountAmount;
|
|
196
|
+
const price = {
|
|
197
|
+
priceBreakdown: {
|
|
198
|
+
totalBeforeDiscounts: {
|
|
199
|
+
amount: totalBeforeDiscountsAmount,
|
|
200
|
+
currency: details.currency
|
|
201
|
+
},
|
|
202
|
+
subtotalItemDiscounts: {
|
|
203
|
+
amount: subtotalItemDiscountsAmount,
|
|
204
|
+
currency: details.currency
|
|
205
|
+
},
|
|
206
|
+
subtotalBasketDiscounts: {
|
|
207
|
+
amount: cartDiscountCalculation.totalDiscountAmount,
|
|
208
|
+
currency: details.currency
|
|
209
|
+
},
|
|
210
|
+
totalDiscounts: {
|
|
211
|
+
amount: totalDiscountsAmount,
|
|
212
|
+
currency: details.currency
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
total: {
|
|
216
|
+
amount: cartDiscountCalculation.remainingAmount,
|
|
217
|
+
currency: details.currency
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
return {
|
|
221
|
+
activeItems,
|
|
222
|
+
totalBeforeDiscountsAmount,
|
|
223
|
+
subtotalItemDiscountsAmount,
|
|
224
|
+
subtotalBasketDiscountsAmount: cartDiscountCalculation.totalDiscountAmount,
|
|
225
|
+
totalDiscountsAmount,
|
|
226
|
+
totalAmount: cartDiscountCalculation.remainingAmount,
|
|
227
|
+
price
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// src/tax/index.ts
|
|
232
|
+
var import_core3 = require("@munchi_oy/core");
|
|
233
|
+
|
|
234
|
+
// src/discount/modulo.ts
|
|
235
|
+
var allocateProportionalMinorUnits = withSafeIntegers(
|
|
236
|
+
(weights, totalAmount) => {
|
|
237
|
+
for (let i = 0; i < weights.length; i++) {
|
|
238
|
+
if (!Number.isInteger(weights[i])) {
|
|
239
|
+
throw new Error(
|
|
240
|
+
`[CartEngine SafeLock] Weight at index ${i} MUST be an integer. Received: ${weights[i]}`
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (weights.length === 0 || totalAmount <= 0) {
|
|
245
|
+
return new Array(weights.length).fill(0);
|
|
246
|
+
}
|
|
247
|
+
const totalWeight = weights.reduce((sum, weight) => sum + weight, 0);
|
|
248
|
+
if (totalWeight <= 0) {
|
|
249
|
+
return new Array(weights.length).fill(0);
|
|
250
|
+
}
|
|
251
|
+
const baseAllocations = weights.map(
|
|
252
|
+
(weight) => Math.floor(weight * totalAmount / totalWeight)
|
|
253
|
+
);
|
|
254
|
+
const remainderOrder = weights.map((weight, index) => ({
|
|
255
|
+
index,
|
|
256
|
+
remainder: weight * totalAmount % totalWeight
|
|
257
|
+
})).sort(
|
|
258
|
+
(left, right) => right.remainder - left.remainder || left.index - right.index
|
|
259
|
+
);
|
|
260
|
+
let remainingAmount = totalAmount - baseAllocations.reduce((sum, amount) => sum + amount, 0);
|
|
261
|
+
for (const { index } of remainderOrder) {
|
|
262
|
+
if (remainingAmount <= 0) break;
|
|
263
|
+
baseAllocations[index] = (baseAllocations[index] ?? 0) + 1;
|
|
264
|
+
remainingAmount -= 1;
|
|
265
|
+
}
|
|
266
|
+
return baseAllocations;
|
|
267
|
+
}
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
// src/tax/index.ts
|
|
271
|
+
function assertMinorUnit2(value, fieldName) {
|
|
272
|
+
if (!Number.isInteger(value)) {
|
|
273
|
+
throw new Error(
|
|
274
|
+
`[CartEngine SafeLock] ${fieldName} MUST be an integer. Received: ${value}`
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
function assertValidTaxRate(value, fieldName) {
|
|
279
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
280
|
+
throw new Error(
|
|
281
|
+
`[CartEngine SafeLock] ${fieldName} MUST be a finite non-negative number. Received: ${value}`
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
function toTaxRateBasisPoints(taxRate) {
|
|
286
|
+
const normalized = taxRate.toString();
|
|
287
|
+
if (!/^\d+(\.\d+)?$/.test(normalized)) {
|
|
288
|
+
throw new Error(
|
|
289
|
+
`[CartEngine SafeLock] taxRate MUST be a decimal number with dot notation. Received: ${taxRate}`
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
const [wholePart, decimalPart = ""] = normalized.split(".");
|
|
293
|
+
const paddedDecimals = `${decimalPart}00`.slice(0, 2);
|
|
294
|
+
return Number(wholePart) * 100 + Number(paddedDecimals);
|
|
295
|
+
}
|
|
296
|
+
function splitTaxInclusiveAmount(totalWithTax, taxRate) {
|
|
297
|
+
assertMinorUnit2(totalWithTax, "totalWithTax");
|
|
298
|
+
assertValidTaxRate(taxRate, "taxRate");
|
|
299
|
+
if (taxRate <= 0) {
|
|
300
|
+
return {
|
|
301
|
+
taxRate,
|
|
302
|
+
totalWithTax,
|
|
303
|
+
totalWithoutTax: totalWithTax,
|
|
304
|
+
totalTax: 0
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
const taxRateBasisPoints = toTaxRateBasisPoints(taxRate);
|
|
308
|
+
const totalWithoutTax = Math.floor(
|
|
309
|
+
totalWithTax * 1e4 / (1e4 + taxRateBasisPoints)
|
|
310
|
+
);
|
|
311
|
+
return {
|
|
312
|
+
taxRate,
|
|
313
|
+
totalWithTax,
|
|
314
|
+
totalWithoutTax,
|
|
315
|
+
totalTax: totalWithTax - totalWithoutTax
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
function calculateOrderTaxSummary(details) {
|
|
319
|
+
const priceSnapshot = calculateCartPriceSnapshot({
|
|
320
|
+
items: details.items,
|
|
321
|
+
discounts: details.discounts,
|
|
322
|
+
currency: details.currency
|
|
323
|
+
});
|
|
324
|
+
const activeItems = details.items.filter(
|
|
325
|
+
(item) => item.kitchenStatus !== import_core3.KitchenStatus.Cancelled
|
|
326
|
+
);
|
|
327
|
+
if (activeItems.length === 0 || priceSnapshot.totalAmount <= 0) {
|
|
328
|
+
return {
|
|
329
|
+
taxSummaries: [],
|
|
330
|
+
totalWithoutTax: 0,
|
|
331
|
+
totalTax: 0,
|
|
332
|
+
grandTotal: 0
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
const basketDiscountAllocations = allocateProportionalMinorUnits(
|
|
336
|
+
activeItems.map((item) => item.itemPrice.total.amount),
|
|
337
|
+
priceSnapshot.subtotalBasketDiscountsAmount
|
|
338
|
+
);
|
|
339
|
+
const taxRateMap = /* @__PURE__ */ new Map();
|
|
340
|
+
activeItems.forEach((item, index) => {
|
|
341
|
+
if (item.taxRate <= 0) {
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
const totalWithTax = Math.max(
|
|
345
|
+
0,
|
|
346
|
+
item.itemPrice.total.amount - (basketDiscountAllocations[index] ?? 0)
|
|
347
|
+
);
|
|
348
|
+
const taxBreakdown = splitTaxInclusiveAmount(totalWithTax, item.taxRate);
|
|
349
|
+
const existing = taxRateMap.get(item.taxRate) ?? {
|
|
350
|
+
totalWithTax: 0,
|
|
351
|
+
totalTax: 0,
|
|
352
|
+
totalWithoutTax: 0
|
|
353
|
+
};
|
|
354
|
+
existing.totalWithTax += taxBreakdown.totalWithTax;
|
|
355
|
+
existing.totalTax += taxBreakdown.totalTax;
|
|
356
|
+
existing.totalWithoutTax += taxBreakdown.totalWithoutTax;
|
|
357
|
+
taxRateMap.set(item.taxRate, existing);
|
|
358
|
+
});
|
|
359
|
+
const taxSummaries = Array.from(taxRateMap.entries()).sort((left, right) => left[0] - right[0]).map(([taxRate, values]) => ({
|
|
360
|
+
taxRate,
|
|
361
|
+
totalWithoutTax: values.totalWithoutTax,
|
|
362
|
+
totalTax: values.totalTax,
|
|
363
|
+
totalWithTax: values.totalWithTax
|
|
364
|
+
}));
|
|
365
|
+
return {
|
|
366
|
+
taxSummaries,
|
|
367
|
+
totalWithoutTax: taxSummaries.reduce(
|
|
368
|
+
(sum, item) => sum + item.totalWithoutTax,
|
|
369
|
+
0
|
|
370
|
+
),
|
|
371
|
+
totalTax: taxSummaries.reduce((sum, item) => sum + item.totalTax, 0),
|
|
372
|
+
grandTotal: priceSnapshot.totalAmount
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// src/cart/Cart.ts
|
|
377
|
+
var { isEqual } = import_lodash.default;
|
|
378
|
+
var Cart = class _Cart {
|
|
379
|
+
id;
|
|
380
|
+
businessId;
|
|
381
|
+
currency;
|
|
382
|
+
createdAt;
|
|
383
|
+
orderNumber;
|
|
384
|
+
orderType;
|
|
385
|
+
orderFormat;
|
|
386
|
+
spotNumber;
|
|
387
|
+
comments;
|
|
388
|
+
orderRefundStatus;
|
|
389
|
+
updatedAt;
|
|
390
|
+
status;
|
|
391
|
+
_loyaltyTransactionIds;
|
|
392
|
+
_loyaltyProgramId;
|
|
393
|
+
_items = [];
|
|
394
|
+
_locatorType;
|
|
395
|
+
_business;
|
|
396
|
+
_discounts = [];
|
|
397
|
+
_customer = null;
|
|
398
|
+
_refunds = [];
|
|
399
|
+
_taxSummary;
|
|
400
|
+
_newlyCancelledItemIds;
|
|
401
|
+
_invoiceCompany;
|
|
402
|
+
_staffId;
|
|
403
|
+
_shiftId;
|
|
404
|
+
version = "1.0.2";
|
|
405
|
+
constructor(id, createdAt, options) {
|
|
406
|
+
this.id = id;
|
|
407
|
+
this.createdAt = createdAt;
|
|
408
|
+
this.businessId = options.businessId;
|
|
409
|
+
this.orderType = options.orderType;
|
|
410
|
+
this.orderNumber = options.orderNumber;
|
|
411
|
+
this._business = options.business;
|
|
412
|
+
this.currency = options.currency;
|
|
413
|
+
this.updatedAt = createdAt;
|
|
414
|
+
this.spotNumber = null;
|
|
415
|
+
this.comments = null;
|
|
416
|
+
this._loyaltyTransactionIds = [];
|
|
417
|
+
this._loyaltyProgramId = null;
|
|
418
|
+
this.orderRefundStatus = import_core4.OrderRefundStatus.None;
|
|
419
|
+
this.status = import_core4.OrderStatusEnum.Draft;
|
|
420
|
+
this.orderFormat = import_core4.OrderFormat.Instant;
|
|
421
|
+
this._locatorType = import_core4.LocatorType.Table;
|
|
422
|
+
this._newlyCancelledItemIds = /* @__PURE__ */ new Set();
|
|
423
|
+
this._staffId = options.staffId ?? null;
|
|
424
|
+
this._shiftId = options.shiftId ?? null;
|
|
425
|
+
this._invoiceCompany = null;
|
|
426
|
+
}
|
|
427
|
+
static create(options) {
|
|
428
|
+
return new _Cart((0, import_cuid2.createId)(), options.createdAt ?? (0, import_dayjs.default)().toDate(), {
|
|
429
|
+
businessId: options.businessId,
|
|
430
|
+
orderType: options.orderType ?? import_core4.OrderTypePOS.DineIn,
|
|
431
|
+
orderNumber: options.orderNumber,
|
|
432
|
+
currency: options.currency,
|
|
433
|
+
business: options.business,
|
|
434
|
+
staffId: options.staffId,
|
|
435
|
+
shiftId: options.shiftId
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
static fromData(orderDto) {
|
|
439
|
+
const cart = new _Cart(orderDto.id, (0, import_dayjs.default)(orderDto.createdAt).toDate(), {
|
|
440
|
+
businessId: orderDto.businessId,
|
|
441
|
+
orderType: orderDto.orderType,
|
|
442
|
+
orderNumber: orderDto.orderNumber,
|
|
443
|
+
currency: orderDto.currency,
|
|
444
|
+
business: orderDto.business,
|
|
445
|
+
staffId: orderDto.staffId ?? null,
|
|
446
|
+
shiftId: orderDto.shiftId ?? null
|
|
447
|
+
});
|
|
448
|
+
cart.updatedAt = (0, import_dayjs.default)(orderDto.updatedAt).toDate();
|
|
449
|
+
cart.spotNumber = orderDto.spotNumber;
|
|
450
|
+
cart.comments = orderDto.comments;
|
|
451
|
+
cart.orderRefundStatus = orderDto.orderRefundStatus;
|
|
452
|
+
cart._locatorType = orderDto.locatorType;
|
|
453
|
+
cart._items = [...orderDto.items];
|
|
454
|
+
cart.status = orderDto.status;
|
|
455
|
+
cart.orderFormat = orderDto.orderFormat ?? import_core4.OrderFormat.Instant;
|
|
456
|
+
cart._discounts = (orderDto.discounts ?? []).map((discount) => ({
|
|
457
|
+
...discount,
|
|
458
|
+
createdAt: (0, import_dayjs.default)(discount.createdAt).toDate().toISOString()
|
|
459
|
+
}));
|
|
460
|
+
cart._customer = orderDto.customer ? { ...orderDto.customer } : null;
|
|
461
|
+
cart._refunds = orderDto.refunds ? [...orderDto.refunds] : [];
|
|
462
|
+
cart._loyaltyTransactionIds = [...orderDto.loyaltyTransactionIds];
|
|
463
|
+
cart._loyaltyProgramId = orderDto.loyaltyProgramId ?? null;
|
|
464
|
+
cart._newlyCancelledItemIds = /* @__PURE__ */ new Set();
|
|
465
|
+
cart._taxSummary = orderDto.taxSummary;
|
|
466
|
+
cart._invoiceCompany = orderDto.invoiceCompany ?? null;
|
|
467
|
+
return cart;
|
|
468
|
+
}
|
|
469
|
+
get items() {
|
|
470
|
+
return this._items;
|
|
471
|
+
}
|
|
472
|
+
get locatorType() {
|
|
473
|
+
return this._locatorType;
|
|
474
|
+
}
|
|
475
|
+
get staffId() {
|
|
476
|
+
return this._staffId;
|
|
477
|
+
}
|
|
478
|
+
get shiftId() {
|
|
479
|
+
return this._shiftId;
|
|
480
|
+
}
|
|
481
|
+
get discounts() {
|
|
482
|
+
return this._discounts;
|
|
483
|
+
}
|
|
484
|
+
get customer() {
|
|
485
|
+
return this._customer;
|
|
486
|
+
}
|
|
487
|
+
get refunds() {
|
|
488
|
+
return this._refunds;
|
|
489
|
+
}
|
|
490
|
+
get loyaltyProgramId() {
|
|
491
|
+
return this._loyaltyProgramId;
|
|
492
|
+
}
|
|
493
|
+
get loyaltyTransactionIds() {
|
|
494
|
+
return this._loyaltyTransactionIds;
|
|
495
|
+
}
|
|
496
|
+
get invoiceCompany() {
|
|
497
|
+
return this._invoiceCompany;
|
|
498
|
+
}
|
|
499
|
+
get price() {
|
|
500
|
+
return calculateCartPriceSnapshot({
|
|
501
|
+
items: this._items,
|
|
502
|
+
discounts: this._discounts,
|
|
503
|
+
currency: this.currency
|
|
504
|
+
}).price;
|
|
505
|
+
}
|
|
506
|
+
setInvoiceCompany(company) {
|
|
507
|
+
if (this._invoiceCompany?.id === company?.id) {
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
this._invoiceCompany = company;
|
|
511
|
+
this.touch();
|
|
512
|
+
}
|
|
513
|
+
addItem(itemData, requiresPrep = false) {
|
|
514
|
+
const lockedStatuses = [
|
|
515
|
+
import_core4.KitchenStatus.DispatchedToKitchen,
|
|
516
|
+
import_core4.KitchenStatus.SentToOm,
|
|
517
|
+
import_core4.KitchenStatus.Completed,
|
|
518
|
+
import_core4.KitchenStatus.Cancelled
|
|
519
|
+
];
|
|
520
|
+
const existingModifiableItem = this._items.find(
|
|
521
|
+
(item) => item.posId === itemData.posId && isEqual(item.options, itemData.options) && item.comments === itemData.comments && !lockedStatuses.includes(item.kitchenStatus) && item.discount === null
|
|
522
|
+
);
|
|
523
|
+
if (existingModifiableItem) {
|
|
524
|
+
this._items = this._items.map((item) => {
|
|
525
|
+
if (item.lineItemId !== existingModifiableItem.lineItemId) {
|
|
526
|
+
return item;
|
|
527
|
+
}
|
|
528
|
+
const quantity = item.quantity + itemData.quantity;
|
|
529
|
+
const itemPrice = calculateItemPrice({
|
|
530
|
+
basePriceInMinorUnit: item.basePrice.amount,
|
|
531
|
+
quantity,
|
|
532
|
+
taxRate: item.taxRate,
|
|
533
|
+
options: item.options,
|
|
534
|
+
currency: this.currency
|
|
535
|
+
});
|
|
536
|
+
return {
|
|
537
|
+
...item,
|
|
538
|
+
quantity,
|
|
539
|
+
itemPrice
|
|
540
|
+
};
|
|
541
|
+
});
|
|
542
|
+
this.touch();
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
this._items.push({
|
|
546
|
+
...itemData,
|
|
547
|
+
lineItemId: (0, import_cuid2.createId)(),
|
|
548
|
+
kitchenStatus: requiresPrep ? import_core4.KitchenStatus.Pending : import_core4.KitchenStatus.NotApplicable,
|
|
549
|
+
paymentStatus: import_core4.PosPaymentStatus.Unpaid,
|
|
550
|
+
discount: null
|
|
551
|
+
});
|
|
552
|
+
this.touch();
|
|
553
|
+
}
|
|
554
|
+
updateItem(lineItemId, updatedItem) {
|
|
555
|
+
const itemIndex = this._items.findIndex(
|
|
556
|
+
(item) => item.lineItemId === lineItemId
|
|
557
|
+
);
|
|
558
|
+
if (itemIndex === -1) {
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
this._items[itemIndex] = updatedItem;
|
|
562
|
+
this.touch();
|
|
563
|
+
}
|
|
564
|
+
removeItem(lineItemId) {
|
|
565
|
+
this._items = this._items.filter((item) => item.lineItemId !== lineItemId);
|
|
566
|
+
this.touch();
|
|
567
|
+
}
|
|
568
|
+
updateItemStatus(lineItemId, newStatus) {
|
|
569
|
+
const itemIndex = this._items.findIndex(
|
|
570
|
+
(item2) => item2.lineItemId === lineItemId
|
|
571
|
+
);
|
|
572
|
+
if (itemIndex === -1 || !this._items[itemIndex]) {
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
const item = this._items[itemIndex];
|
|
576
|
+
const isKitchenStatus = Object.values(import_core4.KitchenStatus).includes(
|
|
577
|
+
newStatus
|
|
578
|
+
);
|
|
579
|
+
if (isKitchenStatus && newStatus === import_core4.KitchenStatus.Cancelled && item.kitchenStatus !== import_core4.KitchenStatus.Cancelled) {
|
|
580
|
+
this._newlyCancelledItemIds.add(lineItemId);
|
|
581
|
+
}
|
|
582
|
+
this._items = this._items.map((currentItem) => {
|
|
583
|
+
if (currentItem.lineItemId !== lineItemId) {
|
|
584
|
+
return currentItem;
|
|
585
|
+
}
|
|
586
|
+
if (isKitchenStatus) {
|
|
587
|
+
return {
|
|
588
|
+
...currentItem,
|
|
589
|
+
kitchenStatus: newStatus
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
return {
|
|
593
|
+
...currentItem,
|
|
594
|
+
paymentStatus: newStatus
|
|
595
|
+
};
|
|
596
|
+
});
|
|
597
|
+
this.touch();
|
|
598
|
+
}
|
|
599
|
+
getNewlyCancelledItemIds() {
|
|
600
|
+
return Array.from(this._newlyCancelledItemIds);
|
|
601
|
+
}
|
|
602
|
+
clearCancellationTracking() {
|
|
603
|
+
this._newlyCancelledItemIds.clear();
|
|
604
|
+
}
|
|
605
|
+
applyDiscountToCart(discountData) {
|
|
606
|
+
this._discounts.push({
|
|
607
|
+
...discountData,
|
|
608
|
+
scope: import_core4.DiscountScope.Cart,
|
|
609
|
+
lineItemId: null,
|
|
610
|
+
createdAt: (0, import_dayjs.default)().toDate().toISOString()
|
|
611
|
+
});
|
|
612
|
+
this.touch();
|
|
613
|
+
}
|
|
614
|
+
applyDiscountToItem(lineItemId, discountData) {
|
|
615
|
+
const newDiscount = {
|
|
616
|
+
...discountData,
|
|
617
|
+
scope: import_core4.DiscountScope.Item,
|
|
618
|
+
lineItemId,
|
|
619
|
+
createdAt: (0, import_dayjs.default)().toDate().toISOString()
|
|
620
|
+
};
|
|
621
|
+
const existingDiscountIndex = this._discounts.findIndex(
|
|
622
|
+
(discount) => discount.lineItemId === lineItemId && discount.scope === import_core4.DiscountScope.Item
|
|
623
|
+
);
|
|
624
|
+
if (existingDiscountIndex !== -1) {
|
|
625
|
+
this._discounts[existingDiscountIndex] = newDiscount;
|
|
626
|
+
} else {
|
|
627
|
+
this._discounts.push(newDiscount);
|
|
628
|
+
}
|
|
629
|
+
this._items = this._items.map((item) => {
|
|
630
|
+
if (item.lineItemId !== lineItemId) {
|
|
631
|
+
return item;
|
|
632
|
+
}
|
|
633
|
+
return {
|
|
634
|
+
...item,
|
|
635
|
+
itemPrice: calculateItemPrice({
|
|
636
|
+
basePriceInMinorUnit: item.basePrice.amount,
|
|
637
|
+
quantity: item.quantity,
|
|
638
|
+
taxRate: item.taxRate,
|
|
639
|
+
options: item.options,
|
|
640
|
+
currency: this.currency,
|
|
641
|
+
discount: {
|
|
642
|
+
type: newDiscount.type,
|
|
643
|
+
value: newDiscount.value
|
|
644
|
+
}
|
|
645
|
+
}),
|
|
646
|
+
discount: newDiscount
|
|
647
|
+
};
|
|
648
|
+
});
|
|
649
|
+
this.touch();
|
|
650
|
+
}
|
|
651
|
+
removeItemDiscount(lineItemId) {
|
|
652
|
+
this._items = this._items.map((item) => {
|
|
653
|
+
if (item.lineItemId !== lineItemId) {
|
|
654
|
+
return item;
|
|
655
|
+
}
|
|
656
|
+
return {
|
|
657
|
+
...item,
|
|
658
|
+
itemPrice: calculateItemPrice({
|
|
659
|
+
basePriceInMinorUnit: item.basePrice.amount,
|
|
660
|
+
quantity: item.quantity,
|
|
661
|
+
taxRate: item.taxRate,
|
|
662
|
+
options: item.options,
|
|
663
|
+
currency: this.currency
|
|
664
|
+
}),
|
|
665
|
+
discount: null
|
|
666
|
+
};
|
|
667
|
+
});
|
|
668
|
+
this._discounts = this._discounts.filter(
|
|
669
|
+
(discount) => discount.lineItemId !== lineItemId || discount.scope !== import_core4.DiscountScope.Item
|
|
670
|
+
);
|
|
671
|
+
this.touch();
|
|
672
|
+
}
|
|
673
|
+
removeDiscount(discountId) {
|
|
674
|
+
this._discounts = this._discounts.filter(
|
|
675
|
+
(discount) => discount.id !== discountId
|
|
676
|
+
);
|
|
677
|
+
this.touch();
|
|
678
|
+
}
|
|
679
|
+
setCustomer(customer) {
|
|
680
|
+
if (this._customer?.id === customer?.id) {
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
this._customer = customer;
|
|
684
|
+
this.touch();
|
|
685
|
+
}
|
|
686
|
+
updateDetails(details) {
|
|
687
|
+
let hasChanges = false;
|
|
688
|
+
[this.orderNumber, hasChanges] = mergeDefinedValue(
|
|
689
|
+
this.orderNumber,
|
|
690
|
+
details.orderNumber,
|
|
691
|
+
hasChanges
|
|
692
|
+
);
|
|
693
|
+
[this.spotNumber, hasChanges] = mergeDefinedValue(
|
|
694
|
+
this.spotNumber,
|
|
695
|
+
details.spotNumber,
|
|
696
|
+
hasChanges
|
|
697
|
+
);
|
|
698
|
+
[this.orderType, hasChanges] = mergeDefinedValue(
|
|
699
|
+
this.orderType,
|
|
700
|
+
details.orderType,
|
|
701
|
+
hasChanges
|
|
702
|
+
);
|
|
703
|
+
[this.comments, hasChanges] = mergeDefinedValue(
|
|
704
|
+
this.comments,
|
|
705
|
+
details.comments,
|
|
706
|
+
hasChanges
|
|
707
|
+
);
|
|
708
|
+
[this._locatorType, hasChanges] = mergeDefinedValue(
|
|
709
|
+
this._locatorType,
|
|
710
|
+
details._locatorType,
|
|
711
|
+
hasChanges
|
|
712
|
+
);
|
|
713
|
+
if (hasChanges) {
|
|
714
|
+
this.touch();
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
setComment(details) {
|
|
718
|
+
if (details.scope === "order") {
|
|
719
|
+
if (this.comments === details.comment) {
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
this.comments = details.comment;
|
|
723
|
+
this.touch();
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
if (!details.lineItemId) {
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
let hasChanges = false;
|
|
730
|
+
this._items = this._items.map((item) => {
|
|
731
|
+
if (item.lineItemId !== details.lineItemId || item.comments === details.comment) {
|
|
732
|
+
return item;
|
|
733
|
+
}
|
|
734
|
+
hasChanges = true;
|
|
735
|
+
return {
|
|
736
|
+
...item,
|
|
737
|
+
comments: details.comment
|
|
738
|
+
};
|
|
739
|
+
});
|
|
740
|
+
if (hasChanges) {
|
|
741
|
+
this.touch();
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
setStaffContext(staffId, shiftId) {
|
|
745
|
+
if (this._staffId === staffId && this._shiftId === shiftId) {
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
this._staffId = staffId;
|
|
749
|
+
this._shiftId = shiftId;
|
|
750
|
+
this.touch();
|
|
751
|
+
}
|
|
752
|
+
updateOrderStatus(newStatus) {
|
|
753
|
+
if (this.status === newStatus) {
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
this.status = newStatus;
|
|
757
|
+
this.touch();
|
|
758
|
+
}
|
|
759
|
+
updateOrderFormat(newFormat) {
|
|
760
|
+
if (this.orderFormat === newFormat) {
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
this.orderFormat = newFormat;
|
|
764
|
+
this.touch();
|
|
765
|
+
}
|
|
766
|
+
updateLoyaltyProgramId(id) {
|
|
767
|
+
if (this._loyaltyProgramId === id) {
|
|
768
|
+
return;
|
|
769
|
+
}
|
|
770
|
+
this._loyaltyProgramId = id;
|
|
771
|
+
this.touch();
|
|
772
|
+
}
|
|
773
|
+
updateLoyaltyTransactionId(id) {
|
|
774
|
+
if (!id || this._loyaltyTransactionIds.includes(id)) {
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
this._loyaltyTransactionIds.push(id);
|
|
778
|
+
this.touch();
|
|
779
|
+
}
|
|
780
|
+
setLoyaltyContext(customer, loyaltyProgramId) {
|
|
781
|
+
let hasChanges = false;
|
|
782
|
+
if (this._customer?.id !== customer?.id) {
|
|
783
|
+
this._customer = customer;
|
|
784
|
+
hasChanges = true;
|
|
785
|
+
}
|
|
786
|
+
if (this._loyaltyProgramId !== loyaltyProgramId) {
|
|
787
|
+
this._loyaltyProgramId = loyaltyProgramId;
|
|
788
|
+
hasChanges = true;
|
|
789
|
+
}
|
|
790
|
+
if (hasChanges) {
|
|
791
|
+
this.touch();
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
toJSON(paymentEvents = []) {
|
|
795
|
+
return {
|
|
796
|
+
id: this.id,
|
|
797
|
+
businessId: this.businessId,
|
|
798
|
+
business: this._business,
|
|
799
|
+
currency: this.currency,
|
|
800
|
+
createdAt: this.createdAt.toISOString(),
|
|
801
|
+
updatedAt: this.updatedAt.toISOString(),
|
|
802
|
+
orderNumber: this.orderNumber,
|
|
803
|
+
orderFormat: this.orderFormat,
|
|
804
|
+
orderType: this.orderType,
|
|
805
|
+
locatorType: this._locatorType,
|
|
806
|
+
spotNumber: this.spotNumber,
|
|
807
|
+
comments: this.comments,
|
|
808
|
+
orderRefundStatus: this.orderRefundStatus,
|
|
809
|
+
items: this._items,
|
|
810
|
+
status: this.status,
|
|
811
|
+
discounts: this._discounts.map((discount) => ({
|
|
812
|
+
...discount,
|
|
813
|
+
createdAt: discount.createdAt
|
|
814
|
+
})),
|
|
815
|
+
customer: this._customer,
|
|
816
|
+
refunds: this._refunds,
|
|
817
|
+
basketPrice: this.price,
|
|
818
|
+
paymentEvents,
|
|
819
|
+
loyaltyTransactionIds: this._loyaltyTransactionIds,
|
|
820
|
+
loyaltyProgramId: this._loyaltyProgramId,
|
|
821
|
+
taxSummary: this.calculateOrderTaxSummary(),
|
|
822
|
+
version: this.version,
|
|
823
|
+
staffId: this._staffId,
|
|
824
|
+
shiftId: this._shiftId,
|
|
825
|
+
invoiceCompany: this._invoiceCompany
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
clone() {
|
|
829
|
+
const newCart = new _Cart(this.id, this.createdAt, {
|
|
830
|
+
businessId: this.businessId,
|
|
831
|
+
orderType: this.orderType,
|
|
832
|
+
orderNumber: this.orderNumber,
|
|
833
|
+
currency: this.currency,
|
|
834
|
+
business: this._business,
|
|
835
|
+
staffId: this._staffId,
|
|
836
|
+
shiftId: this._shiftId
|
|
837
|
+
});
|
|
838
|
+
newCart.updatedAt = this.updatedAt;
|
|
839
|
+
newCart.spotNumber = this.spotNumber;
|
|
840
|
+
newCart.comments = this.comments;
|
|
841
|
+
newCart.orderRefundStatus = this.orderRefundStatus;
|
|
842
|
+
newCart._locatorType = this._locatorType;
|
|
843
|
+
newCart._items = [...this._items];
|
|
844
|
+
newCart.status = this.status;
|
|
845
|
+
newCart.orderFormat = this.orderFormat;
|
|
846
|
+
newCart._discounts = [...this._discounts];
|
|
847
|
+
newCart._customer = this._customer ? { ...this._customer } : null;
|
|
848
|
+
newCart._refunds = [...this._refunds];
|
|
849
|
+
newCart._loyaltyProgramId = this._loyaltyProgramId;
|
|
850
|
+
newCart._loyaltyTransactionIds = [...this._loyaltyTransactionIds];
|
|
851
|
+
newCart._taxSummary = this._taxSummary;
|
|
852
|
+
newCart._newlyCancelledItemIds = new Set(this._newlyCancelledItemIds);
|
|
853
|
+
newCart._invoiceCompany = this._invoiceCompany ? { ...this._invoiceCompany } : null;
|
|
854
|
+
return newCart;
|
|
855
|
+
}
|
|
856
|
+
calculateOrderTaxSummary() {
|
|
857
|
+
return calculateOrderTaxSummary({
|
|
858
|
+
items: this._items,
|
|
859
|
+
discounts: this._discounts,
|
|
860
|
+
currency: this.currency
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
touch() {
|
|
864
|
+
this.updatedAt = (0, import_dayjs.default)().toDate();
|
|
865
|
+
}
|
|
866
|
+
};
|
|
867
|
+
|
|
868
|
+
// src/types/index.ts
|
|
869
|
+
var ApplyTo = /* @__PURE__ */ ((ApplyTo2) => {
|
|
870
|
+
ApplyTo2["Order"] = "order";
|
|
871
|
+
ApplyTo2["Item"] = "item";
|
|
872
|
+
return ApplyTo2;
|
|
873
|
+
})(ApplyTo || {});
|
|
874
|
+
|
|
875
|
+
// src/version.ts
|
|
876
|
+
var VERSION = "0.1.0";
|
|
877
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
878
|
+
0 && (module.exports = {
|
|
879
|
+
ApplyTo,
|
|
880
|
+
Cart,
|
|
881
|
+
VERSION,
|
|
882
|
+
allocateProportionalMinorUnits,
|
|
883
|
+
calculateCartPriceSnapshot,
|
|
884
|
+
calculateDiscountAmount,
|
|
885
|
+
calculateItemPrice,
|
|
886
|
+
calculateOrderTaxSummary,
|
|
887
|
+
calculateSequentialDiscountTotal,
|
|
888
|
+
splitTaxInclusiveAmount
|
|
889
|
+
});
|