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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -43,6 +43,7 @@ export interface Room {
adults: number
bedType: BedTypeSchema | undefined
breakfast: Omit<BreakfastPackage, "requestedPrice"> | false | undefined | null
breakfastChildren?: Omit<BreakfastPackage, "requestedPrice"> | null
breakfastIncluded: boolean
childrenInRoom: Child[] | undefined
packages: Packages | null
@@ -182,6 +183,7 @@ export default function PriceDetailsTable({
<Breakfast
adults={room.adults}
breakfast={room.breakfast}
breakfastChildren={room.breakfastChildren}
breakfastIncluded={room.breakfastIncluded}
childrenInRoom={room.childrenInRoom}
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 { 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(
childrenAges: number[],
childBedPreferences: BookingConfirmation["booking"]["childBedPreferences"]
): Child[] {
return childBedPreferences.map((preference, index) => ({
age: childrenAges[index],
bed: bedTypeToMapEnum[preference.bedType] ?? ChildBedMapEnum.UNKNOWN,
}))
const lookup = childBedPreferences.reduce(
(preferences, preference) => {
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
breakfast: Omit<BreakfastPackage, "requestedPrice"> | null
breakfastChildren: Omit<BreakfastPackage, "requestedPrice"> | null
childrenAsString: string
childrenInRoom: Child[]
isCancelled: boolean