VN VAT and statutory tax
Sub-ledger 5, migration0007.
What it records
VAT is booked on the document by the sub-ledger that raises it: input VAT (VAS 133) on bills by settlement and costing, output VAT (VAS 3331) on invoices by settlement. This sub-ledger keeps the register: onetax_entries row per taxable line, carrying the base amount, the declared rate, the tax amount, the GL account, the document date, the received date, the period and the deductibility flag.
That register is what makes the per-line integrity check and the received-date deductibility rule possible.
It also adds foreign-contractor tax, which has no document-level twin.
Native and posted-base evidence
tax_entries.tax_amount remains the statutory tax amount in the document’s native currency.
For every new row, the register also stores the journal line’s native currency, exact posted base amount, exact immutable entry/line links, and journal_line as the base-evidence kind.
The register writer finds that line inside the same posting transaction by organization, entry, control account, side and native amount.
It does not accept or resolve an FX rate again.
The ledger writer remains the one authority for as-of-date conversion and rounding, and the register records the exact base result already posted.
VAT-133-TIE and VAT-3331-TIE compare register base to GL base.
Their evidence also reports native totals by currency rather than adding unlike currencies together.
A period-scoped read is cumulative through that fiscal period’s end on both sides.
Historical rows were not backfilled.
A legacy row may be valued only when its durable entry link and native facts identify one exact same-organization journal line; missing, ambiguous, cross-organization or contradictory evidence makes the result partial or inconclusive.
A reversal is never re-rated: it counts only when the reversing line exactly swaps the original native amount, base amount, currency and stored rate.
The account legs
Input VAT is an asset, not a cost
This is the point most easily got wrong, so it is worth stating plainly. Vietnamese input VAT (thuế GTGT được khấu trừ) is recoverable. The business offsets it against output VAT rather than bearing it. So it belongs on1300 as an asset, not inside the expense or inventory leg it arrived with.
A supplier VAT invoice for 1,000,000 net plus 100,000 VAT posts three legs:
Both origins split correctly.
A payable raised through the sub-ledgers always did.
A payable that arrives as a document through the intake pipeline now does too: the recoverable VAT is peeled onto
1300, the gross stays the anchor for the AP credit and its obligation, the net is derived as gross - VAT, and a typed InconsistentVatError is raised rather than choosing between three extracted figures that do not add up.
The register line is written in the same transaction as the entry, through this sub-ledger’s own register writer rather than a second insert.
Full rule on Payables and input VAT; the agent that applies it is the Bookkeeper.Rows posted before that change carry their VAT inside the expense or inventory leg.
They need a bookkeeping correction rather than a document edit, and no Close checksum fails on them - the register ties at 0 against 0 - which is precisely why the split is asserted directly by a test rather than left to a reconciliation to find.
1300 is a control account
The same rule that governs AP control 2000 governs input VAT 1300: a debit to it with nothing in the register behind it is a balance the sub-ledger cannot explain.
VAT-133-TIE compares the tax_entries register total against the GL 1300 balance.
Two independent paths, one from the register and one from the ledger, that must agree.
That is why the register line has to travel with the leg rather than being written afterwards by a separate process, and in the agent path it is re-derived from the re-read record and written through the backend’s own tax.record_vat_entry in the same transaction as the entry.
Capability and MCP status
VAT and tax Books is a current, executable registry outcome served through the shared Books family, register and tie-out tools. That read surface exposes native structures, accounting links, 35-day freshness, exact maker/checker evidence where applicable, Assurance findings and control state. It does not create or edit a tax entry. The recoverable input-VAT split onto VAS 133 is finished posting behavior in the canonical Bookkeeper/sub-ledger transaction, not an MCP calculation or a future adapter.Per-line integrity
VAT-INTEGRITY-IN for input, VAT-INTEGRITY-OUT for output.
The output-side twin is a GreatBook addition: the v12 workbook’s B54 only covered input VAT, which meant a mistyped output VAT amount had nothing checking it.
This is where the rounding rule becomes load-bearing.
ROUND_HALF_UP at four decimal places is what makes ROUND(base x rate) reproduce the supplier’s own figure.
Banker’s rounding would differ by one dong on roughly half of all VAT lines, and every one of those would fail this check for a reason invisible in the numbers.The cash rule
A cash payment at or above 5,000,000 VND loses input-VAT deductibility under Vietnamese rules.VAT-CASH-5M is the must-be-zero check for it, and VAT-NONDEDUCTIBLE is the explainable balance that reports how much input VAT was lost to the rule.
Reporting the amount rather than only the count is deliberate: a business that keeps paying suppliers over the threshold in cash wants to see the number that behaviour costs it.
The checks
Foreign-contractor tax
FCT withholds from a foreign contractor’s gross: the expense is recognised gross, the contractor is paid net, and the withholding splits into a VAT component and a CIT component on2130.
The split into 333821 (VAT-FCT) and 333822 (CIT-FCT) is a GreatBook design extension.
The workbook carries FCT only as a capture-document shape with a single combined rate, which is not enough to file from.
Where it is enforced
The test that would fail if it broke
backend/tests/test_vat.py covers the register, the integrity rule and the ties, against the v12 worked data: input VAT 5,800,000, output 0, net 5,800,000 recoverable.
backend/tests/test_vat_register_base_truth.py covers exact journal-line evidence, foreign currency, legacy ambiguity, reversals and period scope.
What goes wrong without it
The VAT return is filed from the register, and the register is what a tax audit examines line by line. Without the per-line integrity rule, a mistyped VAT amount on one bill produces a return that is internally consistent and does not match the supplier’s invoice, which is exactly the discrepancy an audit finds. Without the133 tie, the balance sheet claims a recoverable asset the register cannot substantiate.
Related
- Money is Decimal, always - the rounding rule this family depends on
- Settlement, rails and the AP/AR spine - where input and output VAT are booked
- Statutory reporting - the TT200 projection
- The Bookkeeper - the document-sourced posting path