Files
web/packages/trpc/lib/routers/hotels/availability/enterDetails.ts
Joakim Jäderberg 8498026189 Merged in chore/refactor-hotel-trpc-routes (pull request #2891)
Chore/refactor hotel trpc routes

* chore(SW-3519): refactor trpc hotel routers

* chore(SW-3519): refactor trpc hotel routers

* refactor

* merge

* Merge branch 'master' of bitbucket.org:scandic-swap/web into chore/refactor-hotel-trpc-routes


Approved-by: Linus Flood
2025-10-01 12:55:45 +00:00

179 lines
6.1 KiB
TypeScript

import { z } from "zod"
import { Lang } from "@scandic-hotels/common/constants/language"
import { RateEnum } from "@scandic-hotels/common/constants/rate"
import { RateTypeEnum } from "@scandic-hotels/common/constants/rateType"
import { createLogger } from "@scandic-hotels/common/logger/createLogger"
import { SEARCH_TYPE_REDEMPTION } from "../../../constants/booking"
import { AvailabilityEnum } from "../../../enums/selectHotel"
import { unauthorizedError } from "../../../errors"
import { safeProtectedServiceProcedure } from "../../../procedures"
import { getVerifiedUser } from "../../user/utils/getVerifiedUser"
import { baseBookingSchema, baseRoomSchema, selectedRoomSchema } from "../input"
import { getHotel } from "../services/getHotel"
import { getRoomsAvailability } from "../services/getRoomsAvailability"
import {
getBedTypes,
getSelectedRoomAvailability,
selectRateRedirectURL,
} from "../utils"
import type { Room } from "../../../types/room"
export type RoomsAvailabilityExtendedInputSchema = z.input<
typeof enterDetailsRoomsAvailabilityInputSchema
>
export const enterDetailsRoomsAvailabilityInputSchema = z.object({
booking: baseBookingSchema.extend({
rooms: z.array(baseRoomSchema.merge(selectedRoomSchema)),
}),
lang: z.nativeEnum(Lang),
})
const logger = createLogger("trpc:availability:enterDetails")
export const enterDetails = safeProtectedServiceProcedure
.input(enterDetailsRoomsAvailabilityInputSchema)
.use(async ({ ctx, input, next }) => {
if (input.booking.searchType === SEARCH_TYPE_REDEMPTION) {
if (ctx.session?.token.access_token) {
const verifiedUser = await getVerifiedUser({ session: ctx.session })
if (!verifiedUser?.error) {
return next({
ctx: {
token: ctx.session.token.access_token,
userPoints: verifiedUser?.data.membership?.currentPoints ?? 0,
},
})
}
}
throw unauthorizedError()
}
return next({
ctx: {
token: ctx.serviceToken,
},
})
})
.query(async function ({ ctx, input }) {
const availability = await getRoomsAvailability(
input,
ctx.token,
ctx.serviceToken,
ctx.userPoints
)
const hotelData = await getHotel(
{
hotelId: input.booking.hotelId,
isCardOnlyPayment: false,
language: input.lang || ctx.lang,
},
ctx.serviceToken
)
const selectedRooms = []
for (const [idx, room] of availability.entries()) {
if (!room || "error" in room) {
logger.error(`Availability failed: ${room.error}`, room.details)
selectedRooms.push(null)
continue
}
const bookingRoom = input.booking.rooms[idx]
const selected = getSelectedRoomAvailability(
bookingRoom.rateCode,
room.rateDefinitions,
room.roomConfigurations,
bookingRoom.roomTypeCode,
ctx.userPoints
)
if (!selected) {
logger.error("Unable to find selected room")
selectedRooms.push(null)
continue
}
const { rateDefinition, rateDefinitions, product, rooms, selectedRoom } =
selected
const bedTypes = getBedTypes(
rooms,
selectedRoom.roomType,
hotelData?.roomCategories
)
const counterRateCode = input.booking.rooms[idx].counterRateCode
const rateCode = input.booking.rooms[idx].rateCode
let memberRateDefinition = undefined
if ("member" in product && product.member && counterRateCode) {
memberRateDefinition = rateDefinitions.find(
(rate) =>
(rate.rateCode.toLowerCase() === counterRateCode.toLowerCase() ||
rate.rateCode.toLowerCase() === rateCode.toLowerCase()) &&
rate.isMemberRate
)
}
const selectedPackages = input.booking.rooms[idx].packages
selectedRooms.push({
bedTypes,
breakfastIncluded: rateDefinition.breakfastIncluded,
cancellationText: rateDefinition.cancellationText,
cancellationRule: rateDefinition.cancellationRule,
isAvailable: selectedRoom.status === AvailabilityEnum.Available,
isFlexRate: product.rate === RateEnum.flex,
memberMustBeGuaranteed: memberRateDefinition?.mustBeGuaranteed,
mustBeGuaranteed: rateDefinition.mustBeGuaranteed,
packages: room.packages.filter((pkg) =>
selectedPackages?.includes(pkg.code)
),
rate: product.rate,
rateDefinitionTitle: rateDefinition.title,
rateDetails: rateDefinition.generalTerms,
memberRateDetails: memberRateDefinition?.generalTerms,
// Send rate Title when it is a booking code rate
rateTitle:
rateDefinition.rateType !== RateTypeEnum.Regular
? rateDefinition.title
: undefined,
rateType: rateDefinition.rateType,
roomRate: product,
roomType: selectedRoom.roomType,
roomTypeCode: selectedRoom.roomTypeCode,
})
}
const totalBedsAvailableForRoomTypeCode: Record<string, number> = {}
for (const selectedRoom of selectedRooms) {
if (selectedRoom) {
if (!totalBedsAvailableForRoomTypeCode[selectedRoom.roomTypeCode]) {
totalBedsAvailableForRoomTypeCode[selectedRoom.roomTypeCode] =
selectedRoom.bedTypes.reduce(
(total, bedType) => total + bedType.roomsLeft,
0
)
}
}
}
for (const [idx, selectedRoom] of selectedRooms.entries()) {
if (selectedRoom) {
const totalBedsLeft =
totalBedsAvailableForRoomTypeCode[selectedRoom.roomTypeCode]
if (totalBedsLeft <= 0) {
selectedRooms[idx] = null
continue
}
totalBedsAvailableForRoomTypeCode[selectedRoom.roomTypeCode] =
totalBedsAvailableForRoomTypeCode[selectedRoom.roomTypeCode] - 1
}
}
if (selectedRooms.some((sr) => !sr)) {
console.log("DEBUG: REDIRECTING TO SELECT RATE", selectedRooms)
return selectRateRedirectURL(input, selectedRooms.map(Boolean))
}
const rooms: Room[] = selectedRooms.filter((sr) => !!sr)
return rooms
})