Merged in feature/SW-3616-partner-points-my-stay (pull request #3407)
Feature/SW-3616 partner points my stay * feat(SW-3616): display partner points in my stays * null check roomPointType * Lowercase POINTS in my stay * include other than Scandic points when displaying price details modal Approved-by: Anton Gunnarsson
This commit is contained in:
@@ -71,6 +71,16 @@ export function calculateTotalPrice(rooms: Room[], currency: CurrencyEnum) {
|
|||||||
break
|
break
|
||||||
case PriceTypeEnum.points:
|
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.currency = CurrencyEnum.POINTS
|
||||||
total.local.price = total.local.price + room.totalPoints
|
total.local.price = total.local.price + room.totalPoints
|
||||||
}
|
}
|
||||||
@@ -135,3 +145,11 @@ export function calculateTotalPrice(rooms: Room[], currency: CurrencyEnum) {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const roomPointTypeToCurrencyMap: Record<
|
||||||
|
NonNullable<Room["roomPointType"]>,
|
||||||
|
CurrencyEnum
|
||||||
|
> = {
|
||||||
|
Scandic: CurrencyEnum.POINTS,
|
||||||
|
EuroBonus: CurrencyEnum.EUROBONUS,
|
||||||
|
}
|
||||||
|
|||||||
180
apps/scandic-web/stores/my-stay/helpers.test.ts
Normal file
180
apps/scandic-web/stores/my-stay/helpers.test.ts
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
import { beforeAll, describe, expect, it, vi } from "vitest"
|
||||||
|
|
||||||
|
import { calculateTotalPrice } from "./helpers"
|
||||||
|
|
||||||
|
describe("calculateTotalPrice", () => {
|
||||||
|
const baseRoom: Parameters<typeof calculateTotalPrice>[0][0] = {
|
||||||
|
totalPrice: 0,
|
||||||
|
isCancelled: false,
|
||||||
|
cheques: 0,
|
||||||
|
roomPoints: 0,
|
||||||
|
roomPointType: null,
|
||||||
|
totalPoints: 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}`,
|
||||||
|
}))
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
vi.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should return correct price for conflicting rooms (cash)", () => {
|
||||||
|
const rooms = [
|
||||||
|
{ totalPrice: 1000, isCancelled: false },
|
||||||
|
{ totalPrice: 500, isCancelled: false },
|
||||||
|
] as any
|
||||||
|
|
||||||
|
const result = calculateTotalPrice(
|
||||||
|
rooms,
|
||||||
|
"SEK" as any,
|
||||||
|
mockIntlSimple,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
expect(result).toBe("1500 SEK")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should ignore cancelled rooms if not all are cancelled", () => {
|
||||||
|
vi.mock("@scandic-hotels/common/utils/numberFormatting", () => ({
|
||||||
|
formatPrice: (_intl: any, price: number, currency: string) =>
|
||||||
|
`${price} ${currency}`,
|
||||||
|
}))
|
||||||
|
|
||||||
|
const rooms = [
|
||||||
|
{ totalPrice: 1000, isCancelled: false },
|
||||||
|
{ totalPrice: 500, isCancelled: true },
|
||||||
|
] as any
|
||||||
|
|
||||||
|
const result = calculateTotalPrice(
|
||||||
|
rooms,
|
||||||
|
"SEK" as any,
|
||||||
|
mockIntlSimple,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
expect(result).toBe("1000 SEK")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should format string for Vouchers", () => {
|
||||||
|
const result = calculateTotalPrice(
|
||||||
|
[{ ...baseRoom, vouchers: 2, totalPrice: -1, isCancelled: false }],
|
||||||
|
"SEK" as any,
|
||||||
|
mockIntlSimple,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
expect(result).toContain("2 Vouchers")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should handle mixed Cash and Points", () => {
|
||||||
|
const result = calculateTotalPrice(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
...baseRoom,
|
||||||
|
totalPrice: 100,
|
||||||
|
isCancelled: false,
|
||||||
|
totalPoints: 0,
|
||||||
|
roomPoints: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...baseRoom,
|
||||||
|
totalPrice: 0,
|
||||||
|
totalPoints: 20000,
|
||||||
|
roomPoints: 20000,
|
||||||
|
roomPointType: "Scandic",
|
||||||
|
isCancelled: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"SEK" as any,
|
||||||
|
mockIntlSimple,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(result).toMatch(/20000 Points \+ 100 SEK/)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should sum up Cheques correctly", () => {
|
||||||
|
const rooms = [{ cheques: 2, isCancelled: false }] as any
|
||||||
|
// CurrencyEnum.CC is usually imported, assuming it resolves or we check specific output
|
||||||
|
const result = calculateTotalPrice(
|
||||||
|
rooms,
|
||||||
|
"SEK" as any,
|
||||||
|
mockIntlSimple,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
expect(result).toContain("2 CC")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should combine Vouchers and Cash", () => {
|
||||||
|
const result = calculateTotalPrice(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
...baseRoom,
|
||||||
|
vouchers: 1,
|
||||||
|
totalPrice: -1,
|
||||||
|
isCancelled: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...baseRoom,
|
||||||
|
totalPrice: 500,
|
||||||
|
isCancelled: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"SEK" as any,
|
||||||
|
mockIntlSimple,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
expect(result).toMatch(/1 Voucher \+ 500 SEK/)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should combine Eurobonus points and Cash", () => {
|
||||||
|
const result = calculateTotalPrice(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
...baseRoom,
|
||||||
|
roomPoints: 1,
|
||||||
|
roomPointType: "EuroBonus",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...baseRoom,
|
||||||
|
totalPrice: 500,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"SEK" as any,
|
||||||
|
mockIntlSimple,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
expect(result).toMatch(/1 EuroBonus \+ 500 SEK/)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should combine Eurobonus points, Scandic Friends points and Cash", () => {
|
||||||
|
const result = calculateTotalPrice(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
...baseRoom,
|
||||||
|
roomPoints: 1,
|
||||||
|
roomPointType: "EuroBonus",
|
||||||
|
totalPoints: 500,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...baseRoom,
|
||||||
|
totalPrice: 500,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"SEK" as any,
|
||||||
|
mockIntlSimple,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
expect(result).toMatch(/500 Points \+ 1 EuroBonus \+ 500 SEK/)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -6,7 +6,16 @@ import type { IntlShape } from "react-intl"
|
|||||||
import type { Room } from "@/types/stores/my-stay"
|
import type { Room } from "@/types/stores/my-stay"
|
||||||
|
|
||||||
export function calculateTotalPrice(
|
export function calculateTotalPrice(
|
||||||
rooms: Room[],
|
rooms: Pick<
|
||||||
|
Room,
|
||||||
|
| "cheques"
|
||||||
|
| "vouchers"
|
||||||
|
| "roomPoints"
|
||||||
|
| "roomPointType"
|
||||||
|
| "totalPoints"
|
||||||
|
| "totalPrice"
|
||||||
|
| "isCancelled"
|
||||||
|
>[],
|
||||||
currency: CurrencyEnum,
|
currency: CurrencyEnum,
|
||||||
intl: IntlShape,
|
intl: IntlShape,
|
||||||
allRoomsAreCancelled: boolean
|
allRoomsAreCancelled: boolean
|
||||||
@@ -20,55 +29,77 @@ export function calculateTotalPrice(
|
|||||||
if (room.cheques) {
|
if (room.cheques) {
|
||||||
total.cheques = total.cheques + room.cheques
|
total.cheques = total.cheques + room.cheques
|
||||||
}
|
}
|
||||||
|
|
||||||
if (room.vouchers) {
|
if (room.vouchers) {
|
||||||
total.vouchers = total.vouchers + room.vouchers
|
total.vouchers = total.vouchers + room.vouchers
|
||||||
}
|
}
|
||||||
if (room.totalPoints) {
|
|
||||||
total.points = total.points + room.totalPoints
|
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
|
||||||
|
}
|
||||||
|
|
||||||
// room.totalPrice is a negative value when
|
// room.totalPrice is a negative value when
|
||||||
// its a vouchers booking (╯°□°)╯︵ ┻━┻
|
// its a vouchers booking (╯°□°)╯︵ ┻━┻
|
||||||
if (room.totalPrice > 0) {
|
if (room.totalPrice > 0) {
|
||||||
total.cash = total.cash + room.totalPrice
|
total.cash = total.cash + room.totalPrice
|
||||||
}
|
}
|
||||||
|
|
||||||
return total
|
return total
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cash: 0,
|
cash: 0,
|
||||||
cheques: 0,
|
cheques: 0,
|
||||||
points: 0,
|
scandicFriendsPoints: 0,
|
||||||
|
partnerPoints: 0,
|
||||||
|
partnerPointsCurrency: null as Room["roomPointType"],
|
||||||
vouchers: 0,
|
vouchers: 0,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
let totalPrice = ""
|
const priceParts: string[] = []
|
||||||
if (totals.vouchers) {
|
if (totals.vouchers) {
|
||||||
const appendTotalPrice = totalPrice ? `${totalPrice} + ` : ""
|
priceParts.push(
|
||||||
totalPrice = `${appendTotalPrice}${totals.vouchers} ${intl.formatMessage(
|
`${totals.vouchers} ${intl.formatMessage(
|
||||||
{
|
{
|
||||||
id: "price.numberOfVouchers",
|
id: "price.numberOfVouchers",
|
||||||
defaultMessage:
|
defaultMessage:
|
||||||
"{numberOfVouchers, plural, one {Voucher} other {Vouchers}}",
|
"{numberOfVouchers, plural, one {Voucher} other {Vouchers}}",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
numberOfVouchers: totals.vouchers,
|
numberOfVouchers: totals.vouchers,
|
||||||
}
|
}
|
||||||
)}`
|
)}`
|
||||||
}
|
)
|
||||||
if (totals.cheques) {
|
|
||||||
totalPrice = `${totals.cheques} ${CurrencyEnum.CC}`
|
|
||||||
}
|
|
||||||
if (totals.points) {
|
|
||||||
const appendTotalPrice = totalPrice ? `${totalPrice} + ` : ""
|
|
||||||
totalPrice = `${appendTotalPrice}${totals.points} ${CurrencyEnum.POINTS}`
|
|
||||||
}
|
|
||||||
if (totals.cash) {
|
|
||||||
const appendTotalPrice = totalPrice ? `${totalPrice} + ` : ""
|
|
||||||
const cashPrice = formatPrice(intl, totals.cash, currency)
|
|
||||||
totalPrice = `${appendTotalPrice}${cashPrice}`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return totalPrice
|
if (totals.cheques) {
|
||||||
|
priceParts.push(`${totals.cheques} ${CurrencyEnum.CC}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totals.scandicFriendsPoints) {
|
||||||
|
priceParts.push(`${totals.scandicFriendsPoints} ${CurrencyEnum.POINTS}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totals.partnerPoints) {
|
||||||
|
priceParts.push(`${totals.partnerPoints} ${totals.partnerPointsCurrency}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totals.cash) {
|
||||||
|
const cashPrice = formatPrice(intl, totals.cash, currency)
|
||||||
|
priceParts.push(cashPrice)
|
||||||
|
}
|
||||||
|
|
||||||
|
return priceParts.join(" + ")
|
||||||
}
|
}
|
||||||
|
|
||||||
export function calculateTotalPoints(
|
export function calculateTotalPoints(
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ interface VatProps {
|
|||||||
const noVatCurrencies = [
|
const noVatCurrencies = [
|
||||||
CurrencyEnum.CC,
|
CurrencyEnum.CC,
|
||||||
CurrencyEnum.POINTS,
|
CurrencyEnum.POINTS,
|
||||||
|
CurrencyEnum.EUROBONUS,
|
||||||
CurrencyEnum.Voucher,
|
CurrencyEnum.Voucher,
|
||||||
CurrencyEnum.Unknown,
|
CurrencyEnum.Unknown,
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -176,6 +176,7 @@ export const bookingConfirmationSchema = z
|
|||||||
rateDefinition: rateDefinitionSchema,
|
rateDefinition: rateDefinitionSchema,
|
||||||
reservationStatus: z.string().nullable().default(""),
|
reservationStatus: z.string().nullable().default(""),
|
||||||
roomPoints: z.number(),
|
roomPoints: z.number(),
|
||||||
|
roomPointType: z.nullable(z.enum(["Scandic", "EuroBonus"])).catch(null),
|
||||||
roomPrice: z.number(),
|
roomPrice: z.number(),
|
||||||
roomTypeCode: z.string().default(""),
|
roomTypeCode: z.string().default(""),
|
||||||
totalPoints: z.number(),
|
totalPoints: z.number(),
|
||||||
|
|||||||
Reference in New Issue
Block a user