> ## Documentation Index
> Fetch the complete documentation index at: https://docs.vinmake.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Inventory, costing and payroll

> The one-way costing chain from receipt to COGS, the 627 overhead pool, and what a payroll accrual posts when one person's cost is one bracket.

# Inventory, costing and payroll

These two families are where a manufacturing book is either right or quietly wrong, because both turn a stream of small events into a unit cost that everything downstream believes.

## The costing chain runs one way

`RECEIPT → ISSUE → PRODUCTION_IN → SHIP`, and every movement's unit cost is **stamped immutably on the movement at post time**.

| Step                              | Entry                                         | Value                                      |
| --------------------------------- | --------------------------------------------- | ------------------------------------------ |
| goods receipt (GRN)               | `Dr 1200` (152) / `Cr 2010` (GRNI)            | at PO price                                |
| the material bill clears the GRNI | `Dr 2010` / `Dr 1300` (133) / `Cr 2000` (331) | at invoice                                 |
| landed cost in                    | `Dr 1200` (152) / `Cr 2000` (331)             | exact-sum split by quantity                |
| material issued to WIP            | `Dr 1210` (154) / `Cr 1200` (152)             | quantity × weighted average                |
| labour into WIP                   | `Dr 1210` (154) / `Cr 2200` (334 wages)       | from payroll                               |
| overhead absorbed into WIP        | `Dr 1210` (154) / `Cr 5120` (627 pool)        | absorbed                                   |
| subcontract / CMT into WIP        | `Dr 1210` (154) / `Cr 2000` (331)             | at invoice                                 |
| production finished               | `Dr 1220` (155) / `Cr 1210` (154)             | the whole accumulated `154`                |
| goods shipped                     | `Dr 5000` (632 COGS) / `Cr 1220` (155)        | quantity × finished-goods weighted average |

Because a movement's cost is **frozen** when it posts, a later price change cannot retroactively move both the WIP-in and the WIP-out leg.
That tautology is exactly what the source workbook could not avoid - its stock-movement cost column was a live formula - and it is why the equivalent check there had no teeth.

<Info>
  Stock on hand, the weighted-average unit cost, the WIP balance, the GRNI balance and the `627` pool are all **derived** from movements plus the GL.
  None of them is a stored total that can drift from the rows it summarises.
</Info>

## The 627 overhead pool must equal what was absorbed

The pool is **sourced** from real expenses and **absorbed** into WIP, and the two sides are checked against each other.

| Source of a pool line                        | Entry                                                      |
| -------------------------------------------- | ---------------------------------------------------------- |
| a supplier expense - rent, utilities         | `Dr 5120` (627) / `Cr 2000` (331)                          |
| a month's depreciation of a production asset | `Dr 5120` (627) / `Cr 1510` (214 accumulated depreciation) |

Depreciation lines are recorded with `source_type: ASSET_REGISTER`, so the pool can be checked against an **independent re-derivation** from each asset's cost and life rather than against itself.
A pool that does not equal the overhead absorbed into `154` is a Close-list break.

## An inventory adjustment always carries its stock movement

A signed adjustment on a stock account posts its GL legs at the positive magnitude and a matching stock movement with the signed quantity and value.

An adjustment to a stock account with no stock movement behind it is a break, by design: a value change with no quantity story is how inventory quietly stops matching the warehouse.

## Payroll: one person, one bracket, three bills

Employment terms are a **time series**.
The payroll for a period reads the contract in effect *as of* that period, exactly as FX reads the rate as of the transaction date - the applicable term, not the latest one.

| Operation                                        | Entry                                                                                                                         |
| ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------- |
| accrue one person's payroll                      | `Dr <cost bracket>` at employer cost / `Cr 2200` (334 net) + `Cr 2210` (338 social insurance) + `Cr 2220` (3335 PIT withheld) |
| accrue a month's 13th-month share                | `Dr <expense>` / `Cr 2250` (335 accrual)                                                                                      |
| reverse it when someone leaves before qualifying | `Dr 2250` / `Cr <expense>`                                                                                                    |
| accrue leave liability                           | `Dr <expense>` / `Cr 2250` (352 leave provision)                                                                              |
| settle the period's three bills on their rails   | `Dr 2200` / `Dr 2220` / `Dr 2210` / `Cr <rail>`                                                                               |
| remit PIT to the state                           | `Dr 2220` (3335) / `Cr <rail>`                                                                                                |

Two identities hold by construction and are checked:

* `employer_cost = gross + employer social insurance`
* `net = gross - employee social insurance - PIT`

Each person's cost is allocated to **one** cost bracket.
A production-direct person's labour therefore flows into `154`, which is what ties the payroll sub-ledger to the costing sub-ledger - the same number, reached two ways.

<Note>
  Reversing a 13th-month accrual for someone who left is **real information, not an error to hide**.
  A negative allocation keeps the reconciliation tied, which is why the reversal is a first-class operation rather than a deletion.
</Note>

The three payables the accrual creates - net pay, PIT, and social insurance - become obligations that settle on rails like any other bill, which is what ties payroll to the settlement sub-ledger.
PIT carries its own conservation checksum: `computed == paid + carried`.

<Warning>
  This sub-ledger **records and enforces**; it does not re-derive Vietnamese PIT brackets.
  The gross, the deductions, the PIT and the employer contributions are the payroll engine's computed figures.
  What GreatBook guarantees is that they post as a balanced accrual, that the two identities hold, and that the allocation reconciles - not that the bracket arithmetic upstream was right.
</Warning>

## Where it is enforced

| Concern                                                        | Code                                                                  |
| -------------------------------------------------------------- | --------------------------------------------------------------------- |
| the costing chain, the pool, depreciation, adjustments         | `backend/app/logics/finance/costing.py`                               |
| contracts as-of-date, accrual, the three bills, PIT remittance | `backend/app/logics/finance/payroll.py`                               |
| stock on hand, weighted average, WIP, GRNI, the pool           | `backend/app/logics/finance/derive.py`                                |
| the ties                                                       | `close_checks.run_costing_checks`, the payroll family (period-scoped) |

## The test that would fail if it broke

`backend/tests/test_costing.py` drives the chain end to end and asserts a stamped cost does not move.
`backend/tests/test_payroll.py` covers the identities, the allocation reconciliation and the PIT conservation.

<Note>
  The payroll Close family runs **only when a period is supplied**, and its bill-level checksums appear only once that period's payroll bills are raised.
  That is another reason a live run's executed count differs from the 104 defined checksums.
</Note>

## Related

* [Inventory and costing](/greatbook/capabilities/subledgers/inventory-costing) - the sub-ledger, its movements and its derived balances
* [Payroll](/greatbook/capabilities/subledgers/payroll) - the family in full
* [Derived balances](/greatbook/capabilities/derived-balances) - why a unit cost is not a stored column
* [The Close checksums](/greatbook/capabilities/close-list) - how these families are run
