fix(SW-3667): Remove conditional on Scandic user token * Remove conditional on Scandic user token Approved-by: Joakim Jäderberg
187 lines
6.1 KiB
TypeScript
187 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 { isValidSession } from "../../../utils/session"
|
|
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),
|
|
})
|
|
|
|
type Context = {
|
|
userToken: string | null
|
|
userPoints?: number
|
|
}
|
|
|
|
const logger = createLogger("trpc:availability:enterDetails")
|
|
export const enterDetails = safeProtectedServiceProcedure
|
|
.input(enterDetailsRoomsAvailabilityInputSchema)
|
|
.use(async ({ ctx, input, next }) => {
|
|
const userToken = await ctx.getScandicUserToken()
|
|
if (input.booking.searchType === SEARCH_TYPE_REDEMPTION) {
|
|
if (isValidSession(ctx.session)) {
|
|
const pointsValue = await ctx.getUserPointsBalance()
|
|
if (pointsValue) {
|
|
return next<Context>({
|
|
ctx: {
|
|
userToken,
|
|
userPoints: pointsValue,
|
|
},
|
|
input,
|
|
})
|
|
}
|
|
}
|
|
throw unauthorizedError()
|
|
}
|
|
return next<Context>({
|
|
ctx: {
|
|
userToken,
|
|
},
|
|
})
|
|
})
|
|
.query(async function ({ ctx, input }) {
|
|
const availability = await getRoomsAvailability(
|
|
input,
|
|
ctx.userToken,
|
|
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)) {
|
|
logger.debug("DEBUG: REDIRECTING TO SELECT RATE", selectedRooms)
|
|
return selectRateRedirectURL(input, selectedRooms.map(Boolean))
|
|
}
|
|
|
|
const rooms: Room[] = selectedRooms.filter((sr) => !!sr)
|
|
return rooms
|
|
})
|