fix: improve VAT rounding and add per-line VAT display support
Fix calculateVat/calculateTotal to use proper monetary rounding. Add getVatSummaryFromItems helper for deriving VAT labels from mixed-rate items. Update invoice preview, review, and detail pages to show per-rate VAT breakdown instead of a single aggregated rate. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -440,7 +440,7 @@ export function InvoicePDF({ invoice, customer, items, company, originalInvoiceN
|
||||
))
|
||||
) : (
|
||||
<View style={styles.totalRow}>
|
||||
<Text style={styles.totalLabel}>Moms ({invoice.vat_rate ?? 25}%):</Text>
|
||||
<Text style={styles.totalLabel}>Moms ({invoice.vat_rate ?? (vatByRate.size === 1 ? vatByRate.keys().next().value : 25)}%):</Text>
|
||||
<Text style={styles.totalValue}>{formatCurrency(invoice.vat_amount, invoice.currency)}</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
@@ -118,14 +118,14 @@ export function getVatRules(
|
||||
* Calculate VAT amount
|
||||
*/
|
||||
export function calculateVat(subtotal: number, vatRate: number): number {
|
||||
return subtotal * (vatRate / 100)
|
||||
return Math.round(subtotal * vatRate) / 100
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate total including VAT
|
||||
*/
|
||||
export function calculateTotal(subtotal: number, vatRate: number): number {
|
||||
return subtotal + calculateVat(subtotal, vatRate)
|
||||
return Math.round((subtotal + calculateVat(subtotal, vatRate)) * 100) / 100
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -153,6 +153,36 @@ export function getVatTreatmentLabel(treatment: VatTreatment): string {
|
||||
return labels[treatment]
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive a display-friendly VAT summary from invoice line items.
|
||||
*
|
||||
* - If all items share a single rate → returns that rate's label and treatment
|
||||
* - If items have mixed rates → returns "Blandade momssatser" with null rate/treatment
|
||||
*/
|
||||
export function getVatSummaryFromItems(
|
||||
items: { vat_rate?: number | null }[]
|
||||
): { label: string; treatment: VatTreatment | null; rate: number | null; isMixed: boolean } {
|
||||
const rates = new Set(items.map((item) => item.vat_rate ?? 25))
|
||||
|
||||
if (rates.size === 1) {
|
||||
const rate = rates.values().next().value!
|
||||
const treatment = getVatTreatmentForRate(rate)
|
||||
return {
|
||||
label: getVatTreatmentLabel(treatment),
|
||||
treatment,
|
||||
rate,
|
||||
isMixed: false,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
label: 'Blandade momssatser',
|
||||
treatment: null,
|
||||
rate: null,
|
||||
isMixed: true,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get moms ruta description
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user