From ba58ae6245be238a380bd8295704f74e3c6196ae Mon Sep 17 00:00:00 2001 From: Anton Gunnarsson Date: Mon, 10 Nov 2025 08:04:52 +0000 Subject: [PATCH] Merged in chore/regular-price-tests (pull request #3075) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit chore: Add tests for regular price calculations * Add tests for calculateRegularPrice * Add tests for getRegularPrice * Add multiroom test Approved-by: Joakim Jäderberg Approved-by: Linus Flood --- .../lib/stores/enter-details/helpers.test.ts | 494 ++++++++++++++++++ .../lib/stores/enter-details/helpers.ts | 46 +- .../lib/utils/calculateRegularPrice.test.ts | 105 ++++ 3 files changed, 639 insertions(+), 6 deletions(-) create mode 100644 packages/booking-flow/lib/utils/calculateRegularPrice.test.ts diff --git a/packages/booking-flow/lib/stores/enter-details/helpers.test.ts b/packages/booking-flow/lib/stores/enter-details/helpers.test.ts index 963af0d43..0fb24f62b 100644 --- a/packages/booking-flow/lib/stores/enter-details/helpers.test.ts +++ b/packages/booking-flow/lib/stores/enter-details/helpers.test.ts @@ -5,6 +5,7 @@ import { CurrencyEnum } from "@scandic-hotels/common/constants/currency" import { getAdditionalPrice, getRedemptionPrice, + getRegularPrice, getVoucherPrice, } from "./helpers" @@ -312,6 +313,44 @@ describe("getRedemptionPrice", () => { }) }) +it("returns price and additionalPrice for single room with room features", () => { + const nights = 2 + const result = getRedemptionPrice( + [ + { + adults: 1, + breakfast: false, + roomFeatures: [ + { + localPrice: { totalPrice: 33 }, + requestedPrice: { totalPrice: 33 }, + }, + ], + roomRate: { + redemption: { + localPrice: { + pointsPerStay: 100, + currency: CurrencyEnum.POINTS, + additionalPricePerStay: 0, + }, + }, + }, + }, + ], + nights + ) + + expect(result).toEqual({ + local: { + price: 100, + currency: CurrencyEnum.POINTS, + additionalPrice: 33, + additionalPriceCurrency: CurrencyEnum.POINTS, + }, + requested: undefined, + }) +}) + describe("getVoucherPrice", () => { it("returns price 0 and default currency when rooms are empty", () => { const result = getVoucherPrice([], 1) @@ -458,3 +497,458 @@ describe("getVoucherPrice", () => { }) }) }) + +describe("getRegularPrice", () => { + it("returns price 0 for empty rooms", () => { + const isMember = false + const nights = 1 + const result = getRegularPrice([], isMember, nights) + + expect(result).toEqual({ + local: { + currency: CurrencyEnum.Unknown, + price: 0, + regularPrice: 0, + }, + requested: undefined, + }) + }) + + it("returns price 0 for rooms without public or member rate", () => { + const isMember = false + const nights = 1 + const guest = { join: false } + const result = getRegularPrice( + [ + { + adults: 1, + breakfast: false, + guest, + roomFeatures: [], + roomRate: {}, + }, + ], + isMember, + nights + ) + + expect(result).toEqual({ + local: { + currency: CurrencyEnum.Unknown, + price: 0, + regularPrice: 0, + }, + requested: undefined, + }) + }) + + it("calculates regular price for non-member", () => { + const isMember = false + const nights = 2 + const guest = { join: false } + const result = getRegularPrice( + [ + { + adults: 1, + breakfast: false, + roomFeatures: null, + guest, + roomRate: { + public: { + localPrice: { + pricePerNight: 100, + regularPricePerStay: 100, + currency: CurrencyEnum.SEK, + }, + }, + member: { + localPrice: { + pricePerNight: 50, + regularPricePerStay: 50, + currency: CurrencyEnum.SEK, + }, + }, + }, + }, + ], + isMember, + nights + ) + + expect(result).toEqual({ + local: { + currency: CurrencyEnum.SEK, + price: 0, + regularPrice: 100, + }, + requested: undefined, + }) + }) + + it("calculates regular price for member", () => { + const isMember = true + const nights = 2 + const guest = { join: false } + const result = getRegularPrice( + [ + { + adults: 1, + breakfast: false, + roomFeatures: [], + guest, + roomRate: { + public: { + localPrice: { + pricePerNight: 100, + regularPricePerStay: 100, + currency: CurrencyEnum.SEK, + }, + }, + member: { + localPrice: { + pricePerNight: 50, + regularPricePerStay: 50, + currency: CurrencyEnum.SEK, + }, + }, + }, + }, + ], + isMember, + nights + ) + + expect(result).toEqual({ + local: { + currency: CurrencyEnum.SEK, + price: 0, + regularPrice: 100, + }, + requested: undefined, + }) + }) + + it("calculates regular price for member without public rate", () => { + const isMember = true + const nights = 2 + const guest = { join: false } + const result = getRegularPrice( + [ + { + adults: 1, + breakfast: false, + roomFeatures: [], + guest, + roomRate: { + member: { + localPrice: { + pricePerNight: 50, + regularPricePerStay: 50, + currency: CurrencyEnum.SEK, + }, + }, + }, + }, + ], + isMember, + nights + ) + + expect(result).toEqual({ + local: { + currency: CurrencyEnum.SEK, + price: 0, + regularPrice: 50, + }, + requested: undefined, + }) + }) + + it("calculates regular price for non-member without member rate", () => { + const isMember = false + const nights = 2 + const guest = { join: false } + const result = getRegularPrice( + [ + { + adults: 1, + breakfast: false, + roomFeatures: [], + guest, + roomRate: { + public: { + localPrice: { + pricePerNight: 50, + regularPricePerStay: 50, + currency: CurrencyEnum.SEK, + }, + }, + }, + }, + ], + isMember, + nights + ) + + expect(result).toEqual({ + local: { + currency: CurrencyEnum.SEK, + price: 0, + regularPrice: 50, + }, + requested: undefined, + }) + }) + + it("calculates regular price for non-member with breakfast", () => { + const isMember = false + const nights = 2 + const guest = { join: false } + const result = getRegularPrice( + [ + { + adults: 1, + breakfast: { localPrice: { price: 20, currency: CurrencyEnum.SEK } }, + roomFeatures: [], + guest, + roomRate: { + public: { + localPrice: { + pricePerNight: 100, + regularPricePerStay: 100, + currency: CurrencyEnum.SEK, + }, + }, + member: { + localPrice: { + pricePerNight: 50, + regularPricePerStay: 50, + currency: CurrencyEnum.SEK, + }, + }, + }, + }, + ], + isMember, + nights + ) + + expect(result).toEqual({ + local: { + currency: CurrencyEnum.SEK, + price: 40, + regularPrice: 140, + }, + requested: undefined, + }) + }) + + it("calculates regular price for non-member with breakfast and room features", () => { + const isMember = false + const nights = 2 + const guest = { join: false } + const result = getRegularPrice( + [ + { + adults: 1, + breakfast: { localPrice: { price: 20, currency: CurrencyEnum.SEK } }, + roomFeatures: [ + { + localPrice: { totalPrice: 3, currency: CurrencyEnum.SEK }, + requestedPrice: { totalPrice: 3, currency: CurrencyEnum.SEK }, + }, + { + localPrice: { totalPrice: 7, currency: CurrencyEnum.SEK }, + requestedPrice: { totalPrice: 7, currency: CurrencyEnum.SEK }, + }, + ], + guest, + roomRate: { + public: { + localPrice: { + pricePerNight: 100, + regularPricePerStay: 100, + currency: CurrencyEnum.SEK, + }, + }, + member: { + localPrice: { + pricePerNight: 50, + regularPricePerStay: 50, + currency: CurrencyEnum.SEK, + }, + }, + }, + }, + ], + isMember, + nights + ) + + expect(result).toEqual({ + local: { + currency: CurrencyEnum.SEK, + price: 50, + regularPrice: 150, + }, + requested: undefined, + }) + }) + + it("calculates regular price to 0 when price is same or larger than regular price", () => { + const isMember = false + const nights = 2 + const guest = { join: false } + const result = getRegularPrice( + [ + { + adults: 1, + breakfast: { localPrice: { price: 0, currency: CurrencyEnum.SEK } }, + roomFeatures: [ + { + localPrice: { totalPrice: 100, currency: CurrencyEnum.SEK }, + requestedPrice: { totalPrice: 3, currency: CurrencyEnum.SEK }, + }, + ], + guest, + roomRate: { + public: { + localPrice: { + pricePerNight: 0, + regularPricePerStay: 0, + currency: CurrencyEnum.SEK, + }, + }, + }, + }, + ], + isMember, + nights + ) + + expect(result).toEqual({ + local: { + currency: CurrencyEnum.SEK, + price: 100, + regularPrice: 0, + }, + requested: undefined, + }) + }) + + it("calculates requested price with breakfast and room features", () => { + const isMember = false + const nights = 2 + const guest = { join: false } + const result = getRegularPrice( + [ + { + adults: 1, + breakfast: { + localPrice: { price: 30, currency: CurrencyEnum.SEK }, + requestedPrice: { price: 3, currency: CurrencyEnum.EUR }, + }, + roomFeatures: [ + { + localPrice: { totalPrice: 20, currency: CurrencyEnum.SEK }, + requestedPrice: { totalPrice: 2, currency: CurrencyEnum.EUR }, + }, + ], + guest, + roomRate: { + public: { + localPrice: { + pricePerNight: 100, + regularPricePerStay: 100, + currency: CurrencyEnum.SEK, + }, + requestedPrice: { + currency: CurrencyEnum.EUR, + pricePerNight: 10, + regularPricePerStay: 10, + }, + }, + }, + }, + ], + isMember, + nights + ) + + expect(result).toEqual({ + local: { + currency: CurrencyEnum.SEK, + price: 80, + regularPrice: 180, + }, + requested: { + currency: CurrencyEnum.EUR, + price: 8, + }, + }) + }) + + it("calculates prices for multi-room", () => { + const isMember = true + const nights = 2 + const result = getRegularPrice( + [ + { + adults: 1, + breakfast: { localPrice: { price: 20, currency: CurrencyEnum.SEK } }, + roomFeatures: null, + guest: { join: false }, + roomRate: { + public: { + localPrice: { + pricePerNight: 100, + regularPricePerStay: 100, + currency: CurrencyEnum.SEK, + }, + }, + member: { + localPrice: { + pricePerNight: 50, + regularPricePerStay: 50, + currency: CurrencyEnum.SEK, + }, + }, + }, + }, + { + adults: 1, + breakfast: { localPrice: { price: 25, currency: CurrencyEnum.SEK } }, + roomFeatures: null, + guest: { join: true }, + roomRate: { + public: { + localPrice: { + pricePerNight: 75, + regularPricePerStay: 75, + currency: CurrencyEnum.SEK, + }, + }, + member: { + localPrice: { + pricePerNight: 30, + regularPricePerStay: 30, + currency: CurrencyEnum.SEK, + }, + }, + }, + }, + ], + isMember, + nights + ) + + expect(result).toEqual({ + local: { + currency: CurrencyEnum.SEK, + price: 90, + regularPrice: 265, + }, + requested: undefined, + }) + }) +}) diff --git a/packages/booking-flow/lib/stores/enter-details/helpers.ts b/packages/booking-flow/lib/stores/enter-details/helpers.ts index ef4ff5e29..e5f6c9a48 100644 --- a/packages/booking-flow/lib/stores/enter-details/helpers.ts +++ b/packages/booking-flow/lib/stores/enter-details/helpers.ts @@ -12,7 +12,6 @@ import type { BreakfastPackage } from "@scandic-hotels/trpc/routers/hotels/schem import type { Packages } from "@scandic-hotels/trpc/types/packages" import type { CorporateChequeProduct, - PriceProduct, Product, } from "@scandic-hotels/trpc/types/roomAvailability" import type { User } from "@scandic-hotels/trpc/types/user" @@ -456,12 +455,16 @@ export function getVoucherPrice(rooms: PriceCalculationRoom[], nights: number) { type PriceCalculationRoom = { adults: number breakfast: - | { localPrice: { price: number; currency?: CurrencyEnum } } + | { + localPrice: { price: number; currency?: CurrencyEnum } + requestedPrice?: { price: number; currency?: CurrencyEnum } + } | false | undefined roomFeatures: | { localPrice: { totalPrice: number; currency?: CurrencyEnum } + requestedPrice: { totalPrice: number; currency?: CurrencyEnum } }[] | null | undefined @@ -518,14 +521,45 @@ export function getRedemptionPrice( ) } -interface TRoomPriceProduct extends TRoom { - roomRate: PriceProduct +type RegularPriceCalculationRoom = PriceCalculationRoom & { + guest: { + join: boolean + membershipNo?: string | null + } +} +type RegularRoomLocalPrice = { + price: number + currency: CurrencyEnum + pricePerStay: number + pricePerNight: number + regularPricePerStay: number +} +type RegularRoomRequestedPrice = { + price: number + currency: CurrencyEnum + pricePerStay: number +} +type GetRegularPriceRoom = RegularPriceCalculationRoom & { + roomRate: { + member: { + localPrice: RegularRoomLocalPrice + requestedPrice: RegularRoomRequestedPrice + } + public: { + localPrice: RegularRoomLocalPrice + requestedPrice: RegularRoomRequestedPrice + } + } } -function getRegularPrice(rooms: TRoom[], isMember: boolean, nights: number) { +export function getRegularPrice( + rooms: RegularPriceCalculationRoom[], + isMember: boolean, + nights: number +) { const totalPrice = rooms .filter( - (room): room is TRoomPriceProduct => + (room): room is GetRegularPriceRoom => "member" in room.roomRate || "public" in room.roomRate ) .reduce( diff --git a/packages/booking-flow/lib/utils/calculateRegularPrice.test.ts b/packages/booking-flow/lib/utils/calculateRegularPrice.test.ts new file mode 100644 index 000000000..c2d75df24 --- /dev/null +++ b/packages/booking-flow/lib/utils/calculateRegularPrice.test.ts @@ -0,0 +1,105 @@ +import { describe, expect, it } from "vitest" + +import { CurrencyEnum } from "@scandic-hotels/common/constants/currency" + +import { calculateRegularPrice } from "./calculateRegularPrice" + +describe("calculateRegularPrice", () => { + it("returns total if useMemberRate is true and missing regularMemberPrice", () => { + const result = calculateRegularPrice({ + total: { + local: { price: 1, currency: CurrencyEnum.SEK }, + }, + regularMemberPrice: undefined, + regularPublicPrice: { pricePerStay: 10 }, + useMemberRate: true, + }) + expect(result).toEqual({ + local: { price: 1, currency: CurrencyEnum.SEK }, + }) + }) + + it("returns total if useMemberRate is false and missing regularPublicPrice", () => { + const result = calculateRegularPrice({ + total: { + local: { price: 1, currency: CurrencyEnum.SEK }, + }, + regularMemberPrice: { pricePerStay: 10 }, + regularPublicPrice: undefined, + useMemberRate: false, + }) + expect(result).toEqual({ + local: { price: 1, currency: CurrencyEnum.SEK }, + }) + }) + + it("calculates regularPrice for regularPublicPrice", () => { + const result = calculateRegularPrice({ + total: { + local: { price: 1, currency: CurrencyEnum.SEK }, + }, + regularMemberPrice: undefined, + regularPublicPrice: { pricePerStay: 10 }, + useMemberRate: false, + }) + expect(result).toEqual({ + local: { price: 1, regularPrice: 10, currency: CurrencyEnum.SEK }, + }) + }) + + it("calculates regularPrice for regularPublicPrice with regularPricePerStay", () => { + const result = calculateRegularPrice({ + total: { + local: { price: 1, currency: CurrencyEnum.SEK }, + }, + regularMemberPrice: undefined, + regularPublicPrice: { pricePerStay: 10, regularPricePerStay: 20 }, + useMemberRate: false, + }) + expect(result).toEqual({ + local: { price: 1, regularPrice: 20, currency: CurrencyEnum.SEK }, + }) + }) + + it("calculates regularPrice for member with regularMemberPrice", () => { + const result = calculateRegularPrice({ + total: { + local: { price: 1, currency: CurrencyEnum.SEK }, + }, + regularMemberPrice: { pricePerStay: 10 }, + regularPublicPrice: undefined, + useMemberRate: true, + }) + expect(result).toEqual({ + local: { price: 1, regularPrice: 10, currency: CurrencyEnum.SEK }, + }) + }) + + it("calculates regularPrice for member with regularPublicPrice and regularMemberRate", () => { + const result = calculateRegularPrice({ + total: { + local: { price: 1, currency: CurrencyEnum.SEK }, + }, + regularMemberPrice: { pricePerStay: 15 }, + regularPublicPrice: { pricePerStay: 10 }, + useMemberRate: true, + }) + expect(result).toEqual({ + local: { price: 1, regularPrice: 10, currency: CurrencyEnum.SEK }, + }) + }) + + it("calculates regularPrice for member with regularMemberRate with pricePerStay", () => { + const result = calculateRegularPrice({ + total: { + local: { price: 1, currency: CurrencyEnum.SEK }, + }, + regularMemberPrice: { pricePerStay: 15, regularPricePerStay: 20 }, + regularPublicPrice: undefined, + useMemberRate: true, + }) + expect(result).toEqual({ + local: { price: 1, regularPrice: 20, currency: CurrencyEnum.SEK }, + }) + }) +})