@bash-app/bash-common 30.247.0 → 30.254.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.
Files changed (172) hide show
  1. package/README.md +2 -0
  2. package/dist/__tests__/definitionsHelpers.test.d.ts +2 -0
  3. package/dist/__tests__/definitionsHelpers.test.d.ts.map +1 -0
  4. package/dist/__tests__/definitionsHelpers.test.js +85 -0
  5. package/dist/__tests__/definitionsHelpers.test.js.map +1 -0
  6. package/dist/__tests__/extendedSchemas.test.d.ts +2 -0
  7. package/dist/__tests__/extendedSchemas.test.d.ts.map +1 -0
  8. package/dist/__tests__/extendedSchemas.test.js +40 -0
  9. package/dist/__tests__/extendedSchemas.test.js.map +1 -0
  10. package/dist/__tests__/membershipDefinitions.test.d.ts +2 -0
  11. package/dist/__tests__/membershipDefinitions.test.d.ts.map +1 -0
  12. package/dist/__tests__/membershipDefinitions.test.js +95 -0
  13. package/dist/__tests__/membershipDefinitions.test.js.map +1 -0
  14. package/dist/__tests__/ticketAdmission.test.d.ts +5 -0
  15. package/dist/__tests__/ticketAdmission.test.d.ts.map +1 -0
  16. package/dist/__tests__/ticketAdmission.test.js +42 -0
  17. package/dist/__tests__/ticketAdmission.test.js.map +1 -0
  18. package/dist/__tests__/utmAttribution.test.d.ts +2 -0
  19. package/dist/__tests__/utmAttribution.test.d.ts.map +1 -0
  20. package/dist/__tests__/utmAttribution.test.js +92 -0
  21. package/dist/__tests__/utmAttribution.test.js.map +1 -0
  22. package/dist/extendedSchemas.d.ts +2 -0
  23. package/dist/extendedSchemas.d.ts.map +1 -1
  24. package/dist/extendedSchemas.js +2 -0
  25. package/dist/extendedSchemas.js.map +1 -1
  26. package/dist/index.d.ts +1 -0
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +1 -0
  29. package/dist/index.js.map +1 -1
  30. package/dist/sms/smsTemplates.d.ts +27 -0
  31. package/dist/sms/smsTemplates.d.ts.map +1 -0
  32. package/dist/sms/smsTemplates.js +11 -0
  33. package/dist/sms/smsTemplates.js.map +1 -0
  34. package/dist/utils/__tests__/addressUtils.test.d.ts +2 -0
  35. package/dist/utils/__tests__/addressUtils.test.d.ts.map +1 -0
  36. package/dist/utils/__tests__/addressUtils.test.js +138 -0
  37. package/dist/utils/__tests__/addressUtils.test.js.map +1 -0
  38. package/dist/utils/__tests__/apiUtils.test.d.ts +5 -0
  39. package/dist/utils/__tests__/apiUtils.test.d.ts.map +1 -0
  40. package/dist/utils/__tests__/apiUtils.test.js +161 -0
  41. package/dist/utils/__tests__/apiUtils.test.js.map +1 -0
  42. package/dist/utils/__tests__/arrayUtils.test.d.ts +2 -0
  43. package/dist/utils/__tests__/arrayUtils.test.d.ts.map +1 -0
  44. package/dist/utils/__tests__/arrayUtils.test.js +23 -0
  45. package/dist/utils/__tests__/arrayUtils.test.js.map +1 -0
  46. package/dist/utils/__tests__/blogUtils.test.d.ts +2 -0
  47. package/dist/utils/__tests__/blogUtils.test.d.ts.map +1 -0
  48. package/dist/utils/__tests__/blogUtils.test.js +102 -0
  49. package/dist/utils/__tests__/blogUtils.test.js.map +1 -0
  50. package/dist/utils/__tests__/dateTimeUtils.test.d.ts +2 -0
  51. package/dist/utils/__tests__/dateTimeUtils.test.d.ts.map +1 -0
  52. package/dist/utils/__tests__/dateTimeUtils.test.js +180 -0
  53. package/dist/utils/__tests__/dateTimeUtils.test.js.map +1 -0
  54. package/dist/utils/__tests__/discountEngine/discountCalculator.test.d.ts +2 -0
  55. package/dist/utils/__tests__/discountEngine/discountCalculator.test.d.ts.map +1 -0
  56. package/dist/utils/__tests__/discountEngine/discountCalculator.test.js +356 -0
  57. package/dist/utils/__tests__/discountEngine/discountCalculator.test.js.map +1 -0
  58. package/dist/utils/__tests__/generalDateTimeUtils.test.d.ts +2 -0
  59. package/dist/utils/__tests__/generalDateTimeUtils.test.d.ts.map +1 -0
  60. package/dist/utils/__tests__/generalDateTimeUtils.test.js +26 -0
  61. package/dist/utils/__tests__/generalDateTimeUtils.test.js.map +1 -0
  62. package/dist/utils/__tests__/luxonUtils.test.d.ts +2 -0
  63. package/dist/utils/__tests__/luxonUtils.test.d.ts.map +1 -0
  64. package/dist/utils/__tests__/luxonUtils.test.js +139 -0
  65. package/dist/utils/__tests__/luxonUtils.test.js.map +1 -0
  66. package/dist/utils/__tests__/mathUtils.test.d.ts +5 -0
  67. package/dist/utils/__tests__/mathUtils.test.d.ts.map +1 -0
  68. package/dist/utils/__tests__/mathUtils.test.js +26 -0
  69. package/dist/utils/__tests__/mathUtils.test.js.map +1 -0
  70. package/dist/utils/__tests__/objUtils.test.d.ts +5 -0
  71. package/dist/utils/__tests__/objUtils.test.d.ts.map +1 -0
  72. package/dist/utils/__tests__/objUtils.test.js +41 -0
  73. package/dist/utils/__tests__/objUtils.test.js.map +1 -0
  74. package/dist/utils/__tests__/paymentUtils.test.js +48 -1
  75. package/dist/utils/__tests__/paymentUtils.test.js.map +1 -1
  76. package/dist/utils/__tests__/qrCodeUtils.test.js +16 -1
  77. package/dist/utils/__tests__/qrCodeUtils.test.js.map +1 -1
  78. package/dist/utils/__tests__/reviewUtils.test.d.ts +2 -0
  79. package/dist/utils/__tests__/reviewUtils.test.d.ts.map +1 -0
  80. package/dist/utils/__tests__/reviewUtils.test.js +49 -0
  81. package/dist/utils/__tests__/reviewUtils.test.js.map +1 -0
  82. package/dist/utils/__tests__/slugUtils.test.d.ts +2 -0
  83. package/dist/utils/__tests__/slugUtils.test.d.ts.map +1 -0
  84. package/dist/utils/__tests__/slugUtils.test.js +103 -0
  85. package/dist/utils/__tests__/slugUtils.test.js.map +1 -0
  86. package/dist/utils/__tests__/sortUtils.test.d.ts +5 -0
  87. package/dist/utils/__tests__/sortUtils.test.d.ts.map +1 -0
  88. package/dist/utils/__tests__/sortUtils.test.js +28 -0
  89. package/dist/utils/__tests__/sortUtils.test.js.map +1 -0
  90. package/dist/utils/__tests__/stringUtils.test.d.ts +2 -0
  91. package/dist/utils/__tests__/stringUtils.test.d.ts.map +1 -0
  92. package/dist/utils/__tests__/stringUtils.test.js +27 -0
  93. package/dist/utils/__tests__/stringUtils.test.js.map +1 -0
  94. package/dist/utils/__tests__/ticketListUtils.test.d.ts +2 -0
  95. package/dist/utils/__tests__/ticketListUtils.test.d.ts.map +1 -0
  96. package/dist/utils/__tests__/ticketListUtils.test.js +41 -0
  97. package/dist/utils/__tests__/ticketListUtils.test.js.map +1 -0
  98. package/dist/utils/__tests__/typeUtils.test.d.ts +2 -0
  99. package/dist/utils/__tests__/typeUtils.test.d.ts.map +1 -0
  100. package/dist/utils/__tests__/typeUtils.test.js +11 -0
  101. package/dist/utils/__tests__/typeUtils.test.js.map +1 -0
  102. package/dist/utils/__tests__/userSubscriptionUtils.test.d.ts +5 -0
  103. package/dist/utils/__tests__/userSubscriptionUtils.test.d.ts.map +1 -0
  104. package/dist/utils/__tests__/userSubscriptionUtils.test.js +66 -0
  105. package/dist/utils/__tests__/userSubscriptionUtils.test.js.map +1 -0
  106. package/dist/utils/__tests__/userUtils.test.d.ts +2 -0
  107. package/dist/utils/__tests__/userUtils.test.d.ts.map +1 -0
  108. package/dist/utils/__tests__/userUtils.test.js +34 -0
  109. package/dist/utils/__tests__/userUtils.test.js.map +1 -0
  110. package/dist/utils/__tests__/venueTypeFormatter.test.d.ts +2 -0
  111. package/dist/utils/__tests__/venueTypeFormatter.test.d.ts.map +1 -0
  112. package/dist/utils/__tests__/venueTypeFormatter.test.js +25 -0
  113. package/dist/utils/__tests__/venueTypeFormatter.test.js.map +1 -0
  114. package/dist/utils/service/__tests__/regexUtils.test.d.ts +2 -0
  115. package/dist/utils/service/__tests__/regexUtils.test.d.ts.map +1 -0
  116. package/dist/utils/service/__tests__/regexUtils.test.js +51 -0
  117. package/dist/utils/service/__tests__/regexUtils.test.js.map +1 -0
  118. package/dist/utils/service/__tests__/serviceDBUtils.test.d.ts +2 -0
  119. package/dist/utils/service/__tests__/serviceDBUtils.test.d.ts.map +1 -0
  120. package/dist/utils/service/__tests__/serviceDBUtils.test.js +17 -0
  121. package/dist/utils/service/__tests__/serviceDBUtils.test.js.map +1 -0
  122. package/dist/utils/service/__tests__/serviceFeaturedSortUtils.test.d.ts +2 -0
  123. package/dist/utils/service/__tests__/serviceFeaturedSortUtils.test.d.ts.map +1 -0
  124. package/dist/utils/service/__tests__/serviceFeaturedSortUtils.test.js +60 -0
  125. package/dist/utils/service/__tests__/serviceFeaturedSortUtils.test.js.map +1 -0
  126. package/dist/utils/service/__tests__/serviceUtils.test.d.ts +2 -0
  127. package/dist/utils/service/__tests__/serviceUtils.test.d.ts.map +1 -0
  128. package/dist/utils/service/__tests__/serviceUtils.test.js +135 -0
  129. package/dist/utils/service/__tests__/serviceUtils.test.js.map +1 -0
  130. package/dist/utils/service/serviceFeaturedSortUtils.d.ts.map +1 -1
  131. package/dist/utils/service/serviceFeaturedSortUtils.js +5 -1
  132. package/dist/utils/service/serviceFeaturedSortUtils.js.map +1 -1
  133. package/dist/utils/ticketListUtils.d.ts.map +1 -1
  134. package/dist/utils/ticketListUtils.js +11 -7
  135. package/dist/utils/ticketListUtils.js.map +1 -1
  136. package/package.json +1 -1
  137. package/prisma/schema.prisma +47 -0
  138. package/src/__tests__/definitionsHelpers.test.ts +136 -0
  139. package/src/__tests__/extendedSchemas.test.ts +50 -0
  140. package/src/__tests__/membershipDefinitions.test.ts +144 -0
  141. package/src/__tests__/ticketAdmission.test.ts +73 -0
  142. package/src/__tests__/utmAttribution.test.ts +118 -0
  143. package/src/extendedSchemas.ts +2 -0
  144. package/src/index.ts +1 -0
  145. package/src/sms/smsTemplates.ts +20 -0
  146. package/src/utils/__tests__/addressUtils.test.ts +176 -0
  147. package/src/utils/__tests__/apiUtils.test.ts +219 -0
  148. package/src/utils/__tests__/arrayUtils.test.ts +26 -0
  149. package/src/utils/__tests__/blogUtils.test.ts +145 -0
  150. package/src/utils/__tests__/dateTimeUtils.test.ts +233 -0
  151. package/src/utils/__tests__/discountEngine/discountCalculator.test.ts +410 -0
  152. package/src/utils/__tests__/generalDateTimeUtils.test.ts +34 -0
  153. package/src/utils/__tests__/luxonUtils.test.ts +171 -0
  154. package/src/utils/__tests__/mathUtils.test.ts +30 -0
  155. package/src/utils/__tests__/objUtils.test.ts +48 -0
  156. package/src/utils/__tests__/paymentUtils.test.ts +59 -0
  157. package/src/utils/__tests__/qrCodeUtils.test.ts +28 -1
  158. package/src/utils/__tests__/reviewUtils.test.ts +65 -0
  159. package/src/utils/__tests__/slugUtils.test.ts +143 -0
  160. package/src/utils/__tests__/sortUtils.test.ts +33 -0
  161. package/src/utils/__tests__/stringUtils.test.ts +37 -0
  162. package/src/utils/__tests__/ticketListUtils.test.ts +47 -0
  163. package/src/utils/__tests__/typeUtils.test.ts +12 -0
  164. package/src/utils/__tests__/userSubscriptionUtils.test.ts +87 -0
  165. package/src/utils/__tests__/userUtils.test.ts +41 -0
  166. package/src/utils/__tests__/venueTypeFormatter.test.ts +31 -0
  167. package/src/utils/service/__tests__/regexUtils.test.ts +66 -0
  168. package/src/utils/service/__tests__/serviceDBUtils.test.ts +19 -0
  169. package/src/utils/service/__tests__/serviceFeaturedSortUtils.test.ts +67 -0
  170. package/src/utils/service/__tests__/serviceUtils.test.ts +190 -0
  171. package/src/utils/service/serviceFeaturedSortUtils.ts +5 -1
  172. package/src/utils/ticketListUtils.ts +13 -6
@@ -0,0 +1,356 @@
1
+ import { OfferDiscountType, SpecialOfferType, } from "@prisma/client";
2
+ import { describe, expect, test } from "@jest/globals";
3
+ import { calculatePromoCodeDiscount, calculateSpecialOfferDiscount, calculateBOGODiscount, calculateGroupDiscount, calculateVolumeDiscount, calculateEarlyBirdDiscount, calculateBundleDiscount, formatDiscountDescription, } from "../../discountEngine/discountCalculator.js";
4
+ const NOW = new Date("2026-01-01T00:00:00Z");
5
+ function basePromoCode(overrides) {
6
+ return {
7
+ ticketTierId: "tier-1",
8
+ discountAmountInCents: null,
9
+ discountAmountPercentage: null,
10
+ maxRedemptions: null,
11
+ redeemBy: null,
12
+ promoterId: null,
13
+ stripeCouponId: null,
14
+ meta: null,
15
+ bashPointsRewardPerTicket: null,
16
+ ...overrides,
17
+ };
18
+ }
19
+ function baseSpecialOffer(overrides) {
20
+ return {
21
+ createdAt: NOW,
22
+ updatedAt: NOW,
23
+ ticketTierId: "tier-1",
24
+ description: null,
25
+ discountType: OfferDiscountType.PERCENTAGE,
26
+ discountValue: 10,
27
+ isActive: true,
28
+ displayBadge: true,
29
+ badgeText: null,
30
+ buyQuantity: null,
31
+ getQuantity: null,
32
+ getDiscountPercent: null,
33
+ minGroupSize: null,
34
+ maxGroupSize: null,
35
+ tieredDiscounts: null,
36
+ startDate: null,
37
+ endDate: null,
38
+ availableUntil: null,
39
+ maxRedemptions: null,
40
+ maxPerUser: null,
41
+ limitedQuantity: null,
42
+ currentRedemptions: 0,
43
+ viewCount: 0,
44
+ clickCount: 0,
45
+ lastRedemptionAt: null,
46
+ priceIncreasesAt: null,
47
+ newPriceAfterIncrease: null,
48
+ countdownEnabled: false,
49
+ bundledItems: null,
50
+ bundleSavings: null,
51
+ requiresAttribution: false,
52
+ referralDiscountForBuyer: null,
53
+ referralDiscountForFriend: null,
54
+ commissionRate: null,
55
+ requiresEventAttendance: null,
56
+ hostSpecificLoyalty: false,
57
+ canStackWithPromoCodes: true,
58
+ canStackWithOtherOffers: false,
59
+ priority: 0,
60
+ urgencyMessage: null,
61
+ showSocialProof: false,
62
+ highlightColor: null,
63
+ sortOrder: 0,
64
+ minPriceFloor: null,
65
+ maxDiscountPercent: null,
66
+ ...overrides,
67
+ };
68
+ }
69
+ describe("calculatePromoCodeDiscount", () => {
70
+ test("applies percentage discount", () => {
71
+ const promo = basePromoCode({
72
+ id: "promo-1",
73
+ code: "SAVE20",
74
+ discountAmountPercentage: 20,
75
+ });
76
+ const result = calculatePromoCodeDiscount(promo, 5000, 2);
77
+ expect(result).toMatchObject({
78
+ sourceType: "PROMO_CODE",
79
+ discountType: "PERCENTAGE",
80
+ discountValue: 20,
81
+ originalPrice: 10000,
82
+ amountDiscounted: 2000,
83
+ finalPrice: 8000,
84
+ });
85
+ });
86
+ test("applies fixed amount discount capped at order total", () => {
87
+ const promo = basePromoCode({
88
+ id: "promo-2",
89
+ code: "FLAT10",
90
+ discountAmountInCents: 1500,
91
+ });
92
+ const result = calculatePromoCodeDiscount(promo, 1000, 1);
93
+ expect(result.amountDiscounted).toBe(1000);
94
+ expect(result.finalPrice).toBe(0);
95
+ });
96
+ test("includes attribution when promoter has userId", () => {
97
+ const promo = basePromoCode({
98
+ id: "promo-3",
99
+ code: "PROMO",
100
+ discountAmountPercentage: 10,
101
+ promoterId: "promoter-row-1",
102
+ promoter: { userId: "user-abc" },
103
+ });
104
+ const result = calculatePromoCodeDiscount(promo, 5000, 1);
105
+ expect(result.attribution).toEqual({
106
+ userId: "user-abc",
107
+ commissionRate: 5.0,
108
+ commissionAmount: 25,
109
+ });
110
+ });
111
+ test("omits attribution when promoter userId is missing", () => {
112
+ const promo = basePromoCode({
113
+ id: "promo-4",
114
+ code: "PROMO",
115
+ discountAmountPercentage: 10,
116
+ promoterId: "promoter-row-1",
117
+ promoter: { userId: null },
118
+ });
119
+ expect(calculatePromoCodeDiscount(promo, 5000, 1).attribution).toBeUndefined();
120
+ });
121
+ });
122
+ describe("calculateBOGODiscount", () => {
123
+ test("buy 2 get 1 free (100% off)", () => {
124
+ const offer = baseSpecialOffer({
125
+ id: "bogo-1",
126
+ title: "BOGO",
127
+ offerType: SpecialOfferType.BOGO,
128
+ buyQuantity: 2,
129
+ getQuantity: 1,
130
+ getDiscountPercent: 100,
131
+ });
132
+ // 3 tickets → 1 set → 1 free ticket at $50
133
+ expect(calculateBOGODiscount(offer, 3, 5000)).toBe(5000);
134
+ expect(calculateBOGODiscount(offer, 2, 5000)).toBe(0);
135
+ });
136
+ test("partial discount on free tickets", () => {
137
+ const offer = baseSpecialOffer({
138
+ id: "bogo-2",
139
+ title: "Half off extra",
140
+ offerType: SpecialOfferType.BOGO,
141
+ buyQuantity: 1,
142
+ getQuantity: 1,
143
+ getDiscountPercent: 50,
144
+ });
145
+ expect(calculateBOGODiscount(offer, 2, 4000)).toBe(2000);
146
+ });
147
+ });
148
+ describe("calculateGroupDiscount", () => {
149
+ test("returns 0 when below min group size", () => {
150
+ const offer = baseSpecialOffer({
151
+ id: "group-1",
152
+ title: "Group",
153
+ offerType: SpecialOfferType.GROUP_DISCOUNT,
154
+ minGroupSize: 5,
155
+ discountValue: 20,
156
+ });
157
+ expect(calculateGroupDiscount(offer, 3, 5000)).toBe(0);
158
+ });
159
+ test("returns 0 when above max group size", () => {
160
+ const offer = baseSpecialOffer({
161
+ id: "group-2",
162
+ title: "Group",
163
+ offerType: SpecialOfferType.GROUP_DISCOUNT,
164
+ maxGroupSize: 10,
165
+ discountValue: 20,
166
+ });
167
+ expect(calculateGroupDiscount(offer, 11, 5000)).toBe(0);
168
+ });
169
+ test("uses highest applicable tiered discount", () => {
170
+ const offer = baseSpecialOffer({
171
+ id: "group-3",
172
+ title: "Tiered",
173
+ offerType: SpecialOfferType.GROUP_DISCOUNT,
174
+ tieredDiscounts: [
175
+ { qty: 5, discount: 10 },
176
+ { qty: 10, discount: 20 },
177
+ ],
178
+ });
179
+ expect(calculateGroupDiscount(offer, 10, 5000)).toBe(10000);
180
+ expect(calculateGroupDiscount(offer, 5, 5000)).toBe(2500);
181
+ });
182
+ test("falls back to percentage discount", () => {
183
+ const offer = baseSpecialOffer({
184
+ id: "group-4",
185
+ title: "Simple group",
186
+ offerType: SpecialOfferType.GROUP_DISCOUNT,
187
+ discountType: OfferDiscountType.PERCENTAGE,
188
+ discountValue: 15,
189
+ });
190
+ expect(calculateGroupDiscount(offer, 4, 5000)).toBe(3000);
191
+ });
192
+ test("falls back to fixed amount per ticket", () => {
193
+ const offer = baseSpecialOffer({
194
+ id: "group-5",
195
+ title: "Fixed group",
196
+ offerType: SpecialOfferType.GROUP_DISCOUNT,
197
+ discountType: OfferDiscountType.FIXED_AMOUNT,
198
+ discountValue: 500,
199
+ });
200
+ expect(calculateGroupDiscount(offer, 3, 5000)).toBe(1500);
201
+ });
202
+ });
203
+ describe("calculateVolumeDiscount", () => {
204
+ test("delegates to group discount logic", () => {
205
+ const offer = baseSpecialOffer({
206
+ id: "vol-1",
207
+ title: "Volume",
208
+ offerType: SpecialOfferType.VOLUME_DISCOUNT,
209
+ discountType: OfferDiscountType.PERCENTAGE,
210
+ discountValue: 10,
211
+ });
212
+ expect(calculateVolumeDiscount(offer, 2, 5000)).toBe(calculateGroupDiscount(offer, 2, 5000));
213
+ });
214
+ });
215
+ describe("calculateEarlyBirdDiscount", () => {
216
+ test("returns 0 after availableUntil", () => {
217
+ const offer = baseSpecialOffer({
218
+ id: "eb-1",
219
+ title: "Early bird",
220
+ offerType: SpecialOfferType.EARLY_BIRD,
221
+ availableUntil: new Date("2026-01-01T00:00:00Z"),
222
+ discountValue: 20,
223
+ });
224
+ expect(calculateEarlyBirdDiscount(offer, 5000, new Date("2026-02-01T00:00:00Z"))).toBe(0);
225
+ });
226
+ test("applies percentage before cutoff", () => {
227
+ const offer = baseSpecialOffer({
228
+ id: "eb-2",
229
+ title: "Early bird",
230
+ offerType: SpecialOfferType.EARLY_BIRD,
231
+ availableUntil: new Date("2026-06-01T00:00:00Z"),
232
+ discountType: OfferDiscountType.PERCENTAGE,
233
+ discountValue: 20,
234
+ });
235
+ expect(calculateEarlyBirdDiscount(offer, 5000, new Date("2026-03-01T00:00:00Z"))).toBe(1000);
236
+ });
237
+ test("applies fixed amount capped at ticket price", () => {
238
+ const offer = baseSpecialOffer({
239
+ id: "eb-3",
240
+ title: "Early bird flat",
241
+ offerType: SpecialOfferType.EARLY_BIRD,
242
+ discountType: OfferDiscountType.FIXED_AMOUNT,
243
+ discountValue: 8000,
244
+ });
245
+ expect(calculateEarlyBirdDiscount(offer, 5000, new Date("2026-03-01T00:00:00Z"))).toBe(5000);
246
+ });
247
+ });
248
+ describe("calculateBundleDiscount", () => {
249
+ test("returns bundleSavings from offer", () => {
250
+ const offer = baseSpecialOffer({
251
+ id: "bundle-1",
252
+ title: "Bundle",
253
+ offerType: SpecialOfferType.BUNDLE,
254
+ bundleSavings: 2500,
255
+ });
256
+ expect(calculateBundleDiscount(offer, [{ type: "ticket", price: 5000 }])).toBe(2500);
257
+ });
258
+ test("returns 0 when bundleSavings is null", () => {
259
+ const offer = baseSpecialOffer({
260
+ id: "bundle-2",
261
+ title: "Bundle",
262
+ offerType: SpecialOfferType.BUNDLE,
263
+ bundleSavings: null,
264
+ });
265
+ expect(calculateBundleDiscount(offer, [])).toBe(0);
266
+ });
267
+ });
268
+ describe("calculateSpecialOfferDiscount", () => {
269
+ test("EARLY_BIRD percentage via switch", () => {
270
+ const offer = baseSpecialOffer({
271
+ id: "so-1",
272
+ title: "Early Bird",
273
+ offerType: SpecialOfferType.EARLY_BIRD,
274
+ discountType: OfferDiscountType.PERCENTAGE,
275
+ discountValue: 25,
276
+ });
277
+ const result = calculateSpecialOfferDiscount(offer, 4000, 2);
278
+ expect(result).toMatchObject({
279
+ sourceType: "SPECIAL_OFFER",
280
+ discountType: "PERCENTAGE",
281
+ amountDiscounted: 2000,
282
+ originalPrice: 8000,
283
+ finalPrice: 6000,
284
+ });
285
+ });
286
+ test("BUNDLE uses pre-calculated savings", () => {
287
+ const offer = baseSpecialOffer({
288
+ id: "so-2",
289
+ title: "Bundle Deal",
290
+ offerType: SpecialOfferType.BUNDLE,
291
+ bundleSavings: 1000,
292
+ });
293
+ const result = calculateSpecialOfferDiscount(offer, 5000, 1);
294
+ expect(result.discountType).toBe("FIXED_AMOUNT");
295
+ expect(result.amountDiscounted).toBe(1000);
296
+ });
297
+ test("REFERRAL uses buyer discount", () => {
298
+ const offer = baseSpecialOffer({
299
+ id: "so-3",
300
+ title: "Referral",
301
+ offerType: SpecialOfferType.REFERRAL,
302
+ referralDiscountForBuyer: 500,
303
+ requiresAttribution: true,
304
+ commissionRate: 10,
305
+ });
306
+ const result = calculateSpecialOfferDiscount(offer, 5000, 1, "referrer-user");
307
+ expect(result.amountDiscounted).toBe(500);
308
+ expect(result.attribution).toEqual({
309
+ userId: "referrer-user",
310
+ commissionRate: 10,
311
+ commissionAmount: 50,
312
+ });
313
+ });
314
+ test("BOGO sets FREE_ITEMS discount type", () => {
315
+ const offer = baseSpecialOffer({
316
+ id: "so-4",
317
+ title: "BOGO",
318
+ offerType: SpecialOfferType.BOGO,
319
+ buyQuantity: 1,
320
+ getQuantity: 1,
321
+ getDiscountPercent: 100,
322
+ });
323
+ const result = calculateSpecialOfferDiscount(offer, 5000, 2);
324
+ expect(result.discountType).toBe("FREE_ITEMS");
325
+ expect(result.amountDiscounted).toBe(5000);
326
+ });
327
+ });
328
+ describe("formatDiscountDescription", () => {
329
+ test("formats percentage discount", () => {
330
+ const text = formatDiscountDescription({
331
+ sourceType: "PROMO_CODE",
332
+ sourceId: "1",
333
+ sourceName: "SAVE20",
334
+ discountType: "PERCENTAGE",
335
+ discountValue: 20,
336
+ amountDiscounted: 2000,
337
+ originalPrice: 10000,
338
+ finalPrice: 8000,
339
+ });
340
+ expect(text).toBe("SAVE20: -20% ($20.00)");
341
+ });
342
+ test("formats fixed amount discount", () => {
343
+ const text = formatDiscountDescription({
344
+ sourceType: "SPECIAL_OFFER",
345
+ sourceId: "2",
346
+ sourceName: "Flat Off",
347
+ discountType: "FIXED_AMOUNT",
348
+ discountValue: 500,
349
+ amountDiscounted: 500,
350
+ originalPrice: 5000,
351
+ finalPrice: 4500,
352
+ });
353
+ expect(text).toBe("Flat Off: -$5.00");
354
+ });
355
+ });
356
+ //# sourceMappingURL=discountCalculator.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discountCalculator.test.js","sourceRoot":"","sources":["../../../../src/utils/__tests__/discountEngine/discountCalculator.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=generalDateTimeUtils.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generalDateTimeUtils.test.d.ts","sourceRoot":"","sources":["../../../src/utils/__tests__/generalDateTimeUtils.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,26 @@
1
+ import { DayOfWeek } from "@prisma/client";
2
+ import { describe, expect, test } from "@jest/globals";
3
+ import { dayOfWeekIdxToDay, dayOfWeekIdxToDayOfWeek, dayOfWeekToIdx, dayOfWeekToString, } from "../generalDateTimeUtils.js";
4
+ describe("generalDateTimeUtils", () => {
5
+ test("dayOfWeekToString maps each weekday to a short label", () => {
6
+ expect(dayOfWeekToString[DayOfWeek.Monday]).toBe("M");
7
+ expect(dayOfWeekToString[DayOfWeek.Thursday]).toBe("TR");
8
+ expect(dayOfWeekToString[DayOfWeek.Saturday]).toBe("S");
9
+ });
10
+ test("dayOfWeekToIdx assigns Monday=1 through Sunday=7", () => {
11
+ expect(dayOfWeekToIdx[DayOfWeek.Monday]).toBe(1);
12
+ expect(dayOfWeekToIdx[DayOfWeek.Sunday]).toBe(7);
13
+ });
14
+ test("dayOfWeekIdxToDayOfWeek round-trips with dayOfWeekToIdx", () => {
15
+ for (const day of Object.values(DayOfWeek)) {
16
+ const idx = dayOfWeekToIdx[day];
17
+ expect(dayOfWeekIdxToDayOfWeek[idx]).toBe(day);
18
+ }
19
+ });
20
+ test("dayOfWeekIdxToDay rotates Sunday to index 1", () => {
21
+ expect(dayOfWeekIdxToDay[7]).toBe(1);
22
+ expect(dayOfWeekIdxToDay[1]).toBe(2);
23
+ expect(dayOfWeekIdxToDay[6]).toBe(7);
24
+ });
25
+ });
26
+ //# sourceMappingURL=generalDateTimeUtils.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generalDateTimeUtils.test.js","sourceRoot":"","sources":["../../../src/utils/__tests__/generalDateTimeUtils.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AACvD,OAAO,EACL,iBAAiB,EACjB,uBAAuB,EACvB,cAAc,EACd,iBAAiB,GAClB,MAAM,4BAA4B,CAAC;AAEpC,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE;IACpC,IAAI,CAAC,sDAAsD,EAAE,GAAG,EAAE;QAChE,MAAM,CAAC,iBAAiB,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACtD,MAAM,CAAC,iBAAiB,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACzD,MAAM,CAAC,iBAAiB,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC5D,MAAM,CAAC,cAAc,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACjD,MAAM,CAAC,cAAc,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACnD,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,yDAAyD,EAAE,GAAG,EAAE;QACnE,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC;YAC3C,MAAM,GAAG,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC;YAChC,MAAM,CAAC,uBAAuB,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACjD,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,6CAA6C,EAAE,GAAG,EAAE;QACvD,MAAM,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACrC,MAAM,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACrC,MAAM,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=luxonUtils.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"luxonUtils.test.d.ts","sourceRoot":"","sources":["../../../src/utils/__tests__/luxonUtils.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,139 @@
1
+ import { DayOfWeek } from "@prisma/client";
2
+ import { DateTime } from "luxon";
3
+ import { describe, expect, test } from "@jest/globals";
4
+ import { dateRangeDurationMatch, dateTimeDiffHours, dateTimeFormat, dateTimeFormatDateRange, dateTimeFromDate, dateTimeFromDayTime, dateTimeRangeCrossesMidnight, dateTimeToTimeComponents, dateTimeWithinRange, dayOfWeekFromIdx, dayOfWeekGetIdx, filterConflictingIntervals, luxonDateRangeSort, mergeCrossDayHours, utcOffsetMinutesToTimezone, } from "../luxonUtils.js";
5
+ describe("luxonUtils", () => {
6
+ describe("day of week helpers", () => {
7
+ test("dayOfWeekGetIdx accepts enum and numeric index", () => {
8
+ expect(dayOfWeekGetIdx(DayOfWeek.Wednesday)).toBe(3);
9
+ expect(dayOfWeekGetIdx(5)).toBe(5);
10
+ });
11
+ test("dayOfWeekFromIdx accepts index and enum passthrough", () => {
12
+ expect(dayOfWeekFromIdx(4)).toBe(DayOfWeek.Thursday);
13
+ expect(dayOfWeekFromIdx(DayOfWeek.Friday)).toBe(DayOfWeek.Friday);
14
+ });
15
+ test("dateTimeFromDayTime builds weekday time in zone", () => {
16
+ const dt = dateTimeFromDayTime(DayOfWeek.Monday, 14, 30, "UTC");
17
+ expect(dt.weekday).toBe(1);
18
+ expect(dt.hour).toBe(14);
19
+ expect(dt.minute).toBe(30);
20
+ });
21
+ });
22
+ describe("dateTimeFromDate and formatting", () => {
23
+ test("dateTimeFromDate parses ISO string", () => {
24
+ const dt = dateTimeFromDate("2024-03-15T18:00:00.000Z", "UTC");
25
+ expect(dt.year).toBe(2024);
26
+ expect(dt.month).toBe(3);
27
+ expect(dt.day).toBe(15);
28
+ });
29
+ test("dateTimeToTimeComponents extracts hour and minute", () => {
30
+ const dt = DateTime.fromObject({ hour: 9, minute: 15 }, { zone: "UTC" });
31
+ expect(dateTimeToTimeComponents(dt)).toEqual({ hour: 9, minute: 15 });
32
+ });
33
+ test("dateTimeFormat returns empty string for nullish", () => {
34
+ expect(dateTimeFormat(null)).toBe("");
35
+ expect(dateTimeFormat(undefined)).toBe("");
36
+ });
37
+ });
38
+ describe("range helpers", () => {
39
+ const start = DateTime.fromISO("2024-01-01T10:00:00", { zone: "UTC" });
40
+ const end = DateTime.fromISO("2024-01-01T18:00:00", { zone: "UTC" });
41
+ const range = { start, end };
42
+ test("dateTimeWithinRange detects containment", () => {
43
+ const inside = DateTime.fromISO("2024-01-01T12:00:00", { zone: "UTC" });
44
+ const outside = DateTime.fromISO("2024-01-01T19:00:00", { zone: "UTC" });
45
+ expect(dateTimeWithinRange(inside, range)).toBe(true);
46
+ expect(dateTimeWithinRange(outside, range)).toBe(false);
47
+ });
48
+ test("dateRangeDurationMatch compares durations within tolerance", () => {
49
+ const lhs = {
50
+ start: DateTime.fromISO("2024-01-01T10:00:00", { zone: "UTC" }),
51
+ end: DateTime.fromISO("2024-01-01T12:00:00", { zone: "UTC" }),
52
+ };
53
+ const rhs = {
54
+ start: DateTime.fromISO("2024-02-01T10:00:00", { zone: "UTC" }),
55
+ end: DateTime.fromISO("2024-02-01T12:00:30", { zone: "UTC" }),
56
+ };
57
+ expect(dateRangeDurationMatch(lhs, rhs, 60)).toBe(true);
58
+ });
59
+ test("dateTimeFormatDateRange uses time-only end on same day", () => {
60
+ const sameDayStart = DateTime.fromISO("2024-06-01T18:00:00", { zone: "UTC" });
61
+ const sameDayEnd = DateTime.fromISO("2024-06-01T22:00:00", { zone: "UTC" });
62
+ const formatted = dateTimeFormatDateRange(sameDayStart, sameDayEnd);
63
+ expect(formatted).toContain(" to ");
64
+ expect(formatted.split(" to ")[1]).toMatch(/\d/);
65
+ });
66
+ test("dateTimeRangeCrossesMidnight detects overnight ranges", () => {
67
+ const overnight = {
68
+ start: DateTime.fromObject({ hour: 22, minute: 0 }, { zone: "UTC" }),
69
+ end: DateTime.fromObject({ hour: 2, minute: 0 }, { zone: "UTC" }),
70
+ };
71
+ const sameDay = {
72
+ start: DateTime.fromObject({ hour: 9, minute: 0 }, { zone: "UTC" }),
73
+ end: DateTime.fromObject({ hour: 17, minute: 0 }, { zone: "UTC" }),
74
+ };
75
+ expect(dateTimeRangeCrossesMidnight(overnight)).toBe(true);
76
+ expect(dateTimeRangeCrossesMidnight(sameDay)).toBe(false);
77
+ });
78
+ });
79
+ describe("mergeCrossDayHours", () => {
80
+ test("merges ranges separated by one minute or less", () => {
81
+ const base = DateTime.fromObject({ hour: 9 }, { zone: "UTC" });
82
+ const merged = mergeCrossDayHours([
83
+ { start: base, end: base.plus({ hours: 2 }) },
84
+ { start: base.plus({ hours: 2, minutes: 1 }), end: base.plus({ hours: 4 }) },
85
+ ]);
86
+ expect(merged).toHaveLength(1);
87
+ expect(merged[0].end.hour).toBe(13);
88
+ });
89
+ test("keeps separate ranges when gap exceeds one minute", () => {
90
+ const base = DateTime.fromObject({ hour: 9 }, { zone: "UTC" });
91
+ const merged = mergeCrossDayHours([
92
+ { start: base, end: base.plus({ hours: 1 }) },
93
+ { start: base.plus({ hours: 2 }), end: base.plus({ hours: 3 }) },
94
+ ]);
95
+ expect(merged).toHaveLength(2);
96
+ });
97
+ });
98
+ describe("filterConflictingIntervals", () => {
99
+ test("subtracts blocked hours from available hours", () => {
100
+ const day = DateTime.fromObject({ hour: 8 }, { zone: "UTC" });
101
+ const available = [{ start: day, end: day.plus({ hours: 8 }) }];
102
+ const blocked = [
103
+ {
104
+ start: day.plus({ hours: 2 }),
105
+ end: day.plus({ hours: 3 }),
106
+ },
107
+ ];
108
+ const remaining = filterConflictingIntervals(available, blocked);
109
+ expect(remaining.length).toBeGreaterThan(0);
110
+ expect(remaining.every((r) => r.end > r.start)).toBe(true);
111
+ });
112
+ });
113
+ describe("utcOffsetMinutesToTimezone", () => {
114
+ test("formats positive and negative offsets", () => {
115
+ expect(utcOffsetMinutesToTimezone(300)).toBe("UTC+05:00");
116
+ expect(utcOffsetMinutesToTimezone(-480)).toBe("UTC-08:00");
117
+ });
118
+ });
119
+ describe("dateTimeDiffHours", () => {
120
+ test("returns 0 when start or end missing", () => {
121
+ expect(dateTimeDiffHours(null, DateTime.now())).toBe(0);
122
+ expect(dateTimeDiffHours(DateTime.now(), null)).toBe(0);
123
+ });
124
+ });
125
+ describe("luxonDateRangeSort", () => {
126
+ test("sorts ranges by start time ascending", () => {
127
+ const later = {
128
+ start: DateTime.fromISO("2024-01-02T10:00:00", { zone: "UTC" }),
129
+ end: DateTime.fromISO("2024-01-02T12:00:00", { zone: "UTC" }),
130
+ };
131
+ const earlier = {
132
+ start: DateTime.fromISO("2024-01-01T10:00:00", { zone: "UTC" }),
133
+ end: DateTime.fromISO("2024-01-01T12:00:00", { zone: "UTC" }),
134
+ };
135
+ expect(luxonDateRangeSort([later, earlier])).toEqual([earlier, later]);
136
+ });
137
+ });
138
+ });
139
+ //# sourceMappingURL=luxonUtils.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"luxonUtils.test.js","sourceRoot":"","sources":["../../../src/utils/__tests__/luxonUtils.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Tests for numeric rounding helpers.
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=mathUtils.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mathUtils.test.d.ts","sourceRoot":"","sources":["../../../src/utils/__tests__/mathUtils.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Tests for numeric rounding helpers.
3
+ */
4
+ import { describe, expect, test } from "@jest/globals";
5
+ import { roundToNearestDivisor } from "../mathUtils.js";
6
+ describe("mathUtils", () => {
7
+ describe("roundToNearestDivisor", () => {
8
+ test("rounds to nearest cent (divisor 100)", () => {
9
+ expect(roundToNearestDivisor(12.345, 100)).toBe(12.35);
10
+ expect(roundToNearestDivisor(12.344, 100)).toBe(12.34);
11
+ });
12
+ test("rounds to nearest tenth (divisor 10)", () => {
13
+ expect(roundToNearestDivisor(3.14, 10)).toBe(3.1);
14
+ expect(roundToNearestDivisor(3.16, 10)).toBe(3.2);
15
+ });
16
+ test("returns whole numbers unchanged when divisor is 1", () => {
17
+ expect(roundToNearestDivisor(42, 1)).toBe(42);
18
+ expect(roundToNearestDivisor(42.6, 1)).toBe(43);
19
+ });
20
+ test("handles zero and negative values", () => {
21
+ expect(roundToNearestDivisor(0, 100)).toBe(0);
22
+ expect(roundToNearestDivisor(-1.235, 100)).toBe(-1.24);
23
+ });
24
+ });
25
+ });
26
+ //# sourceMappingURL=mathUtils.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mathUtils.test.js","sourceRoot":"","sources":["../../../src/utils/__tests__/mathUtils.test.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AACvD,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAExD,QAAQ,CAAC,WAAW,EAAE,GAAG,EAAE;IACzB,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;QACrC,IAAI,CAAC,sCAAsC,EAAE,GAAG,EAAE;YAChD,MAAM,CAAC,qBAAqB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACvD,MAAM,CAAC,qBAAqB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACzD,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,sCAAsC,EAAE,GAAG,EAAE;YAChD,MAAM,CAAC,qBAAqB,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAClD,MAAM,CAAC,qBAAqB,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACpD,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,mDAAmD,EAAE,GAAG,EAAE;YAC7D,MAAM,CAAC,qBAAqB,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAC9C,MAAM,CAAC,qBAAqB,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAClD,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,kCAAkC,EAAE,GAAG,EAAE;YAC5C,MAAM,CAAC,qBAAqB,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAC9C,MAAM,CAAC,qBAAqB,CAAC,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;QACzD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Tests for object type guards and deep merge.
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=objUtils.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"objUtils.test.d.ts","sourceRoot":"","sources":["../../../src/utils/__tests__/objUtils.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}