Dentolize · ZATCA E-Invoice Rounding Fix Walkthrough
On this pageIn one sentenceThe problem, plainlyWhy it happenedThe fixWhat did not changeHow to read this site

Overview

Feature: Keep the ZATCA e‑invoice total in sync with the invoice total (rounding). Status: Unreleased — on the branch fix/rounding-money-in-zatca-invoice (PR #360). Scope: One file of real logic — packages/zatca/src/zatca/calc.ts (plus its committed compiled output packages/zatca/lib/zatca/calc.js).

In one sentence

When Dentolize reports an invoice to Saudi Arabia's ZATCA e‑invoicing platform, the tax total it sends is now computed the same way the app computes the invoice total the patient sees and pays — so the two can no longer disagree by a cent.

The problem, plainly

Every invoice in Dentolize has a total that the system stores, shows the patient, and posts to the books. For Saudi clinics, that same invoice also has to be reported to the government's ZATCA e‑invoicing platform (Fatoora).

Those two numbers are produced by two different pieces of code. Before this change they could round VAT differently and land a cent apart. A real report from the field:

An invoice displayed and paid as SAR 2999.99 was reported to ZATCA as SAR 3000.00.

A one‑cent gap sounds trivial, but in a tax‑reporting context it is not cosmetic:

  • The patient's receipt and the government record now tell two different stories.
  • Bookkeeping reconciliation flags the difference.
  • Repeated across thousands of invoices, the drift accumulates and undermines trust in the e‑invoice numbers.

Why it happened

The two calculations disagreed on when to round the VAT:

WhoHow VAT was computedRule
App / database (invoiceUtils.js)VAT once, on the whole taxable base: round(rate × Σ base)This is the number stored, shown, and paid
ZATCA package (calc.ts) — oldVAT per line, each line rounded, then summed: Σ round(rate × baseᵢ)Drifts a cent on multi‑line invoices

On a single‑line invoice the two are identical. On a multi‑line invoice the per‑line fractional cents can round up (or down) independently, and Σ round(rate × baseᵢ) lands a cent away from round(rate × Σ baseᵢ). That cent flowed straight into the amounts ZATCA received.

The fix

In packages/zatca/src/zatca/calc.ts, VAT for each tax category (15% standard, 5%) is now computed once on the aggregated taxable base and rounded a single timeround(rate × Σ base). That is:

  1. The EN16931 / ZATCA category rule (BR‑CO‑17), and
  2. Exactly how the app already computes the invoice total.

Because both sides now apply the same rule, they agree by construction. The e‑invoice's document tax total, its per‑category subtotals, and the payable amount are all derived from this single figure, so they are internally consistent too.

What did not change

  • Per‑line tax amounts on each invoice line are untouched — line‑level rounding was already correct and stays within ZATCA's line‑vs‑category tolerance.
  • No database, schema, or UI change. Invoices already stored keep their totals; only the number the ZATCA XML carries is affected, and only for multi‑line invoices where the old drift occurred.
  • No new settings. Nothing to enable or configure.

How to read this site

  • Walkthrough — a screenshot tour of where invoices, totals, and ZATCA settings live in the product.
  • Feature breakdown — the cent problem and the fix in depth (business first, then the exact code), and how a total travels from an invoice to ZATCA.
  • By team — the same story framed for marketing, sales, support, training, stakeholders, and quality.
  • Reference — glossary and the relevant data‑model notes.
Honesty note: this documentation describes what the code on the branch actually does, verified against /work/repo. Where the product's sandbox can't demonstrate something (e.g. a fully onboarded ZATCA connection), the text says so rather than implying otherwise.