On this page
What to verifyThe core assertionHighest‑value test: the multi‑line driftTest matrixConsistency checks within one e‑invoiceExplicitly unchanged — assert these did NOT moveEdge cases and known boundariesWhere to look in codeBuild / smokeSandbox noteFor Quality
What to verify
The invariant under test: the total reported to ZATCA equals the invoice's stored/displayed total, and the e‑invoice is internally consistent. Focus on the aggregation the fix changed — document‑level tax total, per‑category subtotals, and payable amount.
The core assertion
For any invoice:
ZATCA TaxInclusiveAmount == ZATCA PayableAmount == Invoice.total
ZATCA document TaxAmount == Invoice.tax
Σ per-category TaxSubtotal.TaxAmount == ZATCA document TaxAmount
The document tax for each category must equal round(rate × Σ taxable_base) — a single rounding — not Σ round(rate × baseᵢ).
Highest‑value test: the multi‑line drift
Reproduce the PR's own case and assert the corrected result:
- Input: two lines, 15% VAT, taxable base
100.10each (Σ =200.20). - Expected (new): VAT
30.03, total230.23— matching the app. - Guard against regression: the old code produced
30.02/230.22. A test asserting30.03fails on the old algorithm and passes on the new one.
Vary the fractional cents so the per‑line rounding drifts up in some cases and down in others (the real bug was 2999.99 → 3000.00, a drift up; the example above drifts down). Both must now match the invoice total.
Test matrix
| Dimension | Cases to cover |
|---|---|
| Line count | 1 line (must be unchanged), 2 lines, many lines |
| VAT rate | 15% only, 5% only, mixed 15% + 5% in one invoice, zero‑rated/exempt lines present |
| Fractional cents | bases chosen so per‑line VAT rounds up; and so it rounds down |
| Discounts | no discount; line discounts; discount not evenly divisible by quantity (see edge below) |
| Insurance | patient (B2C simplified) and insurance (B2B standard) invoices |
| Quantity | quantity = 1; quantity > 1 |
Consistency checks within one e‑invoice
- Document
cac:TaxTotalTaxAmount== sum ofcac:TaxSubtotalTaxAmountvalues (for standard‑rated categories). LegalMonetaryTotal.TaxInclusiveAmount==LegalMonetaryTotal.PayableAmount.TaxInclusiveAmount==TaxExclusiveAmount + document TaxAmount.- The 15% subtotal's
TaxAmount==round(0.15 × its TaxableAmount); likewise 5%.
Explicitly unchanged — assert these did NOT move
- Per‑line
cac:TaxTotalamounts. Line‑level tax is stillround(line_extension_amount × VAT_percent). A test that pins line‑level values protects against accidental change. - Zero‑rated / exempt handling (
vat_categorycodesO/Z/E) — separate code path, should be untouched. - Line extension amounts and price/allowance‑charge fields.
Edge cases and known boundaries
- Single‑line invoice: old and new formulas are identical — verify no diff.
- Per‑unit discount rounding (out of scope, but worth a probe): a line whose total discount doesn't divide evenly by quantity can shift the taxable base by a cent via
money(op.discount / op.amount)ininvoiceCalculator.js:313. If a residual mismatch appears after this fix, this is the prime suspect — file it as the documented follow‑up, not as a regression of this PR. - Float precision: because monetary fields are
Float, expect artifacts like15.014999…; assertions should compare rounded 2‑dp values, and test data should include values that expose float boundaries.
Where to look in code
- Changed logic:
packages/zatca/src/zatca/calc.ts—constructTaxTotal(:203), category VAT computation (:344‑364),Calcwiring ofLegalMonetaryTotal(:556‑564). - Compiled mirror that runtime uses:
packages/zatca/lib/zatca/calc.js— confirm it reflects the source (the PR commits it). - App‑side source of truth to compare against:
invoiceUtils.jscalculateInvoiceValues(:163,:205).
Build / smoke
packages/zatcamust compile cleanly (tsc), andlib/must be regenerated so the server (main: lib/index.js) picks up the change — verify the committedlib/zatca/calc.jsmatches the source rather than trusting the.tsalone.
Sandbox note
The walkthrough sandbox company is not onboarded to ZATCA (isConfigured false; env not production), so invoices show "E‑Invoice Not Reported" and nothing is transmitted. End‑to‑end verification against real ZATCA XML needs an onboarded/compliance environment; unit‑level verification of the calculation does not and is the more reliable gate here.