From bba4e24569137a0ed4f2ead4ab15ca25269fc0e9 Mon Sep 17 00:00:00 2001 From: Bianca Widstam Date: Fri, 5 Sep 2025 14:02:47 +0000 Subject: [PATCH] Merged in fix/SW-3198-prices-select-rate (pull request #2763) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix(SW-3198): fix striketrhough/regular prices, the same in enter details as select rate * fix(SW-3198): fix striketrhough/regular prices, the same in enter details as select rate * fix(SW-3198): remove additonalcost if calculating cost per room * fix(SW-3198): include bookingcode in specialrate * fix(SW-3198): remove console log * fix(SW-3198): add or operator * fix(SW-3198): capture total return value * fix(SW-3198): rename and move function Approved-by: Joakim Jäderberg Approved-by: Hrishikesh Vaipurkar --- .../(standard)/details/page.tsx | 4 +- .../EnterDetails/Summary/Desktop.tsx | 4 +- .../Summary/Mobile/BottomSheet/index.tsx | 22 ++--- .../EnterDetails/Summary/Mobile/index.tsx | 8 +- .../EnterDetails/Summary/UI/Room/index.tsx | 48 ++++++----- .../EnterDetails/Summary/UI/index.tsx | 34 ++++---- .../stores/enter-details/helpers.ts | 83 +++++-------------- .../components/hotelReservation/summary.ts | 4 +- .../PriceDetailsTable/Row/Bold.tsx | 4 +- .../PriceDetailsTable/Row/Large.tsx | 4 +- .../MobileSummary/Content/index.tsx | 75 +++++++++++------ .../RateSummary/MobileSummary/Room/index.tsx | 29 +++---- .../RateSummary/MobileSummary/index.tsx | 29 +++---- .../RateSummary/MobileSummary/utils.ts | 13 --- .../contexts/SelectRate/SelectRateContext.tsx | 5 +- .../contexts/SelectRate/getTotalPrice.test.ts | 2 +- .../lib/contexts/SelectRate/getTotalPrice.ts | 79 +++++++++++------- .../lib/utils/calculateRegularPrice.ts | 78 +++++++++++++++++ packages/booking-flow/package.json | 1 + 19 files changed, 290 insertions(+), 236 deletions(-) delete mode 100644 packages/booking-flow/lib/components/SelectRate/RoomsContainer/RateSummary/MobileSummary/utils.ts create mode 100644 packages/booking-flow/lib/utils/calculateRegularPrice.ts diff --git a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/details/page.tsx b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/details/page.tsx index 6dd1ebf56..0f265477c 100644 --- a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/details/page.tsx +++ b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/details/page.tsx @@ -150,8 +150,8 @@ export default async function DetailsPage( state.actions.toggleSummaryOpen ) @@ -27,7 +27,7 @@ export default function DesktopSummary({ isMember }: SummaryProps) { {} export default function SummaryBottomSheet({ children, - isMember, + isUserLoggedIn, }: SummaryBottomSheetProps) { const intl = useIntl() const scrollY = useRef(0) @@ -68,7 +68,7 @@ export default function SummaryBottomSheet({ const containsBookingCodeRate = rooms.find( (r) => r && isBookingCodeRate(r.room.roomRate) ) - const showDiscounted = containsBookingCodeRate || isMember + const showDiscounted = containsBookingCodeRate || isUserLoggedIn return (
@@ -103,13 +103,15 @@ export default function SummaryBottomSheet({ {showDiscounted && totalPrice.local.regularPrice ? ( - - {formatPrice( - intl, - totalPrice.local.regularPrice, - totalPrice.local.currency - )} - +

+ + {formatPrice( + intl, + totalPrice.local.regularPrice, + totalPrice.local.currency + )} + +

) : null} diff --git a/apps/scandic-web/components/HotelReservation/EnterDetails/Summary/Mobile/index.tsx b/apps/scandic-web/components/HotelReservation/EnterDetails/Summary/Mobile/index.tsx index f251aa696..e43595d1e 100644 --- a/apps/scandic-web/components/HotelReservation/EnterDetails/Summary/Mobile/index.tsx +++ b/apps/scandic-web/components/HotelReservation/EnterDetails/Summary/Mobile/index.tsx @@ -11,7 +11,7 @@ import styles from "./mobile.module.css" import type { SummaryProps } from "@/types/components/hotelReservation/summary" -export default function MobileSummary({ isMember }: SummaryProps) { +export default function MobileSummary({ isUserLoggedIn }: SummaryProps) { const { isSummaryOpen, toggleSummaryOpen } = useEnterDetailsStore( (state) => ({ isSummaryOpen: state.isSummaryOpen, @@ -29,7 +29,7 @@ export default function MobileSummary({ isMember }: SummaryProps) { })) const showPromo = - !isMember && + !isUserLoggedIn && rooms.length === 1 && !rooms[0].room.guest.join && !rooms[0].room.guest.membershipNo @@ -51,12 +51,12 @@ export default function MobileSummary({ isMember }: SummaryProps) { /> )} - +
{price}

- {showDiscounted && publicPrice ? ( + {showDiscounted && room.roomPrice.perStay.local.regularPrice ? ( {formatPrice( intl, - publicPrice.amount, - publicPrice.currency + room.roomPrice.perStay.local.regularPrice, + currency )} ) : null} diff --git a/apps/scandic-web/components/HotelReservation/EnterDetails/Summary/UI/index.tsx b/apps/scandic-web/components/HotelReservation/EnterDetails/Summary/UI/index.tsx index 73905d9ab..5d8000abd 100644 --- a/apps/scandic-web/components/HotelReservation/EnterDetails/Summary/UI/index.tsx +++ b/apps/scandic-web/components/HotelReservation/EnterDetails/Summary/UI/index.tsx @@ -32,7 +32,7 @@ export default function SummaryUI({ booking, rooms, totalPrice, - isMember, + isUserLoggedIn, vat, toggleSummaryOpen, defaultCurrency, @@ -59,7 +59,7 @@ export default function SummaryUI({ const roomOneGuest = rooms[0].room.guest const showSignupPromo = rooms.length === 1 && - !isMember && + !isUserLoggedIn && !roomOneGuest.membershipNo && !roomOneGuest.join @@ -67,13 +67,8 @@ export default function SummaryUI({ const roomOneRoomRate = rooms[0].room.roomRate const isVoucherRate = "voucher" in roomOneRoomRate - // In case of Redemption, voucher and Corporate cheque do not show approx price - const isSpecialRate = - "corporateCheque" in roomOneRoomRate || - "redemption" in roomOneRoomRate || - isVoucherRate - const priceDetailsRooms = mapToPrice(rooms, isMember) + const priceDetailsRooms = mapToPrice(rooms, isUserLoggedIn) const isAllCampaignRate = rooms.every( (room) => room.room.roomRate.rateDefinition.isCampaignRate ) @@ -83,7 +78,7 @@ export default function SummaryUI({ const containsBookingCodeRate = rooms.find( (r) => r && isBookingCodeRate(r.room.roomRate) ) - const showDiscounted = containsBookingCodeRate || isMember + const showDiscounted = containsBookingCodeRate || isUserLoggedIn const totalCurrency = isVoucherRate ? CurrencyEnum.Voucher @@ -127,8 +122,7 @@ export default function SummaryUI({ room={room} roomNumber={idx + 1} roomCount={rooms.length} - isMember={isMember} - isSpecialRate={isSpecialRate} + isUserLoggedIn={isUserLoggedIn} nightsCount={nights} /> ))} @@ -192,13 +186,15 @@ export default function SummaryUI({ {showDiscounted && totalPrice.local.regularPrice ? ( - - {formatPrice( - intl, - totalPrice.local.regularPrice, - totalPrice.local.currency - )} - +

+ + {formatPrice( + intl, + totalPrice.local.regularPrice, + totalPrice.local.currency + )} + +

) : null}
@@ -223,7 +219,7 @@ export default function SummaryUI({ alignCenter /> - {showSignupPromo && roomOneMemberPrice && !isMember ? ( + {showSignupPromo && roomOneMemberPrice && !isUserLoggedIn ? ( { const isMainRoomAndMember = idx === 0 && isMember const join = Boolean(room.guest.join || room.guest.membershipNo) - const getMemberRate = isMainRoomAndMember || join - const memberRate = "member" in room.roomRate && room.roomRate.member const publicRate = "public" in room.roomRate && room.roomRate.public + const useMemberRate = (isMainRoomAndMember || join) && memberRate - let rate - if (getMemberRate && memberRate) { - rate = memberRate - } else if (publicRate) { - rate = publicRate - } + const rate = useMemberRate ? memberRate : publicRate if (!rate) { return total @@ -547,61 +542,23 @@ export function getRegularPrice( ) } - // Legend: - // - total.local.price = Total Price = Black price, what the user pays - // - total.local.regularPrice = Regular Price = Strikethrough price (could potentially be none) - // - total.requested.price = Requested Price = EUR approx price - - // We sometimes don't get all the required data to calculate the correct strikethrough total. - // Therefore we try these different approach to get a number that is close - // enough to the real number if all data would've been present. - if (getMemberRate && memberRate) { - if (publicRate) { - // #1 Member price uses public price as strikethrough - total.local.regularPrice = add( - total.local.regularPrice, - publicRate.localPrice.pricePerStay, - additionalCost - ) - } else if (memberRate.localPrice.regularPricePerStay) { - // #2 Member price uses member regular price as strikethrough - total.local.regularPrice = add( - total.local.regularPrice, - memberRate.localPrice.regularPricePerStay, - additionalCost - ) - } else { - // #3 Member price uses member price as strikethrough - // NOTE: If all rooms end up using this, no strikethrough price is shown. - total.local.regularPrice = add( - total.local.regularPrice, - memberRate.localPrice.pricePerStay, - additionalCost - ) - } - } else if (publicRate) { - if (publicRate.localPrice.regularPricePerStay) { - // #1 Public price uses public regular price as strikethrough - total.local.regularPrice = add( - total.local.regularPrice, - publicRate.localPrice.regularPricePerStay, - additionalCost - ) - } else { - // #2 Public price uses public price as strikethrough - // NOTE: If all rooms end up using this, no strikethrough price is shown. - total.local.regularPrice = add( - total.local.regularPrice, - publicRate.localPrice.pricePerStay, - additionalCost - ) - } - } else { - // We cannot do anything, too much data is missing. - return total - } - - return total + return calculateRegularPrice({ + total, + useMemberRate: !!useMemberRate, + regularMemberPrice: memberRate + ? { + pricePerStay: memberRate.localPrice.pricePerNight, + regularPricePerStay: memberRate.localPrice.regularPricePerStay, + } + : undefined, + regularPublicPrice: publicRate + ? { + pricePerStay: publicRate.localPrice.pricePerNight, + regularPricePerStay: publicRate.localPrice.regularPricePerStay, + } + : undefined, + additionalCost, + }) }, { local: { diff --git a/apps/scandic-web/types/components/hotelReservation/summary.ts b/apps/scandic-web/types/components/hotelReservation/summary.ts index 85232c6f1..e979c0e33 100644 --- a/apps/scandic-web/types/components/hotelReservation/summary.ts +++ b/apps/scandic-web/types/components/hotelReservation/summary.ts @@ -22,12 +22,12 @@ export type RoomsData = { } export interface SummaryProps { - isMember: boolean + isUserLoggedIn: boolean } export interface EnterDetailsSummaryProps { booking: DetailsBooking - isMember: boolean + isUserLoggedIn: boolean totalPrice: Price vat: number rooms: RoomState[] diff --git a/packages/booking-flow/lib/components/PriceDetailsModal/PriceDetailsTable/Row/Bold.tsx b/packages/booking-flow/lib/components/PriceDetailsModal/PriceDetailsTable/Row/Bold.tsx index ee8fc8636..d8abef8d7 100644 --- a/packages/booking-flow/lib/components/PriceDetailsModal/PriceDetailsTable/Row/Bold.tsx +++ b/packages/booking-flow/lib/components/PriceDetailsModal/PriceDetailsTable/Row/Bold.tsx @@ -27,7 +27,9 @@ export default function BoldRow({ {isDiscounted && regularValue ? ( - {regularValue} +

+ {regularValue} +

) : null} diff --git a/packages/booking-flow/lib/components/PriceDetailsModal/PriceDetailsTable/Row/Large.tsx b/packages/booking-flow/lib/components/PriceDetailsModal/PriceDetailsTable/Row/Large.tsx index 67970b788..71d1bdf4d 100644 --- a/packages/booking-flow/lib/components/PriceDetailsModal/PriceDetailsTable/Row/Large.tsx +++ b/packages/booking-flow/lib/components/PriceDetailsModal/PriceDetailsTable/Row/Large.tsx @@ -52,7 +52,9 @@ export default function LargeRow({ {isDiscounted && regularPrice ? ( <> - {regularPrice} +

+ {regularPrice} +

) : null} diff --git a/packages/booking-flow/lib/components/SelectRate/RoomsContainer/RateSummary/MobileSummary/Content/index.tsx b/packages/booking-flow/lib/components/SelectRate/RoomsContainer/RateSummary/MobileSummary/Content/index.tsx index 075b197af..5b6bfa567 100644 --- a/packages/booking-flow/lib/components/SelectRate/RoomsContainer/RateSummary/MobileSummary/Content/index.tsx +++ b/packages/booking-flow/lib/components/SelectRate/RoomsContainer/RateSummary/MobileSummary/Content/index.tsx @@ -24,13 +24,13 @@ import styles from "./summaryContent.module.css" import type { Price } from "../../../../../../contexts/SelectRate/getTotalPrice" export type SelectRateSummaryProps = { - isMember: boolean + isUserLoggedIn: boolean bookingCode?: string toggleSummaryOpen: () => void } export default function SummaryContent({ - isMember, + isUserLoggedIn, toggleSummaryOpen, }: SelectRateSummaryProps) { const { selectedRates, input } = useSelectRateContext() @@ -61,7 +61,7 @@ export default function SummaryContent({ return null } - const showDiscounted = containsBookingCodeRate || isMember + const showDiscounted = containsBookingCodeRate || isUserLoggedIn const totalRegularPrice = selectedRates?.totalPrice?.local?.regularPrice ? selectedRates.totalPrice.local.regularPrice : 0 @@ -117,7 +117,7 @@ export default function SummaryContent({ ) })} @@ -192,13 +192,15 @@ export default function SummaryContent({ showStrikeThroughPrice && selectedRates.totalPrice.local.regularPrice ? ( - - {formatPrice( - intl, - selectedRates.totalPrice.local.regularPrice, - selectedRates.totalPrice.local.currency - )} - +

+ + {formatPrice( + intl, + selectedRates.totalPrice.local.regularPrice, + selectedRates.totalPrice.local.currency + )} + +

) : null}
@@ -217,7 +219,7 @@ export default function SummaryContent({ } const mapped = mapToRoom({ - isMember, + isUserLoggedIn, rate: room, input, idx, @@ -231,17 +233,26 @@ export default function SummaryContent({ ) { switch (room.type) { case "regular": + const memberLocalPrice = room.member?.localPrice + ? { + ...room.member.localPrice, + regularPricePerStay: + room.public?.localPrice?.pricePerStay || + room.member.localPrice.regularPricePerStay, + } + : undefined + return { + regular: + isMember && memberLocalPrice + ? memberLocalPrice + : room.public?.localPrice, + } + case "campaign": return { regular: isMember ? (room.member?.localPrice ?? room.public?.localPrice) : room.public?.localPrice, } - case "campaign": - return { - campaign: isMember - ? (room.member ?? room.public) - : room.public, - } case "redemption": return { redemption: room.redemption, @@ -259,10 +270,19 @@ export default function SummaryContent({ } } if ("public" in room) { + const memberLocalPrice = room.member?.localPrice + ? { + ...room.member.localPrice, + regularPricePerStay: + room.public?.localPrice?.pricePerStay || + room.member.localPrice.regularPricePerStay, + } + : undefined return { - regular: isMember - ? (room.member?.localPrice ?? room.public?.localPrice) - : room.public?.localPrice, + regular: + isMember && memberLocalPrice + ? memberLocalPrice + : room.public?.localPrice, } } } @@ -271,7 +291,7 @@ export default function SummaryContent({ } } - const p = getPrice(room!, isMember) + const p = getPrice(room!, isUserLoggedIn && idx === 0) return { ...mapped, @@ -293,7 +313,7 @@ export default function SummaryContent({ vat={selectedRates.vat} /> - {!isMember && memberPrice ? ( + {!isUserLoggedIn && memberPrice ? ( ["selectedRates"]["rates"][number] > @@ -323,6 +343,7 @@ function mapToRoom({ getPriceForRoom: (roomIndex: number) => Price | null rateTitles: ReturnType }) { + const useMemberPrice = isUserLoggedIn && idx === 0 return { adults: input.data?.booking.rooms[idx].adults || 0, childrenInRoom: input.data?.booking.rooms[idx].childrenInRoom, @@ -335,7 +356,7 @@ function mapToRoom({ local: { price: -1, currency: CurrencyEnum.Unknown }, }, }, - rateDetails: isMember + rateDetails: useMemberPrice ? (rate.rateDefinitionMember?.generalTerms ?? rate.rateDefinition.generalTerms) : rate.rateDefinition.generalTerms, diff --git a/packages/booking-flow/lib/components/SelectRate/RoomsContainer/RateSummary/MobileSummary/Room/index.tsx b/packages/booking-flow/lib/components/SelectRate/RoomsContainer/RateSummary/MobileSummary/Room/index.tsx index d2770e8e5..818a0ef15 100644 --- a/packages/booking-flow/lib/components/SelectRate/RoomsContainer/RateSummary/MobileSummary/Room/index.tsx +++ b/packages/booking-flow/lib/components/SelectRate/RoomsContainer/RateSummary/MobileSummary/Room/index.tsx @@ -10,7 +10,6 @@ import { Typography } from "@scandic-hotels/design-system/Typography" import { ChildBedMapEnum } from "@scandic-hotels/trpc/enums/childBedMapEnum" import { isBookingCodeRate } from "../../utils" -import { getMemberPrice } from "../utils" import styles from "./room.module.css" @@ -68,9 +67,7 @@ export default function Room({ const childBedCrib = childrenBeds?.get(ChildBedMapEnum.IN_CRIB) const childBedExtraBed = childrenBeds?.get(ChildBedMapEnum.IN_EXTRA_BED) - const memberPrice = getMemberPrice(room.roomRate) - const showMemberPrice = !!(isMember && memberPrice && roomNumber === 1) - const showDiscounted = isBookingCodeRate(room.roomRate) || showMemberPrice + const showDiscounted = isBookingCodeRate(room.roomRate) || isMember const adultsMsg = intl.formatMessage( { @@ -130,25 +127,19 @@ export default function Room({ [styles.discounted]: showDiscounted, })} > - {showMemberPrice - ? formatPrice( - intl, - memberPrice.amount, - memberPrice.currency - ) - : formatPrice( - intl, - room.roomPrice.perStay.local.price, - room.roomPrice.perStay.local.currency, - room.roomPrice.perStay.local.additionalPrice, - room.roomPrice.perStay.local.additionalPriceCurrency - )} + {formatPrice( + intl, + room.roomPrice.perStay.local.price, + room.roomPrice.perStay.local.currency, + room.roomPrice.perStay.local.additionalPrice, + room.roomPrice.perStay.local.additionalPriceCurrency + )}

- {showDiscounted && room.roomPrice.perStay.local.price ? ( + {showDiscounted && room.roomPrice.perStay.local.regularPrice ? ( {formatPrice( intl, - room.roomPrice.perStay.local.price, + room.roomPrice.perStay.local.regularPrice, room.roomPrice.perStay.local.currency )} diff --git a/packages/booking-flow/lib/components/SelectRate/RoomsContainer/RateSummary/MobileSummary/index.tsx b/packages/booking-flow/lib/components/SelectRate/RoomsContainer/RateSummary/MobileSummary/index.tsx index c8679b7c1..01283337f 100644 --- a/packages/booking-flow/lib/components/SelectRate/RoomsContainer/RateSummary/MobileSummary/index.tsx +++ b/packages/booking-flow/lib/components/SelectRate/RoomsContainer/RateSummary/MobileSummary/index.tsx @@ -61,19 +61,12 @@ export function MobileSummary() { return null } - const totalRegularPrice = selectedRates.totalPrice.local?.regularPrice - ? selectedRates.totalPrice.local.regularPrice - : 0 - - const showStrikeThroughPrice = - totalRegularPrice > selectedRates.totalPrice.local?.price - return (
@@ -106,17 +99,17 @@ export function MobileSummary() { )} - {showDiscounted && - showStrikeThroughPrice && - selectedRates.totalPrice.local.regularPrice ? ( + {showDiscounted && selectedRates.totalPrice.local?.regularPrice ? ( - - {formatPrice( - intl, - selectedRates.totalPrice.local.regularPrice, - selectedRates.totalPrice.local.currency - )} - +

+ + {formatPrice( + intl, + selectedRates.totalPrice.local?.regularPrice, + selectedRates.totalPrice.local.currency + )} + +

) : null} diff --git a/packages/booking-flow/lib/components/SelectRate/RoomsContainer/RateSummary/MobileSummary/utils.ts b/packages/booking-flow/lib/components/SelectRate/RoomsContainer/RateSummary/MobileSummary/utils.ts deleted file mode 100644 index 3fcac662e..000000000 --- a/packages/booking-flow/lib/components/SelectRate/RoomsContainer/RateSummary/MobileSummary/utils.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { Product } from "@scandic-hotels/trpc/types/roomAvailability" - -export function getMemberPrice(roomRate: Product) { - if ("member" in roomRate && roomRate.member) { - return { - amount: roomRate.member.localPrice.pricePerStay, - currency: roomRate.member.localPrice.currency, - pricePerNight: roomRate.member.localPrice.pricePerNight, - } - } - - return null -} diff --git a/packages/booking-flow/lib/contexts/SelectRate/SelectRateContext.tsx b/packages/booking-flow/lib/contexts/SelectRate/SelectRateContext.tsx index dee8470fd..023fb8571 100644 --- a/packages/booking-flow/lib/contexts/SelectRate/SelectRateContext.tsx +++ b/packages/booking-flow/lib/contexts/SelectRate/SelectRateContext.tsx @@ -230,7 +230,7 @@ export function SelectRateProvider({ rate, roomConfiguration: roomAvailability[ix]?.[0], })), - useMemberPrices: isUserLoggedIn, + isMember: isUserLoggedIn, }) const getPriceForRoom = useCallback( @@ -249,7 +249,8 @@ export function SelectRateProvider({ selectedRates: [ { rate, roomConfiguration: roomAvailability[roomIndex]?.[0] }, ], - useMemberPrices: isUserLoggedIn, + isMember: isUserLoggedIn && roomIndex === 0, + addAdditionalCost: false, }) }, [selectedRates, roomAvailability, isUserLoggedIn] diff --git a/packages/booking-flow/lib/contexts/SelectRate/getTotalPrice.test.ts b/packages/booking-flow/lib/contexts/SelectRate/getTotalPrice.test.ts index 9a028d9d1..980cc1a5b 100644 --- a/packages/booking-flow/lib/contexts/SelectRate/getTotalPrice.test.ts +++ b/packages/booking-flow/lib/contexts/SelectRate/getTotalPrice.test.ts @@ -6,7 +6,7 @@ describe("getTotalPrice", () => { it("should return null when no rates are selected", () => { const result = getTotalPrice({ selectedRates: [], - useMemberPrices: false, + isMember: false, }) expect(result).toEqual({ diff --git a/packages/booking-flow/lib/contexts/SelectRate/getTotalPrice.ts b/packages/booking-flow/lib/contexts/SelectRate/getTotalPrice.ts index be4c96009..fb9924b92 100644 --- a/packages/booking-flow/lib/contexts/SelectRate/getTotalPrice.ts +++ b/packages/booking-flow/lib/contexts/SelectRate/getTotalPrice.ts @@ -1,6 +1,7 @@ import { CurrencyEnum } from "@scandic-hotels/common/constants/currency" -import { sumPackages } from "../../utils/SelectRate" +import { calculateRegularPrice } from "../../utils/calculateRegularPrice" +import { sumPackages, sumPackagesRequestedPrice } from "../../utils/SelectRate" import type { RedemptionProduct } from "@scandic-hotels/trpc/types/roomAvailability" @@ -26,10 +27,12 @@ type SelectedRate = { export function getTotalPrice({ selectedRates, - useMemberPrices, + isMember, + addAdditionalCost = true, }: { selectedRates: Array - useMemberPrices: boolean + isMember: boolean + addAdditionalCost?: boolean }): Price | null { const mainRoom = selectedRates[0] const mainRoomRate = mainRoom?.rate @@ -42,7 +45,7 @@ export function getTotalPrice({ } if (!mainRoomRate) { - return calculateTotalPrice(summaryArray, useMemberPrices) + return calculateTotalPrice(summaryArray, isMember, addAdditionalCost) } // In case of reward night (redemption) or voucher only single room booking is supported by business rules @@ -59,14 +62,15 @@ export function getTotalPrice({ return voucherPrice } - return calculateTotalPrice(summaryArray, useMemberPrices) + return calculateTotalPrice(summaryArray, isMember, addAdditionalCost) } function calculateTotalPrice( selectedRateSummary: OneLevelNonNullable[], - useMemberPrices: boolean + isMember: boolean, + addAdditionalCost: boolean ) { - return selectedRateSummary.reduce( + const totalPrice = selectedRateSummary.reduce( (total, room, idx) => { if (!room.rate || !("member" in room.rate) || !("public" in room.rate)) { return total @@ -75,34 +79,25 @@ function calculateTotalPrice( const roomNr = idx + 1 const isMainRoom = roomNr === 1 - const useMemberRate = isMainRoom && useMemberPrices && room.rate.member + const useMemberRate = isMainRoom && isMember && room.rate.member const rate = useMemberRate ? room.rate.member : room.rate.public + const publicRate = room.rate.public + const memberRate = room.rate.member if (!rate) { return total } - const packagesPrice = room.roomConfiguration?.selectedPackages.reduce( - (total, pkg) => { - total.local = total.local + pkg.localPrice.totalPrice - if (pkg.requestedPrice.totalPrice) { - total.requested = total.requested + pkg.requestedPrice.totalPrice - } - return total - }, - { local: 0, requested: 0 } - ) + const packagesPrice = addAdditionalCost + ? sumPackages(room.roomConfiguration?.selectedPackages) + : { price: 0, currency: undefined } + const packagesRequestedPrice = addAdditionalCost + ? sumPackagesRequestedPrice(room.roomConfiguration?.selectedPackages) + : { price: 0, currency: undefined } total.local.currency = rate.localPrice.currency total.local.price = - total.local.price + rate.localPrice.pricePerStay + packagesPrice.local - - if (rate.localPrice.regularPricePerStay) { - total.local.regularPrice = - (total.local.regularPrice || 0) + - rate.localPrice.regularPricePerStay + - packagesPrice.local - } + total.local.price + rate.localPrice.pricePerStay + packagesPrice.price if (rate.requestedPrice) { if (!total.requested) { @@ -119,17 +114,33 @@ function calculateTotalPrice( total.requested.price = total.requested.price + rate.requestedPrice.pricePerStay + - packagesPrice.requested + packagesRequestedPrice.price if (rate.requestedPrice.regularPricePerStay) { total.requested.regularPrice = (total.requested.regularPrice || 0) + rate.requestedPrice.regularPricePerStay + - packagesPrice.requested + packagesRequestedPrice.price } } + return calculateRegularPrice({ + total, + useMemberRate: !!useMemberRate, + regularMemberPrice: memberRate + ? { + pricePerStay: memberRate.localPrice.pricePerNight, + regularPricePerStay: memberRate.localPrice.regularPricePerStay, + } + : undefined, + regularPublicPrice: publicRate + ? { + pricePerStay: publicRate.localPrice.pricePerNight, + regularPricePerStay: publicRate.localPrice.regularPricePerStay, + } + : undefined, - return total + additionalCost: packagesPrice.price, + }) }, { local: { @@ -140,6 +151,15 @@ function calculateTotalPrice( requested: undefined, } ) + + if ( + totalPrice.local.regularPrice && + totalPrice.local.price >= totalPrice.local.regularPrice + ) { + totalPrice.local.regularPrice = undefined + } + + return totalPrice } function calculateRedemptionTotalPrice( @@ -196,6 +216,7 @@ function calculateVoucherPrice( local: { currency: CurrencyEnum.Voucher, price: 0, + regularPrice: undefined, }, requested: undefined, } diff --git a/packages/booking-flow/lib/utils/calculateRegularPrice.ts b/packages/booking-flow/lib/utils/calculateRegularPrice.ts new file mode 100644 index 000000000..cfd3dc907 --- /dev/null +++ b/packages/booking-flow/lib/utils/calculateRegularPrice.ts @@ -0,0 +1,78 @@ +import type { Price } from "../types/price" + +type RegularPrice = { + pricePerStay: number + regularPricePerStay?: number +} + +// Helper function to calculate regular/strikethrough price +export function calculateRegularPrice({ + total, + useMemberRate, + regularMemberPrice, + regularPublicPrice, + additionalCost = 0, +}: { + total: Price + useMemberRate: boolean + regularMemberPrice: RegularPrice | undefined + regularPublicPrice: RegularPrice | undefined + additionalCost?: number +}) { + if ( + !total || + (!useMemberRate && !regularPublicPrice) || + (useMemberRate && !regularMemberPrice) + ) { + return total + } + + let basePrice = 0 + // Legend: + // - total.local.price = Total Price = Black price, what the user pays + // - total.local.regularPrice = Regular Price = Strikethrough price (could potentially be none) + // - total.requested.price = Requested Price = EUR approx price + + // We sometimes don't get all the required data to calculate the correct strikethrough total. + // Therefore we try these different approach to get a number that is close + // enough to the real number if all data would've been present. + + if (useMemberRate && regularMemberPrice) { + if (regularPublicPrice) { + // #1 Member price uses public price as strikethrough + basePrice = regularPublicPrice.pricePerStay + } else if (regularMemberPrice.regularPricePerStay) { + // #2 Member price uses member regular price as strikethrough + basePrice = regularMemberPrice.regularPricePerStay + } else { + // #3 Member price uses member price as strikethrough + basePrice = regularMemberPrice.pricePerStay + } + } else if (regularPublicPrice) { + if (regularPublicPrice.regularPricePerStay) { + // #1 Public price uses public regular price as strikethrough + basePrice = regularPublicPrice.regularPricePerStay + } else { + // #2 Public price uses public price as strikethrough + basePrice = regularPublicPrice.pricePerStay + } + } + + total.local.regularPrice = add( + total.local.regularPrice, + basePrice, + additionalCost + ) + return total +} + +//copied from enter-details/helpers.ts +export function add(...nums: (number | string | undefined)[]) { + return nums.reduce((total: number, num) => { + if (typeof num === "undefined") { + num = 0 + } + total = total + parseInt(`${num}`) + return total + }, 0) +} diff --git a/packages/booking-flow/package.json b/packages/booking-flow/package.json index c0a757d85..18e592e92 100644 --- a/packages/booking-flow/package.json +++ b/packages/booking-flow/package.json @@ -62,6 +62,7 @@ "./utils/isSameBooking": "./lib/utils/isSameBooking.ts", "./utils/url": "./lib/utils/url.ts", "./utils/SelectRate": "./lib/utils/SelectRate/index.tsx", + "./utils/calculateRegularPrice": "./lib/utils/calculateRegularPrice.ts", "./utils/nuqs": "./lib/utils/nuqs.ts" }, "dependencies": {