Glossary & Data Model
Terms
ZATCA — Saudi Arabia's Zakat, Tax and Customs Authority. Runs the mandatory e‑invoicing platform ("Fatoora") that Saudi businesses report invoices to.
E‑invoice — the structured XML document Dentolize builds from a clinic invoice and reports to ZATCA. Not a copy of the stored total; it is rebuilt from line items and its totals are recomputed.
Simplified invoice (B2C) — a patient‑facing sale reported to ZATCA, typically cleared with a QR code on the receipt. Built by reportSimplifiedInvoice.js.
Standard invoice (B2B) — an insurance/business invoice with buyer details, reported through the same platform. Built by reportStandardInvoice.js.
Taxable base / taxable amount — the amount VAT is charged on: (price − discount) × quantity, before tax.
VAT category — the tax bucket a line falls in: standard 15%, standard 5%, or zero‑rated / exempt (codes O/Z/E). Totals are aggregated per category.
BR‑CO‑17 — the EN16931 / ZATCA business rule that a VAT category's tax amount is the category's taxable base times the rate, rounded once. The fix aligns Dentolize's e‑invoice with this rule.
Aggregate‑then‑round vs round‑then‑aggregate — the crux of this fix. round(rate × Σ base) (correct, one rounding) vs Σ round(rate × baseᵢ) (old, can drift a cent on multi‑line invoices).
EGS unit — the "E‑invoice Generation Solution" identity that signs and submits e‑invoices to ZATCA, carrying the clinic's certificates and invoice counter.
Invoice counter / previous‑invoice hash — each e‑invoice is chained to the one before it via a counter number and the prior document's hash. Configured on the ZATCA Integration screen (invoiceCounterNumber, lastSubmittedInvoiceHash).
acceptWarning — a flag threaded through the calculation; it does not affect decimal precision. Rounding always uses Decimal(...).toFixed(2).
halalah — 1/100 of a Saudi Riyal (SAR); "a cent" throughout this documentation.
Key code locations
| What | Where |
|---|---|
| The fix — document tax totals | packages/zatca/src/zatca/calc.ts → constructTaxTotal (:203), category VAT (:344‑364), Calc (:514) |
| Compiled output the server runs | packages/zatca/lib/zatca/calc.js |
| Per‑line tax (unchanged) | calc.ts → constructLineItemTotals (:126‑131) |
| App‑side invoice total (source of truth) | packages/server/.../invoices/invoiceUtils.js → calculateInvoiceValues (:96, VAT :163, total :205) |
| Line‑item builder for ZATCA | packages/server/src/services/eInvoices/invoiceCalculator.js (:300‑364; per‑unit discount :313) |
| Simplified reporter | packages/server/src/services/eInvoices/reportSimplifiedInvoice.js (:129) |
| Standard reporter | packages/server/src/services/eInvoices/reportStandardInvoice.js (:145) |
| Line‑item types | packages/zatca/src/zatca/templates/simplified_tax_invoice_template.ts (:180‑202) |
Data model notes
Invoice (packages/prisma/schema.prisma, model at :2620)
Monetary fields relevant here are stored as Float (not fixed‑point Decimal):
| Field | Line | Type |
|---|---|---|
total | 2622 | Float |
tax | 2623 | Float |
taxPercent | 2624 | Float |
discount | 2625 | Float |
subtotal | 2629 | Float |
insurance | 2633 | Float |
A few settlement fields are Decimal(15,2) — paid (:2632), balance (:2642), paidDiagnosticFee (:2628). The mix of Float and Decimal is the root reason sub‑cent artifacts exist; converting the monetary fields to Decimal is a noted long‑term follow‑up, not part of this PR.
Operation (model at ~:2233)
Line‑level monetary fields (price, total, amount, discount, tax, insurance, …) are likewise Float; only paid is Decimal(15,2).
Per‑company ZATCA configuration
EInvoiceSettings(:6993‑7000) —autoSubmit(auto‑submission toggle), 1:1 withCompany.ZatcaCredentials(:7173‑7188) —isConfigured(onboarded flag),csr,privateKey, compliance (sandbox) and production certificate/secret pairs,lastSubmittedInvoiceHash,invoiceCounterNumber.Company.eInvoiceProvider(:6942) — defaults to"ZATCA".- Sandbox vs production is chosen by environment (
process.env.XLZ_ENV), not per company.
The worked example, once more
Two lines, 15% VAT, taxable base 100.10 each:
| Method | Total | VAT |
|---|---|---|
| App (aggregate VAT) | 230.23 | 30.03 |
| Old ZATCA (per‑line VAT) | 230.22 | 30.02 |
| New ZATCA (category VAT) | 230.23 | 30.03 |
After the fix, the ZATCA column equals the app column — by construction.