Files
web/packages/trpc/lib/routers/hotels/availability/enterDetails.ts
Anton Gunnarsson de94c47f3f Merged in fix/sw-3667-not-enough-points (pull request #3337)
fix(SW-3667): Remove conditional on Scandic user token

* Remove conditional on Scandic user token


Approved-by: Joakim Jäderberg
2025-12-12 09:34:07 +00:00

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
})