SW-3572 API route for listing hotels per city or country * wip hotel data endpoint * Correct route params type * wip * skip static paths call * timeout when getting destinations take too long * call noStore when we get a timeout * add cache-control headers * . * . * . * wip * wip * wip * wip * add route for getting hotels per country * include city when listing by country * fix distance SI unit * fix sorting * Merge branch 'master' of bitbucket.org:scandic-swap/web into feature/SW-3572-hotel-data-endpoint * packages/tracking passWithNoTests * revalidate must be static value * remove oxc reference * cleanup * cleanup hotel api route * feat(SW-3572): cleanup error handling Approved-by: Anton Gunnarsson
225 lines
6.7 KiB
TypeScript
225 lines
6.7 KiB
TypeScript
import { selectRate } from "@scandic-hotels/common/constants/routes/hotelReservation"
|
|
|
|
import { BookingErrorCodeEnum } from "../../enums/bookingErrorCode"
|
|
import { AvailabilityEnum } from "../../enums/selectHotel"
|
|
import { sortRoomConfigs } from "../../utils/sortRoomConfigs"
|
|
|
|
import type { BedTypeSelection } from "../../types/bedTypeSelection"
|
|
import type { Room as RoomCategory } from "../../types/hotel"
|
|
import type {
|
|
Product,
|
|
Products,
|
|
RateDefinition,
|
|
RedemptionsProduct,
|
|
RoomConfiguration,
|
|
} from "../../types/roomAvailability"
|
|
import type { RoomsAvailabilityExtendedInputSchema } from "./availability/enterDetails"
|
|
export const locationsAffix = "locations"
|
|
|
|
export const TWENTYFOUR_HOURS = 60 * 60 * 24
|
|
|
|
function findProduct(product: Products, rateDefinition: RateDefinition) {
|
|
if ("corporateCheque" in product) {
|
|
return product.corporateCheque.rateCode === rateDefinition.rateCode
|
|
}
|
|
|
|
if (("member" in product && product.member) || "public" in product) {
|
|
let isMemberRate = false
|
|
if (product.member) {
|
|
isMemberRate = product.member.rateCode === rateDefinition.rateCode
|
|
}
|
|
let isPublicRate = false
|
|
if (product.public) {
|
|
isPublicRate = product.public.rateCode === rateDefinition.rateCode
|
|
}
|
|
return isMemberRate || isPublicRate
|
|
}
|
|
|
|
if ("voucher" in product) {
|
|
return product.voucher.rateCode === rateDefinition.rateCode
|
|
}
|
|
|
|
if (Array.isArray(product)) {
|
|
return product.find(
|
|
(r) => r.redemption.rateCode === rateDefinition.rateCode
|
|
)
|
|
}
|
|
}
|
|
|
|
export function getSelectedRoomAvailability(
|
|
rateCode: string,
|
|
rateDefinitions: RateDefinition[],
|
|
roomConfigurations: RoomConfiguration[],
|
|
roomTypeCode: string,
|
|
userPoints: number | undefined
|
|
) {
|
|
const rateDefinition = rateDefinitions.find((rd) => rd.rateCode === rateCode)
|
|
if (!rateDefinition) {
|
|
return null
|
|
}
|
|
|
|
const selectedRoom = roomConfigurations.find(
|
|
(room) =>
|
|
room.roomTypeCode === roomTypeCode &&
|
|
room.products.find((product) => findProduct(product, rateDefinition))
|
|
)
|
|
|
|
if (!selectedRoom) {
|
|
return null
|
|
}
|
|
|
|
let product: Product | RedemptionsProduct | undefined =
|
|
selectedRoom.products.find((product) =>
|
|
findProduct(product, rateDefinition)
|
|
)
|
|
|
|
if (!product) {
|
|
return null
|
|
}
|
|
|
|
if (Array.isArray(product)) {
|
|
const redemptionProduct = userPoints
|
|
? product.find(
|
|
(r) =>
|
|
r.redemption.rateCode === rateDefinition.rateCode &&
|
|
r.redemption.localPrice.pointsPerStay <= userPoints
|
|
)
|
|
: undefined
|
|
if (!redemptionProduct) {
|
|
return null
|
|
}
|
|
product = redemptionProduct
|
|
}
|
|
|
|
return {
|
|
rateDefinition,
|
|
rateDefinitions,
|
|
rooms: roomConfigurations,
|
|
product,
|
|
selectedRoom,
|
|
}
|
|
}
|
|
|
|
export function getBedTypes(
|
|
rooms: RoomConfiguration[],
|
|
roomType: string,
|
|
roomCategories?: RoomCategory[]
|
|
) {
|
|
if (!roomCategories) {
|
|
return []
|
|
}
|
|
|
|
return rooms
|
|
.filter(
|
|
(room) => room.status === AvailabilityEnum.Available || room.roomsLeft > 0
|
|
)
|
|
.filter((room) => room.roomType === roomType)
|
|
.map((availRoom) => {
|
|
const matchingRoom = 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,
|
|
roomsLeft: availRoom.roomsLeft,
|
|
extraBed: matchingRoom.fixedExtraBed
|
|
? {
|
|
type: matchingRoom.fixedExtraBed.type,
|
|
description: matchingRoom.fixedExtraBed.description,
|
|
}
|
|
: undefined,
|
|
}
|
|
}
|
|
})
|
|
.filter((bed): bed is BedTypeSelection => Boolean(bed))
|
|
}
|
|
|
|
export function mergeRoomTypes(roomConfigurations: RoomConfiguration[]) {
|
|
// Initial sort to guarantee if one bed is NotAvailable and whereas
|
|
// the other is Available to make sure data is added to the correct
|
|
// roomConfig
|
|
roomConfigurations.sort(sortRoomConfigs)
|
|
|
|
const roomConfigs = new Map<string, RoomConfiguration>()
|
|
for (const roomConfig of roomConfigurations) {
|
|
if (roomConfigs.has(roomConfig.roomType)) {
|
|
const currentRoomConf = roomConfigs.get(roomConfig.roomType)
|
|
if (currentRoomConf) {
|
|
currentRoomConf.features = roomConfig.features.reduce(
|
|
(feats, feature) => {
|
|
const currentFeatureIndex = feats.findIndex(
|
|
(f) => f.code === feature.code
|
|
)
|
|
if (currentFeatureIndex !== -1) {
|
|
feats[currentFeatureIndex].inventory =
|
|
feats[currentFeatureIndex].inventory + feature.inventory
|
|
} else {
|
|
feats.push(feature)
|
|
}
|
|
return feats
|
|
},
|
|
currentRoomConf.features
|
|
)
|
|
currentRoomConf.roomsLeft =
|
|
currentRoomConf.roomsLeft + roomConfig.roomsLeft
|
|
roomConfigs.set(currentRoomConf.roomType, currentRoomConf)
|
|
}
|
|
} else {
|
|
roomConfigs.set(roomConfig.roomType, roomConfig)
|
|
}
|
|
}
|
|
return Array.from(roomConfigs.values())
|
|
}
|
|
|
|
export function selectRateRedirectURL(
|
|
input: RoomsAvailabilityExtendedInputSchema,
|
|
selectedRooms: boolean[]
|
|
) {
|
|
const searchParams = new URLSearchParams({
|
|
errorCode: BookingErrorCodeEnum.AvailabilityError,
|
|
fromdate: input.booking.fromDate,
|
|
hotel: input.booking.hotelId,
|
|
todate: input.booking.toDate,
|
|
})
|
|
if (input.booking.searchType) {
|
|
searchParams.set("searchtype", input.booking.searchType)
|
|
}
|
|
for (const [idx, room] of input.booking.rooms.entries()) {
|
|
searchParams.set(`room[${idx}].adults`, room.adults.toString())
|
|
|
|
if (selectedRooms[idx]) {
|
|
if (room.counterRateCode) {
|
|
searchParams.set(`room[${idx}].counterratecode`, room.counterRateCode)
|
|
}
|
|
searchParams.set(`room[${idx}].ratecode`, room.rateCode)
|
|
searchParams.set(`room[${idx}].roomtype`, room.roomTypeCode)
|
|
} else {
|
|
if (!searchParams.has("activeRoomIndex")) {
|
|
searchParams.set("activeRoomIndex", idx.toString())
|
|
}
|
|
}
|
|
if (room.bookingCode) {
|
|
searchParams.set(`room[${idx}].bookingCode`, room.bookingCode)
|
|
}
|
|
if (room.packages) {
|
|
searchParams.set(`room[${idx}].packages`, room.packages.join(","))
|
|
}
|
|
if (room.childrenInRoom?.length) {
|
|
for (const [i, kid] of room.childrenInRoom.entries()) {
|
|
searchParams.set(`room[${idx}].child[${i}].age`, kid.age.toString())
|
|
searchParams.set(`room[${idx}].child[${i}].bed`, kid.bed.toString())
|
|
}
|
|
}
|
|
}
|
|
|
|
return `${selectRate(input.lang)}?${searchParams.toString()}`
|
|
}
|