feat(SW-1717): rewrite select-rate to show all variants of rate-cards
This commit is contained in:
committed by
Michael Zetterberg
parent
adde77eaa9
commit
ebaea78fb3
@@ -2,6 +2,8 @@ import { z } from "zod"
|
||||
|
||||
import { toLang } from "@/server/utils"
|
||||
|
||||
import { nullableStringValidator } from "@/utils/zod/stringValidator"
|
||||
|
||||
import { occupancySchema } from "./schemas/availability/occupancy"
|
||||
import { productTypeSchema } from "./schemas/availability/productType"
|
||||
import { citySchema } from "./schemas/city"
|
||||
@@ -23,6 +25,7 @@ import { roomConfigurationSchema } from "./schemas/roomAvailability/configuratio
|
||||
import { rateDefinitionSchema } from "./schemas/roomAvailability/rateDefinition"
|
||||
|
||||
import { AvailabilityEnum } from "@/types/components/hotelReservation/selectHotel/selectHotel"
|
||||
import { RateTypeEnum } from "@/types/enums/rateType"
|
||||
import type {
|
||||
AdditionalData,
|
||||
City,
|
||||
@@ -101,20 +104,6 @@ export const hotelsAvailabilitySchema = z.object({
|
||||
),
|
||||
})
|
||||
|
||||
function everyRateHasBreakfastIncluded(
|
||||
product: Product,
|
||||
rateDefinitions: RateDefinition[],
|
||||
userType: "member" | "public"
|
||||
) {
|
||||
const rateDefinition = rateDefinitions.find(
|
||||
(rd) => rd.rateCode === product[userType]?.rateCode
|
||||
)
|
||||
if (!rateDefinition) {
|
||||
return false
|
||||
}
|
||||
return rateDefinition.breakfastIncluded
|
||||
}
|
||||
|
||||
function getRate(rate: RateDefinition) {
|
||||
switch (rate.cancellationRule) {
|
||||
case "CancellableBefore6PM":
|
||||
@@ -124,7 +113,9 @@ function getRate(rate: RateDefinition) {
|
||||
case "NotCancellable":
|
||||
return "save"
|
||||
default:
|
||||
console.info(`Should never happen!`)
|
||||
console.info(
|
||||
`Unknown cancellationRule [${rate.cancellationRule}]. This should never happen!`
|
||||
)
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -151,10 +142,11 @@ function sortRoomConfigs(a: RoomConfiguration, b: RoomConfiguration) {
|
||||
return statusLookup[a.status] - statusLookup[b.status]
|
||||
}
|
||||
|
||||
const baseRoomsCombinedAvailabilitySchema = z
|
||||
export const roomsAvailabilitySchema = z
|
||||
.object({
|
||||
data: z.object({
|
||||
attributes: z.object({
|
||||
bookingCode: nullableStringValidator,
|
||||
checkInDate: z.string(),
|
||||
checkOutDate: z.string(),
|
||||
hotelId: z.number(),
|
||||
@@ -204,159 +196,213 @@ const baseRoomsCombinedAvailabilitySchema = z
|
||||
type: z.string().optional(),
|
||||
}),
|
||||
})
|
||||
.transform(({ data: { attributes } }) => {
|
||||
const rateDefinitions = attributes.rateDefinitions
|
||||
const cancellationRuleLookup = rateDefinitions.reduce((acc, val) => {
|
||||
// @ts-expect-error - index of cancellationRule TS
|
||||
acc[val.rateCode] = cancellationRules[val.cancellationRule]
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
function transformRoomConfigs({
|
||||
data: { attributes },
|
||||
}: typeof baseRoomsCombinedAvailabilitySchema._type) {
|
||||
const rateDefinitions = attributes.rateDefinitions
|
||||
const cancellationRuleLookup = rateDefinitions.reduce((acc, val) => {
|
||||
// @ts-expect-error - index of cancellationRule TS
|
||||
acc[val.rateCode] = cancellationRules[val.cancellationRule]
|
||||
return acc
|
||||
}, {})
|
||||
function getProductRateCode(product: Product) {
|
||||
if ("corporateCheque" in product) {
|
||||
return product.corporateCheque.rateCode
|
||||
}
|
||||
if ("redemption" in product && product.redemption) {
|
||||
return product.redemption.rateCode
|
||||
}
|
||||
if ("voucher" in product) {
|
||||
return product.voucher.rateCode
|
||||
}
|
||||
if ("public" in product && product.public) {
|
||||
return product.public.rateCode
|
||||
}
|
||||
if ("member" in product && product.member) {
|
||||
return product.member.rateCode
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
const roomConfigurations = attributes.roomConfigurations
|
||||
.map((room) => {
|
||||
if (room.products.length) {
|
||||
room.breakfastIncludedInAllRatesMember = room.products.every(
|
||||
(product) =>
|
||||
everyRateHasBreakfastIncluded(product, rateDefinitions, "member")
|
||||
)
|
||||
room.breakfastIncludedInAllRatesPublic = room.products.every(
|
||||
(product) =>
|
||||
everyRateHasBreakfastIncluded(product, rateDefinitions, "public")
|
||||
)
|
||||
function sortProductsBasedOnCancellationRule(a: Product, b: Product) {
|
||||
// @ts-expect-error - index
|
||||
const lookUpA = cancellationRuleLookup[getProductRateCode(a)]
|
||||
// @ts-expect-error - index
|
||||
const lookUpB = cancellationRuleLookup[getProductRateCode(b)]
|
||||
return lookUpA - lookUpB
|
||||
}
|
||||
|
||||
room.products = room.products.map((product) => {
|
||||
const publicRate = product.public
|
||||
if (publicRate?.rateCode) {
|
||||
const publicRateDefinition = rateDefinitions.find(
|
||||
(rateDefinition) =>
|
||||
rateDefinition.rateCode === publicRate.rateCode
|
||||
)
|
||||
if (publicRateDefinition) {
|
||||
const rate = getRate(publicRateDefinition)
|
||||
if (rate) {
|
||||
product.rate = rate
|
||||
if (rate === "flex") {
|
||||
product.isFlex = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
function findRateDefintion(rateCode: string) {
|
||||
return rateDefinitions.find(
|
||||
(rateDefinition) => rateDefinition.rateCode === rateCode
|
||||
)
|
||||
}
|
||||
|
||||
const memberRate = product.member
|
||||
if (memberRate?.rateCode) {
|
||||
const memberRateDefinition = rateDefinitions.find(
|
||||
(rate) => rate.rateCode === memberRate.rateCode
|
||||
)
|
||||
if (memberRateDefinition) {
|
||||
const rate = getRate(memberRateDefinition)
|
||||
if (rate) {
|
||||
product.rate = rate
|
||||
if (rate === "flex") {
|
||||
product.isFlex = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const voucherRate = product.voucher
|
||||
if (voucherRate?.rateCode) {
|
||||
const voucherRateDefinition = rateDefinitions.find(
|
||||
(rate) => rate.rateCode === voucherRate.rateCode
|
||||
)
|
||||
if (voucherRateDefinition) {
|
||||
const rate = getRate(voucherRateDefinition)
|
||||
if (rate) {
|
||||
product.rate = rate
|
||||
if (rate === "flex") {
|
||||
product.isFlex = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const chequeRate = product.bonusCheque
|
||||
if (chequeRate?.rateCode) {
|
||||
const chequeRateDefinition = rateDefinitions.find(
|
||||
(rate) => rate.rateCode === chequeRate.rateCode
|
||||
)
|
||||
if (chequeRateDefinition) {
|
||||
const rate = getRate(chequeRateDefinition)
|
||||
if (rate) {
|
||||
product.rate = rate
|
||||
if (rate === "flex") {
|
||||
product.isFlex = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return product
|
||||
})
|
||||
|
||||
// CancellationRule is the same for public and member per product
|
||||
// Sorting to guarantee order based on rate
|
||||
room.products = room.products.sort(
|
||||
(a, b) =>
|
||||
// @ts-expect-error - index
|
||||
cancellationRuleLookup[a.public?.rateCode || a.member?.rateCode] -
|
||||
// @ts-expect-error - index
|
||||
cancellationRuleLookup[b.public?.rateCode || b.member?.rateCode]
|
||||
)
|
||||
function getRateDetails(product: Product) {
|
||||
let rateCode = ""
|
||||
if ("corporateCheque" in product) {
|
||||
rateCode = product.corporateCheque.rateCode
|
||||
} else if ("redemption" in product && product.redemption) {
|
||||
rateCode = product.redemption.rateCode
|
||||
} else if ("voucher" in product && product.voucher) {
|
||||
rateCode = product.voucher.rateCode
|
||||
} else if ("public" in product && product.public) {
|
||||
rateCode = product.public.rateCode
|
||||
} else if ("member" in product && product.member) {
|
||||
rateCode = product.member.rateCode
|
||||
}
|
||||
|
||||
return room
|
||||
})
|
||||
.sort(sortRoomConfigs)
|
||||
if (!rateCode) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
...attributes,
|
||||
roomConfigurations,
|
||||
}
|
||||
}
|
||||
const rateDefinition = findRateDefintion(rateCode)
|
||||
|
||||
export const roomsAvailabilitySchema = z
|
||||
.object({
|
||||
data: z.object({
|
||||
attributes: z.object({
|
||||
checkInDate: z.string(),
|
||||
checkOutDate: z.string(),
|
||||
hotelId: z.number(),
|
||||
mustBeGuaranteed: z.boolean().optional(),
|
||||
occupancy: occupancySchema.optional(),
|
||||
rateDefinitions: z.array(rateDefinitionSchema),
|
||||
roomConfigurations: z.array(roomConfigurationSchema),
|
||||
}),
|
||||
relationships: relationshipsSchema.optional(),
|
||||
type: z.string().optional(),
|
||||
}),
|
||||
})
|
||||
.transform(transformRoomConfigs)
|
||||
if (!rateDefinition) {
|
||||
return null
|
||||
}
|
||||
|
||||
export const roomsCombinedAvailabilitySchema =
|
||||
baseRoomsCombinedAvailabilitySchema.transform(transformRoomConfigs)
|
||||
const rate = getRate(rateDefinition)
|
||||
if (!rate) {
|
||||
return null
|
||||
}
|
||||
|
||||
export const redemptionRoomsCombinedAvailabilitySchema =
|
||||
baseRoomsCombinedAvailabilitySchema.transform((data) => {
|
||||
// In Redemption, rates are always Flex terms
|
||||
data.data.attributes.roomConfigurations =
|
||||
data.data.attributes.roomConfigurations
|
||||
.map((room) => {
|
||||
room.products = room.products.map((product) => {
|
||||
product.rate = "flex"
|
||||
product.isFlex = true
|
||||
return product
|
||||
})
|
||||
return room
|
||||
})
|
||||
.sort(
|
||||
// @ts-expect-error - array indexing
|
||||
(a, b) => statusLookup[a.status] - statusLookup[b.status]
|
||||
)
|
||||
product.breakfastIncluded = rateDefinition.breakfastIncluded
|
||||
product.rate = rate
|
||||
product.rateDefinition = rateDefinition
|
||||
|
||||
return transformRoomConfigs(data)
|
||||
return product
|
||||
}
|
||||
|
||||
const roomConfigurations = attributes.roomConfigurations
|
||||
.map((room) => {
|
||||
if (room.products.length) {
|
||||
const breakfastIncluded = []
|
||||
const breakfastIncludedMember = []
|
||||
for (const product of room.products) {
|
||||
if ("corporateCheque" in product) {
|
||||
const rateDetails = getRateDetails(product)
|
||||
if (rateDetails) {
|
||||
breakfastIncluded.push(rateDetails.breakfastIncluded)
|
||||
room.code.push({
|
||||
...rateDetails,
|
||||
corporateCheque: product.corporateCheque,
|
||||
})
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if ("voucher" in product) {
|
||||
const rateDetails = getRateDetails(product)
|
||||
if (rateDetails) {
|
||||
breakfastIncluded.push(rateDetails.breakfastIncluded)
|
||||
room.code.push({
|
||||
...rateDetails,
|
||||
voucher: product.voucher,
|
||||
})
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Redemption is an array
|
||||
if (Array.isArray(product)) {
|
||||
if (product.length) {
|
||||
for (const redemption of product) {
|
||||
const rateDetails = getRateDetails(redemption)
|
||||
if (rateDetails) {
|
||||
breakfastIncluded.push(rateDetails.breakfastIncluded)
|
||||
room.redemptions.push({
|
||||
...redemption,
|
||||
...rateDetails,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if (
|
||||
("member" in product && product.member) ||
|
||||
("public" in product && product.public)
|
||||
) {
|
||||
const memberRate = product.member
|
||||
const publicRate = product.public
|
||||
const rateCode = publicRate?.rateCode ?? memberRate?.rateCode
|
||||
const rateDetails = getRateDetails(product)
|
||||
const rateDetailsMember = getRateDetails({
|
||||
...product,
|
||||
public: null,
|
||||
})
|
||||
if (rateDetailsMember) {
|
||||
breakfastIncludedMember.push(
|
||||
rateDetailsMember.breakfastIncluded
|
||||
)
|
||||
}
|
||||
if (rateDetails && rateCode) {
|
||||
const rateDefinition = findRateDefintion(rateCode)
|
||||
if (rateDefinition) {
|
||||
switch (rateDefinition.rateType) {
|
||||
case RateTypeEnum.PublicPromotion:
|
||||
room.campaign.push({
|
||||
...rateDetails,
|
||||
member: memberRate,
|
||||
public: publicRate,
|
||||
})
|
||||
break
|
||||
case RateTypeEnum.Regular:
|
||||
room.regular.push({
|
||||
...rateDetails,
|
||||
member: memberRate,
|
||||
public: publicRate,
|
||||
})
|
||||
break
|
||||
default:
|
||||
room.code.push({
|
||||
...rateDetails,
|
||||
member: memberRate,
|
||||
public: publicRate,
|
||||
})
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
room.breakfastIncludedInAllRates =
|
||||
!!breakfastIncluded.length && breakfastIncluded.every(Boolean)
|
||||
room.breakfastIncludedInAllRatesMember =
|
||||
!!breakfastIncludedMember.length &&
|
||||
breakfastIncludedMember.every(Boolean)
|
||||
|
||||
// CancellationRule is the same for public and member per product
|
||||
// Sorting to guarantee order based on rate
|
||||
room.campaign.sort(sortProductsBasedOnCancellationRule)
|
||||
room.code.sort(sortProductsBasedOnCancellationRule)
|
||||
room.redemptions.sort(sortProductsBasedOnCancellationRule)
|
||||
room.regular.sort(sortProductsBasedOnCancellationRule)
|
||||
|
||||
const hasCampaignProducts = room.campaign.length
|
||||
const hasCodeProducts = room.code.length
|
||||
const hasRedemptionProducts = room.redemptions.length
|
||||
const hasRegularProducts = room.regular.length
|
||||
if (
|
||||
!hasCampaignProducts &&
|
||||
!hasCodeProducts &&
|
||||
!hasRedemptionProducts &&
|
||||
!hasRegularProducts
|
||||
) {
|
||||
room.status = AvailabilityEnum.NotAvailable
|
||||
}
|
||||
}
|
||||
|
||||
return room
|
||||
})
|
||||
.sort(sortRoomConfigs)
|
||||
|
||||
return {
|
||||
...attributes,
|
||||
roomConfigurations,
|
||||
}
|
||||
})
|
||||
|
||||
export const ratesSchema = z.array(rateSchema)
|
||||
@@ -507,7 +553,7 @@ export const ancillaryPackagesSchema = z
|
||||
description: item.descriptions.html,
|
||||
imageUrl: item.images[0]?.imageSizes.small,
|
||||
price: {
|
||||
totalPrice: item.variants.ancillary.price.totalPrice,
|
||||
total: item.variants.ancillary.price.totalPrice,
|
||||
currency: item.variants.ancillary.price.currency,
|
||||
},
|
||||
points: item.variants.ancillaryLoyalty?.points,
|
||||
|
||||
Reference in New Issue
Block a user