feat: add support for bought children breakfast package

This commit is contained in:
Simon Emanuelsson
2025-05-13 11:30:49 +02:00
committed by Simon.Emanuelsson
parent aceb88cb1a
commit b7c78a53b5
9 changed files with 229 additions and 68 deletions

View File

@@ -77,7 +77,7 @@ export default function PriceDetails({
title: `${selectedAncillary.title} / ${intl.formatMessage({ title: `${selectedAncillary.title} / ${intl.formatMessage({
defaultMessage: "Adult", defaultMessage: "Adult",
})}`, })}`,
totalPrice: breakfastData.priceAdult * breakfastData.nrOfAdults, totalPrice: breakfastData.priceAdult,
currency: breakfastData.currency, currency: breakfastData.currency,
quantityWithCard: breakfastData.nrOfAdults * breakfastData.nrOfNights, quantityWithCard: breakfastData.nrOfAdults * breakfastData.nrOfNights,
}, },
@@ -88,7 +88,7 @@ export default function PriceDetails({
title: `${selectedAncillary.title} / ${intl.formatMessage({ title: `${selectedAncillary.title} / ${intl.formatMessage({
defaultMessage: "Children", defaultMessage: "Children",
})} 4-12`, })} 4-12`,
totalPrice: breakfastData.priceChild * breakfastData.nrOfPayingChildren, totalPrice: breakfastData.priceChild,
currency: breakfastData.currency, currency: breakfastData.currency,
quantityWithCard: quantityWithCard:
breakfastData.nrOfPayingChildren * breakfastData.nrOfNights, breakfastData.nrOfPayingChildren * breakfastData.nrOfNights,

View File

@@ -511,20 +511,28 @@ function calculateBreakfastData(
return null return null
} }
const [nrOfPayingChildren, nrOfFreeChildren] = childrenAges.reduce( const { nrOfPayingChildren, nrOfFreeChildren } = childrenAges.reduce(
(acc, curr) => (curr >= 4 ? [acc[0] + 1, acc[1]] : [acc[0], acc[1] + 1]), (total, childAge) => {
[0, 0] if (childAge >= 4) {
total.nrOfPayingChildren = total.nrOfPayingChildren + 1
} else {
total.nrOfFreeChildren = total.nrOfFreeChildren + 1
}
return total
},
{ nrOfPayingChildren: 0, nrOfFreeChildren: 0 }
) )
const priceAdult = packages?.find( const adultPackage = packages?.find(
(p) => p.code === BreakfastPackageEnum.ANCILLARY_REGULAR_BREAKFAST (p) => p.code === BreakfastPackageEnum.ANCILLARY_REGULAR_BREAKFAST
)?.localPrice.price )
const priceChild = packages?.find( const childPackage = packages?.find(
(p) => p.code === BreakfastPackageEnum.ANCILLARY_CHILD_PAYING_BREAKFAST (p) => p.code === BreakfastPackageEnum.ANCILLARY_CHILD_PAYING_BREAKFAST
)?.localPrice.price )
const currency = packages?.find( const priceAdult = adultPackage?.localPrice.price
(p) => p.code === BreakfastPackageEnum.ANCILLARY_REGULAR_BREAKFAST const priceChild = childPackage?.localPrice.price
)?.localPrice.currency const currency =
adultPackage?.localPrice.currency ?? childPackage?.localPrice.currency
if ( if (
typeof priceAdult !== "number" || typeof priceAdult !== "number" ||

View File

@@ -107,33 +107,34 @@ export function Ancillaries({
return undefined return undefined
} }
const breakfastPackage = packages?.find( const breakfastPackageAdults = packages?.find(
(p) => p.code === BreakfastPackageEnum.ANCILLARY_REGULAR_BREAKFAST (p) => p.code === BreakfastPackageEnum.ANCILLARY_REGULAR_BREAKFAST
) )
const breakfastAncillary: SelectedAncillary | undefined = breakfastPackage const breakfastAncillary: SelectedAncillary | undefined =
? { breakfastPackageAdults
description: intl.formatMessage({ ? {
defaultMessage: "Buffet", description: intl.formatMessage({
}), defaultMessage: "Buffet",
id: breakfastPackage.code, }),
title: intl.formatMessage({ id: breakfastPackageAdults.code,
defaultMessage: "Breakfast", title: intl.formatMessage({
}), defaultMessage: "Breakfast",
price: { }),
currency: breakfastPackage.localPrice.currency, price: {
total: breakfastPackage.localPrice.totalPrice, currency: breakfastPackageAdults.localPrice.currency,
}, total: breakfastPackageAdults.localPrice.totalPrice,
// TODO: Change this to the correct URL, whatever that is },
imageUrl: // TODO: Change this to the correct URL, whatever that is
"https://images.scandichotels.com/publishedmedia/inyre69evkpzgtygjnvp/Breakfast_-_Scandic_Sweden_-_Free_to_use.jpg", imageUrl:
requiresDeliveryTime: false, "https://images.scandichotels.com/publishedmedia/inyre69evkpzgtygjnvp/Breakfast_-_Scandic_Sweden_-_Free_to_use.jpg",
loyaltyCode: undefined, requiresDeliveryTime: false,
points: undefined, loyaltyCode: undefined,
hotelId: Number(booking.hotelId), points: undefined,
categoryName: "Food", hotelId: Number(booking.hotelId),
} categoryName: "Food",
: undefined }
: undefined
return breakfastAncillary return breakfastAncillary
}, [ }, [

View File

@@ -10,10 +10,13 @@ import Row from "./Row"
export default function Breakfast() { export default function Breakfast() {
const intl = useIntl() const intl = useIntl()
const { breakfast, rateDefinition } = useMyStayStore((state) => ({ const { breakfast, breakfastChildren, rateDefinition } = useMyStayStore(
breakfast: state.bookedRoom.breakfast, (state) => ({
rateDefinition: state.bookedRoom.rateDefinition, breakfast: state.bookedRoom.breakfast,
})) breakfastChildren: state.bookedRoom.breakfastChildren,
rateDefinition: state.bookedRoom.rateDefinition,
})
)
let breakfastPrice = intl.formatMessage({ let breakfastPrice = intl.formatMessage({
defaultMessage: "No breakfast", defaultMessage: "No breakfast",
@@ -23,9 +26,10 @@ export default function Breakfast() {
defaultMessage: "Included", defaultMessage: "Included",
}) })
} else if (breakfast) { } else if (breakfast) {
const childPrice = breakfastChildren?.localPrice.totalPrice || 0
breakfastPrice = formatPrice( breakfastPrice = formatPrice(
intl, intl,
breakfast.localPrice.totalPrice, breakfast.localPrice.totalPrice + childPrice,
breakfast.localPrice.currency breakfast.localPrice.currency
) )
} }

View File

@@ -31,30 +31,50 @@ export function mapRoomDetails({
.startOf("day") .startOf("day")
.diff(dt(booking.checkInDate).startOf("day"), "days") .diff(dt(booking.checkInDate).startOf("day"), "days")
const validBreakfastPackages: string[] = [ const validBreakfastPackagesAdults: string[] = [
BreakfastPackageEnum.REGULAR_BREAKFAST, BreakfastPackageEnum.REGULAR_BREAKFAST,
BreakfastPackageEnum.ANCILLARY_CHILD_PAYING_BREAKFAST,
BreakfastPackageEnum.ANCILLARY_REGULAR_BREAKFAST, BreakfastPackageEnum.ANCILLARY_REGULAR_BREAKFAST,
] ]
const breakfastPackage = booking.packages.find((pkg) => const breakfastPackageAdults = booking.packages.find((pkg) =>
validBreakfastPackages.includes(pkg.code) validBreakfastPackagesAdults.includes(pkg.code)
) )
// We don't get `requestedPrice` in packages // We don't get `requestedPrice` in packages
const breakfast: Omit<BreakfastPackage, "requestedPrice"> | null = const breakfast: Omit<BreakfastPackage, "requestedPrice"> | null =
breakfastPackage breakfastPackageAdults
? { ? {
code: breakfastPackage.code, code: breakfastPackageAdults.code,
description: breakfastPackage.description, description: breakfastPackageAdults.description,
localPrice: { localPrice: {
currency: breakfastPackage.currency, currency: breakfastPackageAdults.currency,
price: breakfastPackage.unitPrice, price: breakfastPackageAdults.unitPrice,
totalPrice: breakfastPackage.totalPrice, totalPrice: breakfastPackageAdults.totalPrice,
}, },
packageType: PackageTypeEnum.BreakfastAdult, packageType: PackageTypeEnum.BreakfastAdult,
} }
: null : null
const validBreakfastPackagesChildren: string[] = [
BreakfastPackageEnum.ANCILLARY_CHILD_PAYING_BREAKFAST,
]
const breakfastPackageChildren = booking.packages.find((pkg) =>
validBreakfastPackagesChildren.includes(pkg.code)
)
// We don't get `requestedPrice` in packages
const breakfastChildren: Omit<BreakfastPackage, "requestedPrice"> | null =
breakfastPackageChildren
? {
code: breakfastPackageChildren.code,
description: breakfastPackageChildren.description,
localPrice: {
currency: breakfastPackageChildren.currency,
price: breakfastPackageChildren.unitPrice,
totalPrice: breakfastPackageChildren.totalPrice,
},
packageType: PackageTypeEnum.BreakfastChildren,
}
: null
const isCancelled = booking.reservationStatus === BookingStatusEnum.Cancelled const isCancelled = booking.reservationStatus === BookingStatusEnum.Cancelled
const childrenAsString = formatChildBedPreferences({ const childrenAsString = formatChildBedPreferences({
@@ -120,6 +140,7 @@ export function mapRoomDetails({
}, },
bookingCode: booking.bookingCode, bookingCode: booking.bookingCode,
breakfast, breakfast,
breakfastChildren,
canChangeDate: booking.canChangeDate, canChangeDate: booking.canChangeDate,
cancellationNumber: booking.cancellationNumber, cancellationNumber: booking.cancellationNumber,
checkInDate: booking.checkInDate, checkInDate: booking.checkInDate,

View File

@@ -13,6 +13,7 @@ import type { Child } from "@/types/components/hotelReservation/selectRate/selec
interface BreakfastProps { interface BreakfastProps {
adults: number adults: number
breakfast: Omit<BreakfastPackage, "requestedPrice"> | false | undefined | null breakfast: Omit<BreakfastPackage, "requestedPrice"> | false | undefined | null
breakfastChildren: Omit<BreakfastPackage, "requestedPrice"> | null | undefined
breakfastIncluded: boolean breakfastIncluded: boolean
childrenInRoom: Child[] | undefined childrenInRoom: Child[] | undefined
currency: string currency: string
@@ -22,8 +23,9 @@ interface BreakfastProps {
export default function Breakfast({ export default function Breakfast({
adults, adults,
breakfast, breakfast,
breakfastChildren,
breakfastIncluded, breakfastIncluded,
childrenInRoom, childrenInRoom = [],
currency, currency,
nights, nights,
}: BreakfastProps) { }: BreakfastProps) {
@@ -70,17 +72,56 @@ export default function Breakfast({
return null return null
} }
const adultPricePerNight = breakfast.localPrice.price * adults
const breakfastAdultsPricePerNight = formatPrice( const breakfastAdultsPricePerNight = formatPrice(
intl, intl,
breakfast.localPrice.price * adults, adultPricePerNight,
breakfast.localPrice.currency breakfast.localPrice.currency
) )
const breakfastAdultsTotalPrice = formatPrice(
const { payingChildren, freeChildren } = childrenInRoom.reduce(
(total, child) => {
if (child.age >= 4) {
total.payingChildren = total.payingChildren + 1
} else {
total.freeChildren = total.freeChildren + 1
}
return total
},
{ payingChildren: 0, freeChildren: 0 }
)
const childrenPrice = breakfastChildren?.localPrice.price || 0
const childrenPricePerNight = childrenPrice * payingChildren
const childCurrency =
breakfastChildren?.localPrice.currency ?? breakfast.localPrice.currency
const breakfastChildrenPricePerNight = formatPrice(
intl, intl,
breakfast.localPrice.price * adults * nights, childrenPricePerNight,
childCurrency
)
const totalAdultsPrice = adultPricePerNight * nights
const totalChildrenPrice = childrenPricePerNight * nights
const breakfastTotalPrice = formatPrice(
intl,
totalAdultsPrice + totalChildrenPrice,
breakfast.localPrice.currency breakfast.localPrice.currency
) )
const freeChildrenMsg = intl.formatMessage(
{
defaultMessage:
"Breakfast ({totalChildren, plural, one {# child} other {# children}}) x {totalBreakfasts}",
},
{
totalChildren: freeChildren,
totalBreakfasts: nights,
}
)
return ( return (
<Tbody border> <Tbody border>
<RegularRow <RegularRow
@@ -93,7 +134,28 @@ export default function Breakfast({
)} )}
value={breakfastAdultsPricePerNight} value={breakfastAdultsPricePerNight}
/> />
{childrenInRoom?.length ? ( {breakfastChildren ? (
<RegularRow
label={intl.formatMessage(
{
defaultMessage:
"Breakfast ({totalChildren, plural, one {# child} other {# children}}) x {totalBreakfasts}",
},
{
totalChildren: payingChildren,
totalBreakfasts: nights,
}
)}
value={breakfastChildrenPricePerNight}
/>
) : null}
{breakfastChildren && freeChildren ? (
<RegularRow
label={`${freeChildrenMsg} (0-3)`}
value={formatPrice(intl, 0, breakfast.localPrice.currency)}
/>
) : null}
{childrenInRoom?.length && !breakfastChildren ? (
<RegularRow <RegularRow
label={intl.formatMessage( label={intl.formatMessage(
{ {
@@ -110,7 +172,7 @@ export default function Breakfast({
) : null} ) : null}
<BoldRow <BoldRow
label={intl.formatMessage({ defaultMessage: "Breakfast charge" })} label={intl.formatMessage({ defaultMessage: "Breakfast charge" })}
value={breakfastAdultsTotalPrice} value={breakfastTotalPrice}
/> />
</Tbody> </Tbody>
) )

View File

@@ -43,6 +43,7 @@ export interface Room {
adults: number adults: number
bedType: BedTypeSchema | undefined bedType: BedTypeSchema | undefined
breakfast: Omit<BreakfastPackage, "requestedPrice"> | false | undefined | null breakfast: Omit<BreakfastPackage, "requestedPrice"> | false | undefined | null
breakfastChildren?: Omit<BreakfastPackage, "requestedPrice"> | null
breakfastIncluded: boolean breakfastIncluded: boolean
childrenInRoom: Child[] | undefined childrenInRoom: Child[] | undefined
packages: Packages | null packages: Packages | null
@@ -182,6 +183,7 @@ export default function PriceDetailsTable({
<Breakfast <Breakfast
adults={room.adults} adults={room.adults}
breakfast={room.breakfast} breakfast={room.breakfast}
breakfastChildren={room.breakfastChildren}
breakfastIncluded={room.breakfastIncluded} breakfastIncluded={room.breakfastIncluded}
childrenInRoom={room.childrenInRoom} childrenInRoom={room.childrenInRoom}
currency={currency} currency={currency}

View File

@@ -2,19 +2,81 @@ import { ChildBedMapEnum } from "@/types/components/bookingWidget/enums"
import type { Child } from "@/types/components/hotelReservation/selectRate/selectRate" import type { Child } from "@/types/components/hotelReservation/selectRate/selectRate"
import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation" import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation"
const bedTypeToMapEnum: Record<string, ChildBedMapEnum> = {
Crib: ChildBedMapEnum.IN_CRIB,
ExtraBed: ChildBedMapEnum.IN_EXTRA_BED,
ParentsBed: ChildBedMapEnum.IN_ADULTS_BED,
Unknown: ChildBedMapEnum.UNKNOWN,
}
export function convertToChildType( export function convertToChildType(
childrenAges: number[], childrenAges: number[],
childBedPreferences: BookingConfirmation["booking"]["childBedPreferences"] childBedPreferences: BookingConfirmation["booking"]["childBedPreferences"]
): Child[] { ): Child[] {
return childBedPreferences.map((preference, index) => ({ const lookup = childBedPreferences.reduce(
age: childrenAges[index], (preferences, preference) => {
bed: bedTypeToMapEnum[preference.bedType] ?? ChildBedMapEnum.UNKNOWN, preferences[preference.bedType] = preference.quantity
})) return preferences
},
{
Crib: 0,
ExtraBed: 0,
ParentsBed: 0,
Unknown: 0,
}
)
const adultsAndOrCribKids = childrenAges.filter((childAge) => childAge <= 2)
const adultsAndOrExtraBedKids = childrenAges.filter(
(childAge) => childAge >= 3 && childAge <= 5
)
const extraBedKids = childrenAges.filter((childAge) => childAge >= 6)
const cribKids = adultsAndOrCribKids.map((age) => {
if (lookup.Crib) {
lookup.Crib = lookup.Crib - 1
return {
age,
bed: ChildBedMapEnum.IN_CRIB,
}
}
lookup.ParentsBed = lookup.ParentsBed - 1
return {
age,
bed: ChildBedMapEnum.IN_ADULTS_BED,
}
})
const adultOrExtraKids = adultsAndOrExtraBedKids.map((age) => {
if (lookup.ParentsBed) {
lookup.ParentsBed = lookup.ParentsBed - 1
return {
age,
bed: ChildBedMapEnum.IN_ADULTS_BED,
}
}
if (lookup.ExtraBed) {
lookup.ExtraBed = lookup.ExtraBed - 1
return {
age,
bed: ChildBedMapEnum.IN_EXTRA_BED,
}
}
return {
age,
bed: ChildBedMapEnum.UNKNOWN,
}
})
const extraKids = extraBedKids.map((age) => {
if (lookup.ExtraBed) {
lookup.ExtraBed = lookup.ExtraBed - 1
return {
age,
bed: ChildBedMapEnum.IN_EXTRA_BED,
}
}
return {
age,
bed: ChildBedMapEnum.UNKNOWN,
}
})
return [...cribKids, ...adultOrExtraKids, ...extraKids]
} }

View File

@@ -43,6 +43,7 @@ export type Room = Pick<
> & { > & {
bedType: BedTypeSchema bedType: BedTypeSchema
breakfast: Omit<BreakfastPackage, "requestedPrice"> | null breakfast: Omit<BreakfastPackage, "requestedPrice"> | null
breakfastChildren: Omit<BreakfastPackage, "requestedPrice"> | null
childrenAsString: string childrenAsString: string
childrenInRoom: Child[] childrenInRoom: Child[]
isCancelled: boolean isCancelled: boolean