Merged in feat/refactor-enter-details-price-calculation (pull request #3183)
feat: Refactor enter details price calculation * Refactor getTotalPrice and child functions * Move price calculations from helper file to specific file Approved-by: Linus Flood
This commit is contained in:
@@ -1,17 +1,11 @@
|
||||
import { parsePhoneNumberFromString } from "libphonenumber-js"
|
||||
|
||||
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 { detailsStorageName } from "."
|
||||
|
||||
import type { Product } from "@scandic-hotels/trpc/types/roomAvailability"
|
||||
import type { User } from "@scandic-hotels/trpc/types/user"
|
||||
|
||||
import type { Price } from "../../types/price"
|
||||
import type { PersistedState, RoomState } from "./types"
|
||||
|
||||
export function extractGuestFromUser(user: User) {
|
||||
@@ -34,191 +28,6 @@ export function extractGuestFromUser(user: User) {
|
||||
}
|
||||
}
|
||||
|
||||
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 const checkRoomProgress = (steps: RoomState["steps"]) => {
|
||||
return Object.values(steps)
|
||||
.filter(Boolean)
|
||||
@@ -267,430 +76,3 @@ export function clearSessionStorage() {
|
||||
}
|
||||
sessionStorage.removeItem(detailsStorageName)
|
||||
}
|
||||
|
||||
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 CorporateCheckRoom = PriceCalculationRoom & {
|
||||
roomRate: {
|
||||
corporateCheque: {
|
||||
localPrice: {
|
||||
numberOfCheques: number
|
||||
additionalPricePerStay: number
|
||||
currency?: CurrencyEnum
|
||||
}
|
||||
requestedPrice?: {
|
||||
numberOfCheques: number
|
||||
additionalPricePerStay: number
|
||||
currency?: CurrencyEnum
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getCorporateChequePrice(
|
||||
rooms: PriceCalculationRoom[],
|
||||
nights: number
|
||||
) {
|
||||
return rooms
|
||||
.filter(
|
||||
(room): room is CorporateCheckRoom => "corporateCheque" in room.roomRate
|
||||
)
|
||||
.reduce<Price>(
|
||||
(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 = PriceCalculationRoom & {
|
||||
roomRate: {
|
||||
voucher: {
|
||||
numberOfVouchers: number
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getVoucherPrice(rooms: PriceCalculationRoom[], nights: number) {
|
||||
return rooms
|
||||
.filter((room): room is VoucherRoom => "voucher" in room.roomRate)
|
||||
.reduce<Price>(
|
||||
(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 PriceCalculationRoom = {
|
||||
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 roomRate unless it's RedemptionProduct
|
||||
roomRate: object
|
||||
}
|
||||
type RedemptionRoom = PriceCalculationRoom & {
|
||||
roomRate: {
|
||||
redemption: {
|
||||
localPrice: {
|
||||
pointsPerStay: number
|
||||
additionalPricePerStay: number
|
||||
currency?: CurrencyEnum
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getRedemptionPrice(
|
||||
rooms: PriceCalculationRoom[],
|
||||
nights: number,
|
||||
pointsCurrency?: CurrencyEnum
|
||||
) {
|
||||
return rooms
|
||||
.filter((room): room is RedemptionRoom => "redemption" in room.roomRate)
|
||||
.reduce<Price>(
|
||||
(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 = 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getRegularPrice(
|
||||
rooms: RegularPriceCalculationRoom[],
|
||||
isMember: boolean,
|
||||
nights: number
|
||||
) {
|
||||
const totalPrice = rooms
|
||||
.filter(
|
||||
(room): room is GetRegularPriceRoom =>
|
||||
"member" in room.roomRate || "public" in room.roomRate
|
||||
)
|
||||
.reduce<Price>(
|
||||
(total, room, idx) => {
|
||||
const isMainRoomAndMember = idx === 0 && isMember
|
||||
const join = Boolean(room.guest.join || room.guest.membershipNo)
|
||||
const memberRate = "member" in room.roomRate && room.roomRate.member
|
||||
const publicRate = "public" in room.roomRate && 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
|
||||
}
|
||||
|
||||
type TRoom = Pick<
|
||||
RoomState["room"],
|
||||
"adults" | "breakfast" | "guest" | "roomFeatures" | "roomRate"
|
||||
>
|
||||
|
||||
export function getTotalPrice(
|
||||
rooms: TRoom[],
|
||||
isMember: boolean,
|
||||
nights: number,
|
||||
pointsCurrency?: CurrencyEnum
|
||||
) {
|
||||
const hasCorpChqRates = rooms.some(
|
||||
(room) => "corporateCheque" in room.roomRate
|
||||
)
|
||||
if (hasCorpChqRates) {
|
||||
return getCorporateChequePrice(rooms, nights)
|
||||
}
|
||||
|
||||
const hasRedemptionRates = rooms.some((room) => "redemption" in room.roomRate)
|
||||
if (hasRedemptionRates) {
|
||||
return getRedemptionPrice(rooms, nights, pointsCurrency)
|
||||
}
|
||||
|
||||
const hasVoucherRates = rooms.some((room) => "voucher" in room.roomRate)
|
||||
if (hasVoucherRates) {
|
||||
return getVoucherPrice(rooms, nights)
|
||||
}
|
||||
|
||||
return getRegularPrice(rooms, isMember, nights)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user