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
@@ -3,11 +3,10 @@ import { Lang } from "@/constants/languages"
|
||||
import { env } from "@/env/server"
|
||||
import * as api from "@/lib/api"
|
||||
import { dt } from "@/lib/dt"
|
||||
import { badRequestError } from "@/server/errors/trpc"
|
||||
import { badRequestError, unauthorizedError } from "@/server/errors/trpc"
|
||||
import {
|
||||
contentStackBaseWithServiceProcedure,
|
||||
protectedProcedure,
|
||||
protectedServiceProcedure,
|
||||
publicProcedure,
|
||||
router,
|
||||
safeProtectedServiceProcedure,
|
||||
@@ -52,9 +51,7 @@ import {
|
||||
hotelSchema,
|
||||
packagesSchema,
|
||||
ratesSchema,
|
||||
redemptionRoomsCombinedAvailabilitySchema,
|
||||
roomsAvailabilitySchema,
|
||||
roomsCombinedAvailabilitySchema,
|
||||
} from "./output"
|
||||
import tempRatesData from "./tempRatesData.json"
|
||||
import {
|
||||
@@ -65,6 +62,7 @@ import {
|
||||
getHotelIdsByCountry,
|
||||
getHotelsByHotelIds,
|
||||
getLocations,
|
||||
getSelectedRoomAvailability,
|
||||
} from "./utils"
|
||||
|
||||
import type { BedTypeSelection } from "@/types/components/hotelReservation/enterDetails/bedType"
|
||||
@@ -75,8 +73,6 @@ import type { HotelDataWithUrl } from "@/types/hotel"
|
||||
import type {
|
||||
HotelsAvailabilityInputSchema,
|
||||
HotelsByHotelIdsAvailabilityInputSchema,
|
||||
RoomsCombinedAvailabilityInputSchema,
|
||||
SelectedRoomAvailabilitySchema,
|
||||
} from "@/types/trpc/routers/hotel/availability"
|
||||
import type { HotelInput } from "@/types/trpc/routers/hotel/hotel"
|
||||
import type { CityLocation } from "@/types/trpc/routers/hotel/locations"
|
||||
@@ -472,353 +468,6 @@ export const getHotelsAvailabilityByHotelIds = async (
|
||||
)
|
||||
}
|
||||
|
||||
async function getRoomsCombinedAvailability(
|
||||
input: RoomsCombinedAvailabilityInputSchema,
|
||||
token: string // Either service token or user access token in case of redemption search
|
||||
) {
|
||||
const { lang } = input
|
||||
const apiLang = toApiLang(lang)
|
||||
const {
|
||||
adultsCount,
|
||||
bookingCode,
|
||||
childArray,
|
||||
hotelId,
|
||||
rateCode,
|
||||
roomStayEndDate,
|
||||
roomStayStartDate,
|
||||
redemption,
|
||||
} = input
|
||||
|
||||
const metricsData = {
|
||||
hotelId,
|
||||
roomStayStartDate,
|
||||
roomStayEndDate,
|
||||
adultsCount,
|
||||
childArray: childArray ? JSON.stringify(childArray) : undefined,
|
||||
bookingCode,
|
||||
}
|
||||
|
||||
metrics.roomsCombinedAvailability.counter.add(1, metricsData)
|
||||
|
||||
console.info(
|
||||
"api.hotels.roomsCombinedAvailability start",
|
||||
JSON.stringify({ query: { hotelId, params: metricsData } })
|
||||
)
|
||||
|
||||
const availabilityResponses = await Promise.allSettled(
|
||||
adultsCount.map(async (adultCount: number, idx: number) => {
|
||||
const kids = childArray?.[idx]
|
||||
const params: Record<string, string | number | undefined> = {
|
||||
roomStayStartDate,
|
||||
roomStayEndDate,
|
||||
adults: adultCount,
|
||||
...(kids?.length && {
|
||||
children: generateChildrenString(kids),
|
||||
}),
|
||||
...(bookingCode && { bookingCode }),
|
||||
...(redemption && { isRedemption: "true" }),
|
||||
language: apiLang,
|
||||
}
|
||||
|
||||
const apiResponse = await api.get(
|
||||
api.endpoints.v1.Availability.hotel(hotelId.toString()),
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
},
|
||||
params
|
||||
)
|
||||
|
||||
if (!apiResponse.ok) {
|
||||
const text = await apiResponse.text()
|
||||
metrics.roomsCombinedAvailability.fail.add(1, metricsData)
|
||||
console.error("Failed API call", { params, text })
|
||||
return { error: "http_error", details: text }
|
||||
}
|
||||
|
||||
const apiJson = await apiResponse.json()
|
||||
const validateAvailabilityData = redemption
|
||||
? redemptionRoomsCombinedAvailabilitySchema.safeParse(apiJson)
|
||||
: roomsCombinedAvailabilitySchema.safeParse(apiJson)
|
||||
|
||||
if (!validateAvailabilityData.success) {
|
||||
console.error("Validation error", {
|
||||
params,
|
||||
error: validateAvailabilityData.error,
|
||||
})
|
||||
metrics.roomsCombinedAvailability.fail.add(1, metricsData)
|
||||
return {
|
||||
error: "validation_error",
|
||||
details: validateAvailabilityData.error,
|
||||
}
|
||||
}
|
||||
|
||||
if (rateCode) {
|
||||
validateAvailabilityData.data.mustBeGuaranteed =
|
||||
validateAvailabilityData.data.rateDefinitions.find(
|
||||
(rate) => rate.rateCode === rateCode
|
||||
)?.mustBeGuaranteed
|
||||
}
|
||||
|
||||
return validateAvailabilityData.data
|
||||
})
|
||||
)
|
||||
metrics.roomsCombinedAvailability.success.add(1, metricsData)
|
||||
return availabilityResponses.map((availability) => {
|
||||
if (availability.status === "fulfilled") {
|
||||
return availability.value
|
||||
}
|
||||
return {
|
||||
details: availability.reason,
|
||||
error: "request_failure",
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const getRoomAvailability = async (
|
||||
input: SelectedRoomAvailabilitySchema,
|
||||
lang: Lang,
|
||||
token: string, // Either service token or user access token in case of redemption search
|
||||
serviceToken?: string // In Redemption we need serviceToken for hotel api call
|
||||
) => {
|
||||
const {
|
||||
hotelId,
|
||||
roomStayStartDate,
|
||||
roomStayEndDate,
|
||||
adults,
|
||||
children,
|
||||
bookingCode,
|
||||
rateCode,
|
||||
counterRateCode,
|
||||
roomTypeCode,
|
||||
redemption,
|
||||
inputLang,
|
||||
} = input
|
||||
|
||||
const language = inputLang ?? lang
|
||||
|
||||
const params: Record<string, string | number | undefined> = {
|
||||
roomStayStartDate,
|
||||
roomStayEndDate,
|
||||
adults,
|
||||
...(children && { children }),
|
||||
...(bookingCode && { bookingCode }),
|
||||
...(redemption && { isRedemption: "true" }),
|
||||
language: toApiLang(language),
|
||||
}
|
||||
|
||||
metrics.selectedRoomAvailability.counter.add(1, {
|
||||
hotelId,
|
||||
roomStayStartDate,
|
||||
roomStayEndDate,
|
||||
adults,
|
||||
children,
|
||||
bookingCode,
|
||||
})
|
||||
console.info(
|
||||
"api.hotels.selectedRoomAvailability start",
|
||||
JSON.stringify({ query: { hotelId, params } })
|
||||
)
|
||||
const apiResponseAvailability = await api.get(
|
||||
api.endpoints.v1.Availability.hotel(hotelId.toString()),
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
},
|
||||
params
|
||||
)
|
||||
|
||||
if (!apiResponseAvailability.ok) {
|
||||
const text = await apiResponseAvailability.text()
|
||||
metrics.selectedRoomAvailability.fail.add(1, {
|
||||
hotelId,
|
||||
roomStayStartDate,
|
||||
roomStayEndDate,
|
||||
adults,
|
||||
children,
|
||||
bookingCode,
|
||||
error_type: "http_error",
|
||||
error: JSON.stringify({
|
||||
status: apiResponseAvailability.status,
|
||||
statusText: apiResponseAvailability.statusText,
|
||||
text,
|
||||
}),
|
||||
})
|
||||
console.error(
|
||||
"api.hotels.selectedRoomAvailability error",
|
||||
JSON.stringify({
|
||||
query: { hotelId, params },
|
||||
error: {
|
||||
status: apiResponseAvailability.status,
|
||||
statusText: apiResponseAvailability.statusText,
|
||||
text,
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
throw new Error("Failed to fetch selected room availability")
|
||||
}
|
||||
const apiJsonAvailability = await apiResponseAvailability.json()
|
||||
const validateAvailabilityData =
|
||||
roomsAvailabilitySchema.safeParse(apiJsonAvailability)
|
||||
if (!validateAvailabilityData.success) {
|
||||
metrics.selectedRoomAvailability.fail.add(1, {
|
||||
hotelId,
|
||||
roomStayStartDate,
|
||||
roomStayEndDate,
|
||||
adults,
|
||||
children,
|
||||
bookingCode,
|
||||
error_type: "validation_error",
|
||||
error: JSON.stringify(validateAvailabilityData.error),
|
||||
})
|
||||
console.error(
|
||||
"api.hotels.selectedRoomAvailability validation error",
|
||||
JSON.stringify({
|
||||
query: { hotelId, params },
|
||||
error: validateAvailabilityData.error,
|
||||
})
|
||||
)
|
||||
throw badRequestError()
|
||||
}
|
||||
|
||||
const hotelData = await getHotel(
|
||||
{
|
||||
hotelId,
|
||||
isCardOnlyPayment: false,
|
||||
language,
|
||||
},
|
||||
serviceToken ?? token
|
||||
)
|
||||
|
||||
const rooms = validateAvailabilityData.data.roomConfigurations
|
||||
const selectedRoom = rooms.find((room) => room.roomTypeCode === roomTypeCode)
|
||||
|
||||
if (!selectedRoom) {
|
||||
metrics.selectedRoomAvailability.fail.add(1, {
|
||||
hotelId,
|
||||
roomStayStartDate,
|
||||
roomStayEndDate,
|
||||
adults,
|
||||
children,
|
||||
bookingCode,
|
||||
roomTypeCode,
|
||||
error_type: "not_found",
|
||||
error: `Couldn't find selected room with input: ${roomTypeCode}`,
|
||||
})
|
||||
console.error("No matching room found")
|
||||
return null
|
||||
}
|
||||
|
||||
const availableRoomsInCategory = rooms.filter(
|
||||
(room) => room.roomType === selectedRoom?.roomType
|
||||
)
|
||||
|
||||
const rateTypes = selectedRoom.products.find(
|
||||
(rate) =>
|
||||
rate.public?.rateCode === rateCode ||
|
||||
rate.member?.rateCode === rateCode ||
|
||||
rate.redemptions?.find((r) => r?.rateCode === rateCode) ||
|
||||
rate.bonusCheque?.rateCode === rateCode ||
|
||||
rate.voucher?.rateCode === rateCode
|
||||
)
|
||||
|
||||
if (!rateTypes) {
|
||||
metrics.selectedRoomAvailability.fail.add(1, {
|
||||
hotelId,
|
||||
roomStayStartDate,
|
||||
roomStayEndDate,
|
||||
adults,
|
||||
children,
|
||||
bookingCode,
|
||||
error_type: "not_found",
|
||||
error: `Couldn't find rateTypes for selected room: ${JSON.stringify(selectedRoom)}`,
|
||||
})
|
||||
console.error("No matching rate found")
|
||||
return null
|
||||
}
|
||||
const rates = rateTypes
|
||||
|
||||
const rateDefinition = validateAvailabilityData.data.rateDefinitions.find(
|
||||
(rate) => rate.rateCode === rateCode
|
||||
)
|
||||
const memberRateDefinition =
|
||||
validateAvailabilityData.data.rateDefinitions.find(
|
||||
(rate) => rate.rateCode === counterRateCode
|
||||
)
|
||||
|
||||
const bedTypes = availableRoomsInCategory
|
||||
.map((availRoom) => {
|
||||
const matchingRoom = hotelData?.roomCategories
|
||||
?.find((room) =>
|
||||
room.roomTypes
|
||||
.map((roomType) => roomType.code)
|
||||
.includes(availRoom.roomTypeCode)
|
||||
)
|
||||
?.roomTypes.find((roomType) => roomType.code === availRoom.roomTypeCode)
|
||||
|
||||
if (matchingRoom) {
|
||||
return {
|
||||
description: matchingRoom.description,
|
||||
size: matchingRoom.mainBed.widthRange,
|
||||
value: matchingRoom.code,
|
||||
type: matchingRoom.mainBed.type,
|
||||
extraBed: matchingRoom.fixedExtraBed
|
||||
? {
|
||||
type: matchingRoom.fixedExtraBed.type,
|
||||
description: matchingRoom.fixedExtraBed.description,
|
||||
}
|
||||
: undefined,
|
||||
}
|
||||
}
|
||||
})
|
||||
.filter((bed): bed is BedTypeSelection => Boolean(bed))
|
||||
|
||||
metrics.selectedRoomAvailability.success.add(1, {
|
||||
hotelId,
|
||||
roomStayStartDate,
|
||||
roomStayEndDate,
|
||||
adults,
|
||||
children,
|
||||
bookingCode,
|
||||
})
|
||||
console.info(
|
||||
"api.hotels.selectedRoomAvailability success",
|
||||
JSON.stringify({
|
||||
query: { hotelId, params: params },
|
||||
})
|
||||
)
|
||||
|
||||
return {
|
||||
bedTypes,
|
||||
breakfastIncluded: !!rateDefinition?.breakfastIncluded,
|
||||
cancellationRule: rateDefinition?.cancellationRule,
|
||||
cancellationText: rateDefinition?.cancellationText ?? "",
|
||||
chequeRate: rates?.bonusCheque,
|
||||
isFlexRate:
|
||||
rateDefinition?.cancellationRule ===
|
||||
CancellationRuleEnum.CancellableBefore6PM,
|
||||
memberMustBeGuaranteed: !!memberRateDefinition?.mustBeGuaranteed,
|
||||
memberRate: rates?.member,
|
||||
mustBeGuaranteed: !!rateDefinition?.mustBeGuaranteed,
|
||||
publicRate: rates?.public,
|
||||
redemptionRate: rates?.redemptions?.find((r) => r?.rateCode === rateCode),
|
||||
rate: selectedRoom.products[0].rate,
|
||||
rateDefinitionTitle: rateDefinition?.title ?? "",
|
||||
rateDetails: rateDefinition?.generalTerms,
|
||||
// Send rate Title when it is a booking code rate
|
||||
rateTitle:
|
||||
rateDefinition?.rateType !== RateTypeEnum.Regular
|
||||
? rateDefinition?.title
|
||||
: undefined,
|
||||
rateType: rateDefinition?.rateType ?? "",
|
||||
selectedRoom,
|
||||
voucherRate: rates?.voucher,
|
||||
}
|
||||
}
|
||||
|
||||
export const hotelQueryRouter = router({
|
||||
availability: router({
|
||||
hotelsByCity: serviceProcedure
|
||||
@@ -847,33 +496,360 @@ export const hotelQueryRouter = router({
|
||||
return getHotelsAvailabilityByHotelIds(input, apiLang, ctx.serviceToken)
|
||||
}),
|
||||
|
||||
roomsCombinedAvailability: serviceProcedure
|
||||
roomsCombinedAvailability: safeProtectedServiceProcedure
|
||||
.input(roomsCombinedAvailabilityInputSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
return getRoomsCombinedAvailability(input, ctx.serviceToken)
|
||||
}),
|
||||
roomsCombinedAvailabilityWithRedemption: protectedProcedure
|
||||
.input(roomsCombinedAvailabilityInputSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
return getRoomsCombinedAvailability(
|
||||
.use(async ({ ctx, input, next }) => {
|
||||
if (input.redemption) {
|
||||
if (ctx.session?.token.access_token) {
|
||||
return next({
|
||||
ctx: {
|
||||
token: ctx.session.token.access_token,
|
||||
},
|
||||
input,
|
||||
})
|
||||
}
|
||||
throw unauthorizedError()
|
||||
}
|
||||
return next({
|
||||
ctx: {
|
||||
token: ctx.serviceToken,
|
||||
},
|
||||
input,
|
||||
ctx.session.token.access_token
|
||||
})
|
||||
})
|
||||
.query(
|
||||
async ({
|
||||
ctx,
|
||||
input: {
|
||||
adultsCount,
|
||||
bookingCode,
|
||||
childArray,
|
||||
hotelId,
|
||||
lang,
|
||||
rateCode,
|
||||
redemption,
|
||||
roomStayEndDate,
|
||||
roomStayStartDate,
|
||||
},
|
||||
}) => {
|
||||
const apiLang = toApiLang(lang)
|
||||
|
||||
const metricsData = {
|
||||
hotelId,
|
||||
roomStayStartDate,
|
||||
roomStayEndDate,
|
||||
adultsCount,
|
||||
childArray: childArray ? JSON.stringify(childArray) : undefined,
|
||||
bookingCode,
|
||||
}
|
||||
|
||||
metrics.roomsCombinedAvailability.counter.add(1, metricsData)
|
||||
|
||||
console.info(
|
||||
"api.hotels.roomsCombinedAvailability start",
|
||||
JSON.stringify({ query: { hotelId, params: metricsData } })
|
||||
)
|
||||
|
||||
const availabilityResponses = await Promise.allSettled(
|
||||
adultsCount.map(async (adultCount: number, idx: number) => {
|
||||
const kids = childArray?.[idx]
|
||||
const params: Record<string, string | number | undefined> = {
|
||||
roomStayStartDate,
|
||||
roomStayEndDate,
|
||||
adults: adultCount,
|
||||
...(kids?.length && {
|
||||
children: generateChildrenString(kids),
|
||||
}),
|
||||
...(bookingCode && { bookingCode }),
|
||||
language: apiLang,
|
||||
...(redemption ? { isRedemption: "true" } : {}),
|
||||
}
|
||||
|
||||
const apiResponse = await api.get(
|
||||
api.endpoints.v1.Availability.hotel(hotelId.toString()),
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${ctx.token}`,
|
||||
},
|
||||
},
|
||||
params
|
||||
)
|
||||
|
||||
if (!apiResponse.ok) {
|
||||
const text = await apiResponse.text()
|
||||
metrics.roomsCombinedAvailability.fail.add(1, metricsData)
|
||||
console.error("Failed API call", { params, text })
|
||||
return { error: "http_error", details: text }
|
||||
}
|
||||
|
||||
const apiJson = await apiResponse.json()
|
||||
const validateAvailabilityData =
|
||||
roomsAvailabilitySchema.safeParse(apiJson)
|
||||
if (!validateAvailabilityData.success) {
|
||||
console.error("Validation error", {
|
||||
params,
|
||||
error: validateAvailabilityData.error,
|
||||
})
|
||||
metrics.roomsCombinedAvailability.fail.add(1, metricsData)
|
||||
return {
|
||||
error: "validation_error",
|
||||
details: validateAvailabilityData.error,
|
||||
}
|
||||
}
|
||||
|
||||
if (rateCode) {
|
||||
validateAvailabilityData.data.mustBeGuaranteed =
|
||||
validateAvailabilityData.data.rateDefinitions.find(
|
||||
(rate) => rate.rateCode === rateCode
|
||||
)?.mustBeGuaranteed
|
||||
}
|
||||
|
||||
return validateAvailabilityData.data
|
||||
})
|
||||
)
|
||||
metrics.roomsCombinedAvailability.success.add(1, metricsData)
|
||||
|
||||
const data = availabilityResponses.map((availability) => {
|
||||
if (availability.status === "fulfilled") {
|
||||
return availability.value
|
||||
}
|
||||
return {
|
||||
details: availability.reason,
|
||||
error: "request_failure",
|
||||
}
|
||||
})
|
||||
|
||||
return data
|
||||
}
|
||||
),
|
||||
room: safeProtectedServiceProcedure
|
||||
.input(selectedRoomAvailabilityInputSchema)
|
||||
.use(async ({ ctx, input, next }) => {
|
||||
if (input.redemption) {
|
||||
if (ctx.session?.token.access_token) {
|
||||
return next({
|
||||
ctx: {
|
||||
token: ctx.session.token.access_token,
|
||||
},
|
||||
input,
|
||||
})
|
||||
}
|
||||
throw unauthorizedError()
|
||||
}
|
||||
return next({
|
||||
ctx: {
|
||||
token: ctx.serviceToken,
|
||||
},
|
||||
input,
|
||||
})
|
||||
})
|
||||
.query(async ({ input, ctx }) => {
|
||||
let selectedRoomData = await getSelectedRoomAvailability(
|
||||
input,
|
||||
toApiLang(ctx.lang),
|
||||
ctx.token
|
||||
)
|
||||
}),
|
||||
room: serviceProcedure
|
||||
.input(selectedRoomAvailabilityInputSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
return getRoomAvailability(input, ctx.lang, ctx.serviceToken)
|
||||
}),
|
||||
roomWithRedemption: protectedServiceProcedure
|
||||
.input(selectedRoomAvailabilityInputSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
return getRoomAvailability(
|
||||
input,
|
||||
ctx.lang,
|
||||
ctx.session.token.access_token,
|
||||
|
||||
const {
|
||||
adults,
|
||||
bookingCode,
|
||||
children,
|
||||
counterRateCode,
|
||||
hotelId,
|
||||
inputLang,
|
||||
roomStayEndDate,
|
||||
roomStayStartDate,
|
||||
roomTypeCode,
|
||||
} = input
|
||||
|
||||
if (!selectedRoomData) {
|
||||
// There is no way to differentiate if a rateCode
|
||||
// selected is a bookingCode rateCode or just a
|
||||
// regular rateCode, hence we need to make a second
|
||||
// request without the bookingCode if no availability
|
||||
// is found
|
||||
if (bookingCode) {
|
||||
metrics.selectedRoomAvailability.fail.add(1, {
|
||||
hotelId,
|
||||
roomStayStartDate,
|
||||
roomStayEndDate,
|
||||
adults,
|
||||
children,
|
||||
bookingCode,
|
||||
roomTypeCode,
|
||||
error_type: "not_found",
|
||||
error: `Couldn't find selected room with input: ${roomTypeCode}`,
|
||||
})
|
||||
console.error(
|
||||
"No matching room found when making the request with bookingCode, attempting without"
|
||||
)
|
||||
|
||||
metrics.selectedRoomAvailability.counter.add(1, {
|
||||
adults,
|
||||
children,
|
||||
hotelId,
|
||||
roomStayEndDate,
|
||||
roomStayStartDate,
|
||||
})
|
||||
|
||||
const { bookingCode: extractedBookingCode, ...regularRatesInput } =
|
||||
input
|
||||
selectedRoomData = await getSelectedRoomAvailability(
|
||||
regularRatesInput,
|
||||
toApiLang(ctx.lang),
|
||||
ctx.token
|
||||
)
|
||||
|
||||
if (!selectedRoomData) {
|
||||
metrics.selectedRoomAvailability.fail.add(1, {
|
||||
adults,
|
||||
children,
|
||||
hotelId,
|
||||
roomStayEndDate,
|
||||
roomStayStartDate,
|
||||
roomTypeCode,
|
||||
error_type: "not_found",
|
||||
error: `Couldn't find selected room with input: ${roomTypeCode}`,
|
||||
})
|
||||
console.error("No matching room found even without bookingCode")
|
||||
return null
|
||||
}
|
||||
} else {
|
||||
metrics.selectedRoomAvailability.fail.add(1, {
|
||||
hotelId,
|
||||
roomStayStartDate,
|
||||
roomStayEndDate,
|
||||
adults,
|
||||
children,
|
||||
bookingCode,
|
||||
roomTypeCode,
|
||||
error_type: "not_found",
|
||||
error: `Couldn't find selected room with input: ${roomTypeCode}`,
|
||||
})
|
||||
console.error("No matching room found")
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
const hotelData = await getHotel(
|
||||
{
|
||||
hotelId,
|
||||
isCardOnlyPayment: false,
|
||||
language: inputLang ?? ctx.lang,
|
||||
},
|
||||
ctx.serviceToken
|
||||
)
|
||||
|
||||
const {
|
||||
product,
|
||||
rateDefinition,
|
||||
rateDefinitions,
|
||||
rooms,
|
||||
selectedRoom,
|
||||
} = selectedRoomData
|
||||
|
||||
const availableRoomsInCategory = rooms.filter(
|
||||
(room) => room.roomType === selectedRoom?.roomType
|
||||
)
|
||||
|
||||
if (!product) {
|
||||
metrics.selectedRoomAvailability.fail.add(1, {
|
||||
hotelId,
|
||||
roomStayStartDate,
|
||||
roomStayEndDate,
|
||||
adults,
|
||||
children,
|
||||
bookingCode,
|
||||
error_type: "not_found",
|
||||
error: `Couldn't find rateTypes for selected room: ${JSON.stringify(selectedRoom)}`,
|
||||
})
|
||||
console.error("No matching rate found")
|
||||
return null
|
||||
}
|
||||
|
||||
let memberRateDefinition = undefined
|
||||
if ("member" in product) {
|
||||
memberRateDefinition = rateDefinitions.find(
|
||||
(rate) => rate.rateCode === counterRateCode
|
||||
)
|
||||
}
|
||||
|
||||
const bedTypes = availableRoomsInCategory
|
||||
.map((availRoom) => {
|
||||
const matchingRoom = hotelData?.roomCategories
|
||||
?.find((room) =>
|
||||
room.roomTypes
|
||||
.map((roomType) => roomType.code)
|
||||
.includes(availRoom.roomTypeCode)
|
||||
)
|
||||
?.roomTypes.find(
|
||||
(roomType) => roomType.code === availRoom.roomTypeCode
|
||||
)
|
||||
|
||||
if (matchingRoom) {
|
||||
return {
|
||||
description: matchingRoom.description,
|
||||
size: matchingRoom.mainBed.widthRange,
|
||||
value: matchingRoom.code,
|
||||
type: matchingRoom.mainBed.type,
|
||||
extraBed: matchingRoom.fixedExtraBed
|
||||
? {
|
||||
type: matchingRoom.fixedExtraBed.type,
|
||||
description: matchingRoom.fixedExtraBed.description,
|
||||
}
|
||||
: undefined,
|
||||
}
|
||||
}
|
||||
})
|
||||
.filter((bed): bed is BedTypeSelection => Boolean(bed))
|
||||
|
||||
metrics.selectedRoomAvailability.success.add(1, {
|
||||
hotelId,
|
||||
roomStayStartDate,
|
||||
roomStayEndDate,
|
||||
adults,
|
||||
children,
|
||||
bookingCode,
|
||||
})
|
||||
console.info(
|
||||
"api.hotels.selectedRoomAvailability success",
|
||||
JSON.stringify({
|
||||
query: {
|
||||
hotelId,
|
||||
params: {
|
||||
roomStayStartDate,
|
||||
roomStayEndDate,
|
||||
adults,
|
||||
...(children && { children }),
|
||||
...(bookingCode && { bookingCode }),
|
||||
language: inputLang ?? ctx.lang,
|
||||
},
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
return {
|
||||
bedTypes,
|
||||
breakfastIncluded: !!rateDefinition?.breakfastIncluded,
|
||||
cancellationRule: rateDefinition?.cancellationRule,
|
||||
cancellationText: rateDefinition?.cancellationText ?? "",
|
||||
isFlexRate:
|
||||
rateDefinition?.cancellationRule ===
|
||||
CancellationRuleEnum.CancellableBefore6PM,
|
||||
memberMustBeGuaranteed: !!memberRateDefinition?.mustBeGuaranteed,
|
||||
mustBeGuaranteed: !!rateDefinition?.mustBeGuaranteed,
|
||||
product,
|
||||
rate: product.rate,
|
||||
rateDefinitionTitle: rateDefinition?.title ?? "",
|
||||
rateDetails: rateDefinition?.generalTerms,
|
||||
// Send rate Title when it is a booking code rate
|
||||
rateTitle:
|
||||
rateDefinition?.rateType !== RateTypeEnum.Regular
|
||||
? rateDefinition?.title
|
||||
: undefined,
|
||||
rateType: rateDefinition?.rateType ?? "",
|
||||
selectedRoom,
|
||||
}
|
||||
}),
|
||||
hotelsByCityWithBookingCode: serviceProcedure
|
||||
.input(hotelsAvailabilityInputSchema)
|
||||
@@ -896,23 +872,15 @@ export const hotelQueryRouter = router({
|
||||
return null
|
||||
}
|
||||
|
||||
// Do not search for regular rates if voucher or corporate cheque codes
|
||||
const isVoucherOrChqRate =
|
||||
bookingCodeAvailabilityResponse?.availability.some(
|
||||
(hotel) =>
|
||||
hotel.productType?.bonusCheque || hotel.productType?.voucher
|
||||
)
|
||||
|
||||
// Get regular availability of hotels which don't have availability with booking code.
|
||||
const unavailableHotelIds = !isVoucherOrChqRate
|
||||
? bookingCodeAvailabilityResponse?.availability
|
||||
.filter((hotel) => {
|
||||
return hotel.status === "NotAvailable"
|
||||
})
|
||||
.flatMap((hotel) => {
|
||||
return hotel.hotelId
|
||||
})
|
||||
: null
|
||||
const unavailableHotelIds =
|
||||
bookingCodeAvailabilityResponse?.availability
|
||||
.filter((hotel) => {
|
||||
return hotel.status === "NotAvailable"
|
||||
})
|
||||
.flatMap((hotel) => {
|
||||
return hotel.hotelId
|
||||
})
|
||||
|
||||
// All hotels have availability with booking code no need to fetch regular prices.
|
||||
// return response as is without any filtering as below.
|
||||
|
||||
Reference in New Issue
Block a user