fix(SW-3616): Handle EuroBonus point type everywhere * Add tests to formatPrice * formatPrice * More work replacing config with api points type * More work replacing config with api points type * More fixing with currency * maybe actually fixed it * Fix MyStay * Clean up * Fix comments * Merge branch 'master' into fix/refactor-currency-display * Fix calculateTotalPrice for EB points + SF points + cash Approved-by: Joakim Jäderberg
243 lines
7.5 KiB
TypeScript
243 lines
7.5 KiB
TypeScript
import { z } from "zod"
|
|
|
|
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
|
|
import { dt } from "@scandic-hotels/common/dt"
|
|
import { nullableArrayObjectValidator } from "@scandic-hotels/common/utils/zod/arrayValidator"
|
|
import {
|
|
nullableStringEmailValidator,
|
|
nullableStringValidator,
|
|
} from "@scandic-hotels/common/utils/zod/stringValidator"
|
|
|
|
import { BookingStatusEnum } from "../../enums/bookingStatus"
|
|
import { BreakfastPackageEnum } from "../../enums/breakfast"
|
|
import { ChildBedTypeEnum } from "../../enums/childBedTypeEnum"
|
|
import { calculateRefId } from "../../utils/refId"
|
|
|
|
export const guestSchema = z.object({
|
|
email: nullableStringEmailValidator,
|
|
firstName: nullableStringValidator,
|
|
lastName: nullableStringValidator,
|
|
membershipNumber: nullableStringValidator,
|
|
phoneNumber: nullableStringValidator,
|
|
countryCode: nullableStringValidator,
|
|
})
|
|
|
|
export type Guest = z.output<typeof guestSchema>
|
|
|
|
// QUERY
|
|
const childBedPreferencesSchema = z.object({
|
|
bedType: z.nativeEnum(ChildBedTypeEnum),
|
|
quantity: z.number().int(),
|
|
code: z.string().nullable().default(""),
|
|
})
|
|
|
|
const priceSchema = z.object({
|
|
currency: z.nativeEnum(CurrencyEnum).default(CurrencyEnum.Unknown),
|
|
totalPrice: z.number().nullish(),
|
|
totalUnit: z.number().int().nullish(),
|
|
unit: z.number().int().nullish(),
|
|
unitPrice: z.number(),
|
|
})
|
|
|
|
export const packageSchema = z
|
|
.object({
|
|
code: nullableStringValidator,
|
|
comment: z.string().nullish(),
|
|
description: nullableStringValidator,
|
|
price: priceSchema,
|
|
type: z.string().nullish(),
|
|
})
|
|
.transform((packageData) => ({
|
|
code: packageData.code,
|
|
comment: packageData.comment,
|
|
currency: packageData.price.currency,
|
|
description: packageData.description,
|
|
totalPrice: packageData.price.totalPrice ?? 0,
|
|
totalUnit: packageData.price.totalUnit ?? 0,
|
|
type: packageData.type,
|
|
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().default(""),
|
|
title: z.string().nullable().default(""),
|
|
isCampaignRate: z.boolean().default(false),
|
|
})
|
|
|
|
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,
|
|
bookingType: z.string().optional(),
|
|
cancelationNumber: z.string().nullable().default(""),
|
|
checkInDate: z.string().refine((val) => dt(val).isValid()),
|
|
checkOutDate: z.string().refine((val) => dt(val).isValid()),
|
|
childBedPreferences: z.array(childBedPreferencesSchema).default([]),
|
|
childrenAges: z.array(z.number().int()).default([]),
|
|
canChangeDate: z.boolean(),
|
|
bookingCode: z.string().nullable(),
|
|
cheques: z.number(),
|
|
vouchers: z.number(),
|
|
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.string().refine((val) => dt(val).isValid()),
|
|
currencyCode: z.nativeEnum(CurrencyEnum),
|
|
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(""),
|
|
roomPoints: z.number(),
|
|
roomPointType: z
|
|
.enum(["Scandic", "EuroBonus"])
|
|
.nullable()
|
|
.default(null)
|
|
.catch(null),
|
|
roomPrice: z.number(),
|
|
roomTypeCode: z.string().default(""),
|
|
totalPoints: z.number(),
|
|
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,
|
|
refId: calculateRefId(
|
|
data.attributes.confirmationNumber,
|
|
data.attributes.guest.lastName
|
|
),
|
|
linkedReservations: data.attributes.linkedReservations.map(
|
|
(linkedReservation) => {
|
|
/**
|
|
* We lazy load linked reservations in the client.
|
|
* The problem is that we need to load the reservation in order to
|
|
* calculate the refId for the reservation as the refId uses the guest's
|
|
* lastname in it. Ideally we should pass a promise to the React
|
|
* component that uses `use()` to resolve it. But right now we use tRPC
|
|
* in the client. That tRPC endpoint only uses the confirmationNumber
|
|
* from the refId. So that means we can pass whatever as the lastname
|
|
* here, because it is actually never read. We should change this ASAP.
|
|
*/
|
|
return {
|
|
...linkedReservation,
|
|
refId: calculateRefId(
|
|
linkedReservation.confirmationNumber,
|
|
"" // TODO: Empty lastname here, see comment above
|
|
),
|
|
}
|
|
}
|
|
),
|
|
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" ||
|
|
p.code === BreakfastPackageEnum.ANCILLARY_REGULAR_BREAKFAST
|
|
)
|
|
) && data.attributes.reservationStatus !== BookingStatusEnum.Cancelled,
|
|
isCancelable: !!data.links.cancel,
|
|
isModifiable: !!data.links.modify,
|
|
isGuaranteeable: !!data.links.guarantee,
|
|
canModifyAncillaries: !!data.links.addAncillary,
|
|
// Typo from API
|
|
cancellationNumber: data.attributes.cancelationNumber,
|
|
}))
|