Merged in fix/allow-single-rateCode (pull request #1438)

fix: allow rates that only have either of member or public to be selectable

* fix: allow rates that only have either of member or public to be selectable


Approved-by: Michael Zetterberg
This commit is contained in:
Simon.Emanuelsson
2025-03-03 08:28:55 +00:00
committed by Linus Flood
parent 3f01266a75
commit c3e3fa62ec
30 changed files with 487 additions and 573 deletions

View File

@@ -105,7 +105,7 @@ function everyRateHasBreakfastIncluded(
userType: "member" | "public"
) {
const rateDefinition = rateDefinitions.find(
(rd) => rd.rateCode === product.productType[userType]?.rateCode
(rd) => rd.rateCode === product[userType]?.rateCode
)
if (!rateDefinition) {
return false
@@ -113,10 +113,7 @@ function everyRateHasBreakfastIncluded(
return rateDefinition.breakfastIncluded
}
function getRate(rate: RateDefinition | undefined) {
if (!rate) {
return null
}
function getRate(rate: RateDefinition) {
switch (rate.cancellationRule) {
case "CancellableBefore6PM":
return "flex"
@@ -156,77 +153,79 @@ export const roomsAvailabilitySchema = z
type: z.string().optional(),
}),
})
.transform((o) => {
const cancellationRuleLookup = o.data.attributes.rateDefinitions.reduce(
(acc, val) => {
// @ts-expect-error - index of cancellationRule TS
acc[val.rateCode] = cancellationRules[val.cancellationRule]
return acc
},
{}
)
.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
}, {})
o.data.attributes.roomConfigurations =
o.data.attributes.roomConfigurations.map((room) => {
attributes.roomConfigurations = attributes.roomConfigurations.map(
(room) => {
if (room.products.length) {
room.breakfastIncludedInAllRatesMember = room.products.every(
(product) =>
everyRateHasBreakfastIncluded(
product,
o.data.attributes.rateDefinitions,
"member"
)
everyRateHasBreakfastIncluded(product, rateDefinitions, "member")
)
room.breakfastIncludedInAllRatesPublic = room.products.every(
(product) =>
everyRateHasBreakfastIncluded(
product,
o.data.attributes.rateDefinitions,
"public"
)
everyRateHasBreakfastIncluded(product, rateDefinitions, "public")
)
room.products = room.products.map((product) => {
const publicRateDefinition = o.data.attributes.rateDefinitions.find(
(rate) =>
product.productType.public.rateCode
? rate.rateCode === product.productType.public.rateCode
: rate.rateCode === product.productType.public.oldRateCode
)
const publicRate = getRate(publicRateDefinition)
const memberRateDefinition = o.data.attributes.rateDefinitions.find(
(rate) =>
product.productType.member?.rateCode
? rate.rateCode === product.productType.member?.rateCode
: rate.rateCode === product.productType.member?.oldRateCode
)
const memberRate = getRate(memberRateDefinition)
if (publicRate) {
product.productType.public.rate = publicRate
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
}
}
}
}
if (memberRate && product.productType.member) {
product.productType.member.rate = memberRate
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
}
}
}
}
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]
)
}
// 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.productType.public.rateCode] -
// @ts-expect-error - index
cancellationRuleLookup[b.productType.public.rateCode]
)
return room
})
}
)
return o.data.attributes
return attributes
})
export const ratesSchema = z.array(rateSchema)

View File

@@ -535,7 +535,6 @@ export const hotelQueryRouter = router({
}
const apiJson = await apiResponse.json()
const validateAvailabilityData =
roomsAvailabilitySchema.safeParse(apiJson)
@@ -710,8 +709,8 @@ export const hotelQueryRouter = router({
const rateTypes = selectedRoom.products.find(
(rate) =>
rate.productType.public?.rateCode === rateCode ||
rate.productType.member?.rateCode === rateCode
rate.public?.rateCode === rateCode ||
rate.member?.rateCode === rateCode
)
if (!rateTypes) {
@@ -728,7 +727,7 @@ export const hotelQueryRouter = router({
console.error("No matching rate found")
return null
}
const rates = rateTypes.productType
const rates = rateTypes
const rateDefinition =
validateAvailabilityData.data.rateDefinitions.find(
@@ -786,7 +785,7 @@ export const hotelQueryRouter = router({
mustBeGuaranteed: !!rateDefinition?.mustBeGuaranteed,
breakfastIncluded: !!rateDefinition?.breakfastIncluded,
memberRate: rates?.member,
publicRate: rates.public,
publicRate: rates?.public,
bedTypes,
}
}),

View File

@@ -15,10 +15,4 @@ export const productTypePriceSchema = z.object({
rateCode: z.string(),
rateType: z.string().optional(),
requestedPrice: priceSchema.optional(),
// This is only used when a product is filtered out
// so that we can still map out the correct titles a.so.
oldRateCode: z.string().default(""),
// Used to set the rate that we use to chose
// titles etc.
rate: z.string().default(""),
})

View File

@@ -1,11 +1,9 @@
import deepmerge from "deepmerge"
import { z } from "zod"
import { productSchema } from "./product"
import { AvailabilityEnum } from "@/types/components/hotelReservation/selectHotel/selectHotel"
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
import { RateTypeEnum } from "@/types/enums/rateType"
export const roomConfigurationSchema = z
.object({
@@ -31,60 +29,15 @@ export const roomConfigurationSchema = z
})
.transform((data) => {
if (data.products.length) {
const someProductsMissAtLeastOneRateCode = data.products.some(
({ productType }) =>
!productType.public.rateCode || !productType.member?.rateCode
)
if (someProductsMissAtLeastOneRateCode) {
data.products = data.products.map((product) => {
if (
product.productType.public.rateCode &&
product.productType.member?.rateCode
) {
return product
}
// Return rate with only public available when it is a booking code rate
// which can be any one of other rate types
if (product.productType.public.rateType !== RateTypeEnum.Regular) {
return product
}
/**
* Reset both rateCodes if one is missing to show `No prices available` for the same reason as
* mentioned above.
*
* TODO: (Maybe) notify somewhere that this happened
*/
return deepmerge(product, {
productType: {
member: {
rateCode: "",
oldRateCode: product.productType.member?.rateCode,
},
public: {
rateCode: "",
oldRateCode: product.productType.public.rateCode,
},
},
})
})
}
/**
* When all products miss at least one rateCode (member or public), we change the status to NotAvailable
* since we cannot as of now (31 january) guarantee the flow with missing rateCodes.
* This rule applies to regular rates (Save, Change and Flex)
* Exception Booking code rate
*
* TODO: (Maybe) notify somewhere that this happened
* Just guaranteeing that if all products all miss
* both public and member rateCode that status is
* set to `NotAvailable`
*/
const allProductsMissAtLeastOneRateCode = data.products.every(
({ productType }) =>
(!productType.public.rateCode || !productType.member?.rateCode) &&
productType.public.rateType === RateTypeEnum.Regular
const allProductsMissBothRateCodes = data.products.every(
(product) => !product.public?.rateCode && !product.member?.rateCode
)
if (allProductsMissAtLeastOneRateCode) {
if (allProductsMissBothRateCodes) {
data.status = AvailabilityEnum.NotAvailable
}
}

View File

@@ -2,20 +2,19 @@ import { z } from "zod"
import { productTypePriceSchema } from "../productTypePrice"
import { CurrencyEnum } from "@/types/enums/currency"
export const productSchema = z.object({
productType: z.object({
member: productTypePriceSchema.optional(),
public: productTypePriceSchema.default({
localPrice: {
currency: CurrencyEnum.SEK,
pricePerNight: 0,
pricePerStay: 0,
},
rateCode: "",
rateType: "",
requestedPrice: undefined,
export const productSchema = z
.object({
// Is product flex rate
isFlex: z.boolean().default(false),
productType: z.object({
member: productTypePriceSchema.optional(),
public: productTypePriceSchema.optional(),
}),
}),
})
// Used to set the rate that we use to chose titles etc.
rate: z.enum(["change", "flex", "save"]).default("save"),
})
.transform((data) => ({
...data.productType,
isFlex: data.isFlex,
rate: data.rate,
}))