sozsoft-platform/ui/src/utils/basketUtils.ts

177 lines
4.9 KiB
TypeScript
Raw Normal View History

import { BillingCycle, BasketItem, ProductDto } from '@/proxy/order/models'
2026-02-24 20:44:16 +00:00
export interface BasketData {
items: BasketItem[]
subtotal: number
vatTotal: number
total: number
globalBillingCycle: BillingCycle
globalPeriod: number
2026-02-24 20:44:16 +00:00
}
export const initialBasketData: BasketData = {
2026-02-24 20:44:16 +00:00
items: [],
2026-07-07 13:41:07 +00:00
subtotal: 0,
vatTotal: 0,
2026-02-24 20:44:16 +00:00
total: 0,
globalBillingCycle: 'yearly',
globalPeriod: 1,
}
2026-02-24 20:44:16 +00:00
export const calculateItemPrice = (
product: ProductDto,
quantity: number,
billingCycle: BillingCycle,
period: number = 1,
2026-02-24 20:44:16 +00:00
): number => {
const price = billingCycle === 'monthly' ? product.monthlyPrice! : product.yearlyPrice!
const vatRate = product.vatRate ?? 0.2
return roundMoney(price * quantity * period * (1 + vatRate))
}
2026-07-07 13:41:07 +00:00
const roundMoney = (value: number): number => Math.round(value * 100) / 100
2026-07-07 13:41:07 +00:00
const calculateItemVatAmount = (totalPrice: number, vatRate: number = 0.2): number =>
vatRate > 0 ? roundMoney(totalPrice - totalPrice / (1 + vatRate)) : 0
2026-07-07 13:41:07 +00:00
const withItemVat = (
product: ProductDto,
quantity: number,
billingCycle: BillingCycle,
period: number,
): BasketItem => {
const vatRate = product.vatRate ?? 0.2
const totalPrice = calculateItemPrice(product, quantity, billingCycle, period)
2026-07-07 13:41:07 +00:00
return {
product,
quantity,
billingCycle,
vatRate,
vatAmount: calculateItemVatAmount(totalPrice, vatRate),
totalPrice,
}
}
2026-07-07 13:41:07 +00:00
const calculateBasketTotals = (items: BasketItem[]) => {
const total = roundMoney(items.reduce((sum, item) => sum + item.totalPrice, 0))
const vatTotal = roundMoney(items.reduce((sum, item) => sum + (item.vatAmount ?? 0), 0))
2026-07-07 13:41:07 +00:00
return {
subtotal: roundMoney(total - vatTotal),
vatTotal,
total,
}
}
2026-02-24 20:44:16 +00:00
export const addItemToBasket = (
basketData: BasketData,
2026-02-24 20:44:16 +00:00
product: ProductDto,
quantity: number,
billingCycle: BillingCycle,
): BasketData => {
const isAlreadyInBasket = isItemInBasketData(basketData.items, product.id, billingCycle)
2026-02-24 20:44:16 +00:00
if (!product.isQuantityBased && isAlreadyInBasket) {
return basketData
2026-02-24 20:44:16 +00:00
}
const existingItemIndex = basketData.items.findIndex(
(item) => item.product.id === product.id && item.billingCycle === billingCycle,
)
2026-02-24 20:44:16 +00:00
let newItems
2026-02-24 20:44:16 +00:00
if (existingItemIndex > -1) {
newItems = [...basketData.items]
newItems[existingItemIndex].quantity += quantity
2026-07-07 13:41:07 +00:00
newItems[existingItemIndex] = withItemVat(
2026-02-24 20:44:16 +00:00
product,
newItems[existingItemIndex].quantity,
billingCycle,
basketData.globalPeriod,
)
2026-02-24 20:44:16 +00:00
} else {
const actualQuantity = !product.isQuantityBased ? 1 : quantity
2026-07-07 13:41:07 +00:00
newItems = [
...basketData.items,
withItemVat(product, actualQuantity, billingCycle, basketData.globalPeriod),
]
2026-02-24 20:44:16 +00:00
}
const updatedState = { ...basketData, items: newItems, ...calculateBasketTotals(newItems) }
return updatedState
}
2026-02-24 20:44:16 +00:00
export const removeItemFromBasket = (basketData: BasketData, id: string): BasketData => {
const newItems = basketData.items.filter((item) => item.product.id !== id)
const updatedState = { ...basketData, items: newItems, ...calculateBasketTotals(newItems) }
return updatedState
}
export const updateBasketItemQuantity = (
basketData: BasketData,
id: string,
quantity: number,
): BasketData => {
const newItems = basketData.items
.map((item) => {
if (item.product.id === id) {
if (quantity <= 0) return null
if (!item.product.isQuantityBased) return item
return withItemVat(item.product, quantity, item.billingCycle, basketData.globalPeriod)
}
return item
})
.filter(Boolean) as BasketItem[]
const updatedState = { ...basketData, items: newItems, ...calculateBasketTotals(newItems) }
return updatedState
}
2026-02-24 20:44:16 +00:00
export const clearBasket = (): BasketData => {
return initialBasketData
}
2026-02-24 20:44:16 +00:00
export const setBasketGlobalBillingCycle = (basketData: BasketData, cycle: BillingCycle): BasketData => {
const newItems = basketData.items.map((item) =>
withItemVat(item.product, item.quantity, cycle, basketData.globalPeriod),
)
const updatedState = {
...basketData,
globalBillingCycle: cycle,
items: newItems,
...calculateBasketTotals(newItems),
}
return updatedState
}
2026-02-24 20:44:16 +00:00
export const setBasketGlobalPeriod = (basketData: BasketData, period: number): BasketData => {
const newItems = basketData.items.map((item) =>
withItemVat(item.product, item.quantity, item.billingCycle, period),
)
const updatedState = {
...basketData,
globalPeriod: period,
items: newItems,
...calculateBasketTotals(newItems),
}
return updatedState
}
export const isItemInBasket = (
basketData: BasketData,
productId: string,
billingCycle: BillingCycle,
): boolean => {
return basketData.items.some(
(item) => item.product.id === productId && item.billingCycle === billingCycle,
)
}
export const isItemInBasketData = (
items: BasketItem[],
productId: string,
billingCycle: BillingCycle,
): boolean => {
return items.some((item) => item.product.id === productId && item.billingCycle === billingCycle)
}