---
name: evolution-gl-pl-data-model
description: "Evolution GL/P&L data model — accountcats P&L type ids, gledger posting signs, and the branch-0 quirk"
metadata: 
  node_type: memory
  type: reference
  originSessionId: ad28526d-09fc-419e-b563-f045fb13f915
---

P&L reporting off `gledger` + `accounts` (verified against live tenant data 2026-06):

- **accountcats type ids** (`accounts.type` is an FK to `accountcats.id`): P&L = **1 Cost of Goods Sold, 5 Income, 6 Expenses, 7 Other Expenses**. Balance-sheet (exclude from P&L) = 2 Current Assets, 3 Equity, 4 Fixed Asset, 8 Liabilities, 9 Other Current Liabilities, 10 Other Assets. `accountcats.cattype` is blank — use the credit/debit ("increase"/"decrease") columns for natural balance.
- **Posting signs in `gledger`**: Income (type 5) posts as **credit**; COGS/Expenses (1,6,7) post as **debit**. So P&L value = `credit−debit` for income, `debit−credit` for cogs/expense. Filter `g.status='1'` (excludes voided/deleted lines) and `a.status='1'`.
- **Branch-0 quirk (FIXED 2026-06-11):** historically invoice/bill postings were written with `gledger.branchid = 0` because `commitInvoice`/`commitBill`/`commitBillPayments` built their `insertQuery("gledger",...)` field lists **without a `branchid` column** (MyISAM NOT-NULL default → 0); only `doJournal` (AR payments) stamped a branch (from `$_SESSION['branch']`). The fix adds `branchid` (from `invoices.branch` / `bills.branchid`; bill payments inherit the bill's branch) — applied to **BOTH** the web-root `/accounting.php` globals AND the `library/accounting.php` class. ⚠️ The first attempt patched only `library/` and the bug persisted, because invoice/bill Approve runs the **web-root global functions** — see [[evolution-two-accounting-files]]. Historical rows were repaired by `migrations/gledger_branchid_backfill.sql` (idempotent, only touches `branchid=0`), **run across all tenants 2026-06-11**. The finPL branch selector is **re-enabled**.
- **Nesting:** `accounts.parent` points to a parent account id (0 = top-level); sub-accounts of the same type nest under their parent (e.g. Consulting Services parent=Sales, both type 5). The `children` column is just a flag/count — build the tree from `parent`.
- **`payment.billid` is overloaded** — despite the name it holds the **invoice id** for AR rows (`payment.source='AR'`, written by `invoice.php::addPayment`) and the **bill id** for AP rows (`source='AP'`). `payment.date` = banked date, `payment.total` = gross (incl GST), `committed='1'` once posted to GL. Rows are grouped into one bank transaction by `keyID`. There is NO separate payment↔document allocation table.
- **P&L basis (accrual vs cash):** finPL supports both via a `basis` request param (default `accrual`). Accrual = sum gledger P&L lines by `g.date`. **Cash** (`plCashSums` in finPLSave.php) uses the **proportional method**: for each committed payment banked in the period, recognise `payment.total / document.total` of that document's P&L gledger lines (AR→invoice lines, AP→bill lines), `NULLIF(total,0)` guards div-by-zero. GST/AR/AP control accounts fall out naturally (balance-sheet types). Branch filter still works (gledger is branch-aware). UI selector `#plBasis`.
- Contrast: the AR/AP/Revenue/GST reports use `invoices.branch` / `bills.branchid` directly (not gledger), so their branch filters work fine. See [[evolution_ajax_save_auth_gate]] for the report endpoint gating pattern.
