import { CurrencyEnum } from "@scandic-hotels/common/constants/currency" import { RateTypeEnum } from "@scandic-hotels/common/constants/rateType" import { logger } from "@scandic-hotels/common/logger" import { calculateRegularPrice } from "../../utils/calculateRegularPrice" import { sumPackages, sumPackagesRequestedPrice } from "../../utils/SelectRate" import type { Product } from "@scandic-hotels/trpc/types/roomAvailability" import type { Price } from "../../types/price" 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) } export function getRoomPrice( roomRate: Product, isMember: boolean, pointsCurrency?: CurrencyEnum ) { if (isMember && "member" in roomRate && roomRate.member) { let publicRate if ( "public" in roomRate && roomRate.public?.rateType === RateTypeEnum.Regular ) { publicRate = roomRate.public } return { perNight: { requested: roomRate.member.requestedPrice ? { currency: roomRate.member.requestedPrice.currency, price: roomRate.member.requestedPrice.pricePerNight, } : undefined, local: { currency: roomRate.member.localPrice.currency, price: roomRate.member.localPrice.pricePerNight, regularPrice: publicRate?.localPrice.pricePerStay || roomRate.member.localPrice.regularPricePerNight, }, }, perStay: { requested: roomRate.member.requestedPrice ? { currency: roomRate.member.requestedPrice.currency, price: roomRate.member.requestedPrice.pricePerStay, } : undefined, local: { currency: roomRate.member.localPrice.currency, price: roomRate.member.localPrice.pricePerStay, regularPrice: publicRate?.localPrice.pricePerStay || roomRate.member.localPrice.regularPricePerStay, }, }, } } if ("public" in roomRate && roomRate.public) { return { perNight: { requested: roomRate.public.requestedPrice ? { currency: roomRate.public.requestedPrice.currency, price: roomRate.public.requestedPrice.pricePerNight, } : undefined, local: { currency: roomRate.public.localPrice.currency, price: roomRate.public.localPrice.pricePerNight, regularPrice: roomRate.public.localPrice.regularPricePerNight, }, }, perStay: { requested: roomRate.public.requestedPrice ? { currency: roomRate.public.requestedPrice.currency, price: roomRate.public.requestedPrice.pricePerStay, } : undefined, local: { currency: roomRate.public.localPrice.currency, price: roomRate.public.localPrice.pricePerStay, regularPrice: roomRate.public.localPrice.regularPricePerStay, }, }, } } if ("corporateCheque" in roomRate) { return { perNight: { requested: roomRate.corporateCheque.requestedPrice ? { currency: CurrencyEnum.CC, price: roomRate.corporateCheque.requestedPrice.numberOfCheques, additionalPrice: roomRate.corporateCheque.requestedPrice.additionalPricePerStay, additionalPriceCurrency: roomRate.corporateCheque.requestedPrice.currency ?? undefined, } : undefined, local: { currency: CurrencyEnum.CC, price: roomRate.corporateCheque.localPrice.numberOfCheques, additionalPrice: roomRate.corporateCheque.localPrice.additionalPricePerStay, additionalPriceCurrency: roomRate.corporateCheque.localPrice.currency ?? undefined, }, }, perStay: { requested: roomRate.corporateCheque.requestedPrice ? { currency: CurrencyEnum.CC, price: roomRate.corporateCheque.requestedPrice.numberOfCheques, additionalPrice: roomRate.corporateCheque.requestedPrice.additionalPricePerStay, additionalPriceCurrency: roomRate.corporateCheque.requestedPrice.currency ?? undefined, } : undefined, local: { currency: CurrencyEnum.CC, price: roomRate.corporateCheque.localPrice.numberOfCheques, additionalPrice: roomRate.corporateCheque.localPrice.additionalPricePerStay, additionalPriceCurrency: roomRate.corporateCheque.localPrice.currency ?? undefined, }, }, } } if ("voucher" in roomRate) { return { perNight: { requested: undefined, local: { currency: CurrencyEnum.Voucher, price: roomRate.voucher.numberOfVouchers, }, }, perStay: { requested: undefined, local: { currency: CurrencyEnum.Voucher, price: roomRate.voucher.numberOfVouchers, }, }, } } if ("redemption" in roomRate) { return { // ToDo Handle perNight as undefined perNight: { requested: undefined, local: { currency: pointsCurrency ?? CurrencyEnum.POINTS, price: roomRate.redemption.localPrice.pointsPerStay, additionalPrice: roomRate.redemption.localPrice.additionalPricePerStay, additionalPriceCurrency: roomRate.redemption.localPrice.currency ?? undefined, }, }, perStay: { requested: undefined, local: { currency: pointsCurrency ?? CurrencyEnum.POINTS, price: roomRate.redemption.localPrice.pointsPerStay, additionalPrice: roomRate.redemption.localPrice.additionalPricePerStay, additionalPriceCurrency: roomRate.redemption.localPrice.currency ?? undefined, }, }, } } throw new Error( `Unable to calculate RoomPrice since user is neither a member or memberRate is missing, or publicRate is missing` ) } export function getAdditionalPrice( total: { local: { additionalPrice?: number additionalPriceCurrency?: CurrencyEnum } }, adults: number, breakfast: | { localPrice: { price: number; currency?: CurrencyEnum } } | false | undefined, nights: number, packages: | { localPrice: { totalPrice: number; currency?: CurrencyEnum } }[] | null | undefined, additionalPrice = 0, additionalPriceCurrency?: CurrencyEnum | null | undefined ) { const breakfastLocalPrice = (breakfast ? breakfast.localPrice.price : 0) * nights * adults const pkgsSum = sumPackages(packages || []) total.local.additionalPrice = add( total.local.additionalPrice, additionalPrice, breakfastLocalPrice, pkgsSum.price ) if (!total.local.additionalPriceCurrency) { if (additionalPriceCurrency) { total.local.additionalPriceCurrency = additionalPriceCurrency } else if (breakfast && breakfast.localPrice.currency) { total.local.additionalPriceCurrency = breakfast.localPrice.currency } else if (pkgsSum.currency) { total.local.additionalPriceCurrency = pkgsSum.currency } } } export function getRequestedAdditionalPrice( total: Price, adults: number, breakfast: | { requestedPrice?: { price: number; currency?: CurrencyEnum } } | false | undefined, nights: number, packages: | { requestedPrice: { totalPrice: number; currency?: CurrencyEnum } }[] | null | undefined, cheques: number, additionalPrice = 0, additionalPriceCurrency: CurrencyEnum | null | undefined ) { if (!total.requested) { total.requested = { currency: CurrencyEnum.CC, price: 0, } } total.requested.price = add(total.requested.price, cheques) const breakfastRequestedPrice = (breakfast ? breakfast.requestedPrice?.price || 0 : 0) * nights * adults const pkgsSumRequested = sumPackagesRequestedPrice(packages) total.requested.additionalPrice = add( total.requested.additionalPrice, additionalPrice, breakfastRequestedPrice, pkgsSumRequested.price ) if (!total.requested.additionalPriceCurrency) { if (additionalPriceCurrency) { total.requested.additionalPriceCurrency = additionalPriceCurrency } else if (pkgsSumRequested.currency) { total.requested.additionalPriceCurrency = pkgsSumRequested.currency } else if (breakfast && breakfast.requestedPrice) { total.requested.additionalPriceCurrency = breakfast.requestedPrice.currency } } } type BasePriceCalculationRoom = { adults: number breakfast: | { 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 // We don't care about what type of roomRate it is yet roomRate: object } type CorporateCheckRoom = BasePriceCalculationRoom & { roomRate: { corporateCheque: { localPrice: { numberOfCheques: number additionalPricePerStay: number currency?: CurrencyEnum } requestedPrice?: { numberOfCheques: number additionalPricePerStay: number currency?: CurrencyEnum } } } } export function getCorporateChequePrice( rooms: CorporateCheckRoom[], nights: number ) { return rooms.reduce( (total, room) => { const corporateCheque = room.roomRate.corporateCheque total.local.price = add( total.local.price, corporateCheque.localPrice.numberOfCheques ) getAdditionalPrice( total, room.adults, room.breakfast, nights, room.roomFeatures, corporateCheque.localPrice.additionalPricePerStay, corporateCheque.localPrice.currency ) if (corporateCheque.requestedPrice) { getRequestedAdditionalPrice( total, room.adults, room.breakfast, nights, room.roomFeatures, corporateCheque.requestedPrice.numberOfCheques, corporateCheque.requestedPrice?.additionalPricePerStay, corporateCheque.requestedPrice?.currency ) } return total }, { local: { currency: CurrencyEnum.CC, price: 0, }, requested: undefined, } ) } type VoucherRoom = BasePriceCalculationRoom & { roomRate: { voucher: { numberOfVouchers: number } } } export function getVoucherPrice(rooms: VoucherRoom[], nights: number) { return rooms.reduce( (total, room) => { const voucher = room.roomRate.voucher total.local.price = add(total.local.price, voucher.numberOfVouchers) getAdditionalPrice( total, room.adults, room.breakfast, nights, room.roomFeatures ) return total }, { local: { currency: CurrencyEnum.Voucher, price: 0, }, requested: undefined, } ) } type RedemptionRoom = BasePriceCalculationRoom & { roomRate: { redemption: { localPrice: { pointsPerStay: number additionalPricePerStay: number currency?: CurrencyEnum } } } } export function getRedemptionPrice( rooms: RedemptionRoom[], nights: number, pointsCurrency?: CurrencyEnum ) { return rooms.reduce( (total, room) => { const redemption = room.roomRate.redemption total.local.price = add( total.local.price, redemption.localPrice.pointsPerStay ) getAdditionalPrice( total, room.adults, room.breakfast, nights, room.roomFeatures, redemption.localPrice.additionalPricePerStay, redemption.localPrice.currency ) return total }, { local: { currency: pointsCurrency ?? CurrencyEnum.POINTS, price: 0, }, requested: undefined, } ) } type RegularPriceCalculationRoom = BasePriceCalculationRoom & { guest: { join: boolean membershipNo?: string | null } roomRate: { member?: { localPrice: RegularRoomLocalPrice requestedPrice?: RegularRoomRequestedPrice | null } | null public?: { localPrice: RegularRoomLocalPrice requestedPrice?: RegularRoomRequestedPrice | null } | null } } type RegularRoomLocalPrice = { currency: CurrencyEnum pricePerStay: number pricePerNight: number regularPricePerStay: number } type RegularRoomRequestedPrice = { currency: CurrencyEnum pricePerStay: number } export function getRegularPrice( rooms: RegularPriceCalculationRoom[], isMember: boolean, nights: number ) { const totalPrice = rooms.reduce( (total, room, idx) => { const isMainRoomAndMember = idx === 0 && isMember const join = Boolean(room.guest.join || room.guest.membershipNo) const memberRate = room.roomRate.member const publicRate = room.roomRate.public const useMemberRate = (isMainRoomAndMember || join) && memberRate const rate = useMemberRate ? memberRate : publicRate if (!rate) { return total } const breakfastLocalPrice = (room.breakfast ? room.breakfast.localPrice.price || 0 : 0) * nights * room.adults const pkgsSum = sumPackages(room.roomFeatures || []) const additionalCost = breakfastLocalPrice + pkgsSum.price total.local.currency = rate.localPrice.currency total.local.price = add( total.local.price, rate.localPrice.pricePerStay, additionalCost ) if (rate.requestedPrice) { if (!total.requested) { total.requested = { currency: rate.requestedPrice.currency, price: 0, } } const breakfastRequestedPrice = (room.breakfast ? (room.breakfast.requestedPrice?.price ?? 0) : 0) * nights * room.adults const pkgsSumRequested = sumPackagesRequestedPrice(room.roomFeatures) total.requested.price = add( total.requested.price, rate.requestedPrice.pricePerStay, breakfastRequestedPrice, pkgsSumRequested.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, additionalCost, }) }, { local: { currency: CurrencyEnum.Unknown, price: 0, regularPrice: 0, }, requested: undefined, } ) if ( totalPrice.local.regularPrice && totalPrice.local.price >= totalPrice.local.regularPrice ) { totalPrice.local.regularPrice = 0 } return totalPrice } export function getTotalPrice( rooms: ( | RegularPriceCalculationRoom | CorporateCheckRoom | RedemptionRoom | VoucherRoom )[], isMember: boolean, nights: number, pointsCurrency?: CurrencyEnum ) { const corporateChequeRooms = rooms.filter( (x): x is CorporateCheckRoom => "corporateCheque" in x.roomRate ) if (corporateChequeRooms.length > 0) { return getCorporateChequePrice(corporateChequeRooms, nights) } const redemptionRooms = rooms.filter( (x): x is RedemptionRoom => "redemption" in x.roomRate ) if (redemptionRooms.length > 0) { return getRedemptionPrice(redemptionRooms, nights, pointsCurrency) } const voucherRooms = rooms.filter( (x): x is VoucherRoom => "voucher" in x.roomRate ) if (voucherRooms.length > 0) { return getVoucherPrice(voucherRooms, nights) } const regularRooms = rooms.filter( (x): x is RegularPriceCalculationRoom => "member" in x.roomRate || "public" in x.roomRate ) if (regularRooms.length > 0) { return getRegularPrice(regularRooms, isMember, nights) } logger.warn( "Unable to determine room type for price calculation, return zero price" ) return { local: { currency: CurrencyEnum.Unknown, price: 0, regularPrice: 0, }, requested: undefined, } }