import { BillingCycle, BasketItem, ProductDto } from '@/proxy/order/models' export interface BasketData { items: BasketItem[] subtotal: number vatTotal: number total: number globalBillingCycle: BillingCycle globalPeriod: number } export const initialBasketData: BasketData = { items: [], subtotal: 0, vatTotal: 0, total: 0, globalBillingCycle: 'yearly', globalPeriod: 1, } export const calculateItemPrice = ( product: ProductDto, quantity: number, billingCycle: BillingCycle, period: number = 1, ): number => { const price = billingCycle === 'monthly' ? product.monthlyPrice! : product.yearlyPrice! const vatRate = product.vatRate ?? 0.2 return roundMoney(price * quantity * period * (1 + vatRate)) } const roundMoney = (value: number): number => Math.round(value * 100) / 100 const calculateItemVatAmount = (totalPrice: number, vatRate: number = 0.2): number => vatRate > 0 ? roundMoney(totalPrice - totalPrice / (1 + vatRate)) : 0 const withItemVat = ( product: ProductDto, quantity: number, billingCycle: BillingCycle, period: number, ): BasketItem => { const vatRate = product.vatRate ?? 0.2 const totalPrice = calculateItemPrice(product, quantity, billingCycle, period) return { product, quantity, billingCycle, vatRate, vatAmount: calculateItemVatAmount(totalPrice, vatRate), totalPrice, } } 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)) return { subtotal: roundMoney(total - vatTotal), vatTotal, total, } } export const addItemToBasket = ( basketData: BasketData, product: ProductDto, quantity: number, billingCycle: BillingCycle, ): BasketData => { const isAlreadyInBasket = isItemInBasketData(basketData.items, product.id, billingCycle) if (!product.isQuantityBased && isAlreadyInBasket) { return basketData } const existingItemIndex = basketData.items.findIndex( (item) => item.product.id === product.id && item.billingCycle === billingCycle, ) let newItems if (existingItemIndex > -1) { newItems = [...basketData.items] newItems[existingItemIndex].quantity += quantity newItems[existingItemIndex] = withItemVat( product, newItems[existingItemIndex].quantity, billingCycle, basketData.globalPeriod, ) } else { const actualQuantity = !product.isQuantityBased ? 1 : quantity newItems = [ ...basketData.items, withItemVat(product, actualQuantity, billingCycle, basketData.globalPeriod), ] } const updatedState = { ...basketData, items: newItems, ...calculateBasketTotals(newItems) } return updatedState } 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 } export const clearBasket = (): BasketData => { return initialBasketData } 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 } 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) }