An invoice total looks like three multiplications and an addition. Then you print the invoice and the lines do not add up to the total: one centime is missing, or one too many. The usual cause is a double, or a tax that was rounded once on the total while the printed lines were rounded one by one.

Algerian invoices add a second difficulty. A cash payment carries the droit de timbre de quittance, and its rules changed with the 2025 finance law: an exemption threshold, three brackets, a minimum, and no ceiling any more. An engine that still charges a flat 1 % gives a different amount from the current law on many cash invoices.

dz_invoice_tax is a pure Dart engine for exactly this. It computes TVA per rate, line and document discounts and the droit de timbre in integer centimes, under dated rules that cite the law they come from.

What it does

Exact money. Amounts are BigInt centimes. Quantities (6 decimal places) and rates (4 decimal places) are decimal strings. The computation never uses a double.

TVA per line, then per rate. Each line's TVA is rounded once. The breakdown per rate adds up those rounded figures, so the printed lines always reconcile with the totals.

Discounts. Each line can have a rate or fixed-amount discount. A document discount is then spread over the lines by largest remainder, so no centime is lost or invented.

Droit de timbre de quittance, as set by the 2025 finance law. Cash payments only. Amounts up to 300 DA are exempt. Above that, the duty is 1, 1.5 or 2 DA per 100 DA (or part of 100 DA), with a 5 DA minimum and no ceiling.

Configurable, dated rules. DzTaxRules holds every rate, threshold, bracket, taxable payment method and rounding mode. It also records the date the rules take effect, the date they were last reviewed and the legal texts behind them.

Credit notes. Negative quantities give the exact negation of the invoice, centime for centime.

Other regimes. Sellers outside TVA (IFU) and exempt lines with a legal reference are supported.

Helpers. parseScaled, divRoundHalfAway, divRound, allocateLargestRemainder and normaliseRate are public. The package is pure Dart with no dependencies.

How to use it

Add it from GitHub:

dependencies: dz_invoice_tax: git: https://github.com/AbdeldjalilChougui/dz_invoice_tax 

Then build an invoice and compute it. Prices are in centimes, so 85000 is 850,00 DA:

import 'package:dz_invoice_tax/dz_invoice_tax.dart'; void main() { final invoice = DzInvoice( lines: [ DzInvoiceLine(quantity: '12', unitPriceHt: 85000, tvaRate: '0.19'), DzInvoiceLine( quantity: '3.5', unitPriceHt: 124900, tvaRate: '0.19', discount: const DzDiscount.rate('0.05'), ), DzInvoiceLine(quantity: '100', unitPriceHt: 1250, tvaRate: '0.09'), ], documentDiscount: const DzDiscount.rate('0.02'), paymentMethod: DzPaymentMethod.cash, ); final t = computeInvoice(invoice); // uses DzTaxRules.current print(t.totalHt); // 1529086 (15 290,86 DA) for (final b in t.tvaBreakdown) { print('${b.rate}: ${b.amount} on ${b.base}'); } // 0.09: 11025 on 122500 / 0.19: 267251 on 1406586 print(t.totalTtc); // 1807362 (18 073,62 DA) print(t.timbre); // 18100 (181 DA = 181 tranches x 1 DA) print(t.netToPay); // 1825462 (18 254,62 DA) } 

The result also has grossHt, lineDiscountTotal, documentDiscountTotal and tvaTotal. Note that totalTtc is totalHt + tvaTotal, and the stamp duty is added only in netToPay.

computeTimbre gives the duty on any amount, for example the cash part of a split payment:

final duty = computeTimbre( BigInt.from(3500000), // 35 000 DA paymentMethod: DzPaymentMethod.cash, rules: DzTaxRules.current.timbre, ); // 52500 centimes = 350 tranches x 1.5 DA 

To change a rule, pass your own:

final rules = DzTaxRules.current.copyWith( rounding: DzRounding.halfEven, timbre: DzTaxRules.current.timbre.copyWith( taxableMethods: {DzPaymentMethod.cash, DzPaymentMethod.cheque}, ), ); computeInvoice(invoice, rules: rules); 

The default rules and where they come from

DzTaxRules.algeria2026, also available as DzTaxRules.current, takes effect on 1 January 2026. It was checked against the published texts on 24 September 2026. The README gives the legal text behind each line:

  • Standard TVA rate 0.19: art. 21 of the Code des taxes sur le chiffre d'affaires (CTCA), unchanged by LF 2025 and LF 2026.
  • Reduced TVA rate 0.09: art. 23 CTCA, restated at 9 % by art. 53 LF 2026.
  • No 0 % rate. A supply without TVA is an exemption, so you mark the line exempt: true with its exemptionRef. A bare 0 rate is refused by default.
  • Timbre exemption: up to 300 DA, none. Art. 100-I of the Code du timbre as rewritten by art. 46 LF 2025.
  • Timbre brackets: 1 DA per 100 DA up to 30 000 DA, 1.5 DA up to 100 000 DA, 2 DA above. A bracket's rate applies to the whole amount, not only the part inside the bracket, and any part of 100 DA counts as a full 100 DA.
  • Minimum 5 DA, no maximum. LF 2025 removed the old ceiling of 10 000 DA.
  • Cash only. Cheque, transfer, cash deposited at a bank and electronic payment are exempt under art. 258, 258 quater and 258 quinquies of the Code du timbre.
  • Timbre base: the invoice TTC.

The README also shows how much this matters with five cases where an older flat-1 % schedule and the current law disagree. A cash amount of 1 190 DA gives 12 DA instead of 11,90 DA. A cash amount of 11,90 DA gives nothing instead of 5 DA, because it is under 300 DA. A cash amount of 1 190 000 DA gives 23 800 DA instead of 2 500 DA, because there is no ceiling. test/current_law_test.dart covers each of them.

Rounding: what the law does not say

The README is careful here. No Algerian text was found that sets how TVA is rounded on an individual invoice line. Art. 80 bis CTCA refers to art. 324 of the CIDTA, which rounds tax assessment bases down to the dinar or the ten dinars. That rule applies to returns and assessments, not to invoice lines, so the package does not apply it. The text of art. 324 CIDTA was read only in a secondary source.

The default is half away from zero at the centime. Accountants expect it, and it keeps credit notes exact mirrors of their invoices. It is a choice, not a legal requirement, so DzTaxRules.rounding lets you switch to halfEven or towardZero.

Things to know

  • Keeping the rules current is up to you. The defaults were reviewed on 24 September 2026. If a later finance law, including a supplementary one, changes a rate or threshold, pass updated DzTaxRules. No rule set before 2026 is included.
  • The engine does not classify goods. It does not decide whether a product is taxed at 19 % or 9 %, or whether it is exempt. The caller gives each line its rate.
  • Cheque and transfer exemptions are conditional. Art. 258 exempts them only if the receipt shows the cheque or transfer details. The engine trusts paymentMethod.
  • Split payments. An invoice has one payment method. For a partial cash payment, call computeTimbre on the cash amount yourself.
  • Taxes not covered. The engine does not compute other taxes, such as TIC or withholding taxes, or their rounding on returns.
  • No formatting. Amounts are centimes, so display formatting is up to you. example/main.dart prints a complete invoice.
  • JSON. toJson() writes each amount as an int when it is below 2^53, and as a decimal string when it is larger. DzInvoice.fromJson also reads an older wire shape (qty, discountRate, globalDiscountRate, timbreApplicable and so on), and the README has the migration notes.

Why I built it

Two problems from the README. The first is arithmetic: an invoice has to reconcile to the centime, credit notes have to mirror their invoices exactly, and a document discount has to be spread over the lines without losing or inventing a centime. That rules out double and it fixes the order of operations.

The second is the law. The stamp duty schedule changed with LF 2025, and a hard-coded 1 % with a 2 500 DA ceiling is now wrong in several ways at once. So the rules are data, not code: each default carries its article, the date it takes effect and the date it was last checked, and you can replace any of them when the next finance law changes something.

Get the code

The source, the full legal table with links to the official texts, the fixtures and the tests are on GitHub: https://github.com/AbdeldjalilChougui/dz_invoice_tax. It is MIT licensed.

Originally published at abdeldjalilchougui.biz.