Merged in fix/refactor-currency-display (pull request #3434)
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
This commit is contained in:
@@ -9,10 +9,10 @@ import DateSelect from "@scandic-hotels/design-system/Form/Date"
|
||||
import { FormInput } from "@scandic-hotels/design-system/Form/FormInput"
|
||||
import Phone from "@scandic-hotels/design-system/Form/Phone"
|
||||
import { FormSelect } from "@scandic-hotels/design-system/Form/Select"
|
||||
import { PasswordInput } from "@scandic-hotels/design-system/PasswordInput"
|
||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||
|
||||
import { getLocalizedLanguageOptions } from "@/constants/languages"
|
||||
import { PasswordInput } from "@scandic-hotels/design-system/PasswordInput"
|
||||
|
||||
import useLang from "@/hooks/useLang"
|
||||
import { getFormattedCountryList } from "@/utils/countries"
|
||||
|
||||
@@ -34,9 +34,10 @@ export function mapToPrice(room: Room) {
|
||||
return {
|
||||
redemption: {
|
||||
additionalPricePerStay: room.roomPrice.perStay.local.price,
|
||||
currency: room.currencyCode,
|
||||
currency: CurrencyEnum.POINTS,
|
||||
pointsPerNight: room.roomPoints / nights,
|
||||
pointsPerStay: room.roomPoints,
|
||||
pointsType: room.roomPointType,
|
||||
},
|
||||
}
|
||||
case PriceTypeEnum.voucher:
|
||||
@@ -71,16 +72,6 @@ export function calculateTotalPrice(rooms: Room[], currency: CurrencyEnum) {
|
||||
break
|
||||
case PriceTypeEnum.points:
|
||||
{
|
||||
if (
|
||||
room.roomPoints &&
|
||||
room.roomPointType &&
|
||||
room.roomPointType !== "Scandic"
|
||||
) {
|
||||
total.local.currency =
|
||||
roomPointTypeToCurrencyMap[room.roomPointType]
|
||||
total.local.price = total.local.price + room.roomPoints
|
||||
break
|
||||
}
|
||||
total.local.currency = CurrencyEnum.POINTS
|
||||
total.local.price = total.local.price + room.totalPoints
|
||||
}
|
||||
@@ -95,6 +86,17 @@ export function calculateTotalPrice(rooms: Room[], currency: CurrencyEnum) {
|
||||
case PriceTypeEnum.cheque:
|
||||
case PriceTypeEnum.points:
|
||||
{
|
||||
if (
|
||||
room.roomPoints &&
|
||||
room.roomPointType &&
|
||||
room.roomPointType !== "Scandic"
|
||||
) {
|
||||
total.local.currency = CurrencyEnum.POINTS
|
||||
total.local.price = total.local.price + room.roomPoints
|
||||
total.local.pointsType = room.roomPointType
|
||||
break
|
||||
}
|
||||
|
||||
if (room.totalPrice) {
|
||||
total.local.additionalPrice =
|
||||
(total.local.additionalPrice || 0) + room.totalPrice
|
||||
@@ -140,16 +142,9 @@ export function calculateTotalPrice(rooms: Room[], currency: CurrencyEnum) {
|
||||
local: {
|
||||
currency,
|
||||
price: 0,
|
||||
pointsType: undefined,
|
||||
},
|
||||
requested: undefined,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const roomPointTypeToCurrencyMap: Record<
|
||||
NonNullable<Room["roomPointType"]>,
|
||||
CurrencyEnum
|
||||
> = {
|
||||
Scandic: CurrencyEnum.POINTS,
|
||||
EuroBonus: CurrencyEnum.EUROBONUS,
|
||||
}
|
||||
|
||||
@@ -9,16 +9,15 @@ import type { BookingConfirmation } from "@scandic-hotels/trpc/types/bookingConf
|
||||
|
||||
import { PriceTypeEnum } from "@/types/components/hotelReservation/myStay/myStay"
|
||||
|
||||
interface PriceTypeProps
|
||||
extends Pick<
|
||||
BookingConfirmation["booking"],
|
||||
| "cheques"
|
||||
| "currencyCode"
|
||||
| "rateDefinition"
|
||||
| "totalPoints"
|
||||
| "totalPrice"
|
||||
| "vouchers"
|
||||
> {
|
||||
interface PriceTypeProps extends Pick<
|
||||
BookingConfirmation["booking"],
|
||||
| "cheques"
|
||||
| "currencyCode"
|
||||
| "rateDefinition"
|
||||
| "totalPoints"
|
||||
| "totalPrice"
|
||||
| "vouchers"
|
||||
> {
|
||||
formattedTotalPrice: string
|
||||
isCancelled: boolean
|
||||
priceType: PriceTypeEnum
|
||||
|
||||
@@ -10,7 +10,6 @@ export default function TotalPrice() {
|
||||
const { bookedRoom, totalPrice } = useMyStayStore((state) => ({
|
||||
bookedRoom: state.bookedRoom,
|
||||
totalPrice: state.totalPrice,
|
||||
rooms: state.rooms,
|
||||
}))
|
||||
return (
|
||||
<Price
|
||||
|
||||
@@ -1,27 +1,25 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { createIntl, createIntlCache } from "react-intl"
|
||||
import { beforeAll, describe, expect, it, vi } from "vitest"
|
||||
|
||||
import { calculateTotalPrice } from "./helpers"
|
||||
|
||||
const cache = createIntlCache()
|
||||
|
||||
const createTestIntl = (locale: string = "en-US") =>
|
||||
createIntl({ locale, messages: {}, onError: () => {} }, cache)
|
||||
|
||||
describe("calculateTotalPrice", () => {
|
||||
const baseRoom: Parameters<typeof calculateTotalPrice>[0][0] = {
|
||||
totalPrice: 0,
|
||||
isCancelled: false,
|
||||
cheques: 0,
|
||||
roomPoints: 0,
|
||||
roomPointType: null,
|
||||
totalPoints: 0,
|
||||
roomPoints: 0,
|
||||
vouchers: 0,
|
||||
}
|
||||
|
||||
const mockIntlSimple = {
|
||||
formatMessage: vi.fn(({}, values) => {
|
||||
if (values?.numberOfVouchers === 1) return "Voucher"
|
||||
return "Vouchers"
|
||||
}),
|
||||
formatNumber: vi.fn((num) => String(num)),
|
||||
} as any
|
||||
|
||||
vi.mock("@scandic-hotels/common/utils/numberFormatting", () => ({
|
||||
formatPrice: (_intl: any, price: number, currency: string) =>
|
||||
`${price} ${currency}`,
|
||||
@@ -40,7 +38,7 @@ describe("calculateTotalPrice", () => {
|
||||
const result = calculateTotalPrice(
|
||||
rooms,
|
||||
"SEK" as any,
|
||||
mockIntlSimple,
|
||||
createTestIntl(),
|
||||
false
|
||||
)
|
||||
expect(result).toBe("1500 SEK")
|
||||
@@ -60,7 +58,7 @@ describe("calculateTotalPrice", () => {
|
||||
const result = calculateTotalPrice(
|
||||
rooms,
|
||||
"SEK" as any,
|
||||
mockIntlSimple,
|
||||
createTestIntl(),
|
||||
false
|
||||
)
|
||||
expect(result).toBe("1000 SEK")
|
||||
@@ -70,7 +68,7 @@ describe("calculateTotalPrice", () => {
|
||||
const result = calculateTotalPrice(
|
||||
[{ ...baseRoom, vouchers: 2, totalPrice: -1, isCancelled: false }],
|
||||
"SEK" as any,
|
||||
mockIntlSimple,
|
||||
createTestIntl(),
|
||||
false
|
||||
)
|
||||
expect(result).toContain("2 Vouchers")
|
||||
@@ -84,19 +82,17 @@ describe("calculateTotalPrice", () => {
|
||||
totalPrice: 100,
|
||||
isCancelled: false,
|
||||
totalPoints: 0,
|
||||
roomPoints: 0,
|
||||
},
|
||||
{
|
||||
...baseRoom,
|
||||
totalPrice: 0,
|
||||
totalPoints: 20000,
|
||||
roomPoints: 20000,
|
||||
roomPointType: "Scandic",
|
||||
isCancelled: false,
|
||||
},
|
||||
],
|
||||
"SEK" as any,
|
||||
mockIntlSimple,
|
||||
createTestIntl(),
|
||||
false
|
||||
)
|
||||
|
||||
@@ -109,7 +105,7 @@ describe("calculateTotalPrice", () => {
|
||||
const result = calculateTotalPrice(
|
||||
rooms,
|
||||
"SEK" as any,
|
||||
mockIntlSimple,
|
||||
createTestIntl(),
|
||||
false
|
||||
)
|
||||
expect(result).toContain("2 CC")
|
||||
@@ -131,7 +127,7 @@ describe("calculateTotalPrice", () => {
|
||||
},
|
||||
],
|
||||
"SEK" as any,
|
||||
mockIntlSimple,
|
||||
createTestIntl(),
|
||||
false
|
||||
)
|
||||
expect(result).toMatch(/1 Voucher \+ 500 SEK/)
|
||||
@@ -151,10 +147,10 @@ describe("calculateTotalPrice", () => {
|
||||
},
|
||||
],
|
||||
"SEK" as any,
|
||||
mockIntlSimple,
|
||||
createTestIntl(),
|
||||
false
|
||||
)
|
||||
expect(result).toMatch(/1 EuroBonus \+ 500 SEK/)
|
||||
expect(result).toMatch(/1 EB Point \+ 500 SEK/)
|
||||
})
|
||||
|
||||
it("should combine Eurobonus points, Scandic Friends points and Cash", () => {
|
||||
@@ -172,9 +168,9 @@ describe("calculateTotalPrice", () => {
|
||||
},
|
||||
],
|
||||
"SEK" as any,
|
||||
mockIntlSimple,
|
||||
createTestIntl(),
|
||||
false
|
||||
)
|
||||
expect(result).toMatch(/500 Points \+ 1 EuroBonus \+ 500 SEK/)
|
||||
expect(result).toMatch(/1 EB Point \+ 500 Points \+ 500 SEK/)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
|
||||
import { PointType } from "@scandic-hotels/common/constants/pointType"
|
||||
import { formatPrice } from "@scandic-hotels/common/utils/numberFormatting"
|
||||
|
||||
import type { IntlShape } from "react-intl"
|
||||
@@ -10,8 +11,8 @@ export function calculateTotalPrice(
|
||||
Room,
|
||||
| "cheques"
|
||||
| "vouchers"
|
||||
| "roomPoints"
|
||||
| "roomPointType"
|
||||
| "roomPoints"
|
||||
| "totalPoints"
|
||||
| "totalPrice"
|
||||
| "isCancelled"
|
||||
@@ -34,18 +35,19 @@ export function calculateTotalPrice(
|
||||
total.vouchers = total.vouchers + room.vouchers
|
||||
}
|
||||
|
||||
// If roomPointType isn't Scandic, these should be counted as partnerPoints.
|
||||
// If they're not Scandic points, we can ignore them on roomPoints as they
|
||||
// are included in totalPoints, which in turn never contain partner points.
|
||||
if (
|
||||
room.roomPoints &&
|
||||
room.roomPointType &&
|
||||
room.roomPointType !== "Scandic"
|
||||
) {
|
||||
total.partnerPoints = total.partnerPoints + room.roomPoints
|
||||
total.partnerPointsCurrency = room.roomPointType ?? null
|
||||
}
|
||||
|
||||
if (room.totalPoints) {
|
||||
total.scandicFriendsPoints =
|
||||
total.scandicFriendsPoints + room.totalPoints
|
||||
total.scandicPoints = total.scandicPoints + room.totalPoints
|
||||
}
|
||||
|
||||
// room.totalPrice is a negative value when
|
||||
@@ -59,9 +61,8 @@ export function calculateTotalPrice(
|
||||
{
|
||||
cash: 0,
|
||||
cheques: 0,
|
||||
scandicFriendsPoints: 0,
|
||||
scandicPoints: 0,
|
||||
partnerPoints: 0,
|
||||
partnerPointsCurrency: null as Room["roomPointType"],
|
||||
vouchers: 0,
|
||||
}
|
||||
)
|
||||
@@ -86,12 +87,24 @@ export function calculateTotalPrice(
|
||||
priceParts.push(`${totals.cheques} ${CurrencyEnum.CC}`)
|
||||
}
|
||||
|
||||
if (totals.scandicFriendsPoints) {
|
||||
priceParts.push(`${totals.scandicFriendsPoints} ${CurrencyEnum.POINTS}`)
|
||||
if (totals.partnerPoints) {
|
||||
// We can assume that all rooms has the same point type
|
||||
const roomPointType = rooms[0]?.roomPointType || PointType.SCANDIC
|
||||
const currencyText = getPointsCurrencyText(
|
||||
totals.partnerPoints,
|
||||
roomPointType,
|
||||
intl
|
||||
)
|
||||
priceParts.push(`${totals.partnerPoints} ${currencyText}`)
|
||||
}
|
||||
|
||||
if (totals.partnerPoints) {
|
||||
priceParts.push(`${totals.partnerPoints} ${totals.partnerPointsCurrency}`)
|
||||
if (totals.scandicPoints) {
|
||||
const currencyText = getPointsCurrencyText(
|
||||
totals.scandicPoints,
|
||||
PointType.SCANDIC,
|
||||
intl
|
||||
)
|
||||
priceParts.push(`${totals.scandicPoints} ${currencyText}`)
|
||||
}
|
||||
|
||||
if (totals.cash) {
|
||||
@@ -102,18 +115,43 @@ export function calculateTotalPrice(
|
||||
return priceParts.join(" + ")
|
||||
}
|
||||
|
||||
export function calculateTotalPoints(
|
||||
rooms: Room[],
|
||||
allRoomsAreCancelled: boolean
|
||||
) {
|
||||
return rooms.reduce((total, room) => {
|
||||
if (!allRoomsAreCancelled && room.isCancelled) {
|
||||
return total
|
||||
}
|
||||
return total + room.totalPoints
|
||||
}, 0)
|
||||
}
|
||||
|
||||
export function isAllRoomsCancelled(rooms: Room[]) {
|
||||
return !rooms.some((room) => room.isCancelled === false)
|
||||
}
|
||||
|
||||
function getPointsCurrencyText(
|
||||
points: number,
|
||||
pointsType: PointType,
|
||||
intl: IntlShape
|
||||
) {
|
||||
switch (pointsType) {
|
||||
case PointType.SCANDIC: {
|
||||
return intl.formatMessage(
|
||||
{
|
||||
id: "price.numberOfScandicPoints",
|
||||
defaultMessage:
|
||||
"{numberOfScandicPoints, plural, one {Point} other {Points}}",
|
||||
},
|
||||
{
|
||||
numberOfScandicPoints: points,
|
||||
}
|
||||
)
|
||||
}
|
||||
case PointType.EUROBONUS: {
|
||||
return intl.formatMessage(
|
||||
{
|
||||
id: "price.numberOfEuroBonusPoints",
|
||||
defaultMessage:
|
||||
"{numberOfEuroBonusPoints, plural, one {EB Point} other {EB Points}}",
|
||||
},
|
||||
{
|
||||
numberOfEuroBonusPoints: points,
|
||||
}
|
||||
)
|
||||
}
|
||||
default: {
|
||||
const _exhaustiveCheck: never = pointsType
|
||||
return "Points"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,11 +8,7 @@ import { getHotelRoom } from "@scandic-hotels/trpc/routers/booking/helpers"
|
||||
import { mapRoomDetails } from "@/components/HotelReservation/MyStay/utils/mapRoomDetails"
|
||||
import { MyStayContext } from "@/contexts/MyStay"
|
||||
|
||||
import {
|
||||
calculateTotalPoints,
|
||||
calculateTotalPrice,
|
||||
isAllRoomsCancelled,
|
||||
} from "./helpers"
|
||||
import { calculateTotalPrice, isAllRoomsCancelled } from "./helpers"
|
||||
|
||||
import type { InitialState, MyStayState } from "@/types/stores/my-stay"
|
||||
|
||||
@@ -56,8 +52,6 @@ export function createMyStayStore({
|
||||
|
||||
const allRoomsAreCancelled = isAllRoomsCancelled(mappedRooms)
|
||||
|
||||
const totalPoints = calculateTotalPoints(mappedRooms, allRoomsAreCancelled)
|
||||
|
||||
const totalPrice = calculateTotalPrice(
|
||||
mappedRooms,
|
||||
bookedRoom.currencyCode,
|
||||
@@ -80,7 +74,6 @@ export function createMyStayStore({
|
||||
refId,
|
||||
rooms: mappedRooms,
|
||||
savedCreditCards,
|
||||
totalPoints,
|
||||
totalPrice,
|
||||
isPastBooking,
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
|
||||
import type { PointType } from "@scandic-hotels/common/constants/pointType"
|
||||
|
||||
interface TPrice {
|
||||
additionalPrice?: number
|
||||
@@ -6,6 +7,7 @@ interface TPrice {
|
||||
currency: CurrencyEnum
|
||||
price: number
|
||||
regularPrice?: number
|
||||
pointsType?: PointType | null
|
||||
}
|
||||
|
||||
export interface Price {
|
||||
|
||||
@@ -57,7 +57,6 @@ export interface MyStayState {
|
||||
refId: string
|
||||
rooms: Room[]
|
||||
savedCreditCards: CreditCard[] | null
|
||||
totalPoints: number
|
||||
totalPrice: string
|
||||
isPastBooking: boolean
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user