---
name: setup-extension-spec
title: First-Run /setup "Default Accounts" — Per-Module Extension Spec
slug: default-accounts-setup-extension
description: One implementation spec for extending the MoonERP first-run /setup "Default Accounts" page into per-module collapsible sections, each GL field PRE-SELECTED to a sensible default detail leaf in the default Chart of Accounts. Covers the master setting-key→CoA-leaf mapping, the AR/AP-header trap, the FE redesign, the BE seeder/definition work, and open owner decisions.
status: active
owner: hazem
updated: 2026-06-18
refs:
  - moon-erp-be/Modules/Accounting/database/seeders/DefaultChartOfAccountsSeeder.php
  - moon-erp-be/Modules/Core/database/seeders/SettingDefinitionSeeder.php (seedDefaultAccountSettings ~2111)
  - moon-erp-be/Modules/LIS/database/seeders/LabSettingDefinitionSeeder.php
  - moon-erp-be/Modules/Production/app/Services/ProductionAccountResolver.php
  - moon-erp-be/Modules/Accounting/app/Services/AutoAccountService.php (createChildAccount ~46)
  - moon-erp-be/Modules/Accounting/app/Services/AccountCodeGenerator.php (generateChildCode ~9)
  - moon-erp/src/app/features/setup/setup-wizard.component.{ts,html,scss}
---

# First-Run /setup "Default Accounts" — Per-Module Extension Spec

## 🔒 STANDING RULE (owner, 2026-06-19) — keep default accounts COMPLETE as the app grows
**Whenever ANY new `*_account_id` setting is added (any module), it MUST be wired by default — don't leave it null/lazy.** Three touch-points, every time:
1. **`Modules/Core/database/seeders/SettingDefinitionSeeder::seedDefaultAccountSettings`** — add the key to **`$codeMap`** (if it maps to an existing default-CoA detail leaf) OR **`$childAccounts`** (if it needs a find-or-create detail child under a control parent; AR/AP-style → under 1103/2101). This wires it at provisioning AND repairs existing companies (the backfill migration `2026_06_19_150000_wire_all_default_account_settings` re-runs it fleet-wide).
2. **`moon-erp/src/app/features/setup/default-accounts.config.ts`** — add the field to its module section (real key + `autoCodes`/`isAr`/`isCode`) so it appears on `/setup` pre-selected.
3. **Verify** with the SQL: `SELECT … FROM settings s LEFT JOIN accounts a ON a.id=s.value … WHERE setting_key LIKE '%_account_id' AND (a.id IS NULL OR a.account_type='header')` → MUST be empty (every key wired to a postable **detail leaf**, never a header).
**The seeder is the single source of truth for "what gets wired"** — `grep "'setting_key' => '..._account_id'"` across all module seeders must equal the seeder's `$codeMap`+`$childAccounts` keys. Today: **50 keys = 47 defined + 3 consumed-only (commission×2, landed-cost), 100% covered.**

## ✅ FULLY WIRED + VERIFIED (2026-06-19) — "all accounts present + linked in any install"
Beyond the /setup UI, **every** default-account setting is now wired by default (owner: "كل الحسابات تتربط في أي نسخة"). `seedDefaultAccountSettings` (BE `8a85fbf71`): wires all 50 keys (code-based → detail leaves; the rest → find-or-create detail children, incl. all 8 production, lab costing/AR/AP, input-VAT, WHT, zakat, employee loans, doctor-commission ×2, landed-cost, card clearing), AND **repairs** any key pointing at a control HEADER (re-points code keys to the code — or its first detail child when the code itself became a header, e.g. 1101 after treasuries; child keys → a fresh detail child). New companies get it at provisioning; existing companies via the backfill **migration** on update. **Verified on company 4: 53/53 account keys → postable detail leaves, zero headers, zero nulls.** ⏳ next: POS section on the /setup UI; owner browser click-test of the wizard.

## ✅ IMPLEMENTED (2026-06-19) — owner decisions taken
Shipped on `hazemdev` (BE `9615d724c`+`cbac3ded0`+`3d6c53abe`, FE `3ca24be`; FE deployed to moonui `/app`). **Owner decisions:** scope = 7 sections (Lab/MFG/HR expanded + Sales/Purchases/Inventory/Accounting); missing leaves = **hybrid** (pre-create the high-traffic WIP/FG/input-VAT + AR/AP children at provisioning, rest lazy at first post); **AR/AP trap fixed at source** + `seedDefaultAccountSettings` extended; legacy 7 `accounting.default_*` keys **dropped**.
- **BE** `SettingDefinitionSeeder::seedDefaultAccountSettings`: now pre-wires ~17 safe code-based keys + find-or-creates DETAIL CHILDREN for `sales.receivable`/`purchases.payable`/`lis.receivable`/`lis.insurance_receivable`/`lis.external_lab_payable` (under 1103/2101, was the bug) + `production.wip`/`fg` + `purchases.tax_receivable` (under 11). Idempotent. Fixed `inventory.stock_account` 1301→1105 / `inventory.cogs_account` 4101→5101.
- **FE** new `src/app/features/setup/default-accounts.config.ts` (7 sections × real `*_account_id` keys + autoCodes + isAr/isCode); `setup-wizard.component` rebuilt Step 5 = `p-accordion` data-driven sections; LOAD reads stored values over all config modules (incl lis/production) → pre-select stored→autoCode→blank (never header); picker = detail leaves only; SAVE writes real keys (inventory.* stores the CODE), legacy keys gone. Verified: settings endpoints return all keys with stored values (production 8, lis 10, hrm 10, sales 6). ⏳ remaining: owner click-test the wizard UI in the browser; POS section deferred to a later pass.

## 0. Goal & TL;DR

Today the /setup wizard's "Default Accounts" step (`currentStep()===4`) renders **7 hard-coded, inert** `accounting.default_*` selects that no BE posting code consumes, never reads stored values, and auto-selects purely by CoA code. We are replacing it with **data-driven per-module collapsible sections** where **every GL field is pre-selected to a sensible default detail leaf**, and **save writes the REAL namespaced `*_account_id` posting keys** the backend actually posts from (`lis.*`, `production.*`, `hrm.*`, `sales.*`, `purchases.*`).

Two cross-cutting invariants drive the whole design:

1. **Only `account_type='detail'` accounts are postable.** Headers (`1`, `11`, `12`, `2`, `21`, `22`, `3`, `4`, `5`, `51`, `52`, `53`) throw if a JE line targets them. The picker must be detail-leaf-only.
2. **The AR/AP-header trap (CRITICAL, the elmadina incident).** `1103 Accounts Receivable` and `2101 Accounts Payable` are seeded `detail`, but they ARE `accounting.ar_parent_account` / `accounting.ap_parent_account`. The moment the first partner sub-account auto-creates under them (`AutoAccountService::createChildAccount`), they **flip to `header`/control** — and any JE line posting directly to them throws, leaving the source doc silently **draft/unpaid**. No receivable/payable default may ever point at the bare `1103`/`2101` parent — it must be a **dedicated detail CHILD** (e.g. `110301`, `210101`).

---

## 1. Master Default-Account Table

`AR-risk` = the AR/AP-header trap applies (must resolve to a detail child under a control parent, never the parent). `Leaf?` = is the *default* a postable detail leaf as-is (✔) or must it be created / is the named code a header (✘).

### 1.1 Core / Accounting

| Section | setting_key | AR label | EN label | CoA code | CoA name | Leaf? | Resolution / create rule | AR-risk |
|---|---|---|---|---|---|---|---|---|
| accounting | accounting.default_cash_account | حساب الصندوق | Cash Account | 1101 | Cash on Hand / النقدية بالصندوق | ✔ | Code 1101 detail. Legacy FE-only key (no BE consumer). | – |
| accounting | accounting.default_bank_account | حساب البنك | Bank Account | 1102 | Cash at Bank / النقدية بالبنك | ✔ | Code 1102 detail. Legacy FE-only key. | – |
| accounting | accounting.default_client_account | حساب العملاء (المدينون) | Accounts Receivable | 110301 | Customers Receivable / ذمم عملاء (child under 1103) | ✘ | **AR trap.** find-or-create detail child under `ar_parent_account` (1103); never 1103 itself. Legacy FE-only key. | ✔ |
| accounting | accounting.default_supplier_account | حساب الموردين (الدائنون) | Accounts Payable | 210101 | Suppliers Payable / موردون (child under 2101) | ✘ | **AP trap.** find-or-create detail child under `ap_parent_account` (2101); never 2101. Legacy FE-only key. | ✔ |
| accounting | accounting.default_expense_account | حساب المصروفات | Expense Account | 5201 | Salaries & Wages / الرواتب والأجور | ✔ | Detail under header 52; fallback 5101. Legacy FE-only key. | – |
| accounting | accounting.default_revenue_account | حساب الإيرادات | Revenue Account | 4101 | Sales Revenue / إيرادات المبيعات | ✔ | Setup currently maps to `'41'` which **does not exist** (header is `4`); correct = 4101. Legacy FE-only key. | – |
| accounting | accounting.default_check_account | حساب أوراق القبض/الدفع | Notes Receivable/Payable | 1104 | Notes Receivable / أوراق القبض | ✔ | Code 1104 detail. Payable counterpart = 2102. Legacy FE-only key. | – |
| accounting | accounting.checks_received_account_id | حساب الشيكات المُستلَمة الوسيط | Checks Received Intermediate | 1104 | Notes Receivable / أوراق القبض | ✔ | Real posting key (CollectCheck.php:30). Def hints `'1103'` — **DO NOT** (AR trap); use 1104 or create "Checks Under Collection / شيكات تحت التحصيل" detail child under 11. Ships null. | ✔(hint) |
| accounting | accounting.checks_issued_account_id | حساب الشيكات المُصدَرة الوسيط | Checks Issued Intermediate | 2102 | Notes Payable / أوراق الدفع | ✔ | Real posting key (CashCheck.php:32). Def hints `'2103'` (Accrued, also detail); prefer 2102. Ships null. | – |
| accounting | accounting.wht_payable_account_id | حساب ضريبة الاستقطاع المستحقة | Withholding Tax Payable | 210501 | Withholding Tax Payable / ضريبة استقطاع مستحقة (child under 2105) | ✘ | No WHT leaf. find-or-create detail child under 2105; fallback pre-select 2105 directly. ApprovePaymentVoucher throws `wht_payable_account_missing` if unset. | – |
| accounting | accounting.zakat.expense_account_id | مصروف الزكاة | Zakat / Other-tax Expense | — | (none — create under 53) | ✘ | No zakat-expense leaf. find-or-create detail child under header 53 (e.g. 5304); pair payable as child under 21. | – |

### 1.2 Sales

| Section | setting_key | AR label | EN label | CoA code | CoA name | Leaf? | Resolution / create rule | AR-risk |
|---|---|---|---|---|---|---|---|---|
| sales | sales.revenue_account_id | حساب الإيرادات | Revenue GL Account | 4101 | Sales Revenue / إيرادات المبيعات | ✔ | Code 4101 detail. Auto-seeded. Mandatory — invoice post throws `missing_accounting_settings` without it. | – |
| sales | sales.cogs_account_id | حساب تكلفة البضاعة المباعة | COGS GL Account | 5101 | Cost of Goods Sold / تكلفة البضاعة المباعة | ✔ | Code 5101 detail under header 51. Do NOT use 51. Auto-seeded. | – |
| sales | sales.receivable_account_id | حساب الذمم المدينة | AR GL Account | 110301 | Trade Receivables / ذمم العملاء التجارية (child under 1103) | ✘ | **AR trap.** find-or-create detail child under 1103. Existing auto-seed →1103 is the bug to fix. Mandatory. | ✔ |
| sales | sales.inventory_account_id | حساب المخزون | Inventory GL (Sales contra) | 1105 | Inventory / المخزون | ✔ | Code 1105 detail. First link in inventory-asset fallback chain. | – |
| sales | sales.tax_payable_account_id | حساب الضريبة المستحقة | Tax Payable GL | 2105 | Taxes Payable / ضرائب مستحقة | ✔ | Code 2105 detail (output VAT). | – |
| sales | sales.discount_account_id | حساب خصم المبيعات | Sales Discount GL | 4105 | Sales Discounts / خصومات المبيعات | ✔ | Code 4105 detail (contra-revenue, debit). Optional — if null, revenue booked net of discount. | – |

### 1.3 Purchases

| Section | setting_key | AR label | EN label | CoA code | CoA name | Leaf? | Resolution / create rule | AR-risk |
|---|---|---|---|---|---|---|---|---|
| purchases | purchases.inventory_account_id | حساب المخزون | Inventory GL | 1105 | Inventory / المخزون | ✔ | Code 1105 detail. Already auto-seeded. | – |
| purchases | purchases.payable_account_id | حساب الدائنون | AP GL Account | 210101 | Trade Payables / ذمم الموردين التجارية (child under 2101) | ✘ | **AP trap.** find-or-create detail child under 2101; never 2101. Existing auto-seed →2101 is the bug to fix. | ✔ |
| purchases | purchases.expense_account_id | حساب المصروفات | Expense GL | 520701 | Purchases Expense / مصروفات المشتريات (child under 52) | ✘ | No generic purchases-expense leaf (52 is header). find-or-create detail child under 52; acceptable fallback 5207 Office Supplies. Falls back to inventory acct if unset. | – |
| purchases | purchases.tax_receivable_account_id | حساب ضريبة المدخلات | Input Tax GL | 110701 | Recoverable/Input VAT / ضريبة القيمة المضافة المستردة (child under 11) | ✘ | No input-VAT leaf (input VAT is recoverable = asset). find-or-create detail child under 11. Not auto-seeded (null). | – |
| purchases | purchases.discount_account_id | حساب خصم المشتريات | Purchase Discount GL | 410301 | Purchase Discounts Received / خصم مشتريات مكتسب (child under 4) | ✘ | No purchase-discount leaf (4105 is SALES). Optional. find-or-create other-income detail child under 4. | – |
| purchases | purchases.landed_cost_clearing_account_id | حساب تسوية تكلفة الوصول | Landed Cost Clearing | 210301 | Landed Cost Clearing / تسوية تكلفة الوصول (child under 21) | ✘ | NOT in seeder — self-heals at first post (PostLandedCostVoucher::resolveClearingAccount creates child under 21). Optional in /setup; can leave blank. | – |

### 1.4 Inventory (CODE-typed — exclude from id picker)

| Section | setting_key | AR label | EN label | CoA code | CoA name | Leaf? | Resolution / create rule | AR-risk |
|---|---|---|---|---|---|---|---|---|
| inventory | inventory.stock_account | رمز حساب المخزون | Stock Account **Code** | 1105 | Inventory / المخزون | ✔ | **VALUE IS A CODE STRING, not an id.** Default `'1105'`; seeder default `'1301'` does not exist — override. Last code-based fallback in inventory-asset chain. Do NOT feed through the id picker. | – |
| inventory | inventory.cogs_account | رمز حساب تكلفة البضاعة المباعة | COGS Account **Code** | 5101 | Cost of Goods Sold / تكلفة البضاعة المباعة | ✔ | **CODE STRING.** Default `'5101'`; seeder default `'4101'` is wrong (that is revenue) — override. Secondary fallback; real COGS posts via `sales.cogs_account_id`. | – |

### 1.5 Lab (LIS)

All 10 keys defined in `LabSettingDefinitionSeeder.php` (lines 347–724). `cogs`+`reagent` have a **runtime header-guard** (`PostLabInvoice.php:425-435`) → picker MUST be detail-only. Excluded from picker: `lis.default_cost_center_id` (cost center), `lis.method_accounts` (JSON).

| Section | setting_key | AR label | EN label | CoA code | CoA name | Leaf? | Resolution / create rule | AR-risk |
|---|---|---|---|---|---|---|---|---|
| lis | lis.receivable_account_id | حساب الذمم | Receivable Account | 110301 | Lab Patients Receivable / ذمم مرضى المعمل (child under 1103) | ✘ | **AR trap.** find-or-create detail child under `ar_parent_account`(1103); if elmadina-style `110302 ذمم مرضى المعمل` exists, pre-select it. Never 1103. | ✔ |
| lis | lis.revenue_account_id | حساب الإيرادات | Revenue Account | 4102 | Service Revenue / إيرادات الخدمات | ✔ | Code 4102 detail (lab = service). 4101 reserved for Sales. If absent create child under 4 (e.g. 4107 lab revenue). | – |
| lis | lis.tax_payable_account_id | حساب الضريبة المستحقة | Tax Payable Account | 2105 | Taxes Payable / ضرائب مستحقة | ✔ | Code 2105 detail (shared output-VAT with Sales). | – |
| lis | lis.insurance_receivable_account_id | حساب ذمم التأمين | Insurance Receivable | 110302 | Insurance Companies Receivable / ذمم شركات التأمين (child under 1103) | ✘ | **AR trap.** find-or-create detail child under 1103, distinct from patient AR (110301). Never 1103. | ✔ |
| lis | lis.discount_account_id | حساب الخصومات | Discount Account | 4105 | Sales Discounts / خصومات المبيعات | ✔ | Code 4105 detail. **UNUSED** by lab posting (revenue booked net of discount) — optional to expose. | – |
| lis | lis.cash_account_id | حساب النقد | Cash Account | 1101 | Cash on Hand / النقدية بالصندوق | ✔ | Code 1101 detail. CR fallback in doctor-commission settlement; inbound patient pay uses per-treasury receiving acct. If 1101 turned header by treasuries, create generic child 110101. | – |
| lis | lis.external_lab_expense_account_id | حساب مصروفات المختبرات الخارجية | External Lab Expense | — | External Lab Expense / مصروفات المختبرات الخارجية (child under 52) | ✘ | No leaf. find-or-create detail child under 52 (e.g. 5208). Leave blank until created (never auto-pick a header). | – |
| lis | lis.external_lab_payable_account_id | حساب ذمم المختبرات الخارجية | External Lab Payable | 210101 | External Labs Payable / ذمم المختبرات الخارجية (child under 2101) | ✘ | **AP trap.** find-or-create detail child under `ap_parent_account`(2101). Never 2101. | ✔ |
| lis | lis.cogs_account_id | حساب تكلفة البضاعة المباعة | COGS Account | 5101 | Cost of Goods Sold / تكلفة البضاعة المباعة | ✔ | Code 5101 detail. **Runtime header guard** (PostLabInvoice.php:425-435) — must be detail. | – |
| lis | lis.reagent_expense_account_id | حساب مصروفات الكواشف | Reagent Expense | — | Reagent Expense / مصروفات الكواشف (child under 51) | ✘ | No leaf. find-or-create detail child under 51 (e.g. 5102), paired CR vs COGS. **Runtime header guard** — must be detail. Leave blank until created. | – |

### 1.6 Manufacturing (production.*)

NONE are seeded into the default CoA and NONE have a stable leaf code → `default CoA code` is intentionally **empty** for all 8. They are lazily find-or-created on first post by `ProductionAccountResolver` via `AutoAccountService::createChildAccount(companyId, parentCode, nameEn, nameAr)` (always `account_type='detail'`). The lazy code = parent + next 2-digit suffix (`AccountCodeGenerator::generateChildCode`); the "e.g." codes below are fresh-CoA illustrations only — **do not hard-code them**. The /setup page should either **invoke the same resolver** or replicate find-or-create-child-under-parent with the EXACT EN/AR names below (copied verbatim from the resolver).

| Section | setting_key | AR label | EN label | CoA code | CoA name (verbatim) | Leaf? | Resolution / create rule | AR-risk |
|---|---|---|---|---|---|---|---|---|
| production | production.wip_account_id | حساب الإنتاج تحت التشغيل | Work-In-Progress | — | Work In Progress / إنتاج تحت التشغيل (next child under 11, ~1107) | ✔ | find-or-create detail child under header **11** (asset, debit). `resolveWip`. Persist new id. | – |
| production | production.fg_account_id | حساب المنتجات التامة | Finished Goods | — | Finished Goods / منتجات تامة (next child under 11, ~1108) | ✔ | find-or-create detail child under **11** (asset, debit). `resolveFg`. (Resolver makes a dedicated FG leaf, not 1105.) | – |
| production | production.scrap_account_id | حساب هالك الإنتاج | Production Scrap | — | Production Scrap / هالك الإنتاج (next child under 52, ~5208) | ✔ | find-or-create detail child under header **52** (expense, debit). `resolveScrap`. | – |
| production | production.labor_applied_account_id | حساب العمالة المحملة | Labor Applied | — | Labor Applied / العمالة المحملة (next child under 21, ~2106) | ✔ | find-or-create detail child under header **21** (liability, credit — absorption/clearing). `resolveLaborApplied`. | – |
| production | production.overhead_applied_account_id | حساب التكاليف غير المباشرة المحملة | Overhead Applied | — | Overhead Applied / التكاليف غير المباشرة المحملة (next child under 21, ~2107) | ✔ | find-or-create detail child under **21** (liability, credit). `resolveOverheadApplied`. | – |
| production | production.cost_variance_account_id | حساب انحراف تكلفة الإنتاج | Production Cost Variance | — | Production Cost Variance / انحراف تكلفة الإنتاج (next child under 52, ~5209) | ✔ | find-or-create detail child under **52** (expense). `resolveCostVariance`. | – |
| production | production.toll_clearing_account_id | حساب تسوية التصنيع لدى الغير | Toll Clearing | — | Toll Clearing / تسوية التصنيع لدى الغير (next child under 11, ~1109) | ✔ | find-or-create detail child under **11** (receivable-STYLE clearing). MUST be its own detail leaf, NEVER the 1103 AR header. `resolveTollClearing`. | ✔(by-style) |
| production | production.customer_advance_account_id | حساب سُلف العملاء | Customer Advances | — | Customer Advances / سُلف العملاء (next child under 21, ~2108) | ✔ | find-or-create detail child under **21** (liability for unearned toll down-payments — explicitly NOT customer AR). `resolveCustomerAdvance`. | ✔(by-style) |

### 1.7 HR / Payroll (hrm.*)

All 10 defs already exist. Defaults below map to existing detail leaves where sensible; the "umbrella" liability leaves (`2103`, `2105`) are acceptable bases but cleaner ledgers warrant dedicated children. `hrm.income_tax_account_id` / `loan_receivable` have no leaf and need creation.

| Section | setting_key | EN label | CoA code | CoA name | Leaf? | Resolution / create rule | AR-risk |
|---|---|---|---|---|---|---|---|
| hrm | hrm.salary_expense_account_id | Salary Expense | 5201 | Salaries & Wages / الرواتب والأجور | ✔ | Code 5201 detail under header 52. | – |
| hrm | hrm.salary_payable_account_id | Salary Payable | 2103 | Accrued Expenses / مصروفات مستحقة | ✔ | Base on 2103 (accrued umbrella) or create dedicated `رواتب مستحقة` child under 21. | ✔(umbrella) |
| hrm | hrm.social_insurance_account_id | Social Insurance Payable | 2105 | Taxes/Payable / مستحقات | ✔ | Base 2105 or create payable child under 21. Employer-cost vs deduction differs. | ✔(umbrella) |
| hrm | hrm.medical_insurance_account_id | Medical Insurance Payable | 2105 | مستحقات | ✔ | As social insurance — base 2105 or dedicated child under 21. | ✔(umbrella) |
| hrm | hrm.income_tax_account_id | Income Tax Payable | — | (none — create under 21) | ✘ | No leaf. find-or-create liability detail child under 21. | – |
| hrm | hrm.loan_receivable_account_id | Employee Loan Receivable | — | (none — create under 11) | ✘ | No leaf. find-or-create asset detail child under 11 (employee loans = receivable). Never the 1103 AR header. | ✔(by-style) |
| hrm | hrm.eos_provision_account_id | EOS Provision | 2202 | End of Service Benefits / مكافأة نهاية الخدمة | ✔ | Code 2202 detail under header 22 (currently defined-but-unposted). | – |
| hrm | hrm.eos_expense_account_id | EOS Expense | 5201 | Salaries & Wages (base) | ✔ | Base 5201; cleaner = create EOS-expense child under 52. | – |
| hrm | hrm.overtime_expense_account_id | Overtime Expense | 5201 | Salaries & Wages (base) | ✔ | Base 5201; saves via generic PUT even though absent from `UpdateHrSettingsRequest`. | – |
| hrm | hrm.leave_accrual_account_id | Leave Accrual | 2103 | Accrued Expenses / مصروفات مستحقة | ✔ | Base 2103; saves via generic PUT. | ✔(umbrella) |

### 1.8 Treasury / POS

Excluded from picker: `pos.default_receiving_account_type` (enum), per-instance PettyCash/LabPaymentMethod `account_id` rows (auto-provisioned under 1101/1102, remap-on-transfer gotcha).

| Section | setting_key | AR label | EN label | CoA code | CoA name | Leaf? | Resolution / create rule | AR-risk |
|---|---|---|---|---|---|---|---|---|
| pos | pos.cash_receiving_account_id | حساب استلام الكاش (الخزينة) | Cash Receiving Account | 1101 | Cash on Hand / النقدية بالصندوق | ✔ | Code 1101 detail (or per-branch PettyCash leaf under 1101). DR for POS cash/check. POSSaleController.php:187. Ships null. | – |
| pos | pos.card_receiving_account_id | حساب استلام الشبكة (وسيط) | Card/Network Receiving | — | Card Payments Clearing / شبكة تحت التحصيل (child under 11) | ✘ | Money-in-transit; no leaf. find-or-create detail child under 11; fallback 1102. POSSaleController.php:188. Leave blank rather than pick header. | – |
| pos | pos.default_receiving_account_id | حساب الاستلام العام | Default Receiving Account | 1101 | Cash on Hand / النقدية بالصندوق | ✔ | Fallback DR when method-specific unset. Code 1101 detail. POSSaleController.php:195. | – |

---

## 2. FE Redesign Spec

All paths under `/home/moonui/public_html/moon-erp/`.

### Files to touch
- `src/app/features/setup/setup-wizard.component.ts` — config array, load, save, leaf filter.
- `src/app/features/setup/setup-wizard.component.html` — the `@if (currentStep() === 4)` block (lines 259–367) → replace flat `.defaults-grid` with data-driven collapsible sections.
- `src/app/features/setup/setup-wizard.component.scss` — accordion/section styles.
- `src/assets/i18n/en.json` + `ar.json` — `SETUP.*` labels (English authored first).
- **New (many-small-files rule):** `src/app/features/setup/default-accounts.config.ts` — the section/field config.
- Reuse: `src/app/core/services/setting.service.ts` (`list(module)` → `{definition, current_value}`, `update()`); `src/app/core/models/account.model.ts` (`account_type`, `has_children`).

### (1) Data-driven collapsible per-module sections
Replace the 7 signals + 7 inline selects with ONE config + a value Map.

```ts
interface DefaultAccountField { key: string; labelEn: string; labelAr: string; icon: string; isAr?: boolean; autoCodes?: string[]; }
interface DefaultAccountSection { module: string; titleKey: string; icon: string; collapsed?: boolean; fields: DefaultAccountField[]; }
```
`DEFAULT_ACCOUNT_SECTIONS` order: Core/General, Sales, Purchases, (Inventory skipped — CODE-typed), Lab(LIS), Manufacturing(production), HR(hrm), Treasury/POS. Per the master INDEX owner priority is **Lab + MFG + HR** → render those **expanded** by default, others collapsed.

Each `field.key` is the REAL namespaced posting key (`lis.receivable_account_id`, `production.wip_account_id`, …) — NOT the legacy `accounting.default_*` names. `field.isAr=true` marks receivable/payable/AR-style fields (drives the header exclusion in §3).

Values live in ONE signal (immutable updates):
```ts
accountValues = signal<Record<string, number|null>>({});
setAccount(key: string, id: number|null) { this.accountValues.update(v => ({ ...v, [key]: id })); }
```

Render with PrimeNG 21 `<p-accordion [multiple]="true">`, one `<p-accordion-panel value="<moduleIdx>">` per section. Inside, `@for` over `section.fields` emitting ONE shared `<p-select>`:
```html
[ngModel]="accountValues()[f.key]" (ngModelChange)="setAccount(f.key, $event)"
[options]="postableLeaves()" optionLabel="name" optionValue="id"
[filter]="true" filterBy="name,name_ar,name_en,code" appendTo="body" [showClear]="true"
```
Item/selected templates show `{{a.code}} - {{a.name_en || a.name}}` (**English-first** — flip the current `name_ar||name`). Labels via `SETUP.*` translate keys (or render from the loaded `SettingDefinition.label_en/label_ar` to avoid hand-maintaining ~28 i18n keys). This collapses ~85 lines of duplicated HTML into one ~15-line `@for`. `postableLeaves()` is fetched ONCE and reused for every dropdown.

### (2) Load — pre-select stored value, fall back to CoA-code auto-select
Today LOAD ignores stored settings. New `loadDefaults()`:
- (a) Fetch postable leaves ONCE (keep the existing paginated `forkJoin` fetch → `postableLeaves()`; filter per §3).
- (b) Read CURRENT stored values per module via `SettingService.list(module)` → map `setting_key → current_value`. **`ALL_MODULES` in `setting.service.ts` is `['accounting','sales','purchases','inventory','hrm']` and does NOT include `lis`/`production`** → `forkJoin` over the **distinct modules in the config** (explicitly include `lis` and `production`).
- (c) Per field: if stored `current_value` exists AND that id is in `postableLeaves()` → pre-select it. Else if `field.autoCodes` present → auto-select by code (reuse existing `find([codes])` logic, ts:657-663). Else **leave null** (NEVER auto-pick a header — AR trap). Seed `accountValues()` from this merged map → re-running /setup shows what's already configured.
- Trigger load on entering the step (`skipCOA()` ts:609-611 and end of `importCOA()` ts:536).

### (3) Picker filtered to postable detail leaves (+ AR/AP header exclusion)
`Account.account_type` ∈ `'detail' | 'header'`. **Postable leaf = `account_type==='detail'`** (`has_children===false` reinforces). Base filter: `allAccounts.filter(a => a.account_type === 'detail')` — this alone already excludes header-flipped `1103`/`2101`.

For `isAr` fields (`lis.receivable_account_id`, `lis.insurance_receivable_account_id`, `sales.receivable_account_id`, `purchases.payable_account_id`, `lis.external_lab_payable_account_id`): set `autoCodes` to **specific child leaf codes** (`110301`/`110302`/`210101`) — and if absent, **leave BLANK** rather than default to a parent. Optional belt-and-suspenders `leavesFor(field)` that additionally strips any account whose code is exactly an AR/AP parent (`1103`/`2101`). `account_type==='detail'` is the single source of truth for postability; `classification` only labels sections.

### (4) Save — write REAL namespaced `*_account_id` keys via PUT /core/settings
New `saveDefaults()`: iterate `Object.entries(accountValues())`, for each non-null push `{ setting_key: f.key, value: String(id) }` (the REAL key). Keep the existing sequential RxJS chain (`switchMap` reduce; each `SettingService.update(s)` wrapped in `catchError(()=>of(null))` so one failure doesn't abort), then `nextStep()`. `PUT /core/settings` accepts any `{setting_key,value}` with no BE value-validation → all new keys save with **no controller change**. **Decision on the 7 legacy `accounting.default_*` keys:** they're inert (no BE consumer). Simplest correct path = **drop them, use only real keys**; flag as an owner decision in the section header. (Optional back-compat: also mirror Core fields to the legacy keys.)

### Exclusions / gotchas
- Exclude from the GL-id picker: `lis.default_cost_center_id` (cost center), `lis.method_accounts` (JSON), `pos.default_receiving_account_type` (enum), `inventory.stock_account` / `inventory.cogs_account` (CoA **code strings**, not ids), `sales.use_customer_ar_account` / `purchases.use_supplier_ap_account` (booleans → `p-toggleswitch` if exposed).
- `lis.commission_expense_account_id` / `lis.commission_payable_account_id` are consumed-only (no seeder) — omit unless BE defines them first.
- `hrm.overtime_expense_account_id` & `hrm.leave_accrual_account_id` save fine via generic PUT despite being absent from `UpdateHrSettingsRequest`.

---

## 3. BE Work

### 3.1 Setting definitions — exist vs need adding
- **Already defined (no work):** all `sales.*`, `purchases.*`, `accounting.*` (incl. `checks_*`, `wht_payable`), all 10 `hrm.*`, all 8 `production.*`, and 8 of 10 `lis.*` keys.
- **LIS — 2 to ADD** to `LabSettingDefinitionSeeder.php`: `lis.external_lab_expense_account_id` and `lis.reagent_expense_account_id` are referenced/consumed but ensure their `SettingDefinition` rows exist (so `SettingService.list('lis')` returns them with a `current_value` slot). (If already present, no-op.) `lis.commission_expense_account_id` / `lis.commission_payable_account_id` are consumed-only with NO definition → either add definitions or keep them out of the UI (recommend: out of v1).
- **Dedup / fix 4 wrong seeder defaults** (override these definition defaults, all already-existing keys):
  1. `inventory.stock_account` default `'1301'` → **`'1105'`** (1301 doesn't exist).
  2. `inventory.cogs_account` default `'4101'` → **`'5101'`** (4101 is revenue, not COGS).
  3. `accounting.default_revenue_account` setup-map `'41'` → **`'4101'`** (`41` is not a code; header is `4`).
  4. `accounting.checks_received_account_id` def hint `'1103'` → **`'1104'`** (1103 is the AR-trap parent).

### 3.2 Should `seedDefaultAccountSettings()` pre-seed the new keys?
`seedDefaultAccountSettings()` (`SettingDefinitionSeeder.php:2111`) currently auto-wires ONLY 5 sales + 2 purchases keys — and it wires `sales.receivable_account_id→1103` / `purchases.payable_account_id→2101`, **baking the AR/AP-header trap into provisioning**.

Recommendation: **extend it carefully, but NEVER pre-seed an AR/AP-style key to a control parent.**
- **Safe to pre-seed from a fixed default-CoA code** (idempotent find-by-code): `sales.revenue→4101`, `sales.cogs→5101`, `sales.inventory`/`purchases.inventory→1105`, `sales.tax_payable`/`lis.tax_payable→2105`, `sales.discount`/`lis.discount→4105`, `lis.revenue→4102`, `lis.cogs→5101`, `lis.cash→1101`, `hrm.salary_expense→5201`, `hrm.eos_provision→2202`, `accounting.checks_received→1104`, `accounting.checks_issued→2102`, `pos.cash_receiving`/`pos.default_receiving→1101`.
- **AR/AP-style keys** (`*.receivable`, `*.payable`, `insurance_receivable`, `external_lab_payable`): pre-seed via **find-or-create a detail child** under `ar_parent_account`/`ap_parent_account` (reuse `AutoAccountService::createChildAccount`) — e.g. `110301`/`210101` — then store THAT id. **FIX the existing `→1103`/`→2101` wiring to this child pattern** (this is the direct elmadina-incident fix).
- **No-default keys** (production.*, scrap/variance/applied, input VAT, external-lab expense, reagent, card-clearing, zakat, income-tax, loan, WHT): do NOT pre-seed; rely on (a) the /setup page invoking the resolver/find-or-create on save, or (b) lazy creation at first post. Pre-seeding them would require creating leaves at provisioning time — defer to the owner decision in §4.

Net: extending `seedDefaultAccountSettings()` means fresh companies arrive at /setup with most fields **already pre-selected** (load step (c) just echoes them), and the AR/AP trap is eliminated at the source.

### 3.3 Postable-leaf filter source for the picker
The FE already fetches `GET /accounting/accounts` (paginated, `forkJoin`) and filters client-side `account_type==='detail'`. Two options:
- **Keep client filter (recommended, zero BE change):** the response already carries `account_type` + `has_children`; filter in FE. Works today.
- **Optional BE convenience:** add an `?account_type=detail` (and/or `?postable=1`) query param to the accounts index to return only detail leaves — reduces payload and centralizes the rule. Nice-to-have, not required for v1.

The find-or-create-child plumbing (`AutoAccountService::createChildAccount(companyId, parentCode, nameEn, nameAr)` + `AccountCodeGenerator::generateChildCode`) and `ProductionAccountResolver` already exist — the /setup save path should call them for missing leaves rather than re-implement.

---

## 4. Open Decisions (owner)

1. **Create missing leaves automatically vs require manual pick?** For the ~12 keys with no default-CoA leaf (production.*, input VAT, external-lab expense, reagent, card-clearing, zakat, income-tax, loan, WHT), do we (a) auto find-or-create the leaf at /setup-save / provisioning (pre-selected, opinionated CoA), or (b) leave the field blank and let the user pick / let first-post lazily create it (lean CoA, self-healing)? Recommendation: auto-create the high-traffic ones (production WIP/FG, input VAT) on save; lazy-create the rest.
2. **Which modules in v1?** Master INDEX priority = **Lab + MFG + HR** expanded. Ship those three + Core/Sales/Purchases collapsed; defer Treasury/POS section to v2? Or ship all 8 sections at once.
3. **Legacy `accounting.default_*` keys** — drop entirely (use only real `*_account_id` keys) vs keep mirroring for FE back-compat? Recommendation: drop.
4. **AR/AP child naming/codes** — fixed reserved codes (`110301 Trade Receivables`, `210101 Trade Payables`) seeded into the default CoA, vs let `AutoAccountService` generate the next suffix? Fixed codes make /setup pre-selection deterministic; generated codes match current production behavior.
5. **`seedDefaultAccountSettings()` scope** — extend it now to pre-wire all safe keys (so /setup is mostly pre-filled and the AR/AP bug is fixed at the source), or fix only the `1103`/`2101` wiring and rely on the /setup page to populate the rest? Recommendation: extend + fix the AR/AP wiring (biggest correctness win).

---

## 5. Build Order
1. **BE seeder fixes (correctness first):** fix the 4 wrong defaults; re-point `sales.receivable`/`purchases.payable` from `1103`/`2101` to find-or-create detail children; add the 2 missing LIS expense definitions.
2. **BE (optional) extend `seedDefaultAccountSettings()`** to pre-wire all safe-code keys + AR/AP children → fresh installs arrive pre-selected.
3. **FE config** (`default-accounts.config.ts`) — the 8 sections × real `*_account_id` keys, `isAr` flags, `autoCodes`, icons.
4. **FE load + picker** — fetch detail leaves once; `forkJoin` over all config modules (incl. lis/production) to read stored `current_value`; pre-select stored→autoCode→blank; detail-only picker with AR/AP guard.
5. **FE render + save + i18n** — `p-accordion` data-driven sections (Lab/MFG/HR expanded), English-first labels, save writes real keys via `PUT /core/settings`; add `SETUP.*` EN+AR keys; build + deploy to moonui.
