---
name: Default Accounts — Master Inventory & /setup Extension Plan
description: Canonical, de-duplicated inventory of every default GL-account setting across all MoonERP modules, the set missing from the first-run /setup "Default Accounts" page, and a concrete plan to extend that page to cover Lab (LIS), Manufacturing (Production) and HR (payroll) per the owner's priority. Covers the setting-definition schema convention, the legacy unseeded /setup keys vs the real posting keys, and the AR-header trap.
updated: 2026-06-18
---

# Default Accounts — Master Inventory & /setup Extension Plan

> **One-line model:** a "default account" is a company-scoped setting whose `value_type=integer`, whose `setting_key` ends in `_account_id`, and whose stored value is an `accounts.id` (a **detail/postable GL leaf**). Posting code reads it via `SettingsService::get(key, companyId)` cast to `(int)` into a `JournalEntryLine.account_id`. The canonical seeder is `Modules/Core/database/seeders/SettingDefinitionSeeder.php`, but **module seeders also define accounts** (Production +8, LIS +10) — Core is not the full picture.

## 0. Critical context (read first)

1. **The /setup page writes 7 LEGACY keys that nothing reads.** UI Step 5 ("Default Accounts", `currentStep()===4`) writes `accounting.default_cash_account`, `…default_bank_account`, `…default_client_account`, `…default_supplier_account`, `…default_expense_account`, `…default_revenue_account`, `…default_check_account`. These have **NO `SettingDefinition`** and **ZERO posting consumers** in the BE (grep returns 0). They are stored-but-inert and used only by the **frontend** to pre-fill account dropdowns. The actual posting keys are `sales.*` / `purchases.* `/ `lis.*` / `hrm.*` / `production.*` / `pos.*`, all suffixed `_account_id`.
2. **Convention for new keys:** follow the seeded `module.something_account_id` pattern (integer, default null, scope=company, idempotent on `setting_key`). Do NOT mimic the /setup page's unseeded no-`_id` keys.
3. **`seedDefaultAccountSettings()`** (`SettingDefinitionSeeder.php:2111`) auto-fills only **7 of ~33** Core keys from CoA codes at provisioning; the rest ship `null`.
4. **Production accounts are never seeded** — `ProductionAccountResolver` find-or-creates a child detail account under a fixed CoA parent (11 / 21 / 52) on first posting and writes the id back.
5. **The AR-header trap** (known elmadina incident): a receivable/AR setting MUST point to a **detail leaf**, never an AR parent/header/control account — a JE line cannot post to a header, so the invoice post throws and the invoice silently stays draft/unpaid. The account picker MUST be filtered to **postable detail leaves**.

---

## 1. Complete de-duplicated inventory (grouped by module)

Legend: **on /setup?** = exposed on the wizard's Default-Accounts page today. All file paths are under `/home/moonui/moon-erp-be/` unless prefixed `src/` (FE = `/home/moonui/public_html/moon-erp/`).

### 1a. accounting.* (Core / general)

| key | AR label | EN label | purpose | module | on /setup? | definition file:line |
|---|---|---|---|---|---|---|
| accounting.checks_received_account_id | حساب الشيكات المُستلَمة الوسيط | Checks Received Intermediate Account | Asset clearing debited on check receipt, credited when bank collects (~1103). Consumed `CollectCheck.php:30`. | accounting | No | Modules/Core/database/seeders/SettingDefinitionSeeder.php:325 |
| accounting.checks_issued_account_id | حساب الشيكات المُصدَرة الوسيط | Checks Issued Intermediate Account | Liability clearing credited on check issue, debited when it clears (~2103). Consumed `CashCheck.php:32`. | accounting | No | Modules/Core/database/seeders/SettingDefinitionSeeder.php:309 |
| accounting.zakat.expense_account_id | حساب مصروف الزكاة | Zakat Expense Account | P&L expense debited when a zakat declaration is approved. | accounting | No | Modules/Core/database/seeders/SettingDefinitionSeeder.php:341 |
| accounting.zakat.payable_account_id | حساب الزكاة المستحقة | Zakat Payable Account | Liability credited on zakat approval, debited on payment. | accounting | No | Modules/Core/database/seeders/SettingDefinitionSeeder.php:357 |
| accounting.wht_payable_account_id | حساب ضريبة الاستقطاع المستحقة | Withholding Tax Payable Account | KSA WHT payable to ZATCA, credited on payment vouchers carrying WHT. Consumed `ApprovePaymentVoucher.php:140-152`. | accounting | No | Modules/Core/database/seeders/SettingDefinitionSeeder.php:373 |

> **Legacy /setup keys (FE-only, undefined in BE, no posting consumer — to be reconciled, not new):** `accounting.default_cash_account` (FE setup-wizard.component.ts:680), `…default_bank_account` (:685), `…default_client_account` (:690), `…default_supplier_account` (:695), `…default_expense_account` (:700), `…default_revenue_account` (:705), `…default_check_account` (:710). Mapped to CoA codes 1101/1102/1103/2101/5201(→5101)/41/1104. Latent near-miss: `DoctorCommissionService.php:385` reads a DIFFERENT key `accounting.default_cash_account_id` (with `_id`).

### 1b. sales.*

| key | AR label | EN label | purpose | module | on /setup? | definition file:line |
|---|---|---|---|---|---|---|
| sales.revenue_account_id | حساب الإيرادات | Revenue GL Account | CR revenue on sales invoice (DR on return). Mandatory. Auto-seeded code 4101. | sales | No | Modules/Core/database/seeders/SettingDefinitionSeeder.php:663 |
| sales.cogs_account_id | حساب تكلفة البضاعة المباعة | COGS GL Account | DR COGS for inventory items; CR on return. JE skipped if unset. Auto-seeded code 5101. | sales | No | Modules/Core/database/seeders/SettingDefinitionSeeder.php:679 |
| sales.receivable_account_id | حساب المدينين | Accounts Receivable GL Account | DR AR on invoice, CR on payment. Global AR unless `use_customer_ar_account`. Mandatory. Auto-seeded code 1103. | sales | No | Modules/Core/database/seeders/SettingDefinitionSeeder.php:695 |
| sales.inventory_account_id | حساب المخزون | Inventory GL Account | CR inventory contra in COGS JE. Fallback: sales→purchases.inventory→inventory.stock_account. Auto-seeded code 1105. | sales | No | Modules/Core/database/seeders/SettingDefinitionSeeder.php:711 |
| sales.tax_payable_account_id | حساب الضريبة المستحقة | Tax Payable GL Account | CR output VAT when tax>0; reversed on return. | sales | No | Modules/Core/database/seeders/SettingDefinitionSeeder.php:743 |
| sales.discount_account_id | حساب خصم المبيعات | Sales Discount GL Account | DR discount when set (else revenue booked net). Optional. Auto-seeded code 4105. | sales | No | Modules/Core/database/seeders/SettingDefinitionSeeder.php:759 |
| sales.use_customer_ar_account | استخدام حساب المدينين الفرعي للعميل | Use Customer AR Sub-Account | Boolean toggle (NOT an account). True → use customer's own AR sub-account. | sales | No | Modules/Core/database/seeders/SettingDefinitionSeeder.php:727 |

### 1c. purchases.*

| key | AR label | EN label | purpose | module | on /setup? | definition file:line |
|---|---|---|---|---|---|---|
| purchases.inventory_account_id | حساب المخزون | Inventory GL Account | DR inventory on stock-tracked bill; CR on return; inventory leg of landed-cost. Auto-seeded code 1105. | purchases | No | Modules/Core/database/seeders/SettingDefinitionSeeder.php:1084 |
| purchases.expense_account_id | حساب المصروفات | Expense GL Account | DR expense for non-inventory/service lines (fallback to inventory acct). | purchases | No | Modules/Core/database/seeders/SettingDefinitionSeeder.php:1100 |
| purchases.payable_account_id | حساب الدائنين | Accounts Payable GL Account | CR AP on bill; DR on payment/return. Overridden by supplier AP sub-account when toggle on. Auto-seeded code 2101. | purchases | No | Modules/Core/database/seeders/SettingDefinitionSeeder.php:1116 |
| purchases.tax_receivable_account_id | حساب ضريبة المدخلات | Input Tax GL Account | DR input VAT (recoverable) on bill; CR on return. | purchases | No | Modules/Core/database/seeders/SettingDefinitionSeeder.php:1132 |
| purchases.discount_account_id | حساب خصم المشتريات | Purchase Discount GL Account | CR purchase discount on bill; DR on return. | purchases | No | Modules/Core/database/seeders/SettingDefinitionSeeder.php:1148 |
| purchases.use_supplier_ap_account | استخدام حساب الدائنين الفرعي للمورد | Use Supplier AP Sub-Account | Boolean toggle (NOT an account). True → use supplier's own AP sub-account. | purchases | No | Modules/Core/database/seeders/SettingDefinitionSeeder.php:1164 |
| purchases.landed_cost_clearing_account_id | حساب تسوية تكلفة الوصول | Landed Cost Clearing Account | CR liability for landed-cost voucher total. NOT seeded — auto-created under parent 21 on first post. | purchases | No | Modules/Purchases/app/Actions/PostLandedCostVoucher.php:54 (resolved :361-387) |

### 1d. inventory.* (CODE-string settings, not ids)

| key | AR label | EN label | purpose | module | on /setup? | definition file:line |
|---|---|---|---|---|---|---|
| inventory.stock_account | رمز حساب المخزون | Stock Account Code | Inventory-asset GL **CODE** (string, default '1301'). Final code-based fallback in the inventory-asset chain. GOTCHA: stores a CODE, not an id. | inventory | No | Modules/Core/database/seeders/SettingDefinitionSeeder.php:809 |
| inventory.cogs_account | رمز حساب تكلفة البضاعة المباعة | COGS Account Code | COGS GL **CODE** (string, default '4101'). In practice COGS flows through `sales.cogs_account_id`. GOTCHA: stores a CODE. | inventory | No | Modules/Core/database/seeders/SettingDefinitionSeeder.php:825 |

> Inventory module posts NO journal entries of its own; the inventory-asset GL impact is posted by Sales/Production via the shared chain `sales.inventory_account_id → purchases.inventory_account_id → inventory.stock_account (code) → lazily-created child under '11'`. Stock adjustments/counts have **no** GL-account setting.

### 1e. hrm.* (payroll)

| key | AR label | EN label | purpose | module | on /setup? | definition file:line |
|---|---|---|---|---|---|---|
| hrm.salary_expense_account_id | حساب مصروف الرواتب | Salary Expense Account | DR salary expense (per-employee sub) on payroll post. Mandatory (post throws if missing). Consumed `PayrollAccountingService`. | hrm | No | Modules/Core/database/seeders/SettingDefinitionSeeder.php:1443 |
| hrm.salary_payable_account_id | حساب الرواتب المستحقة | Salary Payable Account | CR net salary payable; DR to clear on pay-out. Mandatory. | hrm | No | Modules/Core/database/seeders/SettingDefinitionSeeder.php:1459 |
| hrm.social_insurance_account_id | حساب التأمينات الاجتماعية | Social Insurance Account | CR when a deduction component name contains 'social insurance'. | hrm | No | Modules/Core/database/seeders/SettingDefinitionSeeder.php:1475 |
| hrm.medical_insurance_account_id | حساب التأمين الطبي | Medical Insurance Account | CR when a deduction component contains 'medical insurance'. | hrm | No | Modules/Core/database/seeders/SettingDefinitionSeeder.php:1491 |
| hrm.income_tax_account_id | حساب ضريبة الدخل | Income Tax Account | CR when a deduction component contains 'income tax'. | hrm | No | Modules/Core/database/seeders/SettingDefinitionSeeder.php:1507 |
| hrm.loan_receivable_account_id | حساب سلف الموظفين | Loan Receivable Account | Employee loan receivable. DEFINED but **NOT consumed** by any posting code today (validated only). | hrm | No | Modules/Core/database/seeders/SettingDefinitionSeeder.php:1523 |
| hrm.eos_provision_account_id | حساب مخصص نهاية الخدمة | EOS Provision Account | EOS provision liability. DEFINED but **NOT posted** (EosSettlement has `journal_entry_id` but no writer). | hrm | No | Modules/Core/database/seeders/SettingDefinitionSeeder.php:1539 |
| hrm.eos_expense_account_id | حساب مصروف نهاية الخدمة | EOS Expense Account | EOS expense. DEFINED but **NOT posted** today. | hrm | No | Modules/Core/database/seeders/SettingDefinitionSeeder.php:1555 |
| hrm.overtime_expense_account_id | حساب مصروف الأوفرتايم | Overtime Expense Account | Overtime expense. NOT posted AND **missing from `UpdateHrSettingsRequest`** whitelist → cannot be saved via HR settings API today. | hrm | No | Modules/Core/database/seeders/SettingDefinitionSeeder.php:1711 |
| hrm.leave_accrual_account_id | حساب استحقاق الإجازات | Leave Accrual Account | Leave-accrual liability. NOT posted AND **missing from `UpdateHrSettingsRequest`** whitelist. | hrm | No | Modules/Core/database/seeders/SettingDefinitionSeeder.php:1712 |

### 1f. lis.* (Lab)

| key | AR label | EN label | purpose | module | on /setup? | definition file:line |
|---|---|---|---|---|---|---|
| lis.receivable_account_id | حساب مدينين المختبر | Lab Receivable GL Account | AR debited on lab invoice (fallback after partner AR). **MUST be a detail leaf** (AR-header trap). Read `PostLabInvoice.php:101`. | lis | No | Modules/LIS/database/seeders/LabSettingDefinitionSeeder.php:347 **(DUP: Core SettingDefinitionSeeder.php:1716)** |
| lis.revenue_account_id | حساب إيرادات المختبر | Lab Revenue GL Account | Revenue credited on lab invoice (net of discount). Read `PostLabInvoice.php:102`. | lis | No | Modules/LIS/database/seeders/LabSettingDefinitionSeeder.php:362 **(DUP: Core :1732)** |
| lis.tax_payable_account_id | حساب ضريبة المختبر المستحقة | Lab Tax Payable GL Account | Output VAT credited on lab invoice. Read `PostLabInvoice.php:103`. | lis | No | Modules/LIS/database/seeders/LabSettingDefinitionSeeder.php:377 **(DUP: Core :1764)** |
| lis.insurance_receivable_account_id | حساب ذمم التأمين | Insurance Receivable GL Account | AR for insurance-company (B2B) invoices. Read `PostLabInvoice.php:190`. | lis | No | Modules/LIS/database/seeders/LabSettingDefinitionSeeder.php:392 **(DUP: Core :1748)** |
| lis.discount_account_id | حساب الخصومات | Lab Discount Account | DEFINED but currently UNUSED in posting (revenue netted of discount). | lis | No | Modules/LIS/database/seeders/LabSettingDefinitionSeeder.php:407 |
| lis.cash_account_id | حساب النقد | Lab Cash Account | Cash for lab payments; fallback CR in doctor-commission settlement (`DoctorCommissionService.php:384`). Inbound patient payments use per-instance `receiving_account_id`, not this. | lis | No | Modules/LIS/database/seeders/LabSettingDefinitionSeeder.php:422 |
| lis.external_lab_expense_account_id | حساب مصروفات المختبرات الخارجية | External Lab Expense Account | Expense DR for outbound referrals. Consumed `PostLabInvoice.php:272`. | lis | No | Modules/LIS/database/seeders/LabSettingDefinitionSeeder.php:437 |
| lis.external_lab_payable_account_id | حساب ذمم المختبرات الخارجية | External Lab Payable Account | AP CR for outbound referrals. Consumed `PostLabInvoice.php:276`. | lis | No | Modules/LIS/database/seeders/LabSettingDefinitionSeeder.php:452 |
| lis.cogs_account_id | حساب تكلفة البضاعة المباعة | Lab COGS Account | COGS DR for test costs. Consumed `PostLabInvoice.php:413`. **MUST be a detail account** (header guard :425-435). | lis | No | Modules/LIS/database/seeders/LabSettingDefinitionSeeder.php:709 |
| lis.reagent_expense_account_id | حساب مصروفات الكواشف | Reagent Expense Account | Reagent-consumption CR paired with COGS. Consumed `PostLabInvoice.php:414`. **MUST be a detail account**. | lis | No | Modules/LIS/database/seeders/LabSettingDefinitionSeeder.php:724 |
| lis.commission_expense_account_id | حساب مصروف عمولات الأطباء | Doctor Commission Expense Account | Expense DR for doctor-commission accrual. **CONSUMED-ONLY — UNDEFINED in any seeder.** | lis | No | UNDEFINED (consumed `DoctorCommissionService.php:323`) |
| lis.commission_payable_account_id | حساب ذمم عمولات الأطباء | Doctor Commission Payable Account | AP CR for doctor-commission accrual + settlement. **CONSUMED-ONLY — UNDEFINED in any seeder.** | lis | No | UNDEFINED (consumed `DoctorCommissionService.php:328,378`) |
| lis.method_accounts | خريطة طرق الدفع للحسابات | Payment-method→GL map (JSON) | JSON `{method: account_id}` that SEEDS `lab_payment_methods.account_id`. Read as JSON, never seeded as a definition. | lis | No | UNDEFINED as a definition (consumed `LabPaymentMethodSeeder.php:25`, `LabInfoController.php:379`) |
| lis.default_cost_center_id | مركز التكلفة الافتراضي | Default Cost Center | A COST CENTER, **not a GL account** — exclude from a GL-account picker. | lis | No | Modules/Core/database/seeders/SettingDefinitionSeeder.php:1780 |

### 1g. production.* (Manufacturing)

> All 8 are integer, company-scoped, default null, **never seeded** — `ProductionAccountResolver` find-or-creates a child detail account under the noted CoA parent on first posting and persists the id back.

| key | AR label | EN label | purpose | module | on /setup? | definition file:line |
|---|---|---|---|---|---|---|
| production.wip_account_id | حساب الإنتاج تحت التشغيل | Work-In-Progress Account | WIP control (asset). DR on material/labor/overhead into order; CR on FG receipt/close. Parent 11. | production | No | Modules/Production/database/seeders/ProductionSettingDefinitionSeeder.php:151 |
| production.fg_account_id | حساب المنتجات التامة | Finished Goods Account | FG inventory, DR when produced qty capitalised out of WIP. Consumed `PostGoodsReceiptJournal.php:73`, `CloseProductionOrder.php:129`. Parent 11. | production | No | Modules/Production/database/seeders/ProductionSettingDefinitionSeeder.php:167 |
| production.scrap_account_id | حساب هالك الإنتاج | Production Scrap Account | Expense for scrapped/rejected output. Consumed `PostScrapJournal.php:129`. Parent 52. | production | No | Modules/Production/database/seeders/ProductionSettingDefinitionSeeder.php:183 |
| production.labor_applied_account_id | حساب العمالة المحملة | Labor Applied Account | Absorption/clearing CR as labor charged into WIP on confirmation. Consumed `PostConfirmationJournals.php:61`, `ConfirmOperation.php:560`. Parent 21. | production | No | Modules/Production/database/seeders/ProductionSettingDefinitionSeeder.php:199 |
| production.overhead_applied_account_id | حساب التكاليف غير المباشرة المحملة | Overhead Applied Account | Absorption/clearing CR as overhead absorbed into WIP. Consumed `PostConfirmationJournals.php:98`, `SettleAppliedOverhead.php:239`, `ApportionUtilityCost.php:441`. Parent 21. | production | No | Modules/Production/database/seeders/ProductionSettingDefinitionSeeder.php:219 |
| production.cost_variance_account_id | حساب انحراف تكلفة الإنتاج | Production Cost Variance Account | Expense absorbing std-vs-actual delta + over/under-applied OH. Consumed `PostGoodsReceiptJournal.php:102`, `SettleAppliedOverhead.php:240`. Parent 52. | production | No | Modules/Production/database/seeders/ProductionSettingDefinitionSeeder.php:237 |
| production.toll_clearing_account_id | حساب تسوية التصنيع لدى الغير | Toll Clearing Account | Clearing for toll/consignment mfg; parks conversion cost (DR Toll / CR WIP), cleared by toll-fee invoice. Consumed `PostGoodsReceiptJournal.php:166`, `CloseProductionOrder.php:128`. Parent 11. | production | No | Modules/Production/database/seeders/ProductionSettingDefinitionSeeder.php:278 |
| production.customer_advance_account_id | حساب سُلف العملاء | Customer Advances Account | Liability CR on toll down-payment (DR Cash/Bank / CR Customer Advances). Consumed `RecordTollDownPayment.php:76`. Parent 21. | production | No | Modules/Production/database/seeders/ProductionSettingDefinitionSeeder.php:299 |

### 1h. pos.*

| key | AR label | EN label | purpose | module | on /setup? | definition file:line |
|---|---|---|---|---|---|---|
| pos.cash_receiving_account_id | حساب استلام الكاش (الخزينة) | Cash Receiving Account | DR cash drawer/treasury for POS cash (also 'check'). Consumed `POSSaleController::resolveReceivingAccount`. | pos | No | Modules/Core/database/seeders/SettingDefinitionSeeder.php:1974 |
| pos.card_receiving_account_id | حساب استلام الشبكة (حساب وسيط) | Card/Network Receiving Account | DR intermediary (money in transit) for card/network POS. | pos | No | Modules/Core/database/seeders/SettingDefinitionSeeder.php:1990 |
| pos.default_receiving_account_id | حساب الاستلام العام | Default Receiving Account | Fallback DR when method-specific account unset (before legacy `sales.cash_account_id`). Consumed `POSSaleController.php:195`. | pos | No | Modules/Core/database/seeders/SettingDefinitionSeeder.php:2006 |
| pos.default_receiving_account_type | نوع حساب الاستلام الافتراضي | Default Receiving Account Type | Enum (petty_cash\|bank_account) — NOT an account id. Should NOT go on the page. | pos | No | Modules/Core/database/seeders/SettingDefinitionSeeder.php:2022 |

> POS reuses the Sales actions (`PostSalesInvoice` + `PostSalesPayment`) for revenue/tax/discount/COGS/inventory/receivable — no POS duplicates of those. No change/over-short/rounding account anywhere (session-close variance is data only).

### 1i. Per-INSTANCE accounts (not settings — noted for completeness)

The actual debit account for a received payment usually comes NOT from a setting but from a per-row `account_id` GL leaf on each `Accounting\PettyCash` (treasury) and each `LIS\LabPaymentMethod` row (auto-provisioned under 1101/1102 by `AutoAccountService`; copied into `receiving_account_id` at collect time). **These must be remapped on company transfer** (the elmadina-404 gotcha) and are out of scope for the /setup page.

---

## 2. Accounts MISSING from /setup (the owner's additions)

The /setup page currently has only the 7 legacy `accounting.default_*` keys (which are inert). The owner's priority is to add **Lab, Manufacturing, HR**. Below are the concrete keys to expose, all real posting keys.

### 2a. Lab (LIS) — 10 GL-account settings (+ exclude cost-center, JSON map)

Expose (display groups `lis_accounting` / `lis_costing`):
- `lis.receivable_account_id` — **AR, detail-leaf only** (AR-header trap)
- `lis.revenue_account_id`
- `lis.tax_payable_account_id`
- `lis.insurance_receivable_account_id` — **AR, detail-leaf only**
- `lis.discount_account_id` (defined but unused — optional)
- `lis.cash_account_id`
- `lis.external_lab_expense_account_id`
- `lis.external_lab_payable_account_id`
- `lis.cogs_account_id` — **detail only** (header guard)
- `lis.reagent_expense_account_id` — **detail only**

Pre-work needed: **reconcile the 4 duplicated defs** (receivable/revenue/tax_payable/insurance_receivable exist in BOTH `LabSettingDefinitionSeeder.php` and Core `SettingDefinitionSeeder.php:1714-1778`); and **define the 2 consumed-only keys** if they are to be exposed/usable: `lis.commission_expense_account_id`, `lis.commission_payable_account_id`. Exclude `lis.default_cost_center_id` (cost center) and `lis.method_accounts` (JSON, lives on payment-method rows).

### 2b. Manufacturing (Production) — 8 GL-account settings

Expose (new display group `production_accounting`):
- `production.wip_account_id`
- `production.fg_account_id`
- `production.scrap_account_id`
- `production.labor_applied_account_id`
- `production.overhead_applied_account_id`
- `production.cost_variance_account_id`
- `production.toll_clearing_account_id`
- `production.customer_advance_account_id`

Note: all 8 are **defined in `ProductionSettingDefinitionSeeder.php`** but auto-resolve lazily today. Exposing them lets the owner pin explicit leaves up-front; the lazy resolver remains the safety net.

### 2c. HR (payroll) — 10 GL-account settings (5 live, 5 placeholders)

Expose (display group `hrm_accounting`):
- **Live/consumed:** `hrm.salary_expense_account_id`, `hrm.salary_payable_account_id`, `hrm.social_insurance_account_id`, `hrm.medical_insurance_account_id`, `hrm.income_tax_account_id`
- **Defined but unwired (expose, but they post nothing yet):** `hrm.loan_receivable_account_id`, `hrm.eos_provision_account_id`, `hrm.eos_expense_account_id`, `hrm.overtime_expense_account_id`, `hrm.leave_accrual_account_id`

Pre-work: `hrm.overtime_expense_account_id` and `hrm.leave_accrual_account_id` are **missing from `UpdateHrSettingsRequest`** — but the /setup page writes via the generic `PUT /core/settings` (no per-module whitelist), so they are saveable through that route regardless. If the HR settings screen should also save them, add them to that FormRequest.

> **Optional (not asked but cheap, real posting keys):** sales (6) + purchases (5/6) + pos (3) + the 5 accounting check/zakat/WHT keys are all real and could be added as further grouped sections to fully replace the 7 inert legacy keys.

---

## 3. Extension plan

### 3a. Frontend (`src/app/features/setup/setup-wizard.component.ts` + `.html`)

1. **Restructure Step 5** from a flat 7-field form into **collapsible grouped sections** by module: *Core/General · Sales · Purchases · Lab · Manufacturing · HR · POS* (start with Lab/MFG/HR per owner). Drive each section from a config array `{ key, labelEn, labelAr, group, accountFilter }` instead of 7 hard-coded signals — many small, data-driven controls.
2. **Dropdown options** keep using `GET /accounting/accounts` filtered to `account_type==='detail'` (postable leaves). For AR-typed keys (`*receivable*`) additionally exclude the AR parent/header (see §4). Reuse the existing options fetch; do not re-fetch per field.
3. **LOAD must read stored settings** (today it does not — it only auto-selects by CoA code). Fetch current values via `GET /core/settings` (or the module settings endpoints) so re-running /setup shows what is already saved, then fall back to CoA-code auto-select only for blanks.
4. **SAVE** keeps the existing pattern: iterate non-null fields → `SettingService.update` → `PUT /core/settings` `{setting_key, value}` (value = account id as string), one request per field. New keys use their **real namespaced `_id` names** (`lis.*`, `production.*`, `hrm.*`), NOT the legacy `accounting.default_*` names.
5. **Reconcile the 7 legacy keys**: either (a) keep writing them for FE pre-fill back-compat AND additionally write the real posting keys, or (b) deprecate them. Recommended: keep the 7 Core/General controls but **also** write the real equivalents the BE actually posts from, so the page becomes functionally correct. Document the mapping in the section header.
6. i18n: add EN/AR labels to `app/assets/i18n/en.json` / `ar.json` (English-first per project rule; AR as secondary).

### 3b. Backend

- **Setting definitions:** mostly **already exist**. No new defs needed for production (8) and hrm (10). For LIS, the core posting defs exist; **define the 2 consumed-only keys** (`lis.commission_expense_account_id`, `lis.commission_payable_account_id`) and **de-duplicate** the 4 keys defined in both LIS and Core seeders (keep one canonical definition; prefer the module seeder for LIS-owned keys, or Core — but not both with divergent labels).
- **`seedDefaultAccountSettings()` (`SettingDefinitionSeeder.php:2111`):** today auto-fills only 7 Core keys. **Optionally extend** it to pre-fill the new module keys from stable CoA leaf codes at provisioning so fresh installs aren't fully null — but this is optional because Production self-resolves and the /setup page will let the owner pick. If extended, map to **detail leaves**, never headers.
- **Save endpoint:** `PUT /core/settings` (`SettingController@update`) already accepts any `{setting_key, value}` with **no existence validation on value** — so it can store all new keys with no controller change. (Trade-off: it also won't reject a bad/header account id — see §4; consider adding optional postable-account validation.)
- **HR FormRequest:** add `hrm.overtime_expense_account_id` + `hrm.leave_accrual_account_id` to `UpdateHrSettingsRequest` if the HR settings screen must save them (the /setup generic route doesn't need it).

### 3c. CoA default-leaf considerations

- The auto-select-by-code logic depends on the **DefaultChartOfAccountsSeeder** leaf codes existing. For new module sections, pick stable default leaf codes (e.g. WIP under 11, applied/clearing under 21, scrap/variance under 52; lab AR a **detail child under** the AR parent `accounting.ar_parent_account` (1103) such as `110302`, lab revenue under 41/4101).
- For Production, the lazy `ProductionAccountResolver` parents (11 / 21 / 52) are the source of truth for where leaves get created — keep auto-select consistent with those.
- Every account the page writes must be a **postable detail leaf** that actually exists in the company's CoA. If the expected default leaf is missing, the field should be left blank rather than auto-selecting a header.

---

## 4. Risks / gotchas

1. **AR-header trap (CRITICAL).** Receivable/AR-type settings (`lis.receivable_account_id`, `lis.insurance_receivable_account_id`, `sales.receivable_account_id`, and any future AR key) **MUST point to a DETAIL LEAF**, never the AR parent/header/control account (`accounting.ar_parent_account` = 1103 `الذمم المدينة`). A JE line cannot post to a header → invoice post throws → invoice silently stays **draft/unpaid**. This is the exact elmadina incident (fixed by creating leaf `110302 ذمم مرضى المعمل` and repointing `lis.receivable_account_id`→74). The picker MUST filter to postable detail leaves AND exclude AR/AP parent headers for AR/AP-typed keys.
2. **Detail-only guards exist in code** for `lis.cogs_account_id` / `lis.reagent_expense_account_id` (header guard `PostLabInvoice.php:425-435`) — picking a header throws at post time. Filter the picker to detail leaves to avoid runtime failures.
3. **Legacy /setup keys are inert.** Saving `accounting.default_*` alone changes nothing in posting. Do not let the owner believe Lab/MFG/HR posting is configured just because those 7 are set — write the **real** namespaced keys.
4. **Duplicate LIS definitions** (4 keys in both LIS + Core seeders with different labels) can produce inconsistent labels in the picker; reconcile before exposing.
5. **Consumed-only / undefined keys** (`lis.commission_expense_account_id`, `lis.commission_payable_account_id`, `lis.method_accounts`, `purchases.landed_cost_clearing_account_id`) work today via lazy/JSON paths but have no `SettingDefinition`; if exposed on a definition-driven UI they must be defined first.
6. **CODE vs id mismatch:** `inventory.stock_account` / `inventory.cogs_account` store account **CODE strings**, not ids — do NOT feed them through the id-based account picker.
7. **Cost center, not account:** `lis.default_cost_center_id` is a cost center; exclude from any GL-account picker.
8. **Per-instance accounts** (treasury / payment-method `account_id`) are separate from settings and must be remapped on company transfer; not configurable from /setup.
9. **No value validation on `PUT /core/settings`** — a typo or header id is accepted silently. Prefer FE filtering + (optionally) BE postable-account validation.
10. **HR placeholders post nothing** — exposing the 5 unwired HR accounts is harmless but won't affect any JE until the corresponding posting code is written (loan/EOS/overtime/leave).

---

## 5. Per-area KB topic links

- [setup-page-frontend.md](./setup-page-frontend.md) — the /setup wizard Step 5 component (load/save flow, 7 legacy keys)
- [backend-flow.md](./backend-flow.md) — Core+Accounting plumbing, seeding, posting consumers, orphan-key finding
- [setting-definition-schema.md](./setting-definition-schema.md) — the `_account_id` convention, 33 Core defs, `seedDefaultAccountSettings()`
- [core-general-accounts.md](./core-general-accounts.md) — the 7 FE-only `accounting.default_*` keys
- [sales-accounts.md](./sales-accounts.md) — sales.* (6 + AR toggle)
- [purchases-accounts.md](./purchases-accounts.md) — purchases.* (5 + AP toggle + landed-cost + check/WHT)
- [inventory-accounts.md](./inventory-accounts.md) — shared inventory-asset chain; CODE-string settings
- [hr-payroll-accounts.md](./hr-payroll-accounts.md) — hrm.* (10; 5 live / 5 placeholders)
- [lis-lab-accounts.md](./lis-lab-accounts.md) — lis.* (10 + cost-center + JSON map + 2 undefined)
- [manufacturing-accounts.md](./manufacturing-accounts.md) — production.* (8, lazily resolved)
- [pos-accounts.md](./pos-accounts.md) — pos.* (3 receiving + enum); reuses Sales actions
- [treasury-payment-accounts.md](./treasury-payment-accounts.md) — POS/check/lis-cash settings + per-instance treasury/method `account_id`

---

### De-duplicated count summary

- **Real GL-account settings (id-typed, postable) across all modules:** ~52 distinct keys — accounting 5 (checks×2, zakat×2, WHT) + sales 6 + purchases 6 + hrm 10 + lis 12 (incl. 2 consumed-only) + production 8 + pos 3 + inventory 2 (CODE-typed). Plus 2 boolean toggles, 1 enum, 1 cost-center, 1 JSON map = non-account settings excluded from a picker.
- **On /setup today:** 7 legacy `accounting.default_*` keys (all inert, no BE consumer).
- **Owner's target additions:** Lab 10 + Manufacturing 8 + HR 10 = **28 new account fields** to expose.
