@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.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 { DiscountScope, KitchenStatus } from "@munchi_oy/core";
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((discount) => discount.scope === DiscountScope.Cart).sort(
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 totalDiscountsAmount = subtotalItemDiscountsAmount + cartDiscountCalculation.totalDiscountAmount;
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: cartDiscountCalculation.remainingAmount,
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: cartDiscountCalculation.remainingAmount,
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 ?? null;
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 ? { ...orderDto.tableService } : null;
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._tableService;
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
- ...discountData,
593
- scope: DiscountScope2.Cart,
594
- lineItemId: null,
595
- createdAt: dayjs().toDate().toISOString()
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
- ...discountData,
602
- scope: DiscountScope2.Item,
803
+ const newDiscount = this.createDiscountRecord(
804
+ discountData,
805
+ DiscountScope2.Item,
603
806
  lineItemId,
604
- createdAt: dayjs().toDate().toISOString()
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 ((this._tableService?.seats ?? null) === guestCount) {
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._tableService = guestCount === null ? null : { seats: guestCount };
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 ? { ...this._tableService } : null;
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.2";
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