Feat/SW-3549 pass scandic token * WIP pass scandic token * pass scandic token when booking * Merge branch 'master' of bitbucket.org:scandic-swap/web into feat/SW-3549-pass-scandic-token * pass user token when doing availability search * undo changes * merge * Merged in fix/sw-3551-rsc-bookingflowconfig (pull request #2988) fix(SW-3551): Fix issue with BookingConfigProvider in RSC * wip move config to pages * Move config providing to pages * Merged in fix/update-promo-error-modal-text (pull request #2990) fix: update promo error modal text * fix: update promo error modal text Approved-by: Emma Zettervall * Merged in fix/sw-3514-missing-membership-input-for-multiroom (pull request #2991) fix(SW-3514): Show join Scandic Friends card for SAS multiroom * Show join card for room 2+ Approved-by: Hrishikesh Vaipurkar * Merged in feat/lokalise-rebuild (pull request #2993) Feat/lokalise rebuild * chore(lokalise): update translation ids * chore(lokalise): easier to switch between projects * chore(lokalise): update translation ids * . * . * . * . * . * . * chore(lokalise): update translation ids * chore(lokalise): update translation ids * . * . * . * chore(lokalise): update translation ids * chore(lokalise): update translation ids * . * . * chore(lokalise): update translation ids * chore(lokalise): update translation ids * chore(lokalise): new translations * merge * switch to errors for missing id's * merge * sync translations Approved-by: Linus Flood * Merged in feat/SW-3552-logout-from-social-session-when- (pull request #2994) feat(SW-3552): Removed scandic session on logout Approved-by: Joakim Jäderberg * merge * replace getRedemptionTokenSafely() with context based instead * Refactor user verification and error handling across multiple components; implement safeTry utility for safer async calls * Refactor user verification and error handling across multiple components; implement safeTry utility for safer async calls * merge * Merge branch 'master' of bitbucket.org:scandic-swap/web into feat/SW-3549-pass-scandic-token * add booking scope remove unused getMembershipNumber() Approved-by: Anton Gunnarsson Approved-by: Hrishikesh Vaipurkar
210 lines
6.6 KiB
TypeScript
210 lines
6.6 KiB
TypeScript
import stringify from "json-stable-stringify-without-jsonify"
|
|
|
|
import { getCacheClient } from "@scandic-hotels/common/dataCache"
|
|
import { createCounter } from "@scandic-hotels/common/telemetry"
|
|
|
|
import * as api from "../../../api"
|
|
import { SEARCH_TYPE_REDEMPTION } from "../../../constants/booking"
|
|
import { RoomPackageCodeEnum } from "../../../enums/roomFilter"
|
|
import { AvailabilityEnum } from "../../../enums/selectHotel"
|
|
import { toApiLang } from "../../../utils"
|
|
import { generateChildrenString } from "../helpers"
|
|
import { roomsAvailabilitySchema } from "../output"
|
|
import { getPackages } from "./getPackages"
|
|
import { getRoomFeaturesInventory } from "./getRoomFeaturesInventory"
|
|
|
|
import type { RoomsAvailabilityOutputSchema } from "../availability/selectRate/rooms/schema"
|
|
|
|
export async function getRoomsAvailability(
|
|
input: RoomsAvailabilityOutputSchema,
|
|
userToken: string | null | undefined,
|
|
serviceToken: string,
|
|
userPoints: number | undefined
|
|
) {
|
|
const {
|
|
booking: { bookingCode, fromDate, hotelId, rooms, searchType, toDate },
|
|
lang,
|
|
} = input
|
|
|
|
const redemption = searchType === SEARCH_TYPE_REDEMPTION
|
|
|
|
const getRoomsAvailabilityCounter = createCounter(
|
|
"hotel",
|
|
"getRoomsAvailability"
|
|
)
|
|
const metricsGetRoomsAvailability = getRoomsAvailabilityCounter.init({
|
|
input,
|
|
redemption,
|
|
})
|
|
|
|
metricsGetRoomsAvailability.start()
|
|
|
|
const apiLang = toApiLang(lang)
|
|
|
|
const baseCacheKey = {
|
|
lang,
|
|
hotelId,
|
|
fromDate,
|
|
toDate,
|
|
searchType,
|
|
bookingCode,
|
|
}
|
|
|
|
const cacheClient = await getCacheClient()
|
|
const availabilityResponses = await Promise.allSettled(
|
|
rooms.map((room) => {
|
|
const cacheKey = {
|
|
...baseCacheKey,
|
|
room,
|
|
}
|
|
|
|
const token = userToken
|
|
? ({ type: "userToken", token: userToken } as const)
|
|
: ({ type: "serviceToken", token: serviceToken } as const)
|
|
|
|
const result = cacheClient.cacheOrGet(
|
|
stringify(cacheKey),
|
|
async function () {
|
|
{
|
|
const params = {
|
|
adults: room.adults,
|
|
language: apiLang,
|
|
roomStayStartDate: fromDate,
|
|
roomStayEndDate: toDate,
|
|
...(room.childrenInRoom?.length && {
|
|
children: generateChildrenString(room.childrenInRoom),
|
|
}),
|
|
...(bookingCode && { bookingCode: bookingCode }),
|
|
...(redemption && { isRedemption: "true" }),
|
|
}
|
|
|
|
const apiResponse = await api.get(
|
|
api.endpoints.v1.Availability.hotel(hotelId),
|
|
{
|
|
cache: undefined, // overwrite default
|
|
headers: {
|
|
Authorization: `Bearer ${token.token}`,
|
|
},
|
|
},
|
|
params
|
|
)
|
|
|
|
if (!apiResponse.ok) {
|
|
await metricsGetRoomsAvailability.httpError(apiResponse)
|
|
const text = await apiResponse.text()
|
|
return { error: "http_error", details: text }
|
|
}
|
|
|
|
const apiJson = await apiResponse.json()
|
|
const validateAvailabilityData =
|
|
roomsAvailabilitySchema.safeParse(apiJson)
|
|
if (!validateAvailabilityData.success) {
|
|
metricsGetRoomsAvailability.validationError(
|
|
validateAvailabilityData.error
|
|
)
|
|
|
|
return {
|
|
error: "validation_error",
|
|
details: validateAvailabilityData.error,
|
|
}
|
|
}
|
|
|
|
if (redemption) {
|
|
for (const roomConfig of validateAvailabilityData.data
|
|
.roomConfigurations) {
|
|
for (const product of roomConfig.redemptions) {
|
|
if (userPoints) {
|
|
product.redemption.hasEnoughPoints =
|
|
userPoints >= product.redemption.localPrice.pointsPerStay
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const roomFeatures = await getPackages(
|
|
{
|
|
adults: room.adults,
|
|
children: room.childrenInRoom?.length || 0,
|
|
endDate: input.booking.toDate,
|
|
hotelId: input.booking.hotelId,
|
|
lang,
|
|
packageCodes: [
|
|
RoomPackageCodeEnum.ACCESSIBILITY_ROOM,
|
|
RoomPackageCodeEnum.ALLERGY_ROOM,
|
|
RoomPackageCodeEnum.PET_ROOM,
|
|
],
|
|
startDate: input.booking.fromDate,
|
|
},
|
|
serviceToken
|
|
)
|
|
|
|
if (roomFeatures) {
|
|
validateAvailabilityData.data.packages = roomFeatures
|
|
}
|
|
|
|
// Fetch packages
|
|
if (room.packages?.length) {
|
|
const roomFeaturesInventory = await getRoomFeaturesInventory(
|
|
{
|
|
adults: room.adults,
|
|
childrenInRoom: room.childrenInRoom,
|
|
endDate: input.booking.toDate,
|
|
hotelId: input.booking.hotelId,
|
|
lang,
|
|
roomFeatureCodes: room.packages,
|
|
startDate: input.booking.fromDate,
|
|
},
|
|
serviceToken
|
|
)
|
|
|
|
if (roomFeaturesInventory) {
|
|
const features = roomFeaturesInventory.reduce<
|
|
Record<string, number>
|
|
>((fts, feat) => {
|
|
fts[feat.roomTypeCode] = feat.features?.[0]?.inventory ?? 0
|
|
return fts
|
|
}, {})
|
|
|
|
const updatedRoomConfigurations =
|
|
validateAvailabilityData.data.roomConfigurations
|
|
// This filter is needed since we can get availability
|
|
// back from roomFeatures yet the availability call
|
|
// says there are no rooms left...
|
|
.filter((rc) => rc.roomsLeft)
|
|
.filter((rc) => features?.[rc.roomTypeCode])
|
|
.map((rc) => ({
|
|
...rc,
|
|
roomsLeft: features[rc.roomTypeCode],
|
|
status: AvailabilityEnum.Available,
|
|
}))
|
|
|
|
validateAvailabilityData.data.roomConfigurations =
|
|
updatedRoomConfigurations
|
|
}
|
|
}
|
|
|
|
return validateAvailabilityData.data
|
|
}
|
|
},
|
|
token.type === "userToken" ? "no cache" : "1m"
|
|
)
|
|
|
|
return result
|
|
})
|
|
)
|
|
|
|
const data = availabilityResponses.map((availability) => {
|
|
if (availability.status === "fulfilled") {
|
|
return availability.value
|
|
}
|
|
return {
|
|
details: availability.reason,
|
|
error: "request_failure",
|
|
}
|
|
})
|
|
|
|
metricsGetRoomsAvailability.success()
|
|
|
|
return data
|
|
}
|