# 20 — THE Definitive Build Mapping: Manufacturing Spec → Moon ERP

> Execution-grade mapping. Every entity in the spec's consolidated data model (spec file 06 §6.2,
> cross-checked against 00–05) gets one of three verdicts:
> **NEW** (new `mfg_*` table inside the module) · **REUSE** (existing Moon ERP model, named) ·
> **EXTEND** (existing `Modules/Production` table gains columns).
> Sources: `md/00..06` spec digests + `md/10..13` ERP maps.

---

## 1. The Module Verdict: EXTEND `Modules/Production` in place — do NOT create `Modules/Manufacturing`

**Decision: one module, `Modules/Production` (alias `production`), extended in place.**

Rationale (from `md/10-production-now.md`):

1. **Near-zero migration risk**: zero production data (empty seeder), zero inbound FKs from any
   other module (grep across all modules → 0 hits), zero tests to break.
2. **Scaffolding already paid for**: 19 permissions registered in
   `Modules/Core/database/seeders/RolePermissionSeeder.php` L709-727 and enforced as middleware;
   full Angular FE (`features/production/{centers,boms,orders,reports}`) with services, models,
   routes (`app.routes.ts` L227-233) and nav (`nav-items.config.ts` L316-327) at 1:1 API parity;
   `SequenceService->generateNext(companyId,'production','order')` already wired.
3. **The 7 existing tables ARE spec entities** (Work_Center, BOM_Header, BOM_Line,
   Production_Order_Header, Order_Component, Order_Operation map directly); discarding them buys
   nothing and forfeits the FE + permission map.
4. A parallel `Modules/Manufacturing` would duplicate the `production.*` permission namespace,
   the sequence keys, and the FE feature folder — pure waste plus a deprecation project.

**Naming convention**: the 7 legacy tables keep their names (renames are needless churn);
**all NEW tables take the `mfg_` prefix** per the clinical-module recipe (prefixed tables).
Permission prefix stays **`production.*`**; register a `production` contributor in Core
`PermissionDependencyRegistry` (mirror how LIS registers `lis`).

### Migration path for the 7 existing tables

| Existing table | Path |
|---|---|
| `production_centers` | **EXTEND** → becomes spec `Work_Center` (add columns, §2 row 6) |
| `bill_of_materials` | **EXTEND** → becomes spec `BOM_Header` (versioning + lifecycle) |
| `bom_components` | **EXTEND** → becomes spec `BOM_Line` (issue_type/level, operation_seq) |
| `bom_operations` | **DEPRECATE & MIGRATE** → routing becomes a standalone versioned entity. Backfill: for each BOM with rows here, generate one `mfg_routing_headers` (version 1.0, status Active) for the BOM's product and copy rows into `mfg_routing_operations` (`sequence`→`operation_no`×10, `production_center_id`→`work_center_id`, times copied, `cavity_count`=1). Then freeze table read-only; drop after one release cycle. |
| `production_orders` | **EXTEND** → becomes spec `Production_Order_Header` (state machine widened, snapshot refs, toll fields) |
| `production_order_materials` | **EXTEND** → becomes spec `Order_Component` (it already IS the frozen component snapshot store) |
| `production_order_operations` | **EXTEND** → becomes spec `Order_Operation` (operation_status, confirmed_by/qty, tool_id) |

Also: consolidate the three defensive migrations (000001/000002/000003 with try/catch FKs) —
new migrations are written clean, and a one-time `2026_xx_fix_production_fks` migration enforces
the FKs that the force-create variant skipped.

---

## 2. Entity-by-Entity Decision Table (all 27 spec entities)

Legend: **NEW** = new `mfg_*` table in Modules/Production · **REUSE** = existing Moon model ·
**EXTEND** = add columns to existing Production table.

### 2.1 Master Data (spec file 01)

| # | Spec entity | Verdict | Moon table/model | Notes |
|---|---|---|---|---|
| 1 | **Item** | **REUSE** | `Modules\Core\Models\Product` / `ProductVariant`; UoM via Core `unit_groups`/`units`/`product_units` (conversion_factor) | Spec demands `procurement_type`, `material_ownership`, `costing_method`, `lead_time_days`, `safety_stock` on Item — do NOT bloat `products`; carry them on `mfg_item_mrp_settings` (row 10, extension-table pattern like `AccBpExt`). Core `products` already has `min/max_stock_level`, `reorder_point`, free-text `cost_method` (ignored by StockService — superseded by the extension). UoM conversion gap in the spec is SOLVED by Core `product_units`. |
| 2 | **BOM_Header** | **EXTEND** | `bill_of_materials` | Add: `bom_type` enum {Production, Engineering, Phantom}, `status` (finally cast the declared-but-unused `BomStatus` enum, widened to {Draft, Active, Inactive, Obsolete}; retire reliance on `is_active`), `effective_from/to`, `approved_by`. Existing `version`, `quantity`(=base_quantity), `unit_id`, `product_id`(=parent_item) reused. Rule: one Active version per (product, effectivity window). |
| 3 | **BOM_Line** | **EXTEND** | `bom_components` | Add: `line_no`, `issue_type` enum {Manual, Backflush, AutoIssue}, `issue_level` enum {PerOrder, PerShift, PerOperation}, `operation_seq` FK→`mfg_routing_operations`. `waste_percentage` reused as scrap%. Substitutes → child table **NEW `mfg_bom_substitutes`** (`bom_component_id`, `substitute_product_id`, `priority`, `ratio`) — spec left structure open; ordered list chosen. Multi-level: component_id whose product has its own Active BOM ⇒ recursive explosion (service-level, no schema change). |
| 4 | **Routing_Header** | **NEW** | `mfg_routing_headers` | `product_id` FK→products, `routing_type` enum {Production, Repair, Inspection}, `version`, `lot_size_from/to`, `status` enum {Draft, Active, Inactive}, `effective_from/to`, `total_lead_time` (computed). Replaces BOM-attached `bom_operations` (see migration path §1). |
| 5 | **Routing_Operation** | **NEW** | `mfg_routing_operations` | `routing_id`, `operation_no` (multiples of 10), `work_center_id` FK→`production_centers`, `tool_id` FK→`mfg_tools` nullable, `setup_time`, `run_time_per_unit`, `cavity_count` default 1, `cycle_time`, `queue_time`, `move_time` (lead-time only, not costed), `inspection_required`, `critical_operation`, `required_skill_code`. time_per_piece = cycle_time / cavity_count. |
| 6 | **Work_Center** | **EXTEND** | `production_centers` | Add: `cost_center_id` FK→Accounting `cost_centers` (the interface to GL — currently missing; `account_id` exists but dead), `capacity_unit` enum {Hours, Pieces, Kg}, `daily_capacity`, `capacity_multiplier` default 1, `efficiency_percent`, `utilization_percent`, `calendar_id` FK→`mfg_work_calendars`, `setup_cost_rate`, `labor_cost_rate`, `machine_cost_rate` (`cost_per_hour` retained as machine rate seed), `labor_calc` enum {Hourly, PieceRate}, **`cost_driver` enum {LaborHours, MachineHours, DirectMaterialCost, UnitsProduced}** (the overhead-rate denominator per spec 04 §4.2 — lives HERE, per work center), `budgeted_monthly_volume` (FOVV volume basis v1 — see row 25), `bottleneck_flag`, `plant_location`. `overhead_rate` already exists. Effective capacity = daily × multiplier × eff% × util%. **This EXTEND is a scheduled work item: rate/capacity/driver columns land in roadmap Phase 2, `calendar_id` in Phase 4** (Phase 1 adds only `cost_center_id`). Existing `ProductionCenterType` {machine, labor, mixed} ≙ spec `category`. |
| 7 | **Tool / Mold** | **NEW** | `mfg_tools` (+ links) | `code` (SequenceService), `product_id` nullable, `cavity_count`, `setup_time`, `status` enum {Available, Mounted, Maintenance, Retired}, `life_cycles`, `cycles_used`, `purchase_cost`, **`cmms_asset_id` FK→`cmms_assets`** (PM via `cmms_pm_schedules.meter_field='cycles_used'` + `meter_threshold`; CMMS lacks a meter-reading table — Mfg pushes `cycles_used` into it, gap flagged), **`fixed_asset_id` FK→Accounting `fixed_assets`** — register VERIFIED in Modules/Accounting (`FixedAsset` model + `AssetCategory` + `DepreciationEntry` + `FixedAssetService` + Sell/Dispose Actions); usage-based depreciation (depreciation_per_piece = purchase_cost / life_cycles / cavity_count) still needs a net-new `DepreciationMethod::UnitsOfProduction` case + meter-driven `DepreciationEntry` generation (today only StraightLine/DecliningBalance/Accelerated — gap A26, Phase 2 prerequisite). Tool is a first-class Resource (`resource_type=Tool`) for CRP intersection. |

### 2.2 Planning (spec file 02)

| # | Spec entity | Verdict | Moon table/model | Notes |
|---|---|---|---|---|
| 8 | **MPS_Header** | **NEW** | `mfg_mps_headers` | `plan_name`, `branch_id` (≙ plant_id, via Core DataScope), `time_bucket` enum {Day, Week, Month}, `start_date`, `end_date`, `frozen_fence`, `slushy_fence`, `status`. |
| 9 | **MPS_Line** | **NEW** | `mfg_mps_lines` | `mps_id`, `product_id`, `period_date`, `forecast_qty`, `confirmed_orders_qty` (← Modules\Sales `sales_orders` confirmed, via Action), `planned_production`, `projected_balance` (PAB formula), `safety_stock_target`, `available_to_promise`. Forecast source is a spec gap — v1: manual/import entry on MPS lines. |
| 10 | **Item_MRP_Settings** | **NEW** | `mfg_item_mrp_settings` | 1:1 extension over Core `products` (extension-table pattern): `mrp_type` {Planned, ReorderPoint, NoPlanning}, `procurement_type` {Buy, Make}, `material_ownership` {Own, Customer}, `costing_method` {Standard, Actual, Average, FIFO}, `lot_sizing_rule` {Exact, Fixed, MinMax, EOQ, PeriodOrder}, `lot_size`, `min_lot`, `max_lot`, `safety_stock`, `lead_time_days`, `reorder_point`. This table is the single source for the spec's Item-level mfg fields (row 1). |
| 11 | **MRP_Planned_Order** | **NEW** | `mfg_mrp_runs` + `mfg_mrp_planned_orders` + `mfg_mrp_exceptions` | Transient, regenerated per run (run_id scoped). Firming: planned production order → `production_orders` (status Planned); planned purchase order → **REUSE Modules\Purchases `purchase_requests`** via a new extracted `Purchases\Actions\CreatePurchaseRequest` (PR→PO already exists: `convertFromRequest`). `mfg_mrp_planned_orders` carries `source_demand_id` (+ `source_demand_type`) — spec §6.2; pegging regeneration depends on it. Exceptions: {Late, RescheduleIn, RescheduleOut, Cancel}. Netting inputs REUSE: `inventory_stock_balances` (on_hand/reserved), open `purchase_orders`, open `production_orders`. |
| 12 | **CRP_Load** | **NEW** | `mfg_crp_loads` | `resource_id` + `resource_type` enum {Machine, Tool, Labor} (polymorphic over `production_centers` / `mfg_tools` / labor pools), `period`, `required_load`, `available_capacity` (from calendar × effective capacity), `utilization_pct`, `status` {Overload >100, OK, Underload <70}. earliest_start = MAX(free across required resources). Needs **NEW `mfg_work_calendars` + `mfg_calendar_shifts`** (spec gap: calendar undefined; HRM `shifts` reused as shift definitions where present). |

### 2.3 Execution (spec file 03)

| # | Spec entity | Verdict | Moon table/model | Notes |
|---|---|---|---|---|
| 13 | **Production_Order_Header** | **EXTEND** | `production_orders` | Add: `order_type` enum {Standard, Rework, Repair}, `production_type` enum {MakeToStock, MakeToOrder, AssembleToOrder, TollManufacturing}, `material_ownership` enum {Own, Customer}, `bom_version`/`routing_id`+`routing_version` snapshot stamps, `tool_id`, `wip_account_id`, `parent_order_id` (order network), `priority` exists. **State machine widened**: `ProductionOrderStatus` gains `released` and `closed` → Planned(=draft) → Released → InProcess → Completed → Closed, Cancelled from Planned/Released only; `confirmed` mapped → `released` in a data migration. Release side-effects (Action `ReleaseProductionOrder`): freeze snapshot into order tables, reserve stock + tool. The frozen snapshot LIVES in rows 14–15 (order_materials/operations are the snapshot store), satisfying spec `bom_snapshot_id`/`routing_snapshot_id` intent. `source/target_warehouse_id` columns finally go live. |
| 14 | **Order_Component** | **EXTEND** | `production_order_materials` | Add: `reserved_quantity`, `operation_seq`, `material_ownership`, `scrap_percentage` (frozen from BOM line), `issue_type`/`issue_level` (frozen). `planned_quantity` ≙ required_quantity (= bom_qty × order_qty × (1+scrap%)); `consumed_quantity` ≙ issued_quantity. `populateFromBom()` evolves into the Release-time snapshot writer. |
| 15 | **Order_Operation** | **EXTEND** | `production_order_operations` | Add: `operation_no`, `tool_id`, `status` recast to **NEW `OperationStatus` enum {Waiting, Ready, InProgress, Completed}** (replaces free-string `pending`), `confirmed_by`, `confirmed_quantity`, `scrap_quantity`, `planned/actual` times exist, `started_at/completed_at` ≙ actual_start/end. This is the SFC live surface (spec 05): on Done → event `OperationCompleted` → listener sets next op Ready (`routing.get_next`) or order Completed. |
| 16 | **Material_Issue_Header** | **NEW** (doc) + **REUSE** (stock rail) | `mfg_material_issues` → delegates to `Modules\Inventory\Models\InventoryIssue` + `Actions\ApproveIssue` | Mfg document carries spec fields Inventory lacks: `issue_type`, `issue_level`, `order_no`, `operation_no`, `warehouse_from`, status {Draft, Posted, Reversed}. On Post: create draft `InventoryIssue` (`reference_type='production_order'`) → `ApproveIssue` (costs via `StockService::getIssueCost` FIFO/WAC, decrements stock) → GL **DR WIP / CR Raw Materials** via `Modules\Accounting\Actions\CreateJournalEntry` (Inventory posts no GL — the consuming module owns it, same as Sales COGS pattern `PostSalesInvoice`). Reservation consumed here (Inventory `reserved_quantity` column exists, **reserve/release API is net-new** — `StockService::reserve()/release()`). Backflush issues fire from Confirmation. Add Inventory `MovementType` values `production_issue`/`production_receipt`. |
| 17 | **Material_Issue_Line** | **NEW** | `mfg_material_issue_lines` | `product_id`, `quantity`, `unit_id`, `batch_no`, `cost_price`, `total_cost`, `bin_location`, `ownership` {Own, Customer}. Toll branch: `ownership=Customer` ⇒ movement from consignment bucket, NO RM credit at own cost (tracking-only) — consignment bucket = dedicated `warehouses` rows flagged `is_consignment` (EXTEND Inventory `warehouses`; never valued, never purchasable). Batch is free-text in Inventory today (no lot master/genealogy — flagged gap, v1 carries the string). |
| 18 | **Confirmation_Header** | **NEW** | `mfg_confirmations` | `confirmation_no` (SequenceService), `production_order_id`, `operation_no`, `confirmation_type` {Partial, Final, Reversal}, `operator_id` (→ `users.id`; costing chain via HRM `employees.user_id` unique), `work_center_id`, `shift`, `timestamp`. Posts: **DR WIP / CR Labor Applied + Machine Applied + MOH Applied** via `CreateJournalEntry`; labor branches on `labor_calc` (Hourly: hours×rate from work center `labor_cost_rate` v1 — HRM stores no hourly/piece rate, gap; PieceRate: qty×piece_rate, fed to payroll via NET-NEW `Modules\HRM\Actions\RecordPieceworkEntry` writing HRM-owned `hrm_piecework_entries` — payroll reads its OWN table, never an `mfg_*` one). Scrap → **DR Scrap Expense / CR WIP** + optional QMS NCR (`qms_non_conformances`) by reason_code threshold. |
| 19 | **Confirmation_Yield** | **NEW** | `mfg_confirmation_yields` | `confirmation_id`, `grade_code`, `quantity`, `unit_sale_price`. Yield is a distribution: Σ(grades)+scrap = input; NRV joint-cost allocation grade.cost = total × (qty×price)/total_sale_value; collapses to single-grade default (genericity mandate). |
| 20 | **Confirmation_Detail** | **NEW** | `mfg_confirmation_details` | `scrap_quantity`, `rework_quantity`, `setup_time_actual`, `run_time_actual`, `labor_hours`, `machine_hours`, `reason_code`. Feeds variance engine + future OEE. |
| 21 | **Goods_Receipt_Header** | **NEW** (doc) + **REUSE** (stock rail) | `mfg_goods_receipts` → delegates to `Modules\Inventory\Models\InventoryReceipt` + `Actions\ApproveReceipt` | Canonical pattern = `PurchaseGrnController::approve` (L335-433): create draft `InventoryReceipt` with rolled-up WIP unit_cost → `ApproveReceipt` writes stock + FIFO cost layer. GL: **DR FG (std×qty) + DR/CR Variance / CR WIP (actual)** via `CreateJournalEntry`. Fields: `receipt_type` {Full, Partial}, `warehouse_to`, status {Draft, Posted, Reversed}. Full receipt ⇒ order Completed → Closed (variance decomposition). Toll: output to customer ownership; revenue = conversion fee (DR Customer / CR Toll-service revenue). |
| 22 | **Goods_Receipt_Line** | **NEW** | `mfg_goods_receipt_lines` | One line per grade: `product_id`, `received_quantity`, `grade_code`, `batch_no`, `bin_location`, `cost_per_unit`, `quality_status` {Released, OnHold, Rejected} — resolved from **REUSE QMS** `qms_inspections.result` (`qms_inspection_plans`/`qms_inspections` already carry `production_order_id`; NCR/CAPA link only via `inspection_id`; add the FK constraint + an `operation_no` column to QMS once mfg tables exist — Manufacturing builds NO quality tables of its own). **Default until QMS gates land (Phase 6): `quality_status` = Released (auto-release, D-21).** |

### 2.4 Costing (spec file 04)

| # | Spec entity | Verdict | Moon table/model | Notes |
|---|---|---|---|---|
| 23 | **Standard_Cost** | **NEW** | `mfg_standard_costs` | Per product (+version/effective date): `std_material_cost`, `std_labor_cost`, `std_overhead_cost`, `std_total_cost`, `last_updated`, `update_frequency`. Built by Action `ComputeStandardCost` (BOM roll-up × work-center rates). Per-item `costing_method` lives on `mfg_item_mrp_settings`; Average/FIFO valuation REUSES `inventory_cost_layers` + `StockService` (company setting `inventory.valuation_method`); Standard valuation layer is net-new. |
| 24 | **Cost_Center** | **REUSE + small EXTEND** | `Modules\Accounting\Models\CostCenter` (`cost_centers`, recursive parent/children; line dimension `journal_entry_lines.cost_center_id` already exists and `GeneralLedgerService` already filters by it) | **Accounting is system-of-record** (resolves the spec's ownership ambiguity). EXTEND in Accounting: add `type` enum {Production, Service, Auxiliary} + `manager_id` (both confirmed absent). **No `budget_amount` column needed: fixed-OH budget per cost center already EXISTS** — Accounting `budgets` + `budget_lines` (`cost_center_id` FK + monthly `m1..m12` amounts, verified in the backend) feed FOVV's budgeted spend; only budgeted production VOLUME is net-new (v1: `production_centers.budgeted_monthly_volume`, row 6 — gap A25). Service→production allocation REUSES `CostAllocationService` + `AllocationRule` (Direct method implemented; Step-Down/Reciprocal deferred — accept Direct-only v1). Every production JE line is stamped with the work center's `cost_center_id` (PostLabInvoice mechanic). |
| 25 | **Variance_Record** | **NEW** | `mfg_variance_records` | `production_order_id`, `variance_type` enum {MPV, MUV, LRV, LEV, VOSV, VOEV, FOVV}, `amount`, `percentage`, `classification` {Normal <2%, Monitor 2-5%, Investigate >5%}, `owner` (role-routed via permissions), `investigation_flag`. Computed by Action `CloseProductionOrder` + monthly period job hooked **before** `CloseFiscalPeriod` (CreateJournalEntry refuses closed periods). LRV/LEV n/a under PieceRate. **FOVV inputs**: budgeted fixed-OH spend REUSES Accounting `budget_lines` (per cost center, monthly); budgeted volume from `production_centers.budgeted_monthly_volume` (row 6); no approved budget for the period ⇒ FOVV auto-descoped, 6 variances declared (D-22). Posting accounts via `SettingsService` keys `production.*_account_id`, seeded with `AutoAccountService::createChildAccount` (WIP, Labor Applied, MOH Applied, FG, 7 variance accounts — none exist today). |

### 2.5 Cross-cutting + Shop Floor (spec files 05/06)

| # | Spec entity | Verdict | Moon table/model | Notes |
|---|---|---|---|---|
| 26 | **Pegging** | **NEW** | `mfg_peggings` | `production_order_id` FK→`production_orders`, `sales_order_id` FK→Sales `sales_orders` (+ `sales_order_item_id`), `allocated_quantity`. Sales side untouched (no FK from Sales). Allocation policy unspecified in spec — v1: FIFO by order date, re-peg on MRP regeneration. |
| 27 | **Delivery_Schedule** | **NEW** | `mfg_delivery_schedules` | `sales_order_id`, `quantity`, `scheduled_date`, `status`, `production_order_id`. Plus minimal **EXTEND Sales**: `sales_orders.promised_date` (CTP write-back — no ATP/CTP field exists today; `expected_delivery_date` stays the manual promise). |
| — | **SFC layer** | **REUSE** rows 15+18 | no new tables | Spec 05 explicitly reuses `Order_Operation` + Confirmation engine. Build = touch terminal UI + auto-advance event chain + supervisor dashboard. ~80% pre-exists once Execution lands. |

**Tally: 24 NEW `mfg_*` tables · 6 EXTEND (of 7 legacy; 1 deprecated) · REUSE of 10+ existing models**
(24 = bom_substitutes, routing_headers, routing_operations, tools, mps_headers, mps_lines, item_mrp_settings, mrp_runs, mrp_planned_orders, mrp_exceptions, crp_loads, work_calendars, calendar_shifts, material_issues, material_issue_lines, confirmations, confirmation_yields, confirmation_details, goods_receipts, goods_receipt_lines, standard_costs, variance_records, peggings, delivery_schedules. The HRM piecework staging table is HRM-owned, not `mfg_*`.)
(Core Product/Units, Inventory Issue/Receipt/StockBalance/CostLayer/StockService/Warehouse, Purchases PurchaseRequest/Order, Sales SalesOrder, Accounting CostCenter/JournalEntry/CostAllocationService, QMS Inspection/NCR/CAPA, CMMS Asset/PmSchedule/WorkOrder, HRM Employee/Shift/Attendance).

---

## 3. Module Skeleton (Modules/Production, extended)

```
Modules/Production/
├── app/
│   ├── Enums/                  # PHP backed enums, house recipe
│   │   ├── ProductionOrderStatus.php   (EXTENDED: planned,released,in_process,completed,closed,cancelled)
│   │   ├── OperationStatus.php         (waiting,ready,in_progress,completed)
│   │   ├── BomType.php  BomStatus.php  RoutingType.php  RoutingStatus.php  ToolStatus.php
│   │   ├── ProductionType.php  MaterialOwnership.php  ProcurementType.php  CostingMethod.php
│   │   ├── LaborCalc.php  IssueType.php  IssueLevel.php  ResourceType.php  LotSizingRule.php
│   │   ├── CapacityUnit.php  TimeBucket.php  MrpType.php  ConfirmationType.php  ReceiptType.php
│   │   ├── VarianceType.php  CostDriver.php  OverheadType.php  MrpExceptionType.php
│   ├── Models/                 # all extend app/Models/BaseModel (TenantAware company_id, Auditable)
│   ├── Actions/                # cross-module calls live HERE (recipe)
│   │   ├── ReleaseProductionOrder.php      # snapshot BOM+Routing → order tables; StockService::reserve(); tool reserve
│   │   ├── PostMaterialIssue.php           # → Inventory ApproveIssue → CreateJournalEntry (DR WIP / CR RM)
│   │   ├── PostConfirmation.php            # labor/machine/OH applied JEs; backflush; scrap JE; QMS NCR hook
│   │   ├── PostGoodsReceipt.php            # → Inventory ApproveReceipt → JE (DR FG + Var / CR WIP); toll branch
│   │   ├── CloseProductionOrder.php        # 7-variance decomposition → mfg_variance_records + variance JEs
│   │   ├── RunMrp.php  RunCrp.php  RunMps.php  ComputeStandardCost.php
│   │   ├── FirmPlannedOrders.php           # → production_orders / Purchases CreatePurchaseRequest
│   │   └── PromiseDate.php                 # CTP → sales_orders.promised_date write-back
│   ├── Events/   ProductionOrderReleased, MaterialIssued, OperationCompleted,
│   │             ConfirmationPosted, GoodsReceiptPosted, ProductionOrderClosed, MrpRunCompleted
│   ├── Listeners/ AdvanceNextOperation (SFC auto-advance), PostIssueJournal, PostConfirmationJournal,
│   │             PostReceiptJournal, NotifyWorkCenterTerminal (broadcast), CreateQmsNcrFromScrap
│   ├── Services/  BomExplosionService, MrpEngine, CrpEngine, CtpService, SnapshotService,
│   │             JointProductAllocator (NRV), VarianceEngine, ToolCycleService (→ CMMS meter push)
│   └── Http/Controllers/ (existing 4 + Routing, Tool, Mps, Mrp, Crp, Issue, Confirmation,
│                          GoodsReceipt, ShopFloor, Costing controllers)
├── database/migrations/        # clean new migrations; FK-fix migration for legacy trio
└── database/seeders/           # SettingDefinitions (production.*_account_id …), demo master data
```

**Cross-module one-liners (the contract):**
- GL: `app(\Modules\Accounting\Actions\CreateJournalEntry::class)->execute($data, $lines)` ONLY —
  `source_type='production_order'`, lines stamped `cost_center_id` from work center; entry_types:
  `production_material_issue|labor|overhead|receipt|variance|scrap`.
- Stock: draft `InventoryIssue`/`InventoryReceipt` + `ApproveIssue`/`ApproveReceipt`; never touch
  balances directly. NEW `StockService::reserve()/release()` contributed to Inventory.
- Procurement: extracted `Purchases\Actions\CreatePurchaseRequest` (net-new in Purchases).
- Numbering: `SequenceService::generateNext($companyId,'production', {order|issue|confirmation|receipt|bom|routing|tool|mps|mrp_run})`.
- Approvals: add `Production` case to Core `ApprovalModule` enum (today Sales|Purchases only).
- Scoping: DataScope on all list endpoints (`branch_id` present on orders/centers).

**State machines (events fire on every transition):**
- Order: Planned → Released → InProcess → Completed → Closed (+Cancelled from Planned/Released)
- Operation: Waiting → Ready → InProgress → Completed
- BOM: Draft → Active → Inactive → Obsolete · Routing: Draft → Active → Inactive
- Tool: Available → Mounted → Maintenance → Retired (only Mounted accrues cycles)
- Issue/Confirmation/GR: Draft → Posted → Reversed

---

## 4. Required touches OUTSIDE the module (small, enumerated)

| Module | Change | Size |
|---|---|---|
| Inventory | `StockService::reserve()/release()` (column `reserved_quantity` exists, unwritten); `MovementType` += `production_issue`,`production_receipt`; `ReceiptReferenceType`/`IssueReferenceType` += `ProductionOrder` case (both enum-typed today — Purchase/Return/… and Sale/Consumption/… only); `warehouses.is_consignment` flag | S |
| Accounting | `cost_centers.type` enum + `manager_id`; seed WIP/Applied/FG/variance accounts via `AutoAccountService`; `DepreciationMethod::UnitsOfProduction` case + meter-driven `DepreciationEntry` generation for tools (fixed-asset register itself EXISTS — verified); budget rail (`budgets`/`budget_lines`) reused as-is for FOVV | S–M |
| Purchases | extract `Actions\CreatePurchaseRequest` from controller | S |
| Sales | `sales_orders.promised_date` column (single canonical name — D-16) | XS |
| QMS | FK-constrain `production_order_id`; add `operation_no` to plans/inspections | S |
| CMMS | meter-reading sink for `cycles_used` (or accept Mfg-pushed `meter_field`); read-only `Actions\GetDowntimeWindows` query Action for CRP (no raw `cmms_work_orders` reads) | S |
| HRM | NET-NEW `Actions\RecordPieceworkEntry` + HRM-owned `hrm_piecework_entries` staging table (piece-rate payroll feed, Phase 6); (deferred) `employees.hourly_rate`/`piece_rate` — v1 uses work-center rates | S |
| Core | `ApprovalModule::Production`; permissions appended to `RolePermissionSeeder`; `PermissionDependencyRegistry` contributor `production` | S |

---

## 5. Angular FE feature layout

Extend `features/production/` (path `production`, existing `moduleGuard` + per-child
`permissionGuard` with `data.permissions ['production.*']`):

```
features/production/
├── centers/  boms/  orders/  reports/          # existing, upgraded
├── routings/        # versioned routing CRUD (operations grid, cavity/cycle times)
├── tools/           # tool/mold registry, cycle counters, CMMS link
├── planning/
│   ├── mps/         # plan grid: forecast/orders/PAB/ATP per bucket; time fences
│   ├── mrp/         # run + planned orders workbench + exceptions; firm → PO/PR
│   └── crp/         # load vs capacity heatmap, overload drill-down
├── execution/
│   ├── issues/  confirmations/  receipts/      # the 3 execution documents
├── shop-floor/      # touch terminal per work center — CLONE pos-layout pattern
│   │                # Ready queue, [Start]/[Done], qty+grade+scrap capture, offline-tolerant
│   └── dashboard/   # supervisor live board: order progress %, bottleneck (Waiting queues)
└── costing/         # standard costs, variance Pareto, tolerance-band routing
```

Services in `core/services/` (routing, tool, mps, mrp, shop-floor, mfg-costing), models in
`core/models/`; nav additions in `nav-items.config.ts`; reuse shared `data-table`, `form-dialog`,
`page-header`, `print.service`. English-first translate keys.

---

## 6. Build order (spec 06 §6.4, mapped)

1. Master data: EXTEND boms/components/centers + NEW routings/tools (+ calendar) →
2. `mfg_item_mrp_settings` + `mfg_standard_costs` →
3. MPS → 4. MRP (+ CreatePurchaseRequest) → 5. CRP →
6. Order release (snapshot + reserve + state machine) →
7. Material Issue → 8. Confirmation → 9. Goods Receipt (each with its JE family) →
10. Costing close + 7 variances → 11. SFC terminal/dashboard → 12. Integration hardening (QMS FK, CMMS meters, Sales promise write-back).

Each step ships with tests (80%+ per house rules), seeders, and its permission slice.
