fix: make sure calculations in booking flow are correct
This commit is contained in:
committed by
Michael Zetterberg
parent
3e0f503314
commit
a222ecfc5c
@@ -0,0 +1,3 @@
|
||||
export default function ConfirmedBookingSlot() {
|
||||
return null
|
||||
}
|
||||
@@ -75,6 +75,9 @@ export default function PriceDetailsModal() {
|
||||
return null
|
||||
}
|
||||
|
||||
const checkInDate = dt(fromDate).format("YYYY-MM-DD")
|
||||
const checkOutDate = dt(toDate).format("YYYY-MM-DD")
|
||||
|
||||
const bookingTotal = rooms.reduce(
|
||||
(acc, room) => {
|
||||
if (room) {
|
||||
@@ -89,7 +92,7 @@ export default function PriceDetailsModal() {
|
||||
{ price: 0, priceExVat: 0, vatAmount: 0 }
|
||||
)
|
||||
|
||||
const diff = dt(toDate).diff(fromDate, "days")
|
||||
const diff = dt(checkOutDate).diff(checkInDate, "days")
|
||||
const nights = intl.formatMessage(
|
||||
{ id: "{totalNights, plural, one {# night} other {# nights}}" },
|
||||
{ totalNights: diff }
|
||||
|
||||
@@ -163,21 +163,23 @@ export default function ReceiptRoom({
|
||||
</Body>
|
||||
</div>
|
||||
) : null}
|
||||
<div className={styles.entry}>
|
||||
<Body>{intl.formatMessage({ id: "Breakfast buffet" })}</Body>
|
||||
{(room.rateDefinition.breakfastIncluded ?? room.breakfastIncluded) ? (
|
||||
<Body color="red">{intl.formatMessage({ id: "Included" })}</Body>
|
||||
) : null}
|
||||
{room.breakfast ? (
|
||||
<Body color="uiTextHighContrast">
|
||||
{formatPrice(
|
||||
intl,
|
||||
room.breakfast.totalPrice * room.adults,
|
||||
room.breakfast.currency
|
||||
)}
|
||||
</Body>
|
||||
) : null}
|
||||
</div>
|
||||
{room.breakfast || room.breakfastIncluded ? (
|
||||
<div className={styles.entry}>
|
||||
<Body>{intl.formatMessage({ id: "Breakfast buffet" })}</Body>
|
||||
{(room.rateDefinition.breakfastIncluded ?? room.breakfastIncluded) ? (
|
||||
<Body color="red">{intl.formatMessage({ id: "Included" })}</Body>
|
||||
) : null}
|
||||
{room.breakfast ? (
|
||||
<Body color="uiTextHighContrast">
|
||||
{formatPrice(
|
||||
intl,
|
||||
room.breakfast.totalPrice,
|
||||
room.breakfast.currency
|
||||
)}
|
||||
</Body>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</article>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
type TrackingSDKPaymentInfo,
|
||||
} from "@/types/components/tracking"
|
||||
import { BreakfastPackageEnum } from "@/types/enums/breakfast"
|
||||
import { RateEnum } from "@/types/enums/rate"
|
||||
import type { Room } from "@/types/stores/booking-confirmation"
|
||||
import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation"
|
||||
import type { RateDefinition } from "@/types/trpc/routers/hotel/roomAvailability"
|
||||
@@ -19,11 +20,11 @@ import type { Lang } from "@/constants/languages"
|
||||
function getRate(cancellationRule: RateDefinition["cancellationRule"] | null) {
|
||||
switch (cancellationRule) {
|
||||
case "CancellableBefore6PM":
|
||||
return "flex"
|
||||
return RateEnum.flex
|
||||
case "Changeable":
|
||||
return "change"
|
||||
return RateEnum.change
|
||||
case "NotCancellable":
|
||||
return "save"
|
||||
return RateEnum.save
|
||||
default:
|
||||
return "-"
|
||||
}
|
||||
|
||||
@@ -42,13 +42,11 @@ export default function Breakfast() {
|
||||
: undefined
|
||||
|
||||
const methods = useForm<BreakfastFormSchema>({
|
||||
defaultValues: breakfastSelection
|
||||
? { breakfast: breakfastSelection }
|
||||
: undefined,
|
||||
criteriaMode: "all",
|
||||
mode: "all",
|
||||
resolver: zodResolver(breakfastFormSchema),
|
||||
reValidateMode: "onChange",
|
||||
values: breakfastSelection ? { breakfast: breakfastSelection } : undefined,
|
||||
})
|
||||
|
||||
const onSubmit = useCallback(
|
||||
|
||||
@@ -28,6 +28,14 @@ import styles from "./ui.module.css"
|
||||
import { ChildBedMapEnum } from "@/types/components/bookingWidget/enums"
|
||||
import type { RoomRate } from "@/types/components/hotelReservation/enterDetails/details"
|
||||
import type { EnterDetailsSummaryProps } from "@/types/components/hotelReservation/summary"
|
||||
import { CurrencyEnum } from "@/types/enums/currency"
|
||||
|
||||
const notDisplayableCurrencies = [
|
||||
CurrencyEnum.CC,
|
||||
CurrencyEnum.POINTS,
|
||||
CurrencyEnum.Voucher,
|
||||
CurrencyEnum.Unknown,
|
||||
]
|
||||
|
||||
export default function SummaryUI({
|
||||
booking,
|
||||
@@ -81,6 +89,10 @@ export default function SummaryUI({
|
||||
"redemption" in roomOneRoomRate ||
|
||||
"voucher" in roomOneRoomRate
|
||||
|
||||
const isSameCurrency = totalPrice.requested
|
||||
? totalPrice.requested.currency === totalPrice.local.currency
|
||||
: false
|
||||
|
||||
return (
|
||||
<section className={styles.summary}>
|
||||
<header className={styles.header}>
|
||||
@@ -160,6 +172,10 @@ export default function SummaryUI({
|
||||
guestsParts.push(childrenMsg)
|
||||
}
|
||||
|
||||
const hideBedCurrency = notDisplayableCurrencies.includes(
|
||||
room.roomPrice.perStay.local.currency
|
||||
)
|
||||
|
||||
return (
|
||||
<Fragment key={idx}>
|
||||
<div
|
||||
@@ -262,7 +278,9 @@ export default function SummaryUI({
|
||||
{formatPrice(
|
||||
intl,
|
||||
0,
|
||||
room.roomPrice.perStay.local.currency
|
||||
hideBedCurrency
|
||||
? ""
|
||||
: room.roomPrice.perStay.local.currency
|
||||
)}
|
||||
</Body>
|
||||
</div>
|
||||
@@ -418,7 +436,7 @@ export default function SummaryUI({
|
||||
)}
|
||||
</Caption>
|
||||
) : null}
|
||||
{totalPrice.requested && !isSpecialRate && (
|
||||
{totalPrice.requested && !isSpecialRate && !isSameCurrency && (
|
||||
<Caption color="uiTextMediumContrast">
|
||||
{intl.formatMessage(
|
||||
{ id: "Approx. {value}" },
|
||||
|
||||
@@ -31,7 +31,7 @@ import styles from "./rateSummary.module.css"
|
||||
import type { Price } from "@/types/components/hotelReservation/price"
|
||||
import type { RateSummaryProps } from "@/types/components/hotelReservation/selectRate/rateSummary"
|
||||
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
|
||||
import type { Rate } from "@/types/components/hotelReservation/selectRate/selectRate"
|
||||
import { RateEnum } from "@/types/enums/rate"
|
||||
import { RateTypeEnum } from "@/types/enums/rateType"
|
||||
|
||||
export default function RateSummary({ isUserLoggedIn }: RateSummaryProps) {
|
||||
@@ -111,13 +111,13 @@ export default function RateSummary({ isUserLoggedIn }: RateSummaryProps) {
|
||||
const payLater = intl.formatMessage({ id: "Pay later" })
|
||||
const payNow = intl.formatMessage({ id: "Pay now" })
|
||||
|
||||
function getRateDetails(rate: Rate["rate"]) {
|
||||
function getRateDetails(rate: RateEnum) {
|
||||
switch (rate) {
|
||||
case "change":
|
||||
case RateEnum.change:
|
||||
return `${freeBooking}, ${payNow}`
|
||||
case "flex":
|
||||
case RateEnum.flex:
|
||||
return `${freeCancelation}, ${payLater}`
|
||||
case "save":
|
||||
case RateEnum.save:
|
||||
default:
|
||||
return `${nonRefundable}, ${payNow}`
|
||||
}
|
||||
@@ -243,19 +243,29 @@ export default function RateSummary({ isUserLoggedIn }: RateSummaryProps) {
|
||||
total,
|
||||
{ features, packages: roomPackages, product }
|
||||
) => {
|
||||
if (!("member" in product) || !product.member) {
|
||||
return total
|
||||
}
|
||||
const memberPrice =
|
||||
product.member.localPrice.pricePerStay
|
||||
if (!memberPrice) {
|
||||
const memberExists =
|
||||
"member" in product && product.member
|
||||
const publicExists =
|
||||
"public" in product && product.public
|
||||
if (!memberExists) {
|
||||
if (!publicExists) {
|
||||
return total
|
||||
}
|
||||
}
|
||||
|
||||
const price =
|
||||
product.member?.localPrice.pricePerStay ||
|
||||
product.public?.localPrice.pricePerStay
|
||||
|
||||
if (!price) {
|
||||
return total
|
||||
}
|
||||
|
||||
const hasSelectedPetRoom = roomPackages.includes(
|
||||
RoomPackageCodeEnum.PET_ROOM
|
||||
)
|
||||
if (!hasSelectedPetRoom) {
|
||||
return total + memberPrice
|
||||
return total + price
|
||||
}
|
||||
const isPetRoom = features.find(
|
||||
(feature) =>
|
||||
@@ -265,7 +275,7 @@ export default function RateSummary({ isUserLoggedIn }: RateSummaryProps) {
|
||||
isPetRoom && petRoomPackage
|
||||
? Number(petRoomPackage.localPrice.totalPrice)
|
||||
: 0
|
||||
return total + memberPrice + petRoomPrice
|
||||
return total + price + petRoomPrice
|
||||
},
|
||||
0
|
||||
),
|
||||
|
||||
@@ -34,24 +34,32 @@ export function calculateTotalPrice(
|
||||
const isPetRoom = room.features.find(
|
||||
(feature) => feature.code === RoomPackageCodeEnum.PET_ROOM
|
||||
)
|
||||
let petRoomPrice = 0
|
||||
let petRoomPriceLocal = 0
|
||||
if (
|
||||
petRoomPackage &&
|
||||
isPetRoom &&
|
||||
room.packages.includes(RoomPackageCodeEnum.PET_ROOM)
|
||||
) {
|
||||
petRoomPrice = Number(petRoomPackage.localPrice.totalPrice)
|
||||
petRoomPriceLocal = Number(petRoomPackage.localPrice.totalPrice)
|
||||
}
|
||||
let petRoomPriceRequested = 0
|
||||
if (
|
||||
petRoomPackage &&
|
||||
isPetRoom &&
|
||||
room.packages.includes(RoomPackageCodeEnum.PET_ROOM)
|
||||
) {
|
||||
petRoomPriceRequested = Number(petRoomPackage.requestedPrice.totalPrice)
|
||||
}
|
||||
|
||||
total.local.currency = rate.localPrice.currency
|
||||
total.local.price =
|
||||
total.local.price + rate.localPrice.pricePerStay + petRoomPrice
|
||||
total.local.price + rate.localPrice.pricePerStay + petRoomPriceLocal
|
||||
|
||||
if (rate.localPrice.regularPricePerStay) {
|
||||
total.local.regularPrice =
|
||||
(total.local.regularPrice || 0) +
|
||||
rate.localPrice.regularPricePerStay +
|
||||
petRoomPrice
|
||||
petRoomPriceLocal
|
||||
}
|
||||
|
||||
if (rate.requestedPrice) {
|
||||
@@ -69,13 +77,13 @@ export function calculateTotalPrice(
|
||||
total.requested.price =
|
||||
total.requested.price +
|
||||
rate.requestedPrice.pricePerStay +
|
||||
petRoomPrice
|
||||
petRoomPriceRequested
|
||||
|
||||
if (rate.requestedPrice.regularPricePerStay) {
|
||||
total.requested.regularPrice =
|
||||
(total.requested.regularPrice || 0) +
|
||||
rate.requestedPrice.regularPricePerStay +
|
||||
petRoomPrice
|
||||
petRoomPriceRequested
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,8 +15,8 @@ import { useRoomContext } from "@/contexts/SelectRate/Room"
|
||||
|
||||
import styles from "./selectedRoomPanel.module.css"
|
||||
|
||||
import type { Rate } from "@/types/components/hotelReservation/selectRate/selectRate"
|
||||
import { CurrencyEnum } from "@/types/enums/currency"
|
||||
import { RateEnum } from "@/types/enums/rate"
|
||||
|
||||
export default function SelectedRoomPanel() {
|
||||
const intl = useIntl()
|
||||
@@ -43,13 +43,13 @@ export default function SelectedRoomPanel() {
|
||||
const payLater = intl.formatMessage({ id: "Pay later" })
|
||||
const payNow = intl.formatMessage({ id: "Pay now" })
|
||||
|
||||
function getRateTitle(rate: Rate["rate"]) {
|
||||
function getRateTitle(rate: RateEnum) {
|
||||
switch (rate) {
|
||||
case "change":
|
||||
case RateEnum.change:
|
||||
return `${freeBooking}, ${payNow}`
|
||||
case "flex":
|
||||
case RateEnum.flex:
|
||||
return `${freeCancelation}, ${payLater}`
|
||||
case "save":
|
||||
case RateEnum.save:
|
||||
default:
|
||||
return `${nonRefundable}, ${payNow}`
|
||||
}
|
||||
|
||||
@@ -27,5 +27,5 @@ export function getBreakfastMessage(
|
||||
return msgs.notIncluded
|
||||
}
|
||||
|
||||
return msgs.noSelection
|
||||
return msgs.notIncluded
|
||||
}
|
||||
|
||||
@@ -4,8 +4,6 @@ import { useIntl } from "react-intl"
|
||||
import CampaignRateCard from "@scandic-hotels/design-system/CampaignRateCard"
|
||||
import NoRateAvailableCard from "@scandic-hotels/design-system/NoRateAvailableCard"
|
||||
|
||||
import { useRatesStore } from "@/stores/select-rate"
|
||||
|
||||
import { useRoomContext } from "@/contexts/SelectRate/Room"
|
||||
import useRateTitles from "@/hooks/booking/useRateTitles"
|
||||
|
||||
@@ -30,7 +28,6 @@ export default function Campaign({
|
||||
const intl = useIntl()
|
||||
const { roomAvailability, roomNr, selectedFilter, selectedRate } =
|
||||
useRoomContext()
|
||||
const bookingCode = useRatesStore((state) => state.booking.bookingCode)
|
||||
const rateTitles = useRateTitles()
|
||||
|
||||
let isCampaignRate = false
|
||||
@@ -85,11 +82,11 @@ export default function Campaign({
|
||||
)
|
||||
|
||||
let bannerText = intl.formatMessage({ id: "Campaign" })
|
||||
if (bookingCode) {
|
||||
bannerText = bookingCode
|
||||
if (product.bookingCode) {
|
||||
bannerText = product.bookingCode
|
||||
}
|
||||
|
||||
if (product.rateDefinition?.breakfastIncluded) {
|
||||
if (product.rateDefinition.breakfastIncluded) {
|
||||
bannerText = `${bannerText} ∙ ${intl.formatMessage({ id: "Breakfast included" })}`
|
||||
} else {
|
||||
bannerText = `${bannerText} ∙ ${intl.formatMessage({ id: "Breakfast excluded" })}`
|
||||
|
||||
@@ -38,7 +38,7 @@ export default function Code({
|
||||
|
||||
return code.map((product) => {
|
||||
let bannerText = ""
|
||||
if (product.breakfastIncluded) {
|
||||
if (product.rateDefinition.breakfastIncluded) {
|
||||
bannerText = `${bookingCode} ∙ ${intl.formatMessage({ id: "Breakfast included" })}`
|
||||
} else {
|
||||
bannerText = `${bookingCode} ∙ ${intl.formatMessage({ id: "Breakfast excluded" })}`
|
||||
@@ -141,12 +141,13 @@ export default function Code({
|
||||
petRoomPackage
|
||||
)
|
||||
|
||||
const comparisonRate = regularPricePerNight.totalPrice
|
||||
? {
|
||||
price: regularPricePerNight.totalPrice,
|
||||
unit: localPrice.currency,
|
||||
}
|
||||
: undefined
|
||||
const comparisonRate =
|
||||
+regularPricePerNight.totalPrice > +pricePerNight.totalPrice
|
||||
? {
|
||||
price: regularPricePerNight.totalPrice,
|
||||
unit: localPrice.currency,
|
||||
}
|
||||
: undefined
|
||||
|
||||
const isSelected = isSelectedPriceProduct(
|
||||
product,
|
||||
|
||||
@@ -72,7 +72,7 @@ export default function Redemptions({
|
||||
|
||||
const notEnoughPoints = rates.every((rate) => rate.isDisabled)
|
||||
const firstRedemption = redemptions[0]
|
||||
const bannerText = firstRedemption.breakfastIncluded
|
||||
const bannerText = firstRedemption.rateDefinition.breakfastIncluded
|
||||
? `${rewardNight} ∙ ${breakfastIncluded}`
|
||||
: `${rewardNight} ∙ ${breakfastExcluded}`
|
||||
|
||||
|
||||
@@ -114,7 +114,7 @@ export default function Regular({
|
||||
unit: `${standard!.localPrice.currency}/${night}`,
|
||||
}
|
||||
|
||||
if (standardPricePerNight.totalRequestedPrice) {
|
||||
if (standardPricePerNight.totalRequestedPrice && !isUserLoggedIn) {
|
||||
approximateStandardRatePrice = standardPricePerNight.totalRequestedPrice
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,34 +10,29 @@ export function isSelectedPriceProduct(
|
||||
selectedRate: SelectedRate | null,
|
||||
roomTypeCode: string
|
||||
) {
|
||||
if (!selectedRate) {
|
||||
if (!selectedRate || roomTypeCode !== selectedRate.roomTypeCode) {
|
||||
return false
|
||||
}
|
||||
|
||||
const { member, public: standard } = product
|
||||
let selectedRateMember: PriceProduct["member"] = null
|
||||
if ("member" in selectedRate.product) {
|
||||
selectedRateMember = selectedRate.product.member
|
||||
let isSelected = false
|
||||
if (
|
||||
"member" in selectedRate.product &&
|
||||
selectedRate.product.member &&
|
||||
member
|
||||
) {
|
||||
isSelected = selectedRate.product.member.rateCode === member.rateCode
|
||||
}
|
||||
|
||||
let selectedRatePublic: PriceProduct["public"] = null
|
||||
if ("public" in selectedRate.product) {
|
||||
selectedRatePublic = selectedRate.product.public
|
||||
if (
|
||||
"public" in selectedRate.product &&
|
||||
selectedRate.product.public &&
|
||||
standard
|
||||
) {
|
||||
isSelected = selectedRate.product.public.rateCode === standard.rateCode
|
||||
}
|
||||
|
||||
const selectedRateIsMember =
|
||||
member &&
|
||||
selectedRateMember &&
|
||||
member.rateCode === selectedRateMember.rateCode
|
||||
|
||||
const selectedRateIsPublic =
|
||||
standard &&
|
||||
selectedRatePublic &&
|
||||
standard.rateCode === selectedRatePublic.rateCode
|
||||
return !!(
|
||||
(selectedRateIsMember || selectedRateIsPublic) &&
|
||||
selectedRate.roomTypeCode === roomTypeCode
|
||||
)
|
||||
return isSelected
|
||||
}
|
||||
|
||||
export function isSelectedCorporateCheque(
|
||||
|
||||
@@ -42,6 +42,15 @@ export default function RoomProvider({
|
||||
const dontShowRegularRates =
|
||||
hasRedemptionRates || hasCorporateChequeOrVoucherRates
|
||||
|
||||
// Since input would be the same on single room as already
|
||||
// done in useRoomsAvailability hook, data is already present
|
||||
// and thus runs the appendRegularRates updater resulting in
|
||||
// duplicate data
|
||||
const enabled = !!(
|
||||
booking.bookingCode &&
|
||||
selectedFilter === BookingCodeFilterEnum.All &&
|
||||
!dontShowRegularRates
|
||||
)
|
||||
// Extra query needed to fetch regular rates upon user
|
||||
// selecting to view all rates.
|
||||
// TODO: Setup route to handle singular availability call
|
||||
@@ -58,22 +67,18 @@ export default function RoomProvider({
|
||||
roomStayStartDate: booking.fromDate,
|
||||
},
|
||||
{
|
||||
enabled: !!(
|
||||
booking.bookingCode &&
|
||||
selectedFilter === BookingCodeFilterEnum.All &&
|
||||
!dontShowRegularRates
|
||||
),
|
||||
enabled,
|
||||
}
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (isFetched && !isFetching && data?.length) {
|
||||
if (isFetched && !isFetching && data?.length && enabled) {
|
||||
const regularRates = data[0]
|
||||
if ("roomConfigurations" in regularRates) {
|
||||
appendRegularRates(regularRates.roomConfigurations)
|
||||
}
|
||||
}
|
||||
}, [appendRegularRates, data, isFetched, isFetching])
|
||||
}, [appendRegularRates, data, enabled, isFetched, isFetching])
|
||||
|
||||
return (
|
||||
<RoomContext.Provider
|
||||
|
||||
@@ -30,6 +30,7 @@ import { rateDefinitionSchema } from "./schemas/roomAvailability/rateDefinition"
|
||||
|
||||
import { AvailabilityEnum } from "@/types/components/hotelReservation/selectHotel/selectHotel"
|
||||
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
|
||||
import { RateEnum } from "@/types/enums/rate"
|
||||
import { RateTypeEnum } from "@/types/enums/rateType"
|
||||
import type {
|
||||
AdditionalData,
|
||||
@@ -112,11 +113,11 @@ export const hotelsAvailabilitySchema = z.object({
|
||||
function getRate(rate: RateDefinition) {
|
||||
switch (rate.cancellationRule) {
|
||||
case "CancellableBefore6PM":
|
||||
return "flex"
|
||||
return RateEnum.flex
|
||||
case "Changeable":
|
||||
return "change"
|
||||
return RateEnum.change
|
||||
case "NotCancellable":
|
||||
return "save"
|
||||
return RateEnum.save
|
||||
default:
|
||||
console.info(
|
||||
`Unknown cancellationRule [${rate.cancellationRule}]. This should never happen!`
|
||||
@@ -276,7 +277,6 @@ export const roomsAvailabilitySchema = z
|
||||
} else {
|
||||
product.bookingCode = undefined
|
||||
}
|
||||
product.breakfastIncluded = rateDefinition.breakfastIncluded
|
||||
product.rate = rate
|
||||
product.rateDefinition = rateDefinition
|
||||
|
||||
@@ -292,7 +292,9 @@ export const roomsAvailabilitySchema = z
|
||||
if ("corporateCheque" in product) {
|
||||
const rateDetails = getRateDetails(product)
|
||||
if (rateDetails) {
|
||||
breakfastIncluded.push(rateDetails.breakfastIncluded)
|
||||
breakfastIncluded.push(
|
||||
rateDetails.rateDefinition.breakfastIncluded
|
||||
)
|
||||
room.code.push({
|
||||
...rateDetails,
|
||||
corporateCheque: product.corporateCheque,
|
||||
@@ -304,7 +306,9 @@ export const roomsAvailabilitySchema = z
|
||||
if ("voucher" in product) {
|
||||
const rateDetails = getRateDetails(product)
|
||||
if (rateDetails) {
|
||||
breakfastIncluded.push(rateDetails.breakfastIncluded)
|
||||
breakfastIncluded.push(
|
||||
rateDetails.rateDefinition.breakfastIncluded
|
||||
)
|
||||
room.code.push({
|
||||
...rateDetails,
|
||||
voucher: product.voucher,
|
||||
@@ -319,7 +323,9 @@ export const roomsAvailabilitySchema = z
|
||||
for (const redemption of product) {
|
||||
const rateDetails = getRateDetails(redemption)
|
||||
if (rateDetails) {
|
||||
breakfastIncluded.push(rateDetails.breakfastIncluded)
|
||||
breakfastIncluded.push(
|
||||
rateDetails.rateDefinition.breakfastIncluded
|
||||
)
|
||||
room.redemptions.push({
|
||||
...redemption,
|
||||
...rateDetails,
|
||||
@@ -336,45 +342,46 @@ export const roomsAvailabilitySchema = z
|
||||
) {
|
||||
const memberRate = product.member
|
||||
const publicRate = product.public
|
||||
const rateCode = publicRate?.rateCode ?? memberRate?.rateCode
|
||||
const rateDetails = getRateDetails(product)
|
||||
const rateDetailsMember = getRateDetails({
|
||||
...product,
|
||||
public: null,
|
||||
})
|
||||
|
||||
if (rateDetails && rateCode) {
|
||||
if (rateDetails) {
|
||||
if (publicRate) {
|
||||
breakfastIncluded.push(
|
||||
rateDetails.rateDefinition.breakfastIncluded
|
||||
)
|
||||
}
|
||||
if (rateDetailsMember) {
|
||||
breakfastIncludedMember.push(
|
||||
rateDetailsMember.breakfastIncluded
|
||||
rateDetailsMember.rateDefinition.breakfastIncluded
|
||||
)
|
||||
rateDetails.rateDefinitionMember =
|
||||
rateDetailsMember.rateDefinition
|
||||
}
|
||||
const rateDefinition = findRateDefintion(rateCode)
|
||||
if (rateDefinition) {
|
||||
switch (rateDefinition.rateType) {
|
||||
case RateTypeEnum.PublicPromotion:
|
||||
room.campaign.push({
|
||||
...rateDetails,
|
||||
member: memberRate,
|
||||
public: publicRate,
|
||||
})
|
||||
break
|
||||
case RateTypeEnum.Regular:
|
||||
room.regular.push({
|
||||
...rateDetails,
|
||||
member: memberRate,
|
||||
public: publicRate,
|
||||
})
|
||||
break
|
||||
default:
|
||||
room.code.push({
|
||||
...rateDetails,
|
||||
member: memberRate,
|
||||
public: publicRate,
|
||||
})
|
||||
}
|
||||
switch (rateDetails.rateDefinition.rateType) {
|
||||
case RateTypeEnum.PublicPromotion:
|
||||
room.campaign.push({
|
||||
...rateDetails,
|
||||
member: memberRate,
|
||||
public: publicRate,
|
||||
})
|
||||
break
|
||||
case RateTypeEnum.Regular:
|
||||
room.regular.push({
|
||||
...rateDetails,
|
||||
member: memberRate,
|
||||
public: publicRate,
|
||||
})
|
||||
break
|
||||
default:
|
||||
room.code.push({
|
||||
...rateDetails,
|
||||
member: memberRate,
|
||||
public: publicRate,
|
||||
})
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -56,7 +56,10 @@ export const breakfastPackageSchema = z.object({
|
||||
description: z.string(),
|
||||
localPrice: packagePriceSchema,
|
||||
requestedPrice: packagePriceSchema,
|
||||
packageType: z.literal(PackageTypeEnum.BreakfastAdult),
|
||||
packageType: z.enum([
|
||||
PackageTypeEnum.BreakfastAdult,
|
||||
PackageTypeEnum.BreakfastChildren,
|
||||
]),
|
||||
})
|
||||
|
||||
export const ancillaryPackageSchema = z.object({
|
||||
|
||||
@@ -8,16 +8,16 @@ import {
|
||||
} from "../productTypePrice"
|
||||
import { rateDefinitionSchema } from "./rateDefinition"
|
||||
|
||||
import { RateEnum } from "@/types/enums/rate"
|
||||
|
||||
const baseProductSchema = z.object({
|
||||
// transform empty string to undefined
|
||||
bookingCode: z
|
||||
.string()
|
||||
.optional()
|
||||
.transform((val) => val),
|
||||
// Is breakfast included on product
|
||||
breakfastIncluded: z.boolean().default(false),
|
||||
// Used to set the rate that we use to chose titles etc.
|
||||
rate: z.enum(["change", "flex", "save"]).default("save"),
|
||||
rate: z.nativeEnum(RateEnum).default(RateEnum.save),
|
||||
rateDefinition: rateDefinitionSchema.optional().transform((val) =>
|
||||
val
|
||||
? val
|
||||
@@ -42,7 +42,6 @@ const baseProductSchema = z.object({
|
||||
function mapBaseProduct(baseProduct: typeof baseProductSchema._type) {
|
||||
return {
|
||||
bookingCode: baseProduct.bookingCode,
|
||||
breakfastIncluded: baseProduct.breakfastIncluded,
|
||||
rate: baseProduct.rate,
|
||||
rateDefinition: baseProduct.rateDefinition,
|
||||
rateDefinitionMember: baseProduct.rateDefinitionMember,
|
||||
@@ -97,14 +96,12 @@ export const redemptionsProduct = z
|
||||
data.map(
|
||||
({
|
||||
bookingCode,
|
||||
breakfastIncluded,
|
||||
rate,
|
||||
rateDefinition,
|
||||
rateDefinitionMember,
|
||||
...redemption
|
||||
}) => ({
|
||||
bookingCode,
|
||||
breakfastIncluded,
|
||||
rate,
|
||||
rateDefinition,
|
||||
rateDefinitionMember,
|
||||
|
||||
@@ -430,7 +430,7 @@ export function calcTotalPrice(
|
||||
? (room.breakfast.localPrice?.price ?? 0)
|
||||
: 0
|
||||
|
||||
const roomFeaturesTotal = room.roomFeatures?.reduce(
|
||||
const roomFeaturesTotal = (room.roomFeatures || []).reduce(
|
||||
(total, pkg) => {
|
||||
if (pkg.requestedPrice.totalPrice) {
|
||||
total.requestedPrice = add(
|
||||
@@ -445,45 +445,72 @@ export function calcTotalPrice(
|
||||
{ local: 0, requestedPrice: 0 }
|
||||
)
|
||||
|
||||
const result: Price = {
|
||||
requested: roomPrice.perStay.requested
|
||||
? {
|
||||
currency: roomPrice.perStay.requested.currency,
|
||||
price: add(
|
||||
acc.requested?.price ?? 0,
|
||||
roomPrice.perStay.requested.price,
|
||||
breakfastRequestedPrice * room.adults * nights
|
||||
),
|
||||
}
|
||||
: undefined,
|
||||
local: {
|
||||
currency: roomPrice.perStay.local.currency,
|
||||
price: add(
|
||||
acc.local.price,
|
||||
roomPrice.perStay.local.price,
|
||||
breakfastLocalPrice * room.adults * nights,
|
||||
roomFeaturesTotal?.local ?? 0
|
||||
),
|
||||
regularPrice: add(
|
||||
acc.local.regularPrice,
|
||||
roomPrice.perStay.local.regularPrice,
|
||||
breakfastLocalPrice * room.adults * nights,
|
||||
roomFeaturesTotal?.requestedPrice ?? 0
|
||||
),
|
||||
additionalPrice: add(
|
||||
acc.local.additionalPrice,
|
||||
roomPrice.perStay.local.additionalPrice,
|
||||
breakfastLocalPrice * room.adults * nights,
|
||||
roomFeaturesTotal?.local ?? 0
|
||||
),
|
||||
additionalPriceCurrency: roomPrice.perStay.local
|
||||
.additionalPriceCurrency
|
||||
? roomPrice.perStay.local.additionalPriceCurrency
|
||||
: undefined,
|
||||
},
|
||||
if (roomPrice.perStay.requested) {
|
||||
if (!acc.requested) {
|
||||
acc.requested = {
|
||||
currency: roomPrice.perStay.requested.currency,
|
||||
price: 0,
|
||||
}
|
||||
}
|
||||
|
||||
acc.requested.price = add(
|
||||
acc.requested.price,
|
||||
roomPrice.perStay.requested.price,
|
||||
breakfastRequestedPrice * room.adults * nights
|
||||
)
|
||||
|
||||
// TODO: Come back and verify on CC, PTS, Voucher
|
||||
if (roomPrice.perStay.requested.additionalPrice) {
|
||||
acc.requested.additionalPrice = add(
|
||||
acc.requested.additionalPrice,
|
||||
roomPrice.perStay.requested.additionalPrice
|
||||
)
|
||||
}
|
||||
|
||||
if (
|
||||
roomPrice.perStay.requested.additionalPriceCurrency &&
|
||||
!acc.requested.additionalPriceCurrency
|
||||
) {
|
||||
acc.requested.additionalPriceCurrency =
|
||||
roomPrice.perStay.requested.additionalPriceCurrency
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
const breakfastLocalTotalPrice =
|
||||
breakfastLocalPrice * room.adults * nights
|
||||
|
||||
acc.local.price = add(
|
||||
acc.local.price,
|
||||
roomPrice.perStay.local.price,
|
||||
breakfastLocalTotalPrice,
|
||||
roomFeaturesTotal.local
|
||||
)
|
||||
|
||||
if (roomPrice.perStay.local.regularPrice) {
|
||||
acc.local.regularPrice = add(
|
||||
acc.local.regularPrice,
|
||||
roomPrice.perStay.local.regularPrice,
|
||||
breakfastLocalTotalPrice,
|
||||
roomFeaturesTotal.local
|
||||
)
|
||||
}
|
||||
|
||||
if (roomPrice.perStay.local.additionalPrice) {
|
||||
acc.local.additionalPrice = add(
|
||||
acc.local.additionalPrice,
|
||||
roomPrice.perStay.local.additionalPrice
|
||||
)
|
||||
}
|
||||
|
||||
if (
|
||||
roomPrice.perStay.local.additionalPriceCurrency &&
|
||||
!acc.local.additionalPriceCurrency
|
||||
) {
|
||||
acc.local.additionalPriceCurrency =
|
||||
roomPrice.perStay.local.additionalPriceCurrency
|
||||
}
|
||||
|
||||
return acc
|
||||
},
|
||||
{
|
||||
requested: undefined,
|
||||
|
||||
@@ -168,100 +168,19 @@ export function createDetailsStore(
|
||||
currentRoom.steps[StepEnum.breakfast].isValid = true
|
||||
}
|
||||
|
||||
const currentTotalPriceRequested = state.totalPrice.requested
|
||||
let stateTotalRequestedPrice = 0
|
||||
if (currentTotalPriceRequested) {
|
||||
stateTotalRequestedPrice =
|
||||
currentTotalPriceRequested.price ?? 0
|
||||
}
|
||||
|
||||
const stateTotalLocalPrice = state.totalPrice.local.price
|
||||
const stateTotalLocalRegularPrice =
|
||||
state.totalPrice.local.regularPrice
|
||||
|
||||
const addToTotalPrice =
|
||||
(currentRoom.room.breakfast === undefined ||
|
||||
currentRoom.room.breakfast === false) &&
|
||||
!!breakfast
|
||||
|
||||
const subtractFromTotalPrice =
|
||||
currentRoom.room.breakfast && breakfast === false
|
||||
currentRoom.room.breakfast = breakfast
|
||||
|
||||
const nights = dt(state.booking.toDate).diff(
|
||||
state.booking.fromDate,
|
||||
"days"
|
||||
)
|
||||
|
||||
if (addToTotalPrice) {
|
||||
const breakfastTotalRequestedPrice =
|
||||
breakfast.requestedPrice.price *
|
||||
currentRoom.room.adults *
|
||||
nights
|
||||
const breakfastTotalPrice =
|
||||
breakfast.localPrice.price *
|
||||
currentRoom.room.adults *
|
||||
nights
|
||||
state.totalPrice = {
|
||||
requested: state.totalPrice.requested && {
|
||||
currency: state.totalPrice.requested.currency,
|
||||
price:
|
||||
stateTotalRequestedPrice + breakfastTotalRequestedPrice,
|
||||
},
|
||||
local: {
|
||||
currency: breakfast.localPrice.currency,
|
||||
price: stateTotalLocalPrice ?? 0 + breakfastTotalPrice,
|
||||
regularPrice: stateTotalLocalRegularPrice
|
||||
? stateTotalLocalRegularPrice + breakfastTotalPrice
|
||||
: undefined,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (subtractFromTotalPrice) {
|
||||
let currency = state.totalPrice.local.currency
|
||||
let currentBreakfastTotalPrice = 0
|
||||
let currentBreakfastTotalRequestedPrice = 0
|
||||
if (currentRoom.room.breakfast) {
|
||||
currentBreakfastTotalPrice =
|
||||
currentRoom.room.breakfast.localPrice.price *
|
||||
currentRoom.room.adults *
|
||||
nights
|
||||
currentBreakfastTotalRequestedPrice =
|
||||
currentRoom.room.breakfast.requestedPrice.totalPrice *
|
||||
currentRoom.room.adults *
|
||||
nights
|
||||
currency = currentRoom.room.breakfast.localPrice.currency
|
||||
}
|
||||
|
||||
let requestedPrice =
|
||||
stateTotalRequestedPrice -
|
||||
currentBreakfastTotalRequestedPrice
|
||||
if (requestedPrice < 0) {
|
||||
requestedPrice = 0
|
||||
}
|
||||
let localPrice =
|
||||
stateTotalLocalPrice - currentBreakfastTotalPrice
|
||||
if (localPrice < 0) {
|
||||
localPrice = 0
|
||||
}
|
||||
let regularPrice = stateTotalLocalRegularPrice
|
||||
? stateTotalLocalRegularPrice - currentBreakfastTotalPrice
|
||||
: undefined
|
||||
|
||||
state.totalPrice = {
|
||||
requested: state.totalPrice.requested && {
|
||||
currency: state.totalPrice.requested.currency,
|
||||
price: requestedPrice,
|
||||
},
|
||||
local: {
|
||||
currency,
|
||||
price: localPrice,
|
||||
regularPrice,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
currentRoom.room.breakfast = breakfast
|
||||
state.totalPrice = calcTotalPrice(
|
||||
state.rooms,
|
||||
currentRoom.room.roomPrice.perStay.local.currency,
|
||||
isMember,
|
||||
nights
|
||||
)
|
||||
|
||||
const isAllStepsCompleted = checkRoomProgress(
|
||||
state.rooms[idx].steps
|
||||
|
||||
@@ -5,7 +5,11 @@ import type {
|
||||
RoomConfiguration,
|
||||
} from "@/types/trpc/routers/hotel/roomAvailability"
|
||||
|
||||
export function findProduct(rateCode: string, product: Product) {
|
||||
export function findProduct(
|
||||
rateCode: string,
|
||||
product: Product,
|
||||
counterRateCode = ""
|
||||
) {
|
||||
if ("corporateCheque" in product) {
|
||||
return product.corporateCheque.rateCode === rateCode
|
||||
}
|
||||
@@ -18,21 +22,35 @@ export function findProduct(rateCode: string, product: Product) {
|
||||
return product.voucher.rateCode === rateCode
|
||||
}
|
||||
|
||||
if ("public" in product && product.public) {
|
||||
return product.public.rateCode === rateCode
|
||||
}
|
||||
|
||||
if ("member" in product && product.member) {
|
||||
return product.member.rateCode === rateCode
|
||||
const memberExists = "member" in product
|
||||
const publicExists = "public" in product
|
||||
const isRegularRate = memberExists && publicExists
|
||||
if (isRegularRate) {
|
||||
let isProduct = false
|
||||
if (product.member) {
|
||||
isProduct =
|
||||
product.member.rateCode === rateCode ||
|
||||
product.member.rateCode === counterRateCode
|
||||
}
|
||||
if (product.public) {
|
||||
isProduct =
|
||||
product.public.rateCode === rateCode ||
|
||||
product.public.rateCode === counterRateCode
|
||||
}
|
||||
return isProduct
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export function findProductInRoom(rateCode: string, room: RoomConfiguration) {
|
||||
export function findProductInRoom(
|
||||
rateCode: string,
|
||||
room: RoomConfiguration,
|
||||
counterRateCode = ""
|
||||
) {
|
||||
if (room.campaign.length) {
|
||||
const campaignProduct = room.campaign.find((product) =>
|
||||
findProduct(rateCode, product)
|
||||
findProduct(rateCode, product, counterRateCode)
|
||||
)
|
||||
if (campaignProduct) {
|
||||
return campaignProduct
|
||||
@@ -40,7 +58,7 @@ export function findProductInRoom(rateCode: string, room: RoomConfiguration) {
|
||||
}
|
||||
if (room.code.length) {
|
||||
const codeProduct = room.code.find((product) =>
|
||||
findProduct(rateCode, product)
|
||||
findProduct(rateCode, product, counterRateCode)
|
||||
)
|
||||
if (codeProduct) {
|
||||
return codeProduct
|
||||
@@ -56,7 +74,7 @@ export function findProductInRoom(rateCode: string, room: RoomConfiguration) {
|
||||
}
|
||||
if (room.regular.length) {
|
||||
const regularProduct = room.regular.find((product) =>
|
||||
findProduct(rateCode, product)
|
||||
findProduct(rateCode, product, counterRateCode)
|
||||
)
|
||||
if (regularProduct) {
|
||||
return regularProduct
|
||||
@@ -66,6 +84,7 @@ export function findProductInRoom(rateCode: string, room: RoomConfiguration) {
|
||||
|
||||
export function findSelectedRate(
|
||||
rateCode: string,
|
||||
counterRateCode: string,
|
||||
roomTypeCode: string,
|
||||
rooms: RoomConfiguration[] | AvailabilityError
|
||||
) {
|
||||
@@ -76,7 +95,7 @@ export function findSelectedRate(
|
||||
if (room.roomTypeCode !== roomTypeCode) {
|
||||
return false
|
||||
}
|
||||
return findProductInRoom(rateCode, room)
|
||||
return findProductInRoom(rateCode, room, counterRateCode)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -71,6 +71,7 @@ export function createRatesStore({
|
||||
const roomConfiguration = roomConfigurations?.[idx]
|
||||
const selectedRoom = findSelectedRate(
|
||||
room.rateCode,
|
||||
room.counterRateCode,
|
||||
room.roomTypeCode,
|
||||
roomConfiguration
|
||||
)
|
||||
@@ -79,7 +80,11 @@ export function createRatesStore({
|
||||
continue
|
||||
}
|
||||
|
||||
const product = findProductInRoom(room.rateCode, selectedRoom)
|
||||
const product = findProductInRoom(
|
||||
room.rateCode,
|
||||
selectedRoom,
|
||||
room.counterRateCode
|
||||
)
|
||||
if (product) {
|
||||
rateSummary[idx] = {
|
||||
features: selectedRoom.features,
|
||||
@@ -121,13 +126,18 @@ export function createRatesStore({
|
||||
const selectedRate =
|
||||
findSelectedRate(
|
||||
room.rateCode,
|
||||
room.counterRateCode,
|
||||
room.roomTypeCode,
|
||||
roomConfiguration
|
||||
) ?? null
|
||||
|
||||
let product = null
|
||||
if (selectedRate) {
|
||||
product = findProductInRoom(room.rateCode, selectedRate)
|
||||
product = findProductInRoom(
|
||||
room.rateCode,
|
||||
selectedRate,
|
||||
room.counterRateCode
|
||||
)
|
||||
}
|
||||
|
||||
// Since features are fetched async based on query string, we need to read from query string to apply correct filtering
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { RateEnum } from "@/types/enums/rate"
|
||||
import type {
|
||||
Product,
|
||||
RoomConfiguration,
|
||||
@@ -35,7 +36,7 @@ export type Rate = {
|
||||
priceName?: string
|
||||
priceTerm?: string
|
||||
product: Product
|
||||
rate: "change" | "flex" | "save"
|
||||
rate: RateEnum
|
||||
roomRates?: {
|
||||
rate: Rate
|
||||
roomIndex: number
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { Lang } from "@/constants/languages"
|
||||
import type { MembershipLevel } from "@/constants/membershipLevels"
|
||||
import type { RateEnum } from "../enums/rate"
|
||||
|
||||
export enum TrackingChannelEnum {
|
||||
"scandic-friends" = "scandic-friends",
|
||||
@@ -54,7 +55,7 @@ export type TrackingSDKUserData =
|
||||
export type TrackingSDKHotelInfo = {
|
||||
ageOfChildren?: string // "10", "2,5,10"
|
||||
ancillaries?: Ancillary[]
|
||||
analyticsRateCode?: "flex" | "change" | "save" | string
|
||||
analyticsRateCode?: RateEnum | string
|
||||
arrivalDate?: string
|
||||
availableResults?: number // Number of hotels to choose from after a city search
|
||||
bedType?: string
|
||||
|
||||
5
apps/scandic-web/types/enums/rate.ts
Normal file
5
apps/scandic-web/types/enums/rate.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export enum RateEnum {
|
||||
change = "change",
|
||||
flex = "flex",
|
||||
save = "save",
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { BedTypeSelection } from "@/types/components/hotelReservation/enterDetails/bedType"
|
||||
import type { RoomRate } from "@/types/components/hotelReservation/enterDetails/details"
|
||||
import type { RateEnum } from "@/types/enums/rate"
|
||||
import type { Packages } from "@/types/requests/packages"
|
||||
|
||||
export interface Room {
|
||||
@@ -10,7 +11,7 @@ export interface Room {
|
||||
mustBeGuaranteed: boolean
|
||||
memberMustBeGuaranteed?: boolean
|
||||
packages: Packages | null
|
||||
rate: "change" | "flex" | "save"
|
||||
rate: RateEnum
|
||||
rateDefinitionTitle: string
|
||||
rateDetails: string[]
|
||||
rateTitle?: string
|
||||
|
||||
Reference in New Issue
Block a user