@munchi_oy/cart-engine 0.1.2 → 0.1.5

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 CHANGED
@@ -36,7 +36,9 @@ __export(index_exports, {
36
36
  allocateProportionalMinorUnits: () => allocateProportionalMinorUnits,
37
37
  calculateCartPriceSnapshot: () => calculateCartPriceSnapshot,
38
38
  calculateDiscountAmount: () => calculateDiscountAmount,
39
+ calculateEqualSplitPartPricing: () => calculateEqualSplitPartPricing,
39
40
  calculateItemPrice: () => calculateItemPrice,
41
+ calculateItemSplitSeatPricing: () => calculateItemSplitSeatPricing,
40
42
  calculateOrderTaxSummary: () => calculateOrderTaxSummary,
41
43
  calculateSequentialDiscountTotal: () => calculateSequentialDiscountTotal,
42
44
  splitTaxInclusiveAmount: () => splitTaxInclusiveAmount
@@ -111,7 +113,49 @@ function calculateSequentialDiscountTotal(discounts, initialAmount) {
111
113
  };
112
114
  }
113
115
 
116
+ // src/discount/modulo.ts
117
+ var allocateProportionalMinorUnits = withSafeIntegers(
118
+ (weights, totalAmount) => {
119
+ for (let i = 0; i < weights.length; i++) {
120
+ if (!Number.isInteger(weights[i])) {
121
+ throw new Error(
122
+ `[CartEngine SafeLock] Weight at index ${i} MUST be an integer. Received: ${weights[i]}`
123
+ );
124
+ }
125
+ }
126
+ if (weights.length === 0 || totalAmount <= 0) {
127
+ return new Array(weights.length).fill(0);
128
+ }
129
+ const totalWeight = weights.reduce((sum, weight) => sum + weight, 0);
130
+ if (totalWeight <= 0) {
131
+ return new Array(weights.length).fill(0);
132
+ }
133
+ const baseAllocations = weights.map(
134
+ (weight) => Math.floor(weight * totalAmount / totalWeight)
135
+ );
136
+ const remainderOrder = weights.map((weight, index) => ({
137
+ index,
138
+ remainder: weight * totalAmount % totalWeight
139
+ })).sort(
140
+ (left, right) => right.remainder - left.remainder || left.index - right.index
141
+ );
142
+ let remainingAmount = totalAmount - baseAllocations.reduce((sum, amount) => sum + amount, 0);
143
+ for (const { index } of remainderOrder) {
144
+ if (remainingAmount <= 0) break;
145
+ baseAllocations[index] = (baseAllocations[index] ?? 0) + 1;
146
+ remainingAmount -= 1;
147
+ }
148
+ return baseAllocations;
149
+ }
150
+ );
151
+
114
152
  // src/pricing/calculator.ts
153
+ var EMPTY_SPLIT_PRICING_RESULT = {
154
+ targets: [],
155
+ totalGrossAmount: 0,
156
+ totalDiscountAmount: 0,
157
+ totalNetAmount: 0
158
+ };
115
159
  var assertMinorUnit = (value, fieldName) => {
116
160
  if (!Number.isInteger(value)) {
117
161
  throw new Error(
@@ -119,6 +163,93 @@ var assertMinorUnit = (value, fieldName) => {
119
163
  );
120
164
  }
121
165
  };
166
+ var sortDiscountsByCreatedAt = (discounts) => {
167
+ return [...discounts].sort(
168
+ (left, right) => new Date(left.createdAt).getTime() - new Date(right.createdAt).getTime()
169
+ );
170
+ };
171
+ var calculateTargetPricing = (id, grossAmount, discounts) => {
172
+ assertMinorUnit(grossAmount, `target gross amount (${id})`);
173
+ const { totalDiscountAmount, remainingAmount } = calculateSequentialDiscountTotal(discounts, grossAmount);
174
+ return {
175
+ id,
176
+ grossAmount,
177
+ discountAmount: totalDiscountAmount,
178
+ netAmount: remainingAmount
179
+ };
180
+ };
181
+ var sumAmounts = (targets, field) => {
182
+ return targets.reduce((sum, target) => sum + target[field], 0);
183
+ };
184
+ var buildSplitPricingResult = (targets) => {
185
+ return {
186
+ targets,
187
+ totalGrossAmount: sumAmounts(targets, "grossAmount"),
188
+ totalDiscountAmount: sumAmounts(targets, "discountAmount"),
189
+ totalNetAmount: sumAmounts(targets, "netAmount")
190
+ };
191
+ };
192
+ var filterTargetedDiscounts = (discounts, targetType, targetId) => {
193
+ return sortDiscountsByCreatedAt(
194
+ discounts.filter(
195
+ (discount) => discount.targetType === targetType && discount.targetId === targetId
196
+ )
197
+ );
198
+ };
199
+ var getSeatGrossAmount = (seat) => {
200
+ return seat.assignments.reduce((sum, assignment) => {
201
+ assertMinorUnit(assignment.amount, `seat assignment amount (${seat.id})`);
202
+ return sum + assignment.amount;
203
+ }, 0);
204
+ };
205
+ var getEqualSplitPartIds = (equalSplitState) => {
206
+ const partCount = Math.max(equalSplitState.partCount, equalSplitState.parts.length);
207
+ const ids = equalSplitState.parts.map((part) => part.id);
208
+ for (let index = ids.length; index < partCount; index += 1) {
209
+ ids.push(`__generated_equal_split_part_${index + 1}`);
210
+ }
211
+ return ids;
212
+ };
213
+ function calculateItemSplitSeatPricing(details) {
214
+ if (!details.tableService) {
215
+ return EMPTY_SPLIT_PRICING_RESULT;
216
+ }
217
+ const targets = details.tableService.itemSplitSeats.map(
218
+ (seat) => calculateTargetPricing(
219
+ seat.id,
220
+ getSeatGrossAmount(seat),
221
+ filterTargetedDiscounts(
222
+ details.discounts,
223
+ import_core2.PosDiscountTargetType.ItemSplitSeat,
224
+ seat.id
225
+ )
226
+ )
227
+ );
228
+ return buildSplitPricingResult(targets);
229
+ }
230
+ function calculateEqualSplitPartPricing(details) {
231
+ assertMinorUnit(details.totalAmount, "details.totalAmount");
232
+ if (!details.equalSplitState || details.equalSplitState.partCount <= 0) {
233
+ return EMPTY_SPLIT_PRICING_RESULT;
234
+ }
235
+ const partIds = getEqualSplitPartIds(details.equalSplitState);
236
+ const grossAmounts = allocateProportionalMinorUnits(
237
+ new Array(partIds.length).fill(1),
238
+ details.totalAmount
239
+ );
240
+ const targets = partIds.map(
241
+ (partId, index) => calculateTargetPricing(
242
+ partId,
243
+ grossAmounts[index] ?? 0,
244
+ filterTargetedDiscounts(
245
+ details.discounts,
246
+ import_core2.PosDiscountTargetType.EqualSplitPart,
247
+ partId
248
+ )
249
+ )
250
+ );
251
+ return buildSplitPricingResult(targets);
252
+ }
122
253
  function calculateItemPrice(details) {
123
254
  assertMinorUnit(details.basePriceInMinorUnit, "details.basePriceInMinorUnit");
124
255
  assertMinorUnit(details.quantity, "details.quantity");
@@ -181,7 +312,9 @@ function calculateCartPriceSnapshot(details) {
181
312
  (sum, item) => sum + (item.itemPrice.priceBreakdown.totalDiscounts.amount ?? 0),
182
313
  0
183
314
  );
184
- const cartDiscounts = details.discounts.filter((discount) => discount.scope === import_core2.DiscountScope.Cart).sort(
315
+ const cartDiscounts = details.discounts.filter(
316
+ (discount) => discount.scope === import_core2.DiscountScope.Cart && (discount.targetType === null || discount.targetType === void 0 || discount.targetType === import_core2.PosDiscountTargetType.Order)
317
+ ).sort(
185
318
  (left, right) => new Date(left.createdAt).getTime() - new Date(right.createdAt).getTime()
186
319
  );
187
320
  const grossAfterItemDiscounts = Math.max(
@@ -192,7 +325,21 @@ function calculateCartPriceSnapshot(details) {
192
325
  cartDiscounts,
193
326
  grossAfterItemDiscounts
194
327
  );
195
- const totalDiscountsAmount = subtotalItemDiscountsAmount + cartDiscountCalculation.totalDiscountAmount;
328
+ const itemSplitSeatPricing = details.checkoutModel === import_core2.CheckoutModelEnum.ItemSplit ? calculateItemSplitSeatPricing({
329
+ tableService: details.tableService,
330
+ discounts: details.discounts
331
+ }) : null;
332
+ const equalSplitPartPricing = details.checkoutModel === import_core2.CheckoutModelEnum.EqualSplit ? calculateEqualSplitPartPricing({
333
+ totalAmount: cartDiscountCalculation.remainingAmount,
334
+ equalSplitState: details.equalSplitState,
335
+ discounts: details.discounts
336
+ }) : null;
337
+ const subtotalTargetedDiscountsAmount = itemSplitSeatPricing?.totalDiscountAmount ?? equalSplitPartPricing?.totalDiscountAmount ?? 0;
338
+ const totalAmount = Math.max(
339
+ 0,
340
+ cartDiscountCalculation.remainingAmount - subtotalTargetedDiscountsAmount
341
+ );
342
+ const totalDiscountsAmount = subtotalItemDiscountsAmount + cartDiscountCalculation.totalDiscountAmount + subtotalTargetedDiscountsAmount;
196
343
  const price = {
197
344
  priceBreakdown: {
198
345
  totalBeforeDiscounts: {
@@ -213,7 +360,7 @@ function calculateCartPriceSnapshot(details) {
213
360
  }
214
361
  },
215
362
  total: {
216
- amount: cartDiscountCalculation.remainingAmount,
363
+ amount: totalAmount,
217
364
  currency: details.currency
218
365
  }
219
366
  };
@@ -222,52 +369,17 @@ function calculateCartPriceSnapshot(details) {
222
369
  totalBeforeDiscountsAmount,
223
370
  subtotalItemDiscountsAmount,
224
371
  subtotalBasketDiscountsAmount: cartDiscountCalculation.totalDiscountAmount,
372
+ subtotalTargetedDiscountsAmount,
225
373
  totalDiscountsAmount,
226
- totalAmount: cartDiscountCalculation.remainingAmount,
374
+ totalAmount,
375
+ itemSplitSeatPricing,
376
+ equalSplitPartPricing,
227
377
  price
228
378
  };
229
379
  }
230
380
 
231
381
  // src/tax/index.ts
232
382
  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
383
  function assertMinorUnit2(value, fieldName) {
272
384
  if (!Number.isInteger(value)) {
273
385
  throw new Error(
@@ -375,6 +487,76 @@ function calculateOrderTaxSummary(details) {
375
487
 
376
488
  // src/cart/Cart.ts
377
489
  var { isEqual } = import_lodash.default;
490
+ function cloneEqualSplitPart(part) {
491
+ return {
492
+ ...part,
493
+ name: part.name ?? null,
494
+ loyaltyId: part.loyaltyId ?? null,
495
+ loyaltyUserName: part.loyaltyUserName ?? null,
496
+ appliedDiscountIds: [...part.appliedDiscountIds ?? []]
497
+ };
498
+ }
499
+ function cloneEqualSplitState(equalSplitState) {
500
+ if (!equalSplitState) {
501
+ return null;
502
+ }
503
+ return {
504
+ partCount: equalSplitState.partCount,
505
+ parts: (equalSplitState.parts ?? []).map(cloneEqualSplitPart)
506
+ };
507
+ }
508
+ function createEmptyEqualSplitState() {
509
+ return {
510
+ partCount: 0,
511
+ parts: []
512
+ };
513
+ }
514
+ function cloneItemSplitSeatAssignment(assignment) {
515
+ return {
516
+ ...assignment,
517
+ appliedDiscountIds: [...assignment.appliedDiscountIds ?? []]
518
+ };
519
+ }
520
+ function cloneItemSplitSeat(seat) {
521
+ return {
522
+ ...seat,
523
+ name: seat.name ?? null,
524
+ loyaltyId: seat.loyaltyId ?? null,
525
+ loyaltyUserName: seat.loyaltyUserName ?? null,
526
+ appliedDiscountIds: [...seat.appliedDiscountIds ?? []],
527
+ assignments: (seat.assignments ?? []).map(cloneItemSplitSeatAssignment)
528
+ };
529
+ }
530
+ function cloneTableService(tableService) {
531
+ if (!tableService) {
532
+ return null;
533
+ }
534
+ return {
535
+ name: tableService.name ?? null,
536
+ seats: tableService.seats,
537
+ itemSplitSeats: (tableService.itemSplitSeats ?? []).map(cloneItemSplitSeat)
538
+ };
539
+ }
540
+ function createEmptyTableService() {
541
+ return {
542
+ name: null,
543
+ seats: 0,
544
+ itemSplitSeats: []
545
+ };
546
+ }
547
+ function validateEqualSplitState(equalSplitState) {
548
+ if (!equalSplitState) {
549
+ return;
550
+ }
551
+ if (!Number.isInteger(equalSplitState.partCount) || equalSplitState.partCount < 0) {
552
+ throw new Error("Equal split part count must be a non-negative integer");
553
+ }
554
+ if (equalSplitState.parts.length > equalSplitState.partCount) {
555
+ throw new Error(
556
+ "Equal split state cannot contain more parts than partCount"
557
+ );
558
+ }
559
+ }
378
560
  var Cart = class _Cart {
379
561
  id;
380
562
  businessId;
@@ -399,11 +581,13 @@ var Cart = class _Cart {
399
581
  _customer = null;
400
582
  _refunds = [];
401
583
  _tableService;
584
+ _equalSplitState;
402
585
  _taxSummary;
403
586
  _newlyCancelledItemIds;
404
587
  _invoiceCompany;
405
588
  _staffId;
406
589
  _shiftId;
590
+ _aggregateMatchingItemsOnAdd;
407
591
  version = "1.0.2";
408
592
  constructor(id, createdAt, options) {
409
593
  this.id = id;
@@ -412,6 +596,7 @@ var Cart = class _Cart {
412
596
  this.provider = options.provider;
413
597
  this.orderType = options.orderType;
414
598
  this.orderNumber = options.orderNumber;
599
+ this._aggregateMatchingItemsOnAdd = options.aggregateMatchingItemsOnAdd;
415
600
  this.checkoutModel = options.checkoutModel;
416
601
  this._business = options.business;
417
602
  this.currency = options.currency;
@@ -424,7 +609,8 @@ var Cart = class _Cart {
424
609
  this.status = import_core4.OrderStatusEnum.Draft;
425
610
  this.orderFormat = import_core4.OrderFormat.Instant;
426
611
  this._locatorType = import_core4.LocatorType.Table;
427
- this._tableService = options.tableService ?? null;
612
+ this._tableService = cloneTableService(options.tableService);
613
+ this._equalSplitState = cloneEqualSplitState(options.equalSplitState);
428
614
  this._newlyCancelledItemIds = /* @__PURE__ */ new Set();
429
615
  this._staffId = options.staffId ?? null;
430
616
  this._shiftId = options.shiftId ?? null;
@@ -436,8 +622,10 @@ var Cart = class _Cart {
436
622
  provider: options.provider ?? import_core4.ProviderEnum.MunchiPos,
437
623
  orderType: options.orderType ?? import_core4.OrderTypePOS.DineIn,
438
624
  orderNumber: options.orderNumber,
625
+ aggregateMatchingItemsOnAdd: options.aggregateMatchingItemsOnAdd ?? false,
439
626
  checkoutModel: options.checkoutModel ?? import_core4.CheckoutModelEnum.Full,
440
627
  tableService: options.tableService ?? null,
628
+ equalSplitState: options.equalSplitState ?? null,
441
629
  currency: options.currency,
442
630
  business: options.business,
443
631
  staffId: options.staffId,
@@ -450,8 +638,10 @@ var Cart = class _Cart {
450
638
  provider: orderDto.provider,
451
639
  orderType: orderDto.orderType,
452
640
  orderNumber: orderDto.orderNumber,
641
+ aggregateMatchingItemsOnAdd: false,
453
642
  checkoutModel: orderDto.checkoutModel,
454
643
  tableService: orderDto.tableService ?? null,
644
+ equalSplitState: orderDto.equalSplitState ?? null,
455
645
  currency: orderDto.currency,
456
646
  business: orderDto.business,
457
647
  staffId: orderDto.staffId ?? null,
@@ -473,7 +663,8 @@ var Cart = class _Cart {
473
663
  cart._refunds = orderDto.refunds ? [...orderDto.refunds] : [];
474
664
  cart._loyaltyTransactionIds = [...orderDto.loyaltyTransactionIds];
475
665
  cart._loyaltyProgramId = orderDto.loyaltyProgramId ?? null;
476
- cart._tableService = orderDto.tableService ? { ...orderDto.tableService } : null;
666
+ cart._tableService = cloneTableService(orderDto.tableService);
667
+ cart._equalSplitState = cloneEqualSplitState(orderDto.equalSplitState);
477
668
  cart._newlyCancelledItemIds = /* @__PURE__ */ new Set();
478
669
  cart._taxSummary = orderDto.taxSummary;
479
670
  cart._invoiceCompany = orderDto.invoiceCompany ?? null;
@@ -509,17 +700,29 @@ var Cart = class _Cart {
509
700
  get invoiceCompany() {
510
701
  return this._invoiceCompany;
511
702
  }
703
+ get aggregateMatchingItemsOnAdd() {
704
+ return this._aggregateMatchingItemsOnAdd;
705
+ }
512
706
  get guestCount() {
513
707
  return this._tableService?.seats ?? null;
514
708
  }
515
709
  get tableService() {
516
- return this._tableService;
710
+ return this.getTableService();
711
+ }
712
+ get equalSplitState() {
713
+ return this.getEqualSplitState();
714
+ }
715
+ get equalSplitPartCount() {
716
+ return this._equalSplitState?.partCount ?? null;
517
717
  }
518
718
  get price() {
519
719
  return calculateCartPriceSnapshot({
520
720
  items: this._items,
521
721
  discounts: this._discounts,
522
- currency: this.currency
722
+ currency: this.currency,
723
+ checkoutModel: this.checkoutModel,
724
+ tableService: this._tableService,
725
+ equalSplitState: this._equalSplitState
523
726
  }).price;
524
727
  }
525
728
  setInvoiceCompany(company) {
@@ -529,6 +732,9 @@ var Cart = class _Cart {
529
732
  this._invoiceCompany = company;
530
733
  this.touch();
531
734
  }
735
+ setAggregateMatchingItemsOnAdd(enabled) {
736
+ this._aggregateMatchingItemsOnAdd = enabled;
737
+ }
532
738
  addItem(itemData, requiresPrep = false) {
533
739
  const lockedStatuses = [
534
740
  import_core4.KitchenStatus.DispatchedToKitchen,
@@ -536,9 +742,9 @@ var Cart = class _Cart {
536
742
  import_core4.KitchenStatus.Completed,
537
743
  import_core4.KitchenStatus.Cancelled
538
744
  ];
539
- const existingModifiableItem = this._items.find(
745
+ const existingModifiableItem = this._aggregateMatchingItemsOnAdd ? this._items.find(
540
746
  (item) => item.posId === itemData.posId && isEqual(item.options, itemData.options) && item.comments === itemData.comments && !lockedStatuses.includes(item.kitchenStatus) && item.discount === null
541
- );
747
+ ) : void 0;
542
748
  if (existingModifiableItem) {
543
749
  this._items = this._items.map((item) => {
544
750
  if (item.lineItemId !== existingModifiableItem.lineItemId) {
@@ -561,13 +767,24 @@ var Cart = class _Cart {
561
767
  this.touch();
562
768
  return;
563
769
  }
564
- this._items.push({
565
- ...itemData,
566
- lineItemId: (0, import_cuid2.createId)(),
567
- kitchenStatus: requiresPrep ? import_core4.KitchenStatus.Pending : import_core4.KitchenStatus.NotApplicable,
568
- paymentStatus: import_core4.PosPaymentStatus.Unpaid,
569
- discount: null
570
- });
770
+ const quantityPerLine = this._aggregateMatchingItemsOnAdd ? [itemData.quantity] : Array.from({ length: itemData.quantity }, () => 1);
771
+ for (const quantity of quantityPerLine) {
772
+ this._items.push({
773
+ ...itemData,
774
+ quantity,
775
+ itemPrice: calculateItemPrice({
776
+ basePriceInMinorUnit: itemData.basePrice.amount,
777
+ quantity,
778
+ taxRate: itemData.taxRate,
779
+ options: itemData.options,
780
+ currency: this.currency
781
+ }),
782
+ lineItemId: (0, import_cuid2.createId)(),
783
+ kitchenStatus: requiresPrep ? import_core4.KitchenStatus.Pending : import_core4.KitchenStatus.NotApplicable,
784
+ paymentStatus: import_core4.PosPaymentStatus.Unpaid,
785
+ discount: null
786
+ });
787
+ }
571
788
  this.touch();
572
789
  }
573
790
  updateItem(lineItemId, updatedItem) {
@@ -622,21 +839,25 @@ var Cart = class _Cart {
622
839
  this._newlyCancelledItemIds.clear();
623
840
  }
624
841
  applyDiscountToCart(discountData) {
625
- this._discounts.push({
626
- ...discountData,
627
- scope: import_core4.DiscountScope.Cart,
628
- lineItemId: null,
629
- createdAt: (0, import_dayjs.default)().toDate().toISOString()
630
- });
842
+ this._discounts.push(
843
+ this.createDiscountRecord(
844
+ discountData,
845
+ import_core4.DiscountScope.Cart,
846
+ null,
847
+ import_core4.PosDiscountTargetType.Order,
848
+ null
849
+ )
850
+ );
631
851
  this.touch();
632
852
  }
633
853
  applyDiscountToItem(lineItemId, discountData) {
634
- const newDiscount = {
635
- ...discountData,
636
- scope: import_core4.DiscountScope.Item,
854
+ const newDiscount = this.createDiscountRecord(
855
+ discountData,
856
+ import_core4.DiscountScope.Item,
637
857
  lineItemId,
638
- createdAt: (0, import_dayjs.default)().toDate().toISOString()
639
- };
858
+ import_core4.PosDiscountTargetType.LineItem,
859
+ lineItemId
860
+ );
640
861
  const existingDiscountIndex = this._discounts.findIndex(
641
862
  (discount) => discount.lineItemId === lineItemId && discount.scope === import_core4.DiscountScope.Item
642
863
  );
@@ -667,6 +888,42 @@ var Cart = class _Cart {
667
888
  });
668
889
  this.touch();
669
890
  }
891
+ applyDiscountToItemSplitSeat(seatId, discountData) {
892
+ this._discounts.push(
893
+ this.createDiscountRecord(
894
+ discountData,
895
+ import_core4.DiscountScope.Cart,
896
+ null,
897
+ import_core4.PosDiscountTargetType.ItemSplitSeat,
898
+ seatId
899
+ )
900
+ );
901
+ this.touch();
902
+ }
903
+ applyDiscountToItemSplitAssignment(assignmentId, discountData) {
904
+ this._discounts.push(
905
+ this.createDiscountRecord(
906
+ discountData,
907
+ import_core4.DiscountScope.Cart,
908
+ null,
909
+ import_core4.PosDiscountTargetType.ItemSplitAssignment,
910
+ assignmentId
911
+ )
912
+ );
913
+ this.touch();
914
+ }
915
+ applyDiscountToEqualSplitPart(partId, discountData) {
916
+ this._discounts.push(
917
+ this.createDiscountRecord(
918
+ discountData,
919
+ import_core4.DiscountScope.Cart,
920
+ null,
921
+ import_core4.PosDiscountTargetType.EqualSplitPart,
922
+ partId
923
+ )
924
+ );
925
+ this.touch();
926
+ }
670
927
  removeItemDiscount(lineItemId) {
671
928
  this._items = this._items.map((item) => {
672
929
  if (item.lineItemId !== lineItemId) {
@@ -779,15 +1036,277 @@ var Cart = class _Cart {
779
1036
  this.setCheckoutModel(checkoutModel);
780
1037
  }
781
1038
  setGuestCount(guestCount) {
782
- if ((this._tableService?.seats ?? null) === guestCount) {
1039
+ if (guestCount === null) {
1040
+ if (this._tableService === null) {
1041
+ return;
1042
+ }
1043
+ this._tableService = null;
1044
+ this.touch();
783
1045
  return;
784
1046
  }
785
- this._tableService = guestCount === null ? null : { seats: guestCount };
786
- this.touch();
1047
+ this.setSeatsCount(guestCount);
787
1048
  }
788
1049
  setSeatNumber(guestCount) {
789
1050
  this.setGuestCount(guestCount);
790
1051
  }
1052
+ getTableService() {
1053
+ return cloneTableService(this._tableService);
1054
+ }
1055
+ setTableService(tableService) {
1056
+ const nextTableService = cloneTableService(tableService);
1057
+ if (isEqual(this._tableService, nextTableService)) {
1058
+ return this;
1059
+ }
1060
+ this._tableService = nextTableService;
1061
+ this.touch();
1062
+ return this;
1063
+ }
1064
+ clearTableService() {
1065
+ return this.setTableService(null);
1066
+ }
1067
+ getTableName() {
1068
+ return this._tableService?.name ?? null;
1069
+ }
1070
+ setTableName(name) {
1071
+ const tableService = this.getTableService() ?? createEmptyTableService();
1072
+ if (tableService.name === name) {
1073
+ return this;
1074
+ }
1075
+ tableService.name = name;
1076
+ return this.setTableService(tableService);
1077
+ }
1078
+ getSeatsCount() {
1079
+ return this._tableService?.seats ?? null;
1080
+ }
1081
+ setSeatsCount(seats) {
1082
+ if (!Number.isInteger(seats) || seats < 0) {
1083
+ throw new Error("Seats count must be a non-negative integer");
1084
+ }
1085
+ const tableService = this.getTableService() ?? createEmptyTableService();
1086
+ if (tableService.seats === seats) {
1087
+ return this;
1088
+ }
1089
+ tableService.seats = seats;
1090
+ return this.setTableService(tableService);
1091
+ }
1092
+ getItemSplitSeats() {
1093
+ return this._tableService ? this._tableService.itemSplitSeats.map(cloneItemSplitSeat) : [];
1094
+ }
1095
+ setItemSplitSeats(seats) {
1096
+ const tableService = this.getTableService() ?? createEmptyTableService();
1097
+ tableService.itemSplitSeats = seats.map(cloneItemSplitSeat);
1098
+ return this.setTableService(tableService);
1099
+ }
1100
+ clearItemSplitSeats() {
1101
+ return this.setItemSplitSeats([]);
1102
+ }
1103
+ addItemSplitSeat(seat) {
1104
+ if (this.getItemSplitSeatById(seat.id)) {
1105
+ return this;
1106
+ }
1107
+ const seats = this.getItemSplitSeats();
1108
+ seats.push(cloneItemSplitSeat(seat));
1109
+ return this.setItemSplitSeats(seats);
1110
+ }
1111
+ updateItemSplitSeat(seatId, updater) {
1112
+ const seats = this.getItemSplitSeats();
1113
+ const seatIndex = seats.findIndex((seat) => seat.id === seatId);
1114
+ if (seatIndex === -1 || !seats[seatIndex]) {
1115
+ return this;
1116
+ }
1117
+ seats[seatIndex] = cloneItemSplitSeat(updater(cloneItemSplitSeat(seats[seatIndex])));
1118
+ return this.setItemSplitSeats(seats);
1119
+ }
1120
+ removeItemSplitSeat(seatId) {
1121
+ const seats = this.getItemSplitSeats();
1122
+ const nextSeats = seats.filter((seat) => seat.id !== seatId);
1123
+ if (nextSeats.length === seats.length) {
1124
+ return this;
1125
+ }
1126
+ return this.setItemSplitSeats(nextSeats);
1127
+ }
1128
+ getItemSplitSeatById(seatId) {
1129
+ const seat = this._tableService?.itemSplitSeats.find(
1130
+ (itemSplitSeat) => itemSplitSeat.id === seatId
1131
+ );
1132
+ return seat ? cloneItemSplitSeat(seat) : null;
1133
+ }
1134
+ setItemSplitSeatAssignments(seatId, assignments) {
1135
+ return this.updateItemSplitSeat(seatId, (seat) => ({
1136
+ ...seat,
1137
+ assignments: assignments.map(cloneItemSplitSeatAssignment)
1138
+ }));
1139
+ }
1140
+ addItemSplitSeatAssignment(seatId, assignment) {
1141
+ return this.updateItemSplitSeat(seatId, (seat) => ({
1142
+ ...seat,
1143
+ assignments: [...seat.assignments, cloneItemSplitSeatAssignment(assignment)]
1144
+ }));
1145
+ }
1146
+ removeItemSplitSeatAssignmentsByLineItem(seatId, lineItemId) {
1147
+ return this.updateItemSplitSeat(seatId, (seat) => ({
1148
+ ...seat,
1149
+ assignments: seat.assignments.filter(
1150
+ (assignment) => assignment.lineItemId !== lineItemId
1151
+ )
1152
+ }));
1153
+ }
1154
+ clearItemSplitSeatAssignments(seatId) {
1155
+ return this.setItemSplitSeatAssignments(seatId, []);
1156
+ }
1157
+ setItemSplitSeatLoyalty(seatId, loyaltyId, loyaltyUserName) {
1158
+ return this.updateItemSplitSeat(seatId, (seat) => ({
1159
+ ...seat,
1160
+ loyaltyId,
1161
+ loyaltyUserName
1162
+ }));
1163
+ }
1164
+ clearItemSplitSeatLoyalty(seatId) {
1165
+ return this.setItemSplitSeatLoyalty(seatId, null, null);
1166
+ }
1167
+ setItemSplitSeatAppliedDiscountIds(seatId, appliedDiscountIds) {
1168
+ return this.updateItemSplitSeat(seatId, (seat) => ({
1169
+ ...seat,
1170
+ appliedDiscountIds: [...appliedDiscountIds]
1171
+ }));
1172
+ }
1173
+ addItemSplitSeatAppliedDiscountId(seatId, discountId) {
1174
+ return this.updateItemSplitSeat(seatId, (seat) => {
1175
+ if (seat.appliedDiscountIds.includes(discountId)) {
1176
+ return seat;
1177
+ }
1178
+ return {
1179
+ ...seat,
1180
+ appliedDiscountIds: [...seat.appliedDiscountIds, discountId]
1181
+ };
1182
+ });
1183
+ }
1184
+ removeItemSplitSeatAppliedDiscountId(seatId, discountId) {
1185
+ return this.updateItemSplitSeat(seatId, (seat) => ({
1186
+ ...seat,
1187
+ appliedDiscountIds: seat.appliedDiscountIds.filter(
1188
+ (appliedDiscountId) => appliedDiscountId !== discountId
1189
+ )
1190
+ }));
1191
+ }
1192
+ clearItemSplitSeatAppliedDiscountIds(seatId) {
1193
+ return this.setItemSplitSeatAppliedDiscountIds(seatId, []);
1194
+ }
1195
+ getEqualSplitState() {
1196
+ return cloneEqualSplitState(this._equalSplitState);
1197
+ }
1198
+ setEqualSplitState(equalSplitState) {
1199
+ const nextEqualSplitState = cloneEqualSplitState(equalSplitState);
1200
+ validateEqualSplitState(nextEqualSplitState);
1201
+ if (isEqual(this._equalSplitState, nextEqualSplitState)) {
1202
+ return this;
1203
+ }
1204
+ this._equalSplitState = nextEqualSplitState;
1205
+ this.touch();
1206
+ return this;
1207
+ }
1208
+ clearEqualSplitState() {
1209
+ return this.setEqualSplitState(null);
1210
+ }
1211
+ getEqualSplitParts() {
1212
+ return this._equalSplitState ? this._equalSplitState.parts.map(cloneEqualSplitPart) : [];
1213
+ }
1214
+ setEqualSplitParts(parts) {
1215
+ if (parts.length === 0) {
1216
+ return this.clearEqualSplitState();
1217
+ }
1218
+ const equalSplitState = this.getEqualSplitState() ?? createEmptyEqualSplitState();
1219
+ equalSplitState.parts = parts.map(cloneEqualSplitPart);
1220
+ equalSplitState.partCount = parts.length;
1221
+ return this.setEqualSplitState(equalSplitState);
1222
+ }
1223
+ clearEqualSplitParts() {
1224
+ return this.clearEqualSplitState();
1225
+ }
1226
+ addEqualSplitPart(part) {
1227
+ if (this.getEqualSplitPartById(part.id)) {
1228
+ return this;
1229
+ }
1230
+ const parts = this.getEqualSplitParts();
1231
+ parts.push(cloneEqualSplitPart(part));
1232
+ return this.setEqualSplitParts(parts);
1233
+ }
1234
+ updateEqualSplitPart(partId, updater) {
1235
+ const parts = this.getEqualSplitParts();
1236
+ const partIndex = parts.findIndex((part) => part.id === partId);
1237
+ if (partIndex === -1 || !parts[partIndex]) {
1238
+ return this;
1239
+ }
1240
+ parts[partIndex] = cloneEqualSplitPart(updater(cloneEqualSplitPart(parts[partIndex])));
1241
+ return this.setEqualSplitParts(parts);
1242
+ }
1243
+ removeEqualSplitPart(partId) {
1244
+ const parts = this.getEqualSplitParts();
1245
+ const nextParts = parts.filter((part) => part.id !== partId);
1246
+ if (nextParts.length === parts.length) {
1247
+ return this;
1248
+ }
1249
+ return nextParts.length === 0 ? this.clearEqualSplitState() : this.setEqualSplitParts(nextParts);
1250
+ }
1251
+ getEqualSplitPartById(partId) {
1252
+ const part = this._equalSplitState?.parts.find(
1253
+ (equalSplitPart) => equalSplitPart.id === partId
1254
+ );
1255
+ return part ? cloneEqualSplitPart(part) : null;
1256
+ }
1257
+ getEqualSplitPartCount() {
1258
+ return this._equalSplitState?.partCount ?? null;
1259
+ }
1260
+ setEqualSplitPartCount(partCount) {
1261
+ if (partCount === null) {
1262
+ return this.clearEqualSplitState();
1263
+ }
1264
+ if (!Number.isInteger(partCount) || partCount <= 0) {
1265
+ throw new Error("Equal split part count must be a positive integer or null");
1266
+ }
1267
+ const equalSplitState = this.getEqualSplitState() ?? createEmptyEqualSplitState();
1268
+ if (equalSplitState.parts.length > partCount) {
1269
+ throw new Error(
1270
+ "Equal split part count cannot be less than persisted parts length"
1271
+ );
1272
+ }
1273
+ if (equalSplitState.partCount === partCount) {
1274
+ return this;
1275
+ }
1276
+ equalSplitState.partCount = partCount;
1277
+ return this.setEqualSplitState(equalSplitState);
1278
+ }
1279
+ clearEqualSplitPartCount() {
1280
+ return this.clearEqualSplitState();
1281
+ }
1282
+ setEqualSplitPartAppliedDiscountIds(partId, appliedDiscountIds) {
1283
+ return this.updateEqualSplitPart(partId, (part) => ({
1284
+ ...part,
1285
+ appliedDiscountIds: [...appliedDiscountIds]
1286
+ }));
1287
+ }
1288
+ addEqualSplitPartAppliedDiscountId(partId, discountId) {
1289
+ return this.updateEqualSplitPart(partId, (part) => {
1290
+ if (part.appliedDiscountIds.includes(discountId)) {
1291
+ return part;
1292
+ }
1293
+ return {
1294
+ ...part,
1295
+ appliedDiscountIds: [...part.appliedDiscountIds, discountId]
1296
+ };
1297
+ });
1298
+ }
1299
+ removeEqualSplitPartAppliedDiscountId(partId, discountId) {
1300
+ return this.updateEqualSplitPart(partId, (part) => ({
1301
+ ...part,
1302
+ appliedDiscountIds: part.appliedDiscountIds.filter(
1303
+ (appliedDiscountId) => appliedDiscountId !== discountId
1304
+ )
1305
+ }));
1306
+ }
1307
+ clearEqualSplitPartAppliedDiscountIds(partId) {
1308
+ return this.setEqualSplitPartAppliedDiscountIds(partId, []);
1309
+ }
791
1310
  updateOrderStatus(newStatus) {
792
1311
  if (this.status === newStatus) {
793
1312
  return;
@@ -846,7 +1365,8 @@ var Cart = class _Cart {
846
1365
  locatorType: this._locatorType,
847
1366
  spotNumber: this.spotNumber,
848
1367
  comments: this.comments,
849
- tableService: this._tableService,
1368
+ tableService: cloneTableService(this._tableService),
1369
+ equalSplitState: cloneEqualSplitState(this._equalSplitState),
850
1370
  orderRefundStatus: this.orderRefundStatus,
851
1371
  items: this._items,
852
1372
  status: this.status,
@@ -873,8 +1393,10 @@ var Cart = class _Cart {
873
1393
  provider: this.provider,
874
1394
  orderType: this.orderType,
875
1395
  orderNumber: this.orderNumber,
1396
+ aggregateMatchingItemsOnAdd: this._aggregateMatchingItemsOnAdd,
876
1397
  checkoutModel: this.checkoutModel,
877
1398
  tableService: this._tableService,
1399
+ equalSplitState: this._equalSplitState,
878
1400
  currency: this.currency,
879
1401
  business: this._business,
880
1402
  staffId: this._staffId,
@@ -893,7 +1415,8 @@ var Cart = class _Cart {
893
1415
  newCart._refunds = [...this._refunds];
894
1416
  newCart._loyaltyProgramId = this._loyaltyProgramId;
895
1417
  newCart._loyaltyTransactionIds = [...this._loyaltyTransactionIds];
896
- newCart._tableService = this._tableService ? { ...this._tableService } : null;
1418
+ newCart._tableService = cloneTableService(this._tableService);
1419
+ newCart._equalSplitState = cloneEqualSplitState(this._equalSplitState);
897
1420
  newCart._taxSummary = this._taxSummary;
898
1421
  newCart._newlyCancelledItemIds = new Set(this._newlyCancelledItemIds);
899
1422
  newCart._invoiceCompany = this._invoiceCompany ? { ...this._invoiceCompany } : null;
@@ -906,6 +1429,16 @@ var Cart = class _Cart {
906
1429
  currency: this.currency
907
1430
  });
908
1431
  }
1432
+ createDiscountRecord(discountData, scope, lineItemId, targetType, targetId) {
1433
+ return {
1434
+ ...discountData,
1435
+ scope,
1436
+ lineItemId,
1437
+ targetType,
1438
+ targetId,
1439
+ createdAt: (0, import_dayjs.default)().toDate().toISOString()
1440
+ };
1441
+ }
909
1442
  touch() {
910
1443
  this.updatedAt = (0, import_dayjs.default)().toDate();
911
1444
  }
@@ -919,7 +1452,7 @@ var ApplyTo = /* @__PURE__ */ ((ApplyTo2) => {
919
1452
  })(ApplyTo || {});
920
1453
 
921
1454
  // src/version.ts
922
- var VERSION = "0.1.2";
1455
+ var VERSION = "0.1.5";
923
1456
  // Annotate the CommonJS export names for ESM import in node:
924
1457
  0 && (module.exports = {
925
1458
  ApplyTo,
@@ -928,7 +1461,9 @@ var VERSION = "0.1.2";
928
1461
  allocateProportionalMinorUnits,
929
1462
  calculateCartPriceSnapshot,
930
1463
  calculateDiscountAmount,
1464
+ calculateEqualSplitPartPricing,
931
1465
  calculateItemPrice,
1466
+ calculateItemSplitSeatPricing,
932
1467
  calculateOrderTaxSummary,
933
1468
  calculateSequentialDiscountTotal,
934
1469
  splitTaxInclusiveAmount