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 0fb24f62b..33b7e6c1b 100644 --- a/packages/booking-flow/lib/stores/enter-details/helpers.test.ts +++ b/packages/booking-flow/lib/stores/enter-details/helpers.test.ts @@ -1,11 +1,14 @@ import { describe, expect, it } from "vitest" import { CurrencyEnum } from "@scandic-hotels/common/constants/currency" +import { RoomPackageCodeEnum } from "@scandic-hotels/trpc/enums/roomFilter" import { getAdditionalPrice, + getCorporateChequePrice, getRedemptionPrice, getRegularPrice, + getRequestedAdditionalPrice, getVoucherPrice, } from "./helpers" @@ -137,6 +140,900 @@ describe("getAdditionalPrice", () => { }) }) +type GetRequestedAdditionalPriceParams = Parameters< + typeof getRequestedAdditionalPrice +> +describe("getRequestedAdditionalPrice", () => { + it("should initialize requested object when missing and add cheques to price", () => { + const total: GetRequestedAdditionalPriceParams[0] = { + local: { + price: 100, + currency: CurrencyEnum.SEK, + }, + } + const adults = 1 + const nights = 1 + const breakfast = undefined + const packages = null + const cheques = 5 + const additionalPrice = 0 + const additionalPriceCurrency = undefined + + getRequestedAdditionalPrice( + total, + adults, + breakfast, + nights, + packages, + cheques, + additionalPrice, + additionalPriceCurrency + ) + + expect(total.requested).toEqual({ + currency: CurrencyEnum.CC, + price: 5, + additionalPrice: 0, + }) + }) + + it("should add to existing requested price", () => { + const total: GetRequestedAdditionalPriceParams[0] = { + local: { + price: 100, + currency: CurrencyEnum.SEK, + }, + requested: { + currency: CurrencyEnum.CC, + price: 10, + }, + } + const adults = 1 + const nights = 1 + const breakfast = undefined + const packages = null + const cheques = 5 + const additionalPrice = 0 + const additionalPriceCurrency = undefined + + getRequestedAdditionalPrice( + total, + adults, + breakfast, + nights, + packages, + cheques, + additionalPrice, + additionalPriceCurrency + ) + + expect(total.requested?.price).toBe(15) + }) + + it("should calculate additional price with only additionalPrice parameter", () => { + const total: GetRequestedAdditionalPriceParams[0] = { + local: { + price: 100, + currency: CurrencyEnum.SEK, + }, + } + const adults = 1 + const nights = 1 + const breakfast = undefined + const packages = null + const cheques = 0 + const additionalPrice = 50 + const additionalPriceCurrency = CurrencyEnum.EUR + + getRequestedAdditionalPrice( + total, + adults, + breakfast, + nights, + packages, + cheques, + additionalPrice, + additionalPriceCurrency + ) + + expect(total.requested).toEqual({ + currency: CurrencyEnum.CC, + price: 0, + additionalPrice: 50, + additionalPriceCurrency: CurrencyEnum.EUR, + }) + }) + + it("should calculate additional price with breakfast", () => { + const total: GetRequestedAdditionalPriceParams[0] = { + local: { + price: 100, + currency: CurrencyEnum.SEK, + }, + } + const adults = 2 + const nights = 3 + const breakfast = { + requestedPrice: { + price: 10, + currency: CurrencyEnum.EUR, + }, + } + const packages = null + const cheques = 0 + const additionalPrice = 0 + const additionalPriceCurrency = undefined + + getRequestedAdditionalPrice( + total, + adults, + breakfast, + nights, + packages, + cheques, + additionalPrice, + additionalPriceCurrency + ) + + expect(total.requested?.additionalPrice).toBe(60) + }) + + it("should calculate additional price with packages", () => { + const total: GetRequestedAdditionalPriceParams[0] = { + local: { + price: 100, + currency: CurrencyEnum.SEK, + }, + } + const adults = 1 + const nights = 1 + const breakfast = undefined + const packages = [ + { + code: RoomPackageCodeEnum.PET_ROOM, + description: "Package 1", + localPrice: { + totalPrice: 100, + currency: CurrencyEnum.SEK, + price: 100, + }, + requestedPrice: { + totalPrice: 25, + currency: CurrencyEnum.EUR, + price: 25, + }, + inventories: [], + itemCode: "PKG1", + }, + { + code: RoomPackageCodeEnum.ALLERGY_ROOM, + description: "Package 2", + localPrice: { + totalPrice: 80, + currency: CurrencyEnum.SEK, + price: 80, + }, + requestedPrice: { + totalPrice: 15, + currency: CurrencyEnum.EUR, + price: 15, + }, + inventories: [], + itemCode: "PKG2", + }, + ] + const cheques = 0 + const additionalPrice = 0 + const additionalPriceCurrency = undefined + + getRequestedAdditionalPrice( + total, + adults, + breakfast, + nights, + packages, + cheques, + additionalPrice, + additionalPriceCurrency + ) + + expect(total.requested?.additionalPrice).toBe(40) + }) + + it("should calculate combined price with breakfast, packages, and additionalPrice", () => { + const total: GetRequestedAdditionalPriceParams[0] = { + local: { + price: 100, + currency: CurrencyEnum.SEK, + }, + } + const adults = 2 + const nights = 2 + const breakfast = { + requestedPrice: { + price: 10, + currency: CurrencyEnum.EUR, + }, + } + const packages = [ + { + code: RoomPackageCodeEnum.PET_ROOM, + description: "Package 1", + localPrice: { + totalPrice: 100, + currency: CurrencyEnum.SEK, + price: 100, + }, + requestedPrice: { + totalPrice: 20, + currency: CurrencyEnum.EUR, + price: 20, + }, + inventories: [], + itemCode: "PKG1", + }, + ] + const cheques = 3 + const additionalPrice = 15 + const additionalPriceCurrency = CurrencyEnum.EUR + + getRequestedAdditionalPrice( + total, + adults, + breakfast, + nights, + packages, + cheques, + additionalPrice, + additionalPriceCurrency + ) + + expect(total.requested).toEqual({ + currency: CurrencyEnum.CC, + price: 3, + additionalPrice: 75, + additionalPriceCurrency: CurrencyEnum.EUR, + }) + }) + + it("should set currency from additionalPriceCurrency when provided", () => { + const total: GetRequestedAdditionalPriceParams[0] = { + local: { + price: 100, + currency: CurrencyEnum.SEK, + }, + } + const adults = 1 + const nights = 1 + const breakfast = undefined + const packages = null + const cheques = 0 + const additionalPrice = 50 + const additionalPriceCurrency = CurrencyEnum.EUR + + getRequestedAdditionalPrice( + total, + adults, + breakfast, + nights, + packages, + cheques, + additionalPrice, + additionalPriceCurrency + ) + + expect(total.requested?.additionalPriceCurrency).toBe(CurrencyEnum.EUR) + }) + + it("should set currency from packages when additionalPriceCurrency not provided", () => { + const total: GetRequestedAdditionalPriceParams[0] = { + local: { + price: 100, + currency: CurrencyEnum.SEK, + }, + } + const adults = 1 + const nights = 1 + const breakfast = undefined + const packages = [ + { + code: RoomPackageCodeEnum.PET_ROOM, + description: "Package 1", + localPrice: { + totalPrice: 100, + currency: CurrencyEnum.SEK, + price: 100, + }, + requestedPrice: { + totalPrice: 20, + currency: CurrencyEnum.EUR, + price: 20, + }, + inventories: [], + itemCode: "PKG1", + }, + ] + const cheques = 0 + const additionalPrice = 0 + const additionalPriceCurrency = undefined + + getRequestedAdditionalPrice( + total, + adults, + breakfast, + nights, + packages, + cheques, + additionalPrice, + additionalPriceCurrency + ) + + expect(total.requested?.additionalPriceCurrency).toBe(CurrencyEnum.EUR) + }) + + it("should set currency from breakfast when no other currency available", () => { + const total: GetRequestedAdditionalPriceParams[0] = { + local: { + price: 100, + currency: CurrencyEnum.SEK, + }, + } + const adults = 1 + const nights = 1 + const breakfast = { + requestedPrice: { + price: 10, + currency: CurrencyEnum.EUR, + }, + } + const packages = null + const cheques = 0 + const additionalPrice = 0 + const additionalPriceCurrency = undefined + + getRequestedAdditionalPrice( + total, + adults, + breakfast, + nights, + packages, + cheques, + additionalPrice, + additionalPriceCurrency + ) + + expect(total.requested?.additionalPriceCurrency).toBe(CurrencyEnum.EUR) + }) + + it("should not override existing additionalPriceCurrency", () => { + const total: GetRequestedAdditionalPriceParams[0] = { + local: { + price: 100, + currency: CurrencyEnum.SEK, + }, + requested: { + currency: CurrencyEnum.CC, + price: 0, + additionalPriceCurrency: CurrencyEnum.NOK, + }, + } + const adults = 1 + const nights = 1 + const breakfast = { + requestedPrice: { + price: 10, + currency: CurrencyEnum.EUR, + }, + } + const packages = null + const cheques = 0 + const additionalPrice = 0 + const additionalPriceCurrency = undefined + + getRequestedAdditionalPrice( + total, + adults, + breakfast, + nights, + packages, + cheques, + additionalPrice, + additionalPriceCurrency + ) + + expect(total.requested?.additionalPriceCurrency).toBe(CurrencyEnum.NOK) + }) + + it("should handle breakfast without requestedPrice", () => { + const total: GetRequestedAdditionalPriceParams[0] = { + local: { + price: 100, + currency: CurrencyEnum.SEK, + }, + } + const adults = 2 + const nights = 2 + const breakfast = { + requestedPrice: { + price: 0, + currency: CurrencyEnum.SEK, + }, + } + const packages = null + const cheques = 0 + const additionalPrice = 0 + const additionalPriceCurrency = undefined + + getRequestedAdditionalPrice( + total, + adults, + breakfast, + nights, + packages, + cheques, + additionalPrice, + additionalPriceCurrency + ) + + expect(total.requested?.additionalPrice).toBe(0) + }) + + it("should handle false breakfast value", () => { + const total: GetRequestedAdditionalPriceParams[0] = { + local: { + price: 100, + currency: CurrencyEnum.SEK, + }, + } + const adults = 2 + const nights = 2 + const breakfast = false + const packages = null + const cheques = 0 + const additionalPrice = 0 + const additionalPriceCurrency = undefined + + getRequestedAdditionalPrice( + total, + adults, + breakfast, + nights, + packages, + cheques, + additionalPrice, + additionalPriceCurrency + ) + + expect(total.requested?.additionalPrice).toBe(0) + }) +}) + +type GetCorporateChequePriceParams = Parameters +describe("getCorporateChequePrice", () => { + it("returns price 0 when rooms are empty", () => { + const nights = 1 + const result = getCorporateChequePrice([], nights) + + expect(result).toEqual({ + local: { + price: 0, + currency: CurrencyEnum.CC, + }, + requested: undefined, + }) + }) + + it("returns price for single room with corporate cheque", () => { + const nights = 2 + const rooms: GetCorporateChequePriceParams[0] = [ + { + adults: 1, + breakfast: false, + roomFeatures: [], + roomRate: { + corporateCheque: { + localPrice: { + numberOfCheques: 3, + additionalPricePerStay: 0, + }, + }, + }, + }, + ] + + const result = getCorporateChequePrice(rooms, nights) + + expect(result).toEqual({ + local: { + price: 3, + currency: CurrencyEnum.CC, + additionalPrice: 0, + }, + requested: undefined, + }) + }) + + it("calculates price with additionalPricePerStay", () => { + const nights = 2 + const rooms: GetCorporateChequePriceParams[0] = [ + { + adults: 1, + breakfast: false, + roomFeatures: [], + roomRate: { + corporateCheque: { + localPrice: { + numberOfCheques: 2, + additionalPricePerStay: 150, + currency: CurrencyEnum.SEK, + }, + }, + }, + }, + ] + + const result = getCorporateChequePrice(rooms, nights) + + expect(result).toEqual({ + local: { + price: 2, + currency: CurrencyEnum.CC, + additionalPrice: 150, + additionalPriceCurrency: CurrencyEnum.SEK, + }, + requested: undefined, + }) + }) + + it("calculates price with breakfast", () => { + const nights = 2 + const rooms: GetCorporateChequePriceParams[0] = [ + { + adults: 2, + breakfast: { + localPrice: { + price: 50, + currency: CurrencyEnum.SEK, + }, + }, + roomFeatures: [], + roomRate: { + corporateCheque: { + localPrice: { + numberOfCheques: 3, + additionalPricePerStay: 100, + currency: CurrencyEnum.SEK, + }, + }, + }, + }, + ] + + const result = getCorporateChequePrice(rooms, nights) + + expect(result).toEqual({ + local: { + price: 3, + currency: CurrencyEnum.CC, + additionalPrice: 300, + additionalPriceCurrency: CurrencyEnum.SEK, + }, + requested: undefined, + }) + }) + + it("calculates price with room features", () => { + const nights = 2 + const rooms: GetCorporateChequePriceParams[0] = [ + { + adults: 1, + breakfast: false, + roomFeatures: [ + { + localPrice: { + totalPrice: 75, + currency: CurrencyEnum.SEK, + }, + requestedPrice: { + totalPrice: 10, + currency: CurrencyEnum.EUR, + }, + }, + ], + roomRate: { + corporateCheque: { + localPrice: { + numberOfCheques: 2, + additionalPricePerStay: 50, + currency: CurrencyEnum.SEK, + }, + }, + }, + }, + ] + + const result = getCorporateChequePrice(rooms, nights) + + expect(result).toEqual({ + local: { + price: 2, + currency: CurrencyEnum.CC, + additionalPrice: 125, + additionalPriceCurrency: CurrencyEnum.SEK, + }, + requested: undefined, + }) + }) + + it("calculates requested price when available", () => { + const nights = 2 + const rooms: GetCorporateChequePriceParams[0] = [ + { + adults: 1, + breakfast: false, + roomFeatures: [], + roomRate: { + corporateCheque: { + localPrice: { + numberOfCheques: 3, + additionalPricePerStay: 100, + currency: CurrencyEnum.SEK, + }, + requestedPrice: { + numberOfCheques: 2, + additionalPricePerStay: 15, + currency: CurrencyEnum.EUR, + }, + }, + }, + }, + ] + + const result = getCorporateChequePrice(rooms, nights) + + expect(result).toEqual({ + local: { + price: 3, + currency: CurrencyEnum.CC, + additionalPrice: 100, + additionalPriceCurrency: CurrencyEnum.SEK, + }, + requested: { + price: 2, + currency: CurrencyEnum.CC, + additionalPrice: 15, + additionalPriceCurrency: CurrencyEnum.EUR, + }, + }) + }) + + it("calculates requested price with breakfast", () => { + const nights = 2 + const rooms: GetCorporateChequePriceParams[0] = [ + { + adults: 2, + breakfast: { + localPrice: { + price: 40, + currency: CurrencyEnum.SEK, + }, + requestedPrice: { + price: 5, + currency: CurrencyEnum.EUR, + }, + }, + roomFeatures: [], + roomRate: { + corporateCheque: { + localPrice: { + numberOfCheques: 3, + additionalPricePerStay: 100, + currency: CurrencyEnum.SEK, + }, + requestedPrice: { + numberOfCheques: 2, + additionalPricePerStay: 10, + currency: CurrencyEnum.EUR, + }, + }, + }, + }, + ] + + const result = getCorporateChequePrice(rooms, nights) + + expect(result).toEqual({ + local: { + price: 3, + currency: CurrencyEnum.CC, + additionalPrice: 260, + additionalPriceCurrency: CurrencyEnum.SEK, + }, + requested: { + price: 2, + currency: CurrencyEnum.CC, + additionalPrice: 30, + additionalPriceCurrency: CurrencyEnum.EUR, + }, + }) + }) + + it("calculates price for multiple rooms", () => { + const nights = 1 + const rooms: GetCorporateChequePriceParams[0] = [ + { + adults: 1, + breakfast: false, + roomFeatures: [], + roomRate: { + corporateCheque: { + localPrice: { + numberOfCheques: 2, + additionalPricePerStay: 50, + currency: CurrencyEnum.SEK, + }, + }, + }, + }, + { + adults: 2, + breakfast: { + localPrice: { + price: 30, + currency: CurrencyEnum.SEK, + }, + }, + roomFeatures: [], + roomRate: { + corporateCheque: { + localPrice: { + numberOfCheques: 3, + additionalPricePerStay: 75, + currency: CurrencyEnum.SEK, + }, + requestedPrice: { + numberOfCheques: 2, + additionalPricePerStay: 10, + currency: CurrencyEnum.EUR, + }, + }, + }, + }, + ] + + const result = getCorporateChequePrice(rooms, nights) + + expect(result).toEqual({ + local: { + price: 5, + currency: CurrencyEnum.CC, + additionalPrice: 185, + additionalPriceCurrency: CurrencyEnum.SEK, + }, + requested: { + price: 2, + currency: CurrencyEnum.CC, + additionalPrice: 10, + additionalPriceCurrency: CurrencyEnum.EUR, + }, + }) + }) + + it("does not return price for rooms without corporateCheque", () => { + const nights = 2 + const rooms: GetCorporateChequePriceParams[0] = [ + { + adults: 1, + breakfast: false, + roomFeatures: [], + roomRate: { + public: { + localPrice: { + pricePerStay: 500, + }, + }, + }, + }, + { + adults: 1, + breakfast: false, + roomFeatures: [], + roomRate: { + corporateCheque: { + localPrice: { + numberOfCheques: 3, + additionalPricePerStay: 0, + }, + }, + }, + }, + ] + + const result = getCorporateChequePrice(rooms, nights) + + expect(result).toEqual({ + local: { + price: 3, + currency: CurrencyEnum.CC, + additionalPrice: 0, + }, + requested: undefined, + }) + }) + + it("calculates combined price with breakfast and room features", () => { + const nights = 3 + const rooms: GetCorporateChequePriceParams[0] = [ + { + adults: 2, + breakfast: { + localPrice: { + price: 25, + currency: CurrencyEnum.SEK, + }, + requestedPrice: { + price: 3, + currency: CurrencyEnum.EUR, + }, + }, + roomFeatures: [ + { + localPrice: { + totalPrice: 100, + currency: CurrencyEnum.SEK, + }, + requestedPrice: { + totalPrice: 12, + currency: CurrencyEnum.EUR, + }, + }, + { + localPrice: { + totalPrice: 50, + currency: CurrencyEnum.SEK, + }, + requestedPrice: { + totalPrice: 6, + currency: CurrencyEnum.EUR, + }, + }, + ], + roomRate: { + corporateCheque: { + localPrice: { + numberOfCheques: 5, + additionalPricePerStay: 200, + currency: CurrencyEnum.SEK, + }, + requestedPrice: { + numberOfCheques: 4, + additionalPricePerStay: 25, + currency: CurrencyEnum.EUR, + }, + }, + }, + }, + ] + + const result = getCorporateChequePrice(rooms, nights) + + expect(result).toEqual({ + local: { + price: 5, + currency: CurrencyEnum.CC, + additionalPrice: 500, + additionalPriceCurrency: CurrencyEnum.SEK, + }, + requested: { + price: 4, + currency: CurrencyEnum.CC, + additionalPrice: 61, + additionalPriceCurrency: CurrencyEnum.EUR, + }, + }) + }) +}) + describe("getRedemptionPrice", () => { it("returns price 0 and default currency when rooms are empty", () => { const result = getRedemptionPrice([], 1) diff --git a/packages/booking-flow/lib/stores/enter-details/helpers.ts b/packages/booking-flow/lib/stores/enter-details/helpers.ts index e5f6c9a48..980fd54d6 100644 --- a/packages/booking-flow/lib/stores/enter-details/helpers.ts +++ b/packages/booking-flow/lib/stores/enter-details/helpers.ts @@ -8,12 +8,7 @@ import { calculateRegularPrice } from "../../utils/calculateRegularPrice" import { sumPackages, sumPackagesRequestedPrice } from "../../utils/SelectRate" import { detailsStorageName } from "." -import type { BreakfastPackage } from "@scandic-hotels/trpc/routers/hotels/schemas/packages" -import type { Packages } from "@scandic-hotels/trpc/types/packages" -import type { - CorporateChequeProduct, - Product, -} from "@scandic-hotels/trpc/types/roomAvailability" +import type { Product } from "@scandic-hotels/trpc/types/roomAvailability" import type { User } from "@scandic-hotels/trpc/types/user" import type { Price } from "../../types/price" @@ -315,12 +310,20 @@ export function getAdditionalPrice( } } -function getRequestedAdditionalPrice( +export function getRequestedAdditionalPrice( total: Price, adults: number, - breakfast: BreakfastPackage | false | undefined, + breakfast: + | { + requestedPrice?: { price: number; currency?: CurrencyEnum } + } + | false + | undefined, nights: number, - packages: Packages | null, + packages: + | { requestedPrice: { totalPrice: number; currency?: CurrencyEnum } }[] + | null + | undefined, cheques: number, additionalPrice = 0, additionalPriceCurrency: CurrencyEnum | null | undefined @@ -362,14 +365,30 @@ type TRoom = Pick< "adults" | "breakfast" | "guest" | "roomFeatures" | "roomRate" > -interface TRoomCorporateCheque extends TRoom { - roomRate: CorporateChequeProduct +type CorporateCheckRoom = PriceCalculationRoom & { + roomRate: { + corporateCheque: { + localPrice: { + numberOfCheques: number + additionalPricePerStay: number + currency?: CurrencyEnum + } + requestedPrice?: { + numberOfCheques: number + additionalPricePerStay: number + currency?: CurrencyEnum + } + } + } } -function getCorporateChequePrice(rooms: TRoom[], nights: number) { +export function getCorporateChequePrice( + rooms: PriceCalculationRoom[], + nights: number +) { return rooms .filter( - (room): room is TRoomCorporateCheque => "corporateCheque" in room.roomRate + (room): room is CorporateCheckRoom => "corporateCheque" in room.roomRate ) .reduce( (total, room) => {