@roomstay/frontend 2.6.98 → 2.6.99
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/328.bundle.js +1 -1
- package/dist/370.bundle.js +1 -1
- package/dist/978.bundle.js +1 -1
- package/dist/main.bundle.js +1 -1
- package/dist/src/api/AvailabilityAPI.d.ts +3 -2
- package/dist/src/api/AvailabilityAPI.js +9 -5
- package/dist/src/api/AvailabilityAPI.js.map +1 -1
- package/dist/src/components/steps/addons/AddonCard.js +114 -9
- package/dist/src/components/steps/addons/AddonCard.js.map +1 -1
- package/dist/src/components/steps/room/RoomList.js.map +1 -1
- package/dist/src/components/steps/room/RoomModal.js +1 -1
- package/dist/src/components/steps/room/RoomModal.js.map +1 -1
- package/dist/src/components/steps/room/TabGroupedRooms/TabGroupedRooms.js.map +1 -1
- package/dist/src/components/summary/BEMobileSummaryModal.js +4 -3
- package/dist/src/components/summary/BEMobileSummaryModal.js.map +1 -1
- package/dist/src/components/summary/BESummary.js +4 -3
- package/dist/src/components/summary/BESummary.js.map +1 -1
- package/dist/src/components/summary/BESummaryAddonRow.d.ts +3 -0
- package/dist/src/components/summary/BESummaryAddonRow.js +16 -4
- package/dist/src/components/summary/BESummaryAddonRow.js.map +1 -1
- package/dist/src/contexts/BasketContext/BasketContextType.d.ts +3 -1
- package/dist/src/contexts/BasketContext/BasketContextType.js.map +1 -1
- package/dist/src/contexts/BasketContext/BasketContextWrapper.js +38 -4
- package/dist/src/contexts/BasketContext/BasketContextWrapper.js.map +1 -1
- package/dist/src/contexts/CompanyContext/CompanyContextWrapper.js +1 -1
- package/dist/src/contexts/CompanyContext/CompanyContextWrapper.js.map +1 -1
- package/dist/src/contexts/FullPageEngineContext/FullPageEngineContextWrapper.js +6 -5
- package/dist/src/contexts/FullPageEngineContext/FullPageEngineContextWrapper.js.map +1 -1
- package/dist/src/engines/InlineRoomMiniEngine/InlineRoomMiniEngineElement.js +5 -3
- package/dist/src/engines/InlineRoomMiniEngine/InlineRoomMiniEngineElement.js.map +1 -1
- package/dist/src/models/BasketAddonRow.d.ts +27 -0
- package/dist/src/models/BasketAddonRow.js +50 -0
- package/dist/src/models/BasketAddonRow.js.map +1 -1
- package/dist/src/utils/AddonDiscountCalculator.d.ts +79 -0
- package/dist/src/utils/AddonDiscountCalculator.js +142 -0
- package/dist/src/utils/AddonDiscountCalculator.js.map +1 -0
- package/dist/test.bundle.js +1 -1
- package/dist/vendors.bundle.js +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AddonDiscountCalculator = void 0;
|
|
4
|
+
const core_1 = require("@roomstay/core");
|
|
5
|
+
/**
|
|
6
|
+
* For `flatQuantity` and `flatQuantityPerStay` promo codes, discount allocation is SEQUENTIAL:
|
|
7
|
+
* The discount is consumed in this order:
|
|
8
|
+
* 1. Room rates
|
|
9
|
+
* 2. First addon added to basket
|
|
10
|
+
* 3. Second addon added to basket
|
|
11
|
+
* 4. Etc
|
|
12
|
+
*
|
|
13
|
+
* For `flatPercent` types, order doesn't matter as each addon gets its own percentage discount.
|
|
14
|
+
*/
|
|
15
|
+
class AddonDiscountCalculator {
|
|
16
|
+
/**
|
|
17
|
+
* Calculate total price of all addons with promo code discounts applied.
|
|
18
|
+
* Discounts are applied sequentially for flatQuantityPerStay promo codes.
|
|
19
|
+
*
|
|
20
|
+
* @param addonRows - Array of basket addon rows (ORDER MATTERS for flatQuantityPerStay!)
|
|
21
|
+
* @param roomRows - Array of basket room rows
|
|
22
|
+
* @param promoCodeDetails - Promo code details from basket context
|
|
23
|
+
* @returns Total price of all addons after discounts
|
|
24
|
+
*/
|
|
25
|
+
static calculateTotal(addonRows, roomRows, promoCodeDetails) {
|
|
26
|
+
if (!(promoCodeDetails === null || promoCodeDetails === void 0 ? void 0 : promoCodeDetails.applyToAddons)) {
|
|
27
|
+
return addonRows.reduce((total, row) => total + row.getTotalPrice(), 0);
|
|
28
|
+
}
|
|
29
|
+
const individualDiscounts = this.calculateIndividualDiscounts(addonRows, roomRows, promoCodeDetails);
|
|
30
|
+
return individualDiscounts.reduce((total, { addon, discount }) => {
|
|
31
|
+
return total + addon.getTotalPrice() - discount;
|
|
32
|
+
}, 0);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Calculate discount for each addon individually with remaining discount tracking.
|
|
36
|
+
* Useful for displaying discounts in the UI where you need to show each addon's
|
|
37
|
+
* discount separately.
|
|
38
|
+
*
|
|
39
|
+
* @param addonRows - Array of basket addon rows (ORDER MATTERS!)
|
|
40
|
+
* @param roomRows - Array of basket room rows
|
|
41
|
+
* @param promoCodeDetails - Promo code details
|
|
42
|
+
* @returns Array of objects with addon, its discount, and remaining discount available
|
|
43
|
+
*/
|
|
44
|
+
static calculateIndividualDiscounts(addonRows, roomRows, promoCodeDetails) {
|
|
45
|
+
if (!(promoCodeDetails === null || promoCodeDetails === void 0 ? void 0 : promoCodeDetails.applyToAddons)) {
|
|
46
|
+
return addonRows.map((addon) => ({ addon, discount: 0, remainingDiscount: 0 }));
|
|
47
|
+
}
|
|
48
|
+
const { totalDiscountAvailable, runningDiscount: initialRunningDiscount } = this.getDiscountInfo(roomRows, promoCodeDetails);
|
|
49
|
+
let runningDiscount = initialRunningDiscount;
|
|
50
|
+
const results = [];
|
|
51
|
+
for (const addon of addonRows) {
|
|
52
|
+
const remainingFixedDiscount = this.getRemainingFixedDiscount(promoCodeDetails, totalDiscountAvailable, runningDiscount);
|
|
53
|
+
const discount = addon.getDiscountAmount(promoCodeDetails, remainingFixedDiscount);
|
|
54
|
+
results.push({ addon, discount, remainingDiscount: remainingFixedDiscount });
|
|
55
|
+
runningDiscount += discount;
|
|
56
|
+
}
|
|
57
|
+
return results;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Calculate discount for an addon being previewed (not yet in basket).
|
|
61
|
+
* This is used in the addon selection UI to show what discount would apply.
|
|
62
|
+
*
|
|
63
|
+
* For flatQuantityPerStay promos, this calculates the remaining discount after
|
|
64
|
+
* accounting for rooms and OTHER addons already in the basket.
|
|
65
|
+
*
|
|
66
|
+
* @param basePrice - Base price of the addon (before discount, before fees)
|
|
67
|
+
* @param addonRows - Array of basket addon rows (addons already in basket)
|
|
68
|
+
* @param roomRows - Array of basket room rows
|
|
69
|
+
* @param promoCodeDetails - Promo code details
|
|
70
|
+
* @returns Discount amount that would apply to this addon
|
|
71
|
+
*/
|
|
72
|
+
static calculatePreviewDiscount(basePrice, addonRows, roomRows, promoCodeDetails) {
|
|
73
|
+
if (!(promoCodeDetails === null || promoCodeDetails === void 0 ? void 0 : promoCodeDetails.applyToAddons) || basePrice <= 0) {
|
|
74
|
+
return 0;
|
|
75
|
+
}
|
|
76
|
+
switch (promoCodeDetails.discountType) {
|
|
77
|
+
case core_1.EPromoCodeDiscountType.FlatPercent:
|
|
78
|
+
return Math.round(basePrice * (promoCodeDetails.discountAmount / 100));
|
|
79
|
+
case core_1.EPromoCodeDiscountType.FlatQuantity:
|
|
80
|
+
case core_1.EPromoCodeDiscountType.FlatQuantityPerStay: {
|
|
81
|
+
const { totalDiscountAvailable, runningDiscount: initialRunningDiscount } = this.getDiscountInfo(roomRows, promoCodeDetails);
|
|
82
|
+
// Calculate discount consumed by other addons in basket
|
|
83
|
+
let runningDiscount = initialRunningDiscount;
|
|
84
|
+
addonRows.forEach((row) => {
|
|
85
|
+
const addonRemainingDiscount = this.getRemainingFixedDiscount(promoCodeDetails, totalDiscountAvailable, runningDiscount);
|
|
86
|
+
const addonDiscount = row.getDiscountAmount(promoCodeDetails, addonRemainingDiscount);
|
|
87
|
+
runningDiscount += addonDiscount;
|
|
88
|
+
});
|
|
89
|
+
// Calculate remaining discount for this preview addon
|
|
90
|
+
const remainingDiscount = this.getRemainingFixedDiscount(promoCodeDetails, totalDiscountAvailable, runningDiscount);
|
|
91
|
+
return Math.min(remainingDiscount, basePrice);
|
|
92
|
+
}
|
|
93
|
+
default:
|
|
94
|
+
return 0;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Get discount info including total available and initial running discount from rooms.
|
|
99
|
+
*
|
|
100
|
+
* @param roomRows - Array of basket room rows
|
|
101
|
+
* @param promoCodeDetails - Promo code details
|
|
102
|
+
* @returns Object with totalDiscountAvailable and runningDiscount (from rooms)
|
|
103
|
+
*/
|
|
104
|
+
static getDiscountInfo(roomRows, promoCodeDetails) {
|
|
105
|
+
const nights = roomRows.length > 0 ? roomRows[0].getNumberOfDays() : 0;
|
|
106
|
+
const roomDiscount = this.calculateRoomDiscount(roomRows);
|
|
107
|
+
const totalDiscountAvailable = promoCodeDetails.discountType === core_1.EPromoCodeDiscountType.FlatQuantity ? promoCodeDetails.discountAmount * nights : promoCodeDetails.discountAmount;
|
|
108
|
+
return {
|
|
109
|
+
totalDiscountAvailable,
|
|
110
|
+
runningDiscount: roomDiscount,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Calculate remaining fixed discount available.
|
|
115
|
+
*
|
|
116
|
+
* @param promoCodeDetails - Promo code details
|
|
117
|
+
* @param totalDiscountAvailable - Total discount available
|
|
118
|
+
* @param runningDiscount - Discount already consumed
|
|
119
|
+
* @returns Remaining discount available (0 for FlatPercent)
|
|
120
|
+
*/
|
|
121
|
+
static getRemainingFixedDiscount(promoCodeDetails, totalDiscountAvailable, runningDiscount) {
|
|
122
|
+
if (promoCodeDetails.discountType === core_1.EPromoCodeDiscountType.FlatQuantity || promoCodeDetails.discountType === core_1.EPromoCodeDiscountType.FlatQuantityPerStay) {
|
|
123
|
+
return Math.max(0, totalDiscountAvailable - runningDiscount);
|
|
124
|
+
}
|
|
125
|
+
return 0;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Calculate total discount already consumed by room rates.
|
|
129
|
+
* This is used as the starting point for addon discount calculations.
|
|
130
|
+
*
|
|
131
|
+
* @param roomRows - Array of basket room rows
|
|
132
|
+
* @returns Total discount amount consumed by all rooms
|
|
133
|
+
*/
|
|
134
|
+
static calculateRoomDiscount(roomRows) {
|
|
135
|
+
return roomRows.reduce((total, row) => {
|
|
136
|
+
const rate = row.getRate();
|
|
137
|
+
return total + ((rate === null || rate === void 0 ? void 0 : rate.getTotalDiscount()) || 0);
|
|
138
|
+
}, 0);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
exports.AddonDiscountCalculator = AddonDiscountCalculator;
|
|
142
|
+
//# sourceMappingURL=AddonDiscountCalculator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AddonDiscountCalculator.js","sourceRoot":"/","sources":["src/utils/AddonDiscountCalculator.ts"],"names":[],"mappings":";;;AAAA,yCAA2E;AAK3E;;;;;;;;;GASG;AACH,MAAa,uBAAuB;IAChC;;;;;;;;OAQG;IACH,MAAM,CAAC,cAAc,CAAC,SAA2B,EAAE,QAAqB,EAAE,gBAA0C;QAChH,IAAI,CAAC,CAAA,gBAAgB,aAAhB,gBAAgB,uBAAhB,gBAAgB,CAAE,aAAa,CAAA,EAAE,CAAC;YACnC,OAAO,SAAS,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,KAAK,GAAG,GAAG,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC,CAAC;QAC5E,CAAC;QAED,MAAM,mBAAmB,GAAG,IAAI,CAAC,4BAA4B,CAAC,SAAS,EAAE,QAAQ,EAAE,gBAAgB,CAAC,CAAC;QAErG,OAAO,mBAAmB,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,EAAE;YAC7D,OAAO,KAAK,GAAG,KAAK,CAAC,aAAa,EAAE,GAAG,QAAQ,CAAC;QACpD,CAAC,EAAE,CAAC,CAAC,CAAC;IACV,CAAC;IAED;;;;;;;;;OASG;IACH,MAAM,CAAC,4BAA4B,CAC/B,SAA2B,EAC3B,QAAqB,EACrB,gBAA0C;QAE1C,IAAI,CAAC,CAAA,gBAAgB,aAAhB,gBAAgB,uBAAhB,gBAAgB,CAAE,aAAa,CAAA,EAAE,CAAC;YACnC,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,EAAE,iBAAiB,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QACpF,CAAC;QAED,MAAM,EAAE,sBAAsB,EAAE,eAAe,EAAE,sBAAsB,EAAE,GAAG,IAAI,CAAC,eAAe,CAAC,QAAQ,EAAE,gBAAgB,CAAC,CAAC;QAE7H,IAAI,eAAe,GAAG,sBAAsB,CAAC;QAC7C,MAAM,OAAO,GAAkF,EAAE,CAAC;QAElG,KAAK,MAAM,KAAK,IAAI,SAAS,EAAE,CAAC;YAC5B,MAAM,sBAAsB,GAAG,IAAI,CAAC,yBAAyB,CAAC,gBAAgB,EAAE,sBAAsB,EAAE,eAAe,CAAC,CAAC;YACzH,MAAM,QAAQ,GAAG,KAAK,CAAC,iBAAiB,CAAC,gBAAgB,EAAE,sBAAsB,CAAC,CAAC;YAEnF,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,CAAC,CAAC;YAC7E,eAAe,IAAI,QAAQ,CAAC;QAChC,CAAC;QAED,OAAO,OAAO,CAAC;IACnB,CAAC;IAED;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,wBAAwB,CAAC,SAAiB,EAAE,SAA2B,EAAE,QAAqB,EAAE,gBAA0C;QAC7I,IAAI,CAAC,CAAA,gBAAgB,aAAhB,gBAAgB,uBAAhB,gBAAgB,CAAE,aAAa,CAAA,IAAI,SAAS,IAAI,CAAC,EAAE,CAAC;YACrD,OAAO,CAAC,CAAC;QACb,CAAC;QAED,QAAQ,gBAAgB,CAAC,YAAY,EAAE,CAAC;YACpC,KAAK,6BAAsB,CAAC,WAAW;gBACnC,OAAO,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,gBAAgB,CAAC,cAAc,GAAG,GAAG,CAAC,CAAC,CAAC;YAE3E,KAAK,6BAAsB,CAAC,YAAY,CAAC;YACzC,KAAK,6BAAsB,CAAC,mBAAmB,CAAC,CAAC,CAAC;gBAC9C,MAAM,EAAE,sBAAsB,EAAE,eAAe,EAAE,sBAAsB,EAAE,GAAG,IAAI,CAAC,eAAe,CAAC,QAAQ,EAAE,gBAAgB,CAAC,CAAC;gBAE7H,wDAAwD;gBACxD,IAAI,eAAe,GAAG,sBAAsB,CAAC;gBAC7C,SAAS,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE;oBACtB,MAAM,sBAAsB,GAAG,IAAI,CAAC,yBAAyB,CAAC,gBAAgB,EAAE,sBAAsB,EAAE,eAAe,CAAC,CAAC;oBACzH,MAAM,aAAa,GAAG,GAAG,CAAC,iBAAiB,CAAC,gBAAgB,EAAE,sBAAsB,CAAC,CAAC;oBACtF,eAAe,IAAI,aAAa,CAAC;gBACrC,CAAC,CAAC,CAAC;gBAEH,sDAAsD;gBACtD,MAAM,iBAAiB,GAAG,IAAI,CAAC,yBAAyB,CAAC,gBAAgB,EAAE,sBAAsB,EAAE,eAAe,CAAC,CAAC;gBACpH,OAAO,IAAI,CAAC,GAAG,CAAC,iBAAiB,EAAE,SAAS,CAAC,CAAC;YAClD,CAAC;YAED;gBACI,OAAO,CAAC,CAAC;QACjB,CAAC;IACL,CAAC;IAED;;;;;;OAMG;IACK,MAAM,CAAC,eAAe,CAAC,QAAqB,EAAE,gBAAmC;QACrF,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QACvE,MAAM,YAAY,GAAG,IAAI,CAAC,qBAAqB,CAAC,QAAQ,CAAC,CAAC;QAE1D,MAAM,sBAAsB,GACxB,gBAAgB,CAAC,YAAY,KAAK,6BAAsB,CAAC,YAAY,CAAC,CAAC,CAAC,gBAAgB,CAAC,cAAc,GAAG,MAAM,CAAC,CAAC,CAAC,gBAAgB,CAAC,cAAc,CAAC;QAEvJ,OAAO;YACH,sBAAsB;YACtB,eAAe,EAAE,YAAY;SAChC,CAAC;IACN,CAAC;IAED;;;;;;;OAOG;IACK,MAAM,CAAC,yBAAyB,CAAC,gBAAmC,EAAE,sBAA8B,EAAE,eAAuB;QACjI,IAAI,gBAAgB,CAAC,YAAY,KAAK,6BAAsB,CAAC,YAAY,IAAI,gBAAgB,CAAC,YAAY,KAAK,6BAAsB,CAAC,mBAAmB,EAAE,CAAC;YACxJ,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,sBAAsB,GAAG,eAAe,CAAC,CAAC;QACjE,CAAC;QACD,OAAO,CAAC,CAAC;IACb,CAAC;IAED;;;;;;OAMG;IACK,MAAM,CAAC,qBAAqB,CAAC,QAAqB;QACtD,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;YAClC,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,EAAE,CAAC;YAC3B,OAAO,KAAK,GAAG,CAAC,CAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,gBAAgB,EAAE,KAAI,CAAC,CAAC,CAAC;QACnD,CAAC,EAAE,CAAC,CAAC,CAAC;IACV,CAAC;CACJ;AArJD,0DAqJC","sourcesContent":["import { EPromoCodeDiscountType, IPromoCodeDetails } from '@roomstay/core';\n\nimport BasketAddonRow from '@/models/BasketAddonRow';\nimport BasketRow from '@/models/BasketRow';\n\n/**\n * For `flatQuantity` and `flatQuantityPerStay` promo codes, discount allocation is SEQUENTIAL:\n * The discount is consumed in this order:\n * 1. Room rates\n * 2. First addon added to basket\n * 3. Second addon added to basket\n * 4. Etc\n *\n * For `flatPercent` types, order doesn't matter as each addon gets its own percentage discount.\n */\nexport class AddonDiscountCalculator {\n /**\n * Calculate total price of all addons with promo code discounts applied.\n * Discounts are applied sequentially for flatQuantityPerStay promo codes.\n *\n * @param addonRows - Array of basket addon rows (ORDER MATTERS for flatQuantityPerStay!)\n * @param roomRows - Array of basket room rows\n * @param promoCodeDetails - Promo code details from basket context\n * @returns Total price of all addons after discounts\n */\n static calculateTotal(addonRows: BasketAddonRow[], roomRows: BasketRow[], promoCodeDetails: IPromoCodeDetails | null): number {\n if (!promoCodeDetails?.applyToAddons) {\n return addonRows.reduce((total, row) => total + row.getTotalPrice(), 0);\n }\n\n const individualDiscounts = this.calculateIndividualDiscounts(addonRows, roomRows, promoCodeDetails);\n\n return individualDiscounts.reduce((total, { addon, discount }) => {\n return total + addon.getTotalPrice() - discount;\n }, 0);\n }\n\n /**\n * Calculate discount for each addon individually with remaining discount tracking.\n * Useful for displaying discounts in the UI where you need to show each addon's\n * discount separately.\n *\n * @param addonRows - Array of basket addon rows (ORDER MATTERS!)\n * @param roomRows - Array of basket room rows\n * @param promoCodeDetails - Promo code details\n * @returns Array of objects with addon, its discount, and remaining discount available\n */\n static calculateIndividualDiscounts(\n addonRows: BasketAddonRow[],\n roomRows: BasketRow[],\n promoCodeDetails: IPromoCodeDetails | null\n ): Array<{ addon: BasketAddonRow; discount: number; remainingDiscount: number }> {\n if (!promoCodeDetails?.applyToAddons) {\n return addonRows.map((addon) => ({ addon, discount: 0, remainingDiscount: 0 }));\n }\n\n const { totalDiscountAvailable, runningDiscount: initialRunningDiscount } = this.getDiscountInfo(roomRows, promoCodeDetails);\n\n let runningDiscount = initialRunningDiscount;\n const results: Array<{ addon: BasketAddonRow; discount: number; remainingDiscount: number }> = [];\n\n for (const addon of addonRows) {\n const remainingFixedDiscount = this.getRemainingFixedDiscount(promoCodeDetails, totalDiscountAvailable, runningDiscount);\n const discount = addon.getDiscountAmount(promoCodeDetails, remainingFixedDiscount);\n\n results.push({ addon, discount, remainingDiscount: remainingFixedDiscount });\n runningDiscount += discount;\n }\n\n return results;\n }\n\n /**\n * Calculate discount for an addon being previewed (not yet in basket).\n * This is used in the addon selection UI to show what discount would apply.\n *\n * For flatQuantityPerStay promos, this calculates the remaining discount after\n * accounting for rooms and OTHER addons already in the basket.\n *\n * @param basePrice - Base price of the addon (before discount, before fees)\n * @param addonRows - Array of basket addon rows (addons already in basket)\n * @param roomRows - Array of basket room rows\n * @param promoCodeDetails - Promo code details\n * @returns Discount amount that would apply to this addon\n */\n static calculatePreviewDiscount(basePrice: number, addonRows: BasketAddonRow[], roomRows: BasketRow[], promoCodeDetails: IPromoCodeDetails | null): number {\n if (!promoCodeDetails?.applyToAddons || basePrice <= 0) {\n return 0;\n }\n\n switch (promoCodeDetails.discountType) {\n case EPromoCodeDiscountType.FlatPercent:\n return Math.round(basePrice * (promoCodeDetails.discountAmount / 100));\n\n case EPromoCodeDiscountType.FlatQuantity:\n case EPromoCodeDiscountType.FlatQuantityPerStay: {\n const { totalDiscountAvailable, runningDiscount: initialRunningDiscount } = this.getDiscountInfo(roomRows, promoCodeDetails);\n\n // Calculate discount consumed by other addons in basket\n let runningDiscount = initialRunningDiscount;\n addonRows.forEach((row) => {\n const addonRemainingDiscount = this.getRemainingFixedDiscount(promoCodeDetails, totalDiscountAvailable, runningDiscount);\n const addonDiscount = row.getDiscountAmount(promoCodeDetails, addonRemainingDiscount);\n runningDiscount += addonDiscount;\n });\n\n // Calculate remaining discount for this preview addon\n const remainingDiscount = this.getRemainingFixedDiscount(promoCodeDetails, totalDiscountAvailable, runningDiscount);\n return Math.min(remainingDiscount, basePrice);\n }\n\n default:\n return 0;\n }\n }\n\n /**\n * Get discount info including total available and initial running discount from rooms.\n *\n * @param roomRows - Array of basket room rows\n * @param promoCodeDetails - Promo code details\n * @returns Object with totalDiscountAvailable and runningDiscount (from rooms)\n */\n private static getDiscountInfo(roomRows: BasketRow[], promoCodeDetails: IPromoCodeDetails): { totalDiscountAvailable: number; runningDiscount: number } {\n const nights = roomRows.length > 0 ? roomRows[0].getNumberOfDays() : 0;\n const roomDiscount = this.calculateRoomDiscount(roomRows);\n\n const totalDiscountAvailable =\n promoCodeDetails.discountType === EPromoCodeDiscountType.FlatQuantity ? promoCodeDetails.discountAmount * nights : promoCodeDetails.discountAmount;\n\n return {\n totalDiscountAvailable,\n runningDiscount: roomDiscount,\n };\n }\n\n /**\n * Calculate remaining fixed discount available.\n *\n * @param promoCodeDetails - Promo code details\n * @param totalDiscountAvailable - Total discount available\n * @param runningDiscount - Discount already consumed\n * @returns Remaining discount available (0 for FlatPercent)\n */\n private static getRemainingFixedDiscount(promoCodeDetails: IPromoCodeDetails, totalDiscountAvailable: number, runningDiscount: number): number {\n if (promoCodeDetails.discountType === EPromoCodeDiscountType.FlatQuantity || promoCodeDetails.discountType === EPromoCodeDiscountType.FlatQuantityPerStay) {\n return Math.max(0, totalDiscountAvailable - runningDiscount);\n }\n return 0;\n }\n\n /**\n * Calculate total discount already consumed by room rates.\n * This is used as the starting point for addon discount calculations.\n *\n * @param roomRows - Array of basket room rows\n * @returns Total discount amount consumed by all rooms\n */\n private static calculateRoomDiscount(roomRows: BasketRow[]): number {\n return roomRows.reduce((total, row) => {\n const rate = row.getRate();\n return total + (rate?.getTotalDiscount() || 0);\n }, 0);\n }\n}\n"]}
|