@bash-app/bash-common 30.112.0 → 30.114.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 (40) hide show
  1. package/dist/definitions.d.ts +2 -2
  2. package/dist/definitions.d.ts.map +1 -1
  3. package/dist/definitions.js +2 -2
  4. package/dist/definitions.js.map +1 -1
  5. package/dist/index.d.ts +2 -0
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +2 -0
  8. package/dist/index.js.map +1 -1
  9. package/dist/utils/__tests__/recurrenceUtils.test.d.ts +2 -0
  10. package/dist/utils/__tests__/recurrenceUtils.test.d.ts.map +1 -0
  11. package/dist/utils/__tests__/recurrenceUtils.test.js +193 -0
  12. package/dist/utils/__tests__/recurrenceUtils.test.js.map +1 -0
  13. package/dist/utils/discountEngine/bestPriceResolver.d.ts +31 -0
  14. package/dist/utils/discountEngine/bestPriceResolver.d.ts.map +1 -0
  15. package/dist/utils/discountEngine/bestPriceResolver.js +147 -0
  16. package/dist/utils/discountEngine/bestPriceResolver.js.map +1 -0
  17. package/dist/utils/discountEngine/discountCalculator.d.ts +56 -0
  18. package/dist/utils/discountEngine/discountCalculator.d.ts.map +1 -0
  19. package/dist/utils/discountEngine/discountCalculator.js +219 -0
  20. package/dist/utils/discountEngine/discountCalculator.js.map +1 -0
  21. package/dist/utils/discountEngine/eligibilityValidator.d.ts +36 -0
  22. package/dist/utils/discountEngine/eligibilityValidator.d.ts.map +1 -0
  23. package/dist/utils/discountEngine/eligibilityValidator.js +189 -0
  24. package/dist/utils/discountEngine/eligibilityValidator.js.map +1 -0
  25. package/dist/utils/discountEngine/index.d.ts +4 -0
  26. package/dist/utils/discountEngine/index.d.ts.map +1 -0
  27. package/dist/utils/discountEngine/index.js +5 -0
  28. package/dist/utils/discountEngine/index.js.map +1 -0
  29. package/dist/utils/recurrenceUtils.d.ts.map +1 -1
  30. package/dist/utils/recurrenceUtils.js +17 -4
  31. package/dist/utils/recurrenceUtils.js.map +1 -1
  32. package/package.json +1 -1
  33. package/prisma/schema.prisma +448 -118
  34. package/src/definitions.ts +2 -1
  35. package/src/index.ts +2 -0
  36. package/src/utils/discountEngine/bestPriceResolver.ts +212 -0
  37. package/src/utils/discountEngine/discountCalculator.ts +281 -0
  38. package/src/utils/discountEngine/eligibilityValidator.ts +256 -0
  39. package/src/utils/discountEngine/index.ts +5 -0
  40. package/src/utils/recurrenceUtils.ts +20 -4
@@ -0,0 +1,147 @@
1
+ import { calculatePromoCodeDiscount, calculateSpecialOfferDiscount, formatDiscountDescription, } from "./discountCalculator";
2
+ /**
3
+ * Find the best price by evaluating all possible discount combinations
4
+ * Implements "best price wins" logic with stacking rules
5
+ */
6
+ export const findBestPrice = (options) => {
7
+ const { ticketPrice, quantity, userId, promoCodes, specialOffers, allowStacking } = options;
8
+ const originalTotal = ticketPrice * quantity;
9
+ // Early return if no discounts available
10
+ if (promoCodes.length === 0 && specialOffers.length === 0) {
11
+ return {
12
+ appliedDiscounts: [],
13
+ originalTotal,
14
+ finalTotal: originalTotal,
15
+ totalSavings: 0,
16
+ explanation: "No discounts applied",
17
+ };
18
+ }
19
+ // Calculate all possible discount results
20
+ const promoCodeResults = promoCodes.map(code => calculatePromoCodeDiscount(code, ticketPrice, quantity));
21
+ const offerResults = specialOffers.map(offer => calculateSpecialOfferDiscount(offer, ticketPrice, quantity, userId));
22
+ // Generate all valid combinations
23
+ const combinations = [];
24
+ // Single promo code only
25
+ promoCodeResults.forEach(promoResult => {
26
+ combinations.push([promoResult]);
27
+ });
28
+ // Single special offer only
29
+ offerResults.forEach(offerResult => {
30
+ combinations.push([offerResult]);
31
+ });
32
+ // If stacking is allowed, try combinations
33
+ if (allowStacking) {
34
+ promoCodeResults.forEach(promoResult => {
35
+ offerResults.forEach(offerResult => {
36
+ // Find the original offer to check stacking rules
37
+ const originalOffer = specialOffers.find(o => o.id === offerResult.sourceId);
38
+ if (originalOffer && originalOffer.canStackWithPromoCodes) {
39
+ // Create a combination of promo code + special offer
40
+ const stackedDiscounts = stackDiscounts([promoResult, offerResult], originalTotal);
41
+ combinations.push(stackedDiscounts);
42
+ }
43
+ });
44
+ });
45
+ // Check if any offers can stack with each other
46
+ for (let i = 0; i < offerResults.length; i++) {
47
+ for (let j = i + 1; j < offerResults.length; j++) {
48
+ const offer1 = specialOffers.find(o => o.id === offerResults[i].sourceId);
49
+ const offer2 = specialOffers.find(o => o.id === offerResults[j].sourceId);
50
+ if (offer1?.canStackWithOtherOffers && offer2?.canStackWithOtherOffers) {
51
+ const stackedDiscounts = stackDiscounts([offerResults[i], offerResults[j]], originalTotal);
52
+ combinations.push(stackedDiscounts);
53
+ }
54
+ }
55
+ }
56
+ }
57
+ // Find the combination with the lowest final price
58
+ let bestCombination = [];
59
+ let lowestPrice = originalTotal;
60
+ combinations.forEach(combo => {
61
+ const totalDiscount = combo.reduce((sum, d) => sum + d.amountDiscounted, 0);
62
+ const finalPrice = Math.max(0, originalTotal - totalDiscount);
63
+ if (finalPrice < lowestPrice) {
64
+ lowestPrice = finalPrice;
65
+ bestCombination = combo;
66
+ }
67
+ });
68
+ // Calculate total savings
69
+ const totalSavings = bestCombination.reduce((sum, d) => sum + d.amountDiscounted, 0);
70
+ // Create explanation
71
+ const explanation = bestCombination.length > 0
72
+ ? bestCombination.map(formatDiscountDescription).join(" + ")
73
+ : "No discounts applied";
74
+ return {
75
+ appliedDiscounts: bestCombination,
76
+ originalTotal,
77
+ finalTotal: lowestPrice,
78
+ totalSavings,
79
+ explanation,
80
+ };
81
+ };
82
+ /**
83
+ * Stack multiple discounts, applying guardrails to ensure they work correctly together
84
+ * Discounts are applied sequentially, not compounded
85
+ */
86
+ const stackDiscounts = (discounts, originalTotal) => {
87
+ // Sort by priority (percentage discounts first, then fixed amounts)
88
+ const sorted = [...discounts].sort((a, b) => {
89
+ if (a.discountType === 'PERCENTAGE' && b.discountType !== 'PERCENTAGE')
90
+ return -1;
91
+ if (a.discountType !== 'PERCENTAGE' && b.discountType === 'PERCENTAGE')
92
+ return 1;
93
+ return 0;
94
+ });
95
+ // Apply discounts sequentially
96
+ let runningTotal = originalTotal;
97
+ const stackedResults = [];
98
+ sorted.forEach(discount => {
99
+ // Recalculate discount based on current running total
100
+ let actualDiscount = discount.amountDiscounted;
101
+ if (discount.discountType === 'PERCENTAGE') {
102
+ // Apply percentage to current running total
103
+ actualDiscount = Math.floor((runningTotal * discount.discountValue) / 100);
104
+ }
105
+ else {
106
+ // Fixed amount, but don't exceed running total
107
+ actualDiscount = Math.min(discount.amountDiscounted, runningTotal);
108
+ }
109
+ runningTotal = Math.max(0, runningTotal - actualDiscount);
110
+ stackedResults.push({
111
+ ...discount,
112
+ amountDiscounted: actualDiscount,
113
+ finalPrice: runningTotal,
114
+ });
115
+ });
116
+ return stackedResults;
117
+ };
118
+ /**
119
+ * Apply guardrails to ensure discount doesn't violate business rules
120
+ */
121
+ export const applyGuardrails = (result, minFloor, maxDiscountPercent) => {
122
+ let adjustedResult = { ...result };
123
+ // Enforce minimum price floor
124
+ if (minFloor && adjustedResult.finalPrice < minFloor) {
125
+ const maxAllowedDiscount = adjustedResult.originalPrice - minFloor;
126
+ adjustedResult.amountDiscounted = Math.min(adjustedResult.amountDiscounted, maxAllowedDiscount);
127
+ adjustedResult.finalPrice = adjustedResult.originalPrice - adjustedResult.amountDiscounted;
128
+ }
129
+ // Enforce maximum discount percentage
130
+ if (maxDiscountPercent) {
131
+ const maxAllowedDiscount = Math.floor((adjustedResult.originalPrice * maxDiscountPercent) / 100);
132
+ if (adjustedResult.amountDiscounted > maxAllowedDiscount) {
133
+ adjustedResult.amountDiscounted = maxAllowedDiscount;
134
+ adjustedResult.finalPrice = adjustedResult.originalPrice - maxAllowedDiscount;
135
+ }
136
+ }
137
+ return adjustedResult;
138
+ };
139
+ /**
140
+ * Calculate the effective discount rate as a percentage
141
+ */
142
+ export const calculateEffectiveRate = (result) => {
143
+ if (result.originalTotal === 0)
144
+ return 0;
145
+ return Math.floor((result.totalSavings / result.originalTotal) * 100);
146
+ };
147
+ //# sourceMappingURL=bestPriceResolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bestPriceResolver.js","sourceRoot":"","sources":["../../../src/utils/discountEngine/bestPriceResolver.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ import { BashEventPromoCode, SpecialOffer } from "@prisma/client";
2
+ export interface DiscountResult {
3
+ sourceType: 'PROMO_CODE' | 'SPECIAL_OFFER';
4
+ sourceId: string;
5
+ sourceName: string;
6
+ discountType: 'PERCENTAGE' | 'FIXED_AMOUNT';
7
+ discountValue: number;
8
+ amountDiscounted: number;
9
+ originalPrice: number;
10
+ finalPrice: number;
11
+ attribution?: {
12
+ userId: string;
13
+ commissionRate: number;
14
+ commissionAmount: number;
15
+ };
16
+ }
17
+ /**
18
+ * Calculate discount from a promo code
19
+ */
20
+ export declare const calculatePromoCodeDiscount: (promoCode: BashEventPromoCode, ticketPrice: number, // In cents
21
+ quantity: number) => DiscountResult;
22
+ /**
23
+ * Calculate discount from a special offer
24
+ */
25
+ export declare const calculateSpecialOfferDiscount: (offer: SpecialOffer, ticketPrice: number, // In cents
26
+ quantity: number, userId?: string) => DiscountResult;
27
+ /**
28
+ * Calculate BOGO (Buy One Get One) discount
29
+ * Example: Buy 2 get 1 free, Buy 3 get 2 at 50% off
30
+ */
31
+ export declare const calculateBOGODiscount: (offer: SpecialOffer, quantity: number, ticketPrice: number) => number;
32
+ /**
33
+ * Calculate group discount based on quantity
34
+ */
35
+ export declare const calculateGroupDiscount: (offer: SpecialOffer, quantity: number, ticketPrice: number) => number;
36
+ /**
37
+ * Calculate volume discount (buy more save more)
38
+ * Similar to group discount but typically for larger quantities
39
+ */
40
+ export declare const calculateVolumeDiscount: (offer: SpecialOffer, quantity: number, ticketPrice: number) => number;
41
+ /**
42
+ * Calculate early bird discount (time-based)
43
+ */
44
+ export declare const calculateEarlyBirdDiscount: (offer: SpecialOffer, ticketPrice: number, currentDate: Date) => number;
45
+ /**
46
+ * Calculate bundle discount
47
+ */
48
+ export declare const calculateBundleDiscount: (offer: SpecialOffer, bundledItems: Array<{
49
+ type: string;
50
+ price: number;
51
+ }>) => number;
52
+ /**
53
+ * Helper: Convert discount result to human-readable string
54
+ */
55
+ export declare const formatDiscountDescription: (result: DiscountResult) => string;
56
+ //# sourceMappingURL=discountCalculator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discountCalculator.d.ts","sourceRoot":"","sources":["../../../src/utils/discountEngine/discountCalculator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,YAAY,EAAuC,MAAM,gBAAgB,CAAC;AAGvG,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,YAAY,GAAG,eAAe,CAAC;IAC3C,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,YAAY,GAAG,cAAc,CAAC;IAC5C,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE;QACZ,MAAM,EAAE,MAAM,CAAC;QACf,cAAc,EAAE,MAAM,CAAC;QACvB,gBAAgB,EAAE,MAAM,CAAC;KAC1B,CAAC;CACH;AAED;;GAEG;AACH,eAAO,MAAM,0BAA0B,GACrC,WAAW,kBAAkB,EAC7B,aAAa,MAAM,EAAE,WAAW;AAChC,UAAU,MAAM,KACf,cAsCF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,6BAA6B,GACxC,OAAO,YAAY,EACnB,aAAa,MAAM,EAAE,WAAW;AAChC,UAAU,MAAM,EAChB,SAAS,MAAM,KACd,cA2FF,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,qBAAqB,GAChC,OAAO,YAAY,EACnB,UAAU,MAAM,EAChB,aAAa,MAAM,KAClB,MAYF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,sBAAsB,GACjC,OAAO,YAAY,EACnB,UAAU,MAAM,EAChB,aAAa,MAAM,KAClB,MA4BF,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,uBAAuB,GAClC,OAAO,YAAY,EACnB,UAAU,MAAM,EAChB,aAAa,MAAM,KAClB,MAGF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,0BAA0B,GACrC,OAAO,YAAY,EACnB,aAAa,MAAM,EACnB,aAAa,IAAI,KAChB,MAWF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,uBAAuB,GAClC,OAAO,YAAY,EACnB,cAAc,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,KACnD,MAGF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,yBAAyB,GAAI,QAAQ,cAAc,KAAG,MAMlE,CAAC"}
@@ -0,0 +1,219 @@
1
+ import { SpecialOfferType, OfferDiscountType } from "@prisma/client";
2
+ import { convertCentsToDollars } from "../paymentUtils";
3
+ /**
4
+ * Calculate discount from a promo code
5
+ */
6
+ export const calculatePromoCodeDiscount = (promoCode, ticketPrice, // In cents
7
+ quantity) => {
8
+ const originalTotal = ticketPrice * quantity;
9
+ let amountDiscounted = 0;
10
+ let discountType = 'PERCENTAGE';
11
+ let discountValue = 0;
12
+ if (promoCode.discountAmountPercentage) {
13
+ // Percentage discount
14
+ discountType = 'PERCENTAGE';
15
+ discountValue = promoCode.discountAmountPercentage;
16
+ amountDiscounted = Math.floor((originalTotal * promoCode.discountAmountPercentage) / 100);
17
+ }
18
+ else if (promoCode.discountAmountInCents) {
19
+ // Fixed amount discount (per order, not per ticket)
20
+ discountType = 'FIXED_AMOUNT';
21
+ discountValue = promoCode.discountAmountInCents;
22
+ amountDiscounted = Math.min(promoCode.discountAmountInCents, originalTotal);
23
+ }
24
+ const finalPrice = Math.max(0, originalTotal - amountDiscounted);
25
+ return {
26
+ sourceType: 'PROMO_CODE',
27
+ sourceId: promoCode.id,
28
+ sourceName: promoCode.code,
29
+ discountType,
30
+ discountValue,
31
+ amountDiscounted,
32
+ originalPrice: originalTotal,
33
+ finalPrice,
34
+ // Promo codes always have attribution if there's a promoter
35
+ ...(promoCode.promoterId && {
36
+ attribution: {
37
+ userId: promoCode.promoterId,
38
+ commissionRate: 5.0, // Default 5% commission
39
+ commissionAmount: Math.floor(amountDiscounted * 0.05),
40
+ },
41
+ }),
42
+ };
43
+ };
44
+ /**
45
+ * Calculate discount from a special offer
46
+ */
47
+ export const calculateSpecialOfferDiscount = (offer, ticketPrice, // In cents
48
+ quantity, userId) => {
49
+ const originalTotal = ticketPrice * quantity;
50
+ let amountDiscounted = 0;
51
+ let discountType = 'PERCENTAGE';
52
+ let discountValue = offer.discountValue;
53
+ // Calculate based on offer type
54
+ switch (offer.offerType) {
55
+ case SpecialOfferType.BOGO:
56
+ amountDiscounted = calculateBOGODiscount(offer, quantity, ticketPrice);
57
+ discountType = 'FREE_ITEMS'; // Special case
58
+ break;
59
+ case SpecialOfferType.GROUP_DISCOUNT:
60
+ amountDiscounted = calculateGroupDiscount(offer, quantity, ticketPrice);
61
+ discountType = offer.discountType === OfferDiscountType.PERCENTAGE ? 'PERCENTAGE' : 'FIXED_AMOUNT';
62
+ break;
63
+ case SpecialOfferType.VOLUME_DISCOUNT:
64
+ amountDiscounted = calculateVolumeDiscount(offer, quantity, ticketPrice);
65
+ discountType = offer.discountType === OfferDiscountType.PERCENTAGE ? 'PERCENTAGE' : 'FIXED_AMOUNT';
66
+ break;
67
+ case SpecialOfferType.EARLY_BIRD:
68
+ case SpecialOfferType.FLASH_SALE:
69
+ case SpecialOfferType.LOYALTY:
70
+ case SpecialOfferType.COUNTDOWN:
71
+ // These are simple percentage or fixed amount discounts
72
+ if (offer.discountType === OfferDiscountType.PERCENTAGE) {
73
+ discountType = 'PERCENTAGE';
74
+ amountDiscounted = Math.floor((originalTotal * offer.discountValue) / 100);
75
+ }
76
+ else {
77
+ discountType = 'FIXED_AMOUNT';
78
+ amountDiscounted = Math.min(offer.discountValue * quantity, originalTotal);
79
+ }
80
+ break;
81
+ case SpecialOfferType.BUNDLE:
82
+ // Bundle savings are pre-calculated
83
+ amountDiscounted = offer.bundleSavings || 0;
84
+ discountType = 'FIXED_AMOUNT';
85
+ break;
86
+ case SpecialOfferType.REFERRAL:
87
+ // Referral discounts apply to both parties
88
+ amountDiscounted = offer.referralDiscountForBuyer || 0;
89
+ discountType = 'FIXED_AMOUNT';
90
+ break;
91
+ case SpecialOfferType.FAMILY_PACK:
92
+ // Family pack is typically a percentage off
93
+ if (offer.discountType === OfferDiscountType.PERCENTAGE) {
94
+ discountType = 'PERCENTAGE';
95
+ amountDiscounted = Math.floor((originalTotal * offer.discountValue) / 100);
96
+ }
97
+ else {
98
+ discountType = 'FIXED_AMOUNT';
99
+ amountDiscounted = Math.min(offer.discountValue, originalTotal);
100
+ }
101
+ break;
102
+ default:
103
+ // Default to simple discount
104
+ if (offer.discountType === OfferDiscountType.PERCENTAGE) {
105
+ discountType = 'PERCENTAGE';
106
+ amountDiscounted = Math.floor((originalTotal * offer.discountValue) / 100);
107
+ }
108
+ else {
109
+ discountType = 'FIXED_AMOUNT';
110
+ amountDiscounted = Math.min(offer.discountValue, originalTotal);
111
+ }
112
+ }
113
+ const finalPrice = Math.max(0, originalTotal - amountDiscounted);
114
+ return {
115
+ sourceType: 'SPECIAL_OFFER',
116
+ sourceId: offer.id,
117
+ sourceName: offer.title,
118
+ discountType,
119
+ discountValue,
120
+ amountDiscounted,
121
+ originalPrice: originalTotal,
122
+ finalPrice,
123
+ // Only referral offers have attribution
124
+ ...(offer.requiresAttribution && userId && offer.commissionRate && {
125
+ attribution: {
126
+ userId,
127
+ commissionRate: offer.commissionRate,
128
+ commissionAmount: Math.floor(amountDiscounted * (offer.commissionRate / 100)),
129
+ },
130
+ }),
131
+ };
132
+ };
133
+ /**
134
+ * Calculate BOGO (Buy One Get One) discount
135
+ * Example: Buy 2 get 1 free, Buy 3 get 2 at 50% off
136
+ */
137
+ export const calculateBOGODiscount = (offer, quantity, ticketPrice) => {
138
+ const buyQuantity = offer.buyQuantity || 1;
139
+ const getQuantity = offer.getQuantity || 1;
140
+ const getDiscountPercent = offer.getDiscountPercent || 100; // Default 100% off (free)
141
+ // Calculate how many sets of the offer apply
142
+ const sets = Math.floor(quantity / (buyQuantity + getQuantity));
143
+ const freeTickets = sets * getQuantity;
144
+ // Calculate discount on free tickets
145
+ const discountPerTicket = Math.floor((ticketPrice * getDiscountPercent) / 100);
146
+ return freeTickets * discountPerTicket;
147
+ };
148
+ /**
149
+ * Calculate group discount based on quantity
150
+ */
151
+ export const calculateGroupDiscount = (offer, quantity, ticketPrice) => {
152
+ // Check if quantity qualifies
153
+ if (offer.minGroupSize && quantity < offer.minGroupSize) {
154
+ return 0;
155
+ }
156
+ if (offer.maxGroupSize && quantity > offer.maxGroupSize) {
157
+ return 0; // Or cap at max group size
158
+ }
159
+ // Check for tiered discounts
160
+ if (offer.tieredDiscounts) {
161
+ const tiers = offer.tieredDiscounts;
162
+ // Find the highest tier that applies
163
+ const applicableTier = tiers
164
+ .filter(tier => quantity >= tier.qty)
165
+ .sort((a, b) => b.discount - a.discount)[0];
166
+ if (applicableTier) {
167
+ return Math.floor((ticketPrice * quantity * applicableTier.discount) / 100);
168
+ }
169
+ }
170
+ // Fall back to simple discount
171
+ if (offer.discountType === OfferDiscountType.PERCENTAGE) {
172
+ return Math.floor((ticketPrice * quantity * offer.discountValue) / 100);
173
+ }
174
+ else {
175
+ return Math.min(offer.discountValue * quantity, ticketPrice * quantity);
176
+ }
177
+ };
178
+ /**
179
+ * Calculate volume discount (buy more save more)
180
+ * Similar to group discount but typically for larger quantities
181
+ */
182
+ export const calculateVolumeDiscount = (offer, quantity, ticketPrice) => {
183
+ // Reuse group discount logic
184
+ return calculateGroupDiscount(offer, quantity, ticketPrice);
185
+ };
186
+ /**
187
+ * Calculate early bird discount (time-based)
188
+ */
189
+ export const calculateEarlyBirdDiscount = (offer, ticketPrice, currentDate) => {
190
+ // Check if we're still in early bird period
191
+ if (offer.availableUntil && currentDate > offer.availableUntil) {
192
+ return 0;
193
+ }
194
+ if (offer.discountType === OfferDiscountType.PERCENTAGE) {
195
+ return Math.floor((ticketPrice * offer.discountValue) / 100);
196
+ }
197
+ else {
198
+ return Math.min(offer.discountValue, ticketPrice);
199
+ }
200
+ };
201
+ /**
202
+ * Calculate bundle discount
203
+ */
204
+ export const calculateBundleDiscount = (offer, bundledItems) => {
205
+ // Bundle savings are pre-calculated and stored in the offer
206
+ return offer.bundleSavings || 0;
207
+ };
208
+ /**
209
+ * Helper: Convert discount result to human-readable string
210
+ */
211
+ export const formatDiscountDescription = (result) => {
212
+ if (result.discountType === 'PERCENTAGE') {
213
+ return `${result.sourceName}: -${result.discountValue}% ($${convertCentsToDollars(result.amountDiscounted).toFixed(2)})`;
214
+ }
215
+ else {
216
+ return `${result.sourceName}: -$${convertCentsToDollars(result.amountDiscounted).toFixed(2)}`;
217
+ }
218
+ };
219
+ //# sourceMappingURL=discountCalculator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discountCalculator.js","sourceRoot":"","sources":["../../../src/utils/discountEngine/discountCalculator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ import { BashEventPromoCode, SpecialOffer } from "@prisma/client";
2
+ export interface EligibilityResult {
3
+ eligible: boolean;
4
+ reason?: string;
5
+ }
6
+ /**
7
+ * Validate if a user is eligible to use a promo code
8
+ */
9
+ export declare const validatePromoCodeEligibility: (promoCode: BashEventPromoCode, userId: string, usersWhoUsedCode?: string[]) => EligibilityResult;
10
+ /**
11
+ * Validate if a user is eligible for a special offer
12
+ */
13
+ export declare const validateOfferEligibility: (offer: SpecialOffer, userId: string, quantity: number, currentDate: Date, userRedemptionCount?: number) => EligibilityResult;
14
+ /**
15
+ * Validate if an offer can stack with promo codes
16
+ */
17
+ export declare const validateOfferStacking: (offer: SpecialOffer, hasPromoCode: boolean, hasOtherOffer: boolean) => EligibilityResult;
18
+ /**
19
+ * Validate loyalty offer requirements
20
+ */
21
+ export declare const validateLoyaltyRequirements: (offer: SpecialOffer, userEventAttendanceCount: number, isHostEvent: boolean) => EligibilityResult;
22
+ /**
23
+ * Batch validate multiple offers for a user
24
+ */
25
+ export declare const validateMultipleOffers: (offers: SpecialOffer[], userId: string, quantity: number, currentDate: Date, userRedemptions: Map<string, number>) => Map<string, EligibilityResult>;
26
+ /**
27
+ * Get time remaining until offer expires (for countdown timers)
28
+ */
29
+ export declare const getTimeRemaining: (offer: SpecialOffer, currentDate: Date) => {
30
+ expired: boolean;
31
+ days: number;
32
+ hours: number;
33
+ minutes: number;
34
+ seconds: number;
35
+ };
36
+ //# sourceMappingURL=eligibilityValidator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eligibilityValidator.d.ts","sourceRoot":"","sources":["../../../src/utils/discountEngine/eligibilityValidator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,YAAY,EAAoB,MAAM,gBAAgB,CAAC;AAEpF,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,eAAO,MAAM,4BAA4B,GACvC,WAAW,kBAAkB,EAC7B,QAAQ,MAAM,EACd,mBAAkB,MAAM,EAAO,KAC9B,iBA0BF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,wBAAwB,GACnC,OAAO,YAAY,EACnB,QAAQ,MAAM,EACd,UAAU,MAAM,EAChB,aAAa,IAAI,EACjB,sBAAqB,MAAU,KAC9B,iBAgGF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,qBAAqB,GAChC,OAAO,YAAY,EACnB,cAAc,OAAO,EACrB,eAAe,OAAO,KACrB,iBAgBF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,2BAA2B,GACtC,OAAO,YAAY,EACnB,0BAA0B,MAAM,EAChC,aAAa,OAAO,KACnB,iBAsBF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,sBAAsB,GACjC,QAAQ,YAAY,EAAE,EACtB,QAAQ,MAAM,EACd,UAAU,MAAM,EAChB,aAAa,IAAI,EACjB,iBAAiB,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,KACnC,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAU/B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,gBAAgB,GAAI,OAAO,YAAY,EAAE,aAAa,IAAI,KAAG;IACxE,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CAoBjB,CAAC"}