> ## 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.

# The dual IFRS + TT200 chart

> An IFRS core in the ledger, with the Vietnamese Circular-200 statutory codes carried as a re-mappable projection.

# The dual IFRS + TT200 chart

## What it guarantees

The ledger posts to **one** chart of accounts, designed on IFRS lines.
Vietnam's Circular 200 (Thông tư 200, "TT200") codes are carried as a **mapping**, not as a second set of accounts.

The consequence that matters: re-mapping a VAS code later is metadata, not a ledger correction.
No posted entry is touched.

## The two layers

```mermaid theme={null}
flowchart LR
  subgraph L1["The IFRS core - what entries post to"]
    A["accounts<br/>code · name · type · normal_side · is_active"]
  end
  subgraph L2["The statutory projection - what the return is built from"]
    M["account_tt200_map<br/>account_code → tt200_code (0..n)"]
  end
  A -->|"one account maps to zero or more VAS codes"| M
  M --> R["TT200 statutory report"]
  A --> S["Trial balance · P&L · Balance sheet"]
```

**The IFRS core lives in `accounts`.**
Sixty seeded accounts: 16 asset, 15 liability, 5 equity, 6 revenue, 7 cost of sales, 11 operating expense.
Codes are structured `1xxx` assets, `2xxx` liabilities, `3xxx` equity, `4xxx` revenue, `5xxx` cost of sales, `6xxx` operating expense.

**The statutory projection lives in `account_tt200_map`.**
One IFRS account maps to zero or more TT200 codes.
It is freely editable and never touches a posted entry.

## A sample of the mapping

| GreatBook | Name                         | Normal side | TT200            |
| --------- | ---------------------------- | ----------- | ---------------- |
| `1000`    | Cash on hand                 | debit       | 111              |
| `1100`    | Trade receivables (control)  | debit       | 131              |
| `1200`    | Inventory - raw materials    | debit       | 152              |
| `1210`    | Inventory - work in progress | debit       | 154              |
| `1300`    | Input VAT recoverable        | debit       | 1331             |
| `2000`    | Accounts payable (control)   | credit      | 331              |
| `2100`    | Output VAT payable           | credit      | 33311            |
| `2210`    | Social insurance payable     | credit      | 3383, 3384, 3386 |
| `4000`    | Revenue - manufacturing      | credit      | 5112             |
| `5000`    | COGS - manufacturing         | debit       | 632              |
| `5120`    | Manufacturing overhead       | debit       | 627              |
| `6300`    | Depreciation                 | debit       | 6274, 6424       |

Two shapes worth noticing.
`2210` maps to **three** VAS codes, because Vietnamese social insurance splits into distinct statutory accounts that a single management account covers.
`6300` maps to two, because depreciation is presented differently depending on whether it is production or administrative.

## Management-only accounts have no VAS twin

Four accounts map to nothing, on purpose:

| Account                                   | Why no TT200 code                                                                                           |
| ----------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `2010` Goods received not invoiced (GRNI) | a management accrual between receipt and invoice. It nets to zero by the time the return is filed           |
| `4900` Uncategorized income (suspense)    | a suspense account has no statutory meaning. Anything sitting here is a coding decision nobody has made yet |
| `5300` Cost variance                      | a management analysis account                                                                               |
| `6900` Uncategorized expense (suspense)   | as `4900`                                                                                                   |

An empty mapping is a deliberate statement, and the [statutory report](/greatbook/capabilities/statutory-reporting) reports accounts that have activity and no mapping, because those would otherwise vanish silently from the return.

## The suspense accounts are reserved for humans

`4900` and `6900` exist, and **no agent may use them**.

The legacy poster this replaced did `BILL_KIND_ACCOUNT.get(kind, "6900")`, quietly booking anything it could not code into suspense.
The agent posting rules raise `UnmappedAccountError` instead, and the accountant's adjustment rules refuse a suspense account even when a caller names one explicitly.

Suspense is for an explicit, human-chosen coding decision.
It is not a default, because a default suspense account is how a book accumulates a growing pile of "we will look at that later" that nobody ever does.

## Detail is dimensions, not sub-accounts

There is no `2000-SUPPLIER-A`.
Per-party, per-order and per-activity detail is carried as [dimensions on the line](/greatbook/capabilities/dimensions).

That is why the chart is around sixty accounts and stays that size as the business scales to thousands of counterparties.

## Where it is enforced

| Concern                                                    | Code                                                    |
| ---------------------------------------------------------- | ------------------------------------------------------- |
| the seed, both layers, idempotent per org                  | `backend/app/logics/finance/coa_seed.py`                |
| the `account_tt200_map` table                              | migration `0002_gl_guards_fx_rls`                       |
| the statutory projection that reads it                     | `langgraph_chat/agents/accountant/statutory.py`         |
| account existence, activity and org ownership at post time | [GL invariant 5](/greatbook/capabilities/gl-invariants) |

The seed asserts at import that no account code is duplicated and that every type is one of the six known types.
It is find-or-create by `(org_id, code)` and never overwrites an existing account, so re-seeding an org is safe.

## The test that would fail if it broke

`backend/tests/test_coa_seed.py` covers the seed's idempotency and the mapping rows.
`langgraph_chat/agents/accountant/tests/` covers the TT200 projection, including the unmapped-account report.

## What goes wrong without it

The single-chart alternative is to post directly to VAS codes.
That reads as simpler and costs you the ability to change your mind: every statutory reclassification becomes a ledger correction, in a ledger where corrections are reversals, so a presentational change generates real journal entries that a reader has to understand as noise.

The other failure is sub-accounts.
A chart with one payable account per supplier grows without bound, makes every report a `LIKE '2000-%'` query, and turns "show me payables by activity" into a string-parsing exercise.

## Related

* [Posting rules: the map](/greatbook/gl/posting-rules) - which of these codes each document type moves
* [Dimensions, not sub-accounts](/greatbook/capabilities/dimensions) - where the detail actually lives
* [Statutory reporting](/greatbook/capabilities/statutory-reporting) - what the projection produces
* [The Bookkeeper](/greatbook/agents/bookkeeper) - the account map an agent may use, and the error when it cannot
* [The seven sub-ledgers](/greatbook/capabilities/subledgers/index) - the account legs each family posts
