Payroll
Sub-ledger 3, migration0005.
The rule that shapes everything else
Employment terms are a time series. The payroll for period P reads the contract in effect as of P:contract_asof raises when no contract covers the date, rather than falling back to the most recent one.
A payroll that reads the current contract to compute a past period restates history every time someone gets a raise.
Employer cost, and where it lands
The first row is the cross-family tie.
A production-direct person’s labour is a production cost line, which is exactly the labour the WIP balance consumes.
What becomes payable
The accrual splits into three payables that then settle like any other bill, on rails:
Plus two provisions:
2050 (VAS 335) for the 13th-month accrual and 2250 (VAS 352) for leave.
Payroll bill aggregation carries the PIT payable; it does not pay it.
Only the typed PIT-remittance operation clears 2220 and cash, and net pay, social insurance and PIT remittance each create canonical rail evidence linked to the exact journal entry.
Full and partial remittances retain the unpaid carried balance.
That separation prevents one aggregation step from paying PIT and a later remittance paying the same tax again.
What this sub-ledger does not do.
It does not re-derive Vietnamese PIT brackets.
Gross, deductions, PIT and employer contributions are the payroll engine’s computed figures; this sub-ledger records them, posts the balanced accrual, and enforces the identities.
That boundary is deliberate: a second implementation of a tax table is a second thing to keep current, and the identities catch a wrong figure regardless of which engine produced it.
The identities it enforces
The checks are the value here, because they are what makes a recorded figure verifiable.
The PIT, net and obligation ties are conditional: they compare the payroll to what was billed, so they only run once the period’s bills exist.
Before that, unremitted net and PIT are a payable, not a discrepancy, and reporting them as a break would be a false alarm every month until billing day.
That conditionality is a design choice worth naming.
A check that fires spuriously for two weeks of every month is a check people learn to ignore.
Where it is enforced
The test that would fail if it broke
backend/tests/test_payroll.py covers the operations and the identities, including partial/full PIT remittance and rail evidence, and ties to the v12 worked figures: net 61,135,250, PIT 2,297,932, social insurance 16,960,000, total employer cost 80,393,182, with 29,447,727 of production labour flowing into 154.
What goes wrong without it
Payroll is the largest recurring cash outflow in most businesses and the one with the most statutory exposure. The failure without the identities is not a wrong total; it is a total that nobody can decompose. When the tax authority asks why declared PIT differs from the payroll register by 40,000 dong, “the payroll system said so” is not an answer. The failure without the contract time series is quieter and worse. A retroactive raise restates every prior period’s labour cost, which restates WIP, which restates COGS, which restates margin, in months that were already reported.Related
- Inventory and costing - where production-direct labour lands
- Settlement, rails and the AP/AR spine - where the three payables settle
- FX as of the transaction date - the same as-of-date discipline
- The Close checksums - the payroll family in the full surface