# 12 — Accounting & Core Rails for Manufacturing Costing

Scope: what a Manufacturing (Production) costing build consumes from `Modules/Accounting` and
`Modules/Core` in Moon ERP. Source tree: `/home/moonui/moon-erp-be`. Read-only audit; all line
citations verified. Inferences are marked **(استنتاج / inference)**.

---

## 1. The GL posting rail — `CreateJournalEntry`

The single sanctioned entry point for any module to post to the General Ledger.

- **File:** `Modules/Accounting/app/Actions/CreateJournalEntry.php`
- **Signature:** `execute(array $data, array $lines): JournalEntry` (line 27).
- **`$data` header keys** (lines 48-56, 169-180 in `PostLabInvoice` as the proven caller):
  - `company_id` (required, line 30), `date` (required, drives period resolution line 40),
    `entry_type` (string, e.g. `'lab_cogs'`), `reference`, `description`, `description_ar`,
    `source_type` + `source_id` (polymorphic back-link, see §3), `partner_id` (added by
    `2026_03_10_080728_add_partner_id_to_journal_entries_table.php`), `created_by`.
- **`$lines[]` per-line keys** (lines 94-108):
  - `account_id` (required), `debit`, `credit`, `currency_id` (nullable → base currency),
    `exchange_rate` (optional explicit override, lines 74-83), `cost_center_id` (nullable —
    **this is the costing dimension**, line 104), `description`, `description_ar`.
- **Built-in guardrails relevant to costing:**
  - **Header/control accounts rejected** (lines 38, 157-180): a WIP or variance posting must
    target a *detail* (`AccountType::Detail`) account, never a header. `assertNoHeaderAccountLines`
    throws `je_line_on_header_account` otherwise.
  - **Open-period requirement** (lines 40-44): `resolvePeriod()` must find an OPEN fiscal period
    for `date`+`entry_type`, else `no_open_period` is thrown. WIP settlement dated into a closed
    period will fail — month-end sequencing matters.
  - **Multi-currency** (lines 58-92): non-base `currency_id` lines are converted to
    `base_debit`/`base_credit` via `ExchangeRateService`; missing rate throws. Manufacturing is
    almost always single-currency per company, but imported-material variance could trigger this.
  - **Auto-post** (lines 121-146): when `accounting.auto_post_entries` setting is unset/true the
    entry is auto-approved + posted (reports read POSTED only); any failure leaves a safe `Draft`.
    The business transaction never fails because of posting. **A Manufacturing settlement Action
    must not assume the JE is Posted** — it may be left Draft.

**Pattern to copy:** `Modules/LIS/app/Actions/PostLabInvoice.php` is the reference implementation.
Note `createCogsJournalEntry()` (lines 411-487): it **groups cost lines by `cost_center_id`** (from
`investigation.section.cost_center_id`) and stamps each line — exactly the mechanic Manufacturing
needs to split WIP/variance by production cost center.

---

## 2. Chart of Accounts structure

- **Table:** `accounts` — `Modules/Accounting/database/migrations/2026_02_10_100003_create_accounts_table.php`.
- **Columns:** `company_id`, `code`, `name`/`name_ar`, `parent_id` (self-referencing hierarchy),
  `classification` (line 18), `nature` (line 19), `account_type` (line 20, `header|detail`),
  `level`, `status`, `is_system`, `has_children`. Unique on `(company_id, code)`.
- **`AccountClassification` enum** (`Modules/Accounting/app/Enums/AccountClassification.php`):
  `assets | liabilities | equity | revenue | expenses` (lines 7-11). `nature()` (lines 24-30) maps
  Assets/Expenses → Debit, the rest → Credit.
- **`AccountType` enum** (`AccountType.php`): only `Header` and `Detail` (lines 7-8).
- **WIP-capable account config:** there is **no dedicated "WIP" account type or flag**. WIP is
  simply a **detail asset account** (`classification = assets`, debit nature) the build creates
  under a current-assets header. No schema change is needed to hold WIP — it is configuration/seed
  data, resolved at post time via a settings key (see §6). **(استنتاج)**
- **Programmatic account creation:** `Modules/Accounting/app/Services/AutoAccountService.php`
  `createChildAccount()` (lines 16-67) creates a detail child under a parent code, auto-promoting a
  childless detail parent to header (refuses if the parent already has journal lines, lines 37-48).
  Usable to seed `Manufacturing-WIP`, `Material-Usage-Variance`, `Labor-Variance`, `MOH-Applied`,
  `MOH-Variance` accounts at module install. **(استنتاج)**

---

## 3. `source_type` / `source_id` polymorphic back-link

- **Migration:** `Modules/Accounting/database/migrations/2026_02_23_080008_add_source_fields_to_journal_entries.php`
  — adds `source_type` (string 50) + `source_id` (unsignedBigInteger), indexed together (lines 12-15).
- **Convention** (from `PostLabInvoice`): `source_type` is a stable string tag for the originating
  document (`'lab_invoice'`), `source_id` = that document's PK. This is how a posted JE is traced
  back to and reconciled with its business document.
- **For Manufacturing:** every production JE should set `source_type = 'production_order'`,
  `source_id = production_order.id` (and a distinct `entry_type` per cost event:
  `production_material_issue`, `production_labor`, `production_overhead`, `production_receipt`,
  `production_variance`). **(استنتاج)**

---

## 4. COST CENTERS — they exist (single dimension only)

**Cost centers DO exist** as a first-class Accounting entity. They are the only analytical
dimension on a journal line today.

- **Table:** `cost_centers` — `Modules/Accounting/database/migrations/2026_02_10_100006_create_cost_centers_table.php`.
  Columns: `company_id`, `code`, `name`/`name_ar`, `parent_id` (hierarchy), `level`, `status`,
  `has_children`, descriptions. Unique `(company_id, code)`.
- **Model:** `Modules/Accounting/app/Models/CostCenter.php` — `parent()`/`children()`/
  `childrenRecursive()` (lines 45-58), `scopeRoots`, `scopeActive`. Extends `BaseModel` (tenant +
  audit). **Single hierarchy; there is NO `type` column** (the spec's `Production|Service|Auxiliary`
  classification at `04_costing.md` §4.3 does not exist — gap, see §9).
- **Dimension on journal lines:** `journal_entry_lines.cost_center_id` (nullable) —
  `2026_02_10_100008_create_journal_entry_lines_table.php` line 22, indexed line 31. Stamped by
  `CreateJournalEntry` line 104.
- **GL reporting honours it:** `Modules/Accounting/app/Services/GeneralLedgerService.php` filters
  opening balance and movement by `cost_center_id` (lines 37, 60) — per-cost-center ledger/Trial
  Balance is already supported.
- **Service-to-production allocation already exists:**
  `Modules/Accounting/app/Services/CostAllocationService.php` +
  `AllocationRule`/`AllocationRuleLine` models +
  `2026_02_16_900001_create_allocation_rules_table.php` (`source_cost_center_id`,
  `source_account_id`, `method`) and `..._900002_create_allocation_rule_lines_table.php`
  (`target_cost_center_id`, percentage). `CostAllocationService::execute()` (lines 47-100) posts a
  redistribution JE from a source cost center to target cost centers by percentage, and
  `simulate()` previews it. **This directly covers the spec's "service CC → production CC"
  allocation (Direct method).** Step-Down / Reciprocal are not modeled (`method` string exists but
  only proportional/percentage is implemented). **(استنتاج on method coverage)**

**Conclusion:** Cost centers and single-dimension line tagging are **NOT a net-new build** — they
exist and are report-aware. What is missing is the cost-center *type* classification and a
multi-dimension story (see §9).

---

## 5. Period close & fiscal calendar

- **Tables:** `fiscal_years` (`..._100004`), `fiscal_periods` (`..._100005`).
- **`CloseFiscalPeriod`** action (`Modules/Accounting/app/Actions/CloseFiscalPeriod.php`):
  refuses to close a period with unposted entries (`hasUnpostedEntries`, lines 21-23), sets
  `PeriodStatus::Closed`, fires `PeriodClosed` event (line 27). Also `SoftCloseFiscalPeriod`,
  `ReopenFiscalPeriod`, `CloseFiscalYear`/`ReopenFiscalYear`, `ExecuteYearEndClosing`.
- **Costing impact:** month-end overhead absorption (applied vs actual) and variance settlement
  JEs must post **before** the period is hard-closed; once closed, `CreateJournalEntry` throws
  `no_open_period`. The `PeriodClosed` event is a hook a Manufacturing month-end overhead-variance
  job could subscribe to (or, more safely, the variance run must precede close). **(استنتاج)**

---

## 6. Currency & account-resolution settings pattern

- **Base currency:** `currencies.is_base` (resolved in `CreateJournalEntry` lines 58-61).
- **Account mapping is settings-driven, not hardcoded.** LIS resolves every posting account via
  `SettingsService::get('lis.<role>_account_id', $companyId)` with fallback chains (e.g.
  `PostLabInvoice` lines 101-103, 413-414). Manufacturing must define its own keys, e.g.
  `manufacturing.wip_account_id`, `manufacturing.raw_material_account_id`,
  `manufacturing.labor_applied_account_id`, `manufacturing.moh_applied_account_id`,
  `manufacturing.material_usage_variance_account_id`, `manufacturing.fg_inventory_account_id`,
  seeded through a `SettingDefinition` seeder. **(استنتاج)**

---

## 7. Core rails the Manufacturing module inherits

- **`BaseModel` / `TenantAware` / `Auditable`** — `app/Models/BaseModel.php`, `app/Traits/`. Auto
  `company_id` stamping, soft deletes, dirty-only audit. All Production models already extend
  `BaseModel` (e.g. `CostCenter.php` line 12).
- **`SequenceService`** — `Modules/Core/app/Services/SequenceService.php`:
  `generateNext(int $companyId, string $module, string $entity, ?int $branchId = null): string`
  (line 15). Production order numbers / WIP-batch numbers should be issued here, not hand-rolled.
- **`SettingsService`** — `get(string $key, int $companyId, ?int $branchId = null, ?int $userId = null): mixed`
  (line 18). Scoped via `SettingScope` (`global|company|branch|user`); definitions seeded per
  module. Holds the GL-account mapping keys from §6.
- **`PermissionDependencyRegistry`** — `Modules/Core/app/Support/PermissionDependencyRegistry.php`.
  Manufacturing registers a `manufacturing` prefix contributor at boot (mirroring
  `LISServiceProvider`'s `register('lis', ...)`), exposing `expand()`/`mapFor()`/`presets()`/
  `groupOrder()` for production + costing permissions and one-click role bundles. **(استنتاج)**
- **`DataScope`** — `Modules/Core/app/Support/DataScope.php`. Branch-level `all|branch|own`
  visibility for production-order list endpoints; `operatingBranchId()` stamps the creating branch.
  Production tables already carry `branch_id` (production_orders migration line 133).
- **Approval workflows** — `ApprovalWorkflowService` + `ApprovalModule` enum
  (`Modules/Core/app/Enums/ApprovalModule.php`). **Enum currently only `Sales` and `Purchases`
  (lines 7-8)** — must be extended with a `Production`/`Manufacturing` case (+ `label()` arm and a
  translation key) to route production-order or BOM-change approvals. **GAP.**
- **Attachments** — `Modules\Core\Models\Attachment` (polymorphic `attachments` table) + root
  `HasAttachments` trait. Attach drawings/spec sheets to BOMs/orders for free.
- **Audit** — inherited via `Auditable` on `BaseModel`; role/permission changes logged via Core
  `EventServiceProvider`.

---

## 8. Product model — the single item master (no manufacturing fields)

- **`Modules\Core\Models\Product`** (table `products`,
  `2026_02_16_300001_create_products_table.php`) is **the single item master** consumed by
  Inventory, Sales, Purchases, POS, WebStore and Production (BOM `product_id` FKs reference
  `products`, see Production migration lines 65, 99, 163).
- **Manufacturing-relevant fields present:** `type` (line 18, default `'product'`),
  `cost_method` (line 30, **nullable string — currently unconstrained**), `purchase_price`,
  `sale_price`, `min_sale_price`, `track_inventory`, stock levels, `base_unit_id`.
- **Manufacturing-relevant fields MISSING vs spec `04_costing.md` / `01_master_data.md`:**
  - No `costing_method` enum (`Standard|Actual|Average|FIFO`) — only a free `cost_method` string.
  - No standard-cost build-up columns (`std_material_cost`, `std_labor_cost`,
    `std_overhead_cost`, `std_total_cost`, `last_updated`) on Product/variant. Production BOM has a
    single `standard_cost`/`overhead_cost` (BOM migration lines 55-56) but no decomposed standard.
  - No "is manufactured / is purchased / is phantom" procurement-type flag.
  These are **net-new columns** the Manufacturing build must add (extension table or product
  columns). **GAP / (استنتاج).**

---

## 9. How WIP → Variance → Settlement would post (the key output)

Using `CreateJournalEntry` + `cost_center_id`, with **standard costing** for the finished product.
Each event is one Action calling `CreateJournalEntry->execute([...], $lines)` with
`source_type='production_order'`, `source_id=order.id`, `cost_center_id` = the operation's
production cost center.

```
EVENT 1 — Material issue to order (consumption)
  DR  Manufacturing-WIP            (asset, detail)      [cc = production CC]
      CR  Raw-Material Inventory   (asset, detail)
  entry_type = production_material_issue
  amount = actual qty issued × material cost (per Product.cost_method)

EVENT 2 — Direct labor applied (at confirmation / time booking)
  DR  Manufacturing-WIP            [cc]
      CR  Labor Applied            (clearing/contra)
  entry_type = production_labor
  amount = labor_hours × std_rate  (or qty × piece_rate)

EVENT 3 — Manufacturing overhead applied (at confirmation)
  DR  Manufacturing-WIP            [cc]
      CR  MOH Applied              (clearing/contra)
  entry_type = production_overhead
  amount = cost_driver × overhead_rate  (ProductionCenter.overhead_rate exists, migration line 23)

EVENT 4 — Finished-goods receipt (order completion) at STANDARD cost
  DR  Finished-Goods Inventory     (asset, detail)
      CR  Manufacturing-WIP        [cc]
  entry_type = production_receipt
  amount = produced_qty × std_total_cost

EVENT 5 — Order settlement / variance (close the order)
  Residual WIP balance (actual accumulated DR minus standard CR) = total variance.
  Decompose per spec 04_costing.md §4.4 (MPV, MUV, LRV, LEV, VOSV, VOEV, FOVV):
  DR/CR  Variance accounts (P&L, by type → owner)
      CR/DR  Manufacturing-WIP     [cc]  (zero the order's WIP)
  entry_type = production_variance
  Each variance line tagged with its cost center; favourable = credit variance, adverse = debit.

MONTH-END — Overhead absorption (applied vs actual)
  Compare MOH Applied (EVENT 3 total) vs actual overhead in the cost-center expense accounts.
  Over/under-applied → P&L (spec 04 §4.2). Can be driven by CostAllocationService for
  service-CC → production-CC distribution first, then absorption variance JE.
```

**Everything above posts through the existing `CreateJournalEntry` rail with NO Accounting schema
change** — WIP/variance/applied accounts are seeded detail accounts; the cost center is the
existing `cost_center_id` line dimension; service-to-production allocation reuses
`CostAllocationService`/`AllocationRule`. The net-new work is entirely in a **Manufacturing module**
(Actions that assemble the lines, settings keys for account mapping, a costing engine that computes
the 7 variances) — not in Accounting/Core. **(استنتاج on the posting sequence; the rails are
verified.)**

---

## 10. Gaps (net-new build required)

| # | Gap | Where it lives | Net-new? |
|---|---|---|---|
| 1 | No GL posting from Production at all | `Modules/Production` has zero `CreateJournalEntry` calls; Inventory services also post nothing to GL | **Yes — biggest gap.** Costing Actions + inventory-movement→GL bridge must be built |
| 2 | Cost-center `type` (`Production/Service/Auxiliary`) | `cost_centers` table has no `type` column | Yes — add column/enum |
| 3 | Step-Down / Reciprocal allocation | `AllocationRule.method` string exists; only proportional implemented | Yes (or accept Direct-only v1) |
| 4 | `Product.costing_method` enum + decomposed standard cost | `products.cost_method` is a free nullable string; no std_material/labor/overhead | Yes |
| 5 | `ApprovalModule::Production` | `Modules/Core/app/Enums/ApprovalModule.php` only Sales/Purchases | Yes — extend enum |
| 6 | Manufacturing settings keys + `SettingDefinition` seeder for WIP/variance/applied accounts | none exist | Yes |
| 7 | Manufacturing GL account seed (WIP, MOH Applied, variance accounts) | not in any CoA seeder | Yes (via `AutoAccountService`) |
| 8 | Variance engine (7 variances, tolerance, Pareto, owner routing) | none | Yes — pure Manufacturing logic |
| 9 | Multi-dimension analytics beyond single `cost_center_id` | line carries one cost center only | Only if spec needs >1 dimension; single CC covers v1 |

**Bottom line for the board:** the Accounting & Core financial rails (CreateJournalEntry,
source_type/source_id, chart of accounts, **cost centers + line-level cost_center_id**, allocation
rules, period close, currency, sequences, settings, RBAC, audit) are **already in place and proven
by LIS COGS posting**. Cost centers and the costing dimension are **NOT a net-new build**. The
Manufacturing module must build the *costing logic and the Actions that drive these rails* (WIP
accumulation, the 7-variance engine, settlement), seed its accounts/settings, extend the
`ApprovalModule` enum, and add costing-method/standard-cost fields to the item master.
