Merged in chore/move-enter-details (pull request #2778)
Chore/move enter details Approved-by: Anton Gunnarsson
This commit is contained in:
@@ -0,0 +1,358 @@
|
||||
import { differenceInCalendarDays, format, isWeekend } from "date-fns"
|
||||
|
||||
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
|
||||
import {
|
||||
TrackingChannelEnum,
|
||||
type TrackingSDKAncillaries,
|
||||
type TrackingSDKHotelInfo,
|
||||
type TrackingSDKPageData,
|
||||
} from "@scandic-hotels/tracking/types"
|
||||
import { SEARCH_TYPE_REDEMPTION } from "@scandic-hotels/trpc/constants/booking"
|
||||
import { ChildBedMapEnum } from "@scandic-hotels/trpc/enums/childBedMapEnum"
|
||||
import { PackageTypeEnum } from "@scandic-hotels/trpc/enums/packages"
|
||||
import { RoomPackageCodeEnum } from "@scandic-hotels/trpc/enums/roomFilter"
|
||||
|
||||
import { sumPackages } from "../../../utils/SelectRate"
|
||||
import { getSpecialRoomType } from "../../../utils/specialRoomType"
|
||||
|
||||
import type { Lang } from "@scandic-hotels/common/constants/language"
|
||||
import type { BreakfastPackages } from "@scandic-hotels/trpc/routers/hotels/output"
|
||||
import type { Hotel } from "@scandic-hotels/trpc/types/hotel"
|
||||
import type { Room } from "@scandic-hotels/trpc/types/room"
|
||||
import type {
|
||||
PriceProduct,
|
||||
Product,
|
||||
} from "@scandic-hotels/trpc/types/roomAvailability"
|
||||
|
||||
import type { RoomState } from "../../../stores/enter-details/types"
|
||||
import type { DetailsBooking } from "../../../utils/url"
|
||||
|
||||
export function getTracking(
|
||||
booking: DetailsBooking,
|
||||
hotel: Hotel,
|
||||
rooms: Room[],
|
||||
isMember: boolean,
|
||||
lang: Lang,
|
||||
storedRooms: RoomState[],
|
||||
breakfastPackages: BreakfastPackages,
|
||||
searchTerm?: string
|
||||
) {
|
||||
const arrivalDate = new Date(booking.fromDate)
|
||||
const departureDate = new Date(booking.toDate)
|
||||
const shouldSkipBreakfastOption =
|
||||
storedRooms.every((r) => r.room.breakfast === undefined) ||
|
||||
!breakfastPackages.length
|
||||
|
||||
const breakfastOption = shouldSkipBreakfastOption
|
||||
? undefined
|
||||
: storedRooms
|
||||
.map((r) => {
|
||||
if (r.room.breakfast === undefined) return ""
|
||||
if (!r.room.breakfast) return "no breakfast"
|
||||
return "breakfast buffet"
|
||||
})
|
||||
.join("|")
|
||||
|
||||
const pageTrackingData: TrackingSDKPageData = {
|
||||
channel: TrackingChannelEnum.hotelreservation,
|
||||
domainLanguage: lang,
|
||||
pageId: "details",
|
||||
pageName: "hotelreservation|details",
|
||||
pageType: "bookingenterdetailspage",
|
||||
siteSections: "hotelreservation|details",
|
||||
siteVersion: "new-web",
|
||||
}
|
||||
const hotelsTrackingData: TrackingSDKHotelInfo = {
|
||||
ageOfChildren: booking.rooms
|
||||
.map((room) => room.childrenInRoom?.map((kid) => kid.age).join(",") ?? "")
|
||||
.join("|"),
|
||||
analyticsRateCode: rooms.map((room) => room.rate).join("|"),
|
||||
arrivalDate: format(arrivalDate, "yyyy-MM-dd"),
|
||||
bedType: storedRooms
|
||||
.map((r) => (r.room.bedType ? r.room.bedType.type : ""))
|
||||
.join("|"),
|
||||
// Comma separated booking code values in "code,code,n/a" format for multiroom and "code" or "n/a" for singleroom
|
||||
// n/a is used whenever code is Not applicable as defined by Tracking team
|
||||
bookingCode: rooms
|
||||
.map((room) => room.roomRate.bookingCode ?? "n/a")
|
||||
.join(", "),
|
||||
// Similar to booking code, comma separated room based values.
|
||||
bookingCodeAvailability: booking.bookingCode
|
||||
? rooms
|
||||
.map((room) => (room.roomRate.bookingCode ? "true" : "false"))
|
||||
.join(", ")
|
||||
: undefined,
|
||||
bookingTypeofDay: isWeekend(arrivalDate) ? "weekend" : "weekday",
|
||||
breakfastOption,
|
||||
childBedPreference: booking.rooms
|
||||
.map(
|
||||
(room) =>
|
||||
room.childrenInRoom
|
||||
?.map((kid) => ChildBedMapEnum[kid.bed])
|
||||
.join(",") ?? ""
|
||||
)
|
||||
.join("|"),
|
||||
country: hotel?.address.country,
|
||||
departureDate: format(departureDate, "yyyy-MM-dd"),
|
||||
discount: storedRooms.reduce((total, { room }, idx) => {
|
||||
const isMainRoom = idx === 0
|
||||
if (
|
||||
hasMemberPrice(room.roomRate) &&
|
||||
isMainRoom &&
|
||||
isMember &&
|
||||
hasPublicPrice(room.roomRate)
|
||||
) {
|
||||
const memberPrice = room.roomRate.member.localPrice.pricePerStay
|
||||
const publicPrice = room.roomRate.public.localPrice.pricePerStay
|
||||
total += publicPrice - memberPrice
|
||||
} else if (
|
||||
hasPublicPrice(room.roomRate) &&
|
||||
room.roomRate.public.localPrice.regularPricePerStay
|
||||
) {
|
||||
const publicPrice = room.roomRate.public.localPrice.pricePerStay
|
||||
const regularPrice = room.roomRate.public.localPrice.regularPricePerStay
|
||||
total += regularPrice - publicPrice
|
||||
}
|
||||
return total
|
||||
}, 0),
|
||||
duration: differenceInCalendarDays(departureDate, arrivalDate),
|
||||
hotelID: hotel?.operaId,
|
||||
leadTime: differenceInCalendarDays(arrivalDate, new Date()),
|
||||
noOfAdults: booking.rooms.map((room) => room.adults).join(","),
|
||||
noOfChildren: booking.rooms
|
||||
.map((room) => room.childrenInRoom?.length ?? 0)
|
||||
.join(","),
|
||||
noOfRooms: booking.rooms.length,
|
||||
...(rooms.length > 1 && {
|
||||
multiroomRateIdentity: rooms.every((room) => {
|
||||
const firstRoom = rooms[0]
|
||||
if (
|
||||
hasPublicPrice(firstRoom.roomRate) &&
|
||||
hasPublicPrice(room.roomRate)
|
||||
) {
|
||||
return (
|
||||
firstRoom.roomRate.public?.localPrice.pricePerNight ===
|
||||
room.roomRate.public?.localPrice.pricePerNight
|
||||
)
|
||||
}
|
||||
})
|
||||
? "same rate"
|
||||
: "mixed rate",
|
||||
}),
|
||||
rateCode: rooms
|
||||
.map((room, idx) => {
|
||||
const isMainRoom = idx === 0
|
||||
if (hasMemberPrice(room.roomRate) && isMember && isMainRoom) {
|
||||
return room.roomRate.member.rateCode
|
||||
} else if (hasPublicPrice(room.roomRate)) {
|
||||
return room.roomRate.public.rateCode
|
||||
} else if ("corporateCheque" in room.roomRate) {
|
||||
return room.roomRate.corporateCheque.rateCode
|
||||
} else if ("redemption" in room.roomRate) {
|
||||
return room.roomRate.redemption.rateCode
|
||||
} else if ("voucher" in room.roomRate) {
|
||||
return room.roomRate.voucher.rateCode
|
||||
}
|
||||
return ""
|
||||
})
|
||||
.join("|"),
|
||||
|
||||
rateCodeCancellationRule: rooms
|
||||
.map((room) => room.cancellationRule)
|
||||
.join(","),
|
||||
rateCodeName: rooms
|
||||
.map((room) =>
|
||||
constructRateCodeName(
|
||||
room.roomRate,
|
||||
room.breakfastIncluded,
|
||||
booking.bookingCode
|
||||
)
|
||||
)
|
||||
.join(","),
|
||||
rateCodeType: rooms.map((room) => room.rateType.toLowerCase()).join(","),
|
||||
region: hotel?.address.city,
|
||||
revenueCurrencyCode: [
|
||||
...new Set(
|
||||
rooms.map((room) => {
|
||||
if ("corporateCheque" in room.roomRate) {
|
||||
return CurrencyEnum.CC
|
||||
} else if ("redemption" in room.roomRate) {
|
||||
return CurrencyEnum.POINTS
|
||||
} else if ("voucher" in room.roomRate) {
|
||||
return CurrencyEnum.Voucher
|
||||
} else if (hasPublicPrice(room.roomRate)) {
|
||||
return room.roomRate.public.localPrice.currency
|
||||
} else if (hasMemberPrice(room.roomRate)) {
|
||||
return room.roomRate.member.localPrice.currency
|
||||
}
|
||||
return CurrencyEnum.Unknown
|
||||
})
|
||||
),
|
||||
].join(","),
|
||||
rewardNight: booking.searchType === SEARCH_TYPE_REDEMPTION ? "yes" : "no",
|
||||
rewardNightAvailability:
|
||||
booking.searchType === SEARCH_TYPE_REDEMPTION ? "true" : "false",
|
||||
roomPrice: calcTotalRoomPrice(storedRooms, isMember),
|
||||
roomTypeCode: rooms.map((room) => room.roomTypeCode).join("|"),
|
||||
roomTypeName: rooms.map((room) => room.roomType).join("|"),
|
||||
totalPrice: calcTotalPrice(storedRooms, isMember),
|
||||
points:
|
||||
// @ts-expect-error - redemption object doesn't exist error
|
||||
rooms.find((room) => "redemption" in room.roomRate)?.roomRate.redemption
|
||||
.localPrice.pointsPerStay ?? "n/a",
|
||||
searchTerm,
|
||||
searchType: "hotel",
|
||||
specialRoomType: rooms
|
||||
.map((room) => getSpecialRoomType(room.packages))
|
||||
.join(","),
|
||||
}
|
||||
const roomsWithPetRoom = rooms.filter(hasPetRoom)
|
||||
const petRoomAncillaries: TrackingSDKAncillaries = roomsWithPetRoom
|
||||
.slice(0, 1) // should only be one item
|
||||
.map((room) => {
|
||||
return room.packages
|
||||
.filter((p) => p.code === RoomPackageCodeEnum.PET_ROOM)
|
||||
.map((pkg) => ({
|
||||
hotelId: hotel.operaId,
|
||||
productId: pkg.code,
|
||||
productUnits: roomsWithPetRoom.length,
|
||||
productPoints: 0,
|
||||
productPrice: pkg.localPrice.totalPrice * roomsWithPetRoom.length,
|
||||
productType: "room preference",
|
||||
productName: "pet room",
|
||||
productCategory: "",
|
||||
}))[0]
|
||||
})
|
||||
|
||||
const breakfastAncillaries: TrackingSDKAncillaries = storedRooms
|
||||
.map((room) => {
|
||||
return { breakfast: room.room.breakfast, adults: room.room.adults }
|
||||
})
|
||||
.filter(hasBreakfast)
|
||||
.map(({ breakfast, adults }) => {
|
||||
return {
|
||||
hotelId: hotel.operaId,
|
||||
productId: breakfast.code,
|
||||
productUnits:
|
||||
adults * differenceInCalendarDays(departureDate, arrivalDate),
|
||||
productPoints: 0,
|
||||
productPrice:
|
||||
breakfast.localPrice.price *
|
||||
adults *
|
||||
differenceInCalendarDays(departureDate, arrivalDate),
|
||||
productType: "food",
|
||||
productName: breakfast.description,
|
||||
productCategory: breakfast.packageType,
|
||||
}
|
||||
})
|
||||
const ancillaries = petRoomAncillaries.concat(breakfastAncillaries)
|
||||
|
||||
return {
|
||||
hotelsTrackingData,
|
||||
pageTrackingData,
|
||||
ancillaries,
|
||||
}
|
||||
}
|
||||
|
||||
function hasPublicPrice(
|
||||
roomRate: Product
|
||||
): roomRate is PriceProduct & { public: NonNullable<PriceProduct["public"]> } {
|
||||
if ("public" in roomRate && roomRate.public) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function hasMemberPrice(
|
||||
roomRate: Product
|
||||
): roomRate is PriceProduct & { member: NonNullable<PriceProduct["member"]> } {
|
||||
if ("member" in roomRate && roomRate.member) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function hasPetRoom(
|
||||
room: Room
|
||||
): room is Room & { packages: NonNullable<Room["packages"]> } {
|
||||
if (!room.packages) {
|
||||
return false
|
||||
}
|
||||
return room.packages.some((p) => p.code === RoomPackageCodeEnum.PET_ROOM)
|
||||
}
|
||||
|
||||
type RoomEntry = {
|
||||
breakfast?: BreakfastPackages[number] | false
|
||||
adults: number
|
||||
}
|
||||
|
||||
function hasBreakfast(entry: RoomEntry): entry is RoomEntry & {
|
||||
breakfast: BreakfastPackages[number]
|
||||
adults: number
|
||||
} {
|
||||
return (
|
||||
entry.breakfast !== false &&
|
||||
entry.breakfast?.packageType === PackageTypeEnum.BreakfastAdult
|
||||
)
|
||||
}
|
||||
|
||||
function calcTotalPrice(rooms: RoomState[], isMember: boolean) {
|
||||
const totalRoomPrice = calcTotalRoomPrice(rooms, isMember)
|
||||
const totalPackageSum = rooms.reduce((total, { room }) => {
|
||||
if (room.breakfast) {
|
||||
total += Number(room.breakfast.localPrice.totalPrice) * room.adults
|
||||
}
|
||||
if (room.roomFeatures?.length) {
|
||||
const packageSum = sumPackages(room.roomFeatures)
|
||||
total += packageSum.price
|
||||
}
|
||||
return total
|
||||
}, 0)
|
||||
return totalRoomPrice + totalPackageSum
|
||||
}
|
||||
|
||||
function calcTotalRoomPrice(rooms: RoomState[], isMember: boolean) {
|
||||
return rooms.reduce((total, { room }, idx) => {
|
||||
// When it comes special rates, only redemption has additional price and that should be added
|
||||
// other special rates like voucher, corporateCheck should be added as 0 according to Priyanka
|
||||
if ("redemption" in room.roomRate) {
|
||||
return room.roomRate.redemption.localPrice?.additionalPricePerStay ?? 0
|
||||
} else if (
|
||||
"corporateCheque" in room.roomRate ||
|
||||
"voucher" in room.roomRate
|
||||
) {
|
||||
return 0
|
||||
}
|
||||
const isMainRoom = idx === 0
|
||||
if (hasMemberPrice(room.roomRate) && isMember && isMainRoom) {
|
||||
total += room.roomRate.member.localPrice.pricePerStay
|
||||
} else if (hasPublicPrice(room.roomRate)) {
|
||||
total += room.roomRate.public.localPrice.pricePerStay
|
||||
}
|
||||
return total
|
||||
}, 0)
|
||||
}
|
||||
|
||||
function constructRateCodeName(
|
||||
roomRate: Product,
|
||||
breakfastIncluded: boolean,
|
||||
bookingCode?: string
|
||||
) {
|
||||
if ("corporateCheque" in roomRate) {
|
||||
return "corporate cheque"
|
||||
} else if ("voucher" in roomRate) {
|
||||
return "voucher"
|
||||
} else if ("redemption" in roomRate) {
|
||||
return "redemption"
|
||||
}
|
||||
|
||||
const bookingCodeStr = bookingCode ? bookingCode.toUpperCase() : ""
|
||||
|
||||
const breakfastIncludedStr = breakfastIncluded
|
||||
? "incl. breakfast"
|
||||
: "excl. breakfast"
|
||||
|
||||
return [bookingCodeStr, roomRate.rate, breakfastIncludedStr]
|
||||
.filter(Boolean)
|
||||
.join(" - ")
|
||||
}
|
||||
Reference in New Issue
Block a user