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
This commit is contained in:
178
packages/trpc/lib/routers/hotels/availability/enterDetails.ts
Normal file
178
packages/trpc/lib/routers/hotels/availability/enterDetails.ts
Normal file
@@ -0,0 +1,178 @@
|
||||
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
|
||||
})
|
||||
122
packages/trpc/lib/routers/hotels/availability/hotelsByCity.ts
Normal file
122
packages/trpc/lib/routers/hotels/availability/hotelsByCity.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
import dayjs from "dayjs"
|
||||
import { z } from "zod"
|
||||
|
||||
import { getCacheClient } from "@scandic-hotels/common/dataCache"
|
||||
|
||||
import { env } from "../../../../env/server"
|
||||
import { unauthorizedError } from "../../../errors"
|
||||
import { safeProtectedServiceProcedure } from "../../../procedures"
|
||||
import { toApiLang } from "../../../utils"
|
||||
import { getVerifiedUser } from "../../user/utils/getVerifiedUser"
|
||||
import { getHotelsAvailabilityByCity } from "../services/getHotelsAvailabilityByCity"
|
||||
|
||||
export type HotelsAvailabilityInputSchema = z.output<
|
||||
typeof hotelsAvailabilityInputSchema
|
||||
>
|
||||
export const hotelsAvailabilityInputSchema = z
|
||||
.object({
|
||||
cityId: z.string(),
|
||||
roomStayStartDate: z.string().refine(
|
||||
(val) => {
|
||||
const fromDate = dayjs(val)
|
||||
|
||||
return fromDate.isValid()
|
||||
},
|
||||
{
|
||||
message: "FROMDATE_INVALID",
|
||||
}
|
||||
),
|
||||
roomStayEndDate: z.string().refine(
|
||||
(val) => {
|
||||
const fromDate = dayjs(val)
|
||||
return fromDate.isValid()
|
||||
},
|
||||
{
|
||||
message: "TODATE_INVALID",
|
||||
}
|
||||
),
|
||||
adults: z.number(),
|
||||
children: z.string().optional(),
|
||||
bookingCode: z.string().optional().default(""),
|
||||
redemption: z.boolean().optional().default(false),
|
||||
})
|
||||
.refine(
|
||||
(data) => {
|
||||
const fromDate = dayjs(data.roomStayStartDate).startOf("day")
|
||||
const toDate = dayjs(data.roomStayEndDate).startOf("day")
|
||||
|
||||
return fromDate.isBefore(toDate)
|
||||
},
|
||||
{
|
||||
message: "FROMDATE_BEFORE_TODATE",
|
||||
}
|
||||
)
|
||||
.refine(
|
||||
(data) => {
|
||||
const fromDate = dayjs(data.roomStayStartDate)
|
||||
const today = dayjs().startOf("day")
|
||||
|
||||
return fromDate.isSameOrAfter(today)
|
||||
},
|
||||
{
|
||||
message: "FROMDATE_CANNOT_BE_IN_THE_PAST",
|
||||
}
|
||||
)
|
||||
|
||||
export const hotelsByCity = safeProtectedServiceProcedure
|
||||
.input(hotelsAvailabilityInputSchema)
|
||||
.use(async ({ ctx, input, next }) => {
|
||||
if (input.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,
|
||||
},
|
||||
input,
|
||||
})
|
||||
}
|
||||
}
|
||||
throw unauthorizedError()
|
||||
}
|
||||
return next({
|
||||
ctx: {
|
||||
token: ctx.serviceToken,
|
||||
},
|
||||
input,
|
||||
})
|
||||
})
|
||||
.query(async ({ ctx, input }) => {
|
||||
const { lang } = ctx
|
||||
const apiLang = toApiLang(lang)
|
||||
const {
|
||||
cityId,
|
||||
roomStayStartDate,
|
||||
roomStayEndDate,
|
||||
adults,
|
||||
children,
|
||||
bookingCode,
|
||||
redemption,
|
||||
} = input
|
||||
|
||||
// In case of redemption do not cache result
|
||||
if (redemption) {
|
||||
return getHotelsAvailabilityByCity(
|
||||
input,
|
||||
apiLang,
|
||||
ctx.token,
|
||||
ctx.userPoints
|
||||
)
|
||||
}
|
||||
|
||||
const cacheClient = await getCacheClient()
|
||||
return await cacheClient.cacheOrGet(
|
||||
`${cityId}:${roomStayStartDate}:${roomStayEndDate}:${adults}:${children}:${bookingCode}`,
|
||||
async () => {
|
||||
return getHotelsAvailabilityByCity(input, apiLang, ctx.token)
|
||||
},
|
||||
env.CACHE_TIME_CITY_SEARCH
|
||||
)
|
||||
})
|
||||
@@ -0,0 +1,58 @@
|
||||
import { serviceProcedure } from "../../../procedures"
|
||||
import { toApiLang } from "../../../utils"
|
||||
import { getHotelsAvailabilityByCity } from "../services/getHotelsAvailabilityByCity"
|
||||
import { getHotelsAvailabilityByHotelIds } from "../services/getHotelsAvailabilityByHotelIds"
|
||||
import { hotelsAvailabilityInputSchema } from "./hotelsByCity"
|
||||
|
||||
export const hotelsByCityWithBookingCode = serviceProcedure
|
||||
.input(hotelsAvailabilityInputSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { lang } = ctx
|
||||
const apiLang = toApiLang(lang)
|
||||
|
||||
const bookingCodeAvailabilityResponse = await getHotelsAvailabilityByCity(
|
||||
input,
|
||||
apiLang,
|
||||
ctx.serviceToken
|
||||
)
|
||||
|
||||
// Get regular availability of hotels which don't have availability with booking code.
|
||||
const unavailableHotelIds = bookingCodeAvailabilityResponse.availability
|
||||
.filter((hotel) => {
|
||||
return hotel.status === "NotAvailable"
|
||||
})
|
||||
.flatMap((hotel) => {
|
||||
return hotel.hotelId
|
||||
})
|
||||
|
||||
// All hotels have availability with booking code no need to fetch regular prices.
|
||||
// return response as is without any filtering as below.
|
||||
if (!unavailableHotelIds || !unavailableHotelIds.length) {
|
||||
return bookingCodeAvailabilityResponse
|
||||
}
|
||||
|
||||
const unavailableHotelsInput = {
|
||||
...input,
|
||||
bookingCode: "",
|
||||
hotelIds: unavailableHotelIds,
|
||||
}
|
||||
const unavailableHotels = await getHotelsAvailabilityByHotelIds(
|
||||
unavailableHotelsInput,
|
||||
apiLang,
|
||||
ctx.serviceToken
|
||||
)
|
||||
|
||||
// No regular rates available due to network or API failure (no need to filter & merge).
|
||||
if (!unavailableHotels) {
|
||||
return bookingCodeAvailabilityResponse
|
||||
}
|
||||
|
||||
// Filtering the response hotels to merge bookingCode rates and regular rates in single response.
|
||||
return {
|
||||
availability: bookingCodeAvailabilityResponse.availability
|
||||
.filter((hotel) => {
|
||||
return hotel.status === "Available"
|
||||
})
|
||||
.concat(unavailableHotels.availability),
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,97 @@
|
||||
import dayjs from "dayjs"
|
||||
import { z } from "zod"
|
||||
|
||||
import { unauthorizedError } from "../../../errors"
|
||||
import { safeProtectedServiceProcedure } from "../../../procedures"
|
||||
import { toApiLang } from "../../../utils"
|
||||
import { getVerifiedUser } from "../../user/utils/getVerifiedUser"
|
||||
import { getHotelsAvailabilityByHotelIds } from "../services/getHotelsAvailabilityByHotelIds"
|
||||
|
||||
export type HotelsByHotelIdsAvailabilityInputSchema = z.output<
|
||||
typeof getHotelsByHotelIdsAvailabilityInputSchema
|
||||
>
|
||||
export const getHotelsByHotelIdsAvailabilityInputSchema = z
|
||||
.object({
|
||||
hotelIds: z.array(z.number()),
|
||||
roomStayStartDate: z.string().refine(
|
||||
(val) => {
|
||||
const fromDate = dayjs(val)
|
||||
return fromDate.isValid()
|
||||
},
|
||||
{
|
||||
message: "FROMDATE_INVALID",
|
||||
}
|
||||
),
|
||||
roomStayEndDate: z.string().refine(
|
||||
(val) => {
|
||||
const toDate = dayjs(val)
|
||||
|
||||
return toDate.isValid()
|
||||
},
|
||||
{
|
||||
message: "TODATE_INVALID",
|
||||
}
|
||||
),
|
||||
adults: z.number(),
|
||||
children: z.string().optional(),
|
||||
bookingCode: z.string().optional().default(""),
|
||||
redemption: z.boolean().optional().default(false),
|
||||
})
|
||||
.refine(
|
||||
(data) => {
|
||||
const fromDate = dayjs(data.roomStayStartDate).startOf("day")
|
||||
const toDate = dayjs(data.roomStayEndDate).startOf("day")
|
||||
|
||||
return fromDate.isBefore(toDate)
|
||||
},
|
||||
{
|
||||
message: "FROMDATE_BEFORE_TODATE",
|
||||
}
|
||||
)
|
||||
.refine(
|
||||
(data) => {
|
||||
const fromDate = dayjs(data.roomStayStartDate)
|
||||
const today = dayjs().startOf("day")
|
||||
|
||||
return fromDate.isSameOrAfter(today)
|
||||
},
|
||||
{
|
||||
message: "FROMDATE_CANNOT_BE_IN_THE_PAST",
|
||||
}
|
||||
)
|
||||
|
||||
export const hotelsByHotelIds = safeProtectedServiceProcedure
|
||||
.input(getHotelsByHotelIdsAvailabilityInputSchema)
|
||||
.use(async ({ ctx, input, next }) => {
|
||||
if (input.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,
|
||||
},
|
||||
input,
|
||||
})
|
||||
}
|
||||
}
|
||||
throw unauthorizedError()
|
||||
}
|
||||
return next({
|
||||
ctx: {
|
||||
token: ctx.serviceToken,
|
||||
},
|
||||
input,
|
||||
})
|
||||
})
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { lang } = ctx
|
||||
const apiLang = toApiLang(lang)
|
||||
return getHotelsAvailabilityByHotelIds(
|
||||
input,
|
||||
apiLang,
|
||||
ctx.token,
|
||||
ctx.userPoints
|
||||
)
|
||||
})
|
||||
16
packages/trpc/lib/routers/hotels/availability/index.ts
Normal file
16
packages/trpc/lib/routers/hotels/availability/index.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { router } from "../../.."
|
||||
import { enterDetails } from "./enterDetails"
|
||||
import { hotelsByCity } from "./hotelsByCity"
|
||||
import { hotelsByCityWithBookingCode } from "./hotelsByCityWithBookingCode"
|
||||
import { hotelsByHotelIds } from "./hotelsByHotelIds"
|
||||
import { myStay } from "./myStay"
|
||||
import { selectRate } from "./selectRate"
|
||||
|
||||
export const availability = router({
|
||||
hotelsByCity,
|
||||
hotelsByHotelIds,
|
||||
enterDetails,
|
||||
myStay,
|
||||
selectRate,
|
||||
hotelsByCityWithBookingCode,
|
||||
})
|
||||
81
packages/trpc/lib/routers/hotels/availability/myStay.ts
Normal file
81
packages/trpc/lib/routers/hotels/availability/myStay.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { z } from "zod"
|
||||
|
||||
import { Lang } from "@scandic-hotels/common/constants/language"
|
||||
import { createLogger } from "@scandic-hotels/common/logger/createLogger"
|
||||
|
||||
import { SEARCH_TYPE_REDEMPTION } from "../../../constants/booking"
|
||||
import { unauthorizedError } from "../../../errors"
|
||||
import { safeProtectedServiceProcedure } from "../../../procedures"
|
||||
import { getVerifiedUser } from "../../user/utils/getVerifiedUser"
|
||||
import { baseBookingSchema, baseRoomSchema, selectedRoomSchema } from "../input"
|
||||
import { getRoomsAvailability } from "../services/getRoomsAvailability"
|
||||
import { getSelectedRoomAvailability } from "../utils"
|
||||
|
||||
export const myStayRoomAvailabilityInputSchema = z.object({
|
||||
booking: baseBookingSchema.extend({
|
||||
room: baseRoomSchema.merge(selectedRoomSchema),
|
||||
}),
|
||||
lang: z.nativeEnum(Lang),
|
||||
})
|
||||
|
||||
const logger = createLogger("trpc:availability:myStay")
|
||||
export const myStay = safeProtectedServiceProcedure
|
||||
.input(myStayRoomAvailabilityInputSchema)
|
||||
.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(
|
||||
{
|
||||
booking: {
|
||||
...input.booking,
|
||||
rooms: [input.booking.room],
|
||||
},
|
||||
lang: input.lang,
|
||||
},
|
||||
ctx.token,
|
||||
ctx.serviceToken,
|
||||
ctx.userPoints
|
||||
)
|
||||
|
||||
if (!availability || "error" in availability) {
|
||||
return null
|
||||
}
|
||||
|
||||
const bookingRoom = input.booking.room
|
||||
const selected = getSelectedRoomAvailability(
|
||||
bookingRoom.rateCode,
|
||||
availability.rateDefinitions,
|
||||
availability.roomConfigurations,
|
||||
bookingRoom.roomTypeCode,
|
||||
ctx.userPoints
|
||||
)
|
||||
|
||||
if (!selected) {
|
||||
logger.error("Unable to find selected room")
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
product: selected.product,
|
||||
selectedRoom: selected.selectedRoom,
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,8 @@
|
||||
import { router } from "../../../.."
|
||||
import { room } from "./room"
|
||||
import { rooms } from "./rooms"
|
||||
|
||||
export const selectRate = router({
|
||||
room,
|
||||
rooms,
|
||||
})
|
||||
@@ -0,0 +1,69 @@
|
||||
import { z } from "zod"
|
||||
|
||||
import { Lang } from "@scandic-hotels/common/constants/language"
|
||||
|
||||
import { SEARCH_TYPE_REDEMPTION } from "../../../../constants/booking"
|
||||
import { unauthorizedError } from "../../../../errors"
|
||||
import { safeProtectedServiceProcedure } from "../../../../procedures"
|
||||
import { getVerifiedUser } from "../../../user/utils/getVerifiedUser"
|
||||
import { baseBookingSchema, baseRoomSchema } from "../../input"
|
||||
import { getRoomsAvailability } from "../../services/getRoomsAvailability"
|
||||
import { mergeRoomTypes } from "../../utils"
|
||||
|
||||
export const selectRateRoomAvailabilityInputSchema = z.object({
|
||||
booking: baseBookingSchema.extend({
|
||||
room: baseRoomSchema,
|
||||
}),
|
||||
lang: z.nativeEnum(Lang),
|
||||
})
|
||||
|
||||
export const room = safeProtectedServiceProcedure
|
||||
.input(selectRateRoomAvailabilityInputSchema)
|
||||
.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(
|
||||
{
|
||||
booking: {
|
||||
...input.booking,
|
||||
rooms: [input.booking.room],
|
||||
},
|
||||
lang: input.lang,
|
||||
},
|
||||
ctx.token,
|
||||
ctx.serviceToken,
|
||||
ctx.userPoints
|
||||
)
|
||||
|
||||
if (!availability || "error" in availability) {
|
||||
return null
|
||||
}
|
||||
|
||||
const roomConfigurations = mergeRoomTypes(availability.roomConfigurations)
|
||||
|
||||
return {
|
||||
...availability,
|
||||
roomConfigurations,
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,58 @@
|
||||
import "server-only"
|
||||
|
||||
import { SEARCH_TYPE_REDEMPTION } from "../../../../../constants/booking"
|
||||
import { unauthorizedError } from "../../../../../errors"
|
||||
import { safeProtectedServiceProcedure } from "../../../../../procedures"
|
||||
import { getVerifiedUser } from "../../../../user/utils/getVerifiedUser"
|
||||
import { getRoomsAvailability } from "../../../services/getRoomsAvailability"
|
||||
import { mergeRoomTypes } from "../../../utils"
|
||||
import { selectRateRoomsAvailabilityInputSchema } from "./schema"
|
||||
|
||||
export const rooms = safeProtectedServiceProcedure
|
||||
.input(selectRateRoomsAvailabilityInputSchema)
|
||||
.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 }) {
|
||||
input.booking.rooms = input.booking.rooms.map((room) => ({
|
||||
...room,
|
||||
bookingCode: room.bookingCode || input.booking.bookingCode,
|
||||
}))
|
||||
|
||||
const availability = await getRoomsAvailability(
|
||||
input,
|
||||
ctx.token,
|
||||
ctx.serviceToken,
|
||||
ctx.userPoints
|
||||
)
|
||||
|
||||
for (const room of availability) {
|
||||
if (!room || "error" in room) {
|
||||
continue
|
||||
}
|
||||
|
||||
room.roomConfigurations = mergeRoomTypes(room.roomConfigurations)
|
||||
}
|
||||
|
||||
return availability
|
||||
})
|
||||
@@ -0,0 +1,64 @@
|
||||
import dayjs from "dayjs"
|
||||
import { z } from "zod"
|
||||
|
||||
import { Lang } from "@scandic-hotels/common/constants/language"
|
||||
|
||||
import { baseBookingSchema, baseRoomSchema } from "../../../input"
|
||||
|
||||
export type RoomsAvailabilityInputRoom =
|
||||
RoomsAvailabilityInputSchema["booking"]["rooms"][number]
|
||||
export type RoomsAvailabilityOutputSchema = z.output<
|
||||
typeof selectRateRoomsAvailabilityInputSchema
|
||||
>
|
||||
export type RoomsAvailabilityInputSchema = z.input<
|
||||
typeof selectRateRoomsAvailabilityInputSchema
|
||||
>
|
||||
export const selectRateRoomsAvailabilityInputSchema = z
|
||||
.object({
|
||||
booking: baseBookingSchema.extend({
|
||||
rooms: z.array(baseRoomSchema),
|
||||
}),
|
||||
lang: z.nativeEnum(Lang),
|
||||
})
|
||||
.refine(
|
||||
(data) => {
|
||||
const fromDate = dayjs(data.booking.fromDate)
|
||||
|
||||
return fromDate.isValid()
|
||||
},
|
||||
{
|
||||
message: "FROMDATE_INVALID",
|
||||
}
|
||||
)
|
||||
.refine(
|
||||
(data) => {
|
||||
const toDate = dayjs(data.booking.toDate)
|
||||
|
||||
return toDate.isValid()
|
||||
},
|
||||
{
|
||||
message: "TODATE_INVALID",
|
||||
}
|
||||
)
|
||||
.refine(
|
||||
(data) => {
|
||||
const fromDate = dayjs(data.booking.fromDate).startOf("day")
|
||||
const toDate = dayjs(data.booking.toDate).startOf("day")
|
||||
|
||||
return fromDate.isBefore(toDate)
|
||||
},
|
||||
{
|
||||
message: "TODATE_MUST_BE_AFTER_FROMDATE",
|
||||
}
|
||||
)
|
||||
.refine(
|
||||
(data) => {
|
||||
const fromDate = dayjs(data.booking.fromDate)
|
||||
const today = dayjs().startOf("day")
|
||||
|
||||
return fromDate.isSameOrAfter(today)
|
||||
},
|
||||
{
|
||||
message: "FROMDATE_CANNOT_BE_IN_THE_PAST",
|
||||
}
|
||||
)
|
||||
Reference in New Issue
Block a user