---
name: lis-lab-accounts
description: Every default GL-account setting used by the LIS / lab module (receivable, revenue, COGS/reagent, insurance receivable, tax, discount, cash, external-lab expense/payable, doctor-commission, payment-method/treasury routing) — with definition file:line, posting consumers, and /setup status. For extending the first-run "Default Accounts" (/setup) page.
updated: 2026-06-18
---

# LIS / Lab — Default Accounts

This topic maps **every default-account setting** in the LIS (lab) module. A "default account" =
a setting whose stored value is a **GL account id** that the system uses automatically when posting
journal entries. The owner wants to surface all of these on the first-run **/setup → Default Accounts**
page (which today only carries the 7 core `accounting.default_*` keys — **zero LIS keys**).

## TL;DR for the /setup work

- LIS account settings live in the **LIS module seeder**, not Core:
  `Modules/LIS/database/seeders/LabSettingDefinitionSeeder.php` (display_group `lis_accounting` / `lis_costing`).
- **Duplicate definitions exist**: 4 keys (`receivable`, `revenue`, `insurance_receivable`, `tax_payable`) are
  *also* defined in `Modules/Core/database/seeders/SettingDefinitionSeeder.php` (lines 1714–1778) with
  slightly different labels. Whoever seeds last wins; both target the same `lis.*` keys. **Reconcile labels** when wiring /setup.
- **3 settings are consumed in code but NOT defined in any seeder** → they only resolve via per-record fallbacks
  or return null: `lis.commission_expense_account_id`, `lis.commission_payable_account_id`, `lis.method_accounts`.
  Add seeder definitions if you put them on /setup.
- Payment **receiving** GL accounts are NOT a setting at posting time — they come from the
  `lab_payment_methods.account_id` column + `PettyCash` (treasury) `account_id`. The `lis.method_accounts`
  JSON map only *seeds* those columns. See "Payment-method / treasury routing" below.
- None of the `lis.*` account keys are on /setup yet — confirmed in
  `src/app/features/setup/setup-wizard.component.ts` (only `accounting.default_*` keys are posted, lines 680–710).

---

## 1. Account settings table (all `lis.*` GL-account keys)

| # | Key | EN label | AR label | Purpose | Defined at (file:line) | display_group | On /setup? |
|---|-----|----------|----------|---------|------------------------|---------------|:----------:|
| 1 | `lis.receivable_account_id` | Receivable Account / Lab Receivable GL Account | حساب الذمم / حساب مدينين المختبر | AR account debited on a (cash/credit) lab invoice; credited on payment | LIS `LabSettingDefinitionSeeder.php:347`; **dup** Core `SettingDefinitionSeeder.php:1716` | lis_accounting | No |
| 2 | `lis.revenue_account_id` | Revenue Account / Lab Revenue GL Account | حساب الإيرادات / حساب إيرادات المختبر | Revenue credited on lab invoice | LIS `:362`; **dup** Core `:1732` | lis_accounting | No |
| 3 | `lis.tax_payable_account_id` | Tax Payable Account | حساب الضريبة المستحقة | VAT/output-tax credited on lab invoice | LIS `:377`; **dup** Core `:1764` | lis_accounting | No |
| 4 | `lis.insurance_receivable_account_id` | Insurance Receivable Account | حساب ذمم التأمين | AR for insurance-company invoices (B2B insurance billing) | LIS `:392`; **dup** Core `:1748` | lis_accounting | No |
| 5 | `lis.discount_account_id` | Discount Account | حساب الخصومات | Account for lab discounts (defined; **no posting consumer found** — see §4) | LIS `:407` | lis_accounting | No |
| 6 | `lis.cash_account_id` | Cash Account | حساب النقد | Cash account for lab payments / commission settlement fallback | LIS `:422` | lis_accounting | No |
| 7 | `lis.external_lab_expense_account_id` | External Lab Expense Account | حساب مصروفات المختبرات الخارجية | Expense DR for outbound external-lab referrals (we pay the lab) | LIS `:437` | lis_accounting | No |
| 8 | `lis.external_lab_payable_account_id` | External Lab Payable Account | حساب ذمم المختبرات الخارجية | AP CR for outbound external-lab referrals | LIS `:452` | lis_accounting | No |
| 9 | `lis.cogs_account_id` | COGS Account | حساب تكلفة البضاعة المباعة | Cost-of-goods-sold DR for lab test costs (reagent/consumable cost) | LIS `:709` | lis_costing | No |
| 10 | `lis.reagent_expense_account_id` | Reagent Expense Account | حساب مصروفات الكواشف | Reagent-consumption CR paired with COGS | LIS `:724` | lis_costing | No |
| 11 | `lis.default_cost_center_id` | Default Cost Center | مركز التكلفة الافتراضي | Default **cost center** (not a GL account) for lab JEs | Core `SettingDefinitionSeeder.php:1780` | lis_accounting | No |

### Consumed-only settings (read in code, NOT seeded — verify/seed before exposing)

| Key | EN label (suggested) | AR label (suggested) | Purpose | Consumed at | Defined? |
|-----|----------------------|----------------------|---------|-------------|:--------:|
| `lis.commission_expense_account_id` | Doctor Commission Expense Account | حساب مصروف عمولات الأطباء | Expense DR for doctor-commission accrual (fallback after `doctor.commission_account_id`) | `DoctorCommissionService.php:323` | **No** — undefined; returns null unless doctor record has `commission_account_id` |
| `lis.commission_payable_account_id` | Doctor Commission Payable Account | حساب ذمم عمولات الأطباء | AP CR for commission accrual + settlement (fallback after partner AP / `doctor.commission_payable_account_id`) | `DoctorCommissionService.php:328, 378` | **No** — undefined |
| `lis.method_accounts` | Payment-method → GL account map (JSON) | خريطة طرق الدفع للحسابات | JSON map `{method_key: account_id}` used to seed `lab_payment_methods.account_id` (cashier receiving routing) | `LabPaymentMethodSeeder.php:25`, `LabInfoController.php:379` | **No** — read as a JSON setting; never seeded as a definition |

> NOTE: items 5 (`discount`) is defined but I found **no posting code** consuming it — likely a planned/legacy
> key. The lab invoice JE computes revenue net of discount (`subtotal - discount_amount`) rather than posting
> a separate discount line, so the discount account is currently unused.

---

## 2. Where these are CONSUMED (posting code)

All lab journal entries are built in `Modules/LIS/app/Actions/` and posted via
`Modules\Accounting\Actions\CreateJournalEntry`. Accounts are resolved with a **fallback chain**:
*per-record override → partner sub-account (AccBpExt) → `lis.*` setting*.

### Standard cash/credit lab invoice — `PostLabInvoice.php`
- Direct patient invoice JE reads: `lis.receivable_account_id` (`:101,:191,:523`), `lis.revenue_account_id`
  (`:102,:194,:525`), `lis.tax_payable_account_id` (`:103,:230,:526`).
  - DR Receivable (or partner AR via `resolvePartnerReceivableAccount`), CR Revenue, CR Tax Payable.
- **Insurance invoice JE** `createInsuranceInvoiceJE` (`:183`): `insurance_receivable` →
  `contract.insurance_receivable_account_id ?? partner AR ?? lis.insurance_receivable_account_id ?? lis.receivable_account_id`
  (`:188-191`); revenue → `contract.revenue_account_id ?? lis.revenue_account_id` (`:193-194`); tax → `lis.tax_payable_account_id` (`:230`).
- **External-lab OUTBOUND JE** `createExternalLabOutboundJE` (`:259`): DR `externalLab.expense_account_id ?? lis.external_lab_expense_account_id` (`:271-272`);
  CR `externalLab.payable_account_id ?? partner AP ?? lis.external_lab_payable_account_id` (`:274-276`).
- **External-lab INBOUND JE** `createExternalLabInboundJE` (`:509`): DR `externalLab.receivable_account_id ?? partner AR ?? lis.receivable_account_id` (`:521-523`);
  CR `lis.revenue_account_id` (`:525`); CR `lis.tax_payable_account_id` (`:526`).
- **COGS JE** `createCogsJournalEntry` (`:411`): DR `lis.cogs_account_id` (`:413`) / CR `lis.reagent_expense_account_id` (`:414`),
  grouped by cost center. **Guard at `:425-435`**: if either points at a *header* (control) account it throws a clear
  error (this was the "invoice silently stays draft" bug class — header accounts can't receive JE lines).

### Per-item posting — `PostLabInvoiceItem.php`
- Reads `lis.receivable_account_id` (`:136`), `lis.revenue_account_id` (`:137`), `lis.tax_payable_account_id` (`:138`)
  for the granular per-line variant.

### Lab payment — `PostLabPayment.php`
- DR `payment.receiving_account_id` (the cashier's chosen treasury/method GL — see §3), CR the same receivable used by the invoice.
- `resolveReceivableAccount` (`:75`): insurance → `contract.insurance_receivable_account_id ?? partner AR ?? lis.insurance_receivable_account_id` (`:80-82`);
  else partner AR; else `lis.receivable_account_id` (`:96`).

### External-lab payment — `RecordExternalLabPayment.php:253`
- Falls back to `lis.receivable_account_id`.

### Doctor commission — `DoctorCommissionService.php`
- Accrual JE `createAccrualJournalEntry` (`:314`): DR `doctor.commission_account_id ?? lis.commission_expense_account_id` (`:322-323`);
  CR `partner AP ?? doctor.commission_payable_account_id ?? lis.commission_payable_account_id` (`:326-328`).
- Settlement JE `createSettlementJournalEntry` (`:365`): DR commission payable (same chain, `:376-378`);
  CR `bankAccountId` (for bank_transfer) else `lis.cash_account_id ?? accounting.default_cash_account_id` (`:381-385`).

---

## 3. Payment-method / treasury routing (cash, visa, tamara, tabby, bank_transfer)

There is **no per-method `_account_id` setting** consumed at posting time. Instead:

- Default methods are seeded by `LabPaymentMethodSeeder.php` (cash / visa / tamara / tabby / bank_transfer)
  into the `lab_payment_methods` table; each row has an `account_id` column.
- That column is seeded from the JSON setting **`lis.method_accounts`** (`{method_key: account_id}`),
  `LabPaymentMethodSeeder.php:25-33`.
- At payment time the **cashier picks a method**; the FE resolves its GL account via
  `LabPaymentController.php` "routing" endpoint (`:48-104`):
  - `lab_payment_methods.account_id` → `method_accounts` map (`:55-60`).
  - Cash methods re-route to the **logged-in user's branch cash box** (`PettyCash` treasury with `branch_id`,
    `:78-88`) → `branch_cash_account_id`.
- Treasuries (cash boxes) are managed by `LabTreasuryController.php`; creating one auto-creates a GL child account
  under the cash parent. It reads `accounting.cash_parent_account` (`:107`) and `accounting.bank_parent_account` (`:402`)
  — **these are Accounting-module parent settings, not LIS**, used only to place the new treasury/bank child account.

**Implication for /setup:** to set the cashier receiving accounts from the onboarding page you would edit the
`lis.method_accounts` JSON map (or the treasury rows), not a single `_account_id` setting.

---

## 4. Per-record overrides that settings fall back FROM

The `lis.*` settings are the **last** fallback. Higher-priority overrides:
- **Insurance contract** (`LabInsuranceContract.php:41-42`): `insurance_receivable_account_id`, `revenue_account_id`.
- **External lab** (`LabExternalLab.php:43-45`): `expense_account_id`, `receivable_account_id`, `payable_account_id`.
- **Doctor** (`LabDoctor.php:46-47`, migration `2026_03_04_000001_add_commission_fields_to_lab_doctors_table.php`):
  `commission_account_id`, `commission_payable_account_id`.
- **Partner sub-accounts** (`AccBpExt.ar_account_id` / AP) auto-created via `CreatePartnerAccounts` /
  `ensurePartnerAccounts` — used for receivable/payable before the company-wide setting.

So a /setup page that sets the `lis.*` keys provides the **system-wide default**; partner/contract/doctor records can still override.

---

## 5. /setup page status (FE)

- Component: `src/app/features/setup/setup-wizard.component.ts` (+ `.html`, `.scss`).
- It posts **only** these keys (lines 680–710): `accounting.default_cash_account`, `…default_bank_account`,
  `…default_client_account`, `…default_supplier_account`, `…default_expense_account`, `…default_revenue_account`,
  `…default_check_account`; plus `setup.completed` (`:866`).
- **No `lis.*` account key is present.** Extending the page = add the table-1 keys (and decide on the 3
  consumed-only keys + the `method_accounts` map) as new account pickers, posting to the existing
  settings batch endpoint.

---

## 6. Gotchas / decisions for whoever extends /setup

1. **De-dup the definitions**: keep ONE source of truth for `lis.receivable/revenue/insurance_receivable/tax_payable`
   (LIS seeder vs Core seeder lines 1714–1778). Labels differ ("Receivable Account" vs "Lab Receivable GL Account").
2. **Seed the missing keys** if exposing commission/method accounts:
   `lis.commission_expense_account_id`, `lis.commission_payable_account_id` are undefined; `lis.method_accounts`
   is a JSON map, not a single account.
3. **Detail accounts only** — all posting accounts must be DETAIL leaves, never header/control accounts
   (COGS guard at `PostLabInvoice.php:425-435`; same class of bug fixed for receivable — see KB
   `lis-receivable-header-account-trap`). The /setup picker should filter `account_type === 'detail'`
   (the wizard already does this at `:645`).
4. `lis.discount_account_id` is defined but currently **unused** in posting (revenue is netted of discount).
   Decide whether to expose it.
5. `lis.default_cost_center_id` is a **cost center**, not a GL account — exclude it from a "Default Accounts" GL picker.
6. Payment receiving routing is branch-aware (cash → user's branch cash box). A single company-wide cash
   account on /setup won't capture per-branch boxes — those still need the treasuries screen.
