Files
web/apps/scandic-web/server/routers/booking/output.ts
Arvid Norlin 540402b969 Merged in feat/SW-1813 (pull request #1516)
Feat/SW-1813

* feat(SW-1652): handle linkedReservations fetching

* feat: add linkedReservation retry functionality

* chore: align naming

* feat(SW-1813): Add booking confirmation PriceDetailsModal


Approved-by: Simon.Emanuelsson
2025-03-14 13:49:22 +00:00

249 lines
7.1 KiB
TypeScript

import { z } from "zod"
import { ChildBedTypeEnum } from "@/constants/booking"
import { nullableArrayObjectValidator } from "@/utils/zod/arrayValidator"
import { phoneValidator } from "@/utils/zod/phoneValidator"
import { nullableStringValidator } from "@/utils/zod/stringValidator"
// MUTATION
export const createBookingSchema = z
.object({
data: z.object({
attributes: z.object({
reservationStatus: z.string(),
paymentUrl: z.string().nullable().optional(),
rooms: z
.array(
z.object({
confirmationNumber: z.string(),
cancellationNumber: z.string().nullable(),
priceChangedMetadata: z
.object({
roomPrice: z.number(),
totalPrice: z.number(),
})
.nullable()
.optional(),
})
)
.default([]),
errors: z
.array(
z.object({
confirmationNumber: z.string().nullable().optional(),
errorCode: z.string(),
description: z.string().nullable().optional(),
meta: z
.record(z.string(), z.union([z.string(), z.number()]))
.nullable()
.optional(),
})
)
.default([]),
}),
type: z.string(),
id: z.string(),
links: z.object({
self: z.object({
href: z.string().url(),
meta: z.object({
method: z.string(),
}),
}),
}),
}),
})
.transform((d) => ({
id: d.data.id,
links: d.data.links,
type: d.data.type,
reservationStatus: d.data.attributes.reservationStatus,
paymentUrl: d.data.attributes.paymentUrl,
rooms: d.data.attributes.rooms,
errors: d.data.attributes.errors,
}))
// QUERY
const childBedPreferencesSchema = z.object({
bedType: z.nativeEnum(ChildBedTypeEnum),
quantity: z.number().int(),
code: z.string().nullable().default(""),
})
const guestSchema = z.object({
email: z.string().email().nullable().default(""),
firstName: z.string().nullable().default(""),
lastName: z.string().nullable().default(""),
membershipNumber: z.string().nullable().default(""),
phoneNumber: phoneValidator().nullable().default(""),
countryCode: z.string().nullable().default(""),
})
export type Guest = z.output<typeof guestSchema>
export const packageSchema = z
.object({
type: z.string().nullable(),
description: nullableStringValidator,
code: z.string().nullable().default(""),
price: z.object({
unit: z.number().int().nullable(),
unitPrice: z.number(),
totalPrice: z.number().nullable(),
totalUnit: z.number().int().nullable(),
currency: z.string().default(""),
points: z.number().int().nullable(),
}),
comment: z.string().nullable().optional(),
})
.transform((packageData) => ({
type: packageData.type,
description: packageData.description,
comment: packageData.comment,
code: packageData.code,
currency: packageData.price.currency,
points: packageData.price.points,
totalPrice: packageData.price.totalPrice ?? 0,
totalUnit: packageData.price.totalUnit ?? 0,
unit: packageData.price.unit ?? 0,
unitPrice: packageData.price.unitPrice,
}))
const ancillarySchema = z
.object({
comment: z.string().default(""),
deliveryTime: z.string().default(""),
})
.nullable()
.default({
comment: "",
deliveryTime: "",
})
const rateDefinitionSchema = z.object({
breakfastIncluded: z.boolean().default(false),
cancellationRule: z.string().nullable().default(""),
cancellationText: z.string().nullable().default(""),
generalTerms: z.array(z.string()).default([]),
isMemberRate: z.boolean().default(false),
mustBeGuaranteed: z.boolean().default(false),
rateCode: z.string().nullable().default(""),
title: z.string().nullable().default(""),
})
export const linkedReservationSchema = z.object({
confirmationNumber: z.string().default(""),
hotelId: z.string().default(""),
checkinDate: z.string(),
checkoutDate: z.string(),
cancellationNumber: nullableStringValidator,
roomTypeCode: z.string().default(""),
adults: z.number().int(),
children: z.number().int(),
profileId: z.string().default(""),
})
const linksSchema = z.object({
addAncillary: z
.object({
href: z.string(),
meta: z.object({
method: z.string(),
}),
})
.nullable(),
cancel: z
.object({
href: z.string(),
meta: z.object({
method: z.string(),
}),
})
.nullable(),
guarantee: z
.object({
href: z.string(),
meta: z.object({
method: z.string(),
}),
})
.nullable(),
modify: z
.object({
href: z.string(),
meta: z.object({
method: z.string(),
}),
})
.nullable(),
self: z
.object({
href: z.string(),
meta: z.object({
method: z.string(),
}),
})
.nullable(),
})
export const bookingConfirmationSchema = z
.object({
data: z.object({
attributes: z.object({
adults: z.number().int(),
ancillary: ancillarySchema,
cancellationNumber: z.string().nullable().default(""),
checkInDate: z.date({ coerce: true }),
checkOutDate: z.date({ coerce: true }),
childBedPreferences: z.array(childBedPreferencesSchema).default([]),
childrenAges: z.array(z.number().int()).default([]),
canChangeDate: z.boolean(),
bookingCode: z.string().nullable(),
guaranteeInfo: z
.object({
maskedCard: z.string(),
cardType: z.string(),
paymentMethod: z.string(),
paymentMethodDescription: z.string(),
})
.nullish(),
computedReservationStatus: z.string().nullable().default(""),
confirmationNumber: nullableStringValidator,
createDateTime: z.date({ coerce: true }),
currencyCode: z.string(),
guest: guestSchema,
linkedReservations: nullableArrayObjectValidator(
linkedReservationSchema
),
hotelId: z.string(),
mainRoom: z.boolean(),
multiRoom: z.boolean(),
packages: z.array(packageSchema).default([]),
rateDefinition: rateDefinitionSchema,
reservationStatus: z.string().nullable().default(""),
roomPrice: z.number(),
roomTypeCode: z.string().nullable().default(""),
totalPrice: z.number(),
totalPriceExVat: z.number(),
vatAmount: z.number(),
vatPercentage: z.number(),
}),
id: z.string(),
type: z.literal("booking"),
links: linksSchema,
}),
})
.transform(({ data }) => ({
...data.attributes,
packages: data.attributes.packages.filter((p) => p.type !== "Ancillary"),
ancillaries: data.attributes.packages.filter((p) => p.type === "Ancillary"),
extraBedTypes: data.attributes.childBedPreferences,
showAncillaries: !!(
data.links.addAncillary ||
data.attributes.packages.some((p) => p.type === "Ancillary")
),
isCancelable: !!data.links.cancel,
isModifiable: !!data.links.modify,
}))