garage-erp/apps/dashboard/shared/hooks/use-document-totals.ts
2026-04-23 14:38:41 +03:00

85 lines
2.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* useDocumentTotals
*
* Pure calculation hook for invoice-style documents (invoices, bills, estimates,
* credit notes, vendor credits, etc.).
*
* Inputs
* ────────────────────────────────────────────────────────────────────────────
* lineItems flat array of all items (parts + services + expenses)
* discountType 'no' | 'line_item_level' | 'transaction_level'
* discountAmount used when discountType === 'transaction_level'
* taxRate percentage (e.g. 15 for 15 %). Undefined → no tax.
*
* Calculation order
* ────────────────────────────────────────────────────────────────────────────
* 1. subTotal = Σ (qty × rate)
* 2. lineItemDiscount = Σ discount_amount (only when discountType === 'line_item_level')
* 3. transactionDiscount = discountAmount (only when discountType === 'transaction_level')
* 4. totalDiscount = lineItemDiscount + transactionDiscount
* 5. afterDiscount = subTotal - totalDiscount
* 6. taxAmount = afterDiscount × (taxRate / 100)
* 7. total = afterDiscount + taxAmount
*/
export type DocumentLineItem = {
quantity: number
rate: number
discount_amount?: number
}
export type UseDocumentTotalsInput = {
lineItems: DocumentLineItem[]
discountType?: string | null
discountAmount?: number
taxRate?: number
}
export type DocumentTotals = {
subTotal: number
lineItemDiscount: number
transactionDiscount: number
totalDiscount: number
afterDiscount: number
taxAmount: number
total: number
hasLineItems: boolean
}
export function useDocumentTotals({
lineItems,
discountType,
discountAmount,
taxRate,
}: UseDocumentTotalsInput): DocumentTotals {
const subTotal = lineItems.reduce((sum, item) => {
const qty = Number(item.quantity) || 0
const rate = Number(item.rate) || 0
return sum + qty * rate
}, 0)
const lineItemDiscount =
discountType === "line_item_level"
? lineItems.reduce((sum, item) => sum + (Number(item.discount_amount) || 0), 0)
: 0
const transactionDiscount =
discountType === "transaction_level" ? Number(discountAmount) || 0 : 0
const totalDiscount = lineItemDiscount + transactionDiscount
const afterDiscount = Math.max(0, subTotal - totalDiscount)
const taxAmount = taxRate ? afterDiscount * (taxRate / 100) : 0
const total = afterDiscount + taxAmount
return {
subTotal,
lineItemDiscount,
transactionDiscount,
totalDiscount,
afterDiscount,
taxAmount,
total,
hasLineItems: lineItems.length > 0,
}
}