# Manufacturing Module × Moon ERP — Complete Build Analysis (Companion MD)

> **Purpose:** Machine-consumable technical companion to `manufacturing-erp-analysis-report.html`.
> Produced 2026-06-11 by a 17-agent study (7 spec readers + 4 ERP code readers on Opus 4.8 →
> 4 decision analysts → adversarial review → fix round, all findings corrected in place).
> **Verdict:** EXTEND `Modules/Production` in place (its 7 tables ARE spec entities; zero data,
> zero inbound FKs, FE+permissions already paid for). New entities use `mfg_` prefix (24 tables),
> permission prefix stays `production.*`. Dependency rule: Manufacturing imports Core/Inventory/
> Purchases/Sales/Accounting/HRM/QMS/CMMS forward; NO module ever reads `mfg_*` tables.
> Stock via ApproveIssue/ApproveReceipt documents only; GL via CreateJournalEntry only.
> Spec source: /home/amrtechogate/public_html/files/manufacturing_spec/markdown (7 files).

## Index
| File | Content |
|---|---|
| 00-06 | Spec digests (lossless): architecture/principles, master data, planning MPS/MRP/CRP, execution, costing & 7 variances, shop floor, integrations & data model |
| 10 | Existing Modules/Production deep map + extend-vs-rebuild evidence |
| 11 | Inventory & Purchases contracts (StockService, ApproveIssue/Receipt, costing layers) |
| 12 | Accounting & Core rails (CreateJournalEntry, cost centers, fixed assets, sequences) |
| 13 | Sales/HRM/QMS/CMMS touchpoints + Angular FE conventions |
| 20 | THE build mapping: every spec entity → NEW mfg_* / REUSE / EXTEND (execution-grade) |
| 21 | Gap analysis both directions (ERP gaps + spec gaps), severity-ranked |
| 22 | Integration contracts: per-module matrix, named events, named Actions, posting map |
| 23 | Roadmap: phases 0-6 with usable increments, migration/seed/testing strategy, decisions table |
| 24 | Adversarial review findings (all fixed in place) |

---


# 00 — Manufacturing Spec: Architecture & Principles (Technical Digest)

> Lossless digest of `/home/amrtechogate/public_html/files/manufacturing_spec/markdown/00_architecture_and_principles.md`
> (cross-checked against `README.md`). This is the *governing* spec file: every other spec file (01–06)
> inherits these rules. The module must be **generic** (works for any manufacturer). The Melamine
> tableware factory is a stress-test example *only* — it is NOT to be hard-coded.

---

## 0.1 Purpose & Scope

Build a **generic Manufacturing module** that plugs into an existing ERP that already has
Finance/GL, Inventory, Procurement, Sales, HR.

Lifecycle covered:

```
Master Data → Planning → Execution → Costing
```

Plus a **Shop Floor Control (SFC)** layer (real-time operation tracking) that cross-cuts Execution,
and integration points to existing ERP modules.

**Out of scope** (already exist in host ERP — INTEGRATE, do not rebuild):
General Ledger, Inventory/Warehouse core, Procurement core, Sales orders, HR/payroll.

---

## 0.2 The Genericity Mandate (PRIME DIRECTIVE)

Single most important rule: **never hard-code one factory's behaviour.** Every factory-specific
behaviour must be expressed as **configuration (a field/enum) + branching logic**, NOT as a separate
code path or a per-factory branch.

**Rule of thumb for the AI builder:** when you encounter behaviour that "only this factory does," do
NOT branch the code by factory. Add a configuration field that **defaults to the common behaviour**,
and branch on that field.

The design was validated against a Melamine factory whose complexity exposed the required fields.
The full validation table (factory-specific reality → generic mechanism):

| Factory-specific reality (Melamine) | Generic mechanism in the system |
|---|---|
| Make-to-order, make-to-stock, toll manufacturing all happen | `production_type` enum + branching |
| Customer supplies raw material (toll) | `material_ownership` enum {Own, Customer} |
| One press cycle yields multiple pieces (multi-cavity mold) | `cavity_count` field on the tool/operation |
| One cycle produces grades A/B/C at different prices | Joint-products: yield is a **distribution**, not a scalar |
| Press operator paid per piece, others fixed salary | `labor_calc` enum {Hourly, PieceRate} |
| Worker draws powder for a whole shift | `issue_level` enum {PerOrder, PerShift, PerOperation} |
| Mold (not machine) is the capacity constraint | `Resource` abstraction with `resource_type` {Machine, Tool, Labor} |
| Glaze sprayed in tiny amounts | Material treated as **overhead consumable**, not a BOM line |
| Only mounted molds wear out | Depreciation by **usage (cycles)**, not by calendar time |

Key modelling consequences embedded above:
- **Yield is a distribution, not a scalar** — one production run can output multiple co-products /
  quality grades (A/B/C), each at a different price. Costing must allocate to joint products.
- **`cavity_count`** lives on the tool/operation: one machine cycle yields N units.
- **Overhead consumables** (e.g. glaze) are NOT BOM lines; they are absorbed as overhead.
- **Usage-based depreciation** (cycles), not calendar depreciation, for tools/molds.

---

## 0.3 The Four-Layer Architecture

```
┌──────────────────────────────────────────────────┐
│  SHOP FLOOR CONTROL (SFC) — real-time tracking     │  ← cross-cuts Execution
├──────────────────────────────────────────────────┤
│  ① MASTER DATA   — the foundation (definitions)    │
│  ② PLANNING      — the brain (what/how much/when)  │
│  ③ EXECUTION     — reality (actual events)         │
│  ④ COSTING       — money (translate to figures)    │
└──────────────────────────────────────────────────┘
        ↕ integrates with: Inventory, Procurement, Sales, GL, HR
```

Data flows **top-down** (Master Data → Planning → Execution → Costing). There is a **closed loop**:
Costing **variances** and CRP **capacity conflicts** feed back to correct Planning.

| Layer | Spec file | Core question it answers |
|---|---|---|
| Master Data | `01_master_data.md` | What is the product and how is it made? |
| Planning | `02_planning.md` | Can we make it? What materials? What schedule? |
| Execution | `03_execution.md` | What actually happened on the floor? |
| Costing | `04_costing.md` | What did it cost? Where are the problems? |
| Shop Floor Control | `05_shop_floor_control.md` | Where is each order right now (real-time)? |
| Integrations | `06_integrations_and_data_model.md` | How does it connect + the full data model |

---

## 0.4 Cross-Cutting Design Rules (apply to EVERY entity & process)

1. **Frozen Snapshots.** When a Production Order is **released**, it must snapshot the active BOM and
   Routing. Later changes to master data must NOT affect already-released orders. Protects historical
   cost and audit trail.

2. **Status as State Machine.** Every transactional entity (Order, Issue, Confirmation, GR) has an
   explicit status with defined transitions, preconditions, and side-effects. **No ad-hoc status
   changes.**

3. **Many-to-Many Demand↔Supply.** A sales order can split into many production orders (delivery
   batches); a production order can serve many sales orders (batching to reduce setup). A **Pegging**
   table records the links.

4. **Multi-level everything.** BOM, orders, and costing must support **arbitrary nesting** (finished
   good → sub-assembly → component → raw material). Use **recursion**.

5. **Configuration over code.** See §0.2. Every variant point is a field.

6. **Every actual event posts an accounting entry.** Material issue, confirmation, and goods receipt
   each generate GL postings. Costing is woven into execution — NOT a separate batch step bolted on
   later.

7. **Resource abstraction.** Capacity is consumed by `Resource` objects of different `resource_type`
   (Machine, Tool, Labor). The **effective capacity** of an operation is constrained by the
   **MINIMUM** available across all required resources.

---

## 0.5 Key Enumerations (used module-wide)

| Enum | Values |
|---|---|
| `production_type` | MakeToStock, MakeToOrder, AssembleToOrder, TollManufacturing |
| `material_ownership` | Own, Customer |
| `procurement_type` | Buy, Make |
| `costing_method` | Standard, Actual, Average, FIFO |
| `labor_calc` | Hourly, PieceRate |
| `issue_type` | Manual, Backflush, AutoIssue |
| `issue_level` | PerOrder, PerShift, PerOperation |
| `resource_type` | Machine, Tool, Labor |
| `lot_sizing_rule` | Exact, Fixed, MinMax, EOQ, PeriodOrder |
| `order_status` | Planned, Released, InProcess, Completed, Closed |
| `operation_status` | Waiting, Ready, InProgress, Completed |

State-machine note: `order_status` and `operation_status` are the two canonical state machines named
in this file; transitions/preconditions/side-effects are detailed in spec files 03 (execution) and 05
(SFC). Per §0.4 rule 2, *every* transactional entity also carries its own status machine.

---

## 0.6 How to Use These Specs (builder guidance)

1. Read file `00` fully first — it sets the rules.
2. Build in dependency order: Master Data → Planning → Execution → Costing → SFC.
3. Each spec file has: **Purpose**, **Entities & Fields**, **Logic/Algorithms (pseudo-code)**,
   **Generic-design notes**, **Melamine test case**.
4. Pseudo-code is **language-agnostic intent**, not literal code. Implement idiomatically in the host
   ERP's stack.
5. Honour the genericity mandate: Melamine test cases prove edge cases — they are NOT the spec to
   hard-code.
6. The full relational data model is consolidated in file `06`.

Recommended build order (from README):
```
1. Master Data (recursive BOM, Routing, WC, Tool) + CRUD
2. Item MRP Settings + Standard Cost
3. MPS → 4. MRP → 5. CRP
6. Production Order → 7. Material Issue → 8. Confirmation → 9. Goods Receipt
10. Costing (methods, elements, cost centers, variances)
11. SFC (real-time operation tracking, terminals, dashboard)
12. Integrations (Sales, Procurement, Inventory, GL, HR)
```

---

## 0.7 Build Status Reference (from the analysis)

| Component | Status | Notes for builder |
|---|---|---|
| Master Data (BOM, Routing, WC) | Fully specified | Add Tool/Mold as first-class resource |
| Planning (MPS, MRP, CRP) | Fully specified | Detailed APS scheduling is a known gap (see 02 §future) |
| Execution (PO, Issue, Confirm, GR) | Fully specified | Includes grades, piece-rate, shift-issue |
| Costing (Method, Elements, CC, Variance) | Fully specified | Joint-products allocation included |
| Shop Floor Control | Specified at logic level | UI/terminals layer to build (see 05) |
| Quality, Tool Mgmt, Maintenance, OEE | Identified, NOT specified | Future phases — interfaces noted |

Future phases (interfaces noted in `06` §6.5): APS detailed scheduling, Quality, Tool/Mold
management, Maintenance, OEE, PLM/ECO.

---

## Entities / Contracts this file DEFINES (for downstream mapping)

- **Four-layer architecture** (Master Data, Planning, Execution, Costing) + SFC cross-cutting layer.
- **`Resource`** abstraction with `resource_type` {Machine, Tool, Labor}; effective capacity =
  MIN across required resources.
- **Pegging** table — the many-to-many demand↔supply link (sales orders ↔ production orders).
- **Frozen Snapshot** contract on Production Order release (snapshot BOM + Routing).
- **Joint-products / yield-as-distribution** costing model (grades A/B/C, co-products, by-products).
- **11 master enums** (see §0.5) — the configuration vocabulary of the whole module.
- **GL-on-every-event** contract: material issue, confirmation, goods receipt each post to GL.
- Two named state machines: `order_status`, `operation_status` (plus per-entity status rule).

## What this spec EXPECTS FROM THE HOST ERP (integration contract)

- **General Ledger / Finance** — to receive postings on every issue/confirm/GR event.
- **Inventory / Warehouse core** — stock balances, material issues, goods receipts land here.
- **Procurement core** — `procurement_type = Buy` items and MRP purchase suggestions hand off here.
- **Sales orders** — demand source; pegging links manufacturing supply to sales demand.
- **HR / payroll** — labor (`labor_calc` Hourly/PieceRate) and operator pay integrate here.
- Implementation must be done **idiomatically in the host ERP's stack** (here: Laravel 12 module).
</content>
</invoke>

---


# 01 — Master Data Layer (Technical Digest)

> Source: `files/manufacturing_spec/markdown/01_master_data.md` (+ governing principles from `00_architecture_and_principles.md`, `README.md`). This digest is lossless: every entity, field, enum, rule and formula from the source file is reproduced here. An implementer must NOT need to reopen the original.

---

## 0. Governing principles inherited (from `00`)

This layer inherits all cross-cutting rules. The ones that bind Master Data directly:

1. **Genericity mandate (prime directive).** Never hard-code a single factory's behaviour. Every factory-specific behaviour = a configuration field/enum + branching logic that **defaults to the common behaviour**. The Melamine tableware factory is a *stress-test example only*, NOT something to hard-code.
2. **Frozen Snapshots.** When a Production Order is released, it snapshots the *active* BOM and Routing. Later master-data edits MUST NOT affect already-released orders (protects historical cost + audit). → Master Data is the *snapshot source*.
3. **Status as State Machine.** Every entity with a `status` has explicit states, transitions, preconditions, side-effects. No ad-hoc status changes.
4. **Multi-level everything.** BOM must support arbitrary nesting (finished good → sub-assembly → component → raw). Use recursion.
5. **Configuration over code.** Every variant point is a field.
6. **Resource abstraction.** Capacity is consumed by `Resource` objects with `resource_type ∈ {Machine, Tool, Labor}`. An operation's effective capacity is constrained by the **minimum** available across all required resources.

### Master enums relevant to this layer (defined in `00`)
```
production_type     ∈ { MakeToStock, MakeToOrder, AssembleToOrder, TollManufacturing }
material_ownership  ∈ { Own, Customer }
procurement_type    ∈ { Buy, Make }
costing_method      ∈ { Standard, Actual, Average, FIFO }
labor_calc          ∈ { Hourly, PieceRate }
issue_type          ∈ { Manual, Backflush, AutoIssue }
issue_level         ∈ { PerOrder, PerShift, PerOperation }
resource_type       ∈ { Machine, Tool, Labor }
```

---

## 1.1 Purpose of the Master Data layer

Master Data holds the **stable definitions** that all transactions reference:
- the product recipe — **BOM**
- the manufacturing path — **Routing**
- the production resources — **Work Center / Tool**
- item settings.

Created once; changed only via **controlled change orders (ECO)**. "If master data is wrong, everything downstream is wrong."

---

## 1.2 Entity: BOM (Bill of Materials)

**Purpose:** the product's manufacturing identity — what components and quantities go into one unit. Must support **multi-level nesting**.

### BOM_Header — fields
| Field | Notes / rule |
|---|---|
| `bom_id` | PK |
| `parent_item_id` | FK → Item |
| `bom_type` | enum `{ Production, Engineering, Phantom }` |
| `version` | multiple versions kept for history |
| `base_quantity` | usually 1 — the unit the BOM is defined per |
| `uom` | unit of measure of the parent |
| `status` | enum `{ Draft, Active, Inactive, Obsolete }` |
| `effective_from` / `effective_to` | validity window |
| `created_by` / `approved_by` | approval audit |

### BOM_Line — fields
| Field | Notes / rule |
|---|---|
| `bom_id` | FK → BOM_Header |
| `line_no` | line sequence |
| `component_id` | FK → Item |
| `quantity` | per one `base_quantity` of parent |
| `uom` | line unit |
| `scrap_percentage` | expected waste; **MRP multiplies qty by `(1 + scrap%)`** |
| `issue_type` | enum `{ Manual, Backflush, AutoIssue }` |
| `issue_level` | enum `{ PerOrder, PerShift, PerOperation }` |
| `operation_seq` | which routing operation consumes this line — **the link BOM ↔ Routing** |
| `substitute_items` | alternatives if primary unavailable |

### Rules / generic-design notes
- **Base quantity defaults to 1** (per piece) for flexibility. Shared packaging (e.g. one carton per set) is handled **at the parent assembly level**, not forced onto each piece.
- **Multi-level:** a BOM_Line's `component_id` may itself have a BOM (sub-assembly / semi-finished). **MRP explodes recursively.**
- **Low-value consumables** (negligible cost, hard to meter per unit) should NOT be BOM lines — treat as **overhead**. Provide a flag, or omit and account in an overhead pool.
- `bom_type = Phantom` is an enum value (a phantom/transient assembly that is exploded through, not stocked). The spec names it but does not further detail phantom explosion behaviour here.

### Melamine stress-test (example, do NOT hard-code)
4-level BOM proven:
```
Set (50 pcs)                          ← level 0, type=Production
 ├─ Large dish ×6                      ← level 1, procurement=Make
 │   └─ Melamine powder 0.15kg, scrap 3%   ← level 3 raw
 │   └─ Ready decor cutout ×1          ← level 2, semi-finished
 │        └─ Decor sheet, scrap 15%    ← level 3 raw
 ├─ ... other pieces
 └─ Printed carton ×1                  ← packaging at SET level (not piece)
Glaze: NOT a BOM line → overhead consumable (sprayed, negligible).
```

---

## 1.3 Entity: Routing

**Purpose:** the manufacturing path — the sequence of operations, who does each, how long. Enables scheduling and time calculation.

### Routing_Header — fields
| Field | Notes / rule |
|---|---|
| `routing_id` | PK |
| `item_id` | FK → Item |
| `routing_type` | enum `{ Production, Repair, Inspection }` |
| `version` | versioned |
| `lot_size_from` / `lot_size_to` | a product may have **different routings per batch size** |
| `status` | enum `{ Draft, Active, Inactive }` |
| `effective_from` / `effective_to` | validity window |
| `total_lead_time` | **computed** |

### Routing_Operation — fields
| Field | Notes / rule |
|---|---|
| `operation_no` | sequence 10, 20, 30 — **multiples of 10 to allow inserts** (15, 25) without renumbering |
| `description` | text |
| `work_center_id` | FK → Work_Center |
| `tool_id` | FK → Tool, **nullable** — the mold/die required |
| `setup_time` | fixed, independent of batch size |
| `run_time_per_unit` | per-unit run time |
| `cavity_count` | units produced per cycle — **DEFAULT 1**; >1 for multi-cavity |
| `cycle_time` | time for one cycle/press — use with `cavity_count` |
| `queue_time` | wait before start — adds to lead time, **not cost** |
| `move_time` | transfer to next op — lead time, **not cost** |
| `inspection_required` | bool |
| `critical_operation` | bool — **cannot be skipped** |
| `required_skill_code` | skill needed |

### Formulas / rules
- **Per-piece time for molding / multi-output:**
  ```
  time_per_piece = cycle_time / cavity_count
  ```
  Default `cavity_count = 1` collapses this to normal behaviour for discrete manufacturers.
- `queue_time` / `move_time` affect lead time but are usually **NOT costed**.
- An operation may reference a **Tool** in addition to a Work Center — **both must be available to run** (resource intersection, §1.5).
- `cavity_count` is the **key generalization** for molding / multi-output processes.

### Melamine stress-test
```
Plain dish routing:
  Op 10 Press (WC-PRESS, MOLD-LG, cycle 70s, cavity 1)  → 70s/piece
  Op 15 Cooling (queue, no cost)
  Op 20 Deflash (WC-DEFLASH)
  Op 30 Sanding (WC-SAND)
  Op 40 Grading (WC-QC) ← produces grade distribution
Multi-cavity proof: cup mold cavity=4, cycle 60s → 15s/piece (4× throughput, same machine).
Printed dish: SAME routing (decor prepared separately as semi-finished).
```

---

## 1.4 Entity: Work Center

**Purpose:** where work happens. **The interface between production and accounting — all costs flow through it.**

### Fields
| Field | Notes / rule |
|---|---|
| `wc_id` | PK |
| `description` | text |
| `category` | enum `{ Machine, Labor, SetupGroup }` |
| `plant_location` | location |
| `cost_center_id` | FK → Cost_Center |
| `capacity_unit` | enum `{ Hours, Pieces, Kg }` |
| `daily_capacity` | per single unit |
| `capacity_multiplier` | **number of identical machines/workers** in this WC (capacity pool) |
| `efficiency_percent` | efficiency factor |
| `utilization_percent` | utilization factor |
| `calendar_id` | shifts & holidays |
| `setup_cost_rate` | rate |
| `labor_cost_rate` | rate |
| `machine_cost_rate` | rate |
| `overhead_rate` | rate |
| `labor_calc` | enum `{ Hourly, PieceRate }` — see §1.6 |
| `bottleneck_flag` | bool |

### Effective-capacity formula
```
effective_capacity = daily_capacity × capacity_multiplier × efficiency% × utilization%
```
**CRP uses effective (not gross) capacity.**

### Rules / generic-design notes
- `capacity_multiplier` lets **N identical machines be ONE work center** (a "pool of capacity"). Use separate WCs only when operations/costs genuinely differ.
- **Decision rule (what defines a separate WC):** the *type of operation and its cost*, NOT the count of machines. Identical machines, identical work, identical cost = one WC with a multiplier. Different work or different cost = separate WCs.

### Melamine stress-test
```
WC-PRESS: 2 groups × 2 presses each. Modeled as multiplier=2 per group, 3 shifts, eff 85%. Bottleneck=true.
Machine_cost_rate = (depreciation + maintenance + cooling water) / press-hours.
Labor: PieceRate (operator paid per piece, NOT hourly).
Note: operator runs 2 presses alternately → worker may be the real constraint if manual handling time exceeds cycle time (machine-coupling).
```

---

## 1.5 Entity: Tool / Mold (Resource generalization)

**Purpose:** in molding/tooling industries the **tool is the capacity constraint, not the machine.** Generalized as a `Resource` of `resource_type = Tool`.

### Fields
| Field | Notes / rule |
|---|---|
| `tool_id` | PK |
| `description` | text |
| `product_id` | which product it makes — **or many** |
| `cavity_count` | pieces per cycle |
| `setup_time` | mount/dismount — **often large** |
| `status` | enum `{ Available, Mounted, Maintenance, Retired }` |
| `life_cycles` | expected cycles before replacement (usage depreciation) |
| `cycles_used` | running counter |
| `purchase_cost` | for depreciation per cycle |

### Formulas / rules
- **Resource abstraction:** Machine, Tool, Labor are all `Resource` with `resource_type`. An operation may require **several resources simultaneously**.
  ```
  available_start = MAX(earliest free time across all required resources)
  ```
- **Usage depreciation:** tools wear by **cycles, not calendar**. Only mounted/running tools accrue wear.
  ```
  depreciation_per_piece = purchase_cost / life_cycles / cavity_count
  ```
- A factory with no tooling simply has **no Tool resources** — the abstraction collapses gracefully.

### Melamine stress-test
```
50 molds total, but only ~4 mounted (running) at a time → only those 4 depreciate.
Mold cost from 40k EGP up. Setup (mounting) heavy → drives large lot sizing.
Some molds multi-cavity (e.g. tea-set tray mold = 4 small pieces/cycle).
```

---

## 1.6 Generic point: Labor calculation mode

`labor_calc` on the **Work Center (or item)** decides how direct labor cost is computed:
```
if labor_calc == Hourly:    cost = labor_hours × labor_rate
if labor_calc == PieceRate: cost = quantity × piece_rate   (piece_rate may vary by product/size)
```
**Consequence:** under `PieceRate`, a slow worker costs *himself* (earns less), not the factory — so there is **no traditional Labor Efficiency Variance**; the deviation shifts to machine/overhead. **The system must handle both modes.**

---

## 1.7 Dependencies & Integrations (this layer)

- **Depends on:** Item Master (host ERP), Calendar, Skills/Tools master, Approval workflow (ECO).
- **Feeds:** MRP (BOM explosion), Production Order (snapshot source), Costing (standard cost build-up), CRP (capacity definition).

---

## State machines (status enums as transitions)

- **BOM_Header.status:** `Draft → Active → Inactive → Obsolete` (`{ Draft, Active, Inactive, Obsolete }`).
- **Routing_Header.status:** `Draft → Active → Inactive` (`{ Draft, Active, Inactive }`).
- **Tool.status:** `Available → Mounted → Maintenance → Retired`; Maintenance/Available cycling possible (`{ Available, Mounted, Maintenance, Retired }`). Only `Mounted` accrues cycle wear.

---

## Config / variant points introduced by this layer

| Config field | Where | Purpose / default |
|---|---|---|
| `bom_type` | BOM_Header | Production / Engineering / Phantom |
| `base_quantity` | BOM_Header | default 1 (per piece) |
| `scrap_percentage` | BOM_Line | MRP multiplier `(1+scrap%)` |
| `issue_type` | BOM_Line | Manual / Backflush / AutoIssue |
| `issue_level` | BOM_Line | PerOrder / PerShift / PerOperation |
| `substitute_items` | BOM_Line | alternates |
| `routing_type` | Routing_Header | Production / Repair / Inspection |
| `lot_size_from/to` | Routing_Header | routing-per-batch-size |
| `cavity_count` | Routing_Operation & Tool | multi-output; default 1 |
| `capacity_multiplier` | Work Center | machine pool; default 1 |
| `category` | Work Center | Machine / Labor / SetupGroup |
| `capacity_unit` | Work Center | Hours / Pieces / Kg |
| `labor_calc` | Work Center / item | Hourly / PieceRate |
| `bottleneck_flag` | Work Center | constraint marker |
| `resource_type` | Resource (Tool/Machine/Labor) | abstraction switch |
| Low-value-consumable flag | BOM line / item | omit from BOM → overhead |

---

## Explicit "expects from the host ERP" list

The Master Data layer assumes these already exist in Moon ERP and integrates (does not rebuild):

1. **Item / Product Master** — `parent_item_id`, `component_id`, `item_id` all FK → host Item. Must carry `procurement_type {Buy, Make}` and a UoM.
2. **UoM master + conversions** — every BOM line/header and WC capacity carries a `uom`/`capacity_unit`; conversions between them are assumed available.
3. **Cost Center master** — `Work_Center.cost_center_id` FK; "all costs flow through the WC into accounting."
4. **Calendar master** — `Work_Center.calendar_id` (shifts & holidays) feeds effective capacity / lead-time.
5. **Skills master** — `Routing_Operation.required_skill_code` references an HR/skills catalog.
6. **Approval workflow (ECO / change orders)** — master data is "changed via controlled change orders"; `approved_by` on BOM_Header.
7. **GL / Cost accounting** — WC rates (`setup/labor/machine/overhead`) and tool depreciation feed standard cost build-up and GL.
8. **User master** — `created_by` / `approved_by`.
9. **Downstream module hooks** — MRP (recursive BOM explosion), Production Order (frozen snapshot of active BOM+Routing), CRP (effective capacity), Costing (standard cost build-up).
</content>
</invoke>

---


# 02 — Planning Layer (MPS / MRP / CRP) — Technical Digest

> Lossless English digest of `files/manufacturing_spec/markdown/02_planning.md`. An implementer should never need the original. Source-of-truth wording, every entity, field, rule, enum, and formula reproduced, plus an explicit "expects from ERP" contract for mapping onto Moon ERP.

The Planning layer is "the brain": it translates expected/actual demand into a concrete, **feasible** production plan. It inherits all cross-cutting rules from `00` (frozen snapshots, status state machines, many-to-many demand↔supply, multi-level recursion, config-over-code, real-time GL postings, resource abstraction). Three **sequential** components with a **closed feedback loop**: `MPS → MRP → CRP`.

---

## 2.1 The Three Outputs of Planning

Planning as a whole produces exactly three deliverables (mental model):

| # | Output | Question answered | Produced by |
|---|---|---|---|
| 1 | Order acceptance + delivery date | "Can we accept the order? By when can we deliver?" | MPS + CTP |
| 2 | Material schedule | "What to buy/make, how much, when" | MRP |
| 3 | Detailed production plan | "When does each stage start?" | MRP draft + CRP confirm |

**Inputs to planning:** customer order, master data (BOM / Routing / Work Center), current inventory, item settings, material ownership.

---

## 2.2 Component: MPS (Master Production Schedule)

**Purpose:** Decides **what** to produce, **how much**, **when**. Under make-to-order it shifts role from "forecaster" to "order translator + acceptance/capacity checker."

### Behaviour branches by `production_type`
Enum `production_type ∈ { MakeToStock, MakeToOrder, AssembleToOrder, TollManufacturing }`.

```
if MakeToStock:
    demand = apply_forecast_consumption(forecast, actual_orders)
    PAB[t] = PAB[t-1] + planned_production[t] - demand
    if PAB[t] < safety_stock: suggest production
if MakeToOrder:
    ignore forecast; planned_production = confirmed_orders; safety_stock = 0
    run CTP check (capacity-based promise)
if AssembleToOrder:
    components → MakeToStock; finished assembly → MakeToOrder
if TollManufacturing:
    plan capacity only (material is the customer's — see 2.3)
```

### Core formula — PAB (Projected Available Balance)
```
PAB[t] = PAB[t-1] + scheduled_production[t] - demand[t]
demand[t] = (per forecast_consumption + time_fence rules below)
```

### Forecast Consumption (avoid double-counting)
Actual orders **consume** the forecast; they do not add to it.
```
consumed            = MIN(actual_orders[t], forecast[t])
remaining_forecast  = forecast[t] - consumed
total_demand[t]     = actual_orders[t] + remaining_forecast
if actual_orders[t] > forecast[t]: total_demand[t] = actual_orders[t]
```
**Config:** `forecast_consumption_method ∈ {None, Backward, Forward, Both}`, `consumption_window`.

### Time Fences (zone determined by distance from today)
```
distance = t - today
if   distance <= DTF (Demand Time Fence):   zone = FROZEN  → demand = actual_orders only (ignore forecast)
elif distance <= PTF (Planning Time Fence): zone = SLUSHY  → MAX/consumption blend; changes need approval
else:                                       zone = LIQUID  → demand = forecast (no orders yet)
```
Purpose: prevents "system nervousness" — the near-term plan is stable, the far-term flexible.

### CTP (Capable to Promise) — core of make-to-order
Capacity-based delivery promise (distinct from ATP which is inventory-based).
```
CTP_Check(product P, qty Q, due D):
    for op in routing(P):
        if op.wc is PRESS-like:
            cycles = Q / op.cavity_count
            load[wc] += cycles × op.cycle_time + op.setup_time
        else:
            load[wc] += Q × op.run_time + op.setup_time
    for wc in required: earliest[wc] = find_earliest_slot(wc, load[wc])
    promised = MAX(earliest[wc])          # bottleneck decides
    return promised <= D ? "on time" : ("earliest: " + promised)
```

### Order splitting & batching (Many-to-Many)
- Large order → multiple **delivery schedules** (e.g. monthly).
- Multiple similar orders → ONE production order (to reduce setup / mold changes).
- Links recorded in the **Pegging** table (defined in spec file 06):
```
Pegging: { production_order_id, sales_order_id, allocated_quantity }
```

### MPS data model
```
MPS_Header: { id, plan_name, plant_id, time_bucket ∈ {Day, Week, Month},
              start_date, end_date, frozen_fence, slushy_fence, status }
MPS_Line:   { product_id, period_date, forecast_qty, confirmed_orders_qty,
              planned_production, projected_balance, safety_stock_target,
              available_to_promise }
```

### ATP calculation — 3 modes (config)
```
Discrete:   ATP[t] = production[t] - orders until next production
Cumulative: ATP[t] = Σ production≤t - Σ orders≤t
Look-ahead: ATP[t] = production[t] - orders(t .. next production)   ← safest
```

---

## 2.3 Component: MRP (Material Requirements Planning)

**Purpose:** Turn production orders into material needs — what to buy/make, how much, when. MRP is a **process, not a table**; it produces transient **Planned Orders**.

### Item MRP settings
```
Item_MRP_Settings: {
  item_id,
  mrp_type           ∈ {Planned, ReorderPoint, NoPlanning},
  procurement_type   ∈ {Buy, Make},
  material_ownership ∈ {Own, Customer},
  lot_sizing_rule    ∈ {Exact, Fixed, MinMax, EOQ, PeriodOrder},
  lot_size, min_lot, max_lot,
  safety_stock, lead_time_days, reorder_point
}
```

### MRP algorithm (level-by-level, top to deepest)
```
MRP_Run():
  sort items by BOM level (finished = 0, raw = deepest)
  for each item, for each period t:
    Gross[t]     = MPS_demand (finished) + dependent_demand_from_parents (component)
    Available[t] = on_hand + open_POs[t] + open_WOs[t] - reserved[t]
    Net[t]       = MAX(0, Gross[t] - Available[t] + safety_stock)
    Order_Qty    = apply_lot_sizing(Net[t], lot_rule)
    Release_Date = Required_Date - lead_time
    if procurement_type == Make:
        explode_BOM → dependent demand for components → recurse deeper
```

### BOM Explosion (recursive, multi-level)
```
explode_BOM(product P, qty Q):
  for line in BOM(P):
    required = Q × line.quantity × (1 + line.scrap_pct)
    dependent_demand[line.component] += required
    if line.component.procurement_type == Make:
        explode_BOM(line.component, required)
```
Note: scrap is applied at each BOM line via `line.scrap_pct`.

### Lot Sizing (key branch for tooling factories)
```
apply_lot_sizing(net, rule):
  Exact:       net
  Fixed:       round_up(net, lot_size)
  MinMax:      clamp(net, min, max)
  EOQ:         economic_order_qty()
  PeriodOrder: Σ net over several periods   ← consolidate to cut setup / mold changes
```
Tooling-heavy items use `PeriodOrder` / `Fixed` to batch demand and minimize mold mounts.

### Toll-manufacturing branch (driven by `material_ownership`)
```
when generating purchase for a component:
  if component.material_ownership == Customer:
      DO NOT raise a purchase order        (customer supplies it)
      instead: verify customer-supplied quantity is sufficient; if short → alert
  else:
      raise a normal purchase order
```

### MRP outputs
```
- Planned Purchase Orders    → Procurement module
- Planned Production Orders   → Execution layer
- Exception Messages          → "will be late", "reschedule in/out", "cancel"
```

---

## 2.4 Component: CRP (Capacity Requirements Planning)

**Purpose:** MRP plans assuming **infinite capacity**. CRP injects reality — verifies that work centers (and tools, and labor) have enough capacity at the required time. In tooling factories CRP matters **more** than MRP (materials are easy; scheduling N tools on M machines is the real problem).

### CRP algorithm
```
CRP_Run():
  # 1. Load per planned order
  for order, op:
    if op.wc is PRESS-like:
      cycles = order.qty / op.cavity_count
      load   = cycles × op.cycle_time + op.setup_time
    else:
      load   = order.qty × op.run_time + op.setup_time
  # 2. Distribute over periods → 3. Aggregate per resource
  Total_Load[resource, period] = Σ loads
  # 4. Compare
  Util = Total_Load / effective_capacity × 100
  Util > 100 → OVERLOAD ; Util < 70 → UNDERLOAD ; else OK
  # 5. Resolve overload
  → reschedule / overtime / extra shift / defer order / use multi-cavity tool
```
Thresholds: `Util > 100 → OVERLOAD`, `Util < 70 → UNDERLOAD`, otherwise OK.

### Resource intersection (Machine + Tool + Labor)
```
earliest_start = MAX(machine_free_time, tool_free_time, labor_free_time)
```
The binding constraint is the **minimum-availability** resource. A **tool** is a *movable* resource that must be tracked across machines. (`resource_type ∈ {Machine, Tool, Labor}`.)

### RCCP vs CRP (both supported)
```
RCCP (Rough Cut): runs on MPS, critical resources only → used by CTP for order acceptance
CRP  (Detailed):  runs on MRP, all resources         → used for detailed scheduling
```

### Melamine test case (proves the design)
```
WC-PRESS week3 capacity 90h. Order 6000 dishes, mold cavity=1, cycle 80s → 133h.
Util = 148% → OVERLOAD. Resolution options surfaced, including:
  use a 4-cavity mold variant → 6000/4 × 80s = 33h ✓
Links a scheduling decision (CRP) to a master-data choice (which mold).
```

---

## 2.5 Known Gap — Detailed Scheduling (APS)

CRP detects overload but does **NOT** sequence operations hour-by-hour on specific machines. A future **APS (Advanced Planning & Scheduling)** component should:
- Sequence operations on each machine/tool on a timeline (Gantt).
- Optimize mold-mount order to minimize changeovers (the hardest problem: ~50 molds on ~4 presses).
- Respect machine warm-up, shift patterns, tool availability.

**APS interface:** consumes Planned Orders + Routing + Resource calendars; emits a time-phased schedule that SFC (spec file 05) executes.

---

## 2.6 Closed Loop

```
CRP finds conflict → feeds back to MPS → adjusts promised_date → re-runs
Costing variances (file 04) → inform standards & future planning
```

---

## Enums & config points introduced/used in this file

| Identifier | Domain | Owner |
|---|---|---|
| `production_type` | MakeToStock, MakeToOrder, AssembleToOrder, TollManufacturing | item / order setting (00) |
| `material_ownership` | Own, Customer | Item_MRP_Settings (00) |
| `procurement_type` | Buy, Make | Item_MRP_Settings (00) |
| `mrp_type` | Planned, ReorderPoint, NoPlanning | Item_MRP_Settings (02) |
| `lot_sizing_rule` | Exact, Fixed, MinMax, EOQ, PeriodOrder | Item_MRP_Settings (00/02) |
| `resource_type` | Machine, Tool, Labor | Resource (00) |
| `forecast_consumption_method` | None, Backward, Forward, Both | MPS config (02) |
| `time_bucket` | Day, Week, Month | MPS_Header (02) |
| ATP mode | Discrete, Cumulative, Look-ahead | MPS config (02) |
| Time-fence zone | FROZEN, SLUSHY, LIQUID | derived from DTF/PTF (02) |
| CRP util state | OVERLOAD (>100), OK, UNDERLOAD (<70) | CRP output (02) |

Numeric config fields: `DTF` (Demand Time Fence), `PTF` (Planning Time Fence), `consumption_window`, `lot_size`, `min_lot`, `max_lot`, `safety_stock`, `lead_time_days`, `reorder_point`, `frozen_fence`, `slushy_fence`, `scrap_pct` (per BOM line), `cavity_count`, `cycle_time`, `setup_time`, `run_time`.

---

## Entities this part defines (mapping contracts)

1. **MPS_Header** — planning run header (plan_name, plant_id, time_bucket, start/end dates, frozen/slushy fences, status).
2. **MPS_Line** — per product/period grid (forecast, confirmed orders, planned production, projected balance, safety-stock target, ATP).
3. **Item_MRP_Settings** — per-item planning policy (mrp_type, procurement_type, material_ownership, lot sizing, safety stock, lead time, reorder point).
4. **Planned Order (transient)** — output of MRP: Planned Purchase Order + Planned Production Order; promotable into real PO / Production Order.
5. **Exception Message** — MRP advisory (late / reschedule-in / reschedule-out / cancel).
6. **CRP Load record** — `Total_Load[resource, period]` with utilization & state vs effective capacity.
7. **Pegging** (referenced; defined in 06) — `{production_order_id, sales_order_id, allocated_quantity}` linking demand↔supply.

---

## Expects from ERP (integration contract)

- **Sales (demand)**: confirmed customer orders and their due dates + delivery schedules to feed MPS `confirmed_orders_qty` and CTP. Moon ERP `Modules\Sales\SalesOrder` (+ delivery-note schedules).
- **A forecast source**: nothing in Moon ERP produces demand forecasts today — MPS needs `forecast_qty` per product/period (new table or import).
- **Inventory (supply/availability)**: `on_hand`, `reserved` per item/period. Moon ERP `Modules\Inventory\StockBalance` + `InventoryMovement`.
- **Open supply visibility**: `open_POs[t]` (Purchases `PurchaseOrder` not yet received) and `open_WOs[t]` (Production `ProductionOrder` not yet completed) time-phased by due date.
- **Master data**: BOM (with per-line `quantity` + `scrap_pct` + `procurement_type`), Routing operations (with `run_time`, `setup_time`, `cycle_time`, `cavity_count`, target work center), Work Center capacity. Moon ERP `Modules\Production\BillOfMaterials` / `BomComponent` / `BomOperation` / `ProductionCenter` (`capacity_per_hour`).
- **Resource calendars & effective capacity** per work center / tool / labor — needed for CTP/RCCP/CRP `find_earliest_slot` and `effective_capacity`. Moon `ProductionCenter.capacity_per_hour` exists but no calendar/shift model yet.
- **Procurement handoff**: MRP Planned Purchase Orders must become Purchases `PurchaseRequest`/`PurchaseOrder`; must skip components where `material_ownership == Customer`.
- **Execution handoff**: MRP Planned Production Orders must become `Modules\Production\ProductionOrder` (Planned status); CRP must reschedule via `planned_start_date`/`planned_end_date`.
- **Pegging store**: a many-to-many link table between Sales orders and Production orders.
- **Item master**: every planned SKU resolves to Core `Product`; Item_MRP_Settings extends Product (extension-table pattern, like `AccBpExt`).
- **UoM**: BOM line quantities, lot sizes, stock balances, and order quantities must share a unit basis (UoM conversion service) — spec assumes consistent units but never states conversion.
</content>
</invoke>

---


# 03 — Execution Layer (Technical Digest)

> Lossless digest of `manufacturing_spec/markdown/03_execution.md`. The Execution layer translates plans into actually-recorded events. The delta between planned and actual = variances (handled in file 04 / Costing). Inherits all cross-cutting rules from `00` (frozen snapshots, status state machines, many-to-many demand↔supply pegging, multi-level recursion, config-over-code, every actual event posts a GL entry, resource abstraction).

Four sequential components, executed in order:

```
Production Order → Material Issue → Confirmation → Goods Receipt
```

---

## 3.1 Production Order (PO)

**Purpose:** The internal contract authorizing production of a specific quantity. Every downstream document (Issue, Confirmation, Goods Receipt) links to a specific Production Order.

### Critical concept — Frozen Snapshot
On the transition `Planned → Released`, the order **snapshots** the currently-active BOM and Routing into order-local copies. Later changes to the master BOM/Routing do NOT affect this already-released order. This protects historical cost and the audit trail. The snapshot ids are stored on the header (`bom_snapshot_id`, `routing_snapshot_id`) and the snapshot rows live in `Order_Component` (BOM copy) and `Order_Operation` (Routing copy).

### Entity: `Production_Order_Header`
| Field | Type / Enum | Notes |
|---|---|---|
| `order_no` | PK | Document number |
| `order_type` | enum { Standard, Rework, Repair } | Type of production order |
| `product_id` | FK | What is being produced |
| `quantity` | number | Order quantity |
| `production_type` | enum { MakeToStock, MakeToOrder, TollManufacturing } | (master enum in 00 also lists AssembleToOrder) drives branching |
| `material_ownership` | enum { Own, Customer } | Customer = consignment / toll |
| `start_date` | date | Planned start |
| `finish_date` | date | Planned finish |
| `status` | enum { Planned, Released, InProcess, Completed, Closed } | State machine below |
| `bom_snapshot_id` | FK | Frozen BOM reference |
| `routing_snapshot_id` | FK | Frozen Routing reference |
| `tool_id` | FK | Assigned mold/die |
| `source_orders[]` | list | Pegging — which sales orders this order serves |
| `wip_account` | FK (GL) | WIP control account this order posts to |

### Entity: `Order_Component` (snapshot copy of BOM lines)
| Field | Notes |
|---|---|
| `component_id` | The material/component item |
| `required_quantity` | `= bom_qty × order_qty × (1 + scrap%)` |
| `issued_quantity` | Running total issued so far |
| `reserved_quantity` | Reserved at Release |
| `operation_seq` | Which operation consumes it |

**Formula:** `required_quantity = bom_qty × order_qty × (1 + scrap%)`

### Entity: `Order_Operation` (snapshot copy of Routing operations)
| Field | Notes |
|---|---|
| `operation_no` | Operation sequence id |
| `work_center_id` | Where it runs |
| `tool_id` | Tool/mold for this operation |
| `planned_setup_time`, `planned_run_time` | Standards |
| `actual_setup_time`, `actual_run_time` | Recorded actuals |
| `status` | enum { Waiting, Ready, InProgress, Completed } — consumed by SFC (file 05) |
| `actual_start_time`, `actual_end_time` | Timestamps |
| `confirmed_quantity`, `scrap_quantity` | Per-operation actuals |

### State machine (transitions + side-effects)
```
Planned → Released:    snapshot BOM + Routing; reserve materials; reserve tool & capacity
Released → InProcess:  first issue/confirmation begins; time tracking starts
InProcess → Completed: all operations done; confirmed_qty ≈ order_qty
Completed → Closed:    close WIP; compute final variances; block further postings
```

### Generic-design notes
- **Order network:** orders link parent ↔ child. A set-assembly order spawns piece-production orders, which spawn semi-finished (cutout) orders. Each order knows its parent and children. Delays are tracked by walking the network.
- **Confirmed quantity may be a grade distribution**, not a scalar (see 3.3). `confirmed_quantity` generalizes to `{ grade → qty }`.

### Toll branch (when `production_type == TollManufacturing`)
```
on Released:  do NOT reserve/purchase customer material; verify consignment sufficiency
on Closed:    output goes to CUSTOMER ownership (not own FG); cost = conversion only
```

---

## 3.2 Material Issue

**Purpose:** The first real accounting movement. Material moves from Raw Materials (RM) inventory to Work-In-Process (WIP).

### Entity: `Material_Issue_Header`
| Field | Type / Enum | Notes |
|---|---|---|
| `issue_no` | PK | |
| `issue_date` | date | |
| `issue_type` | enum { Manual, Backflush, AutoIssue } | Timing differs (below) |
| `issue_level` | enum { PerOrder, PerShift, PerOperation } | Granularity of issuing |
| `order_no` | FK | Owning production order |
| `operation_no` | FK | Owning operation (if per-operation) |
| `issuer_id` | FK | Who issued |
| `warehouse_from` | FK | Source warehouse |
| `status` | enum { Draft, Posted, Reversed } | State machine |

### Entity: `Material_Issue_Line`
| Field | Notes |
|---|---|
| `material_code` | Item |
| `quantity` | Issued qty |
| `batch_no` / `lot_no` | Lot tracking |
| `cost_price` | Per `costing_method` (FIFO/LIFO/Standard/Average) |
| `total_cost` | qty × cost_price |
| `bin_location` | Source bin |
| `ownership` | enum { Own, Customer } — consignment tracking |

### Issue types (timing differs)
```
Manual:    worker issues explicitly, when needed, actual qty. Most accurate, slowest.
Backflush: system auto-issues at confirmation; qty = produced × BOM. Diff → variance.
AutoIssue: all materials issued at Release, in one shot. Suits small orders.
```

### Algorithm
```
Material_Issue(target, material, qty):
  validate: order is Released/InProcess; available ≥ qty; material is in BOM snapshot
  cost_price = get_cost(material, costing_method)     # FIFO/LIFO/Standard/Average
  on_hand[material]  -= qty
  reserved[material]  -= qty
  WIP[order]         += qty × cost_price
  POST: Debit WIP  /  Credit Raw Materials   (qty × cost_price)
  order_component[material].issued += qty
```

### Toll branch (when `material.ownership == Customer`)
```
issue from separate customer (consignment) stock, NOT own stock
accounting differs: NO "Credit Raw Materials" at your cost (it is not your asset)
record a "customer material consumption" movement for tracking only
```

### Generic-design notes
- **`issue_level`** generalizes shift-level issuing: a worker draws a whole shift's material upfront; reconciliation at shift end produces the usage variance.
- Each BOM line chooses its own `issue_type` / `issue_level`: heavy/expensive → Manual; small → Backflush; negligible → treated as overhead, not issued at all.

### Melamine test case (shift-level manual issue)
```
Worker draws 16 kg powder; produces 300 dishes.
Theoretical = 300 × 0.15 × 1.03 = 46.4 kg ... (per-shift reconciliation):
Material Usage Variance = issued − (produced × BOM)  → surfaces powder waste
without per-piece tracking.
```
(Note: the 16 kg vs 46.4 kg figures are the spec's own illustrative numbers; the load-bearing point is the reconciliation formula, not the arithmetic.)

---

## 3.3 Confirmation

**Purpose:** Records that part of an order actually executed, **per operation**: how much was produced, labor/machine time used, scrap. The true source of "actuals" compared against standards.

### Entity: `Confirmation_Header`
| Field | Type / Enum | Notes |
|---|---|---|
| `confirmation_no` | PK | |
| `order_no`, `operation_no` | FK | What is being confirmed |
| `confirmation_type` | enum { Partial, Final, Reversal } | |
| `operator_id`, `work_center_id` | FK | |
| `shift` | value | Which shift |
| `timestamp` | datetime | |

### Entity: `Confirmation_Yield` (GENERALIZED — a distribution, not a scalar)
| Field | Notes |
|---|---|
| `grade_code` | A/B/C... or a single grade for normal factories |
| `quantity` | Qty at that grade |
| `unit_sale_price` | For joint-product NRV cost allocation |

### Entity: `Confirmation_Detail`
| Field | Notes |
|---|---|
| `scrap_quantity` | |
| `rework_quantity` | |
| `setup_time_actual`, `run_time_actual` | |
| `labor_hours`, `machine_hours` | |
| `reason_code` | for scrap/delay → Pareto analysis |

### Core rule (yield identity & KPIs)
```
Σ(grade quantities) + scrap = total_input
yield%      = Σgrades  / input
first_pass% = grade_A  / input     (key quality KPI)
```

### Algorithm (with backflush & GL postings)
```
Confirmation(order, operation, results):
  validate: order InProcess; operation open
  # backflush materials if configured on this operation
  if operation has backflush materials:
      consumed = total_input × BOM_qty ; auto Material_Issue
  # cost posting — labor depends on labor_calc
  labor_cost   = (labor_calc == Hourly) ? labor_hours × labor_rate
                                        : produced × piece_rate
  machine_cost = machine_hours × machine_rate
  overhead     = (labor_hours + machine_hours) × overhead_rate   # or qty × oh_rate
  POST:
    Debit  WIP                 (labor + machine + overhead)
    Credit Labor Applied       (labor_cost)
    Credit Machine Applied     (machine_cost)
    Credit Overhead Applied    (overhead)
  # scrap
  POST: Debit Scrap Expense / Credit WIP   (scrap × unit_cost)
  # advance the routing
  if Final: close operation; if not the last: mark next operation Ready  (→ SFC)
```

### Joint-products cost allocation (NRV method — at confirmation/receipt)
```
total_sale_value = Σ(grade.qty × grade.price)
for each grade:
  grade.allocated_cost = total_cost × (grade.qty × grade.price) / total_sale_value
  grade.unit_cost      = grade.allocated_cost / grade.qty
```
Higher-priced grade absorbs more cost (NRV). Normal factories use a single grade → this collapses to standard single-output behaviour.

### Generic-design notes
- **Three generalizations from Melamine:** (1) yield is a grade distribution; (2) labor may be piece-rate; (3) cost allocates across grades at confirmation.
- **`reason_code`** drives Pareto analysis of defects (the 20% of causes producing 80% of scrap).

---

## 3.4 Goods Receipt (GR)

**Purpose:** The last step — finished product enters inventory. Closes WIP, opens FG. Converts accumulated cost into ready-to-sell product cost.

### Entity: `Goods_Receipt_Header`
| Field | Type / Enum | Notes |
|---|---|---|
| `gr_no` | PK | |
| `gr_date` | date | |
| `order_no` | FK | |
| `receipt_type` | enum { Full, Partial } | |
| `warehouse_to` | FK | Destination FG warehouse |
| `receiver_id` | FK | |
| `status` | enum { Draft, Posted, Reversed } | |

### Entity: `Goods_Receipt_Line` (One-to-Many — a line per grade)
| Field | Notes |
|---|---|
| `item_id` | Finished item |
| `received_quantity` | |
| `grade_code` | Per-grade line |
| `batch_no` / `lot_no` | |
| `bin_location` | |
| `cost_per_unit` | `= allocated WIP / qty` |
| `quality_status` | enum { Released, OnHold, Rejected } |

### Algorithm
```
Goods_Receipt(order, received_qty):
  Total_WIP = Σ(material + labor + machine + overhead)
  # multi-grade: split into lines, each with allocated cost (see 3.3)
  for each grade line: FG_Inventory[item, grade] += qty  (at allocated unit cost)
  POST (standard costing):
    Debit  FG Inventory   (standard_cost × qty)
    Debit  Variance Acct  (difference)
    Credit WIP            (total_actual_cost)
  if Full: order → Completed
  if pegged to sales orders: allocate quantities to customers
  compute final variances; order → Closed
```

### Multi-grade receipt example (Melamine)
```
One press order of 100 dishes → 3 receipt lines + scrap:
  A: 70 @ 11.77 ,  B: 18 @ 7.06 ,  C: 7 @ 3.53 ,  scrap 5
POST:
  Debit FG-A 824 / FG-B 127 / FG-C 25 / Scrap 24 ; Credit WIP 1000
```

### Toll branch (when `production_type == TollManufacturing`)
```
output → CUSTOMER ownership (not own FG inventory)
revenue = conversion fee only:
  Debit Customer (receivable)  /  Credit Toll-service revenue
close conversion-WIP (labor + machine + overhead, NO material)
```

### Final variance gate
At Close, compute and decompose variances (price / usage / labor / overhead). This is the entry point to Costing (file 04).

---

## Enums introduced or used by this layer
| Enum | Values | Where |
|---|---|---|
| `order_type` | Standard, Rework, Repair | PO header |
| `production_type` | MakeToStock, MakeToOrder, TollManufacturing (00 adds AssembleToOrder) | PO header |
| `material_ownership` | Own, Customer | PO header, Issue line |
| `order_status` | Planned, Released, InProcess, Completed, Closed | PO header |
| `operation_status` | Waiting, Ready, InProgress, Completed | Order_Operation |
| `issue_type` | Manual, Backflush, AutoIssue | Issue header |
| `issue_level` | PerOrder, PerShift, PerOperation | Issue header |
| `issue/GR status` | Draft, Posted, Reversed | Issue & GR headers |
| `confirmation_type` | Partial, Final, Reversal | Confirmation header |
| `receipt_type` | Full, Partial | GR header |
| `quality_status` | Released, OnHold, Rejected | GR line |
| `costing_method` | Standard, Actual, Average, FIFO (00); 3.2 text also mentions LIFO/Standard | Issue costing |
| `labor_calc` | Hourly, PieceRate | Confirmation labor cost |

## GL postings introduced by this layer (every actual event posts)
1. **Material Issue:** Debit WIP / Credit Raw Materials (qty × cost_price). Toll/customer-owned: tracking movement only, no RM credit at own cost.
2. **Confirmation (conversion):** Debit WIP / Credit Labor Applied + Machine Applied + Overhead Applied.
3. **Confirmation (scrap):** Debit Scrap Expense / Credit WIP (scrap × unit_cost).
4. **Goods Receipt (standard costing):** Debit FG Inventory (std × qty) + Debit/Credit Variance Acct (diff) / Credit WIP (actual).
5. **Toll GR:** Debit Customer receivable / Credit Toll-service revenue; close conversion-WIP only.

---

## What this layer EXPECTS FROM THE HOST ERP (integration contract)
- **Inventory core:** on-hand & reserved quantities per item/warehouse/bin; decrement on issue, increment FG on receipt; lot/batch & bin tracking; reservation of materials at Release.
- **Costing/valuation service:** `get_cost(material, costing_method)` resolving FIFO/LIFO/Standard/Average unit costs (the layer does NOT compute inventory cost layers itself — it asks Inventory).
- **General Ledger:** ability to post arbitrary journal entries (WIP, Raw Materials, Labor/Machine/Overhead Applied, Scrap Expense, FG Inventory, Variance, Toll revenue/receivable). One JE per actual event.
- **Chart of accounts:** WIP control account (per order: `wip_account`), Raw Materials, FG Inventory, Labor/Machine/Overhead Applied, Scrap Expense, Variance accounts, Toll-service revenue.
- **Sales:** sales orders to peg against (`source_orders[]`), and allocation of received FG quantities back to those customer orders.
- **HR / labor rates:** `labor_rate`, `piece_rate` per operator/work center; `labor_hours` capture.
- **Work centers / resources & tools:** capacity & tool reservation at Release; work-center & tool master to reference.
- **Master Data (file 01):** active BOM and Routing to snapshot at Release; `scrap%`, `bom_qty`, overhead/machine rates.
- **SFC (file 05):** consumes `operation_status`; receives the "mark next operation Ready" signal on Final confirmation.
- **Costing (file 04):** receives the final variance decomposition at Close.
</content>
</invoke>

---


# 04 — Costing Layer (Technical Digest)

> Source: `files/manufacturing_spec/markdown/04_costing.md`. Lossless digest: every entity, field, rule, enum and formula. Inherits all cross-cutting rules from `00` (frozen snapshots, status state machines, "every actual event posts a GL entry", config-over-code, multi-level recursion, resource abstraction).

The costing layer is the "money" layer: it translates all production operations into financial figures and reveals problems. It has **four components**. The most valuable output is **Variance Analysis**.

---

## 4.1 Component: Costing Method

**Purpose:** Strategic decision on how inventory & production are valued. "Set once; hard to change." Configurable **per item** (field `Item.costing_method`).

### Enum
```
costing_method ∈ { Standard, Actual, Average, FIFO }
```

| Method | Philosophy | Suits |
|---|---|---|
| Standard | Pre-set standard cost; difference between actual and standard = variance | Stable mass production |
| Actual | Real cost computed per order | Job shops |
| Average | Moving average cost | Volatile prices |
| FIFO | Oldest batch issued first | Products with shelf life |

### Formulas
```
Average (moving):  new_avg = (existing_qty × old_cost + new_qty × new_cost) / total_qty
FIFO:              issue at oldest batch prices, in order
```

### Standard cost build-up entity
```
Standard_Cost: {
  std_material_cost,
  std_labor_cost,
  std_overhead_cost,
  std_total_cost,        # = sum of the three above
  last_updated,
  update_frequency
}
```

### Generic-design rule — MIXED METHODS (mandatory)
The system MUST support **mixed methods across items**, not one global method:
- Standard for finished products → enables variances + stable inventory valuation.
- Average for volatile raw materials.
- Per-item `costing_method` field is the branch point.

### Melamine test case
- Powder (volatile price) → `Average`.
- Dishes (finished good) → `Standard`.

---

## 4.2 Component: Cost Elements

**Purpose:** Total product cost decomposes into three elements, each with its own source and calculation.

```
Total Cost = Direct Material + Direct Labor + Manufacturing Overhead
```

### Element 1 — Direct Material
```
Direct Material = Σ( BOM_qty × material_price )
source: BOM (quantities) + Material Master (price per the item's costing_method)
GL POST: Debit WIP / Credit Raw Materials
```

### Element 2 — Direct Labor (generalized by `labor_calc` enum)
```
labor_calc ∈ { Hourly, PieceRate }
Hourly:     labor_cost = labor_hours × labor_rate
PieceRate:  labor_cost = quantity × piece_rate
GL POST: Debit WIP / Credit Labor Applied
```
**Critical classification rule:** Only labor whose **pay varies with output** is Direct Labor. Fixed-salary workers (even if standing on the production line) → Overhead.
> Test: "Does this person's pay change with production volume?" Yes → Direct Labor. No → Overhead.

### Element 3 — Manufacturing Overhead (MOH)
```
overhead_rate = total_annual_overhead / total_annual_cost_driver
cost_driver ∈ { LaborHours, MachineHours, DirectMaterialCost, UnitsProduced }
GL POST (at confirmation): Debit WIP / Credit MOH Applied
month-end reconciliation: applied OH vs actual OH → over-applied / under-applied → P&L
```
**Overhead types:**
- Variable OH — electricity, consumables.
- Fixed OH — rent, depreciation, salaries.
- Semi-variable OH — maintenance.

### Generic-design rules
- **Negligible consumables → overhead pool**, NOT BOM lines (e.g. sprayed glaze, tape). (Mirrors `00` rule: tiny-quantity materials treated as overhead consumable, not a BOM line.)
- **Usage-based depreciation for tools** — depreciate by cycles/usage, not by calendar. Only running/mounted tools wear. Depreciation is allocated **per piece per its specific tool**, so expensive/large items carry proportionally more depreciation cost.

### Melamine cost model (illustrative decomposition)
```
piece cost = material (powder + decor if printed)   [direct, varies per unit]
           + press labor (piece_rate)               [direct, varies per unit]
           + everything else (overhead rate)        [fixed pool]
           + that shape's mold depreciation         [usage-based, per tool]
Only material & press-labor vary per piece; all else is overhead.
```

---

## 4.3 Component: Cost Center

**Purpose:** Accounting unit that accumulates costs for a part of the plant. Enables statements like "pressing cost = X, assembly cost = Y."

### Entity
```
Cost_Center: {
  cc_id,
  type ∈ { Production, Service, Auxiliary },
  parent_cc,           # hierarchical / nestable
  manager_id,
  budget_amount,
  actual_amount,
  status
}
```

### Allocation methods (service cost centers → production cost centers)
```
Direct:      service → production only (ignores service-to-service)
Step-Down:   service → other services + production, sequentially (one-directional cascade)
Reciprocal:  all services allocate mutually + to production (handles two-way dependencies)
```

### Generic-design rule
Service costs allocate **by cause**, not evenly. Maintenance of presses → allocate mainly to the pressing center, not split equally (assembly should not bear press maintenance).

### Melamine test case
```
Maintenance 8000, Direct method:
  CC-PRESS    90% → 7200
  CC-FINISH   10% → 800
  CC-ASSEMBLY  0% → 0   (no presses)
```

---

## 4.4 Component: Variance Analysis (THE key output)

**Purpose:** Reveals production problems. `Variance = actual − standard`. The total variance is useless alone; the value is in **decomposition** — each variance type points to a specific problem and a specific owner.

### The seven variances (formulas + owner)
```
1. Material Price Variance    MPV  = (actual_price − std_price) × actual_qty        → Purchasing
2. Material Usage Variance    MUV  = (actual_qty − std_qty)   × std_price           → Production (waste)
3. Labor Rate Variance        LRV  = (actual_rate − std_rate) × actual_hours        → (n/a if piece-rate)
4. Labor Efficiency Variance  LEV  = (actual_hours − std_hours) × std_rate          → Production
5. Variable OH Spending Var.  VOSV = actual_var_OH − (actual_hours × std_VOH_rate)  → Management
6. Variable OH Efficiency Var.VOEV = (actual_hours − std_hours) × std_VOH_rate
7. Fixed OH Volume Variance   FOVV = (actual_production − budgeted) × std_fixed_OH_per_unit
```

### Generic-design rules
- Under **PieceRate**, traditional LRV/LEV do NOT apply (a slow worker only costs himself); the deviation shifts to machine / overhead variances instead.
- **FOVV is critical when fixed costs are large**: lower output spreads fixed cost over fewer units → higher unit cost. This drives the "run at full capacity" management insight.

### Variance tolerance bands
```
< 2%    → Normal — no investigation
2–5%    → Monitor
> 5%    → Investigation Required
```

### Analysis logic (pseudo-code)
```
monthly, for each closed order:
    compute the 7 variances
    for each variance > tolerance:
        classify (material / labor / overhead)
        route to owner
    generate Pareto report (biggest causes first)
```

### Owner mapping (routing output)
```
Material Price   ↑  → Purchasing
Material Usage   ↑  → Production (waste)
Labor Efficiency ↑  → Production
Overhead         ↑  → Management
Volume Variance     → Sales / Management (output dropped)
```

### Joint-products costing (recap from Execution layer)
```
total_sale_value = Σ( grade.qty × grade.price )
grade.cost       = total_cost × (grade.qty × grade.price) / total_sale_value
```
Cost follows **value**, not an equal split. Grade A (priciest) absorbs the most cost per unit.

---

## WIP accounting & period-end settlement (woven through all components)

Per `00` rule "every actual event posts an accounting entry," costing is woven into execution, not a separate batch:
- **Material issue** → Debit WIP / Credit Raw Materials.
- **Labor confirmation** → Debit WIP / Credit Labor Applied.
- **Overhead application at confirmation** → Debit WIP / Credit MOH Applied (using `overhead_rate`).
- **Goods receipt of finished good** → (implied) Credit WIP / Debit Finished Goods at standard or actual cost.
- **Month-end:** applied OH vs actual OH → over-/under-applied variance posted to P&L; the 7 variances computed per closed order and routed to owners.

---

## Enums / config points introduced or used by this file
```
costing_method ∈ { Standard, Actual, Average, FIFO }          (per item)
labor_calc     ∈ { Hourly, PieceRate }                        (drives Direct Labor calc + LRV/LEV applicability)
cost_driver    ∈ { LaborHours, MachineHours, DirectMaterialCost, UnitsProduced }   (overhead_rate denominator)
cc_type        ∈ { Production, Service, Auxiliary }            (Cost_Center.type)
allocation     ∈ { Direct, Step-Down, Reciprocal }            (service → production allocation)
overhead_type  ∈ { Variable, Fixed, Semi-variable }
tolerance band ∈ { <2% Normal, 2–5% Monitor, >5% Investigate }
```

---

## What this layer EXPECTS FROM THE ERP (integration contract)

1. **GL posting engine** for every event: WIP, Raw Materials, Labor Applied, MOH Applied, Finished Goods, over/under-applied OH, variance accounts. (Moon ERP: `Modules\Accounting\Actions\CreateJournalEntry`.)
2. **Inventory valuation engine** that can value the SAME inventory by different methods per item: Standard, Actual, Average (moving), FIFO. (Moon ERP: `InventoryCostLayer` already supports Average/FIFO layers; Standard/Actual + per-item method selection must be confirmed/added.)
3. **Cost center master** with type, hierarchy (`parent_cc`), manager, budget vs actual, status. (Moon ERP: `Modules\Accounting\Models\CostCenter` exists — must confirm it carries `type`, `parent`, budget/actual, and that JE lines can be tagged with a cost center.)
4. **Material Master price** per costing method (raw-material price the BOM cost calc reads).
5. **BOM quantities** (from Master Data / Production module BOM) for Direct Material.
6. **Routing + confirmation actuals**: actual hours, actual qty, labor rate / piece rate (from Execution / SFC).
7. **Standard cost record** per item: `std_material_cost`, `std_labor_cost`, `std_overhead_cost`, `std_total_cost`, `last_updated`, `update_frequency`.
8. **Fiscal calendar / period close** to drive month-end OH reconciliation and per-order variance run. (Moon ERP: `FiscalYear` / `FiscalPeriod`, events `PeriodClosed` / `FiscalYearClosed`.)
9. **Tool/Mold registry** with per-cycle usage counters to drive usage-based depreciation per piece per tool.
10. **Joint-product grade data** (grade.qty, grade.price) from Execution to allocate cost by value.
11. **Budget data** per cost center and budgeted production volume (for FOVV and cost-center variance).

---


# 05 — Shop Floor Control (SFC) — Technical Digest (lossless)

> Source: `files/manufacturing_spec/markdown/05_shop_floor_control.md`. This digest reproduces every entity, field, rule, enum, and formula in the source so an implementer needs no access to the original. Governing principles inherited from `00` (genericity mandate, frozen snapshots, state machines, resource abstraction, config-over-code).

## 5.1 Purpose & Positioning

SFC is a **real-time tracking layer** that cross-cuts the Execution layer. It answers the question: **"where is each order right now?"**

It provides **per-department terminals** where a worker closes an operation, and the order **advances automatically** to the next stage.

Positioning — SFC sits **between Planning and Execution** and connects them in real time:

```
Planning (CRP schedules tentatively)
   ↕
SFC: terminals + operation status + auto-advance + live dashboard
   ↕  reads Routing (the defined sequence)
   ↕  writes Confirmation (actual completion)
Execution (Confirmation, GR — the engine)
```

**Key clarification (do not confuse SFC with Routing):**
- **Routing** = the *defined* sequence; static master data (from file 01).
- **SFC** = the *real-time execution* of that sequence. It *uses* Routing to know "what is the next operation" and dispatches the order to that department's terminal.

The execution engine **already exists** (Confirmation, Routing, `Order_Operation` status). What SFC **adds** is:
1. the real-time interface,
2. the automatic advance between terminals,
3. live monitoring.

## 5.2 The Core Mechanism

```
Each department          = a Work Center Terminal (a screen).
Worker closes a stage    = Operation Confirmation (engine exists).
Order moves to next stage= Routing Step Advance (auto).
Everyone sees status live = Real-time Shop Floor Monitoring.
```

### Walkthrough (example routing: Press → Deflash → Sand → Grade)

```
WO-001 routing: Press → Deflash → Sand → Grade

[Press terminal] incoming: WO-001
  worker taps [Start] → operation[10].status = InProgress, actual_start = now
  worker taps [Done]  → record qty; operation[10].status = Completed
       system reads Routing → next op = Deflash
       operation[20].status = Ready ; notify Deflash terminal
[Deflash terminal] WO-001 appears automatically ← the "advance"
  ... and so on to Grading (last op) → order Completed → ready for GR
```

The "advance" = on `Done`, the system consults **Routing** for the next operation and dispatches the order to the responsible department's terminal automatically.

Operation numbers in the example increment by 10 (operation[10], [20], [30] …) — the conventional routing step numbering.

## 5.3 Key Entity — Operation Status (live)

**This entity already exists** on `Order_Operation` (defined in file 03). SFC **activates its real-time tracking** — it does not introduce a new master table.

### `Order_Operation` fields (as used by SFC)
| Field | Notes |
|---|---|
| `order_no` | the production order this operation belongs to |
| `operation_no` | step number within the routing (e.g. 10, 20, 30) |
| `work_center_id` | which work center / department executes it |
| `tool_id` | the tool/mold resource attached to this operation |
| `status` | enum ∈ { Waiting, Ready, InProgress, Completed } |
| `actual_start_time` | stamped when worker taps [Start] |
| `actual_end_time` | stamped when worker taps [Done] |
| `confirmed_by` | the worker who closed the operation |
| `quantity_completed` | quantity recorded at confirmation |

### Enum — `operation_status`
```
operation_status ∈ { Waiting, Ready, InProgress, Completed }
```
(Also declared as a master enum in `00` §0.5 / §0.2.)

Semantics:
- **Waiting** — predecessor not yet complete; the operation is not actionable on its terminal.
- **Ready** — predecessor completed; operation now appears in the terminal's incoming queue and can be Started.
- **InProgress** — worker tapped [Start]; `actual_start_time` set.
- **Completed** — worker tapped [Done]; `actual_end_time` set, confirmation recorded.

### Advance logic (pseudo-code, language-agnostic intent)
```
on tap "Done" for operation N:
  operation[N].status = Completed
  operation[N].end_time = now
  record confirmation (qty, grades, scrap) → calls Execution engine (file 03)
  next_op = routing.get_next(N)          # ← reads Routing
  if next_op:
      operation[next_op].status = Ready
      notify(next_op.work_center)        # appears on that terminal
  else:
      order.status = Completed           # last op → ready for GR
```

Bridge relationship:
- `routing.get_next(N)` — **Routing** answers "what's next?"
- **Operation Status** records "where we are."

Note: the confirmation recorded on Done carries **qty, grades, scrap** — i.e. yield as a distribution (grades A/B/C) plus a scrap quantity, consistent with the joint-products / grade-distribution rule from `00`.

## 5.4 Terminal UI (per department)

A **minimal worker-facing screen per Work Center**:

- **Incoming orders queue** — orders with status `Ready`, **sorted by priority/schedule**.
- For the **active order**: `[Start]` / `[Done]` buttons.
- **On Done**: prompt for **quantity**, **grade distribution**, **scrap + reason_code**.
- **Shows**: order no, product, target qty, **this operation only**.
- **Sequence enforcement**: an operation appears **only after the prior one is Completed**.

Design constraints for floor use:
- large touch targets,
- minimal typing,
- **offline-tolerant if possible** (sync when connected).

## 5.5 Real-time Dashboard (supervisor)

A **live board** showing each order's current operation and progress, e.g.:
```
WO-001: Sanding (Op 30) — 60% done
WO-002: Pressing (Op 10) — just started
WO-003: Waiting for press (mold busy)
```

**Live bottleneck detection:** many orders `Waiting` before a resource → that resource is the **live constraint**. This **links to CRP** (file 02) but is **real-time** (CRP is the planned/tentative view; the dashboard is the actual current view).

## 5.6 What SFC Affects — the 5 impacts

1. **Real-time visibility** — management knows exactly where each order is, no asking workers.
2. **Auto-feeds Confirmation** — each "Done" creates an **immediate confirmation** (qty + time), making cost data **live & accurate** (no end-of-day manual entry).
3. **Live bottleneck detection** — many "Waiting" before a resource exposes the current constraint.
4. **Actual operation times** — start/end per operation feeds **OEE**, routing-time refinement, and **time variances (actual vs planned)**.
5. **Sequence enforcement** — an operation can't appear before its predecessor completes → quality & process integrity.

## 5.7 Build Note

- **~80% of the logic pre-exists**: Routing defined (file 01), `Order_Operation` status present (file 03), Confirmation engine ready (file 03).
- The build is: **real-time interface + automatic terminal-to-terminal advance + live dashboard** — a layer **ON TOP of execution**, not a rebuild.
- This is the **MES-level functionality**.
- **Optional integrations for a fuller MES** (named, NOT specified here): machine data capture (IoT), **OEE computation**, barcode/RFID scanning.

---

## OEE note (what the file actually says)

OEE is **referenced twice but never defined/formulated** in this file:
- §5.6 impact #4: actual start/end times "feeds OEE."
- §5.7: OEE computation listed as an **optional** fuller-MES integration.

There is **no OEE formula, no availability/performance/quality factor definition** in this spec part. (Confirmed also a "Future phase" in `00` §0.7.) An implementer must NOT assume OEE is in scope of the SFC build — only that actual times are captured to enable a future OEE calculation.

---

## Entities / contracts this part defines or activates

- `Order_Operation` (live status surface) — reused from file 03; SFC adds real-time semantics and the four-state machine.
- **Work Center Terminal** (a screen/UI per Work Center) — new UI surface keyed by `work_center_id`.
- **Real-time Shop Floor Monitoring / Dashboard** — supervisor live board with per-order current-operation + progress %.
- **Auto-advance contract**: on Done → record confirmation (calls Execution engine, file 03) → `routing.get_next(N)` (reads Routing, file 01) → set next op `Ready` + notify its work center, OR mark order `Completed`.
- **Confirmation capture contract** at terminal: `{ quantity, grade_distribution, scrap_qty, scrap_reason_code, confirmed_by, actual_start_time, actual_end_time }`.

## Explicitly "expects from ERP" (dependencies SFC consumes, does not build)

1. **Routing** master data with an ordered sequence and a `get_next(operation_no)` lookup (file 01 / Master Data).
2. **`Order_Operation`** records pre-generated per production order with `work_center_id`, `tool_id`, and `operation_no` (file 03 / Execution).
3. **Confirmation engine** (file 03) to receive each Done event and post qty/time + the implied GL/inventory side-effects (file 03 + `00` §0.4 rule 6: every actual event posts an accounting entry).
4. **Work Center** master records (file 01) so each terminal maps to a department.
5. **CRP** (file 02) for the planned-capacity view that the live bottleneck view contrasts against.
6. **Goods Receipt (GR)** trigger when the last operation completes and order status flips to Completed (file 03).
7. **Priority/schedule** data on orders to sort the Ready queue (from Planning).
8. **Notification mechanism** to push a Ready order onto the next work center's terminal in real time.
</content>
</invoke>

---


# 06 — Integrations & Consolidated Data Model (Technical Digest)

> Lossless English digest of `files/manufacturing_spec/markdown/06_integrations_and_data_model.md`.
> An implementer must not need the original file. Governing principles inherited from spec file `00`
> (genericity mandate, frozen snapshots, state machines, many-to-many demand↔supply, multi-level
> recursion, config-over-code, real-time GL postings, resource abstraction).

---

## 6.1 Integration Map — "Manufacturing is NOT an island"

The Manufacturing module integrates with modules that already exist in the host ERP. It must NOT
rebuild them. Out-of-scope (integrate, do not rebuild): General Ledger, Inventory/Warehouse core,
Procurement core, Sales orders, HR/payroll.

ASCII map:
```
                    ┌──────────────┐
       Sales Order →│              │→ Procurement (Planned POs from MRP)
                    │ MANUFACTURING │
        Inventory  ↔│   MODULE      │↔ General Ledger (all postings)
   (RM, WIP, FG)    │              │
                    └──────────────┘
                       ↕ HR (labor rates / piece-rate payroll)
```

### Integration points (by direction)

| External module | Direction | What flows |
|---|---|---|
| **Sales** | → Mfg | Customer orders trigger MTO/Toll production; promised dates returned to Sales |
| **Procurement** | ← Mfg | MRP emits Planned Purchase Orders; receipts update material availability |
| **Inventory** | ↔ Mfg | Material reservations; RM→WIP→FG movements; batch/lot; consignment (customer-owned) stock |
| **General Ledger** | ← Mfg | Every issue/confirmation/GR posts journal entries (WIP, applied labor/machine/OH, variances) |
| **HR** | → Mfg | Labor rates supplied to Mfg; piece-rate quantities feed back to payroll |

### Key integration rules (verbatim intent)

1. **Consignment (toll):** customer-owned material lives in a **separate inventory bucket**
   (`ownership = Customer`). It is **never valued as own asset**, and **never triggers a purchase**.
2. **GL postings are real-time** — woven into execution events, NOT a nightly batch.
3. **Reservations** in Inventory are created at order **Release**, and consumed at **Material Issue**.

---

## 6.2 Consolidated Relational Data Model

Every entity in the module with keys and relationships. Types are indicative; implement per host ERP
conventions. Grouped by layer. `PK` = primary key, `FK→X` = foreign key to entity X.

### MASTER DATA

**Item** *(host ERP, referenced — NOT owned by Mfg)*
- `item_id` PK, `name`, `uom`, `procurement_type`, `material_ownership`, `costing_method`,
  `lead_time_days`, `safety_stock`, … (extensible)

**BOM_Header**
- `bom_id` PK, `parent_item_id` FK→Item, `bom_type`, `version`, `base_quantity`, `uom`, `status`,
  `effective_from`, `effective_to`, `created_by`, `approved_by`

**BOM_Line** *(BOM_Header 1—* BOM_Line)*
- `bom_id` FK→BOM_Header, `line_no`, `component_id` FK→Item, `quantity`, `uom`,
  `scrap_percentage`, `issue_type`, `issue_level`, `operation_seq`, `substitute_items`

**Routing_Header**
- `routing_id` PK, `item_id` FK→Item, `routing_type`, `version`, `lot_size_from`, `lot_size_to`,
  `status`, `effective_from`, `effective_to`, `total_lead_time`

**Routing_Operation** *(Routing_Header 1—* Routing_Operation)*
- `routing_id` FK→Routing_Header, `operation_no`, `description`, `work_center_id` FK→Work_Center,
  `tool_id` FK→Tool, `setup_time`, `run_time_per_unit`, `cavity_count`, `cycle_time`,
  `queue_time`, `move_time`, `inspection_required`, `critical_operation`, `required_skill_code`

**Work_Center**
- `wc_id` PK, `description`, `category`, `plant_location`, `cost_center_id` FK→Cost_Center,
  `capacity_unit`, `daily_capacity`, `capacity_multiplier`, `efficiency_percent`,
  `utilization_percent`, `calendar_id`, `setup_cost_rate`, `labor_cost_rate`,
  `machine_cost_rate`, `overhead_rate`, `labor_calc`, `bottleneck_flag`

**Tool** *(a Resource with resource_type = Tool)*
- `tool_id` PK, `description`, `product_id` FK→Item, `cavity_count`, `setup_time`, `status`,
  `life_cycles`, `cycles_used`, `purchase_cost`

### PLANNING

**MPS_Header**
- `id` PK, `plan_name`, `plant_id`, `time_bucket`, `start_date`, `end_date`, `frozen_fence`,
  `slushy_fence`, `status`

**MPS_Line**
- `mps_id` FK→MPS_Header, `product_id` FK→Item, `period_date`, `forecast_qty`,
  `confirmed_orders_qty`, `planned_production`, `projected_balance`, `safety_stock_target`,
  `available_to_promise`

**Item_MRP_Settings**
- `item_id` FK→Item, `mrp_type`, `procurement_type`, `material_ownership`, `lot_sizing_rule`,
  `lot_size`, `min_lot`, `max_lot`, `safety_stock`, `lead_time_days`, `reorder_point`

**MRP_Planned_Order** *(transient — regenerated each MRP run)*
- `item_id` FK→Item, `order_type`, `quantity`, `required_date`, `release_date`,
  `source_demand_id`, `mrp_run_id`

**CRP_Load** *(output of capacity check)*
- `resource_id`, `resource_type`, `period`, `required_load`, `available_capacity`,
  `utilization_pct`, `status`

### EXECUTION

**Production_Order_Header**
- `order_no` PK, `order_type`, `product_id` FK→Item, `quantity`, `production_type`,
  `material_ownership`, `start_date`, `finish_date`, `status`, `bom_snapshot_id`,
  `routing_snapshot_id`, `tool_id` FK→Tool, `wip_account`

**Order_Component**
- `order_no` FK→PO_Header, `component_id` FK→Item, `required_quantity`, `issued_quantity`,
  `reserved_quantity`, `operation_seq`

**Order_Operation**
- `order_no` FK→PO_Header, `operation_no`, `work_center_id` FK→Work_Center, `tool_id` FK→Tool,
  `planned_setup_time`, `planned_run_time`, `actual_setup_time`, `actual_run_time`, `status`,
  `actual_start_time`, `actual_end_time`, `confirmed_by`, `confirmed_quantity`, `scrap_quantity`
- `status ∈ {Waiting, Ready, InProgress, Completed}` — driven by the SFC layer

**Material_Issue_Header**
- `issue_no` PK, `issue_date`, `issue_type`, `issue_level`, `order_no` FK→PO_Header,
  `operation_no`, `issuer_id`, `warehouse_from`, `status`

**Material_Issue_Line**
- `issue_no` FK→MI_Header, `material_code` FK→Item, `quantity`, `batch_no`, `cost_price`,
  `total_cost`, `bin_location`, `ownership`

**Confirmation_Header**
- `confirmation_no` PK, `order_no` FK→PO_Header, `operation_no`, `confirmation_type`,
  `operator_id`, `work_center_id`, `shift`, `timestamp`

**Confirmation_Yield** *(grade distribution — yield is a distribution, not a scalar)*
- `confirmation_no` FK→Conf_Header, `grade_code`, `quantity`, `unit_sale_price`

**Confirmation_Detail**
- `confirmation_no` FK→Conf_Header, `scrap_quantity`, `rework_quantity`, `setup_time_actual`,
  `run_time_actual`, `labor_hours`, `machine_hours`, `reason_code`

**Goods_Receipt_Header**
- `gr_no` PK, `gr_date`, `order_no` FK→PO_Header, `receipt_type`, `warehouse_to`,
  `receiver_id`, `status`

**Goods_Receipt_Line** *(per grade)*
- `gr_no` FK→GR_Header, `item_id` FK→Item, `received_quantity`, `grade_code`, `batch_no`,
  `bin_location`, `cost_per_unit`, `quality_status`

### COSTING

**Standard_Cost**
- `item_id` FK→Item, `std_material_cost`, `std_labor_cost`, `std_overhead_cost`,
  `std_total_cost`, `last_updated`, `update_frequency`

**Cost_Center**
- `cc_id` PK, `type`, `parent_cc` FK→Cost_Center *(recursive hierarchy)*, `manager_id`,
  `budget_amount`, `actual_amount`, `status`

**Variance_Record**
- `order_no` FK→PO_Header, `variance_type`, `amount`, `percentage`, `classification`,
  `owner`, `investigation_flag`

### CROSS-CUTTING

**Pegging** *(Many-to-Many demand↔supply)*
- `production_order_id` FK→PO_Header, `sales_order_id` FK→SalesOrder, `allocated_quantity`

**Delivery_Schedule** *(order splitting)*
- `schedule_id` PK, `parent_order_id` FK→SalesOrder, `quantity`, `scheduled_date`, `status`,
  `linked_production_order` FK→PO_Header

---

## 6.3 Relationship Summary (key cardinalities)

```
Item 1—* BOM_Header 1—* BOM_Line *—1 Item (component)   [recursive multi-level]
Item 1—* Routing_Header 1—* Routing_Operation *—1 Work_Center
Routing_Operation *—1 Tool
Work_Center *—1 Cost_Center
Sales_Order *—* Production_Order   (via Pegging)
Sales_Order 1—* Delivery_Schedule
Production_Order 1—* Order_Component / Order_Operation
Production_Order 1—* Material_Issue 1—* Material_Issue_Line
Production_Order 1—* Confirmation 1—* Confirmation_Yield   (grades)
Production_Order 1—* Goods_Receipt 1—* Goods_Receipt_Line  (per grade)
Production_Order 1—* Variance_Record
```

---

## 6.4 Build Order (recommended for the AI builder — each step depends on the prior)

1. Master Data entities + CRUD (BOM recursive, Routing, WC, Tool)
2. Item_MRP_Settings + Standard_Cost
3. MPS (forecast consumption, time fences, CTP)
4. MRP (recursive explosion, lot sizing, toll branch)
5. CRP (resource intersection, overload detection)
6. Production Order (snapshot, state machine, order network)
7. Material Issue (3 types, 3 levels, consignment)
8. Confirmation (grade distribution, piece-rate, postings)
9. Goods Receipt (multi-grade, variance gate, toll branch)
10. Costing (methods, elements, cost centers, 7 variances)
11. SFC (operation-status real-time, terminal UI, auto-advance, dashboard)
12. Integrations (Sales, Procurement, Inventory, GL, HR)

Honour the genericity mandate (file 00 §0.2) throughout.

---

## 6.5 Future Phases (identified, NOT specified in this build)

| Component | Interface to current build |
|---|---|
| APS (detailed scheduling) | consumes Planned Orders + Resource calendars → time-phased schedule for SFC |
| Quality Management | hooks Confirmation grades/reason_codes; inspection gates on operations |
| Tool/Mold Management | extends Tool entity: lifecycle, maintenance, cycle tracking, replacement alerts |
| Maintenance (PM) | reduces Work_Center capacity during downtime windows |
| OEE / Dashboards | consumes Order_Operation actual times + scrap |
| PLM / ECO | governs BOM/Routing change orders (formalizes the Snapshot concept) |

---

## Enums referenced by this data model (defined in spec file 00 §0.5)

```
production_type     ∈ { MakeToStock, MakeToOrder, AssembleToOrder, TollManufacturing }
material_ownership  ∈ { Own, Customer }
procurement_type    ∈ { Buy, Make }
costing_method      ∈ { Standard, Actual, Average, FIFO }
labor_calc          ∈ { Hourly, PieceRate }
issue_type          ∈ { Manual, Backflush, AutoIssue }
issue_level         ∈ { PerOrder, PerShift, PerOperation }
resource_type       ∈ { Machine, Tool, Labor }
lot_sizing_rule     ∈ { Exact, Fixed, MinMax, EOQ, PeriodOrder }
order_status        ∈ { Planned, Released, InProcess, Completed, Closed }
operation_status    ∈ { Waiting, Ready, InProgress, Completed }
```

---

## "Expects from ERP" — explicit contract this spec part demands from the host

| # | Host module | Contract the Manufacturing module requires |
|---|---|---|
| 1 | **Item master** (Core/Inventory) | `Item` with `uom`, `procurement_type`, `material_ownership`, `costing_method`, `lead_time_days`, `safety_stock`. Mfg references it; never owns it. |
| 2 | **Inventory** | A `Material reservation` API (create at Release, consume at Issue). |
| 3 | **Inventory** | A **separate ownership bucket** so `ownership = Customer` (consignment) stock is held but NOT valued and NOT purchasable. |
| 4 | **Inventory** | `batch_no` / lot and `bin_location` tracking on movements. |
| 5 | **Inventory** | RM→WIP→FG stock movements driven by Issue / Confirmation / GR. |
| 6 | **Procurement** | Ability to consume MRP `Planned Purchase Orders` and feed receipts back to availability. |
| 7 | **Sales** | Sales Orders as a demand source; ability to return promised/ATP dates; a `SalesOrder` entity for Pegging and Delivery_Schedule FKs. |
| 8 | **General Ledger** | **Real-time** journal posting on every issue/confirmation/GR: WIP debit/credit, applied labor / machine / overhead, and variance accounts. `wip_account` per order. |
| 9 | **HR / Payroll** | Supply labor cost rates to Mfg; receive piece-rate confirmed quantities back for payroll. |
| 10 | **Cost Center** master | A (recursive) `Cost_Center` master that Work_Centers map to (may be owned by GL/Accounting or by Mfg — spec leaves owner ambiguous). |
| 11 | **Calendar** | A `calendar_id` resource calendar driving Work_Center available capacity (consumed by CRP / future APS). |

---


# Production Module — Current State (Moon ERP)

Deep map of `Modules/Production` (backend at `/home/moonui/moon-erp-be/Modules/Production`) and its Angular FE (`/home/moonui/public_html/moon-erp/src/app/features/production`). Read-only audit. Inferences marked **(inference)**.

## 1. Summary verdict

The Production module is a clean but shallow **MVP nucleus**. It implements masters (work centers, BOMs) and a basic production-order lifecycle with **book-only costing**. It does **NOT** post to Inventory stock or Accounting GL, emits **no domain events**, has **no Actions/Services** layer, **no tests**, an **empty seeder**, and is **referenced by no other module**. It follows the house recipe structurally (BaseModel/company_id, branch scoping columns, SequenceService numbering, permissions registered in Core, full Angular FE).

**Recommendation: EXTEND in place, do not rebuild.** Migration risk is near-zero — no production data, no inbound FKs **(inference)**.

## 2. Tables & columns

Three defensive migrations, all guarded by `if (! Schema::hasTable(...))`:
- `database/migrations/2026_03_31_000001_create_production_tables.php` (Blueprint, with FK constraints in try/catch)
- `database/migrations/2026_03_31_000002_ensure_production_tables_exist.php` (Blueprint, nullable variants, no FKs)
- `database/migrations/2026_03_31_000003_force_create_production_tables.php` (raw `CREATE TABLE` MySQL, skips sqlite)

The triple-migration + try/catch FK pattern indicates prior migration-ordering failures **(inference)**.

### `production_centers` (000001 L12-37)
`id`, `company_id` FK→companies, `branch_id` FK→branches nullable, `code(50)`, `name`, `name_ar`, `name_en`, `type(20)` default `machine` (000002/000003 default `mixed`), `capacity_per_hour(12,3)`, `cost_per_hour(12,3)`, `overhead_rate(8,4)`, `account_id` (FK→accounts, **unused in any posting**), `is_active`, `notes`, timestamps, softDeletes. Unique `(company_id, code)`.

### `bill_of_materials` (000001 L40-82)
`id`, `company_id` FK, `product_id` FK→products, `product_variant_id` nullable, `code(50)`, `name`/`name_ar`/`name_en`, `version(20)` default `1.0`, `quantity(12,3)` default 1, `unit_id` FK→units, `is_active`, `is_default`, `standard_cost(12,3)`, `overhead_cost(12,3)`, `notes`, `created_by`, timestamps, softDeletes. Unique `(company_id, code)`.

### `bom_components` (000001 L84-110)
`id`, `bom_id` FK→bill_of_materials cascade, `product_id` FK, `product_variant_id`, `quantity(12,3)`, `unit_id` FK, `waste_percentage(8,4)`, `cost_per_unit(12,3)`, `sort_order`, `notes`, timestamps.

### `bom_operations` (000001 L112-127)
`id`, `bom_id` FK, `production_center_id` FK→production_centers, `name`/`name_ar`/`name_en`, `sequence`, `setup_time_minutes(8,2)`, `run_time_minutes(8,2)`, `labor_cost_per_hour(12,3)`, `notes`, timestamps.

### `production_orders` (000001 L129-192)
`id`, `company_id` FK, `branch_id` FK nullable, `order_number` unique, `bom_id` FK nullable, `product_id` FK, `product_variant_id`, `production_center_id` FK nullable, `source_warehouse_id` (FK→warehouses, declared but no stock movement uses it), `target_warehouse_id` (same), `planned_quantity`, `produced_quantity`, `scrap_quantity`, `unit_id` FK, `planned/actual_start_date`, `planned/actual_end_date`, `status(20)` default `draft`, `priority` default `normal`, `planned_material_cost`/`planned_labor_cost`/`planned_overhead_cost`, `actual_material_cost`/`actual_labor_cost`/`actual_overhead_cost`, `notes`, `created_by`, timestamps, softDeletes.

### `production_order_materials` (000001 L194-220)
`id`, `production_order_id` FK cascade, `product_id` FK, `product_variant_id`, `planned_quantity`, `consumed_quantity`, `unit_id` FK, `planned_cost`, `actual_cost`, `sort_order`, timestamps.

### `production_order_operations` (000001 L222-240)
`id`, `production_order_id` FK cascade, `production_center_id` FK, `name`, `sequence`, `planned/actual_setup_time(8,2)`, `planned/actual_run_time(8,2)`, `planned/actual_cost`, `status(20)` default `pending`, `started_at`, `completed_at`, timestamps.

## 3. Enums (state machines)

- `ProductionOrderStatus` (`app/Enums/ProductionOrderStatus.php`): `draft → confirmed → in_progress → completed`, plus `cancelled`. Guards: `isEditable`/`canConfirm` (Draft), `canStart` (Confirmed), `canComplete` (InProgress), `canCancel` (Draft|Confirmed). Cast on model L54.
- `BomStatus` (`active`/`inactive`/`draft`) — **declared but NOT cast/used** anywhere; BOM state is the boolean `is_active` column instead.
- `ProductionCenterType` (`machine`/`labor`/`mixed`).

## 4. Order lifecycle (controller logic)

`app/Http/Controllers/ProductionOrderController.php`:
- `store` (L95): numbers via `SequenceService->generateNext(companyId,'production','order')` (L108); if `bom_id` and no materials passed → `populateFromBom` (L435) which scales BOM component qty by `planned_quantity/bom.quantity` ratio with waste %, and computes planned material/labor costs (L473-476).
- `confirm` (L204) → `Confirmed`; `start` (L226) → `InProgress` + actual_start_date; `complete` (L251) → `Completed`, recomputes actual costs from `materials.actual_cost ?: planned_cost` and `operations.actual_cost ?: planned_cost` (L262-263); `cancel` (L286) → `Cancelled`.
- `consume` (L311): in-progress only; increments `consumed_quantity`/`actual_cost` on order materials, re-sums `actual_material_cost`. **No stock movement.**
- `recordOutput` (L354): in-progress only; increments `produced_quantity`/`scrap_quantity`. **No stock receipt.**
- `costing` (L384): returns planned/actual/variance/yield/unit-cost JSON. Pure read; no GL.

BOM controller (`BomController.php`) adds `syncComponents`/`syncOperations`/`duplicate`.

## 5. Stock & accounting integration — NONE

`grep -niE "StockMovement|InventoryService|stock|CreateJournalEntry|JournalEntry|Accounting"` over `Modules/Production/app` → **0 hits**. No inventory deduction on consume, no finished-goods receipt on output, no journal entry on completion. `production_centers.account_id` and order `source/target_warehouse_id` are dead columns **(inference)**.

## 6. Permissions

All 19 registered in `Modules/Core/database/seeders/RolePermissionSeeder.php` L709-727 (under a `// ── Production ──` block) and enforced as `permission:` middleware in controllers, fully matching Angular `permissionGuard` data:
`production.centers.{view,create,update,delete}`, `production.boms.{view,create,update,delete}`, `production.orders.{view,create,update,delete,confirm,start,cancel}`, `production.orders.complete` (also gates `recordOutput`), `production.consume`, `production.costing`, `production.reports.view`. No gaps found between controllers and seeder.

## 7. Maturity / usage

- **Seeder** `database/seeders/ProductionDatabaseSeeder.php`: empty (`// $this->call([]);`). No master/demo data.
- **Tests**: `tests/Feature` and `tests/Unit` contain only `.gitkeep`. Zero tests.
- **Events/Listeners/Actions/Services**: none. `EventServiceProvider` `$listen = []`.
- **Cross-module references**: `grep` for `Modules\Production`/`production_orders`/`BillOfMaterials` across other modules → **0 hits**. No module consumes Production.
- **Factories** exist for BillOfMaterials, ProductionCenter, ProductionOrder (test scaffolding only).

## 8. Angular FE

`src/app/features/production/{centers,boms,orders,reports}/` — 4 standalone components (ts+html+scss each). Services: `core/services/{production-order,bom,production-center}.service.ts`; models: `core/models/{bom,production-center,production-order}.model.ts`. Routes `app.routes.ts` L227-233 under `path:'production'` with `permissionGuard`. Nav `core/config/nav-items.config.ts` L316-327. The order service (`production-order.service.ts`) exposes confirm/start/complete/cancel/consume/costing + 3 reports — full parity with the BE API.

## 9. Spec coverage matrix

| Spec concept | State | Evidence |
|---|---|---|
| BOM versions | Partial | `version` string only; no versioning logic |
| Routing/operations | Partial | bom_operations + order_operations w/ sequence & times |
| Work centers | Present | production_centers (type/capacity/cost/overhead) |
| Tools/molds | Missing | no tables |
| MRP | Missing | no planning/requirements |
| Confirmations/shop floor | Partial | consume + record-output only |
| Costing | Partial | planned/actual/variance, book-only (no GL) |
| Stock posting | Missing | no StockMovement on consume/output |

## 10. Recommendation: EXTEND in place

Reuse existing tables, the 19-permission map, the order state machine, and the Angular FE. Add the missing layers per the house recipe: inventory posting (issue components, receive finished goods), GL via `Modules\Accounting\Actions\CreateJournalEntry`, domain events + listeners, a versioning layer, MRP, and tools/molds. Rebuild is unwarranted — nothing is in production, no inbound FKs, near-zero migration risk **(inference)**.

---


# 11 — Inventory & Purchases: Manufacturing's Most-Coupled Neighbors

Technical mapping of `Modules\Inventory` and `Modules\Purchases` (Moon ERP backend at
`/home/moonui/moon-erp-be`) as the foundation a Manufacturing module must build on. All
citations are `file:line`. Inferences are marked **(inference)**.

---

## 1. Item / Product Master (lives in Core, NOT Inventory)

The product master is owned by `Modules\Core`, shared by every module. Inventory and
Purchases only reference `products` / `product_variants` by FK.

| Concern | Where | Citation |
|---|---|---|
| Product table | `products` | `Modules/Core/database/migrations/2026_02_16_300001_create_products_table.php:11` |
| Variants | `product_variants` | `Modules/Core/database/migrations/2026_02_16_300003_create_product_variants_table.php` |
| Variant flag | `has_variants` column | `Modules/Core/database/migrations/2026_02_19_000003_add_has_variants_to_products_table.php` |
| Tracking type cast | `Product::$casts['tracking_type'] => ProductTrackingType::class` | `Modules/Core/app/Models/Product.php:73` |

**Key product columns** (`...300001...products_table.php:14-40`): `code`, `sku`, `name`,
`name_ar`, `type` (default `product`), `status`, `product_category_id`, `barcode`, `brand`,
`base_unit_id` (→ `units`), `purchase_price`, `sale_price`, `cost_method` (per-product
costing override, nullable, line 30), `track_inventory` (line 32), and the
**reorder fields**: `min_stock_level`, `max_stock_level`, `reorder_point` (lines 33-35).

**`ProductType` enum has only `Product` and `Service`** — there is NO
raw-material / WIP / finished-good / by-product classification
(`Modules/Core/app/Enums/ProductType.php:7-8`). **Gap input:** Manufacturing's
RM/WIP/FG/co-product/by-product distinction, `procurement_type` (make vs buy), and
`material_ownership` (own vs customer/consignment) do not exist and must be added (likely as
new columns/enum on `products` or a Manufacturing-side item-settings table). **(inference)**

### UoM model + conversions — STRONG, reusable
- `unit_groups` → `units` (`Modules/Core/database/migrations/2026_02_16_100002...` and
  `...100003_create_units_table.php`). Each `units` row has `unit_group_id`, `symbol`,
  `conversion_factor` (decimal 15,6), `is_base` (`...units_table.php:14-22`).
- Per-product unit conversions: `product_units` with `conversion_factor` (decimal 15,6),
  `is_purchase`, `is_sale` flags, plus per-unit barcode/prices
  (`Modules/Core/database/migrations/2026_02_16_300002_create_product_units_table.php:16-21`).
- Product base unit: `products.base_unit_id` (`...products_table.php:26`).

Conclusion: full UoM + conversion infrastructure exists and Manufacturing BOM lines /
routing can reuse `units` + `product_units` directly. BOM/issue/receipt rows already carry
`unit_id` (see inventory item tables below).

### Batch / Lot / Serial support — PARTIAL (key gap input)
- `ProductTrackingType` enum: `None`, `Batch`, `Serial`
  (`Modules/Core/app/Enums/ProductTrackingType.php:7-9`).
- `tracking_type` column added at **app level**, not in the Core module:
  `database/migrations/2026_02_23_102407_add_tracking_type_to_products_table.php`.
- **Serial** is fully modelled: `product_serials` table
  (`database/migrations/2026_02_23_102436_create_product_serials_table.php:15`) with
  `serial_number`, `batch_number`, `expiry_date`, `status`, `cost`, `warehouse_id`,
  `reference_type/id`. Serial lifecycle (`SerialStatus`: available → sold) is enforced in
  `ApproveReceipt` (creates serials, `...ApproveReceipt.php:62-77`) and `ApproveIssue`
  (validates + marks Sold, `...ApproveIssue.php:41-87`).
- **Batch/lot is NOT a first-class entity.** There is no `batches` master table and no batch
  balance. `batch_number` / `expiry_date` are merely free-text string columns on
  receipt/issue items (`inventory_receipt_items.batch_number/expiry_date`,
  `...200002...:20-21`; `inventory_issue_items.batch_number`, `...200006...:20`) and on GRN
  items (`...300002_create_purchase_grn_items_table.php:31-33`). Stock balances and cost
  layers are keyed by `(product, variant, warehouse)` only — **NOT by batch**
  (`...200003_create_inventory_stock_balances_table.php:22`,
  `...400005_create_inventory_cost_layers_table.php:29`).

**Gap input:** Manufacturing needs true batch/lot genealogy (which RM lots were consumed into
which FG lot, expiry-driven FEFO issue). The current model cannot trace batch consumption or
hold batch-level quantities/costs. A batch-balance table + batch-aware StockService would be
required. **(inference)**

---

## 2. Warehouses / Locations

Single-level `warehouses` table (`Modules/Inventory/database/migrations/2026_02_22_100001_create_warehouses_table.php:11`):
- `company_id`, `branch_id` (branch scoping), `code`, `name`/`name_ar`, `type` (default
  `main`; see `WarehouseType` enum), `parent_warehouse_id` (self-reference, line 19),
  `manager_id`, `is_active`, **`allow_negative_stock`** (line 24), and
  **`account_id`** → Accounting `Account` (line 25; relation at
  `Modules/Inventory/app/Models/Warehouse.php:76-79`).

**No bin / location / sub-warehouse-slot model below the warehouse** (only
`parent_warehouse_id` hierarchy). **Gap input:** Manufacturing shop-floor staging
locations, WIP locations, and line-side supermarkets would map onto separate `warehouses`
rows (or need a new bin model). RM / WIP / FG buckets are most cheaply modelled as
distinct warehouses with `type`. **(inference)**

---

## 3. StockService — the central stock API

`Modules/Inventory/app/Services/StockService.php`. Constructor injects
`Core\Services\SettingsService` (`:13`). Public API:

| Method | Signature | Purpose | Citation |
|---|---|---|---|
| `increaseStock(array $data): StockBalance` | data: company_id, product_id, product_variant_id, warehouse_id, quantity, unit_cost, movement_type, reference_type, reference_id, date, notes | Adds qty, recomputes WAC, **always writes a FIFO cost layer**, writes an `inventory_movements` row | `:35-93` |
| `decreaseStock(array $data): StockBalance` | same shape | Removes qty; FIFO mode consumes oldest layers, WAC mode uses passed unit_cost; writes movement | `:113-169` |
| `getIssueCost(companyId, productId, variantId, warehouseId, quantity): {unit_cost,total_cost}` | — | Preview issue cost without consuming (FIFO peek or WAC avg) | `:177-196` |
| `getProductCost(companyId, productId, variantId=null, warehouseId=null): float` | — | Current unit cost (oldest FIFO layer or WAC avg) | `:201-232` |
| `getValuationMethod(companyId): string` | — | Reads setting `inventory.valuation_method`, default `weighted_avg` | `:237-240` |

Private: `consumeFifoLayers` (`:245`), `calculateFifoCost` (peek, `:279`),
`getOrCreateBalance` (`:308`).

### Movement types
`MovementType` enum (`Modules/Inventory/app/Enums/MovementType.php:7-13`):
`receipt`, `issue`, `transfer_in`, `transfer_out`, `adjustment`, `opening`, `return`.
**No manufacturing-specific types** (no `production_issue`, `production_receipt`,
`wip_in/out`, `scrap`, `rework`). **Gap input:** Manufacturing must add movement types (or
reuse `issue`/`receipt` with distinct `reference_type` strings). **(inference)**

### Costing layers (FIFO + WAC)
- Cost layers: `inventory_cost_layers` (`...400005...:15`) — `original_quantity`,
  `remaining_quantity`, `unit_cost`, `date`, keyed by `(product, variant, warehouse)`.
  `increaseStock` ALWAYS creates a layer regardless of method (`StockService.php:60-72`), so
  switching FIFO↔WAC is data-safe.
- WAC: maintained on `inventory_stock_balances.average_cost` / `.total_value`
  (`StockService.php:50-58`).
- Per-company method via `inventory.valuation_method` setting (`:239`). Per-product override
  column `products.cost_method` exists but **StockService does not read it** — only the
  company-level setting drives behaviour. **(inference)** This is a gap for Manufacturing
  standard-costing (a common mfg requirement: standard cost + variance accounts), which is
  not supported at all today.

### Reservation support — COLUMN EXISTS, NO LOGIC (key gap input)
- `inventory_stock_balances.reserved_quantity` exists
  (`...500001_add_tracking_columns_to_stock_balances_table.php:16`), and
  `StockBalance::getAvailableQuantityAttribute()` returns `quantity - reserved_quantity`
  (`Modules/Inventory/app/Models/StockBalance.php:47-50`).
- **But NO service writes `reserved_quantity`.** Grep across Inventory + Sales finds only the
  model accessor, the fillable entry, and the API resource field — there is no
  `reserve()` / `release()` method anywhere
  (`Modules/Inventory/app/Models/StockBalance.php:25,49`;
  `.../Http/Resources/StockBalanceResource.php:26`). `ApproveIssue` checks
  `balance->quantity` (gross), NOT available qty (`...ApproveIssue.php:97`).

**Gap input:** the spec requires reservations created at order Release and consumed at
Material Issue (`06_integrations_and_data_model.md` §6.1). The `reserved_quantity` field is a
ready-made hook, but **Manufacturing must implement the reserve/release logic itself**
(new `StockService::reserve()/release()` or a Manufacturing Action that updates
`reserved_quantity` transactionally). **(inference)**

### Stock-transaction schema
- `inventory_movements` (immutable ledger): `movement_type`, `reference_type`,
  `reference_id`, `date`, `quantity_in`, `quantity_out`, `unit_cost`, `total_cost`,
  `balance_after`, `cost_after`, indexed by `(product, warehouse, date)` and
  `(reference_type, reference_id)`
  (`...200004_create_inventory_movements_table.php:11-32`).
- `inventory_stock_balances` (current state): `quantity`, `reserved_quantity`,
  `average_cost`, `total_value`, `last_receipt_date`, `last_issue_date`; unique on
  `(product, variant, warehouse)` (`...200003...:22`, `...500001...:16-18`).

### Inventory does NOT post to GL (important)
`Modules\Inventory` has **no** journal-entry integration: its `EventServiceProvider` listens
to nothing, there are no listeners, and no file references `CreateJournalEntry`
(the only `Accounting` reference in the whole module is the `Warehouse::account()` relation).
`ApproveReceipt` / `ApproveIssue` move stock only — they create **no** journal entries.
**The consuming module owns the GL posting** (Purchases posts DR Inventory on bill;
Sales posts DR COGS / CR Inventory on invoice — see §6). **Manufacturing must post its own
WIP / applied-cost / variance entries** via `CreateJournalEntry`, mirroring Purchases/Sales.

---

## 4. GRN / Purchase Receipt flow (the canonical "create a draft InventoryReceipt then call ApproveReceipt" pattern)

`Modules\Purchases\Http\Controllers\PurchaseGrnController::approve()`
(`.../PurchaseGrnController.php:335-433`) is the reference implementation Manufacturing
should copy for **finished-goods receipt**:

1. Generate receipt number via `SequenceService::generateNext($companyId,'inventory','receipt')` (`:359`).
2. `InventoryReceipt::create([... status => ReceiptStatus::Draft, reference_type => ReceiptReferenceType::Purchase, reference_id => $grn->id ...])` (`:365-376`).
3. Add receipt items (uses `accepted_quantity` in quality mode, else `quantity`); carries
   `batch_number`, `expiry_date`, `serial_numbers` through (`:379-400`).
4. `app(ApproveReceipt::class)->execute($inventoryReceipt, $userId)` — this is what actually
   increases stock (`:406`).
5. Update PO `received_quantity` per line and recompute PO receive status
   (`:410-411`, `updatePoReceivedQuantities` `:508-533`).

GRN modes (`purchases.grn_mode` setting): `direct` (no GRN; stock comes in at bill posting),
`grn`, `grn_quality` (`:490-506`). `ApproveReceipt::execute` validates serials and calls
`StockService::increaseStock` per item (`...ApproveReceipt.php:80-92`).

`PurchaseGrn` status machine: `PurchaseGrnStatus` (draft → pending_quality →
quality_approved/quality_rejected → approved/cancelled).

---

## 5. Purchase Requisition → PO flow (MRP entry point)

This is what MRP will drive to auto-create procurement.

- **Purchase Request (requisition):** `purchase_requests`
  (`Modules/Purchases/database/migrations/2026_02_25_100001...:11`) with `request_number`,
  `date`, `requested_by`, `department`, `needed_by`, `priority`, `cost_center_id`, `status`
  (default `draft`), `approved_by/at`, **`converted_to_order_id`** (line 33), `subtotal`.
  Lines in `purchase_request_items` (`...100002...`).
- Status machine: `PurchaseRequestStatus` (draft → pending/approved → converted / rejected /
  cancelled). Controller `PurchaseRequestController` has `submitApproval`, `approve`,
  `reject`, `cancel` (`.../PurchaseRequestController.php:202,229,254,280`). PR is created via
  `store()` with `SequenceService::generateNext($companyId,'purchases','request')` **(inference on key name)** (`:99-105`).
- **PR → PO conversion:** `PurchaseOrderController` declares a `convertFromRequest`
  permission/route (`.../PurchaseOrderController.php:40`); `StorePurchaseOrderRequest` accepts
  `purchase_request_id` (`.../StorePurchaseOrderRequest.php:23`); on store, if
  `purchase_request_id` is set the PR is marked `PurchaseRequestStatus::Converted`
  (`.../PurchaseOrderController.php:146-151`).

**For MRP, the cleanest integration is:** MRP creates `PurchaseRequest` + items
(`status = draft/approved`) tagged with the source demand, exactly the same way a user would,
then the normal Purchases approval/convert flow turns them into POs. There is **no
programmatic Action** (e.g. `CreatePurchaseRequest`) exposed today — PR creation lives in the
controller's `store()`. **Gap input:** Manufacturing/MRP should not call the HTTP controller;
a thin `Purchases\Actions\CreatePurchaseRequest` Action should be extracted so MRP can invoke
it in-process. **(inference)**

### Min/max / reorder rules
- Reorder thresholds live on the **product**: `reorder_point`, `min_stock_level`,
  `max_stock_level` (`...products_table.php:33-35`).
- Surfacing is read-only/reporting: `ReorderAlertController` queries products with
  `track_inventory = true`, `is_active = true`, `reorder_point > 0`, joins
  `StockBalance`, and emits alerts/notifications
  (`Modules/Inventory/app/Http/Controllers/ReorderAlertController.php:47-118`).
- **No automatic requisition generation** from reorder breaches — it is alert-only. MRP /
  reorder-point procurement must be built by Manufacturing (or a scheduler) on top of these
  fields. **(inference)**

---

## 6. How COGS / inventory value posts to Accounting (the pattern Manufacturing must mirror)

GL is always written through `Modules\Accounting\Actions\CreateJournalEntry` — never directly.

- **Inbound value (Purchases):** `PostPurchaseBill::execute` builds the purchase JE via
  `CreateJournalEntry` — **DR Inventory** (for `track_inventory` products) / DR Expense /
  DR Input-Tax / CR Discount / **CR Accounts Payable**
  (`Modules/Purchases/app/Actions/PostPurchaseBill.php:36-164`). In `direct` GRN mode it also
  creates+approves an `InventoryReceipt` to move stock (`handleDirectModeStock` `:182-249`).
  Account IDs come from settings: `purchases.inventory_account_id`,
  `purchases.expense_account_id`, `purchases.payable_account_id`,
  `purchases.tax_receivable_account_id` (`:65-69`).
- **Outbound COGS (Sales):** `PostSalesInvoice::handleCogsAndStock` computes cost via
  `StockService` and posts **DR COGS / CR Inventory** through `CreateJournalEntry`
  (`Modules/Sales/app/Actions/PostSalesInvoice.php:171-291`), then
  `StockService::decreaseStock` and creates an `InventoryIssue`
  (`:220`, `:352`). COGS account from `sales.cogs_account_id`; inventory credit account
  resolved from `sales.inventory_account_id` → `purchases.inventory_account_id`
  (`:299-307`).

So **stock movement and GL posting are decoupled**: StockService moves quantity/value;
the business module writes the journal. Manufacturing follows the same recipe.

---

## 7. Exact service / Action calls a Manufacturing module would use

All calls are in-process Action/Service invocations (Moon "Actions for cross-module calls"
recipe). Namespaces: `Modules\Inventory\Services\StockService`,
`Modules\Inventory\Actions\*`, `Modules\Inventory\Models\*`,
`Modules\Accounting\Actions\CreateJournalEntry`, `Modules\Core\Services\SequenceService`.

### (a) Reserve materials (at Production Order Release)
- **No ready API.** Manufacturing must transactionally increment
  `StockBalance::reserved_quantity` for each `Order_Component`
  (`StockBalance` keyed by product/variant/warehouse; field at
  `...500001...:16`). Recommended: add `StockService::reserve(array)` /
  `release(array)`, or a `Manufacturing\Actions\ReserveOrderComponents` Action.
  Availability check uses `StockBalance::available_quantity`
  (`Models/StockBalance.php:47`). **(inference / gap)**

### (b) Issue materials to production (RM → WIP)
1. Build a draft `InventoryIssue` (+ items with `unit_id`, `quantity`, optional
   `batch_number`, `serial_numbers`) — reference pattern in
   `PostSalesInvoice.php:352`. Set `reference_type = 'production_order'` (new),
   `reference_id = $order->id`.
2. `app(\Modules\Inventory\Actions\ApproveIssue::class)->execute($issue, $userId)` — this
   calls `StockService::getIssueCost` + `decreaseStock` with `MovementType::Issue`,
   relieving stock at FIFO/WAC cost (`...ApproveIssue.php:110-136`).
3. Capture returned issue cost (`$item->total_cost`) and post **DR WIP / CR Inventory** via
   `app(CreateJournalEntry::class)->execute([...], $lines)` — Manufacturing owns this JE
   (mirror `PostSalesInvoice.php:264-296`). **(inference)**
   - Release reservation as the issue is posted (decrement `reserved_quantity`).

### (c) Receive finished goods (WIP → FG)
1. `SequenceService::generateNext($companyId,'inventory','receipt')`
   (pattern `PurchaseGrnController.php:359`).
2. `InventoryReceipt::create([... status => Draft, reference_type => 'production_order',
   reference_id => $order->id, unit_cost => <computed FG cost> ...])` + items
   (`PurchaseGrnController.php:365-400`).
3. `app(\Modules\Inventory\Actions\ApproveReceipt::class)->execute($receipt, $userId)` —
   increases stock and creates a FIFO cost layer at the supplied FG unit cost
   (`...ApproveReceipt.php:80-92`). FG `unit_cost` must be the rolled-up WIP cost computed by
   Manufacturing (materials issued + applied labor/machine/OH).
4. Post **DR Finished Goods Inventory / CR WIP** via `CreateJournalEntry`. **(inference)**

### (d) Trigger purchase requisitions (MRP → Procurement)
- Create `Modules\Purchases\Models\PurchaseRequest` + `PurchaseRequestItem` rows
  (status `draft` or `approved`), populating `needed_by`, `priority`, `cost_center_id`, and
  linking back to the MRP demand. Today PR creation lives only in
  `PurchaseRequestController::store` — **recommended:** extract a
  `Purchases\Actions\CreatePurchaseRequest` so MRP can call it in-process; PR then flows
  through `convertFromRequest` to a PO (`PurchaseOrderController.php:40,146-151`).
  **(inference / gap)**

---

## 8. Summary of gap inputs for the board

1. **No batch/lot master or batch-level balances** — only serial is first-class;
   batch is free-text. Blocks lot genealogy / FEFO. (`inventory_stock_balances` keyed
   product/variant/warehouse, `...200003...:22`.)
2. **Reservation logic absent** — `reserved_quantity` column exists but nothing writes it
   (`StockBalance.php:25,49`).
3. **No manufacturing item classification** — `ProductType` is only Product/Service; no
   RM/WIP/FG, `procurement_type`, or `material_ownership`/consignment
   (`ProductType.php:7-8`).
4. **No standard costing / variance** — only FIFO + WAC; per-product `cost_method` unused by
   StockService (`StockService.php:239`).
5. **No production movement types** — `MovementType` lacks production issue/receipt/scrap/
   rework (`MovementType.php:7-13`).
6. **No GL in Inventory** — Manufacturing must author all WIP/applied/variance journals via
   `CreateJournalEntry`.
7. **No programmatic PR creation Action** — MRP needs a `CreatePurchaseRequest` Action
   (currently controller-only).
8. **No sub-warehouse bin/location model** — shop-floor/WIP locations must be modelled as
   warehouses or new bins.
9. **Reorder is alert-only** — no auto-requisition from reorder-point breach
   (`ReorderAlertController.php`).
</content>
</invoke>

---


# 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.

---


# 13 — Neighbors (Sales, HRM, QMS, CMMS) & Frontend Conventions

> Survey of the remaining Manufacturing touchpoints in the host ERP modules
> `Sales`, `HRM`, `QMS`, `CMMS` (Laravel 12, nwidart) plus the Angular FE conventions
> (`/home/moonui/public_html/moon-erp`). Read-only. Inferences marked **(inference)**.
> The spec's own integration map (`files/manufacturing_spec/markdown/06_integrations_and_data_model.md`)
> declares Manufacturing must NOT rebuild Sales, HR, GL, Inventory, Procurement — and §6.5 lists
> Quality Management and Maintenance (PM) as future phases that *hook into* Confirmation, which the
> existing QMS/CMMS modules already satisfy.

---

## 13.1 Sales — Demand source for MPS / delivery promising

Backend: `/home/moonui/moon-erp-be/Modules/Sales`

- `sales_orders` table — `database/migrations/2026_02_24_200001_create_sales_orders_table.php`:
  - `company_id` + `branch_id` (DataScope ready), `order_number`, `date`, `expected_delivery_date:18`,
    `customer_id` → `business_partners`, `quotation_id` → `sales_quotations`,
    `status` default `draft`, `delivery_status` default `pending`, `invoice_status` default `pending`.
  - Models: `app/Models/SalesOrder.php`, `SalesOrderItem.php`.
- `sales_order_items` — `2026_02_24_200002_create_sales_order_items_table.php`:
  - `product_id` → `products` (line ~14), optional `product_variant_id`, `unit_id`, `quantity`,
    `delivered_quantity`, `invoiced_quantity`. This `product_id` is the same `products` reference the
    Manufacturing BOM is built on.
- `sales_quotations` — `2026_02_24_100001_create_sales_quotations_table.php`: conversion tracking via
  `sales_order_id` (line ~62); `sales_orders.quotation_id` is the reverse link.

**Manufacturing handshake**
- MPS `confirmed_orders_qty` (spec file `02`) is fed from confirmed `sales_orders`.
- `Pegging` (spec §6.2: `production_order_id` ↔ `sales_order_id`, `allocated_quantity`) is the
  many-to-many demand↔supply link; `Delivery_Schedule` (spec §6.2) splits a `sales_order` into
  `linked_production_order`.

**Gaps**
- No ATP/CTP field on `sales_orders`. `expected_delivery_date` is a manual promise with no link to
  production capacity. **(inference)** A Manufacturing Action must write the promised date back and
  update `delivery_status`; there is no return field today.
- No FK from any Sales table to a production order — the link will live on the Manufacturing side
  (`Pegging`, `Delivery_Schedule`), which does not yet exist.

---

## 13.2 HRM — Labor confirmations costing / employees.user_id chain

Backend: `/home/moonui/moon-erp-be/Modules/HRM`

- `employees` — `database/migrations/2026_03_01_100003_create_employees_table.php`:
  - `user_id` nullable **unique** → `users` (line ~18) — the `employees.user_id → users.id` chain that
    ties a shop-floor operator (a `user`) back to an employee for costing/payroll.
  - `department_id`, `position_id`, `branch_id`, `basic_salary:decimal(12,2)` (line ~26),
    `currency_id`, `status`, `company_id`.
  - Model: `app/Models/Employee.php`.
- `shifts` — `2026_03_01_100007_create_shifts_table.php`: `start_time`, `end_time`,
  `working_hours`, `is_night_shift`, `grace_period_minutes`, `overtime_start_after_minutes`.
- `attendances` — `2026_03_01_200001_create_attendances_table.php`: `employee_id`, `date`,
  `check_in/out`, `worked_hours`, `overtime_hours`, `late_minutes`; unique `[employee_id, date]`.
- Rate derivation: `app/Services/PayrollService.php:48-49` —
  `dailyRate = basic_salary / salaryDays`; `hourlyRate = dailyRate / hoursPerDay`. There is **no stored
  per-employee hourly or piece rate**.

**Manufacturing handshake**
- Confirmation (`Confirmation_Detail.labor_hours`, spec file `03`) needs a rate per operator/work center.
- Spec `labor_calc ∈ {Hourly, PieceRate}` (file `01` §1.6, file `04` §2): `cost = labor_hours × labor_rate`
  OR `cost = quantity × piece_rate`.

**Gaps (material)**
- No `hourly_rate` / `piece_rate` column on `employees` (only `basic_salary`); the hourly rate is a
  runtime payroll computation, not a stored costing input. A Manufacturing rate source is required —
  either a new field, a work-center `labor_cost_rate` (spec carries this on `Work_Center`), or an HR
  rate service. **(inference)**
- `attendances` keys on `employee_id` not `user_id`; the SFC terminal will authenticate a `user`, so the
  `users → employees` resolution (via `employees.user_id`) is the join point for shift/attendance context.

---

## 13.3 QMS — In-process inspection & NCR on scrap (REPLACES spec-internal quality)

Backend: `/home/moonui/moon-erp-be/Modules/QMS`, single migration
`database/migrations/2026_03_31_300001_create_qms_tables.php`.

The spec (`06` §6.5) lists Quality Management as a future phase that "hooks Confirmation
grades/reason_codes; inspection gates on operations." The spec defines NO internal quality tables —
Goods_Receipt carries only `quality_status ∈ {Released, OnHold, Rejected}` and Confirmation carries
`reason_code`. **QMS supplies the full quality layer, so Manufacturing builds none of it.**

Tables (all prefixed `qms_`):
- `qms_inspection_plans` — `name`, `type` default `incoming_material`, **`production_order_id` (nullable)**,
  `product_id`, `supplier_id`, `frequency` default `per_batch`. Model `app/Models/InspectionPlan.php`.
- `qms_inspection_criteria` — `check_type` (visual/measurement), `acceptance_criteria`,
  `min_value`/`max_value`, `is_critical`. Model `InspectionCriteria.php`.
- `qms_inspections` — `inspection_number` unique, `product_id`, **`production_order_id`**,
  `quantity_inspected/accepted/rejected`, `result` default `pending`, `inspector_id`.
  Model `Inspection.php` (`production_order_id` in `$fillable`, line ~25).
- `qms_inspection_results` — per-criteria `measured_value`, `result`. Model `InspectionResultRecord.php`.
- `qms_non_conformances` — `ncr_number` unique, `type` default `product_defect`, `severity`, `status`,
  `source`, **`inspection_id` (FK, nullable)**, `product_id`, `root_cause`, `immediate_action`,
  `assigned_to`. Model `NonConformance.php`.
- `qms_capa_actions` — `non_conformance_id` FK, `capa_number`, `type` (corrective/preventive),
  `effectiveness_check`, `verified_by`. Model `CapaAction.php`.
- `qms_supplier_evaluations` — supplier quality/delivery/price scoring. Model `SupplierEvaluation.php`.

**Spec-need vs QMS-has mapping**

| Spec need (manufacturing) | QMS table/field | Link |
|---|---|---|
| Inspection gate on operation (`inspection_required`, Routing_Operation) | `qms_inspection_plans` + `qms_inspection_criteria` | `type='in_process'`, `production_order_id` |
| Record inspection during confirmation | `qms_inspections` (`quantity_*`, `result`) + `qms_inspection_results` | `production_order_id` column present |
| NCR on scrap (`reason_code` → Pareto) | `qms_non_conformances` | via `inspection_id` → `production_order_id` |
| Corrective/preventive on recurring defects | `qms_capa_actions` | via `non_conformance_id` |
| GR quality gate (`quality_status`) | `qms_inspections.result` | drives Released/OnHold/Rejected |

**Gaps**
- `qms_inspections.production_order_id` / `qms_inspection_plans.production_order_id` are plain
  `unsignedBigInteger` with **no `constrained()` FK** (manufacturing tables don't exist yet); the FK is
  deferred to the Manufacturing build.
- No `operation_no` column — an inspection can be tied to a production order but **not pinned to a
  specific routing operation**; the spec's operation-level gate needs this. **(inference)**
- Scrap → NCR conversion (threshold on `reason_code`) is an Action to build on the Manufacturing side;
  QMS provides the destination tables only.

---

## 13.4 CMMS — Work centers & molds as maintainable assets; downtime ↔ WO; shot-count PM

Backend: `/home/moonui/moon-erp-be/Modules/CMMS`, single migration
`database/migrations/2026_03_31_200001_create_cmms_tables.php`.

Spec `06` §6.5: Maintenance (PM) "reduces Work_Center capacity during downtime windows"; Tool/Mold
Management "extends Tool entity: lifecycle, maintenance, cycle tracking, replacement alerts."

- `cmms_asset_categories` — hierarchical (`parent_id`). Model `app/Models/CmmsAssetCategory.php`.
- `cmms_assets` — `code` (unique per company), `category_id`, `serial_number`, `model`,
  `manufacturer`, `location`, `status`, `parent_asset_id`, **`specifications` JSON**. Model `CmmsAsset.php`.
  → Work_Center = machine asset; Tool/Mold = a `cmms_assets` row (Tool category).
- `cmms_pm_schedules` — `asset_id`, `frequency_type` (default `monthly`), `frequency_value`,
  **`meter_field`** (string), **`meter_threshold`** (decimal), `next_due_at`, `checklist` JSON.
  Model `CmmsPmSchedule.php` (`meter_field`/`meter_threshold` in `$fillable`, lines ~30-31).
  → supports "PM every N shots/cycles" for molds.
- `cmms_work_orders` — `order_number` unique, `asset_id`, `type` (`WorkOrderType ∈ {Preventive,
  Corrective}`, `app/Enums/WorkOrderType.php`), `pm_schedule_id`, `status`, **`downtime_hours`**,
  `failure_code`, `actual_start/end`, `actual_cost`. Model `CmmsWorkOrder.php`.
- `cmms_work_order_parts` — `product_id`, `quantity`, `total_cost` (spare-parts consumption).
- `cmms_work_order_labor` — **`user_id`**, `hours`, `hourly_rate`, `total_cost`. Model `CmmsWorkOrderLabor.php`.

**Manufacturing handshake**
- Tool entity (spec §1.5: `life_cycles`, `cycles_used`, `status ∈ {Available, Mounted, Maintenance,
  Retired}`) maps onto `cmms_assets` + `cmms_pm_schedules` meter PM.
- Production downtime ↔ `cmms_work_orders` with `downtime_hours` + `actual_start/end` → subtract from
  CRP available capacity for that work center.
- Mold shot-count PM: `cmms_pm_schedules.meter_field='cycles_used'` + `meter_threshold` triggers a
  preventive WO when cumulative shots cross the threshold.

**Gaps**
- **No asset meter-reading table** (e.g. `cmms_asset_meters`) to accumulate cumulative mold shot counts;
  `meter_field` is a free string with no time-series data source. Confirmation `cycle`/`cavity` counts
  would need to post readings into a structure CMMS does not have yet. **(inference)**
- `cmms_work_order_labor.user_id` (not `employee_id`) — resolve via `employees.user_id` for HR costing.
- `cmms_assets` has no explicit `work_center_id`/`tool_id` cross-link; the Manufacturing side must hold
  the FK from Work_Center/Tool to `cmms_assets.id`. **(inference)**

---

## 13.5 Frontend conventions (Angular, `/home/moonui/public_html/moon-erp`)

Standalone components, lazy routes, signal store. Two guards layered:

- **`moduleGuard`** — `src/app/core/guards/auth.guard.ts:162` — blocks URL-typing into a system-wide
  deactivated module (`SystemModulesService.isRouteEnabled`). Applied at module-group level in
  `src/app/app.routes.ts` (e.g. accounting group `canActivate: [moduleGuard]`, line ~101).
- **`permissionGuard`** — `auth.guard.ts:125` — reads `route.data.permissions` (prefix match, e.g.
  `['accounting.accounts']`); applied per child route. Usage doc at `auth.guard.ts:118-119`.
- Lazy loading via `loadComponent: () => import(...)` per route (`app.routes.ts` passim).

Module route shape (per existing modules, mirror for `manufacturing`):
```
{ path: 'manufacturing', canActivate: [moduleGuard], children: [
    { path: '', canActivate: [permissionGuard], data: { permissions: ['manufacturing.'] }, loadComponent: ... },
    { path: 'bom', canActivate: [permissionGuard], data: { permissions: ['manufacturing.bom'] }, loadComponent: ... },
    ...
]}
```
A self-contained module may also ship a standalone routes file + layout, as LIS does:
`src/app/features/lis/lis-standalone.routes.ts` (LisLayoutComponent + permission-guarded children,
landing uses prefix `data: { permissions: ['lis.'] }`).

Shared components (reuse, do not rebuild):
- `src/app/shared/components/data-table/data-table.component.ts` — list grids.
- `src/app/shared/components/form-dialog/form-dialog.component.ts` — create/edit dialogs.
- `src/app/shared/components/module-nav/module-nav.component.ts` — module sidebar.
- `src/app/shared/components/page-header/`, `sales-filter-bar/`.
- Print: `src/app/shared/print/print.service.ts`, `print-templates.ts`, `print.interfaces.ts`,
  `print-settings/`.

**Manufacturing workspace structure (recommended)**
- `features/manufacturing/manufacturing-layout/` + dashboard + master data (BOM/Routing/Work Center/Tool).
- Planning (MPS/MRP/CRP) and execution (production order, material issue, confirmation, goods receipt).
- **Touch shop-floor terminal**: model on `features/pos/pos-layout/` (`pos-layout.component.{ts,html,scss}`) —
  large touch targets, `[Start]`/`[Done]` buttons, Ready-queue per Work Center, quantity/grade/scrap entry
  (spec file `05` §5.4). Offline-tolerant per spec §5.4. **(inference: POS layout is the closest existing
  touch-terminal pattern to reuse.)**
- Supervisor **real-time dashboard** (spec §5.5) listing each order's current operation + live bottleneck.

---

## 13.6 Consolidated gap list (for the Manufacturing build)

1. **Sales**: no ATP/CTP field, no promised-date return field, no production-order link on Sales side
   (lives in `Pegging`/`Delivery_Schedule` on Manufacturing).
2. **HRM**: no stored `hourly_rate`/`piece_rate` per employee (only `basic_salary`; hourly is derived in
   `PayrollService:49`). Manufacturing needs an explicit labor-rate source for both `labor_calc` modes.
3. **QMS**: `production_order_id` columns exist but unconstrained; **no `operation_no`** to pin inspection
   to a routing operation; scrap→NCR conversion is a Manufacturing-side Action.
4. **CMMS**: **no asset meter-reading table** for cumulative mold shot counts; no Work_Center/Tool ↔
   `cmms_assets` cross-link; labor keyed by `user_id` not `employee_id`.
5. **FE**: no `manufacturing` route group yet; reuse `moduleGuard` + `permissionGuard` pattern, shared
   data-table/form-dialog/print, and the POS touch layout for the shop-floor terminal.

---


# 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.

---


# 21 — Gap Analysis (Both Directions)

Answer to the owner's question "ايه اللي ناقص" in two directions:

- **Direction A** — what Moon ERP lacks today to host the Manufacturing spec (verified against
  `md/10` Production-now, `md/11` Inventory/Purchases, `md/12` Accounting/Core, `md/13` Sales/HRM/QMS/CMMS/FE).
- **Direction B** — what the SPEC itself fails to specify (expert critique of
  `files/manufacturing_spec/markdown` 00–06, verified against `md/00..06` digests).

Severity: **CRITICAL** (blocks a correct go-live), **IMPORTANT** (blocks completeness / causes rework),
**NICE-TO-HAVE** (quality/maturity). Sizing: **S** ≤ ~3 dev-days, **M** ≤ ~3 dev-weeks, **L** > 3 dev-weeks.
⚠️ **This is the GAP-level legend.** The roadmap (23) uses a different PHASE-level legend (S ≈ ≤1 wk,
M ≈ 2–4 wks, L ≈ 4–8 wks) — do NOT map gap sizes onto phase sizes when budgeting.

**Candidates checked and EXCLUDED (already covered, NOT gaps):**

| Candidate | Verdict |
|---|---|
| UoM conversion engine | EXISTS — Core `unit_groups`/`units` (`conversion_factor` 15,6) + per-product `product_units` with `is_purchase`/`is_sale`; BOM/issue/receipt rows already carry `unit_id` (md/11 §1). The remaining issue is a SPEC gap (B1): the spec never states conversion/rounding policy. |
| Cost centers / dimension on journal lines | EXISTS — `cost_centers` (hierarchical) + `journal_entry_lines.cost_center_id`, report-aware in `GeneralLedgerService`, plus `CostAllocationService` for Direct allocation (md/12 §4). Only the `type` column (A18) and Step-Down/Reciprocal (A22) are missing. |
| Document numbering / tenancy / branch scoping / permissions registry / attachments / audit | All inherited rails (`SequenceService`, `BaseModel`/TenantAware, `DataScope`, `PermissionDependencyRegistry`, `Attachment`) — proven by LIS. |
| ATP/CTP as a spec gap | NOT a spec gap — spec 02 fully defines CTP (capacity-based) and ATP (3 modes). The gap is Moon-side only (A14: Sales has no field to receive the promised date). |
| Quality layer | Spec defers it; Moon **QMS already supplies it** — `qms_inspection_plans`/`qms_inspections` carry `production_order_id` waiting; NCR/CAPA link only via `inspection_id` (no direct `production_order_id`). Residual gaps are A10-adjacent (operation pinning) and B22 (sampling plans). |

---

## Direction A — What Moon ERP lacks for this spec

### CRITICAL

**A1. Zero GL posting from production (the biggest single gap)**
- **What:** `Modules/Production` contains no `CreateJournalEntry` call; Inventory services also post nothing. The entire WIP rail — DR WIP/CR RM on issue, DR WIP/CR Labor-Applied + MOH-Applied on confirmation, DR FG + variance/CR WIP on receipt, settlement on close — does not exist (md/12 §10.1).
- **Why:** Spec rule 6 (00 §0.4): *every actual event posts a GL entry, real-time, not batch*. Without it production is financially invisible: no WIP balance, no COGM, no variances, books wrong at any month-end with open orders.
- **Remedy:** Manufacturing Actions per event (`PostMaterialIssue`, `PostConfirmation`, `PostGoodsReceipt`, `CloseProductionOrder` — canonical name per mapping/D-17) calling `Modules\Accounting\Actions\CreateJournalEntry` with `source_type='production_order'`, distinct `entry_type` per event, `cost_center_id` per line — pattern copied 1:1 from `PostLabInvoice` (md/12 §9). Rails need NO Accounting schema change.
- **Size: L**

**A2. Production does not move stock**
- **What:** `consume()` / `record-output()` only mutate quantity columns inside production tables; `source_warehouse_id`/`target_warehouse_id` are dead columns; no `InventoryIssue`/`InventoryReceipt` created (md/10).
- **Why:** Inventory balances and cost layers are never affected by manufacturing — stock-on-hand is wrong the moment the first order runs.
- **Remedy:** Material Issue → draft `InventoryIssue` (`reference_type='production_order'`) + `app(ApproveIssue::class)->execute()`; FG receipt → `InventoryReceipt` + `ApproveReceipt` at rolled-up WIP cost — the exact GRN pattern in `PurchaseGrnController::approve` (md/11 §7).
- **Size: M**

**A3. No planning layer at all (MPS / MRP / CRP)**
- **What:** Greenfield: no `mfg_mps_*`, no `Item_MRP_Settings`, no MRP run, no CRP loads, no pegging table, no exception messages (md/10, spec 02).
- **Why:** Spec build-order steps 2–5; without MRP there is no automatic procurement, no promised dates, no capacity check — the module degrades to manual order entry.
- **Remedy:** Build per spec 02 on existing inputs: demand ← `sales_orders`, supply ← `StockBalance`/`PurchaseOrder`/`ProductionOrder`, recursion ← BOM. Output planned POs via A11 and planned production orders in `Planned` status.
- **Size: L**

**A4. Batch/lot is not first-class in Inventory**
- **What:** Only Serial is fully modelled (`product_serials`). `batch_number`/`expiry_date` are free-text strings on receipt/issue/GRN items; stock balances and cost layers are keyed `(product, variant, warehouse)` only — no batch master, no batch balances, no genealogy, no FEFO (md/11 §1).
- **Why:** Manufacturing traceability (which RM lots entered which FG lot), expiry-driven issue, and recall capability are impossible; toll/consignment and graded outputs make this worse.
- **Remedy:** Add a `batches` master + batch-keyed balance (or batch dimension on `inventory_cost_layers`), batch-aware `StockService`, and a genealogy link table written at Material Issue ↔ Goods Receipt.
- **Size: L**

**A5. Stock reservation logic absent**
- **What:** `inventory_stock_balances.reserved_quantity` and the `available_quantity` accessor exist, but **nothing writes them**; `ApproveIssue` checks gross qty not available (md/11 §3).
- **Why:** Spec hard rule (06 §6.1): reserve at order Release, consume at Material Issue. Without it, two orders can promise the same stock.
- **Remedy:** Add `StockService::reserve()/release()` (transactional) + call from order Release/Issue/Cancel. The column hook is ready — logic only.
- **Size: S**

**A6. Item master has no manufacturing fields**
- **What:** `ProductType` = Product|Service only; no RM/WIP/FG class, no `procurement_type` (Make/Buy), no `material_ownership` (Own/Customer), `cost_method` is a free nullable string ignored by `StockService`, no decomposed standard cost (md/11 §1, md/12 §8).
- **Why:** Nearly every spec branch (MRP make-vs-buy, toll, costing method per item, MPS by production_type) keys off these fields.
- **Remedy:** Manufacturing-side item-settings extension table (`mfg_item_mrp_settings` — canonical name per mapping row 10, AccBpExt pattern) carrying `procurement_type`, `material_ownership`, `production_type`, `costing_method` enum, MRP params, + `mfg_standard_costs` (std material/labor/overhead/total).
- **Size: M**

**A7. No standard costing or variance engine**
- **What:** StockService supports FIFO + WAC (company-level) only; no Standard valuation, no 7-variance computation (MPV, MUV, LRV, LEV, VOSV, VOEV, FOVV), no tolerance bands, no owner routing (md/11 §3, md/12 §10.8).
- **Why:** Spec 04 calls variance analysis the most valuable output of the whole module; FG receipt at standard + variance posting is the core costing contract.
- **Remedy:** Variance engine as Actions triggered on order Close + monthly period job hooked before `CloseFiscalPeriod`; accounts seeded via `AutoAccountService`; settings keys `production.*_account_id` (the module's single settings namespace, matching the `production.*` permission prefix and `'production'` sequence key — D-01) via `SettingDefinition` seeder.
- **Size: L**

**A8. No work-center calendar / shift-capacity model**
- **What:** `production_centers` has `capacity_per_hour` but no calendar, shifts, holidays, or capacity-by-period; HRM `shifts` exist for attendance only, not machine capacity (md/10, md/13 §2).
- **Why:** CRP, CTP, `effective_capacity`, `find_earliest_slot` and `issue_level=PerShift` all require a working calendar — without it every capacity number is fiction.
- **Remedy:** `mfg_work_calendars` + `mfg_calendar_shifts` (canonical names per mapping row 12; working days, shift hours, exceptions) referenced by work center; derive `effective_capacity = daily_capacity × multiplier × efficiency% × utilization%` per period.
- **Size: M**

**A9. BOM versioning, Routing entity and Frozen Snapshot missing**
- **What:** `BomStatus` enum declared but unused (state = `is_active` bool); `version` is free text with no effective dates; routing operations hang off BOM (no versioned `Routing_Header`); released orders copy from BOM at creation but there is no Release-time frozen snapshot, and the order state machine differs (Draft/Confirmed/... vs Planned/Released/InProcess/Completed/Closed) (md/10).
- **Why:** Spec cross-cutting rule 1: master-data edits must never alter released orders — audit and cost integrity depend on it.
- **Remedy:** Add `mfg_routing_headers/_operations` (versioned, effective-dated, lot-size ranges, cavity/cycle/queue/move times); BOM versions with status state machine; snapshot copy on `Planned→Released`; align `ProductionOrderStatus` to the spec machine (additive states).
- **Size: M–L**

**A10. Tool/Mold entity + cycle counters missing (and CMMS has no meter-reading table)**
- **What:** No Tool entity anywhere in Production; CMMS `cmms_pm_schedules.meter_field/meter_threshold` support shot-count PM but there is **no `cmms_asset_meters` time-series** to accumulate cycles, and no Work_Center/Tool ↔ `cmms_assets` cross-link (md/13 §4).
- **Why:** In molding factories the tool, not the machine, is the constraint; usage-based depreciation (`purchase_cost / life_cycles / cavity_count`) and mold-availability scheduling depend on cycle counters.
- **Remedy:** `mfg_tools` (cavity_count, life_cycles, cycles_used, status machine Available→Mounted→Maintenance→Retired) with FK to `cmms_assets.id`; add `cmms_asset_meters` readings table fed by Confirmation cycle counts; depreciation JE via CreateJournalEntry.
- **Size: M**

**A23. Overhead `cost_driver` has no storage home**
- **What:** Spec 04 §4.2 makes `cost_driver ∈ {LaborHours, MachineHours, DirectMaterialCost, UnitsProduced}` the overhead-rate denominator; Moon stores `production_centers.overhead_rate` but NOTHING stores which driver applies — the confirmation OH posting (`cost_driver × overhead_rate`) is uncomputable as specified.
- **Remedy:** `cost_driver` enum column on `production_centers` (per work center — mapping row 6), resolved by `PostConfirmation`. Scheduled with the Phase 2 work-center extension.
- **Size: S**

**A24. Work-center capacity/rate columns absent (and previously unscheduled)**
- **What:** `production_centers` lacks the ~14 columns the mapping's Work_Center EXTEND requires (`capacity_unit`, `daily_capacity`, `capacity_multiplier`, `efficiency_percent`, `utilization_percent`, `setup/labor/machine_cost_rate`, `labor_calc`, `cost_driver`, `calendar_id`, `bottleneck_flag`…). Phase 2 costing posts from WC rates and Phase 4 CRP computes effective capacity from them.
- **Remedy:** Schedule the EXTEND explicitly: rate/capacity/driver columns in roadmap Phase 2, `calendar_id` in Phase 4 (Phase 1 adds only `cost_center_id`).
- **Size: M**

### IMPORTANT

**A11. No programmatic PurchaseRequest creation for MRP** — PR creation lives only in `PurchaseRequestController::store`; extract `Purchases\Actions\CreatePurchaseRequest` so MRP raises requisitions in-process, flowing to PO via the existing `convertFromRequest`. **(S)**

**A12. `ApprovalModule` enum lacks Production** — only Sales/Purchases (md/12 §7); production-order / BOM-change approvals cannot route. Add `Production` case + label + translation key. **(S)**

**A13. No stored labor rate in HRM** — employees carry `basic_salary` only; hourly rate is derived at payroll runtime (`PayrollService:48-49`); no piece_rate anywhere (md/13 §2). Decide rate source: `Work_Center.labor_cost_rate` (spec-native, recommended v1) + optional employee override; PieceRate quantities flow back to payroll. **(S)**

**A14. Sales cannot receive the promised date** — no ATP/CTP field on `sales_orders`; `expected_delivery_date` is manual (md/13 §1). Add `promised_date` (+source flag) written back by a Manufacturing Action; Pegging/Delivery_Schedule live Manufacturing-side. **(S)**

**A15. No consignment/toll ownership bucket in Inventory** — spec hard rule: customer-owned material is held, never valued, never procured. v1 = dedicated consignment warehouses flagged `warehouses.is_consignment` (never valued, never purchasable — mapping row 17, ratified D-15); full `ownership` dimension on balances/movements deferred. **(M)**

**A16. Production module has no events, Actions, or tests** — empty `EventServiceProvider`, no Actions/Services dirs, zero tests, empty seeder (md/10). Violates the house recipe (domain events + listeners, Actions for cross-module calls). Build the event spine (`OrderReleased`, `MaterialIssued`, `OperationCompleted`, `GoodsReceived`, `OrderClosed`) + listeners + Pest coverage. **(M)**

**A17. No shop-floor terminal UI or realtime channel** — no `manufacturing` FE route group; no touch terminal; no broadcast mechanism for the Ready-queue push. Clone `features/pos/pos-layout` as the terminal template; supervisor dashboard; websockets/Core notifications for auto-advance. **(M)**

**A18. Cost-center `type` + work-center→cost-center link missing** — `cost_centers` has no Production/Service/Auxiliary type column; `production_centers` has `account_id` but no `cost_center_id` FK (md/12 §10.2/§10.10). Add both. **(S)**

**A25. FOVV budgeted-volume input missing (budget money EXISTS)** — backend audit: Accounting **already has** a budgeting rail — `budgets` (per fiscal year) + `budget_lines` (`account_id`, `cost_center_id`, annual + monthly `m1..m12` amounts) — md/12 missed it. So FOVV's budgeted fixed-OH spend per cost center is a REUSE; what is genuinely missing is **budgeted production VOLUME** (units). Remedy: `production_centers.budgeted_monthly_volume` v1 (+ entry screen); no approved budget for the period ⇒ FOVV auto-descoped to 6 declared variances (D-22). **(S)**

**A26. Usage-based depreciation method missing in Accounting (register EXISTS)** — backend audit: the fixed-asset register exists (`fixed_assets`, `FixedAsset` + `AssetCategory` + `DepreciationEntry` models, `FixedAssetService`, Sell/Dispose Actions), so `mfg_tools.fixed_asset_id` has a real FK target. Missing: `DepreciationMethod` has only StraightLine/DecliningBalance/Accelerated — the spec's usage-driven (units-of-production) variant for molds needs a new enum case + meter-driven `DepreciationEntry` generation. Phase 2 prerequisite. **(M)**

**A27. Inventory reference-type enums lack a production case** — `ReceiptReferenceType` {Purchase, Return, Adjustment, Opening, Other} and `IssueReferenceType` {Sale, Consumption, Damage, Adjustment, Other} are backed enums (verified); `reference_type='production_order'` requires adding a `ProductionOrder` case to BOTH — a required Inventory touch alongside `MovementType` (A19). **(S)**

### NICE-TO-HAVE

**A19. Production movement types** — `MovementType` lacks production_issue/production_receipt/scrap/rework; v1 can overload issue/receipt with `reference_type='production_order'`. **(S)**
**A20. Bin/location model below warehouse** — shop-floor staging/WIP locations map onto `warehouses` rows for v1; true bins later. **(M)**
**A21. Barcode printing for lots/travel cards** — `product_units` barcodes + `print.service.ts` exist; add order travel-card / lot-label templates. **(S)**
**A22. Step-Down / Reciprocal allocation** — `CostAllocationService` implements Direct only; acceptable v1. **(M)**

---

## Direction B — What the SPEC itself is missing (expert critique)

### CRITICAL

**B1. UoM conversion policy never stated**
- Every entity carries a `uom` but the spec never defines conversion application points (BOM explosion kg→pcs, issue, costing) or rounding policy — it silently assumes the host. Moon's engine covers the mechanics; the BUILD must still decide policy (base-unit normalization at explosion time, banker's rounding, conversion at issue vs at BOM). **Remedy:** write a one-page UoM policy addendum binding `units`/`product_units` to each touchpoint. **(S)**

**B2. Lot/batch traceability absent as a rule**
- No genealogy requirement anywhere (which RM lots → which FG lot), no FEFO/lot-selection on issue — despite grades, toll ownership and expiry implying it. **Remedy:** add a traceability cross-cutting rule + genealogy table to the data model (pairs with A4). **(M)**

**B3. Subcontracted (outsourced) operations not modelled**
- Routing ops always reference an internal work center. TollManufacturing covers customer-material INBOUND only; sending OUR WIP out for one operation (plating, printing…) has no operation type, no subcontract PO link, no in-transit WIP bucket, no service-cost element. Very common in real factories. **Remedy:** `operation_type=Subcontract` + vendor + linked PurchaseOrder + WIP-at-vendor location + service cost into WIP. **(M)**

**B4. Planning/shop calendar entity undefined**
- `calendar_id` is referenced on Work_Center; CRP, CTP, `issue_level=PerShift` and confirmation.shift all need it, but the calendar/shift model is never specified (deferred with APS). **Remedy:** specify the calendar entity now (pairs with A8) — it cannot wait for APS. **(M)**

### IMPORTANT

**B5. Rework/repair flow undefined** — `order_type=Rework`, `routing_type=Repair` and `rework_quantity` are named, but no flow: how rework qty spawns/links an order, its costing, re-entry into routing, normal-vs-abnormal spoilage. **(M)**
**B6. Co-product vs by-product distinction + allocation method config** — only graded joint products with NRV allocation; no by-product NRV-credit treatment, no allocation-method enum (market value / physical units / fixed ratio), and BOM has no output-structure for secondary products. **(M)**
**B7. Reversal semantics unspecified** — Issue/Confirmation/GR all carry Reversed status but no algorithm (restock, negative WIP, GL reversal, preconditions). **(S)**
**B8. Backflush shortage handling undefined** — block confirmation vs allow negative vs partial backflush is never stated; Moon has per-warehouse `allow_negative_stock` to key off. **(S)**
**B9. Over/under-production tolerance & partial close** — "confirmed ≈ ordered" with no tolerance %, no auto-close threshold, no rule for WIP carrying between partial receipts. **(S)**
**B10. Pegging allocation policy** — table defined, but no priority/proportional rule on shortage and no re-pegging rule when MRP regenerates. **(S)**
**B11. Multi-plant/warehouse netting ambiguous** — `plant_id` on MPS but MRP nets a single `on_hand`; no warehouse dimension, no transfer-order sourcing — must be reconciled with Moon's branch+warehouse model. **(M)**
**B12. No demand-forecast source** — MPS consumes `forecast_qty` but nobody produces it; Moon has no forecasting module. v1: manual forecast entry/import screen; later statistical. **(M)**
**B13. ECO change management named but absent** — master data "changes only via ECO" yet no ECO entity/workflow is specified (future phase). Interim: BOM/Routing versioning + `ApprovalModule::Production` approvals as the lightweight substitute. **(M)**
**B14. Return-to-stock of unused issued material** — no return-from-shop-floor document (excess issue back to warehouse, WIP credit). Standard in every MES. **(S)**
**B15. SFC: downtime, pause/resume, andon, OEE all undefined** — operation enum has no Paused/Down state; no stoppage taxonomy; OEE referenced with no formula. CMMS work orders cover maintenance downtime; production micro-stops need a minimal downtime log + Paused state. **(M)**
**B16. Variance disposition & standard-cost revision** — whether variances are expensed or prorated into inventory at close is unstated; no procedure for re-standardization and on-hand revaluation. Decide policy (v1: expense to P&L) and a `RevalueAtNewStandard` action. **(S)**
**B17. Cost-center ownership ambiguous** — spec lists Cost_Center as a Mfg entity; in Moon, Accounting owns `cost_centers` (exists, report-aware). Decision: Accounting is system-of-record; Manufacturing only links to it (A18). **(S)**
**B18. Toll conversion-fee invoicing / e-invoicing untouched** — toll GR posts "DR Customer / CR Toll-service revenue" but no invoice document is specified; in Moon this must be a Sales service invoice flowing through the EInvoicing module (ZATCA/ETA compliance) — a regulatory touchpoint the spec ignores. **(S)**

### NICE-TO-HAVE

**B19. Multi-currency costing** — material price currency basis undefined; Moon's `CreateJournalEntry` handles FX, but standard costs should be declared base-currency-only. **(S)**
**B20. WIP physical count** — no period-end WIP inventory/count procedure. **(M)**
**B21. Serialized finished goods** — GR lines carry batch only; Moon's `product_serials` could serialize FG; spec silent. **(S)**
**B22. Quality sampling plans (AQL)** — spec defers quality wholly; QMS has criteria but no sampling/AQL tables; acceptable later. **(S)**
**B23. Arabic/RTL & bilingual fields** — spec has zero localization requirements; Moon convention (`name_ar`, English-first translate keys, RTL FE) must be imposed on all 26 entities. **(S)**
**B24. Reporting/BI pack** — only a variance Pareto is mentioned; no WIP aging, order cost sheet, capacity load, scrap analysis, OEE reports. Define a v1 report list. **(M)**
**B25. Phantom BOM semantics & substitute_items structure** — both enum/field named, semantics unspecified (pass-through explosion; substitute priority/ratio/validity). **(S)**

---

## Combined risk table

| # | Gap | Direction | Severity | Remedy (short) | Size |
|---|---|---|---|---|---|
| A1 | No GL posting from production | A | CRITICAL | Posting Actions over `CreateJournalEntry` (LIS pattern) | L |
| A2 | Production moves no stock | A | CRITICAL | `ApproveIssue`/`ApproveReceipt` integration (GRN pattern) | M |
| A3 | No MPS/MRP/CRP layer | A | CRITICAL | Build planning per spec 02 on existing demand/supply rails | L |
| A4 | Batch/lot not first-class | A | CRITICAL | Batch master + batch balances + genealogy + FEFO | L |
| A5 | Reservations: column, no logic | A | CRITICAL | `StockService::reserve()/release()` at Release/Issue | S |
| A6 | Item master mfg fields missing | A | CRITICAL | `mfg_item_mrp_settings` + `mfg_standard_costs` extension tables | M |
| A7 | No standard costing / 7-variance engine | A | CRITICAL | Variance Actions on Close + monthly job before period close | L |
| A8 | No WC calendar/shift capacity | A | CRITICAL | `mfg_work_calendars` + shifts; effective-capacity derivation | M |
| A9 | BOM versioning/Routing/Frozen snapshot | A | CRITICAL | Versioned routing entity + Release-time snapshot + status machine | M–L |
| A10 | Tool/Mold + cycle meters | A | CRITICAL | `mfg_tools` ↔ `cmms_assets` + `cmms_asset_meters` | M |
| A11 | No `CreatePurchaseRequest` Action | A | IMPORTANT | Extract Action; MRP → PR → PO | S |
| A12 | `ApprovalModule` lacks Production | A | IMPORTANT | Extend enum + label | S |
| A13 | No stored labor/piece rate | A | IMPORTANT | Rate on Work Center (+employee override) | S |
| A14 | Sales promised-date field missing | A | IMPORTANT | Add field + write-back Action | S |
| A15 | No consignment ownership bucket | A | IMPORTANT | Consignment warehouses (`is_consignment`) v1; ownership dimension deferred (D-15) | M |
| A16 | No events/Actions/tests in Production | A | IMPORTANT | Event spine + listeners + Pest coverage | M |
| A17 | No shop-floor terminal / realtime | A | IMPORTANT | POS-layout clone + broadcast channel | M |
| A18 | CC type + WC→CC link | A | IMPORTANT | Add `type` column + FK | S |
| A19 | Production movement types | A | NICE | Overload issue/receipt v1; enum later | S |
| A20 | No bin model | A | NICE | Warehouses-as-locations v1 | M |
| A21 | Lot/travel-card barcode printing | A | NICE | Print templates | S |
| A22 | Step-Down/Reciprocal allocation | A | NICE | Direct-only v1 | M |
| A23 | `cost_driver` has no storage home | A | CRITICAL | `production_centers.cost_driver` enum column (Phase 2) | S |
| A24 | WC capacity/rate columns unscheduled | A | CRITICAL | Schedule EXTEND: rates/capacity/driver Phase 2, calendar Phase 4 | M |
| A25 | FOVV budgeted-volume input | A | IMPORTANT | REUSE `budget_lines` (exists) + `budgeted_monthly_volume` column; else 6 variances (D-22) | S |
| A26 | Usage depreciation method (FA register exists) | A | IMPORTANT | `DepreciationMethod::UnitsOfProduction` + meter-driven entries | M |
| A27 | Inventory reference-type enums | A | IMPORTANT | `ProductionOrder` case on Receipt/Issue ReferenceType | S |
| B1 | UoM conversion policy unstated | B | CRITICAL | Policy addendum bound to Moon UoM engine | S |
| B2 | Lot traceability not a rule | B | CRITICAL | Genealogy rule + table (with A4) | M |
| B3 | Subcontracted operations missing | B | CRITICAL | `operation_type=Subcontract` + PO link + WIP-at-vendor | M |
| B4 | Shop calendar undefined | B | CRITICAL | Specify calendar entity now (with A8) | M |
| B5 | Rework flow undefined | B | IMPORTANT | Rework-order flow + costing addendum | M |
| B6 | Co/by-product + allocation method | B | IMPORTANT | Output structure + allocation enum | M |
| B7 | Reversal semantics | B | IMPORTANT | Define reversal algorithms per document | S |
| B8 | Backflush shortage handling | B | IMPORTANT | Policy keyed to `allow_negative_stock` | S |
| B9 | Over/under tolerance & partial close | B | IMPORTANT | Tolerance % config + auto-close rule | S |
| B10 | Pegging allocation policy | B | IMPORTANT | Priority rule + re-pegging on replan | S |
| B11 | Multi-plant netting ambiguity | B | IMPORTANT | Per-warehouse netting decision vs branches | M |
| B12 | No forecast source | B | IMPORTANT | Manual forecast entry/import v1 | M |
| B13 | ECO absent | B | IMPORTANT | Versioning + approvals as interim ECO | M |
| B14 | Return-to-stock missing | B | IMPORTANT | Production-return document (issue reversal) | S |
| B15 | Downtime/pause/OEE undefined | B | IMPORTANT | Paused state + downtime log; OEE later | M |
| B16 | Variance disposition / std revision | B | IMPORTANT | Expense-to-P&L policy + revaluation action | S |
| B17 | Cost-center ownership ambiguous | B | IMPORTANT | Decide: Accounting owns; Mfg links | S |
| B18 | Toll fee invoicing / e-invoicing | B | IMPORTANT | Sales service invoice via EInvoicing | S |
| B19 | Multi-currency costing | B | NICE | Base-currency standard costs | S |
| B20 | WIP physical count | B | NICE | Period-end WIP count procedure | M |
| B21 | Serialized FG | B | NICE | Optional FG serialization via `product_serials` | S |
| B22 | Sampling plans (AQL) | B | NICE | QMS extension later | S |
| B23 | Arabic/RTL/bilingual | B | NICE | Impose Moon `name_ar` convention on all entities | S |
| B24 | Reporting/BI pack | B | NICE | Define v1 report list (WIP aging, cost sheet, load, scrap) | M |
| B25 | Phantom BOM / substitutes semantics | B | NICE | Specify explosion + substitute structure | S |

**Headline:** 27 Moon-side gaps (12 critical) + 25 spec-side gaps (4 critical). The financial/stock
rails all exist and are proven (CreateJournalEntry, cost centers, budgets, fixed assets, UoM,
StockService, sequences, permissions) — the critical Moon work is the production-to-rails wiring
(GL, stock, reservations, planning, batch). The spec's worst blind spots are operational reality at
the edges: subcontracting, rework, returns, reversals, calendars, and traceability — every one must
be resolved as a written addendum BEFORE the build order starts, or it resurfaces as rework in
Execution/Costing. **Owner + timebox for those addenda is now scheduled: the roadmap's Phase 0
spec-addenda track (product architect + factory SME, one-week timebox, hard gate before Phase 1
exit).**

---


# 22 — Integration Contracts: Manufacturing (Modules/Production) ↔ Every Moon ERP Module

Execution-grade contract design. Pattern source: the proven LIS recipe — **Actions forward,
events backward** (`Modules/LIS/app/Actions/PostLabInvoice.php` calling
`Modules\Accounting\Actions\CreateJournalEntry`). Evidence: `md/04` (costing spec),
`md/06` (spec integration), `md/10`–`md/13` (ERP maps). Net-new items are marked **NET-NEW**.

> **Module identity (D-01, binding for every contract below):** the manufacturing layer lives
> INSIDE the extended `Modules/Production` — namespace `Modules\Production`, permission prefix
> `production.*` (registered contributor `production` in `PermissionDependencyRegistry`),
> sequence key `'production'` (already wired), settings namespace `production.*`, work centers =
> `production_centers` (EXTEND — there is NO `mfg_work_centers` table). "Manufacturing" below is
> shorthand for this extended Production module, never a separate `Modules\Manufacturing`.

---

## 0. The two prime rules

1. **Forward = synchronous Action call.** Manufacturing depends on (imports) Core, Inventory,
   Purchases, Sales, Accounting, HRM, QMS, CMMS. When Manufacturing needs something done in
   another module it calls `app(TargetModule\Actions\X::class)->execute(...)` inside the same
   DB transaction. Nothing else (no HTTP, no direct table writes into foreign modules except
   via their Actions).
2. **Backward = domain event.** No existing module ever imports `Modules\Production`.
   Cross-module *reactions* are implemented as **Manufacturing-side listeners** that consume
   Manufacturing's own events and then call the target module's Action — so the dependency
   arrow always points out of Manufacturing. The only inbound signals Manufacturing listens to
   are Core/Accounting events that already exist (`PeriodClosed`) or generic model events.

POS, WebStore and EInvoicing have **zero direct coupling** — they see Manufacturing only
through Inventory stock balances and normal Sales invoices.

---

## 1. Per-module interaction matrix

| Module | Manufacturing TAKES from it | Manufacturing GIVES to it | Mechanism |
|---|---|---|---|
| **Core** | Item master `products`/`product_variants` (BOM/order FKs); UoM `units` + `product_units` (conversion_factor); `SequenceService::generateNext(company,'production',entity)` (already wired); `SettingsService::get('production.*')`; `DataScope` branch scoping; `BaseModel`/TenantAware/Auditable; `Attachment`; `ApprovalWorkflowService` | Item manufacturing settings extension table `mfg_item_mrp_settings` (procurement_type, material_ownership, costing_method, lot rules, lead_time, safety_stock — AccBpExt pattern, no `products` rebuild); permission contributor registered to `PermissionDependencyRegistry` under the existing prefix `production`; **NET-NEW** `ApprovalModule::Production` enum case | FK (read) + Service calls forward; boot-time registry registration. No Core code calls Manufacturing |
| **Inventory** | `StockService::getIssueCost/getProductCost/getValuationMethod` (read-only costing); `ApproveIssue`/`ApproveReceipt` Actions — **the ONLY balance-mutation rail: `increaseStock`/`decreaseStock` are NEVER called directly** (every movement rides an `InventoryIssue`/`InventoryReceipt` document, "never touch balances directly"); `StockBalance::available_quantity`; `warehouses` (RM/WIP-staging/FG as warehouse rows); FIFO/WAC valuation | Reservations at Release (**NET-NEW** `StockService::reserve()/release()` writing `reserved_quantity`); `InventoryIssue` with `reference_type='production_order'` (RM→WIP); `InventoryReceipt` with rolled-up FG `unit_cost` (WIP→FG, copies `PurchaseGrnController::approve` lines 359–406); consignment bucket = dedicated `warehouses` rows flagged `is_consignment` for `material_ownership=Customer` (held, never valued, never purchasable — D-15) | Actions forward (`ApproveIssue`, `ApproveReceipt`); FK `reference_type/reference_id`; **NET-NEW** movement types `production_issue`, `production_receipt`, `production_scrap` + `ProductionOrder` case on the `ReceiptReferenceType`/`IssueReferenceType` enums |
| **Purchases** | Open `purchase_orders` (time-phased supply for MRP netting); GRN receipts restore availability; `purchases.inventory_account_id` fallback chain | MRP Planned Purchase Orders → `PurchaseRequest` + items (needed_by, priority, cost_center_id, source-demand tag) flowing through the normal `convertFromRequest` PO path | **NET-NEW** Action `Modules\Purchases\Actions\CreatePurchaseRequest` (extracted from `PurchaseRequestController::store`) called forward by the MRP run. Purchases never calls Manufacturing; next MRP run re-reads PO status |
| **Sales** | Confirmed `sales_orders`/`sales_order_items` = MPS demand (`confirmed_orders_qty`); MTO/Toll order triggers; delivery dates | CTP/ATP promised date write-back (**NET-NEW** column `sales_orders.promised_date` — single canonical name, D-16 — + thin Action `Modules\Sales\Actions\SetPromisedDate`); demand↔supply link via Manufacturing-owned `mfg_peggings (production_order_id, sales_order_id, allocated_quantity)`; FG availability on `GoodsReceiptPosted` | FK lives on Manufacturing side only (Pegging); Action forward for the promise write-back; Manufacturing listener on its own `GoodsReceiptPosted` updates pegged-order delivery readiness |
| **Accounting** | `CreateJournalEntry::execute($data,$lines)` — the ONLY GL rail; `cost_centers` + `journal_entry_lines.cost_center_id` dimension; `CostAllocationService`/`AllocationRule` (service→production CC, Direct method); `AutoAccountService::createChildAccount` (seed WIP/applied/variance accounts); open-period check; `PeriodClosed` event; `budgets`/`budget_lines` (`cost_center_id` + monthly amounts — EXISTS, feeds FOVV budgeted spend); `fixed_assets` register + `DepreciationEntry` (EXISTS — tool `fixed_asset_id` target) | 5 JE families per order (§4) + month-end OH absorption JE; every JE stamped `source_type='production_order'`, `source_id=order.id`, distinct `entry_type` per event; per-line `cost_center_id` from the work center | Action forward only. **NET-NEW**: `cost_centers.type` column (Production/Service/Auxiliary), `production_centers.cost_center_id` FK, settings keys `production.*_account_id` + `SettingDefinition` seeder, `DepreciationMethod::UnitsOfProduction` case for usage-based tool depreciation. Manufacturing variance run scheduled BEFORE `CloseFiscalPeriod` |
| **HRM** | Operator identity chain `users → employees.user_id` (unique); shifts/attendances context. Labor rate v1 = `production_centers.labor_cost_rate` (D-06 — HRM stores no hourly/piece rate today, only `basic_salary` derived in `PayrollService:48-49`); stored HRM rates + a `GetLaborRate` resolution Action are **DEFERRED post-v1** (optional employee override) | Piece-rate confirmed quantities per employee/period for payroll (`labor_calc=PieceRate`); actual labor hours per operation | Payroll feed = Manufacturing-side listener on `ConfirmationPosted` calling **NET-NEW** `Modules\HRM\Actions\RecordPieceworkEntry`, which writes the **HRM-owned** staging table `hrm_piecework_entries`; payroll reads its OWN table — HRM never imports Manufacturing and never reads an `mfg_*` table |
| **QMS** | Inspection results (`qms_inspections.result`) gate operation advance and GR `quality_status` {Released, OnHold, Rejected} | In-process inspection requests when `Routing_Operation.inspection_required` (tables already carry `production_order_id`); scrap-threshold NCRs; GR final-gate inspections | **NET-NEW** Actions `Modules\QMS\Actions\CreateInspection` / `CreateNonConformance` called forward from Manufacturing listeners on `ProductionOrderReleased`/`ConfirmationPosted`. **NET-NEW**: `operation_no` column on `qms_inspections`/`qms_inspection_plans` + real FK constraints on `production_order_id` once mfg tables exist |
| **CMMS** | Asset master for machines/molds (`cmms_assets`); PM schedules (`meter_field='cycles_used'`, `meter_threshold`); downtime windows for CRP capacity netting | Cumulative tool/mold cycle counts (cavity-adjusted) after each confirmation; PM trigger when shot-count threshold reached; downtime reports from SFC | Manufacturing holds the FK `mfg_tools.cmms_asset_id` (work-center↔asset link deferred). **NET-NEW**: `cmms_asset_meters` reading table + `Modules\CMMS\Actions\RecordMeterReading` + `CreateWorkOrder` + read-only query Action `Modules\CMMS\Actions\GetDowntimeWindows` (CRP consumes downtime via this Action — no raw `cmms_work_orders` table reads); Manufacturing listener on `MoldShotCountReached` calls them forward |
| **POS / WebStore** | — | FG availability only, **indirectly**: `GoodsReceiptPosted` → `ApproveReceipt` → `StockBalance` rises; POS/WebStore already read stock via Inventory | **Zero coupling.** No import either direction; Inventory is the mediator. Optional later: expose ATP endpoint for WebStore lead-time display |
| **EInvoicing** | — | Nothing direct. Toll-manufacturing conversion-fee revenue is billed as a normal Sales invoice (DR Customer / CR Toll-service revenue), which flows to EInvoicing through the existing Sales→EInvoicing pipe | **Zero coupling.** Relevance limited to ensuring the toll service item exists as a Core `Product` of type Service so the fee invoice e-invoices normally |

---

## 2. Named event contracts

All events live in `Modules\Production\Events` (canonical names per mapping/D-17), fired inside the emitting Action after commit
(or `afterCommit` listeners). Consumers are **Manufacturing-side listeners** that call foreign
Actions forward — preserving the dependency direction.

| Event | Emitter (Action) | Consumers (listeners → forward Action) | Payload fields |
|---|---|---|---|
| `ProductionOrderReleased` | `ReleaseProductionOrder` (Planned→Released; freezes BOM+Routing snapshot) | `ReserveOrderComponents` (→ `StockService::reserve`); `PrepareInProcessInspections` (→ `QMS\CreateInspection` plans for ops with `inspection_required`); SFC queue seeder (first op → Ready) | `order_id, order_no, company_id, branch_id, product_id, quantity, production_type, material_ownership, bom_snapshot_id, routing_snapshot_id, tool_id, warehouse_id, source_sales_order_ids[], released_by, released_at` |
| `MaterialIssued` | `PostMaterialIssue` (after `ApproveIssue` + JE) | WIP cost accumulator (order actual-cost ledger); reservation release; shortage/pegging refresh; SFC dashboard broadcast | `issue_id, issue_no, order_id, operation_no, issue_type, issue_level, warehouse_from_id, lines[{product_id, quantity, unit_id, unit_cost, total_cost, batch_no, ownership}], journal_entry_id, issued_by, issued_at` |
| `ConfirmationPosted` | `PostConfirmation` (posts labor + OH applied JEs; backflush issue if configured) | `AdvanceRouting` (next op → Ready or order → Completed; broadcast to next terminal — fires `OperationCompleted`); `AccumulateToolCycles` (cavity-adjusted; may fire `MoldShotCountReached`); `RecordPieceworkForPayroll` (PieceRate → `HRM\RecordPieceworkEntry` → HRM-owned `hrm_piecework_entries`); `EvaluateScrapForNcr` (reason_code threshold → `QMS\CreateNonConformance`); WIP accumulator | `confirmation_id, order_id, operation_no, work_center_id, tool_id, operator_user_id, employee_id, confirmation_type, yield[{grade_code, quantity, unit_sale_price}], scrap_quantity, rework_quantity, reason_code, setup_time_actual, run_time_actual, labor_hours, machine_hours, shift, journal_entry_ids[], confirmed_at` |
| `GoodsReceiptPosted` | `PostGoodsReceipt` (after `ApproveReceipt` + JE; NRV grade-cost allocation) | Pegging fulfillment (pegged sales orders → deliverable; promised-date status update via `Sales\SetPromisedDate` where needed); MPS/ATP refresh; QMS GR-gate follow-up for `OnHold` lines (default until Phase 6 QMS gates: `quality_status=Released` — D-21); order Completed check | `gr_id, gr_no, order_id, receipt_type, warehouse_to_id, lines[{item_id, grade_code, received_quantity, unit_cost, quality_status, batch_no}], journal_entry_id, received_by, posted_at` |
| `ProductionOrderClosed` | `CloseProductionOrder` (Completed→Closed; computes 7 variances; zeroes WIP) | Variance owner routing (notify Purchasing/Production/Management roles per variance type + tolerance band); Planning feedback queue (standard-cost revision candidates); Pareto report aggregator | `order_id, order_no, total_actual_cost, total_standard_cost, variances[{type∈{MPV,MUV,LRV,LEV,VOSV,VOEV,FOVV}, amount, percent, band∈{Normal,Monitor,Investigate}, owner_role}], journal_entry_id, settled_by, settled_at` |
| `MoldShotCountReached` | `AccumulateToolCycles` listener (when `cycles_used + Δ ≥ next meter_threshold`) | `RaiseToolMaintenance` (→ `CMMS\RecordMeterReading` + `CMMS\CreateWorkOrder` Preventive); tool status → Maintenance (blocks CRP allocation of that tool) | `tool_id, cmms_asset_id, work_center_id, cycles_used, life_cycles, threshold, last_order_id, reached_at` |
| `MrpRunCompleted` | `RunMrp` | `EmitPlannedPurchases` (→ `Purchases\CreatePurchaseRequest` per supplier/date bucket); `EmitPlannedProductionOrders` (create Planned `ProductionOrder` rows); exception-message notifier | `mrp_run_id, horizon_start, horizon_end, planned_purchase_count, planned_order_count, exceptions[{type, item_id, message, suggested_action}]` |
| *(inbound)* `PeriodClosed` (Accounting) | `CloseFiscalPeriod` | Manufacturing month-end guard: verifies OH-absorption + variance JEs were posted for the period; flags unsettled orders | per Accounting contract |

---

## 3. Named Action entry points Manufacturing calls (forward)

| # | Action / Service | Module | Status | Used for |
|---|---|---|---|---|
| 1 | `Modules\Accounting\Actions\CreateJournalEntry::execute($data,$lines)` | Accounting | **EXISTS** (proven by `PostLabInvoice`) | All 5 JE families + month-end absorption (§4). Guardrails: detail accounts only, open period required, may leave Draft |
| 2 | `Modules\Inventory\Actions\ApproveIssue::execute($issue,$userId)` | Inventory | **EXISTS** | RM→production issue at FIFO/WAC cost (`reference_type='production_order'`) |
| 3 | `Modules\Inventory\Actions\ApproveReceipt::execute($receipt,$userId)` | Inventory | **EXISTS** | FG receipt at rolled-up WIP unit cost (writes cost layer) |
| 4 | `Modules\Inventory\Services\StockService::getIssueCost / getProductCost / getValuationMethod` | Inventory | **EXISTS** | Cost preview for JE amounts; valuation method resolution |
| 5 | `Modules\Inventory\Services\StockService::reserve() / release()` | Inventory | **NET-NEW** (column `reserved_quantity` exists, no writer) | Reserve at Release, release at Issue/Cancel |
| 6 | `Modules\Purchases\Actions\CreatePurchaseRequest` | Purchases | **NET-NEW** (extract from `PurchaseRequestController::store`) | MRP planned purchases → requisition → existing `convertFromRequest` PO flow |
| 7 | `Modules\Sales\Actions\SetPromisedDate` | Sales | **NET-NEW** (thin; + `sales_orders.promised_date` column — D-16) | CTP write-back to sales orders |
| 8 | `Modules\QMS\Actions\CreateInspection` / `CreateNonConformance` | QMS | **NET-NEW** (tables ready: `qms_inspections.production_order_id`) | Operation gates, GR gate, scrap→NCR |
| 9 | `Modules\CMMS\Actions\RecordMeterReading` / `CreateWorkOrder` | CMMS | **NET-NEW** (`cmms_asset_meters` table missing; WO model exists) | Mold shot-count PM, downtime WOs |
| 10 | `Modules\CMMS\Actions\GetDowntimeWindows` | CMMS | **NET-NEW** (read-only query Action) | CRP subtracts maintenance downtime from available capacity — no raw `cmms_work_orders` reads |
| 11 | `Modules\HRM\Actions\RecordPieceworkEntry` | HRM | **NET-NEW** (writes HRM-owned `hrm_piecework_entries`) | Piece-rate payroll feed from confirmations; payroll reads its own table |
| 12 | `Modules\HRM\Actions\GetLaborRate(employee_id, labor_calc)` | HRM | **DEFERRED post-v1** (v1 rate = `production_centers.labor_cost_rate` — D-06) | Optional employee-rate override once HRM stores rates |
| 13 | `Modules\Accounting\Services\CostAllocationService::execute` | Accounting | **EXISTS** (Direct method) | Month-end service-CC → production-CC distribution before absorption variance |
| 14 | `Modules\Core\Services\SequenceService::generateNext(company,'production',entity)` | Core | **EXISTS** (key already wired) | order_no, issue_no, confirmation_no, gr_no, mrp_run_no |
| 15 | `Modules\Core\Services\SettingsService::get('production.*')` | Core | **EXISTS** (keys to seed) | Account mapping, tolerances, GRN-like mode flags |

---

## 4. Journal-posting map (costing cycle) — consistent with md/04 + md/12 §9

Every JE: `source_type='production_order'`, `source_id=order.id`, line `cost_center_id` = the
operation's work-center cost center (`production_centers.cost_center_id`, NET-NEW FK).
Accounts resolved via `SettingsService` keys (`production.wip_account_id`,
`production.raw_material_account_id`, `production.labor_applied_account_id`,
`production.moh_applied_account_id`, `production.fg_inventory_account_id`,
`production.scrap_expense_account_id`, `production.toll_revenue_account_id`,
7 × `production.*_variance_account_id`), seeded by `AutoAccountService` as **detail** accounts —
ONE namespace (`production.*`), identical to what Phase 0 seeds, so account resolution can never
split across two key spaces.

| Event | entry_type | DR | CR | Amount |
|---|---|---|---|---|
| Material issue | `production_material_issue` | WIP | Raw-Material Inventory | issued qty × FIFO/WAC cost (`getIssueCost`) |
| Labor applied (confirmation) | `production_labor` | WIP | Labor Applied (contra) | Hourly: hours × std rate · PieceRate: qty × piece_rate |
| Overhead applied (confirmation) | `production_overhead` | WIP | MOH Applied (contra) | cost_driver × overhead_rate — both per work center: `production_centers.cost_driver` (NET-NEW column) × `production_centers.overhead_rate` (exists) |
| Scrap (abnormal) | `production_scrap` | Scrap Expense (P&L) | WIP | scrap qty × accumulated unit WIP cost |
| FG receipt | `production_receipt` | Finished-Goods Inventory (per grade, NRV-allocated) | WIP | produced qty × std_total_cost (Standard) / allocated actual |
| Settlement / variances (Close) | `production_variance` | / CR variance accounts: MPV→Purchasing, MUV→Production, LRV+LEV→Production (n/a PieceRate), VOSV→Mgmt, VOEV, FOVV→Sales/Mgmt | WIP residual zeroed | residual WIP decomposed; adverse = DR variance, favourable = CR. Bands: <2% Normal, 2–5% Monitor, >5% Investigate |
| Month-end OH absorption | `production_oh_absorption` | Over/under-applied → P&L | MOH Applied cleared | applied (Σ event 3) vs actual CC expenses, after `CostAllocationService` distribution. MUST post before `CloseFiscalPeriod` |
| **Toll order** (material_ownership=Customer) | — | No material JE at issue (tracking-only consignment move); WIP carries conversion cost only (labor+OH); fee invoice via Sales: DR Customer / CR Toll-service Revenue → EInvoicing as normal | | |

---

## 5. Dependency rules (the law)

1. `Modules\Production` (manufacturing-extended) **imports forward**: Core, Inventory, Purchases,
   Sales, Accounting, HRM, QMS, CMMS — Actions/Services/Models read-only or via Actions.
2. **No module ever imports `Modules\Production`.** All reactions to manufacturing events are
   implemented as Production-owned listeners that call the target module's Action.
   New foreign Actions (#5–#11 above) are generic, Manufacturing-agnostic entry points added
   to their home modules. No foreign module ever reads an `mfg_*` table (the piecework payroll
   feed is pushed INTO HRM-owned `hrm_piecework_entries` via `RecordPieceworkEntry`).
3. Cross-module FKs live on the Manufacturing side only (`mfg_peggings.sales_order_id`,
   `mfg_tools.cmms_asset_id`/`fixed_asset_id`, `production_centers.cost_center_id`); foreign
   tables get at most nullable untyped reference columns (QMS already has `production_order_id`).
4. GL exclusively via `CreateJournalEntry`; stock exclusively via
   `StockService`/`ApproveIssue`/`ApproveReceipt`; numbering exclusively via `SequenceService`.
5. POS/WebStore/EInvoicing: zero direct contracts — Inventory and Sales mediate.
6. Every transactional flow: Action opens transaction → foreign Actions inside → events fired
   `afterCommit` → listeners idempotent (event payloads carry document numbers for dedup).

---


# 23 — Manufacturing Build Roadmap (Phased, Execution-Grade)

> Reconciles the spec's prescribed 12-step build order (`md/06-spec-integration.md` §6.4) with Moon ERP
> reality (existing `Modules/Production` skeleton — `md/10-production-now.md`; Inventory/Purchases rails —
> `md/11-inventory-purchases.md`; Accounting rails — `md/12-accounting-core.md`; neighbors/FE — `md/13-neighbors-fe.md`).
> Direction A (decided): **EXTEND `Modules/Production` in place** — zero data, zero inbound FKs, full FE +
> 19-permission scaffolding already exist. Rebuild is unwarranted.

---

## 1. Reconciliation: spec build order vs Moon ERP build order

Spec §6.4 prescribes: 1 Master Data → 2 MRP-Settings/StdCost → 3 MPS → 4 MRP → 5 CRP → 6 Production Order
→ 7 Material Issue → 8 Confirmation → 9 Goods Receipt → 10 Costing → 11 SFC → 12 Integrations (last).

**Three deliberate inversions for Moon ERP:**

| # | Spec says | Moon plan does | Why |
|---|---|---|---|
| 1 | Integrations LAST (step 12) | Integrations **woven into every phase** | Spec rule 00 §0.4-6 ("every actual event posts a GL entry, real-time, not batch") contradicts its own step 12. Moon's house recipe (`CreateJournalEntry` Action calls, `ApproveIssue`/`ApproveReceipt`) makes integration the cheapest part, and the existing module's single biggest gap IS the missing Inventory/GL posting. |
| 2 | Full Planning (MPS/MRP/CRP) before Execution | **Execution-with-real-postings FIRST**, Planning in Phase 4 | A factory can run manual production orders profitably without MRP; it cannot run MRP without trusted stock balances and BOMs. The existing module already has order lifecycle + FE — closing the stock/GL gap converts it from a "book-only toy" to a usable system in one phase. |
| 3 | MPS with forecast consumption early (step 3) | **MPS-lite deferred**; MRP v1 driven by confirmed `sales_orders` + reorder points | Moon has NO forecasting source (gap confirmed in `02-spec-planning` digest). Building forecast-consumption/time-fences before a demand source exists yields dead code. |

Everything else keeps the spec's dependency chain: Master Data → Order/Issue/Confirm/GR → Costing → Planning → SFC → Toll/advanced. The genericity mandate (00 §0.2: config field + branch, never hard-coded factory behaviour) governs every phase.

**Sizing legend (PHASE-level):** S ≈ ≤1 dev-week · M ≈ 2–4 dev-weeks · L ≈ 4–8 dev-weeks (single senior dev + Claude Code; FE included).
⚠️ Distinct from the GAP-level legend in 21 (S ≤ ~3 dev-days / M ≤ ~3 dev-weeks / L > 3 dev-weeks) — do not map gap sizes onto phase sizes when budgeting.

---

## 2. Phase 0 — Hardening & Foundations (S)

**Scope (no factory-visible features; everything later stands on this):**
- Consolidate the 3 defensive migrations (`2026_03_31_000001/000002/000003`) into clean additive migrations going forward; new tables use `mfg_` prefix; existing 7 tables are KEPT as-is (see §9 migration strategy).
- Create the missing recipe layers in `Modules/Production`: `app/Actions/`, `app/Events/`, `app/Listeners/`, `app/Services/`; wire `EventServiceProvider`.
- Register a `production` contributor in Core `PermissionDependencyRegistry` (mirror `LISServiceProvider::register('lis', ...)`) — expand/mapFor/presets/groupOrder for the existing 19 permissions.
- Add `Production` case to `Modules/Core/app/Enums/ApprovalModule.php` (+ `label()` arm + translation key).
- Activate the declared-but-unused `BomStatus` enum: cast on `BillOfMaterials`, backfill `status` from `is_active`.
- `SettingDefinition` seeder skeleton for the **`production.*`** keys (ONE settings namespace, matching the `production.*` permission prefix and `'production'` sequence key — the same namespace every posting Action resolves; C2 closed); Pest scaffolding (`tests/Feature`, `tests/Unit` currently `.gitkeep` only).
- **Spec-addenda track (owner: product architect + factory SME · timebox: 1 week, parallel to the hardening work · hard gate: no Phase 1 exit without it):** author the written addenda 21 mandates for all Direction-B gaps (B1–B25). D-01..D-22 below ratify the contested ones; B-gaps without a decision row (B5 rework flow, B6 co/by-products, B11 multi-plant netting, B19 currency basis, B20 WIP count, etc.) each get a one-page addendum signed off by the board.

**Modules touched:** Production, Core.
**Prerequisite gap-fixes:** none (this phase IS the fix layer).
**Usable increment:** none functional — engineering debt cleared; CI green with first Pest suite; all spec addenda written.
**Risks:** low. Migration consolidation must be proven idempotent on the dev DB that caused the original triple-guard pattern.

---

## 3. Phase 1 — Master Data v2 + Real Execution: stock-posting & GL-posting orders (L) ⭐ first usable increment

**Scope — tables:**
- Extend `bill_of_materials`: `bom_type` enum {Production, Engineering, Phantom}, `status` (BomStatus, now real), `effective_from/to`, `base_quantity` semantics; extend `bom_components`: `issue_type` {Manual, Backflush, AutoIssue}, `issue_level` {PerOrder, PerShift, PerOperation}, `operation_seq`, `scrap_percentage` (rename/alias `waste_percentage`); substitutes in the child table **`mfg_bom_substitutes`** (`bom_component_id`, `substitute_product_id`, `priority`, `ratio` — per mapping row 3, D-18; no JSON column); allow component `product_id` to itself own a BOM (multi-level recursion + cycle guard).
- Extend `production_orders`: `production_type`, `material_ownership`, `order_type` {Standard, Rework, Repair}, `wip_account_id`, snapshot stamps `bom_snapshot_version`/`snapshot_taken_at`; new statuses (see state machine below).
- New: `mfg_material_issues` + `mfg_material_issue_lines` (thin headers over Inventory `InventoryIssue` via `reference_type='production_order'`) and `mfg_goods_receipts` + lines (over `InventoryReceipt`). **Definitive per the mapping (D-19)** — the mfg doc tables exist precisely because Inventory documents have no home for `issue_type`/`issue_level`/`operation_no`/grade/`quality_status` fields; using Inventory documents directly is NOT an option. GR lines carry `quality_status` defaulting to **Released** until the QMS gates land in Phase 6 (D-21).

**Scope — state machine (replaces current draft/confirmed/in_progress/completed/cancelled):**
`Planned → Released → InProcess → Completed → Closed` (+ `Cancelled` from Planned/Released — spec gap filled per house pattern). `ProductionOrderStatus` enum rewritten with `can*()` guards; data mapping: draft→Planned, confirmed→Released, in_progress→InProcess, completed→Completed, cancelled→Cancelled (no rows exist in prod, so mapping is for dev/demo data only).
- **Release side-effects:** freeze snapshot (`production_order_materials`/`_operations` already copy BOM — formalize as immutable after Release), reserve stock, number via existing `SequenceService('production','order')`.
- **Close side-effects:** WIP residual settlement (Phase 1: settle to FG cost / P&L rounding; full variance decomposition arrives Phase 3).

**Scope — services/actions (all new, house recipe):**
- `Actions/ReleaseProductionOrder` — snapshot + `StockService::reserve()` per component.
- `Actions/IssueMaterials` — creates draft `InventoryIssue` (`reference_type='production_order'`) → `app(ApproveIssue::class)->execute()` (FIFO/WAC cost via `StockService::getIssueCost`) → posts **DR WIP / CR Inventory** via `Modules\Accounting\Actions\CreateJournalEntry` (`source_type='production_order'`, `entry_type='production_material_issue'`, lines stamped `cost_center_id`) → releases reservation.
- `Actions/ReceiveFinishedGoods` — `InventoryReceipt` at rolled-up WIP cost → `ApproveReceipt` (writes cost layer) → **DR FG / CR WIP**.
- `Actions/CloseProductionOrder` — WIP zero-out + lock.
- Domain events (canonical names per mapping/D-17): `ProductionOrderReleased`, `MaterialIssued`, `GoodsReceiptPosted`, `ProductionOrderClosed` (+ listeners doing the GL calls — keeps Actions composable).

**Scope — screens (Angular, extend `features/production`):** BOM editor v2 (versions/status/multi-level tree), order release/issue/receive dialogs, stock-aware availability panel.

**Modules touched:** Production, Inventory, Accounting, Core.

**Prerequisite gap-fixes (Direction A):**
1. **Inventory:** implement `StockService::reserve()/release()` writing the existing-but-orphan `reserved_quantity` column (`...500001:16`); make `ApproveIssue` check `available_quantity` not gross.
2. **Inventory:** add production `MovementType` values (`production_issue`, `production_receipt`, `scrap`) or adopt `reference_type` convention (decision D-05); add the `ProductionOrder` case to BOTH `ReceiptReferenceType` and `IssueReferenceType` backed enums (md/11: `reference_type` is enum-typed — A27).
3. **Accounting:** seed WIP / FG / Scrap Expense detail accounts via `AutoAccountService::createChildAccount()`; seed `production.{wip,fg,scrap}_account_id` setting keys (resolved via `SettingsService`, fallback chains à la `PostLabInvoice` — same `production.*` namespace Phase 0 seeded).
4. **Production:** link `production_centers` → `cost_centers` (`cost_center_id` FK; `account_id` column stays for the WC-level GL account).

**Usable increment:** a factory can define multi-level versioned BOMs and work centers, create → release → issue materials (real stock deduction at FIFO/WAC + WIP journal) → record output → receive FG into a warehouse at actual rolled-up cost (real stock + GL) → close the order. **Manual production with correct inventory and ledger — the module stops lying to accounting.**

**Risks:** state-machine rename ripples into Angular order service/guards (full parity exists — must update both sides atomically); WIP cost roll-up correctness (cover with Pest money-assertions); reservation race conditions (DB transactions + `lockForUpdate`). **Sizing risk:** this phase sits at the aggressive end of L for a single dev (BOM versioning + multi-level + atomic BE+FE state-machine rename + two posting rails + reservations + FE dialogs); pre-agreed fallback if it slips = split into 1a (master data + state machine) and 1b (posting rails), with "factory operational" moving to end of 1b.

---

## 4. Phase 2 — Routing, Tools/Molds, Operation Confirmations & Conversion Costing (L)

**Scope — tables:**
- `mfg_routing_headers` {item/product_id, routing_type {Production, Repair, Inspection}, version, lot_size_from/to, status, effective_from/to} + `mfg_routing_operations` {operation_no (steps of 10), work_center_id, tool_id nullable, setup_time, run_time_per_unit, **cavity_count default 1**, cycle_time, queue_time, move_time, inspection_required, critical_operation, required_skill_code}. `bom_operations` becomes legacy/migrated into routing (one auto-generated routing per existing BOM).
- `mfg_tools` {code, product_id, cavity_count, setup_time, status {Available, Mounted, Maintenance, Retired}, life_cycles, cycles_used, purchase_cost, `cmms_asset_id` FK→`cmms_assets`, `fixed_asset_id` FK→Accounting `fixed_assets` (register exists — verified)}.
- **Extend `production_centers` (the mapping row-6 work-center columns, scheduled HERE — closes the hidden prerequisite):** `capacity_unit`, `daily_capacity`, `capacity_multiplier`, `efficiency_percent`, `utilization_percent`, `setup_cost_rate`, `labor_cost_rate`, `machine_cost_rate`, `labor_calc` {Hourly, PieceRate}, **`cost_driver`** {LaborHours, MachineHours, DirectMaterialCost, UnitsProduced} (the overhead-rate denominator — A23), `bottleneck_flag`, `plant_location` (`calendar_id` follows in Phase 4 with the calendar entity).
- `mfg_confirmations` (header: order, operation_no, confirmation_type {Partial, Final, Reversal}, operator_id, work_center_id, shift, timestamp) + `mfg_confirmation_yields` (grade_code, quantity, unit_sale_price) + `mfg_confirmation_details` (scrap_quantity, rework_quantity, setup/run actual, labor_hours, machine_hours, reason_code).
- Extend `production_order_operations`: `status` {Waiting, Ready, InProgress, Completed} enum, `tool_id`, `confirmed_by`, `confirmed_quantity`, actual timestamps.

**Scope — logic:** `time_per_piece = cycle_time / cavity_count`; effective WC capacity = daily × multiplier × efficiency% × utilization%; **Backflush** auto-issue at confirmation (`consumed = input × BOM qty`; shortage policy = block, configurable — D-08); labor cost branches on `labor_calc` {Hourly: hours×rate | PieceRate: qty×piece_rate}; conversion-cost postings **DR WIP / CR Labor Applied + Machine/MOH Applied** per confirmation (`entry_type='production_labor'/'production_overhead'`; OH amount = `production_centers.cost_driver` × `overhead_rate`); multi-grade GR (one line per grade, NRV value-based allocation: `grade.cost = total × (qty×price)/total_sale_value`); tool `cycles_used += qty/cavity_count` on confirmation; usage depreciation `purchase_cost / life_cycles / cavity_count` into overhead, posted via `CreateJournalEntry` against the verified `fixed_assets` register (needs the net-new `DepreciationMethod::UnitsOfProduction` case — prerequisite 5).

**Scope — screens:** routing editor, tool registry, confirmation entry (qty + grade distribution + scrap/reason), operation board (precursor of SFC terminals).

**Modules touched:** Production, Accounting, Inventory, HRM, CMMS.

**Prerequisite gap-fixes (Direction A):**
1. **Labor rate source = `production_centers.labor_cost_rate` (D-06, per mapping §4):** HRM `employees.hourly_rate`/`piece_rate` are **deferred post-v1** as an optional employee override — NO HRM schema change in this phase (today only `basic_salary`; `PayrollService.php:48-49` derives at payroll-time only).
2. **CMMS:** add `cmms_asset_meters` time-series table so mold shot-counts accumulate and feed `cmms_pm_schedules.meter_field/meter_threshold` (today free-string with no data source).
3. **Accounting:** seed Labor Applied / MOH Applied accounts + `production.labor_applied_account_id` etc.
4. **HRM/SFC identity:** confirm `employees.user_id` (unique) as the operator→employee→rate chain.
5. **Accounting (tool depreciation):** fixed-asset register EXISTS (`fixed_assets`, `FixedAssetService`, `DepreciationEntry` — verified); add the net-new `DepreciationMethod::UnitsOfProduction` enum case + meter-driven `DepreciationEntry` generation (A26).

**Usable increment:** operation-level execution with actual times, hourly OR piece-rate labor, machine/overhead absorption into WIP, multi-grade output with value-based cost split, tool life tracking with PM triggers. **Real conversion cost — unit cost per grade is finally true.**

**Risks:** NRV allocation and cavity math are the spec's most "exotic" logic — pure-function unit tests mandatory; routing-vs-BOM `operation_seq` linkage integrity; CMMS meter table is a cross-module change needing CMMS owner sign-off.

---

## 5. Phase 3 — Costing Engine: Standard Costs, 7 Variances, Period Settlement (M)

**Scope — tables:** `mfg_standard_costs` {product_id, std_material/labor/overhead/total_cost, last_updated, update_frequency}; `mfg_variance_records` {production_order_id, variance_type, amount, percentage, classification, owner, investigation_flag}; **`mfg_item_mrp_settings` extension table CREATED here** (pulled forward from Phase 4 — D-20) carrying the per-item `costing_method` enum {Standard, Actual, Average, FIFO} (mixed methods supported) + procurement/ownership columns — per mapping rows 1/10/23 `products` is NOT bloated and the free-text `products.cost_method` stays deprecated/ignored (NO products enum migration); planning columns of the same table are populated in Phase 4; `cost_centers.type` column {Production, Service, Auxiliary}.

**Scope — logic/actions:**
- `Actions/RollUpStandardCost` — BOM × material std + routing × WC rates (recursive).
- `Actions/ComputeOrderVariances` on Close: MPV, MUV, LRV, LEV, VOSV, VOEV, FOVV (LRV/LEV n/a under PieceRate — deviation shifts to machine/OH per spec); tolerance bands <2% Normal / 2–5% Monitor / >5% Investigate; owner routing via notifications (Purchasing/Production/Management/Sales roles).
- GR posting switches (for Standard-method items) to **DR FG @std + DR/CR Variance / CR WIP @actual**.
- Monthly job: applied-vs-actual overhead → over/under-applied to P&L; runs **before** `CloseFiscalPeriod` (which rejects unposted entries); hook `PeriodClosed` guard.
- Reuse `CostAllocationService` + `AllocationRule` for Service→Production cost-center distribution (Direct method only v1; Step-Down/Reciprocal deferred — D-09).
- Variance Pareto report + cost dashboards.

**Modules touched:** Production, Accounting.
**Prerequisite gap-fixes (Direction A):** `cost_centers.type` column; 7 variance accounts + over/under-applied OH account seeded; standard-cost revaluation policy for on-hand stock (D-10); **FOVV inputs (A25):** budgeted fixed-OH spend per cost center REUSES the existing Accounting `budgets`/`budget_lines` rail (`cost_center_id` + monthly `m1..m12` — verified in backend); budgeted production VOLUME is net-new — add `production_centers.budgeted_monthly_volume` + entry screen; with no approved budget for the period, FOVV auto-descopes and 6 variances are declared (D-22).
**Usable increment:** management gets the spec's "most valuable output" — per-order variance decomposition with owners and tolerance routing, plus standard costing for FG while raw materials stay Average/FIFO.
**Risks:** variance math must reconcile to the GL to the cent (property-based Pest tests: Σ variances = WIP residual); period-close ordering with Accounting team.

---

## 6. Phase 4 — Planning: MRP, Pegging, Purchase Requisitions, CRP, MPS-lite (L)

**Scope — tables:** `mfg_item_mrp_settings` planning columns ACTIVATED (table created in Phase 3 — D-20; AccBpExt pattern over Core `products`: mrp_type, procurement_type {Buy, Make}, material_ownership, lot_sizing_rule {Exact, Fixed, MinMax, EOQ, PeriodOrder}, lot/min/max, safety_stock, lead_time_days, reorder_point); `mfg_mrp_runs` + `mfg_mrp_planned_orders` (transient, regenerated per run; each carries `source_demand_id` for pegging) + `mfg_mrp_exceptions` (late/reschedule-in/out/cancel); `mfg_crp_loads` {resource_id, resource_type, period, required_load, available_capacity, utilization_pct, status OVERLOAD/OK/UNDERLOAD}; `mfg_peggings` {production_order_id, sales_order_id, allocated_quantity}; `mfg_delivery_schedules`; **`mfg_work_calendars` + `mfg_calendar_shifts`** (working days/shifts/holidays — the spec's unspecified calendar, built here because CRP needs it; add `production_centers.calendar_id`, completing the row-6 work-center EXTEND begun in Phase 2).
- MPS-lite: `mfg_mps_headers/lines` capturing demand = confirmed `sales_orders` (+ manual forecast entry screen as the interim forecast source — D-11).

**Scope — logic:** level-by-level netting `Net = MAX(0, Gross − (on_hand + open POs + open WOs − reserved) + safety_stock)`; per-line scrap inflation; lot-sizing rules; lead-time offset; recursive BOM explosion for Make items; **toll branch**: `material_ownership=Customer` → never raise a PO, alert on shortage; outputs → Planned Production Orders (creates `production_orders` in Planned) and Planned POs → **new `Modules\Purchases\Actions\CreatePurchaseRequest`** (extracted from controller-only logic) → existing `convertFromRequest` flow; CRP loads from routing times ÷ effective capacity per calendar (maintenance downtime subtracted via the read-only `CMMS\GetDowntimeWindows` Action); CTP-lite promised date written back to Sales (D-12/D-16).

**Scope — screens:** MRP run cockpit (run → planned orders → firm/convert), exception inbox, CRP load board (per WC/tool, OVERLOAD red), pegging view per sales order, item MRP settings tab on product.

**Modules touched:** Production, Purchases, Sales, Inventory, Core.
**Prerequisite gap-fixes (Direction A):** `Purchases\Actions\CreatePurchaseRequest` (no programmatic PR creation today); `sales_orders.promised_date` column (canonical single name — D-16) + `Sales\Actions\SetPromisedDate` — Sales has no ATP/CTP field; calendar entity (net-new, spec gap).
**Usable increment:** planner runs MRP from real demand+stock, gets purchase requests and planned production orders with exception messages, sees capacity overloads before releasing, and every production order is pegged to the sales orders it serves.
**Risks:** MRP correctness on deep BOMs (golden-file Pest fixtures: melamine 6000-dish case from the spec, incl. 1-cavity 148% overload → 4-cavity 33h resolution); run performance (queue the run, chunked); pegging re-allocation policy on re-run is a spec gap — v1 = delete-and-rebuild non-firmed pegs.

---

## 7. Phase 5 — Shop Floor Control: Terminals, Auto-Advance, Live Dashboard (M)

**Scope:** NO new business engine (~80% exists per spec 05) — reuses `production_order_operations` as the live surface.
- Auto-advance: on terminal **[Done]** → create Confirmation (Phase 2 engine, GL implied) → `OperationCompleted` event → listener `routing.get_next()` → next op `Ready` + notify its work center; last op → order `Completed` (GR-ready).
- Terminal UI: clone `features/pos/pos-layout` pattern — per-WC Ready queue sorted by priority, large touch targets, [Start]/[Done], qty + grade + scrap/reason prompt, strict sequence enforcement.
- Supervisor dashboard: live per-order current-op + progress %, Waiting-queue-per-resource bottleneck board.
- Transport: polling v1 (30s) or Laravel Reverb websockets (D-13); offline-tolerant queueing deferred.
- New permissions `production.shopfloor.{operate,supervise}`.

**Modules touched:** Production, HRM (operator identity), FE.
**Prerequisite gap-fixes (Direction A):** broadcast channel decision; order `priority` already exists on `production_orders` (reuse for queue sort).
**Usable increment:** paperless floor — workers Start/Done at terminals, costs become live (no end-of-day entry), supervisors see bottlenecks in real time, actual times accumulate for future OEE/routing refinement.
**Risks:** UX on real shop hardware; concurrency (two operators, one operation — `lockForUpdate` + idempotent confirmations); spec gaps (no pause/downtime state) — add `OnHold` to operation enum as a Moon extension.

---

## 8. Phase 6 — Toll Manufacturing, Batch Genealogy & Neighbor Deep-Hooks (L)

**Scope:**
- **Toll/consignment (per mapping row 17, D-15):** dedicated consignment warehouses flagged `warehouses.is_consignment` (held, never valued, never purchasable); issue/GR lines carry `ownership` {Own, Customer}; a full ownership dimension on balances/movements stays deferred. Release skips reservation-as-asset; Issue = tracking-only movement (no RM credit); GR to customer ownership; invoice = conversion fee only (**DR Customer / CR Toll-Service Revenue** via Sales invoice integration).
- **Batch/lot first-class (Inventory gap-fix):** `inventory_batches` master + batch-keyed balances/cost-layer dimension; genealogy link issued-input-lots → produced-FG-lot; FEFO pick suggestion on issue. (Today batch_number is free-text; Serial is first-class, batch is not.)
- **QMS hooks:** add `operation_no` to `qms_inspections`/`qms_inspection_plans` + real FKs to `production_orders`; in-process gates on `inspection_required` operations; GR `quality_status` resolved from inspection result; `Actions/RaiseNcrFromScrap` (reason-code threshold → `qms_non_conformances`).
- **CMMS hook:** downtime work orders subtract from CRP available capacity.
- **HRM hook:** piece-rate confirmed quantities pushed to payroll via NET-NEW `Modules\HRM\Actions\RecordPieceworkEntry` → HRM-owned `hrm_piecework_entries` (payroll reads its OWN table; HRM never reads `mfg_*`).

**Modules touched:** Production, Inventory, Sales, Accounting, QMS, CMMS, HRM.
**Usable increment:** the factory can take customer-material toll jobs with clean books, trace any FG batch back to raw lots (recall-ready), gate operations on QC, and pay piece-rate from confirmations.
**Risks:** batch-keyed inventory is the largest cross-module schema change in the program — isolate behind `StockService`, feature-flag per company; toll GL treatment needs accountant sign-off.

**Explicit future phases (interfaces reserved, NOT in this roadmap):** APS detailed scheduling/Gantt, OEE computation (actual times already captured Phase 5), PLM/ECO change orders, IoT machine data, Step-Down/Reciprocal allocation.

---

## 9. Data Migration & Seed Strategy (existing 7 Production tables)

1. **Keep all 7 tables; rename nothing.** Zero inbound FKs from other modules and zero production data (verified) → migration risk near-zero. New tables take `mfg_` prefix; existing names stay for FE/API stability.
2. **Consolidate defensively:** Phase 0 adds ONE new baseline-verification migration asserting the 7 tables' final shape; the 3 guarded legacy migrations stay untouched (history) but all future change is additive `ALTER`s — no more `force_create`/raw-SQL pattern.
3. **Status mapping migration** (Phase 1): data-update migration maps draft→planned, confirmed→released, in_progress→in_process, completed→completed, cancelled→cancelled; enum strings stay snake_case in DB. Angular models updated in the same release.
4. **Column activation:** `production_centers.account_id` (dead) gains real use as WC GL account; add `cost_center_id`; `source/target_warehouse_id` on orders become required-at-Release.
5. **`bom_operations` → routing migration** (Phase 2): generate one `mfg_routing_header` (version 1.0, Active) per BOM that has operations; copy operations with `cavity_count=1`; keep `bom_operations` read-only for one release, then drop.
6. **Seeders:** `ProductionDatabaseSeeder` (currently empty) gains: (a) `ManufacturingSettingsSeeder` — `SettingDefinition` rows for all `production.*_account_id` keys (the module's single settings namespace); (b) `ManufacturingAccountsSeeder` — WIP, FG, Labor Applied, MOH Applied, Scrap Expense, 7 variance accounts via `AutoAccountService::createChildAccount()`; (c) `ManufacturingDemoSeeder` — the melamine stress-test dataset (powder RM, 1-cavity + 4-cavity molds, press WC, A/B/C grades) used by both demos and Pest golden tests; (d) permission appends to Core `RolePermissionSeeder` per phase.

## 10. Testing Strategy (Pest, house pattern, 80%+ per phase gate)

| Phase | Suite | Key assertions |
|---|---|---|
| 0 | `tests/Unit/Enums`, `tests/Feature/Permissions` | enum guards; PermissionDependencyRegistry expand/presets; ApprovalModule case |
| 1 | `Feature/OrderLifecycle`, `Feature/MaterialIssue`, `Feature/GoodsReceipt`, `Unit/SnapshotImmutability` | Release reserves stock; Issue decrements stock + posts balanced DR WIP/CR Inventory; GR posts DR FG/CR WIP; snapshot immune to BOM edit after Release; cancel forbidden after InProcess; multi-company isolation (TenantAware) |
| 2 | `Unit/CavityMath`, `Unit/NrvAllocation`, `Feature/Confirmation`, `Feature/Backflush`, `Feature/ToolLife` | time_per_piece=cycle/cavity; Σ grade costs = total; PieceRate vs Hourly postings; backflush shortage blocks; cycles_used increments + PM trigger |
| 3 | `Unit/VarianceFormulas` (property-based: Σ7 variances = WIP residual), `Feature/PeriodSettlement` | each variance formula vs golden numbers; tolerance band routing; settlement refused in closed period |
| 4 | `Unit/MrpExplosion` (golden-file melamine fixture), `Unit/LotSizing`, `Feature/MrpToPurchaseRequest`, `Feature/CrpOverload` | multi-level netting w/ scrap; toll branch never raises PO; 148%→33h cavity scenario; pegging totals = order qty |
| 5 | `Feature/AutoAdvance`, `Feature/TerminalConcurrency` | Done sets next op Ready; last op completes order; double-confirm idempotent |
| 6 | `Feature/TollFlow`, `Feature/BatchGenealogy`, `Feature/QmsGates` | customer stock never valued; FG lot traces to RM lots; inspection_required blocks advance |

Every GL-touching test asserts journal balance AND `source_type/source_id` linkage. CI gate: suite green + coverage ≥80% before phase exit (TDD per house rules).

## 11. Decisions Needed from Management

| # | Decision (القرار المطلوب) | Options (الخيارات) | Recommendation (التوصية) |
|---|---|---|---|
| D-01 | Module identity | New `Modules/Manufacturing` vs extend `Modules/Production` | **Extend Production in place** (zero data/FKs, FE+permissions exist) |
| D-02 | Order state machine | Keep current 5 statuses vs adopt spec machine (Planned→Released→InProcess→Completed→Closed +Cancelled) | **Adopt spec machine** with mapping migration |
| D-03 | Cost Center system-of-record | Mfg-owned vs Accounting-owned | **Accounting `cost_centers`** + add `type` column; WCs reference it |
| D-04 | Costing v1 method | Standard from day one vs Actual (FIFO/WAC) first, Standard in Phase 3 | **Actual first** — rails exist; standard needs the variance engine |
| D-05 | Inventory movement typing | New `MovementType` values vs `reference_type` convention only | **New enum values** (cleaner reporting) — Inventory owner sign-off |
| D-06 | Labor rate source | `employees.hourly_rate/piece_rate` vs WC rate vs HR service | **WC rate v1 (`production_centers.labor_cost_rate`); HRM stored rates + `GetLaborRate` deferred post-v1 as optional override** (matches mapping §4) |
| D-07 | Toll manufacturing timing | Phase 6 vs pull forward | **Phase 6** unless a toll customer is signed (then swap with Phase 4) |
| D-08 | Backflush shortage policy | Block confirmation vs allow negative vs partial | **Block (default), per-warehouse `allow_negative_stock` respected** |
| D-09 | Service-cost allocation methods | Direct only vs build Step-Down/Reciprocal | **Direct v1** (exists via `CostAllocationService`) |
| D-10 | Std-cost revaluation of on-hand | Revalue + revaluation JE vs new-receipts-only | **Revalue with JE** at standard activation (accountant sign-off) |
| D-11 | Forecast source for MPS | Manual entry screen vs import vs defer MPS entirely | **Manual entry + confirmed SO consumption (MPS-lite)** |
| D-12 | Sales promised-date writeback | Auto-write CTP date vs advisory only | **Advisory field v1** (`sales_orders.promised_date` — name per D-16), auto later |
| D-13 | SFC real-time transport | Polling vs Laravel Reverb websockets | **Polling v1, Reverb in Phase 5.1** if floor latency hurts |
| D-14 | Batch-keyed inventory scope | Full batch dimension on balances/layers vs genealogy-table-only | **Full batch dimension, feature-flagged per company** |
| D-15 | Consignment/toll ownership model | `warehouses.is_consignment` flag vs `ownership` dimension on balances/movements | **`is_consignment` warehouses v1** (mapping row 17); ownership dimension deferred |
| D-16 | Sales promise column name | `promised_date` vs `promised_delivery_date` vs `production_promised_date` | **`sales_orders.promised_date`** (mapping row 27) — single name everywhere, Action `SetPromisedDate` |
| D-17 | Canonical event/Action names | mapping set vs contracts/roadmap variants | **Mapping set ratified:** `ProductionOrderReleased, MaterialIssued, OperationCompleted, ConfirmationPosted, GoodsReceiptPosted, ProductionOrderClosed, MrpRunCompleted`; settlement Action = `CloseProductionOrder` |
| D-18 | BOM substitutes structure | `mfg_bom_substitutes` child table vs `substitute_items` JSON | **Child table** (priority + ratio — mapping row 3) |
| D-19 | Issue/GR document ownership | mfg doc tables wrapping Inventory rails vs Inventory documents directly | **mfg doc tables — definitive** (Inventory docs lack issue_type/issue_level/operation_no/grade) |
| D-20 | Per-item `costing_method` home & phase | `mfg_item_mrp_settings` vs `products.costing_method` enum | **`mfg_item_mrp_settings`, created in Phase 3** (no `products` bloat; planning columns filled Phase 4) |
| D-21 | GR `quality_status` before QMS gates | Auto-Released vs OnHold-by-default | **Auto-Released for Phases 1–5**; QMS-resolved from Phase 6 |
| D-22 | FOVV v1 policy | Compute from existing `budget_lines` + new budgeted volume vs descope | **Compute when an approved budget exists** (`budget_lines` REUSE + `production_centers.budgeted_monthly_volume`); otherwise auto-descope to 6 declared variances |

---

**Bottom line:** 6 phases (0:S, 1:L, 2:L, 3:M, 4:L, 5:M, 6:L). The factory is genuinely operational (real stock + real GL on manual orders) at the END OF PHASE 1; every later phase adds a self-contained capability (conversion costing → variances → MRP/CRP → live floor → toll/traceability) without rework, because integrations are built in from the start rather than bolted on at step 12.

---


# 24 — Adversarial Principal Review of the Four Decision Documents

Reviewed: `20-mapping.md`, `21-gaps.md`, `22-contracts.md`, `23-roadmap.md` against the spec
digests (`00..06`), the ERP evidence maps (`10..13`), and the original spec at
`files/manufacturing_spec/markdown`. Verdict up front: **the entity coverage is complete and the
GL/stock rails are respected, but the four documents do not agree on the module's identity
(naming/namespace), the roadmap silently reverses two mapping decisions, and several roadmap
phases have unscheduled prerequisites.** None of this is fatal; all of it will cause rework if
the docs are handed to builders as-is.

---

## 1. Spec entities silently dropped from the mapping

**Result: NONE dropped.** All 27 entities in `06-spec-integration.md` §6.2 are present in the
mapping's decision table (rows 1–27): Item, BOM_Header, BOM_Line, Routing_Header,
Routing_Operation, Work_Center, Tool (7 master data); MPS_Header, MPS_Line, Item_MRP_Settings,
MRP_Planned_Order, CRP_Load (5 planning); Production_Order_Header, Order_Component,
Order_Operation, Material_Issue_Header/Line, Confirmation_Header/Yield/Detail,
Goods_Receipt_Header/Line (10 execution); Standard_Cost, Cost_Center, Variance_Record (3
costing); Pegging, Delivery_Schedule (2 cross-cutting). The SFC layer (spec 05) is correctly
treated as a reuse of rows 15+18, matching spec 05 §5.3 ("does not introduce a new master table").

Near-misses worth recording (field/config-level, not entity-level):

- **`cost_driver` has no home.** Spec 04 §4.2 makes `cost_driver ∈ {LaborHours, MachineHours,
  DirectMaterialCost, UnitsProduced}` the overhead-rate denominator and the mapping declares a
  `CostDriver` enum in the skeleton, but NO table in the mapping carries a `cost_driver` column
  (work center? overhead pool? settings key?). The confirmation OH posting (`cost_driver ×
  overhead_rate`) cannot be computed without deciding where it lives.
- **`MRP_Planned_Order.source_demand_id`** (spec §6.2) is not explicitly listed among
  `mfg_mrp_planned_orders` columns in mapping row 11 — pegging depends on it.
- **Tally arithmetic is wrong:** mapping §2 claims "**20 NEW `mfg_*` tables**" but the document
  itself enumerates **24** distinct new tables (verified by grep: 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) — plus
  `mfg_piecework_log` appearing only in 22 (25th). Sizing inputs derived from "20" are understated.

---

## 2. Contract violations (house dependency rules / rail bypasses)

**Positive finding first:** no GL bypass anywhere — every posting in all four documents goes
through `Modules\Accounting\Actions\CreateJournalEntry`, and stock mutation is routed through
`ApproveIssue`/`ApproveReceipt` with the new `reserve()/release()` correctly contributed INTO
Inventory's `StockService`. The Sales-COGS "consuming module owns the GL" pattern is applied
correctly.

Violations and risks found:

- **V1 (real violation): HRM reads a Manufacturing table.** Contracts §1 HRM row: piece-rate
  payroll feed = "a `mfg_piecework_log` **HRM payroll reads**". A foreign module querying an
  `mfg_*` table is exactly the inbound dependency that contracts' own Rule 2 ("No module ever
  imports Manufacturing") forbids. Fix: Manufacturing-side listener pushes rows into an
  HRM-owned staging table via a new `Modules\HRM\Actions\RecordPieceworkEntry`, mirroring how
  every other contract in the same document is shaped.
- **V2 (violates the adopted decision): the entire contracts document is written for the wrong
  module.** 22 uses `Modules\Manufacturing\Events`, permission prefix `manufacturing` in
  `PermissionDependencyRegistry`, `SequenceService::generateNext(company,'manufacturing',…)`,
  `SettingsService::get('manufacturing.*')`, and tables `mfg_work_centers` / `mfg_item_settings`
  / `mfg_pegging`. The mapping (D-01, ratified in the roadmap) decided **extend
  `Modules/Production`**, prefix **`production.*`**, sequence module key **`'production'`**
  (already wired), settings keys **`production.*_account_id`** (mapping §2.4 row 25), and EXTEND
  `production_centers` (no `mfg_work_centers` table exists in the mapping). Implementing 22 as
  written re-creates the duplicate namespace/permission/sequence waste that mapping §1.4
  explicitly calls "pure waste plus a deprecation project".
- **V3 (bypass invitation):** Contracts §1 Inventory row lists
  `StockService::increaseStock/decreaseStock` among what Manufacturing "TAKES". Direct
  increase/decrease without an `InventoryIssue`/`InventoryReceipt` document breaks the document
  trail the mapping itself mandates ("never touch balances directly"). The contract should
  expose only `getIssueCost/getProductCost/getValuationMethod/reserve/release` plus the two
  Approve Actions.
- **V4 (soft):** "CRP reads `cmms_work_orders` directly (read-only query)" — a raw cross-module
  table read instead of an Action/Service. Forward direction, so tolerable, but inconsistent
  with the document's own "Nothing else (no … direct table writes/reads except via Actions)"
  posture; a `CMMS\GetDowntimeWindows` query service would be recipe-clean.
- **V5 (unverified reuse claim):** Mapping row 7 gives `mfg_tools.fixed_asset_id` an "FK →
  Accounting fixed asset for usage-based depreciation". **No evidence document (md/12 included)
  shows that Moon Accounting has any fixed-asset register at all.** Either the FK target does
  not exist (the claim is false and Accounting needs a fixed-asset build — a missed gap), or it
  was never audited. Roadmap Phase 2 books usage depreciation "into overhead" without any
  Accounting prerequisite for this.

---

## 3. Gap analysis quality (false gaps / missed gaps)

**False gaps: none found.** Every Direction-A claim was verified against the evidence maps:
A1/A2 (zero GL, zero stock movement — md/10 §5 grep evidence), A5 (`reserved_quantity` orphan —
md/11 §3), A6 (ProductType Product|Service only — md/11 §1), A8 (no calendar — md/10/13), A10
(no `cmms_asset_meters` — md/13 §4), A11 (PR creation controller-only — md/11 §5), A12
(ApprovalModule Sales|Purchases only — md/12 §7), A13 (PayrollService derives hourly at runtime —
md/13 §2), A14 (no ATP/CTP field on sales_orders — md/13 §1), A18 (no cost_centers.type —
md/12 §4). The "excluded candidates" table (UoM engine, cost centers, sequences, DataScope,
QMS) is also correct — these genuinely exist. One overstatement: the exclusions table says
QMS "NCR/CAPA with `production_order_id` columns waiting" — per md/13 only
`qms_inspection_plans`/`qms_inspections` carry `production_order_id`; `qms_non_conformances`
links only via `inspection_id`.

**Missed real gaps:**

1. **FOVV inputs do not exist.** Spec 04 expects "budget data per cost center and budgeted
   production volume" (expects-from-ERP #11); FOVV = (actual − **budgeted**) × std fixed OH/unit.
   Moon `cost_centers` has **no `budget_amount`/`manager_id`** (md/12 §4 column list) and no
   budgeting feature exists anywhere. The mapping hedges ("+ budget_amount if absent"); 21 has no
   gap entry; roadmap Phase 3 computes all 7 variances with no budget source scheduled. The
   7-variance engine is structurally incomplete without this.
2. **`cost_driver` storage** (see §1) — neither a gap entry nor a mapped column.
3. **Accounting fixed-asset register** (see V5) — required by the mapping's own tool-depreciation
   design, never audited, never listed as a gap.
4. **Inventory `ReceiptReferenceType`/issue reference enums:** md/11 shows
   `reference_type => ReceiptReferenceType::Purchase` is an enum; adding
   `'production_order'` requires extending those Inventory enums — absent from 21 and from
   mapping §4's "touches outside the module" table (only `MovementType` is listed).
5. **`production_centers` capacity/rate columns as a scheduled work item** — mapping row 6
   specifies ~14 new WC columns (daily_capacity, multiplier, efficiency, utilization,
   setup/labor/machine rates, labor_calc, calendar_id, bottleneck_flag…), but no gap entry and
   no roadmap phase ever schedules this EXTEND (see §4.1).

Direction-B critique is strong (subcontracting B3, reversals B7, return-to-stock B14 are real
spec blind spots correctly caught), and the "ATP/CTP is not a spec gap" exclusion is correct.

---

## 4. Roadmap: hidden prerequisites & sizing

1. **The work-center extension is scheduled nowhere.** Phase 2 posts labor/machine/OH from WC
   rates and computes effective capacity = daily × multiplier × eff% × util%, but no phase's
   table scope includes extending `production_centers` with those columns (Phase 1 adds only
   `cost_center_id`). Phase 4 CRP needs `calendar_id` + capacity fields too. Hidden prerequisite
   spanning Phases 2 and 4.
2. **Per-item `costing_method` ordering knot.** Phase 3 (costing) needs it; the mapping houses it
   on `mfg_item_mrp_settings`, which the roadmap builds in **Phase 4**. The roadmap "solves" this
   by migrating `products.cost_method` to an enum in Phase 3 — directly contradicting mapping
   row 1 ("do NOT bloat `products`") and row 23. Pick one home and one phase.
3. **Phase 3 FOVV has no budget source** (see §3.1) — either descope FOVV in v1 (declare 6
   variances) or schedule a budget input.
4. **Phase 2 tool depreciation** depends on the unverified Accounting fixed-asset capability and
   the "usage-driven depreciation variant [that] is net-new in Accounting" (mapping row 7) — not
   in Phase 2's prerequisite list.
5. **Phase 1 reopens a settled decision:** "OR use Inventory documents directly with reference
   linkage (**recommended**; fewer tables)" — the mapping decisively created
   `mfg_material_issues`/`mfg_goods_receipts` precisely because Inventory documents lack
   `issue_type`/`issue_level`/`operation_no`/grade fields. If the roadmap's recommendation is
   followed, those spec fields have nowhere to live; if not, the sentence is noise. Also Phase 1
   `substitute_items` JSON contradicts mapping's `mfg_bom_substitutes` table.
6. **The "addendum-first" mandate has no owner.** 21's headline: every spec-side gap (B1–B25)
   "must be resolved as a written addendum BEFORE the build order starts, or it resurfaces as
   rework". The roadmap allocates zero time/phase to authoring ~25 addenda; D-01..D-14 cover
   only some of them (B5, B6, B11, B19, B20 etc. have no decision row).
7. **Sizing legends conflict:** 21 defines S ≤ 3 dev-days / M ≤ 3 dev-weeks / L > 3 dev-weeks;
   23 defines S ≈ ≤1 wk / M 2–4 wks / L 4–8 wks. A board member mapping gap-sizes onto
   phase-sizes will mis-budget. Phase 1 itself (BOM versioning + multi-level + full state-machine
   rename rippling into Angular atomically + two posting rails + reservations + FE dialogs) is at
   the aggressive end of "L" for a single senior dev; the bottom-line "factory genuinely
   operational at END OF PHASE 1" assumes none of the D-decisions stall.
8. **Quality-status orphan window:** GR lines carry `quality_status` resolved from QMS
   (mapping row 22), but QMS hooks arrive only in Phase 6 — receipts shipped Phases 1–5 need a
   defined default behaviour (auto-Released?) that no document states.

---

## 5. Contradictions between the four documents

| # | Topic | 20-mapping | 21-gaps | 22-contracts | 23-roadmap |
|---|---|---|---|---|---|
| C1 | Module identity / namespace | Extend `Modules/Production`, permission prefix `production.*`, sequence key `'production'` | (follows 20) | **`Modules\Manufacturing`**, prefix `manufacturing`, sequence `'manufacturing'` | Extend Production (D-01) |
| C2 | Settings keys | `production.*_account_id` (§2.4 row 25) | `manufacturing.*_account_id` (A7) | `manufacturing.*` (§3 #13, §4) | `manufacturing.*` (Phases 0/1/2) |
| C3 | Item-settings table | `mfg_item_mrp_settings` | `mfg_item_settings` (A6) | `mfg_item_settings` | `mfg_item_mrp_settings` |
| C4 | Pegging / calendar tables | `mfg_peggings`, `mfg_work_calendars`+`mfg_calendar_shifts` | `mfg_calendars` (A8) | `mfg_pegging` | `mfg_pegging`, `mfg_calendars`+`mfg_calendar_shifts` |
| C5 | Work-center store | EXTEND `production_centers` | — | **`mfg_work_centers`** (nonexistent table, §1 CMMS + §5) | EXTEND `production_centers` |
| C6 | Sales promise column | `sales_orders.promised_date` | `promised_date` (A14) | `promised_delivery_date` | `production_promised_date` (D-12) |
| C7 | Event names | `ConfirmationPosted`, `OperationCompleted`, `ProductionOrderClosed`, `MaterialIssued` | — | `OperationConfirmed`, `ProductionOrderSettled`, `MaterialIssued` | `MaterialsIssued`, `FinishedGoodsReceived`, `ProductionOrderClosed`, `OperationCompleted` |
| C8 | BOM substitutes | `mfg_bom_substitutes` child table | — | — | `substitute_items` JSON v1 |
| C9 | Issue/GR documents | NEW `mfg_*` doc tables (definitive) | — | (follows 20) | "OR use Inventory documents directly (recommended)" |
| C10 | Consignment model | `warehouses.is_consignment` flag (rows 17) | either/or (A15) | "consignment bucket" (unspecified) | `ownership` dimension on balances/movements (Phase 6) |
| C11 | Per-item costing-method home | `mfg_item_mrp_settings`; "do NOT bloat products" | extension table (A6) | `mfg_item_settings` | `products.costing_method` enum migration (Phase 3) |
| C12 | Labor-rate resolution | WC rate v1, HRM deferred (§4 XS) | WC rate recommended (A13) | NET-NEW `HRM\GetLaborRate` Action in the confirmation path | WC rate v1 (D-06) but HRM columns added as Phase 2 prerequisite |
| C13 | Settlement action | `CloseProductionOrder` computes variances | `SettleProductionOrder` (A1) | `SettleProductionOrder` | `CloseProductionOrder` (Phase 1, no variances) |
| C14 | NEW-table tally | "20 NEW" claimed, 24 enumerated | — | +`mfg_piecework_log` (25) | — |

C1/C2 are the dangerous ones: registry contributor name, sequence module key, and settings-key
namespace are **runtime identifiers** — if Phase 0 seeds `production.*` keys while the
confirmation Action (built from 22) resolves `manufacturing.wip_account_id`, postings fail at
the first material issue.

---

## 6. What is sound (credit where due)

- Entity coverage is genuinely complete (27/27) with defensible verdicts; the
  extension-table pattern for Item and the Accounting-owns-cost-centers ruling resolve the
  spec's two ownership ambiguities correctly.
- Evidence discipline is high: every Direction-A gap traces to a file:line in md/10–13, and no
  claimed gap turned out to exist in the ERP.
- The three roadmap inversions (integrations woven in, execution before planning, MPS-lite) are
  well-argued and consistent with the spec's own contradiction (real-time GL rule vs
  integrations-last build order).
- The GL/stock rails are never bypassed; the JE family table (22 §4) matches md/12 §9 exactly.

## 7. Required fixes before board sign-off

1. Rewrite 22 against the D-01 decision: `Modules\Production`, `production.*` permissions,
   `'production'` sequence key, ONE settings namespace (recommend `production.*` to match the
   existing permission/sequence space), `production_centers` not `mfg_work_centers`. Or, if the
   board prefers the `manufacturing` namespace, change D-01's naming addendum — but pick one.
2. Re-route the piecework payroll feed through an HRM-owned Action/table (V1) and drop
   `increaseStock/decreaseStock` from the Manufacturing-facing Inventory contract (V3).
3. Audit Accounting for a fixed-asset register; add a gap entry (and Phase 2 prerequisite) or
   drop `fixed_asset_id` from `mfg_tools` v1.
4. Add gap entries + phase scheduling for: FOVV budget inputs, `cost_driver` storage,
   `production_centers` capacity/rate columns, Inventory reference-type enums.
5. Resolve C6–C11 into single named decisions (add D-15+ rows); fix the "20 NEW" tally; align
   the two sizing legends; assign an owner + timebox for the B1–B25 addenda demanded by 21.

---

---

# ADDENDUM (2026-06-11): Pharma CMO Client Requirements

> A real prospective customer (pharma contract manufacturer, "تصنيع للغير بالأوردر") described his
> operating cycle in a meeting with the owner. Sections 30-36 below: requirements formalization,
> coverage audit vs the published analysis, ERP evidence, pharma domain considerations, the
> exploratory-costing cycle design, the gated MTO cycle design (trial batch → deposit gate →
> reservation → stage screens), and the consolidated plan amendments (canonical entities:
> mfg_cost_estimates + mfg_order_cases; roadmap re-phased with Phase 2.5 costing funnel).
> The addendum passed its own adversarial review (37) — 24 findings, all fixed in place.



# 30 — Client Requirements Addendum: Pharma Contract-Manufacturer (Toll / Make-to-Order)

> **Source:** Owner interview, pharmaceutical factory operating **contract / toll manufacturing**
> ("تصنيع للغير بالأوردر" — make-to-order on behalf of a customer). This file formalizes the
> verbatim meeting summary into a numbered, testable requirements specification and maps every
> client statement onto standard contract-manufacturing terminology.
> **Scope of this addendum:** it ADDS the customer-facing front of the lifecycle (exploratory
> quotation, draft BOM, pilot batch, advance-payment gate, stage-gate screens) that the published
> analysis (`md/00..06` spec digests, `md/20` mapping, `md/22` contracts, `md/23` roadmap) did not
> cover. It does NOT re-open the already-ratified decisions on toll/consignment (D-15), reservation
> (A5), the production state machine, or the JE families (`md/22` §4) — it pegs the new requirements
> onto those existing rails.
> **Module identity (binding, D-01):** everything below lands inside the extended
> `Modules/Production` (`production.*` permissions, `mfg_*` new tables) plus the named reuse rails
> (Inventory / Accounting / Sales) already established in `md/20`–`md/22`.

---

## 0. Terminology mapping (client phrasing → standard CM term → Moon ERP anchor)

| # | Client said (Arabic) | Standard contract-manufacturing term | Moon ERP anchor (this addendum) |
|---|---|---|---|
| T-1 | دورة التكلفة الاستكشافية / تسعير مبدئي | **RFQ / quotation costing** (pre-cost estimate) | NEW `mfg_cost_estimates` + `mfg_cost_estimate_versions` (canonical names per `md/34` — this file's earlier provisional `mfg_quotations`/`mfg_quotation_costings` are retired); the customer-facing quote REUSES Sales `SalesQuotation` (D-29) |
| T-2 | تركيبة العميل، R&D تعمل BOM مبدئية | **Draft BOM** (engineering/estimating BOM, not yet released) | `bill_of_materials.bom_type=Engineering` + `status=Draft` (EXTEND already in `md/20` row 2) |
| T-3 | عينة / باتش تجريبي للعميل | **Pilot / sample (trial) batch** | NEW `mfg_trial_batches` — a real, costed production run tagged `order_type=Trial` |
| T-4 | دفعة تحت حساب | **Customer advance / down-payment gate** | NEW `mfg_customer_advances` (keyed to the `mfg_order_cases` case, `md/35`) + a gate blocking Release AND on-behalf purchasing; cash via `ReceiptVoucher`+`ApproveReceiptVoucher` (LA-1/D-33) |
| T-5 | العميل يورّد خامته وتظل ملكه | **Customer-supplied / free-issue (toll) material — consignment, customer property** | `material_ownership=Customer` + `warehouses.is_consignment` (D-15, already ratified) |
| T-6 | حجز الخامات لهذا الأوردر | **Order-pegged material reservation** | `StockService::reserve()/release()` (A5) pegged to the production order |
| T-7 | واجهات لكل مرحلة إنتاجية تنقل الأوردر | **Stage-gate workspaces / operation-stage screens** | NEW `mfg_order_stage_gates` (keyed to `mfg_order_cases.case_id`, `md/35`) driving the CASE stage machine from per-stage FE screens — interpretation of «مرحلة إنتاجية» logged as **OQ-9** (commercial case stages vs manufacturing operation stages) |
| T-8 | كل المستندات مربوطة بأوردر العميل | **Order-pegged document lineage** (everything pegs to the customer order) | `mfg_peggings` (`md/20` row 26) + `source_sales_order_id` stamped on every mfg doc |

---

## 1. Numbered requirements (R-01 .. R-18)

Each requirement: **Actor · Trigger · Inputs · Outputs · Business rule.** Identifiers in `code`.

### Cycle 1 — Exploratory Costing (RFQ / quotation)

**R-01 — Receive customer pricing request (RFQ intake)**
- **Actor:** Sales / front office.
- **Trigger:** customer submits a product with its specification/composition (تركيبته) and asks for a price.
- **Inputs:** customer id (Core `Product`-customer / Sales party), product description, target quantity (optional), customer-supplied spec/composition document (Core `Attachment`).
- **Outputs:** `mfg_cost_estimates` row, `status=Draft`, sequenced via `SequenceService::generateNext(company,'production','cost_estimate')`.
- **Business rule:** a quotation is NOT a sales order and creates NO stock/GL effect. It is the head of an order-pegged lineage (T-8); once it converts (R-08) every downstream document carries its `quotation_id` and resulting `sales_order_id`.

**R-02 — R&D drafts an estimating BOM**
- **Actor:** R&D department.
- **Trigger:** estimate routed to R&D (workflow assignment; estimate stays `Draft` — the lean `md/34` machine, routing is workflow metadata not status).
- **Inputs:** customer composition, Core `products` (raw materials), UoM `product_units`.
- **Outputs:** a **draft BOM** — `bill_of_materials` with `bom_type=Engineering`, `status=Draft`, linked to the estimate via `mfg_cost_estimates.draft_bom_id`.
- **Business rule:** a `Draft`/`Engineering` BOM can never be snapshotted into a production order (only an `Active`/`Production` BOM can be Released — guard reuses the `md/20` row 2 lifecycle), **with the single ratified D-32 carve-out: `order_type=Trial` orders may snapshot a Draft BOM under setting `production.trial_allow_draft_bom` (R-05/R-06); Standard orders never.** Draft BOM may reference not-yet-stocked materials (estimating placeholders).

**R-03 — Accounting computes preliminary (exploratory) costing**
- **Actor:** Accounting / cost engineer.
- **Trigger:** draft BOM ready (`DraftBomReady`; `status: Draft → Estimated` on compute).
- **Inputs:** draft BOM explosion, work-center rates (`production_centers.labor_cost_rate`/`machine_cost_rate`/`overhead_rate`), `cost_driver` (`md/20` row 6), draft routing if any, current material prices (`StockService::getProductCost`).
- **Outputs:** `mfg_cost_estimate_versions` row: `material_cost`, `labor_cost`, `overhead_cost`, `total_unit_cost`, `markup_percent`, `quoted_unit_price`, `valid_until` (price-validity period — see OQ-5/D-34).
- **Business rule:** costing reuses the same roll-up engine as `ComputeStandardCost` (`md/20` row 23) but runs against the **draft** BOM and produces an *estimate*, not a standard cost. No GL posting. Recompute is versioned (keep history; do not mutate — immutability per house rules).

**R-04 — Return preliminary product cost / quote to customer**
- **Actor:** Sales.
- **Trigger:** costing approved (`status: Estimated → Quoted`).
- **Inputs:** `mfg_cost_estimate_versions.quoted_unit_price`, `valid_until`.
- **Outputs:** the customer-facing `SalesQuotation` (REUSE, priced by push via `UpsertQuotationPricing`) + PDF via `print.service`; `status=Quoted`.
- **Business rule:** the quote carries an explicit validity window (`valid_until`); after expiry it must be re-costed (R-03) before it can convert (R-08). Quote is read-only once sent (new version on change).

### Cycle 2 — Production (make-to-order), Stage (a): Trial

**R-05 — Customer requests a trial/sample batch**
- **Actor:** Sales → R&D.
- **Trigger:** customer (post-quote) asks for a sample batch to evaluate, OR converts intent into a trial.
- **Inputs:** estimate id, trial quantity, target evaluation criteria.
- **Outputs:** `mfg_trial_batches` row + a real production order tagged `order_type=Trial`, `production_type=MakeToOrder`, pegged to the estimate/case/sales order.
- **Business rule:** **a trial is a real, costed manufacturing run** — it consumes real materials, accrues real WIP, and posts the normal JE families (`md/22` §4). It is owned/executed by **R&D** (not the line). It is distinguished only by `order_type=Trial` so its cost, scrap and yield are reported separately and excluded from standard-cost baselining until approved.

**R-06 — Execute and cost the trial run**
- **Actor:** R&D / pilot line.
- **Trigger:** trial order Released.
- **Inputs:** draft (or trial-finalized) BOM + routing, materials. (Snapshotting a **Draft** BOM into the Trial order is legal ONLY under the ratified D-32 carve-out, setting `production.trial_allow_draft_bom`.)
- **Outputs:** trial confirmations, trial WIP cost, trial yield/scrap; `mfg_trial_batches.actual_unit_cost`, `result ∈ {Pending, Passed, Failed}`.
- **Business rule:** the trial follows the same Release→Issue→Confirm→Receive→Close rails as a normal order (reuse, no new posting logic). Trial cost is captured so the final quote can be trued-up. **Who bears trial cost and what happens on trial failure are NOT specified by the client — see OQ-1, OQ-3.**

**R-07 — Customer evaluates trial**
- **Actor:** customer (recorded by Sales).
- **Trigger:** trial batch received/handed to customer **via the R-20 sample hand-over document** (trial output sits in the trial/evaluation bucket, never sellable FG).
- **Inputs:** customer decision.
- **Outputs:** `mfg_trial_batches.result` set to `Passed` or `Failed`; on `Passed` the lineage may proceed to R-08.
- **Business rule:** recording the decision is **gated on the R-20 hand-over document existing** (`mfg_trial_batches.sample_delivery_doc_id`). A `Failed` trial blocks formal-order creation until a new trial passes or the customer abandons (lineage closed). Re-trial loops back to R-05.

### Cycle 2 — Stage (b): Formal order → finalize → plan → advance → reserve → manufacture → receive

**R-08 — Convert to formal customer order**
- **Actor:** Sales.
- **Trigger:** customer agreement (after `Quoted` and/or trial `Passed`).
- **Inputs:** quotation, agreed quantity (e.g. 1000 units), agreed price, delivery date.
- **Outputs:** a confirmed Sales order (`Modules\Sales\SalesOrder`); the quotation `status=Converted`; `mfg_peggings` seed prepared.
- **Business rule:** **all structures/documents from here on are pegged to this customer order** (T-8). The formal order is the demand anchor for MPS/pegging (`md/20` rows 9, 26).

**R-09 — R&D finalizes the BOM**
- **Actor:** R&D.
- **Trigger:** formal order created.
- **Inputs:** the draft/trial BOM + trial learnings.
- **Outputs:** the draft BOM is promoted to a **production BOM** — `bom_type=Production`, `status=Active`, one Active version per (product, effectivity window) (`md/20` row 2 rule).
- **Business rule:** only an `Active`/`Production` BOM is eligible for order Release snapshotting. Promotion is a controlled lifecycle transition (Draft→Active), not an edit-in-place.

**R-10 — Planning determines requirements and checks readiness**
- **Actor:** Planning.
- **Trigger:** BOM finalized.
- **Inputs:** finalized BOM explosion × order qty (+scrap%), current stock (`inventory_stock_balances`), open POs/production orders, customer-supplied material on hand (consignment bucket).
- **Outputs:** required-quantity list (gross→net), shortage list, readiness verdict; for own-procured materials, **DRAFT purchase requests prepared but NOT submitted** — `CreatePurchaseRequest` (`md/22` #6) fires only after the advance gate (R-11) is satisfied, because the deposit exists precisely to fund that buying (U-6 rule).
- **Business rule:** readiness must separate **own-procured** materials (factory buys on the customer's behalf) from **customer-supplied** materials (free-issue, customer property, in the consignment warehouse — T-5/D-15, received via R-19). Customer-supplied materials are never purchased and never valued.

**R-11 — Customer advance / down-payment gate**
- **Actor:** Accounting (records receipt); gate enforced by `ReleaseProductionOrder`.
- **Trigger:** Planning ready AND materials need to be procured on the customer's behalf.
- **Inputs:** required advance amount/percentage, customer payment.
- **Outputs:** `mfg_customer_advances` row keyed to the **case** (`case_id`, `amount`, `percent`, `status ∈ {Due, Received, Waived}`, `receipt_voucher_id`, `journal_entry_id`); cash receipt rides **`ReceiptVoucher` + `ApproveReceiptVoucher`** with `reference_type='mfg_order_case'` and line account = Customer Advances — **the voucher engine's own JE (DR Cash/Bank / CR Customer Advances liability, `entry_type='production_customer_advance'`) is the single advance posting (LA-1/D-33; this file's earlier bare-`CreateJournalEntry` design is superseded — `md/32` §2 proved the voucher rail exists).**
- **Business rule:** **the advance gate blocks BOTH `ReleaseProductionOrder` (no reservation, no manufacturing) AND on-behalf purchasing (no `CreatePurchaseRequest` submission for billable-to-customer material) until satisfied** (`Received` or explicitly `Waived` by an authorized role) — the client ties the deposit to procurement, not only to release. The advance is later settled against the final invoice (`production_advance_settlement`, R-16/R-21). **Advance percentage is NOT specified by the client — see OQ-2/D-25.**

**R-12 — Reserve materials for this order (a side-effect OF Release, R-13)**
- **Actor:** `ReleaseProductionOrder` (system — reservation happens INSIDE the Release transition, per the ratified `md/20` row 13; it is never a separate stage that precedes Release).
- **Trigger:** `ReleaseProductionOrder` executing (which itself requires the advance gate, R-11).
- **Inputs:** snapshotted order components, stock balances.
- **Outputs:** `reserved_quantity` written per component (`StockService::reserve()`, A5), pegged to the order; tool reserved.
- **Business rule:** reservation is **order-pegged** (T-6) so two customer orders cannot promise the same stock. Customer-supplied components reserve against the consignment bucket; own components reserve against own stock. Reservation is released on Issue or Cancel.

**R-13 — Create the manufacturing order (Release)**
- **Actor:** Planning / production manager.
- **Trigger:** advance gate satisfied (R-11) and finances OK.
- **Inputs:** Active BOM + routing snapshot, work centers, tool.
- **Outputs:** production order `Planned → Released`; BOM+routing snapshot frozen into `production_order_materials`/`_operations`; reservation executed as a Release side-effect (R-12); `ProductionOrderReleased` event (`md/22` §2).
- **Business rule:** Release is the single point that freezes the snapshot and fires the reservation/inspection-prep side-effects atomically (already defined `md/20` row 13 / `md/22` — this fixes the earlier circular R-12↔R-13 ordering). The advance gate (R-11) sits BEFORE this transition.

**R-14 — Issue materials**
- **Actor:** Store keeper.
- **Trigger:** order Released.
- **Inputs:** reserved components.
- **Outputs:** `mfg_material_issues` → `ApproveIssue` → **DR WIP / CR Raw Materials** (own); customer-owned = tracking-only consignment move, no RM credit (`md/22` §4 toll row).
- **Business rule:** ownership branch per line (`ownership ∈ {Own, Customer}`); customer-supplied issues never credit own RM and never post material cost into WIP (conversion cost only). Reservation consumed here.

**R-15 — Manufacture (operation confirmations)**
- **Actor:** Operators / line.
- **Trigger:** order InProcess.
- **Inputs:** routing operations, labor/machine time, yield, scrap.
- **Outputs:** `mfg_confirmations` (+ labor/OH applied JEs); auto-advance to next operation.
- **Business rule:** reuse the confirmation engine and SFC auto-advance unchanged (`md/20` rows 18–20, `md/22` §2).

**R-16 — Finish & receive into finished-goods warehouse**
- **Actor:** Store keeper / system.
- **Trigger:** final operation confirmed.
- **Inputs:** produced qty/grade, WIP cost.
- **Outputs:** `mfg_goods_receipts` → `ApproveReceipt` → **DR FG / CR WIP** (own) OR toll branch: output to customer ownership + the on-behalf-material WIP relief (`production_toll_material_cogs`, R-21) + the final invoice; order Completed→Closed.
- **Business rule:** on a toll order the FG belongs to the customer; the final invoice — created forward via `Modules\Sales\Actions\CreateServiceInvoice` (net-new thin Action, U-4; never a controller call or direct Sales writes) — carries the **conversion fee** PLUS the **material pass-through line(s) for factory-bought-on-behalf RM** (R-21), and the customer advance (R-11) is settled against it (`production_advance_settlement`: DR Customer Advances / CR AR). Flows to EInvoicing (B18).

### Cross-cutting

**R-17 — Stage-gate screens (واجهات لكل مرحلة)**
- **Actor:** stage owners (R&D, Planning, Accounting, Store, Operators, Supervisor).
- **Trigger:** order present in any stage.
- **Inputs:** order id, current stage, allowed transitions for the actor's role.
- **Outputs:** a per-stage Angular workspace (a screen per stage) showing only that stage's data + the action button(s) that advance the order to the next stage; backed by `mfg_order_stage_gates` (audit of who moved the order, when, from→to).
- **Business rule:** each stage transition is permission-guarded (`production.*`) and maps onto the existing/extended order state machine (`Planned → Released → InProcess → Completed → Closed`) PLUS the customer-front pre-states (`Quotation`, `Trial`, `Awaiting-Advance`). Screens never bypass the state machine — the button calls the same Action the API exposes.

**R-18 — Order-pegged document lineage**
- **Actor:** system.
- **Trigger:** any mfg document created within a customer-order lineage.
- **Inputs:** the originating `quotation_id` / `sales_order_id`.
- **Outputs:** every quotation, trial batch, advance, production order, issue, confirmation, receipt carries the pegging keys; `mfg_peggings` links production↔sales.
- **Business rule:** the customer order is the spine (T-8). A lineage view must reconstruct: RFQ → draft BOM → quote → trial → formal order → finalized BOM → plan → advance → reservation → MO → issues → confirmations → receipt → toll invoice.

**R-19 — Receive & return customer free-issue material (consignment intake document)**
- **Actor:** Store keeper (intake/return); Production Action `ReceiveCustomerMaterial`/`ReturnCustomerMaterial` forward.
- **Trigger:** customer delivers his own raw material for the order; order/case close for the return leg.
- **Inputs:** case id, consignment warehouse (`is_consignment`, D-15), material lines + lot numbers.
- **Outputs:** a real **Inventory receipt document** approved via `ApproveReceipt` running a **tracking-only / no-valuation / no-GL branch** for consignment warehouses (the customer's property is held in bailment, never on our books); on close, unused material returns via the mirror tracking-only `ApproveIssue` document; a case-close reconciliation: received − issued-to-orders − consignment scrap (D-30) = returned.
- **Business rule:** **no stock balance is ever touched outside the Approve* document rails** (`md/22` §1) — the intake/return are first-class documents, not screen-side mutations; lots are written into batch genealogy (md/33 P3).

**R-20 — Trial-sample hand-over to the customer**
- **Actor:** Store keeper / Sales.
- **Trigger:** trial goods receipt completed.
- **Inputs:** trial order output, customer.
- **Outputs:** trial output received into a **trial/evaluation bucket** (warehouse-as-state, non-sellable FG); a **sample delivery document** (tracking-only issue out of the evaluation bucket) recorded on `mfg_trial_batches.sample_delivery_doc_id` + `sample_delivered_at`.
- **Business rule:** the customer evaluation (R-07) cannot be recorded before the hand-over document exists — the sample never "teleports"; ownership/valuation of the trial output follows the trial cost policy (D-23).

**R-21 — Bill back materials purchased on the customer's behalf (pass-through)**
- **Actor:** Accounting / system (with R-16 invoicing).
- **Trigger:** final invoicing of a toll order whose RM the factory bought on behalf of the customer.
- **Inputs:** billable-to-customer tagged POs/issues pegged to the case; contractual handling %.
- **Outputs:** on the toll FG receipt, the on-behalf material portion of WIP relieves via `production_toll_material_cogs` (DR Toll material COGS / CR WIP); the final invoice (R-16) carries a **material pass-through line at cost (+ handling %)** beside the conversion fee.
- **Business rule:** RM bought on the customer's behalf is **factory property until invoiced** (normal own stock, reserved/pegged to the case); title to the material content passes at delivery/invoice. The advance — sized as % of own-procured material cost (D-25) — therefore settles against an invoice that **contains the material it funded**: the cash math closes (closes review gap U-1).

---

## 2. Cycle 1 — Exploratory Costing flow (ordered)

1. Customer submits product + composition + (optional) target qty → Sales opens `mfg_cost_estimates` (`status=Draft`). **[R-01]**
2. Estimate routed to R&D; R&D builds a **draft BOM** (`bom_type=Engineering`, `status=Draft`). **[R-02]**
3. Routed to Accounting; cost engine explodes the draft BOM, applies work-center rates and `cost_driver`, produces a `mfg_cost_estimate_versions` row (`status=Estimated`; material+labor+overhead → unit cost → markup → quoted price → `valid_until`). **[R-03]**
4. Costing approved (`Quoted`); Sales returns the preliminary product cost / quote to the customer as a validity-dated `SalesQuotation`. **[R-04]**
5. END of Cycle 1. No stock, no GL, no commitment. Outcome is a priced offer that, if accepted, seeds Cycle 2.

## 3. Cycle 2 — Production (make-to-order) flow (ordered)

1. **Trial request:** customer asks for a sample batch → `mfg_trial_batches` + production order `order_type=Trial`, executed by **R&D**, pegged to the estimate/case. **[R-05]**
2. **Trial run:** real Release→Issue→Confirm→Receive→Close; trial cost/yield/scrap captured (`actual_unit_cost`); output lands in the trial/evaluation bucket and is handed over via the sample delivery document. **[R-06, R-20]**
3. **Trial evaluation:** customer decides (hand-over document required) → `result = Passed | Failed`. Failed ⇒ re-trial or abandon. **[R-07]**
4. **Formal order:** on agreement, Sales creates the confirmed customer order; **all downstream documents peg to it**. **[R-08]**
5. **Finalize BOM:** R&D promotes draft → `bom_type=Production`, `status=Active` (one Active version per effectivity window). **[R-09]**
6. **Planning & readiness:** required quantities computed (gross→net), shortages identified, own-procured vs customer-supplied materials separated; **draft** PRs prepared (not submitted). **[R-10]**
7. **Advance gate:** because the factory buys raw materials on the customer's behalf, the customer MUST pay a down-payment → `mfg_customer_advances` (`Received` via receipt voucher, LA-1); **Release AND on-behalf purchasing are blocked until satisfied** — PRs submit only now. **[R-11]**
8. **Consignment intake (if any):** customer free-issue material received into the consignment warehouse via the tracking-only receipt document. **[R-19]**
9. **Create MO (Release):** snapshot frozen, reservation executed as a Release side-effect (own stock and/or consignment bucket; tool reserved), `ProductionOrderReleased`. **[R-13, R-12]**
10. **Issue materials:** WIP debited for own materials; customer-supplied issued as tracking-only consignment moves. **[R-14]**
11. **Manufacture:** operation confirmations, labor/OH applied, auto-advance. **[R-15]**
12. **Finish & receive into FG warehouse:** DR FG / CR WIP (own) or toll branch (customer-owned output + final invoice = conversion fee **+ material pass-through line (R-21)**, advance settled against it); unused customer material returned/reconciled (R-19); order Completed→Closed. **[R-16]**
13. Stage-gate screens (**R-17**) move the order between every step above; lineage (**R-18**) keeps it pegged to the customer order throughout.

---

## 4. New / changed Moon ERP objects this addendum introduces

| Object | Verdict | Notes |
|---|---|---|
| `mfg_cost_estimates` | **NEW** | estimate header / RFQ head (canonical name per `md/34`): customer, prospect product, qty, `draft_bom_id`, `sales_quotation_id` (REUSE Sales doc), lean `status ∈ {Draft, Estimated, Quoted, Won, Lost}`, `sales_order_id` (on Won). Seq key `'production'`/`cost_estimate`. |
| `mfg_cost_estimate_versions` | **NEW** | versioned, immutable estimate runs (canonical per `md/34`): material/labor/overhead, `total_unit_cost`, `markup_percent`, `quoted_unit_price`, `valid_until`, `price_basis`. |
| `mfg_order_cases` | **NEW** (per `md/35`) | the Cycle-2 commercial umbrella: 14-stage case machine, deposit fields, `consignment_warehouse_id`, `active_bom_id`; 1 case ↔ N production orders (trial loops + main). The order state machine stays untouched. |
| `mfg_trial_batches` | **NEW** | `case_id`, `cost_estimate_id`, `production_order_id` (the `order_type=Trial` order), `trial_quantity`, `actual_unit_cost`, `result ∈ {Pending, Passed, Failed}`, `evaluated_at`, `sample_delivery_doc_id`/`sample_delivered_at` (R-20). Kept separate from the order (vs `md/32` §4's flag-only lean) because one case spans N re-trial orders each carrying its own verdict/sign-off/hand-over. |
| `mfg_customer_advances` | **NEW** | **`case_id`** (one advance per engagement, `md/35`), `amount`, `percent`, `status ∈ {Due, Received, Waived}`, `receipt_voucher_id`, `journal_entry_id` (the voucher's JE — LA-1), `applied_invoice_id`. |
| `mfg_order_stage_gates` | **NEW** | audit + transition table backing the per-stage screens, keyed to the case (`md/35`): `case_id`, `from_stage`, `to_stage`, `actor_user_id`, `gate_snapshot`, `at`. |
| `bill_of_materials.bom_type/status` | **EXTEND** (already in `md/20` row 2) | `Engineering+Draft` = the exploratory draft BOM; `Production+Active` = finalized. No new column needed beyond the ratified extension. |
| `production_orders.order_type` | **EXTEND** | add `Trial` to the `{Standard, Rework, Repair}` enum (`md/20` row 13) so a trial run is a first-class, separately-reported order. |
| `ReleaseProductionOrder` Action | **EXTEND** | add the advance-payment precondition (R-11) before snapshot/reserve; the same gate also holds back on-behalf `CreatePurchaseRequest` submission. |
| `mfg_peggings` | **REUSE** (`md/20` row 26) | extended lineage anchor; add `cost_estimate_id` + `case_id` to the peg chain. |
| Consignment intake/return documents | **EXTEND** (Inventory docs, R-19) | tracking-only `ApproveReceipt`/`ApproveIssue` branch for `is_consignment` warehouses — the free-issue intake and the unused-material return ride real documents, never direct balance writes. |
| Trial-sample delivery document | **NEW thin doc** (R-20) | tracking-only issue out of the trial/evaluation bucket; referenced from `mfg_trial_batches`. |
| Accounting `entry_type` | **EXTEND** | `production_customer_advance` (the voucher's JE, LA-1/D-33), `production_advance_settlement` (DR Customer Advances / CR AR at invoicing — single canonical name), `production_advance_refund` (D-35), the D-23 trial-policy family (`production_trial_absorb/_defer/_apply/_writeoff`), `production_toll_material_cogs` (R-21); setting key `production.customer_advance_account_id`, seeded via `AutoAccountService`. |

All NEW tables follow the `mfg_*` prefix, `BaseModel` (TenantAware/Auditable), `SequenceService`
numbering, `production.*` permissions, and the Actions-forward / events-backward contract law
(`md/22` §5). No foreign module imports `Modules\Production`.

---

## 5. What the client did NOT specify (open questions — require sign-off before Phase build)

| # | Open question | Why it matters | Proposed default (pending board) |
|---|---|---|---|
| **OQ-1** | **Who pays for the trial batch?** Customer, or factory absorbs it as cost-of-sale? | Determines whether the trial run bills the customer (Sales invoice) or posts to an internal R&D/marketing expense; affects R-06 JE. | Configurable per case: `trial_cost_policy ∈ {Bill, Absorb, CreditOnWin}` with the `md/35` §1.1 posting family (ratified as D-23 — supersedes this file's earlier two-valued `{Customer, Absorbed}` proposal). |
| **OQ-2** | **Deposit / advance percentage?** Fixed %, per-customer, per-order, or cost-of-materials-based? | Drives R-11 gate amount and credit policy. | Configurable `production.default_advance_percent` setting + per-order override; v1 = % of own-procured material cost. |
| **OQ-3** | **What happens if the trial fails?** Re-trial (who pays the re-run?), renegotiate, or abandon? | Affects R-07 loop, cost ownership, and lineage closure. | Re-trial allowed; cost ownership inherits OQ-1 decision; N failed trials ⇒ lineage auto-flagged for review. |
| **OQ-4** | **Scrap ownership for customer-supplied (free-issue) materials.** Who owns scrap/waste of customer property — returned, billed, or written off against the customer? | Toll/consignment scrap is customer property; cannot be valued as own scrap expense. | Track consignment scrap separately (no own-scrap JE); reconcile/return to customer per contract; surface in a consignment movement report. |
| **OQ-5** | **Price validity period** for the quote (`valid_until`). | Stale quotes must be re-costed before conversion (R-04→R-08). | Configurable `production.quote_validity_days` (v1 = 30); expired quote blocks conversion until re-costed. |
| **OQ-6** | Advance refund / forfeiture policy on customer cancellation after advance received. | Liability handling for `Customer Advances` account. | Per-contract; default = applied to incurred cost, balance refundable. |
| **OQ-7** | Are the "stage screens" role-exclusive (each stage one department) or can one user span stages? | Drives permission granularity for R-17. | Per-stage permission slice under `production.*`; supervisor role can span all. |
| **OQ-8** | Minimum/target trial quantity vs full-order quantity relationship. | Trial costing true-up to full order. | Trial qty is independent; full-order cost re-estimated from trial actuals. |
| **OQ-9** | **What does «واجهات لكل مرحلة إنتاجية» actually mean?** Commercial CASE stages (R&D/Planning/deposit/reserve workbenches — the reading designed in `md/35` §4) or per-MANUFACTURING-stage screens (mixing/granulation/compression/packaging — pharma-plausible)? | Determines whether R-17 is satisfied by the case workbenches (Phases 2–2.5) or additionally needs per-operation workbenches over routing operations (Phase 5 SFC). | Ask the client at the Phase-0 gate. Default: case workbenches Phases 2–2.5; if the manufacturing-stage reading is confirmed, per-operation screens ride Phase 5 SFC with an early read-only per-operation monitor in Phase 2.5. |

---

## 6. Roadmap amendments (to `md/23`)

This addendum is **additive** and slots ahead of the order lifecycle without disturbing the
existing phase sequence:

- **Phase 0 (Spec-addenda track):** ratify the open questions via **the authoritative decision
  register `md/36` §4 (D-23..D-36 + OQ-9)** — NOT a positional OQ-x→D-x mapping (this file's
  earlier "OQ-1..OQ-8 = D-23..D-30 in order" is retired; e.g. OQ-1/OQ-3→D-23, OQ-2→D-25,
  OQ-4→D-30, OQ-5→D-34, OQ-6→D-35, OQ-7→D-28, OQ-8→D-36). **Hard gate: no customer-front build
  without these decisions.**
- **New Phase 2.5 — "Customer Front" (M), after Phase 1 (real execution) AND Phase 2
  (routing/confirmations — labor/machine rates + `cost_driver` land there, so exploratory costing
  cannot price conversion before it), before Phase 4 (Planning):** `mfg_cost_estimates` +
  `mfg_cost_estimate_versions`, draft-BOM lifecycle (rides the Phase 1 BOM extension),
  `mfg_trial_batches` + the evaluation bucket/sample hand-over (rides the Phase 1 order rails with
  `order_type=Trial`), and the Cycle-1/case stage screens. The case core, deposit gate
  (`mfg_customer_advances` + the `ReleaseProductionOrder` precondition) and `mfg_order_stage_gates`
  land already in Phase 2 — see `md/36` §3 (which renamed this slot from the provisional "1.5").
- No change to the toll/consignment, reservation, confirmation, or variance phases — this addendum
  consumes them.

---

## 7. Citations

- Toll / `material_ownership=Customer` / consignment, customer property: `md/03-spec-execution.md`
  L27-28, L73-75, L107, L128-130, L274-278; `md/20-mapping.md` rows 13/16/17; `md/22-contracts.md`
  §1 (Inventory row), §4 (toll JE row); D-15 (`md/21-gaps.md` A15).
- Reservation at Release: `md/21-gaps.md` A5 (L55-58); `md/22-contracts.md` §3 #5
  (`StockService::reserve/release`).
- Draft/Engineering BOM lifecycle + Active/Production promotion: `md/20-mapping.md` row 2.
- Standard-cost roll-up engine reused for estimating: `md/20-mapping.md` row 23 (`ComputeStandardCost`).
- Order state machine + Release snapshot/reserve side-effects: `md/20-mapping.md` row 13;
  `md/22-contracts.md` §2 (`ProductionOrderReleased`); `md/23-roadmap.md` §3 (Phase 1 state machine).
- Pegging / demand anchor on customer order: `md/20-mapping.md` row 26; `md/02-spec-planning.md`
  L19, L89, L282-290; `md/22-contracts.md` §1 (Sales row).
- JE families: `md/22-contracts.md` §4; `md/03-spec-execution.md` L305-317. Customer-advance
  cash-in rides `ReceiptVoucher`+`ApproveReceiptVoucher` per `md/32` §2 (LA-1/D-33 — amended from
  this file's original bare-`CreateJournalEntry` design); all other postings stay
  `CreateJournalEntry`-only.
- Toll fee invoicing via Sales→EInvoicing: `md/21-gaps.md` B18; `md/22-contracts.md` §1 (EInvoicing row).
- Module identity / `mfg_*` prefix / Actions-forward law: `md/20-mapping.md` §1; `md/22-contracts.md`
  §0 (D-01), §5.
</content>

---


# 31 — Coverage Audit: Pharma Toll-Manufacturing Client Workflow vs Published Analysis

> **Addendum to the Manufacturing × Moon-ERP analysis (reports 00–24).** Source of requirements:
> client interview — a pharmaceutical *contract manufacturing / toll* factory ("تصنيع للغير بالأوردر").
> This file audits EVERY requirement derived from that interview against the published analysis
> (`md/20-mapping.md`, `md/21-gaps.md`, `md/22-contracts.md`, `md/23-roadmap.md`, spec digests `md/00..06`)
> and emits the definitive list of NEW work items the published plan does not yet contain.
>
> **Verdict legend:** ✅ FULLY COVERED (cite where) · 🟡 PARTIALLY COVERED (what exists vs what's missing) ·
> ❌ NOT COVERED (net-new). Requirement IDs **IR-1..IR-9** are derived below.
> **Numbering note (post-review):** this file's derived requirements were renumbered
> **R-01..R-09 → IR-1..IR-9** to end the collision with `md/30`'s authoritative requirement scheme
> (R-01..R-21); its decision rows were re-aligned to the single authoritative register in
> `md/36` §4 (the former "D-23 pre-sales scope" here is now **D-31**).

---

## 0. Derived requirements (IR-1..IR-9)

The interview describes **two business cycles** plus a cross-cutting UI demand. Decomposed:

### Cycle 1 — Exploratory Costing (دورة التكلفة الاستكشافية)
- **IR-1** Customer brings a product + its composition (تركيبته) and requests *exploratory pricing* →
  an intake/request document (a **manufacturing RFQ / costing request**) must exist as a first-class entity.
- **IR-2** R&D department authors a **DRAFT BOM** for that request (recipe not yet productionized).
- **IR-3** Accounting computes a **preliminary / exploratory cost** off that draft BOM and returns a
  **quote (preliminary product cost)** to the customer. This is a *costing-only, pre-order* pass —
  no stock, no GL, no production order.

### Cycle 2 — Production, make-to-order (دورة الإنتاج)
- **IR-4** Every structure/document in this cycle is **tied to the customer order** (order-as-anchor;
  full traceability from sales order → BOM → trial → production order → receipt).
- **IR-5** **TRIAL / sample batch** stage (دورة التجربة): R&D produces a real test batch with **real cost**;
  it needs its own cycle/order type distinct from a normal production order, and the customer evaluates it.
- **IR-6** On customer agreement → **formal order** → R&D **finalizes the BOM** (Draft → Active) →
  **Planning** computes required quantities + checks readiness.
- **IR-7** **DEPOSIT / down-payment gate** (دفعة تحت حساب): because the factory buys raw materials *on
  behalf of the customer*, a customer down-payment must be **received and verified BEFORE** materials are
  reserved/purchased. This is a hard financial gate inserted between "planning OK" and "reserve".
- **IR-8** **Customer-supplied raw materials** that remain **the customer's property** (consignment) may be
  used alongside factory-purchased materials, mixed within the same order.
- **IR-9** (cross-cutting) **Dedicated per-stage workspace SCREENS** (واجهات لكل مرحلة إنتاجية) so the order is
  advanced from stage to stage from those screens — beyond shop-floor operator terminals; these are
  back-office *stage workbenches* (R&D, Planning, Finance-gate, Reserve/Issue, etc.).

After IR-6 the order rejoins the already-mapped chain: finalize BOM → Planning → (deposit gate, IR-7) →
reserve → manufacturing order → issue → manufacture → finish → receive into FG warehouse.

---

## 1. Verdict table (IR-1..IR-9 vs published analysis)

| # | Requirement (client) | Verdict | Where covered / what's missing |
|---|---|---|---|
| **IR-1** | Manufacturing RFQ / exploratory-costing request as a first-class intake document | ❌ **NOT COVERED** | No RFQ/costing-request entity anywhere. Spec digests 00–06 model demand only as a **confirmed `sales_order`** (`md/02` L19,L89,L282; `md/03` L318 pegging `source_orders[]`). `21-gaps.md` does not list a quoting/RFQ gap (grep: no "RFQ/quote/exploratory" token). Mapping's 27 entities start at confirmed demand → MPS/MRP. **The entire pre-sales costing cycle is outside the published scope.** |
| **IR-2** | R&D authors a **Draft BOM** before any order | 🟡 **PARTIAL** | The *data structure* exists: `BomStatus` widened to `{Draft, Active, Inactive, Obsolete}` and lifecycle `Draft → Active → Inactive → Obsolete` is mapped (`20-mapping.md` row 2; `01-spec-masterdata.md` L57,L255; activated in roadmap Phase 0/Phase 1). **What's missing:** (a) **R&D as an actor/owner** of the Draft state — no R&D department/role; permissions are flat `production.*` with no R&D-vs-Planning split; (b) a Draft BOM **detached from a product master** (an exploratory recipe for a not-yet-onboarded customer product) — Moon BOM requires a `product_id` FK (`20-mapping.md` row 2), so a "BOM for a prospect's product" has no home; (c) the **request→Draft-BOM workflow** linking IR-1 to this BOM. |
| **IR-3** | Accounting computes **preliminary/exploratory cost** → quote to customer | 🟡 **PARTIAL** | The **costing engine exists**: `ComputeStandardCost`/`RollUpStandardCost` (BOM roll-up × WC rates), `mfg_standard_costs`, per-item `costing_method` (`20-mapping.md` rows 23–25; `23-roadmap.md` Phase 3; `04-spec-costing.md` §4.1–4.2). **What's missing:** (a) running that roll-up on a **Draft/exploratory BOM** *before* a product/order exists and persisting the result as a **quote** (no quote entity, no quote→customer document); (b) the **Accounting-reviews-then-returns-price** handoff workflow; (c) the cost-roll-up is currently scheduled at **Phase 3** (`23-roadmap.md`) but Cycle-1 quoting is needed at sales time, i.e. **before** Phase 1 execution — a sequencing conflict. No exploratory/preliminary cost mode is specified (costing assumes a *closed order's* actuals/standards). |
| **IR-4** | All Cycle-2 documents anchored to the customer order (full order traceability) | ✅ **FULLY COVERED** | `mfg_peggings (production_order_id, sales_order_id, sales_order_item_id, allocated_quantity)` links every production order to the sales order it serves (`20-mapping.md` row 26; `22-contracts.md` §1 Sales row, §2 events; `mfg_mrp_planned_orders.source_demand_id` carries it through MRP). `production_type=MakeToOrder` drives the order-anchored branch (`02-spec-planning.md` L35). Production-order network (parent/child) walks set→piece→semi-finished orders (`03-spec-execution.md` L70). **Order-as-anchor is the published model.** |
| **IR-5** | **Trial / sample batch** stage with real cost (R&D-run), customer evaluates | ❌ **NOT COVERED** | `order_type ∈ {Standard, Rework, Repair}` only (`03-spec-execution.md` L24,L291; `20-mapping.md` row 13). **There is no `Trial`/`Sample`/`Pilot` order type**, no "produce-for-customer-evaluation → approval gate → convert-to-production" flow. The spec's only sample-ish concept is `routing_type=Inspection` (a routing flavor, not an order lifecycle). A trial batch is "a real process with real cost" → it needs the full Issue/Confirm/Receipt+GL rail (which exists) but **gated as a trial, with an evaluation/approval outcome that either promotes the order to production or stops it** — that gating + the trial→approval→formal-order transition is entirely net-new. Closest published item: `B5 rework flow undefined` and `B13 ECO` — neither is the trial cycle. |
| **IR-6** | On agreement → formal order → R&D finalizes BOM → Planning computes qty + readiness | 🟡 **PARTIAL** | **BOM finalize (Draft→Active):** covered (`20-mapping.md` row 2; lifecycle in `01-spec-masterdata.md` L255). **Planning qty + readiness:** covered by MRP netting + CRP readiness (`20-mapping.md` rows 11–12; `23-roadmap.md` Phase 4; `02-spec-planning.md`). **What's missing:** the **explicit business-process orchestration** "agreement event → R&D hand-off → Planning hand-off" as a tracked, role-routed multi-department workflow; the published plan has the *engines* (BOM lifecycle, MRP, CRP) but not the **staged approval/hand-off pipeline with department ownership** that the client describes (ties to IR-9). |
| **IR-7** | **Deposit / down-payment gate** before reserve/purchase (factory buys RM on customer's behalf) | ❌ **NOT COVERED** | No financial gate exists between Planning and Reservation. Published `ReleaseProductionOrder` (Planned→Released) **reserves stock unconditionally** (`22-contracts.md` §2 `ProductionOrderReleased`; `23-roadmap.md` Phase 1 "Release side-effects: reserve stock"; `03-spec-execution.md` L63). The spec has **no down-payment / advance-payment / customer-deposit concept** (grep: no "deposit/advance/دفعة" in digests). This is a **toll-specific hard gate**: receive + verify a customer deposit (an AR/finance document) → only then allow reserve/PO. Requires (a) a deposit/advance document (Sales/Accounting), (b) a **Release precondition** checking deposit sufficiency, (c) wiring the factory-buys-RM-on-behalf semantics (procurement billed back to the customer). None are in scope. `B18 toll fee invoicing` mentions toll *output* invoicing only — not an *upfront* deposit. |
| **IR-8** | Customer-supplied raw materials remain customer property; mixed with own materials | ✅ **FULLY COVERED** | `production_type=TollManufacturing` + `material_ownership ∈ {Own, Customer}` on order, component, and issue line (`03-spec-execution.md` L27,L107,L128; `20-mapping.md` rows 13,17). Consignment = dedicated `warehouses.is_consignment` rows — held, never valued, never purchasable (`20-mapping.md` row 17; `21-gaps.md` A15; `22-contracts.md` §1 Inventory row; decision **D-15**; `23-roadmap.md` Phase 6). Toll issue = tracking-only movement, **no RM credit at own cost** (`22-contracts.md` §4 toll row; `03-spec-execution.md` L128). **Mixing own + customer materials in one order is exactly the per-line `ownership` model** — fully handled. (Note: this is scheduled at **Phase 6**; if this client is the launch customer, Phase 6 toll/consignment must pull forward — decision **D-07** already anticipates this.) |
| **IR-9** | Dedicated **per-stage workspace screens** to advance the order stage-by-stage | 🟡 **PARTIAL** | **Shop-floor operator terminals** are covered (per-WC touch terminals, Ready queue, Start/Done, supervisor dashboard — `20-mapping.md` §5 `shop-floor/`; `23-roadmap.md` Phase 5; `05-spec-shopfloor.md`). The Angular FE layout (`20-mapping.md` §5) has stage-grouped folders (`planning/`, `execution/`, `costing/`). **What's missing:** the client wants **back-office STAGE WORKBENCHES for the whole order journey** — R&D screen, Planning screen, Finance-deposit-gate screen, Reserve/Issue screen, Trial screen — each owning a stage and providing the "move to next stage" action with role-gated hand-off. The published FE is organized **by functional module** (boms, planning, execution…), not as a **single order-centric stage pipeline / workflow board**. The orchestration UI (one order walking named stages with per-stage ownership) is net-new. |

**Tally:** ✅ 2 fully covered (IR-4, IR-8) · 🟡 4 partial (IR-2, IR-3, IR-6, IR-9) · ❌ 3 not covered (IR-1, IR-5, IR-7).

---

## 2. Root-cause: why the gaps exist

The published analysis is an **execution-grade map of the manufacturing spec**, and the spec itself
**begins at a confirmed customer order** and ends at finished-goods receipt + costing. It models the
*factory floor and its books* superbly. The client's workflow adds a **pre-order commercial/engineering
funnel** the spec never contemplated:

1. **Pre-sales costing funnel** (IR-1→IR-3): request → R&D draft recipe → exploratory price → quote.
   The spec has no quoting, no exploratory/draft-cost mode, no R&D actor — demand is *already confirmed*
   when its model starts. This is the single biggest blind spot and it is **commercial, not manufacturing**.
2. **Trial-batch lifecycle** (IR-5): a real but *provisional* production run whose business outcome is a
   customer go/no-go, not finished-goods stock. The spec's `order_type` enum has no trial state.
3. **Deposit-gated procurement** (IR-7): a toll-business cash-control rule (buy RM only after the customer
   pays) that sits at the Release transition. The spec reserves unconditionally.
4. **Department-staged workflow + per-stage UI** (IR-6 partial, IR-9): the client thinks in *departments
   and stages* (R&D → Accounting → Planning → Finance → Floor); the published plan thinks in *engines and
   modules*. The engines exist; the **staged orchestration layer and its screens** do not.

Everything *downstream of the confirmed formal order* (BOM lifecycle, MRP/CRP, reserve, issue, confirm,
receive, cost, toll consignment, pegging) is already covered — IR-4 and IR-8 are textbook hits, and IR-2/IR-3/IR-6
reuse existing engines with workflow glue missing.

---

## 3. Definitive NEW work items (addendum to roadmap 23)

Each item is sized with the **gap-level** legend from `21-gaps.md` (S ≤ ~3 dev-days · M ≤ ~3 dev-weeks · L > 3 dev-weeks)
and assigned a target phase relative to the published roadmap.

| New ID | Work item | Covers | Type | Size | Target phase |
|---|---|---|---|---|---|
| **C-01** | **Manufacturing RFQ / Costing-Request entity** `mfg_costing_requests` (+ lines): customer, brought-product description/composition, status `{Requested, BomDrafted, Priced, Quoted, Won, Lost}`; links to the draft BOM (C-02) and the quote (C-03). First-class intake; numbered via `SequenceService('production','costing_request')`. | IR-1 | NEW table + Action | M | **New Phase 2.5** (pre-sales; renamed from the provisional "1.5" — needs Phase-2 WC rates, `md/36` §3) |
| **C-02** | **Draft-BOM for prospect products**: allow a BOM in `Draft` whose parent is a *prospective* product. Either (a) make BOM `product_id` nullable with a free-text `prospect_product_name` for exploratory BOMs, or (b) auto-create a `Product` of a new `lifecycle=Prospect` state. Plus **R&D ownership**: new role + permissions `production.rnd.bom.{draft,finalize}`. | IR-2, IR-6 | EXTEND `bill_of_materials` + Core role/perms | M | Phase 1 (BOM v2) + Phase 0 (perms) |
| **C-03** | **Exploratory/Preliminary costing mode + Quote document**: run `RollUpStandardCost` against a Draft BOM with *estimated* material prices, persist as `mfg_cost_estimates`, and emit a customer-facing **quote** (reuse Sales quotation if present, else `mfg_quotes`). Accounting-review step. **Pull cost roll-up forward** from Phase 3 (at least an estimate-only variant). | IR-3 | NEW Action (estimate mode) + NEW table + Sales tie-in | M | Pull part of Phase 3 cost roll-up into **Phase 2.5** (after Phase-2 labor/OH rates land) |
| **C-04** | **R&D actor / department model**: register R&D as a role/department with its own permission slice and as the **owner of Draft-BOM and Trial stages**; extend `PermissionDependencyRegistry` `production` contributor with R&D presets. Distinguish R&D vs Planning vs Finance vs Floor ownership for stage routing (feeds C-08). | IR-2, IR-5, IR-6 | Core perms + role config | S | **Phase 0** (perms layer) |
| **C-05** | **Trial / Sample-batch order type**: add `Trial` (and/or `Sample`,`Pilot`) to `ProductionOrderStatus`/`order_type`; a Trial order runs the **real Issue→Confirm→Receipt+GL rail** but flagged `is_trial`, output routed to a *trial/evaluation* bucket (not sellable FG), and on customer evaluation either **promotes to a formal production order** (carrying the finalized BOM/routing) or closes as Lost. New events `TrialBatchProduced`, `TrialApproved`/`TrialRejected`. | IR-5 | EXTEND `production_orders` enum + Actions/events | M | **New, between Phase 1 and Phase 2** (needs execution rail from P1) |
| **C-06** | **Customer-deposit / down-payment gate**: a deposit document (reuse Sales/Accounting **advance-payment / customer-deposit** if it exists in Moon — VERIFY; else `mfg_order_deposits`) capturing required %, received amount, status; and a **`ReleaseProductionOrder` precondition** that blocks reserve/PO until `deposit_received ≥ deposit_required` (configurable per order/customer). Wire "factory purchases RM on customer's behalf" so MRP-raised POs are tagged billable-to-customer **and are raised ONLY after the deposit gate is satisfied — the gate governs purchasing as well as release/reservation (the client's stated reason for the deposit; planning prepares draft PRs only)**. | IR-7 | EXTEND Release guard + PR gate + Sales/Accounting deposit doc | M | **Phase 2** (gate, per `md/36` §3) + **Phase 4** (MRP billable-PO tag) |
| **C-07** | **Pull Toll/consignment forward** (decision D-07): if this client is the launch customer, move `warehouses.is_consignment`, per-line `material_ownership`, and toll GL branch from **Phase 6 → Phase 1/2**. Already fully designed (IR-8) — this is a *scheduling* change, not new design. | IR-8 (timing) | Roadmap re-sequence | — | Re-sequence Phase 6 → early |
| **C-08** | **Order stage-pipeline + per-stage workbench screens**: a single **order workflow state model** (`Request → DraftBom → Quoted → Trial → Agreed → BomFinal → Planned → DepositGate → Reserved → InProduction → Received`) with a **stage owner** per step, and back-office **stage workbench screens** (R&D, Planning, Finance-gate, Reserve/Issue, Trial-review) each exposing "advance to next stage" with role-gated hand-off and an order-centric **pipeline board**. Distinct from the shop-floor operator terminals (which stay). **Interpretation caveat (OQ-9):** this reads «مرحلة إنتاجية» as *commercial case stages*; if the client means per-MANUFACTURING-stage screens (mixing/granulation/compression/packaging), the answer is per-operation workbenches over routing operations — must be confirmed at the Phase-0 gate. | IR-6, IR-9 | NEW workflow engine + Angular stage screens | L | Board MVP Phase 2 + gate-critical workbenches Phase 2.5; completion Phase 5 (`md/36` §3) |

**New tables introduced by this addendum:** `mfg_costing_requests` (+lines), `mfg_cost_estimates`,
optionally `mfg_quotes` (prefer reuse of Sales quotation), optionally `mfg_order_deposits` (prefer reuse of an
Accounting/Sales advance-payment doc). Net **2–4 new `mfg_*` tables** on top of the published 24, pending
Sales/Accounting reuse verification for quote + deposit.

**New enums / state extensions:** `order_type += Trial` (or new `is_trial` + trial sub-state); BOM
`product_id` nullable / `Product.lifecycle=Prospect`; `mfg_costing_requests.status`; an **order
stage-pipeline** workflow state separate from the financial `ProductionOrderStatus`.

**New actors:** **R&D department/role** (owns Draft-BOM + Trial); explicit **Finance deposit-gate** owner.

---

## 4. Decisions this addendum needs from management (extends D-01..D-22)

> **Authoritative numbering = `md/36` §4.** This table proposed the decisions; the register in
> `md/36` §4 (D-23..D-36 + OQ-9) carries the final numbers and recommendations and supersedes any
> divergence below (e.g. D-24's recommendation here was superseded by the ratified
> `order_type=Trial`).

| # | Decision | Options | Recommendation |
|---|---|---|---|
| **D-31** *(was provisionally "D-23" here)* | Pre-sales costing cycle scope | Build full RFQ→DraftBOM→Quote (C-01..C-03) vs minimal estimate spreadsheet | **Build C-01..C-03** — it is this client's entry point; without it Cycle-1 is unserved |
| **D-24** | Trial-batch modeling | New `order_type=Trial` vs reuse Standard order + flag vs separate trial module | **`is_trial` flag + trial sub-state on production order** (reuses the full P1 rail; lightest) |
| **D-25** | Deposit gate enforcement | Hard block at Release vs advisory warning vs per-customer config | **Hard block, configurable threshold per order/customer**; reuse Moon advance-payment if it exists |
| **D-26** | Draft-BOM for prospect product | Nullable `product_id` vs `Product.lifecycle=Prospect` state | **`Product.lifecycle=Prospect`** (keeps BOM FK integrity; promotes cleanly on Won) |
| **D-27** | Toll/consignment timing for this client | Keep Phase 6 vs pull to Phase 1/2 | **Pull forward** (this is a toll factory — D-07 trigger fired) |
| **D-28** | Stage-pipeline UI scope | Full workflow engine + per-stage screens (C-08, L) vs order-centric pipeline board MVP first | **Pipeline board MVP early; full per-stage workbenches Phase 5** |
| **D-29** | Quote / deposit document ownership | New `mfg_*` tables vs reuse Sales quotation + Accounting advance-payment | **Reuse Sales/Accounting where they exist** (VERIFY in `/home/moonui/moon-erp-be`); `mfg_*` only as fallback |

---

## 5. Bottom line

The published analysis **fully covers the order-anchored, toll/consignment manufacturing core** this client
needs (IR-4, IR-8) — order-to-everything pegging and customer-owned-material handling are textbook hits, only
needing toll/consignment **pulled forward** in time (C-07/D-27). The **engineering and planning engines** for
IR-2/IR-3/IR-6 already exist (BOM lifecycle, standard-cost roll-up, MRP/CRP) but lack the **workflow glue,
R&D actor, exploratory-cost mode, and quote document** to serve the client's *commercial* funnel.

The **three genuine net-new capabilities** are all *outside the manufacturing spec's universe*: the
**exploratory-costing/RFQ cycle** (IR-1, ❌), the **trial-batch order lifecycle** (IR-5, ❌), and the
**customer-deposit gate before reservation** (IR-7, ❌) — plus the **per-stage workbench orchestration UI**
(IR-9, 🟡). These become work items **C-01..C-08** and decisions in the authoritative register
**`md/36` §4 (D-23..D-36 + OQ-9)**, appended to roadmap 23.
None invalidate the published architecture; all are additive and reuse the existing rails.
</content>
</invoke>

---


# 32 — ERP Evidence for the Client Cycle (Pharma Toll / Contract Manufacturing)

> Addendum to the published analysis. This file answers ONE question with code citations:
> **for the pharma toll-manufacturing client's two cycles (Exploratory Costing + Make-to-Order
> Production), what building blocks already EXIST in Moon ERP, and what is MISSING?**
>
> Client cycles (verbatim from owner interview):
> - **CYCLE 1 — Exploratory Costing**: customer brings a product + composition → R&D drafts a BOM →
>   Accounting computes preliminary cost → preliminary price returned as a quote.
> - **CYCLE 2 — Make-to-Order Production**: customer orders N units; everything ties to that customer
>   order. (a) **TRIAL**: R&D produces a real sample batch (real cost → own trial cycle).
>   (b) On agreement → formal order → R&D finalizes BOM → Planning sizes & checks readiness →
>   customer pays a **DEPOSIT / down-payment** (factory buys raw materials on the customer's behalf;
>   customer MAY also supply his own materials which remain HIS property) → if finances OK →
>   **RESERVE** materials → create manufacturing order → issue → manufacture → finish → receive into
>   finished-goods. Client also wants **dedicated screens per production stage**.
>
> Read-only review of `/home/moonui/moon-erp-be`. Verdict legend: ✅ EXISTS (reuse as-is) ·
> 🟡 PARTIAL (primitive present, needs wiring/extension) · ❌ MISSING (build new).

---

## Executive verdict

| # | Building block the cycle needs | Verdict | Where it lives / what's missing |
|---|---|---|---|
| 1 | Sales quotation + quotation→order conversion (CYCLE 1 quote, CYCLE 2 formal order) | ✅ EXISTS | `Modules/Sales` full lifecycle Draft→Sent→Accepted→Converted; `convertFromQuotation` action |
| 2 | Customer deposit / down-payment with correct GL (advance liability, not AR settlement) | 🟡 PARTIAL | `ReceiptVoucher` + COA `2104 Unearned Revenue` exist; **NOT linked to a sales/mfg order**, and `SalesPayment` is invoice-bound only |
| 3 | Customer-owned (consignment / toll) raw materials kept as the customer's property | ❌ MISSING | `Warehouse.type` enum has no `consignment`/owner concept; no ownership flag on stock |
| 4 | R&D / project / trial-batch workflow | 🟡 PARTIAL | `BomStatus::Draft` exists for draft BOM; CMMS = asset maintenance (not R&D); no trial-batch entity |
| 5 | Approval workflow to gate stages (BOM sign-off, deposit-gate, order release) | 🟡 PARTIAL | Generic engine in `Modules/Core` (ApprovalWorkflow/Log) — but only `Sales`/`Purchases` modules registered, **no Production** |
| 6 | CRM pipeline for RFQ intake | ❌ MISSING | `Modules/CRM` is support/ticketing (tickets, SLA, interactions) — no lead/opportunity/pipeline |
| — | Material reservation primitive | 🟡 PARTIAL | `StockBalance.reserved_quantity` column + `available` accessor exist, but **no code writes them** |
| — | Per-stage production transitions (basis for the per-stage screens) | 🟡 PARTIAL | `ProductionOrderController` has confirm/start/complete/consume/recordOutput; no customer/trial/deposit/reserve stages |

---

## 1. Sales quotations & quotation→order conversion — ✅ EXISTS

`Modules/Sales` already implements the exact spine CYCLE 1 (the quote) and CYCLE 2's "formal order"
need.

- **Model**: `Modules/Sales/app/Models/SalesQuotation.php` — header with `customer_id`, items,
  totals, `valid_until`, and crucially a `sales_order_id` back-link (fillable L56).
- **Lifecycle** (`Modules/Sales/app/Enums/QuotationStatus.php`,
  controller `Modules/Sales/app/Http/Controllers/SalesQuotationController.php`):
  `Draft → Sent → Accepted / Rejected / Expired → Converted / Cancelled`, plus `duplicate`
  (re-quote). Routes in `Modules/Sales/routes/api.php` L21-26.
- **Conversion**: `SalesOrderController::convertFromQuotation()`
  (`Modules/Sales/app/Http/Controllers/SalesOrderController.php` L368) — guarded by
  `status === Accepted` (L372), copies header + items, sets `quotation_id` on the order, and marks
  the quotation `Converted` (L426). Route: `POST quotations/{quotation}/convert-to-order`
  (api.php L33).

**Fit for the client cycle:**
- CYCLE 1 quote = a `SalesQuotation` priced from the R&D draft BOM. The preliminary cost feeding
  the quote is the gap addressed by the Production costing work in the main plan (md/04, md/20).
- CYCLE 2 "formal order on agreement" = `convertFromQuotation` → `SalesOrder`. **Everything in
  CYCLE 2 must hang off that `SalesOrder` id** — which means the new `mfg_*` production-order rows
  need a `sales_order_id` FK (see §7 amendments).

**Missing**: there is no field carrying the *exploratory vs. trial vs. final* intent on the
quotation, and no link from the quotation/order to a Production draft BOM. Both are small additive
fields, not new subsystems.

---

## 2. Customer deposit / down-payment & its GL — 🟡 PARTIAL (primitives exist, not wired)

This is the financially most delicate requirement: a **down-payment (دفعة تحت حساب)** is an
**advance liability**, not a payment against a receivable. Booking it as a normal sales payment
would mis-state both AR and revenue.

What EXISTS:
- **Chart of accounts already has the right liability**:
  `Modules/Accounting/database/seeders/DefaultChartOfAccountsSeeder.php` L44 →
  `2104 Unearned Revenue / إيرادات مقدمة` (classification `liabilities`, nature `credit`).
  Also `1106 Prepaid Expenses` for the mirror case.
- **A generic cash-in voucher**: `Modules/Accounting/app/Models/ReceiptVoucher.php` (سند قبض).
  It has `partner_id`, `receiving_account_id`/`bank_account_id`, **polymorphic
  `reference_type`/`reference_id`/`reference_number`** (fillable L37-39), and child
  `ReceiptVoucherLine` rows each with their own `account_id` + `partner_account_id`.
- **Its posting is account-agnostic**:
  `Modules/Accounting/app/Actions/ApproveReceiptVoucher.php::buildJournalLines()` (L76) DRs the
  receiving (cash/bank) account and CRs *whatever account the line carries* — so a deposit can be
  booked **DR Cash/Bank · CR 2104 Unearned Revenue** by setting the line account, without any code
  change. When no line account is given it falls back to the partner's AR/AP account
  (`resolvePartnerAccount` L128) — which is the wrong default for a deposit.

What is MISSING:
- **No order linkage / deposit semantics.** `ReceiptVoucher.reference_type` is free-form; nothing
  ties a voucher to a `SalesOrder` (or the new `mfg` production order) as a *required deposit before
  release*. There is no "deposit covered?" gate anywhere.
- **`SalesPayment` is the wrong tool** for this. `Modules/Sales/app/Models/SalesPayment.php` is
  bound to `invoice_id` (fillable L31), and `Modules/Sales/app/Actions/PostSalesPayment.php` is
  hard-wired **DR Cash · CR Accounts Receivable** against an invoice (L30-56) and allocates to
  invoice payment schedules (L106). There is no advance/unearned path; it cannot represent a deposit
  taken before any invoice exists.
- **No precedent in LIS.** The LIS "advance" hits (`CourierPickupService::advanceRequestStatus`,
  L680) are *status* progression, not financial advances — no customer-wallet/credit-balance
  precedent to copy.

**Amendment**: add a deposit step on the production/sales-order flow that issues a `ReceiptVoucher`
with `reference_type = sales_order` (or `mfg_production_order`) and a line crediting a
**Customer Advances** liability (use `2104` or a new `2105 Customer Advances / دفعات عملاء تحت
الحساب` sub-account). On final invoicing, the advance is drawn down (DR Unearned Revenue · CR AR or
revenue). The "finances OK → proceed" gate = `sum(approved deposit vouchers for this order) ≥
required_deposit`.

---

## 3. Customer-owned / consignment (toll) raw materials — ❌ MISSING

The owner explicitly said the customer **may supply his own raw materials which remain HIS
property**. Moon ERP today has **no concept of stock ownership**.

- `Modules/Inventory/app/Models/Warehouse.php` fillable (L19-34) has `type`, `account_id`,
  `allow_negative_stock` — **no owner/partner field**.
- `Modules/Inventory/app/Enums/WarehouseType.php` = `Main, Sub, Transit, Damaged, Returns` — **no
  `Consignment` / customer-owned type**.
- `grep -rin "consign|owned_by|ownership|customer_owned"` across `Modules/Inventory/app` and its
  migrations → **0 hits**. Stock movements and `StockBalance` carry no owner.

**Consequence**: customer-supplied materials would otherwise be valued and posted to the company's
inventory GL (overstating assets) and could be consumed by other orders. The factory holds them in
**bailment**, not ownership.

**Amendment options (pick one, documented for the plan):**
1. **Warehouse flag** — per the **already-ratified D-15** (`md/20` row 17, `md/22` §1) this is the
   `warehouses.is_consignment` boolean flag, NOT a new `WarehouseType::Consignment` enum case —
   plus `owner_partner_id` on `warehouses`; consignment warehouses post to an **off-book / memo**
   valuation (no GL asset), and issuing from them does NOT create a material-cost GL line (cost
   stays with the customer). *(Correction: this option originally proposed a new enum case,
   overlooking that D-15 had already ratified the flag form — `md/33`/`md/36` M-11 follow D-15.)*
2. **Per-balance ownership** — add `owner_partner_id` (nullable; null = company-owned) on
   `stock_balances` + stock movements, with available-stock and valuation filtered by owner.

Either way this is a genuine **new build** (a `mfg_*`-adjacent inventory extension), not a reuse.

---

## 4. R&D / trial-batch / project workflow — 🟡 PARTIAL

The cycle needs two R&D touchpoints: (a) draft a BOM (CYCLE 1 + CYCLE 2 finalize), and (b) run a
**real trial/sample batch with real cost** (CYCLE 2a).

What EXISTS / is reusable:
- **Draft BOM** is already a first-class state: `Modules/Production/app/Enums/BomStatus.php` =
  `Active, Inactive, Draft`. R&D's exploratory BOM = a `BillOfMaterials` in `Draft`. (The published
  plan, md/20, already calls for activating versioning/lifecycle on `bill_of_materials`.)
- **A real production order with real cost** is exactly what `ProductionOrder` is
  (`Modules/Production/app/Models/ProductionOrder.php` carries planned/actual
  material/labor/overhead cost, L41-46). A trial batch is a `ProductionOrder` flagged as trial.

What is MISSING:
- **No trial-batch concept** — `ProductionOrder` has no `kind`/`is_trial` field and no
  customer/sales-order link (fillable L21-48 has neither `customer_id` nor `sales_order_id`).
- **No R&D/project module.** `Modules/CMMS` is **asset maintenance** (`CmmsAsset`,
  `CmmsWorkOrder`, `CmmsPmSchedule`) — work orders for *fixing equipment*, not R&D projects, so it
  is **not** a fit for the R&D trial workflow despite the "work order" name.
- No task/project entity anywhere to model the R&D request intake before a BOM exists.

**Amendment**: model the trial as a `ProductionOrder` with `kind = trial` (vs `production`) +
`sales_order_id`, so the trial inherits all costing/stock plumbing and stays tied to the customer
order. The R&D "exploratory request" intake (CYCLE 1 entry) is the same record-class as a
quotation request (§6).

---

## 5. Approval workflows for gating — 🟡 PARTIAL (engine exists, Production not registered)

A reusable approval engine already lives in Core — ideal for the stage gates the client wants
(BOM sign-off, deposit-cleared, order release, trial acceptance).

- `Modules/Core/app/Models/ApprovalWorkflow.php` + `ApprovalLog.php`;
  controllers `ApprovalWorkflowController` / `ApprovalLogController`;
  statuses `Modules/Core/app/Enums/ApprovalLogStatus.php`.
- **But scope is limited**: `Modules/Core/app/Enums/ApprovalModule.php` = `Sales, Purchases`
  only; `Modules/Core/app/Enums/ApprovalDocumentType.php` covers quotation/order/invoice/return/
  delivery_note/PR/PO/bill/return/GRN — **no Production / BOM / production-order / trial types.**

**Amendment**: add `ApprovalModule::Production` and document types
(`bom`, `production_order`, `trial_batch`, optionally `deposit`) to reuse the existing engine for
stage gating instead of building bespoke approval logic.

---

## 6. CRM pipeline for RFQ intake — ❌ MISSING (wrong CRM shape)

`Modules/CRM` is a **customer-support / ticketing** module, not a sales pipeline:
- Models: `CrmTicket`, `CrmTicketComment`, `CrmSlaPolicy`, `CrmCustomerInteraction`,
  `CrmCustomerExt`, `CrmCustomerTag`. No `Lead`, `Opportunity`, `Pipeline`, or `Stage`.
- `grep -rin "lead|opportunity|pipeline|rfq"` over `Modules/CRM/app/Models` → only `CrmCustomerExt`
  matches incidentally (no pipeline entity).

**Consequence**: there is no opportunity-pipeline to host the "customer brings a product → RFQ"
intake. Two pragmatic options:
1. **Reuse `SalesQuotation` as the RFQ record** (Draft status = open RFQ) — cheapest; the
   exploratory request and the quote are one record, statuses carry the funnel. Recommended.
2. **Reuse `CrmCustomerInteraction`** to log the inbound enquiry, then spawn a quotation.

A full CRM pipeline is **not** required to satisfy the client; the quotation lifecycle already is a
mini-funnel.

---

## 7. Production stage transitions & the "screens per stage" request — 🟡 PARTIAL

The client wants **a dedicated screen per production stage** to push the order stage-to-stage.
The transition primitives partly exist.

- `Modules/Production/app/Http/Controllers/ProductionOrderController.php` exposes
  `confirm` (L204), `start` (L226), `complete` (L251), `consume` (materials issue, L311),
  `recordOutput` (L354), `cancel` (L286) — each permission-guarded. This is the skeleton for
  stage screens.
- `Modules/Production/app/Enums/ProductionOrderStatus.php` = `Draft, Confirmed, InProgress,
  Completed, Cancelled` with `canConfirm/canStart/canComplete/canCancel` guards — a small state
  machine.
- **Material reservation primitive exists but is dormant**:
  `Modules/Inventory/app/Models/StockBalance.php` has `reserved_quantity` (L25) and an
  `available = quantity - reserved_quantity` accessor (L47) — but `grep` shows **no code writes
  `reserved_quantity`** outside resources/factory. The "RESERVE materials for this order" step has a
  column to land in, but no reserve action yet.

What is MISSING for the client's specific flow (the stages between order and manufacture):
- No `customer_id` / `sales_order_id` on `ProductionOrder`.
- No **trial** stage, no **deposit-cleared** gate, no **reserve** stage, no **readiness/planning
  check** stage. The existing enum jumps straight Draft→Confirmed→InProgress.
- No per-stage screen metadata; FE screens must be added per the main plan's FE work (md/13, md/23).

**Amendment**: add a parallel commercial stage machine (NOT a widening of `ProductionOrderStatus`
— resolved as the `mfg_order_cases` umbrella, `md/35` §0) covering the client's chain — e.g.
`Requested → TrialInProgress → TrialAccepted → AwaitingDeposit → Released(+Reserved) → InProgress
→ Completed → ReceivedToFG` — and wire a reserve action that increments
`StockBalance.reserved_quantity`. **Ordering correction:** per the ratified `md/20` row 13,
reservation is a *side-effect of* `ReleaseProductionOrder`, so a `MaterialsReserved` stage is
*entered by* Release — it never precedes it (this file's original chain had reserve before
Release, which was circular against the ratified design). Each stage maps 1:1 to a stage screen.

---

## Net new vs. reuse (summary for the roadmap addendum)

**Reuse as-is (✅):** `SalesQuotation` lifecycle + `convertFromQuotation`; `ReceiptVoucher` posting
engine; COA `2104 Unearned Revenue`; `BomStatus::Draft`; Core `ApprovalWorkflow` engine;
`ProductionOrder` cost plumbing; `StockBalance.reserved_quantity` column.

**Extend (🟡):** add `sales_order_id` + `customer_id` + `kind(trial|production)` to the production
order; add `ApprovalModule::Production` + production document types; default a deposit
`ReceiptVoucher` to credit a Customer-Advances liability + link `reference_type=sales_order`; wire a
reserve action onto `reserved_quantity`; extend the production status machine to the client's stage
chain.

**Build new (❌):** customer-owned / consignment stock ownership in Inventory
(`WarehouseType::Consignment` + `owner_partner_id`, off-book valuation); trial-batch entity/flag;
RFQ intake (recommended: reuse quotation Draft rather than a new CRM pipeline).

---


# 33 — Pharma CMO Domain Addendum: What the Client Did Not Say

> Companion to the client-workflow addendum (Cycle 1 Exploratory Costing + Cycle 2 Production/MTO,
> toll/"تصنيع للغير"). The client described his commercial flow (R&D draft BOM → Accounting quote →
> trial batch → deposit → reserve → MO → issue → manufacture → FG). This file lists what a regulated
> **pharmaceutical contract-manufacturing (CMO/toll)** operation will ALSO require, so management is
> not surprised in execution or audit. Scope: **high-confidence pharma-domain items only**, each
> flagged اختياري (optional/nice) or لاحق (deferred phase) with a phase suggestion against the
> published roadmap (`md/23-roadmap.md`).
>
> Method: each item is cross-checked against the published gap analysis (`md/21-gaps.md`) and the
> definitive mapping (`md/20-mapping.md`) to label it **ALREADY-LISTED** (the published analysis
> already carries it — pharma only re-prioritises it) or **NEW** (genuinely not in the published
> docs). Sources cited inline.

---

## 0. Why this addendum exists

The Manufacturing spec (`files/manufacturing_spec/markdown`) is **deliberately industry-generic** —
its melamine-tableware worked example, its `quality_status ∈ {Released, OnHold, Rejected}` enum
(`03_execution.md:209`) and its free-text `batch_no/lot_no` fields (`03_execution.md:81, 207`) are
the entire extent of its regulated-manufacturing coverage. The spec EXPLICITLY defers the quality
layer (`md/21-gaps.md` excluded-candidates table: *"Quality layer — Spec defers it; Moon QMS already
supplies it"*) and never states a lot-traceability rule (`md/21-gaps.md` B2). For a **pharma** CMO
this generic posture is insufficient: several items the spec treats as optional become **regulatory
hard requirements** (GMP / batch-record / release-control), and a few have no home anywhere in the
published analysis. This addendum closes that interpretation gap **before** the board signs the
client addendum.

The reclassification principle: **pharma does not add many new tables — it changes the SEVERITY and
the DEFAULT of items the published analysis already sized as deferrable.** The single largest example:
GR `quality_status` defaults to **Auto-Released** for Phases 1–5 (`md/23-roadmap.md` D-21). For a
pharma line that default is **non-compliant** — FG must be held QC-pending and may not move to
sellable FG stock until a Qualified-Person/QC release exists.

---

## 1. The pharma considerations (cross-checked)

| # | Item | Published status | Verdict | Severity for pharma | Flag + phase |
|---|---|---|---|---|---|
| P1 | **Batch Manufacturing Record (BMR) + batch numbering** | Partially: `SequenceService` numbering exists; per-event docs (Issue/Confirmation/GR) exist as `mfg_*` tables — but no consolidated *batch record* artifact | **NEW (assembly is new)** | CRITICAL (GMP core) | لاحق — Phase 2→6 |
| P2 | **QC release hold before FG transfer** | ALREADY-LISTED — `quality_status` enum (mapping row 22), QMS `qms_inspections.production_order_id` exists; default Auto-Released (D-21) | **ALREADY-LISTED — default flip** | CRITICAL | لاحق — Phase 6 (pull QC-gate config earlier for pharma) |
| P3 | **Mandatory lot + expiry tracking, FEFO** | ALREADY-LISTED — gap **A4** (batch not first-class) + **B2** (no traceability rule); roadmap Phase 6, D-14 | **ALREADY-LISTED — now CRITICAL** | CRITICAL (recall/expiry law) | لاحق — Phase 6 (candidate to pull forward) |
| P4 | **Artwork / packaging spec per customer** | NEW — not in `md/20`/`md/21` | **NEW** | IMPORTANT | اختياري/لاحق — Phase 6+ |
| P5 | **Retained / reference samples** | NEW — not in published docs | **NEW** | IMPORTANT (GMP retention) | لاحق — Phase 6 |
| P6 | **Cleaning / changeover between products on shared lines** | Partially: `setup_time` exists (mapping row 5); downtime/pause undefined (**B15**); cleaning as a costed/validated step is NEW | **NEW (cleaning step) / capacity impact** | IMPORTANT (cross-contamination) | لاحق — Phase 2 (cost) / Phase 4 (capacity) |
| P7 | **Regulatory dossier / registration linkage** | NEW — not in published docs | **NEW** | IMPORTANT (market-authorisation) | اختياري/لاحق — Phase 6+ |
| P8 | **Yield reconciliation requirement** | Partially: `yield% = Σgrades/input` exists (`04_costing.md`/`03_execution.md:152`); reconciliation *as a mandatory accountability gate* is NEW | **NEW (mandatory gate)** | IMPORTANT (GMP material balance) | لاحق — Phase 2 (capture) / Phase 6 (gate) |

Net: of the 8 items, **2 are already in the published gap analysis** (P2 release-control, P3 lot/expiry
— pharma only re-prioritises them), **1 is partially covered** (P1 numbering/event-docs exist;
the consolidated record is new), and the remaining **4–5 are genuinely new** (artwork, retained
samples, cleaning-as-step, dossier linkage, and yield reconciliation as a hard gate).

---

## 2. Item detail

### P1 — Batch Manufacturing Record (BMR) & batch numbering — لاحق (Phase 2 capture → Phase 6 assembly), CRITICAL
- **What pharma needs:** every manufactured batch carries one auditable **Batch Manufacturing Record**
  — a single document tying the released order to: the frozen BOM/routing snapshot, every material
  issue (with the RM lot numbers consumed), in-process checks, operator/equipment/line, deviations,
  yield, and the QC release signature. Batch numbering must be a controlled, gap-free, per-product
  sequence (often year/line-encoded), distinct from the order number.
- **Published status:** the *ingredients* of a BMR already exist in the mapping — frozen snapshot in
  `production_order_materials/_operations` (mapping rows 14–15), `mfg_material_issues` /
  `mfg_confirmations` / `mfg_goods_receipts` event documents, and `SequenceService` numbering
  (`md/20-mapping.md` §3). What is **NEW** is the *consolidated, immutable batch-record assembly/print*
  over those rows, plus a dedicated batch-number sequence key.
- **Remedy:** a `production` batch-number sequence (`SequenceService::generateNext(company,'production','batch')`),
  a `batch_no` stamped at Release onto the order and propagated to every child document, and a
  BMR assembly view/print that gathers the existing event rows. No new transactional engine — a
  reporting/print layer (analogous to the travel-card gap **A21** in `md/21-gaps.md`).
- **Phase:** capture the `batch_no` link in **Phase 1/2** (cheap, additive); the formal BMR
  print/assembly with deviations + signatures in **Phase 6** alongside QMS gates and batch genealogy.

### P2 — QC release hold before FG transfer — لاحق (Phase 6; pull config earlier for pharma), CRITICAL
- **What pharma needs:** finished goods may NOT become sellable stock on goods-receipt. They land in
  a **QC-pending / quarantine** state and are released only after a QC/QP decision. This is the single
  most important pharma deviation from the generic plan.
- **Published status: ALREADY-LISTED.** The GR line already carries
  `quality_status ∈ {Released, OnHold, Rejected}` (`md/20-mapping.md` row 22; `03_execution.md:209`),
  QMS `qms_inspections`/`qms_inspection_plans` already carry `production_order_id`, and the roadmap
  schedules QMS gates in **Phase 6**. The catch is **D-21**: *"GR `quality_status` defaults to
  **Released** until the QMS gates land in Phase 6."* For pharma that default is non-compliant.
- **Remedy (no new schema):** for pharma companies flip the default to **OnHold**, and either
  (a) route OnHold FG into a quarantine warehouse (reuse the warehouse-as-state pattern, like the
  `is_consignment` flag in **A15**/D-15), or (b) block sellable availability until
  `qms_inspections.result = Released`. The `GoodsReceiptPosted` event already has a *"QMS GR-gate
  follow-up for OnHold lines"* consumer in the contracts (`md/22-contracts.md` §2). Cleanest
  early win: a per-company setting `production.gr_default_quality_status` so the pharma client
  gets OnHold-by-default without waiting for full Phase 6.
- **Phase:** the gate logic is **Phase 6**; the **default flip + quarantine warehouse** can be
  delivered as a small Phase 1 configuration item for this client.

### P3 — Mandatory lot + expiry tracking + FEFO — لاحق (Phase 6, candidate to pull forward), CRITICAL
- **What pharma needs:** lot and **expiry date** are mandatory, not free-text. Issue must be
  **FEFO** (first-expiry-first-out); full **genealogy** (which RM lots entered which FG lot) is a
  legal recall prerequisite; expiry must block issue/sale of expired stock.
- **Published status: ALREADY-LISTED as a CRITICAL gap** — this is gap **A4** (*"Batch/lot is not
  first-class… `batch_number`/`expiry_date` are free-text strings… no FEFO"*) plus the spec-side
  **B2** (*"Lot/batch traceability absent as a rule"*) in `md/21-gaps.md`, and decision **D-14**
  (*"Full batch dimension, feature-flagged per company"*), scheduled in roadmap **Phase 6**. The
  spec itself only carries `batch_no/lot_no` as free-text (`03_execution.md:81, 207`).
- **Pharma reclassification:** A4 is already CRITICAL; pharma confirms it is **non-deferrable for
  this client** — a pharma line cannot legally go live without lot+expiry+FEFO+genealogy. This is
  the strongest candidate to **pull forward** from Phase 6 (or to ship A4's batch-master subset in
  the same release that makes the client operational).
- **Remedy:** as A4/D-14 — `inventory_batches` master (with `expiry_date`), batch-keyed balances,
  FEFO pick on issue, and the genealogy link written at Material Issue ↔ Goods Receipt.

### P4 — Artwork / packaging specification per customer — اختياري/لاحق (Phase 6+), IMPORTANT
- **What pharma needs:** in toll/CMO work the **labelling and packaging artwork** are
  customer-and-market-specific and version-controlled; using the wrong/obsolete artwork is a
  recall-class error. Packaging materials are themselves controlled BOM components with their own
  lot control.
- **Published status: NEW.** Neither `md/20-mapping.md` nor `md/21-gaps.md` mentions artwork or
  packaging-spec control. The spec models packaging only as ordinary BOM components.
- **Remedy:** an artwork/label-version master keyed to (customer, product, market) with an
  effective/approved version (reuse `Attachment` rails + the BOM-versioning pattern, **A9**); the
  packaging BOM line references the approved artwork version. Lightweight; can ride the Phase 6
  toll-deepening work.

### P5 — Retained / reference samples — لاحق (Phase 6), IMPORTANT
- **What pharma needs:** GMP requires **retained samples** of each batch kept for the product's
  shelf-life + margin, logged and retrievable. Often a small reserved quantity of FG (and sometimes
  RM) per batch.
- **Published status: NEW.** Not present in any published doc.
- **Remedy:** a retained-sample register linked to `batch_no` (depends on P1/P3), optionally a
  small auto-reservation of FG at goods-receipt into a "retained-samples" warehouse (warehouse-as-state
  pattern again). Schedule with QMS/batch work in **Phase 6**.

### P6 — Cleaning / changeover between products on shared lines — لاحق (Phase 2 cost / Phase 4 capacity), IMPORTANT
- **What pharma needs:** on shared equipment, a validated **cleaning/changeover** between products is
  mandatory (cross-contamination control). It consumes real capacity and real cost, and gates the
  next product's start.
- **Published status: PARTIAL/NEW.** `setup_time` exists on routing operations (mapping row 5) and
  could absorb changeover time, but cleaning **as a distinct, validated, costed step** is not
  modelled; the broader downtime/pause taxonomy is an open spec gap (**B15** in `md/21-gaps.md`).
  Its **capacity impact** is genuinely new for CRP.
- **Remedy:** model changeover/cleaning as a routing operation type or a sequence-dependent setup on
  the work center; feed its duration into CRP effective-capacity (Phase 4 calendar/CRP work, **A8/B4**)
  and its cost into conversion costing (Phase 2). For v1 it can be approximated by `setup_cost_rate` +
  `setup_time`; a true validated-cleaning step is a later refinement.
- **Phase:** cost capture **Phase 2**; capacity/CRP impact **Phase 4**.

### P7 — Regulatory dossier / product registration linkage — اختياري/لاحق (Phase 6+), IMPORTANT
- **What pharma needs:** each manufactured product is tied to a **market-authorisation / registration
  dossier** (e.g. local MoH/EDA registration). The approved master BOM/routing must match the
  registered dossier; production of an unregistered or registration-expired product must be flagged.
- **Published status: NEW.** No dossier/registration concept anywhere in the published analysis.
- **Remedy:** a lightweight registration master (product, market, registration_no, validity dates,
  linked approved BOM version) with a check at order Release. Reuses BOM versioning (**A9**) +
  `Attachment`. Optional for v1; advisory flag is enough to start.

### P8 — Yield reconciliation as a mandatory accountability gate — لاحق (Phase 2 capture / Phase 6 gate), IMPORTANT
- **What pharma needs:** GMP requires a **material balance / yield reconciliation** per batch — inputs
  must reconcile to outputs + scrap + samples within validated limits, and out-of-limit yields must
  be explained and signed off **before** release.
- **Published status: PARTIAL.** The spec computes `yield% = Σgrades / input` (`03_execution.md:152`)
  and `mfg_confirmation_yields`/`_details` capture grade/scrap quantities (mapping rows 19–20). What
  is **NEW** is yield reconciliation **as a hard gate** (limit bands + mandatory explanation +
  block-release-on-breach), analogous to how variance tolerance bands work in costing
  (`md/23-roadmap.md` Phase 3) but applied to physical material balance for batch release.
- **Remedy:** reuse the tolerance-band pattern (Normal/Investigate) on the yield computation; an
  out-of-limit yield raises a deviation and blocks QC release (ties to P2). Capture exists from
  **Phase 2**; the release-blocking gate lands with QMS in **Phase 6**.

---

## 3. Cross-reference summary (published vs new)

**Already in the published gaps/mapping (pharma only re-prioritises):**
- P2 QC release hold → `md/20-mapping.md` row 22 + **D-21** (default flip is the only change).
- P3 lot/expiry/FEFO/genealogy → gap **A4** + spec-gap **B2** + **D-14** (already CRITICAL;
  pharma makes it non-deferrable for this client).
- P1 (partial) numbering + event documents + frozen snapshot → mapping rows 14–15, §3.
- P6 (partial) `setup_time` → mapping row 5; downtime taxonomy → **B15**.
- P8 (partial) `yield%` + `mfg_confirmation_yields` → `04_costing.md`, mapping rows 19–20.

**Genuinely NEW (no home in the published analysis):**
- P1 — consolidated **BMR assembly/print** + dedicated batch-number sequence.
- P4 — **artwork / packaging-spec** version control per customer/market.
- P5 — **retained / reference samples** register.
- P6 — **cleaning/changeover as a distinct validated, costed, capacity-consuming step** (+ its CRP impact).
- P7 — **regulatory dossier / registration** linkage.
- P8 — **yield reconciliation as a mandatory release gate** (vs the existing yield % metric).

**Phase placement (suggested, against `md/23-roadmap.md`):**
- Phase 1: P1 batch-number link (additive); P2 default-flip config + quarantine warehouse (this client).
- Phase 2: P1 capture continues; P6 cleaning cost; P8 yield capture.
- Phase 4: P6 cleaning capacity into CRP.
- Phase 6: P2 full QC gate, P3 lot/expiry/genealogy/FEFO (pull-forward candidate), P5 retained samples,
  P8 yield release gate, P1 BMR print, plus P4/P7 as optional add-ons.

---

## 4. One-line guidance for management
Pharma adds **no large new engine** to the published plan; it **upgrades the severity and changes the
defaults** of release-control (P2) and lot/expiry traceability (P3 = gap A4/B2 — already in the
analysis, now non-negotiable for go-live), and adds a small set of GMP artifacts (BMR, retained
samples, artwork/packaging versions, dossier linkage, yield-reconciliation gate). Recommend pulling
**A4 (batch+expiry+FEFO+genealogy)** forward to land with this client's first operational release, and
flipping **GR quality_status to OnHold-by-default** for the pharma company immediately.
</content>
</invoke>

---


# 34 — Cycle 1 Design: Exploratory Costing (دورة التكلفة الاستكشافية), End-to-End

> **Binding design addendum** to the published Manufacturing × Moon-ERP analysis. Designs the
> client's CYCLE 1 (customer brings a product + composition → R&D draft BOM → Accounting
> preliminary costing → quote back to customer) end-to-end inside the extended
> `Modules/Production`, per the ratified rails: **D-01** module identity (`mfg_*` tables,
> `production.*` permissions), **CreateJournalEntry-only GL** (`md/22` §5 — and Cycle 1 posts
> **zero** JEs), **reuse-first** (`md/31` D-29, `md/32` evidence).
>
> **Naming reconciliation (canonical, supersedes provisional names):** `md/30` proposed
> `mfg_quotations` + `mfg_quotation_costings`; `md/31` C-01/C-03 proposed `mfg_costing_requests` +
> `mfg_cost_estimates`. This file fixes the canonical pair: **`mfg_cost_estimates`** (estimate
> header = RFQ intake + funnel state) + **`mfg_cost_estimate_versions`** (immutable, versioned
> roll-up results). The customer-facing quote document is **NOT** a new table — it is the existing
> `Modules/Sales` `SalesQuotation` (md/32 §1, ✅ EXISTS), linked from the estimate.
> Net-new tables for Cycle 1: **2**; the consolidated addendum total is **+6** (24 → 30 — this
> pair, plus Cycle 2's `mfg_order_cases`, `mfg_trial_batches`, `mfg_customer_advances`,
> `mfg_order_stage_gates`; see `md/36` §1). **This naming is ratified by `md/36` M-01/M-02** —
> `mfg_quotations`/`mfg_quotation_costings` appear nowhere in the consolidated sheet.

---

## 0. Design invariants (inherited, non-negotiable)

1. **No GL in Cycle 1.** The roll-up runs in **simulation mode**: it never writes
   `mfg_standard_costs`, never calls `Modules\Accounting\Actions\CreateJournalEntry`, never touches
   stock. Outcome is a priced offer only (md/30 §2 step 5).
2. **Draft BOM rides the ratified BOM lifecycle** (`md/20` row 2): `bill_of_materials` with
   `bom_type=Engineering`, `status=Draft`. Guard (already ratified): an `Engineering`/`Draft` BOM
   can **never** be snapshotted into a production order — with exactly ONE ratified exception
   outside Cycle 1: **D-32** lets `order_type=Trial` orders snapshot a Draft BOM under setting
   `production.trial_allow_draft_bom` (`md/35` row 2, `md/36` §4). Within Cycle 1 itself nothing
   is ever snapshotted.
3. **Prospect product** (md/31 D-26): the customer's brought product is onboarded as a Core
   `Product` with `lifecycle=Prospect` (keeps the BOM `product_id` FK integral; promotes cleanly on
   Won). Prospect products are excluded from sales/stock/MRP universes.
4. **Actions forward, events backward** (`md/22` §5): Production Actions call foreign Actions
   (Sales, Core) directly; no module ever imports `Modules\Production`; all reactions to Cycle-1
   milestones are events in `Modules\Production\Events` consumed by Production-side listeners.
5. **House rails:** `BaseModel` (TenantAware/Auditable), `SequenceService` numbering, `production.*`
   permission slices, immutable versioning (recompute = new row, never mutate).

---

## 1. Entities

### 1.1 `mfg_cost_estimates` — estimate header (NEW)

| Column | Type / FK | Notes |
|---|---|---|
| `id`, `company_id`, `branch_id`, audit cols | BaseModel | TenantAware/Auditable |
| `estimate_no` | string, unique/company | `SequenceService::generateNext(company,'production','cost_estimate')` |
| `customer_id` | FK → Core `business_partners` | the requesting customer (same FK target as `SalesQuotation.customer_id`, verified in `Modules/Sales/app/Models/SalesQuotation.php` L137) |
| `prospect_product_id` | FK → Core `products` | `lifecycle=Prospect` (D-26); the customer's brought product |
| `spec_attachment_id` (+ many via morph) | Core `Attachment` | the composition/spec document (تركيبة العميل) |
| `target_quantity` / `unit_id` | decimal / FK | optional indicative volume |
| `draft_bom_id` | FK → `bill_of_materials` | the R&D Engineering/Draft BOM (nullable until R&D submits) |
| `current_version_id` | FK → `mfg_cost_estimate_versions` | pointer to the latest costing version |
| `sales_quotation_id` | FK → `sales_quotations` | set when Quoted (reuse, md/32 §1) |
| `sales_order_id` | FK → `sales_orders` | set on Won (via `convertFromQuotation`) |
| `status` | enum | `{Draft, Estimated, Quoted, Won, Lost}` (+ derived `is_expired` from `valid_until`) |
| `valid_until` | date | quote validity (OQ-5: `production.quote_validity_days`, default 30) |
| `assigned_rnd_user_id`, `lost_reason`, `won_at`, `lost_at`, `notes` | — | workflow/outcome metadata |

### 1.2 `mfg_cost_estimate_versions` — immutable costing runs (NEW)

| Column | Notes |
|---|---|
| `cost_estimate_id`, `version_no` | unique per estimate; recompute = new row (immutability law) |
| `bom_id` + `bom_explosion_snapshot` (json) | frozen explosion of the draft BOM at compute time |
| `price_basis` enum | `{CurrentCost, LastPurchase, Manual}` — material price source per line; `CurrentCost` via `StockService::getProductCost`, `Manual` for not-yet-stocked estimating placeholders |
| `material_cost`, `labor_cost`, `overhead_cost`, `total_unit_cost` | roll-up outputs (work-center rates from `production_centers.labor_cost_rate/machine_cost_rate/overhead_rate` + `cost_driver`, md/20 row 6) |
| `markup_percent`, `quoted_unit_price`, `currency` | commercial layer (Sales) |
| `valid_until`, `computed_by`, `computed_at`, `approved_by`, `approved_at`, `is_current` | review trail |

### 1.3 Reused / extended objects (no new tables)

| Object | Verdict | Role in Cycle 1 |
|---|---|---|
| `bill_of_materials` | **EXTEND (already ratified, md/20 row 2)** | the draft BOM: `bom_type=Engineering`, `status=Draft` |
| Core `products.lifecycle` | **EXTEND** (`+Prospect`, D-26) | prospect product master for the BOM FK |
| `ComputeStandardCost` roll-up engine | **EXTEND** (`mode=Simulation`) | same explosion × WC-rates math as md/20 row 23, but persists to `mfg_cost_estimate_versions` instead of `mfg_standard_costs`; **no GL** |
| `Modules/Sales` `SalesQuotation` + `convertFromQuotation` | **REUSE as-is** (md/32 §1 ✅) | the customer-facing quote + the Won conversion to `SalesOrder` |
| Core `BusinessPartner`, `Attachment`, `SequenceService`, `print.service` | **REUSE** | customer, spec docs, numbering, quote PDF |
| Core `ApprovalWorkflow` engine | **EXTEND** (`ApprovalModule::Production` + doc type `cost_estimate`, md/32 §5) | optional approval gate on Estimated→Quoted |
| `mfg_peggings` | **EXTEND** (md/20 row 26) | add `cost_estimate_id` to the peg chain (lineage spine, R-18) |

---

## 2. State machine (with actors)

Statuses: `Draft → Estimated → Quoted → Won | Lost` (+ derived `Expired` guard on `valid_until`).

| # | From | Action (API entry) | Actor | Guard | To | Event fired |
|---|---|---|---|---|---|---|
| 1 | — | `CreateCostEstimate` | **Sales** | customer is a valid `BusinessPartner`; spec attached; prospect product auto-created (`lifecycle=Prospect`) | `Draft` | `CostEstimateRequested` |
| 2 | `Draft` | `SubmitDraftBom` | **R&D** | BOM is `Engineering`+`Draft`, parent = the prospect product; sets `draft_bom_id` (status unchanged — sub-state = "BOM ready") | `Draft` | `DraftBomReady` |
| 3 | `Draft` | `SimulateCostEstimate` | **Accounting** | `draft_bom_id` set; every BOM line has a resolvable price (`CurrentCost`/`LastPurchase`/`Manual`); writes a new `mfg_cost_estimate_versions` row; **no GL, no stock** | `Estimated` | — (internal) |
| 4 | `Estimated` | `RecostEstimate` | **Accounting** | always allowed; new immutable version, `is_current` moves | `Estimated` | — |
| 5 | `Estimated` | `QuoteCostEstimate` | **Sales** (after Accounting approval of current version — optional Core ApprovalWorkflow gate) | current version approved; sets `markup_percent`, `quoted_unit_price`, `valid_until`; **creates + sends `SalesQuotation`** (Draft→Sent) pricing the prospect product; renders PDF via `print.service`; quote read-only from here (change ⇒ new version + re-quote) | `Quoted` | `EstimateQuoted` |
| 6 | `Quoted` | `RecostEstimate` (re-quote loop) | **Accounting/Sales** | mandatory when `now > valid_until` (Expired) or on renegotiation; returns to step 4 | `Estimated` | — |
| 7 | `Quoted` | `MarkEstimateWon` | **Sales** (triggered by customer acceptance) | linked `SalesQuotation.status=Accepted` **AND** `now ≤ valid_until` (Expired blocks — must re-cost first, OQ-5) | `Won` | `EstimateWon` |
| 8 | `Quoted` | `MarkEstimateLost` | **Sales** | `lost_reason` required; `SalesQuotation` → Rejected/Expired; prospect product stays `Prospect` (archived) | `Lost` | `EstimateLost` |

Cross-cutting guards: every transition is permission-gated (`production.estimates.*`, §5);
the Engineering/Draft BOM remains release-ineligible throughout (invariant #2); zero JE/stock
side-effects in the whole machine (invariant #1).

---

## 3. Events and consumers

All in `Modules\Production\Events`, fired after commit; consumers are Production-side listeners
calling foreign Actions forward (md/22 §2 law).

| Event | Emitter | Payload (key fields) | Consumers |
|---|---|---|---|
| `CostEstimateRequested` | `CreateCostEstimate` | `estimate_id, estimate_no, company_id, customer_id, prospect_product_id, target_quantity, requested_by, requested_at` | R&D worklist/notification feeder (populates the R&D workbench queue); estimates pipeline-board broadcast |
| `DraftBomReady` | `SubmitDraftBom` | `estimate_id, draft_bom_id, bom_version, line_count, has_unpriced_lines, submitted_by` | Accounting costing-workbench queue feeder; optional auto-trigger of `SimulateCostEstimate` (company setting); pipeline-board broadcast |
| `EstimateQuoted` | `QuoteCostEstimate` | `estimate_id, version_id, sales_quotation_id, quoted_unit_price, currency, valid_until, quoted_by` | Sales follow-up scheduler (expiry watcher: flags `Expired` at `valid_until`, blocks Won); management pipeline/dashboard; document archive (PDF attach) |
| `EstimateWon` | `MarkEstimateWon` | `estimate_id, sales_quotation_id, sales_order_id, customer_id, prospect_product_id, draft_bom_id` | **Cycle-2 seeder** (§6): product promotion, pegging seed, trial-batch offer (R-05) |
| `EstimateLost` | `MarkEstimateLost` | `estimate_id, lost_reason, lost_at` | pipeline analytics (win/loss reporting); lineage closure |

---

## 4. Screens (stage-gate pattern, R-17; Angular folder `production/estimates/`)

| # | Screen | Owner (actor) | Contents / actions |
|---|---|---|---|
| 1 | **Estimates pipeline board** | Sales + management | kanban `Draft / Estimated / Quoted / Won / Lost` (+Expired badge), one card per estimate; entry to all other screens |
| 2 | **RFQ intake** | Sales | create estimate: customer (BusinessPartner picker), product description, spec attachments, target qty → `CreateCostEstimate` |
| 3 | **R&D draft-BOM workbench** | R&D | queue from `CostEstimateRequested`; create prospect product; author Engineering/Draft BOM (lines may use Manual-price placeholders); `SubmitDraftBom` button |
| 4 | **Costing workbench** | Accounting | queue from `DraftBomReady`; run `SimulateCostEstimate`; M/L/OH breakdown per version; price-basis per line; version history (immutable); approve current version; `RecostEstimate` |
| 5 | **Quote screen** | Sales | markup %, quoted price, `valid_until`; generate/send `SalesQuotation` + PDF → `QuoteCostEstimate`; Won/Lost buttons (Won disabled when Expired) |
| 6 | **Estimate lineage view** | all (read) | timeline: RFQ → draft BOM → versions → quote → outcome → (Cycle-2 documents once Won), walking `mfg_peggings` (R-18) |

Each screen exposes only its stage's data + the transition button(s); buttons call the same Actions
the API exposes (screens never bypass the state machine — R-17 rule).

---

## 5. Permissions (`production.*` slices, D-01 + C-04 R&D actor)

`production.estimates.view` · `production.estimates.create` (Sales) ·
`production.estimates.rnd.draft_bom` (R&D) · `production.estimates.cost` + `.approve_costing`
(Accounting) · `production.estimates.quote` (Sales) · `production.estimates.convert` (Sales,
Won/Lost). Supervisor role spans all (OQ-7 default).

---

## 6. Conversion path: Won estimate → Cycle-2 entry

`MarkEstimateWon` runs one transaction (then fires `EstimateWon`):

1. **Sales order** — customer acceptance flows through the existing conversion *logic*, invoked
   ONLY via the forward Action **`Modules\Sales\Actions\ConvertQuotationToOrder`** (the `md/36`
   #19 extraction of `SalesOrderController::convertFromQuotation()` L368, same controller→Action
   recipe as `CreatePurchaseRequest`, `md/20` §4 — Production never calls a foreign controller,
   `md/22` §0.1). Guard `Accepted`; stamps `quotation_id` on the order, marks the quotation
   `Converted`. The resulting `sales_order_id` is stamped on the estimate. **This sales order is
   the Cycle-2 demand anchor** (T-8/R-08).
2. **Product promotion** — prospect product `lifecycle: Prospect → Active` (D-26): now eligible
   for stock, MRP and sales lines.
3. **BOM hand-off (NOT auto-promotion)** — the Engineering/Draft BOM is handed to Cycle 2 still
   Draft. Per R-09, R&D finalizes it (promotion `Engineering/Draft → Production/Active`, one
   Active version per effectivity window, controlled transition not edit-in-place) **after** the
   trial outcome — because trial learnings feed the final recipe. If the customer skips the trial,
   R&D may promote immediately on Won.
4. **Pegging seed** — `mfg_peggings` row linking `cost_estimate_id ↔ sales_order_id` so the full
   lineage (RFQ → draft BOM → versions → quote → order → trial → MO → … → FG) reconstructs (R-18).
5. **Cycle-2 fork** — the `EstimateWon` consumer offers the two ratified entries:
   (a) **Trial** (R-05): create `mfg_trial_batches` + production order `order_type=Trial` pegged to
   the order, executed by R&D on the real Release→Issue→Confirm→Receive rails (real cost, real JEs
   — Cycle 2's first GL); or (b) **direct formal**: straight to R-09 finalize BOM → R-10 planning →
   R-11 advance gate → R-12 reserve → R-13 Release.

Cost continuity: the winning `mfg_cost_estimate_versions` row is the baseline against which the
trial's `actual_unit_cost` (R-06) is trued-up, and a candidate seed for the Phase-3
`mfg_standard_costs` first standard once the BOM is Active.

---

## 7. Reused vs net-new (summary)

**REUSE as-is:** `SalesQuotation` lifecycle + `convertFromQuotation`; `BusinessPartner`;
`Attachment`; `SequenceService`; `print.service`; Core `ApprovalWorkflow` engine;
`StockService::getProductCost` (price source).
**EXTEND (already ratified or small):** `bill_of_materials` lifecycle (md/20 row 2 — no new work);
`products.lifecycle += Prospect` (D-26); `ComputeStandardCost += mode=Simulation` (pulls the
explosion×rates math forward from Phase 3 as an estimate-only variant — C-03); `ApprovalModule +=
Production` + doc type `cost_estimate`; `mfg_peggings += cost_estimate_id`.
**NET-NEW (2 tables):** `mfg_cost_estimates`, `mfg_cost_estimate_versions` — plus the 8 Actions,
5 events, 6 screens above. **Zero new GL paths** (Cycle 1 posts nothing).

## 8. Roadmap placement & open decisions

- Lands in **Phase 2.5 "Customer Front"** (`md/36` §3 — the slot `md/30` §6 provisionally called
  "1.5") — after Phase 1 (BOM lifecycle) **AND Phase 2**, because the labor/machine work-center
  rates and `cost_driver` the roll-up prices conversion with land in **Phase 2** (`md/23` Phase 2;
  this corrects the earlier claim that Phase 1 alone sufficed); before/independent of Planning.
  The simulation roll-up is the C-03 pull-forward of Phase 3's estimate-only math (no variance
  engine needed).
- Requires sign-off (authoritative register `md/36` §4): **D-31** (build the funnel — recommended;
  formerly cited here under `md/31`'s provisional "D-23"), **D-26** (Prospect lifecycle),
  **D-29** (reuse SalesQuotation — evidence confirmed md/32 §1), **D-34/OQ-5** (`valid_until`
  default 30 days; expired quote blocks Won until re-costed).

---


# 35 — Gated MTO Production Cycle Design (Cycle 2, end-to-end)

> **Design addendum.** Turns the client requirements (`md/30` R-05..R-18), the coverage audit
> (`md/31` C-05/C-06/C-08), the ERP evidence (`md/32`) and the pharma defaults (`md/33` P2/P3)
> into ONE executable design for the make-to-order customer case: state machine, entity decision,
> stage screens, events, postings, permissions. Everything stays inside the ratified rails:
> `Modules/Production` extension, `mfg_*` prefix, `production.*` permissions, Actions-forward /
> events-backward (`md/22` §0/§5), `SequenceService` numbering. Nothing here re-opens D-01..D-22 —
> with TWO explicitly ratified amendments carried as decision rows in the authoritative register
> (`md/36` §4): **D-32** (Trial draft-BOM snapshot carve-out, amends `md/20` row 2) and
> **LA-1/D-33** (advance cash-in rides the `ReceiptVoucher` rail, amends `md/22` §5.4).
> Cycle-1 naming follows the canonical `md/34` pair (`mfg_cost_estimates` /
> `mfg_cost_estimate_versions` — the provisional `mfg_quotations` names are retired).

---

## 0. THE ENTITY DECISION — thin umbrella `mfg_order_cases` (RECOMMENDED), not columns

**Question:** model the customer production case as columns on `production_orders`
(case linkage + `order_type` + `sales_order_id` + deposit fields), or as a thin umbrella entity?

**Decision: `mfg_order_cases` umbrella entity + minimal columns on `production_orders`.**

| # | Reason |
|---|---|
| 1 | **1 case ↔ N production orders.** A case owns the trial order(s) (`order_type=Trial`, re-trials loop) *and* the main order (and possible split lots). Columns on one order row cannot hold a lifecycle that spans several orders. |
| 2 | **7 of 14 case stages exist when NO released production order exists** (SalesOrder, TrialRequested, TrialApproved, BomFinalized, PlanningChecked, DepositGate, and Cancelled-before-release). Columns would have no row to live on. |
| 3 | **Two machines, one spine.** `ProductionOrderStatus` (Planned→Released→InProcess→Completed→Closed, `md/20` row 13) remains the untouched financial/execution machine; the case machine is the *commercial orchestration* layer above it. Widening the order enum to 14 states would corrupt the Phase-1 state machine already ratified. |
| 4 | **Deposit is case-level, not order-level.** One advance covers the whole engagement (trial + main); `mfg_customer_advances` pegs naturally to the case. |
| 5 | **The stage board (R-17) operates on cases.** Kanban cards are cases; shop-floor terminals stay operation-level inside one stage (see §4). |

**Rejected:** columns-only on `production_orders` — duplicates deposit fields across trial+main
rows, has no home for pre-order stages, and forces the execution enum to absorb commercial states.

### 0.1 Schema

```text
mfg_order_cases                              -- NEW (BaseModel: TenantAware, Auditable)
  id, company_id, branch_id
  case_no            SequenceService::generateNext($company,'production','case')
  partner_id         customer (Core partner)
  sales_order_id     FK sales_orders        -- demand anchor (R-08); FK lives mfg-side only (law §5.3)
  cost_estimate_id   FK mfg_cost_estimates NULL -- Cycle-1 lineage (md/34 canonical naming)
  product_id, quantity, unit_id
  stage              enum {SalesOrder, TrialRequested, TrialInProduction, TrialApproved,
                           BomFinalized, PlanningChecked, DepositGate, MaterialsReserved,
                           InProduction, QcRelease, InFgWarehouse, Delivered, Closed, Cancelled}
  trial_cost_policy  enum {Bill, Absorb, CreditOnWin}  -- default settings 'production.trial_cost_policy'
  deposit_percent    decimal NULL           -- override of production.default_advance_percent (OQ-2)
  deposit_basis      enum {OwnMaterialCost, OrderValue}
  deposit_required, deposit_received        -- denormalized from mfg_customer_advances
  deposit_status     enum {NotRequired, Due, Received, Waived}
  consignment_warehouse_id FK warehouses NULL  -- customer free-issue bucket (is_consignment, D-15)
  active_bom_id      FK bill_of_materials NULL -- stamped at BomFinalized
  planning_verdict   json NULL              -- shortages snapshot at PlanningChecked
  closed_at, cancelled_at
```

**EXTEND `production_orders`:** `case_id` FK NULL (Standard MTS orders carry none) ·
`order_type` enum gains `Trial` (→ `{Standard, Rework, Repair, Trial}`, amends `md/20` row 13) ·
`sales_order_id` FK NULL (direct spine column per `md/32` §7; `mfg_peggings` stays canonical for
MRP allocation quantities). **No deposit columns on orders.**

**Other tables (already in `md/30` §4, amended here):** `mfg_trial_batches` gains `case_id`;
`mfg_customer_advances` keys on `case_id` (+ `receipt_voucher_id`); **`mfg_order_stage_gates` is
re-keyed to `case_id`** (`case_id, from_stage, to_stage, actor_user_id, gate_snapshot json, at`)
— it is the audit log of every board move; **human sign-off gates (BOM finalize, trial decision,
advance waive, QC release) ride the Core `ApprovalWorkflow` engine (`md/32` §5) as transition
preconditions — the gate table is the audit log, never a second approval mechanism** (split rule,
`md/36` M-10). Net table delta vs the published 24: **+6 → 30 total**
(`mfg_cost_estimates`, `mfg_cost_estimate_versions`, `mfg_trial_batches`, `mfg_customer_advances`,
`mfg_order_stage_gates`, `mfg_order_cases`) — ratified by `md/36` §1.

---

## 1. The gated state machine

One transition = one permission-guarded Action that (a) validates the gate, (b) mutates `stage`,
(c) appends `mfg_order_stage_gates`, (d) fires the event `afterCommit`. Screens call the same
Actions the API exposes — no bypass (R-17). `Cancelled` is reachable from every stage before
`InProduction` via `CancelProductionCase` (reservations released, advance refunded/forfeited per
OQ-6 policy).

| # | Transition | Gate (precondition) | Actor (perm) | Action → Event |
|---|---|---|---|---|
| 0 | — → `SalesOrder` | Confirmed `SalesOrder` exists (via the forward Action `Sales\ConvertQuotationToOrder` — the `md/36` #19 extraction of `convertFromQuotation`, `md/32` §1) | Sales (`production.case.open`) | `OpenProductionCase` → `ProductionCaseOpened` |
| 1 | `SalesOrder` → `TrialRequested` | Trial qty + `trial_cost_policy` captured | Sales (`production.case.trial_request`) | `RequestCaseTrial` → `CaseTrialRequested` |
| 1b | `SalesOrder` → `BomFinalized` *(skip-trial path)* | Repeat product with existing `Active` BOM AND customer waiver recorded | Supervisor (`production.case.trial_skip`) | `SkipCaseTrial` → `CaseTrialSkipped` |
| 2 | `TrialRequested` → `TrialInProduction` | A BOM exists. **Carve-out (ratified as D-32, an explicit amendment to `md/20` row 2):** `order_type=Trial` orders MAY snapshot an `Engineering/Draft` BOM (guarded by setting `production.trial_allow_draft_bom`); Standard orders never | R&D (`production.case.trial_create`) | `CreateTrialOrder` — real production order, `order_type=Trial`, small qty, `case_id` set; runs the FULL Release→Issue→Confirm→Receive→Close rail, fully costed; **trial output is received into the trial/evaluation bucket (warehouse-as-state, non-sellable FG)** → `TrialOrderCreated` |
| 3 | `TrialInProduction` → `TrialApproved` | Trial order `Closed` + **trial-sample hand-over document exists** (tracking-only issue out of the evaluation bucket, stamped on `mfg_trial_batches.sample_delivery_doc_id`/`sample_delivered_at` — the sample never reaches the customer undocumented, U-3) + customer sign-off attachment + `mfg_trial_batches.result=Passed` | Sales (`production.case.trial_decide`) | `RecordTrialDecision` → `CaseTrialDecided` |
| 3b | `TrialInProduction` → `TrialRequested` *(re-trial)* / → `Cancelled` | `result=Failed`; cost ownership inherits `trial_cost_policy` (OQ-1/OQ-3) | Sales | same Action, `Failed` branch |
| 4 | `TrialApproved` → `BomFinalized` | R&D promotes Draft → `bom_type=Production`,`status=Active`; one Active version per effectivity window (`md/20` row 2); `active_bom_id` stamped | R&D (`production.case.bom_finalize`) | `PromoteCaseBom` → `CaseBomFinalized` |
| 5 | `BomFinalized` → `PlanningChecked` | **Single-order MRP run**: `RunMrp` scoped to this case's pegged demand — gross→net (on-hand − reserved + open POs), explode `active_bom_id` × qty (+scrap%), **split Own vs Customer-supplied lines** (R-10); own shortages → **DRAFT purchase requests prepared but NOT submitted** (the deposit funds this buying — see row 7); verdict persisted in `planning_verdict` | Planning (`production.case.plan`) | `RunCasePlanning` → `CasePlanningChecked` |
| 6 | `PlanningChecked` → `DepositGate` | Auto: compute `deposit_required = deposit_percent × basis`; if zero own-procurement and policy allows → `deposit_status=NotRequired`, auto-pass | system | auto → — |
| 7 | `DepositGate` → `MaterialsReserved` | **HARD GATE:** Σ approved `ReceiptVoucher`s with `reference_type='mfg_order_case'`, `reference_id=case` ≥ `deposit_required` (`md/32` §2; the voucher's own JE is the advance posting — LA-1/D-33), OR `Waived` by `production.case.deposit_waive`. **On `CaseDepositSatisfied` the row-5 draft PRs are NOW submitted via `Purchases\CreatePurchaseRequest` (`md/22` #6) tagged billable-to-customer — purchasing-on-behalf NEVER precedes the deposit (the client's stated causal rule, U-6; fixes the earlier row-5 placement).** Then `ReleaseProductionOrder` runs for the main order — the advance gate is its new precondition (per `md/30` R-11) — freezing the snapshot and reserving **as its ratified side-effect** (`md/20` row 13): `StockService::reserve()` per component pegged to the order (A5); own components from own stock, customer components from the consignment warehouse; tool reserved | Accounting records (`production.case.deposit_record`); release by Planning/PM (`production.case.release`) | `AllocateCaseAdvance` / `WaiveCaseDeposit` → `CaseDepositSatisfied`; then `ReleaseProductionOrder` → `ProductionOrderReleased` (reused) + `CaseMaterialsReserved` |
| 8 | `MaterialsReserved` → `InProduction` | **Auto** on first `MaterialIssued` event of the main order (case-side listener). Customer-supplied materials: received beforehand into the consignment warehouse as **free-issue via a real Inventory receipt document** (`ReceiveCustomerMaterial` → tracking-only `ApproveReceipt` branch for `is_consignment` warehouses — the U-2 intake document; never a direct balance write, `md/22` §1) — tracking-only movements, **never costed into our COGS** (no RM credit, no WIP material debit — `md/22` §4 toll row), but their **lot numbers ARE written into batch genealogy** (md/33 P3). **Unused customer material at case close returns via the mirror tracking-only issue document (`ReturnCustomerMaterial`) + reconciliation: received − issued − consignment scrap (D-30) = returned** | Stores issue; floor executes | existing rail: `PostMaterialIssue`/`PostConfirmation`/`PostGoodsReceipt`; new `CaseConsignmentReceived`/`CaseConsignmentReturned` |
| 9 | `InProduction` → `QcRelease` | Auto on `GoodsReceiptPosted` covering order qty; GR lines land `quality_status=OnHold` (pharma default flip, `production.gr_default_quality_status=OnHold`, md/33 P2) — FG sits in quarantine, not sellable | system | listener → `CaseAwaitingQc` |
| 10 | `QcRelease` → `InFgWarehouse` | ALL `qms_inspections` for the GR = `Released` (QMS hold; rejected lines → NCR loop via `QMS\CreateNonConformance`) | QC (`production.case.qc_release`) | `ReleaseCaseQc` → `CaseQcReleased` |
| 11 | `InFgWarehouse` → `Delivered` | Delivery note confirmed + **final invoice posted — created forward via the net-new thin Action `Modules\Sales\Actions\CreateServiceInvoice` (`md/36` #20; the U-4 compliant rail — never a controller call or direct Sales writes)**: toll branch = conversion-fee line **+ material pass-through line(s) at cost (+ contractual handling %) for factory-bought-on-behalf RM (U-1, §3)**, FG is customer property; own branch: product invoice + **deposit settled** against it (§3) | Sales/Accounting (`production.case.deliver`) | `InvoiceAndSettleCase` → `CaseDelivered` |
| 12 | `Delivered` → `Closed` | Main order `Closed` (7 variances posted, WIP zeroed — `ProductionOrderClosed`) AND invoice settlement complete | system/Accounting | auto → `CaseClosed` |

### 1.1 Trial cost policy (configurable, OQ-1/OQ-3 resolution)

`trial_cost_policy` per case, default `production.trial_cost_policy`:

- **Bill** — trial billed at cost + agreed markup as a Sales service invoice on trial decision
  (win or lose).
- **Absorb** — at trial-order close: DR R&D Expense / CR WIP (`entry_type=production_trial_absorb`).
- **CreditOnWin** — at trial-order close: DR Deferred Trial Cost (asset) / CR WIP
  (`production_trial_defer`); on win, drawn into the engagement (commercial credit line on the final
  invoice + DR order cost / CR Deferred — `production_trial_apply`); on loss/abandon, written off:
  DR R&D Expense / CR Deferred (`production_trial_writeoff`).

Trial orders are excluded from standard-cost baselining and reported separately (filter
`order_type=Trial` — `md/30` R-05).

---

## 2. Events (new + reused)

New, in `Modules\Production\Events`, fired `afterCommit`, idempotent listeners (`md/22` §2 pattern):
`ProductionCaseOpened`, `CaseTrialRequested`, `TrialOrderCreated`, `CaseTrialDecided`,
`CaseBomFinalized`, `CasePlanningChecked`, `CaseDepositSatisfied`, `CaseMaterialsReserved`,
`CaseQcReleased`, `CaseDelivered`, `CaseClosed`, `CaseCancelled` (+ `CaseConsignmentReceived`,
`CaseConsignmentReturned` for the U-2 free-issue intake/return documents). **This vocabulary —
together with `md/34`'s five estimate events — is the single canonical event set, ratified by
`md/36` §2.1.** Payload core:
`case_id, case_no, company_id, branch_id, sales_order_id, stage_from, stage_to, actor_user_id, at`
(+ stage-specific fields, e.g. `deposit_required/received` on `CaseDepositSatisfied`).

Reused unchanged: `ProductionOrderReleased`, `MaterialIssued`, `ConfirmationPosted`,
`GoodsReceiptPosted`, `ProductionOrderClosed` — **case-side listeners** on these auto-advance the
case (rows 8/9/12 above). Dependency law intact: no foreign module imports Production; the case
listeners call foreign Actions forward only.

---

## 3. Accounting postings (deposit liability → settlement)

Accounts via `SettingsService` keys seeded by `AutoAccountService` (detail accounts):
`production.customer_advance_account_id` (new `2105 Customer Advances / دفعات عملاء تحت الحساب`
under `2104 Unearned Revenue` — `md/32` §2), `production.deferred_trial_cost_account_id`,
`production.rnd_expense_account_id`. Cash-in rides `ReceiptVoucher` + `ApproveReceiptVoucher`
(account-agnostic line posting — EXISTS, `md/32` §2) — **this is the explicitly ratified law
amendment LA-1/D-33 to `md/22` §5.4: the voucher engine's own JE IS the advance posting (single
posting, no parallel `CreateJournalEntry` call)**; everything else rides `CreateJournalEntry`
with `source_type='production_order'`/`'mfg_order_case'`.

| Event | entry_type | DR | CR | Amount |
|---|---|---|---|---|
| Advance received (DepositGate) | `production_customer_advance` | Cash/Bank | **2105 Customer Advances (liability)** | receipt amount; voucher `reference_type='mfg_order_case'`; posted ONCE by the voucher engine (LA-1/D-33) |
| Trial — Bill | (normal Sales invoice) | AR | Trial-service revenue | trial cost + markup |
| Trial — Absorb | `production_trial_absorb` | R&D Expense | WIP | trial WIP residual |
| Trial — CreditOnWin (defer) | `production_trial_defer` | Deferred Trial Cost | WIP | trial WIP residual |
| Trial — applied on win / written off on loss | `production_trial_apply` / `production_trial_writeoff` | Order cost / R&D Expense | Deferred Trial Cost | deferred balance |
| Production run | — | *the existing 5 JE families unchanged* (`md/22` §4: issue, labor, OH, scrap, FG receipt + variances) | | |
| Customer-supplied (free-issue) | — | **no JE ever** — tracking-only consignment movement; lots recorded in genealogy | | |
| **On-behalf material relief at toll FG receipt (U-1)** | `production_toll_material_cogs` | Toll material COGS (recharged) | WIP | material portion of WIP for factory-bought-on-behalf components |
| Final invoice (toll, via `Sales\CreateServiceInvoice` — U-4) | (Sales invoice → EInvoicing) | AR | Toll-service revenue + **Material pass-through revenue (U-1)** | conversion fee + on-behalf material at cost (+ contractual handling %) — the invoice now CONTAINS the material the advance funded; the cash math closes |
| **Deposit settlement** | `production_advance_settlement` *(single canonical name — `md/36`'s earlier `production_advance_application` alias retired)* | **2105 Customer Advances** | **AR** | `min(advance balance, invoice total)` |
| Advance refund (cancel, per OQ-6) | `production_advance_refund` | 2105 Customer Advances | Cash/Bank | residual after incurred cost |

---

## 4. The stage screens (R-17): board + per-stage workspaces

**Pattern:** one order-centric **kanban stage board** (`production/cases/board` — columns = case
stages, cards = cases, drag disabled: moves happen ONLY through the stage action) + a **dedicated
workspace per stage** showing that stage's queue, its gate checklist (live evaluation of the
precondition), and the single primary action that advances the case. Every action button calls the
same API Action as §1; every move is audited in `mfg_order_stage_gates`.

| Screen (route under `production/cases/`) | Owner dept | Queue shows | Primary action |
|---|---|---|---|
| `intake` — Case desk | Sales | confirmed sales orders without a case / stage `SalesOrder` | Open case · Request trial · Skip trial |
| `trial-bench` | R&D | `TrialRequested` + `TrialInProduction` monitor (links into the trial order detail) | Create trial order |
| `trial-review` | Sales | trials awaiting customer verdict | Record sample hand-over (delivery document out of the evaluation bucket — required first) · Record decision (Passed/Failed + sign-off attachment) |
| `bom-bench` | R&D | `TrialApproved` | Promote BOM Draft→Active |
| `planning-bench` | Planning | `BomFinalized` | Run case planning (MRP single-order) · view shortages / Own-vs-Customer split · raise PRs |
| `deposit-desk` | Accounting | `DepositGate` with required/received/remaining | Allocate receipt voucher · Waive (privileged) |
| `reserve-desk` | Stores + Planning | deposit-satisfied cases | Receive customer free-issue into consignment (**backed by the `ReceiveCustomerMaterial` tracking-only receipt document — the U-2 rail, not a bare button**) · Release order (snapshot + reserve as Release side-effect) · Return unused customer material at close (`ReturnCustomerMaterial` + reconciliation) |
| `production-monitor` | Production | `InProduction` — order/operation progress, issues, confirmations | (monitoring; advance is automatic) |
| `qc-desk` | QC | `QcRelease` — GR lines OnHold + inspection results | Release / Reject (NCR) |
| `delivery-desk` | Sales/Accounting | `InFgWarehouse` | Delivery note + final invoice via `CreateServiceInvoice` (conversion fee + material pass-through lines) + deposit settlement |

**Relation to shop-floor terminals (Phase 5, `md/20` §5):** terminals remain **operation-level
inside the `InProduction` stage** — operators Start/Done operations and post confirmations; they
**never move the case**. The case auto-advances off the events those confirmations already fire
(`MaterialIssued` → InProduction; `GoodsReceiptPosted` → QcRelease). Stage screens are back-office
workbenches; terminals are floor devices. Two layers, one state machine each, joined by events.

**Interpretation caveat (OQ-9, must be answered at the Phase-0 gate):** this whole section reads
the client's «واجهات لكل مرحلة إنتاجية» as *commercial case stages*. If he means
per-MANUFACTURING-stage screens (mixing/granulation/compression/packaging — pharma-plausible),
those are per-operation workbenches over routing operations: served by the Phase 5 SFC terminals,
with an early read-only per-operation `production-monitor` view shippable in Phase 2.5. Logged as
**OQ-9** in `md/30` §5 / `md/36` §4. Phasing of THESE workbenches: board MVP Phase 2,
gate-critical workbenches Phase 2.5, completion Phase 5 (`md/36` §3 — D-28).

---

## 5. Permissions per stage/department

Slice under the existing `production` contributor in `PermissionDependencyRegistry`; supervisor
role spans all (OQ-7 default — folded into D-28). **This `production.case.*` namespace (with
`md/34`'s `production.estimates.*` for Cycle 1) is the single canonical permission taxonomy,
ratified by `md/36` M-19.** Human sign-offs ride Core `ApprovalWorkflow` once
`ApprovalModule::Production` lands (`md/32` §5) — as transition *preconditions*, per the §0.1
split rule (the gate table stays the audit log).

| Permission | Department | Guards |
|---|---|---|
| `production.case.view` | all stage owners | board + read |
| `production.case.open` / `.trial_request` / `.trial_decide` / `.deliver` | Sales | rows 0,1,3,11 |
| `production.case.trial_create` / `.bom_finalize` | R&D | rows 2,4 |
| `production.case.plan` | Planning | row 5 |
| `production.case.deposit_record` / `.deposit_waive` | Accounting (waive = elevated) | row 7 gate |
| `production.case.release` | Planning / Production manager | row 7 release |
| `production.case.consignment_receive` + existing issue perms | Stores | free-issue + issues |
| `production.case.qc_release` | QC | row 10 |
| `production.case.trial_skip` / `.cancel` | Supervisor | 1b, Cancelled |

---

## 6. Settings keys (seed via `SettingDefinition`)

`production.default_advance_percent` (OQ-2; v1 basis = own-procured material cost) ·
`production.advance_basis` {OwnMaterialCost, OrderValue} · `production.trial_cost_policy`
{Bill, Absorb, CreditOnWin} · `production.trial_allow_draft_bom` (bool) ·
`production.customer_advance_account_id` · `production.deferred_trial_cost_account_id` ·
`production.rnd_expense_account_id` · `production.gr_default_quality_status` (pharma: `OnHold`).

## 7. Roadmap & published-doc amendments

- Lands per the **`md/36` §3 phasing** (which supersedes the provisional "Phase 1.5" slot of
  `md/30` §6 — correctly placed AFTER Phase 2, since labor/OH work-center rates + `cost_driver`
  land in Phase 2, `md/23` L89): case core + deposit gate + stage-gate table + board MVP in
  **Phase 2**; estimates/trial flow + gate-critical workbenches in **Phase 2.5 "Customer Front"**;
  no planning engine needed except the scoped single-order MRP call, which can ship as a
  case-local explosion before full Phase 4 MRP.
- Amends `md/30` §4: `mfg_order_stage_gates` re-keyed to `case_id`; adds `mfg_order_cases`
  (table count 24 → 30 total new `mfg_*` tables; Cycle-1 pair under `md/34`'s canonical names).
- Amends `md/20` row 13: `order_type` += `Trial`; adds `case_id`, `sales_order_id` columns; and
  row 2 gains the **D-32** Trial draft-BOM carve-out (explicit ratified amendment).
- Amends `md/22`: `ReleaseProductionOrder` gains the advance-gate precondition (which also holds
  back on-behalf PR submission); **LA-1/D-33** amends §5.4 for the voucher-ridden advance; new
  `entry_type` values listed in §3; 12+2 new case events appended to §2; `CreateServiceInvoice`,
  `ReceiveCustomerMaterial`, `ReturnCustomerMaterial` registered as forward entry points.
- Decisions consumed: D-15 consignment, A5 reservation, D-21→OnHold flip for pharma (md/33 P2),
  OQ-1/2/3/6/7 given concrete configurable defaults pending board ratification via the
  authoritative register **`md/36` §4 (D-23..D-36 + OQ-9)**.

---


# 36 — Plan Amendments: Consolidated Changes to Mapping (20), Contracts (22) and Roadmap (23)

> **Status: amendment sheet — the single consolidated delta** that the pharma toll-manufacturing
> client interview (`md/30` requirements, `md/31` coverage audit, `md/32` ERP evidence, `md/33`
> pharma domain) imposes on the three published decision docs: `md/20-mapping.md`,
> `md/22-contracts.md`, `md/23-roadmap.md`. Everything here is **additive**, with exactly TWO
> explicitly ratified exceptions (each carries its own decision row — nothing is changed silently):
> **LA-1 / D-33** (the customer-advance cash-in posts via the `ReceiptVoucher` rail, an amendment
> to `md/22` §5.4) and **D-32** (Trial orders may snapshot a Draft BOM, an amendment to `md/20`
> row 2). All other published verdicts (D-01..D-22), the 5 JE families, the production-order state
> machine and the dependency law stand untouched.
> The client is a **real prospective customer** — re-phasing below treats his flow as launch scope.
>
> **Entity & naming model (consolidated, ends the 34/35/36 fork):** this sheet ratifies
> **`md/34`'s canonical Cycle-1 pair** (`mfg_cost_estimates` + `mfg_cost_estimate_versions` —
> the provisional `mfg_quotations`/`mfg_quotation_costings` names from `md/30` are retired) and
> **`md/35`'s case umbrella** (`mfg_order_cases` — the Cycle-2 commercial machine lives on the
> case, **never** as pre-states grafted onto `production_orders.status`). Event names, permission
> namespaces, sequence keys and FKs below all follow `md/34`/`md/35`.

---

## 1. Mapping amendments (delta to `md/20`)

Format: البند | جديد / تعديل على | الجدول | الملاحظة.
New-table tally moves **24 → 30 `mfg_*` tables** (+6: cost_estimates, cost_estimate_versions,
trial_batches, customer_advances, order_stage_gates, order_cases).

| # | البند (item) | جديد / تعديل على | الجدول (table) | الملاحظة (note) |
|---|---|---|---|---|
| M-01 | Exploratory-costing estimate header / RFQ intake (ملف التسعير الاستكشافي) | **NEW** | `mfg_cost_estimates` | Cycle-1 lineage head (T-8). `customer_id`, prospect product (`lifecycle=Prospect`, D-26), spec attachments, `draft_bom_id`, `current_version_id`, `sales_quotation_id` (**REUSE** `Modules\Sales\SalesQuotation` as the customer-facing commercial doc — verified EXISTS with full Draft→Sent→Accepted→Converted lifecycle + conversion, `md/32` §1; D-29), `sales_order_id` on Won. **Lean mfg-side status `{Draft, Estimated, Quoted, Won, Lost}`** — the commercial funnel states (Sent/Accepted/Rejected/Expired/Converted) live ONLY on `SalesQuotation`, never duplicated mfg-side (avoids a dual-state-machine sync problem). Seq `SequenceService('production','cost_estimate')`. Canonical per `md/34`; retires `mfg_quotations`. |
| M-02 | Exploratory cost estimate versions (التكلفة الاستكشافية) | **NEW** | `mfg_cost_estimate_versions` | **Versioned, immutable** estimate rows (recompute = new version): `material_cost`, `labor_cost`, `overhead_cost`, `total_unit_cost`, `markup_percent`, `quoted_unit_price`, `valid_until`, `price_basis`. Runs the `RollUpStandardCost` core in **simulation-only mode** against a Draft/Engineering BOM. **No GL, no stock.** Canonical per `md/34`; retires `mfg_quotation_costings`. |
| M-03 | Production case umbrella (ملف أمر العميل) | **NEW** | `mfg_order_cases` | **The Cycle-2 entity decision (per `md/35` §0, ratified here):** one case per customer engagement, `sales_order_id` demand anchor, `cost_estimate_id` Cycle-1 lineage, 14-stage commercial machine `{SalesOrder, TrialRequested, TrialInProduction, TrialApproved, BomFinalized, PlanningChecked, DepositGate, MaterialsReserved, InProduction, QcRelease, InFgWarehouse, Delivered, Closed, Cancelled}`, `trial_cost_policy`, deposit fields, `consignment_warehouse_id`, `active_bom_id`, `planning_verdict`. Rationale: 1 case ↔ N production orders (trial loops + main); 7 of 14 stages exist before any production order; deposit is case-level. **The ratified order state machine is NOT widened — no pre-states on `production_orders.status`.** |
| M-04 | Trial batch (الباتش التجريبي) | **NEW** | `mfg_trial_batches` | `case_id`, `cost_estimate_id`, `production_order_id` (the Trial order), `trial_quantity`, `actual_unit_cost` (true-up source for the final quote), `result ∈ {Pending, Passed, Failed}`, `evaluated_at`, `sample_delivery_doc_id` + `sample_delivered_at` (U-3 hand-over). Failed blocks formal-order conversion (`md/30` R-07). **Kept as a separate entity deliberately, against `md/32` §4's leaner order-flag recommendation:** one case spans N re-trial orders, each needing its own customer verdict/sign-off/sample-delivery record that the production order (cost carrier) should not absorb; the approval engine records the sign-off, the trial row anchors it. |
| M-05 | Trial order type | **EXTEND** | `production_orders` | Mapping row 13 enum widens: `order_type ∈ {Standard, Rework, Repair, **Trial**}` (D-24 ratified as enum value, not flag). Trial orders ride the FULL Release→Issue→Confirm→Receive→Close + JE rail unchanged; reported separately; **excluded from standard-cost baselining**. **D-32 carve-out (explicit amendment to `md/20` row 2):** ONLY `order_type=Trial` orders may snapshot an `Engineering/Draft` BOM, guarded by setting `production.trial_allow_draft_bom`; Standard orders never — without this ratified exception the trial-before-BOM-finalization flow is unimplementable. |
| M-06 | Sales-order / case link on the order | **EXTEND** | `production_orders` | Add `sales_order_id` + `case_id` columns (verified absent — fillable has neither, `md/32` §4; per `md/35` §0.1). Direct demand anchor for MTO; complements (does not replace) `mfg_peggings`. No deposit columns and **no commercial states** on orders. |
| M-07 | Customer advance / deposit (الدفعة المقدمة) | **NEW** | `mfg_customer_advances` | **Keyed to `case_id`** (one advance covers the whole engagement — trial + main; per `md/35`, ends the three-way anchor fork): `case_id`, `amount`, `percent`, `basis ∈ {OwnMaterialCost, OrderValue}`, `status ∈ {Due, Received, Waived}`, `receipt_voucher_id`, `journal_entry_id` (the voucher's JE), `applied_invoice_id`. The data backing the Release gate (R-11). `Waived` requires `production.case.deposit_waive` and is audited. |
| M-08 | Customer-advances liability account | **EXTEND** | Accounting COA + settings | New detail account **Customer Advances (دفعات عملاء تحت الحساب)** as child of `2104 Unearned Revenue` (seeded via `AutoAccountService`); setting key `production.customer_advance_account_id`. NOT `SalesPayment` (invoice-bound, hard-wired DR Cash/CR AR — wrong tool, `md/32` §2). |
| M-09 | Deposit cash receipt | **REUSE + LA-1 law amendment (D-33)** | `receipt_vouchers` | `ReceiptVoucher` + `ApproveReceiptVoucher` are account-agnostic (line account drives the CR — verified `buildJournalLines`). Wire **`reference_type='mfg_order_case'`** + line account = Customer Advances. **LA-1 (explicit amendment to `md/22` §5.4):** the advance's DR Cash / CR Customer-Advances JE is **the voucher engine's own JE** — a single posting; Production does NOT additionally call `CreateJournalEntry` for it (the earlier double-listing is retired). `CreateJournalEntry` remains the exclusive rail for every other production posting, including the settlement JE. **Zero schema change in Accounting.** |
| M-10 | Stage gates (بوابات المراحل) | **NEW** | `mfg_order_stage_gates` | Audit + transition table behind the per-stage screens (R-17), **keyed to `case_id`** (per `md/35`): `case_id`, `from_stage`, `to_stage`, `actor_user_id`, `gate_snapshot` json, `at`. The stage pipeline IS the `mfg_order_cases` machine (M-03); screens call the same Actions as the API — never bypass either state machine. **Split rule vs the Core approval engine (RM-2):** `mfg_order_stage_gates` is the *transition audit log* (every move, automated or human); Core `ApprovalWorkflow`/`ApprovalLog` (M-14) carries *human sign-off gates* (BOM finalize, trial decision, advance waive, QC release) whose approval is a transition *precondition* — never a duplicate audit trail. |
| M-11 | Consignment ownership (مخازن الأمانة) | **EXTEND** (pull-forward) | `warehouses` | `is_consignment` (D-15, already ratified) **+ `owner_partner_id`** (`md/32` §3: zero ownership concept exists today). Held, never valued, never purchasable. **Moves Phase 6 → Phase 1/2** (C-07/D-27 — toll client is the launch customer). |
| M-12 | Customer free-issue intake & return (استلام/إرجاع خامة العميل) | **EXTEND** (documents) | Inventory receipt/issue docs | **U-2 closure — the intake document:** customer free-issue material enters the consignment warehouse via a real **Inventory receipt document** approved by `ApproveReceipt` running a **no-valuation/no-GL branch** for `is_consignment` warehouses (tracking-only quantities; lots recorded for genealogy, md/33 P3), referenced to the case. Return of UNUSED customer material at case close = the mirror **Inventory issue document** (tracking-only) + a case-close reconciliation (received − issued-to-orders − consignment scrap = returned). No stock balance is ever touched outside the Approve* document rails (`md/22` §1). Production calls these forward via `ReceiveCustomerMaterial` / `ReturnCustomerMaterial` (§2.2 #23/#24). |
| M-13 | Reservation activation | **EXTEND** (logic) | `inventory_stock_balances` | `StockService::reserve()/release()` stays a **Phase 1** item exactly as published (A5). Amendments: (a) the **advance gate precedes Release** — and reservation remains a *side-effect of* `ReleaseProductionOrder` exactly as ratified (`md/20` row 13; the case stage `MaterialsReserved` is *entered by* Release, never before it); (b) new branch: customer-supplied components reserve against the consignment bucket (R-12). |
| M-14 | Lineage chain | **EXTEND** | `mfg_peggings` | Add `cost_estimate_id` + `case_id` to the peg chain so the lineage view reconstructs RFQ → draft BOM → quote → trial → order → MO → docs (R-18). |
| M-15 | Approval gating | **EXTEND** | Core enums | `ApprovalModule::Production` (already Phase 0) **plus** new `ApprovalDocumentType` cases: `bom`, `production_order`, `trial_batch`, `customer_advance` — reuse the Core ApprovalWorkflow engine for human sign-offs (`md/32` §5), under the M-10 split rule. |
| M-16 | GR quality default (pharma) | **EXTEND** | settings | New per-company setting `production.gr_default_quality_status`; pharma companies flip **Released → OnHold** + quarantine warehouse (warehouse-as-state, same pattern as `is_consignment`). D-21 stays the generic default; this is the compliant override (`md/33` P2). |
| M-17 | Batch number stamp | **EXTEND** | `production_orders` | `batch_no` stamped at Release via new sequence key `SequenceService('production','batch')`, propagated to every child document — the cheap Phase 1 slice of the pharma BMR (`md/33` P1). |
| M-18 | Prospect product | **EXTEND** | Core `products` | `lifecycle=Prospect` state (D-26) so a Draft BOM for a not-yet-onboarded customer product keeps its `product_id` FK; promotes cleanly on Won. |
| M-19 | R&D actor / permission namespaces | **EXTEND** | Core permissions | **One taxonomy (ends the three-namespace fork):** Cycle-1 slices under `production.estimates.*` per `md/34` §5 (incl. `production.estimates.rnd.draft_bom`); Cycle-2 slices under `production.case.*` per `md/35` §5 (waive = **`production.case.deposit_waive`**). The Phase-0 seeder is written from those two files verbatim. R&D ownership routing per C-04. |

---

## 2. Contracts amendments (delta to `md/22`)

All additions obey the two prime rules and §5 law (as amended by **LA-1** only): **Actions forward,
events backward, no module ever reads an `mfg_*` table.** Sales receives prices by **push**
(Action); Accounting receives a voucher and never knows `mfg_customer_advances` exists; QMS
inspections are created forward and Manufacturing reads `qms_inspections.result` read-only
(already permitted).

### 2.1 New domain events (extends §2)

**One vocabulary (ends the three-vocabulary fork):** Cycle-1 events are `md/34` §3's five —
`CostEstimateRequested`, `DraftBomReady`, `EstimateQuoted`, `EstimateWon`, `EstimateLost`.
Cycle-2 events are `md/35` §2's twelve `Case*` events — `ProductionCaseOpened`,
`CaseTrialRequested`, `TrialOrderCreated`, `CaseTrialDecided`, `CaseBomFinalized`,
`CasePlanningChecked`, `CaseDepositSatisfied`, `CaseMaterialsReserved`, `CaseQcReleased`,
`CaseDelivered`, `CaseClosed`, `CaseCancelled` — plus `CaseConsignmentReceived` /
`CaseConsignmentReturned` (M-12). Key wiring:

| Event | Emitter (Action) | Consumers (Manufacturing-side listeners → forward Action) | Payload (key fields) |
|---|---|---|---|
| `EstimateQuoted` | `QuoteCostEstimate` (after the simulation-only roll-up version is approved) | `PushQuotePricing` → **NET-NEW** `Modules\Sales\Actions\UpsertQuotationPricing` (writes price + `valid_until` into the `SalesQuotation` — Sales never reads `mfg_cost_estimate_versions`) | `estimate_id, version_id, sales_quotation_id, quoted_unit_price, currency, valid_until` |
| `CaseTrialDecided` | `RecordTrialDecision` (records customer verdict; **gate: trial sample hand-over document exists**, U-3) | Lineage gate (Passed ⇒ conversion eligible; Failed ⇒ block + optional re-trial loop); trial cost-policy posting per D-23; notification to Sales/R&D roles | `case_id, trial_batch_id, production_order_id, result, actual_unit_cost, evaluated_at` |
| `CaseDepositSatisfied` | `AllocateCaseAdvance` / `WaiveCaseDeposit` (after `ApproveReceiptVoucher` posts DR Cash / CR Customer Advances — LA-1) | Release-gate re-evaluation (Σ approved vouchers `reference_type='mfg_order_case'` ≥ required); **NOW raise the on-behalf purchase requests** (`Purchases\CreatePurchaseRequest`, billable-to-customer tag) — PRs are NEVER raised before this event (U-6/C-5 rule); notify Planning | `case_id, sales_order_id, deposit_required, deposit_received, receipt_voucher_id, journal_entry_id` |
| `CaseConsignmentReceived` / `CaseConsignmentReturned` | `ReceiveCustomerMaterial` / `ReturnCustomerMaterial` (M-12 documents) | Consignment balance/reconciliation view; genealogy lot capture; case-close reconciliation gate | `case_id, warehouse_id, document_id, lines[{product_id, lot_no, quantity}]` |
| `CaseQcReleased` | `ReleaseCaseQc` (reads `qms_inspections.result` read-only) | Quarantine→FG move via the **ratified document rails only**: a paired tracking transfer (ApproveIssue out of quarantine + ApproveReceipt into FG — registered below as contract addition #25; no ad-hoc "transfer rail"); pegged delivery release; advance settlement reminder | `case_id, gr_id, order_id, lines[{product_id, quality_status}], resolved_at` |

(The generic per-move audit row is written by each transition Action itself into
`mfg_order_stage_gates` — there is no separate `StageAdvanced` event; the per-stage `Case*` events
above ARE the stage-advanced notifications.)

### 2.2 New / changed forward Action entry points (extends §3)

| # | Action | Module | Status | Used for |
|---|---|---|---|---|
| 16 | `Modules\Accounting\Actions\ApproveReceiptVoucher` | Accounting | **EXISTS** (verified — account-agnostic posting, `md/32` §2) | Deposit receipt: DR Cash/Bank / CR Customer Advances; caller sets the line account + `reference_type='mfg_order_case'`. **The voucher's JE IS the advance posting (LA-1/D-33) — no second JE.** |
| 17 | `Modules\Accounting\Actions\CreateJournalEntry` | Accounting | **EXISTS** | The **settlement JE** at invoicing: `entry_type='production_advance_settlement'` (DR Customer Advances / CR AR), plus the D-23 trial-policy entry_types (`production_trial_absorb/_defer/_apply/_writeoff`) and `production_toll_material_cogs` (#22). |
| 18 | `Modules\Sales\Actions\UpsertQuotationPricing` | Sales | **NET-NEW** (thin) | Push estimate price/validity into `SalesQuotation`; keeps the no-`mfg_*`-reads law |
| 19 | `Modules\Sales\Actions\ConvertQuotationToOrder` | Sales | **NET-NEW** (extraction of `SalesOrderController::convertFromQuotation` L368 — same recipe as `CreatePurchaseRequest`) | Programmatic Accepted→Order conversion from `MarkEstimateWon` / the case workflow; stamps lineage. `md/34` §6 step 1 binds to THIS Action (never the controller). |
| 20 | `Modules\Sales\Actions\CreateServiceInvoice` | Sales | **NET-NEW** (extraction, same recipe) | **U-4 closure — the compliant invoice creation rail:** Production triggers the final case invoice forward: toll conversion-fee line + **material pass-through line(s)** (#22) + optional trial-billing line (D-23 Bill). Flows to EInvoicing (B18). No controller call, no direct Sales table writes. |
| 21 | `Modules\QMS\Actions\CreateInspection` | QMS | already #8 | **Changed timing**: also called at GR for the pharma final-release inspection; gate config (`production.gr_default_quality_status`) lands Phase 1 instead of waiting for full Phase 6 gates |
| 22 | *(journal map — see §2.3)* Material pass-through billing | Accounting (via #17/#20) | **NEW design (U-1 closure)** | RM the factory buys **on the customer's behalf** is FACTORY property until invoiced (normal own stock, reserved/pegged to the case; its purchase POs carry the billable-to-customer tag). At FG receipt on the toll branch the material portion of WIP relieves to toll-material COGS; the final invoice (#20) carries a **material pass-through line at cost (+ contractual handling %)** beside the conversion fee — so the advance (sized as % of own-procured material cost, D-25) settles against an invoice that **contains the material it funded**: the cash math closes. Title to the material content passes to the customer at delivery/invoice. |
| 23 | `Modules\Production\Actions\ReceiveCustomerMaterial` → Inventory receipt doc | Production → Inventory | **NEW** (M-12) | Customer free-issue intake into the consignment warehouse — tracking-only `ApproveReceipt` branch; lots captured |
| 24 | `Modules\Production\Actions\ReturnCustomerMaterial` → Inventory issue doc | Production → Inventory | **NEW** (M-12) | Return of unused customer material at case close — tracking-only `ApproveIssue` branch + reconciliation |
| 25 | Quarantine→FG release transfer | Inventory documents | **EXTEND** (contract addition) | QC release (CaseQcReleased) moves stock ONLY via a paired tracking ApproveIssue/ApproveReceipt transfer document — registered here so no un-ratified "transfer rail" exists |
| 26 | Trial-sample hand-over delivery | Production doc → tracking issue | **NEW** (U-3 closure) | Trial output is received into the **trial/evaluation bucket** (warehouse-as-state, non-sellable — `md/31` C-05 made concrete); the hand-over to the customer is a **sample delivery document** (tracking-only issue out of the evaluation bucket, referenced to `mfg_trial_batches.sample_delivery_doc_id`); `RecordTrialDecision` is gated on its existence |
| — | `ReleaseProductionOrder` precondition | Production (internal) | **EXTEND** | Hard gate: block Planned→Released until `Σ(approved case advances) ≥ required` OR status `Waived` (permission-guarded). Sits BEFORE snapshot + reserve. **The same gate governs on-behalf purchasing: `CreatePurchaseRequest` for billable-to-customer material fires only on `CaseDepositSatisfied`** (U-6). Not a cross-module call — the gate reads Manufacturing's own tables. |

### 2.3 Journal map additions (extends §4)

| Event | entry_type | DR | CR | Amount |
|---|---|---|---|---|
| Customer advance received | `production_customer_advance` | Cash/Bank | Customer Advances (liability, child of 2104) | received amount — **posted once, by the voucher engine (`ApproveReceiptVoucher`), LA-1/D-33; tagged with this entry_type via the voucher reference** |
| Advance settled at final invoicing | `production_advance_settlement` | Customer Advances | Accounts Receivable | min(advance balance, invoice total) — single canonical name (the earlier `production_advance_application` alias is retired) |
| Advance refund on cancel (OQ-6/D-35) | `production_advance_refund` | Customer Advances | Cash/Bank | residual after incurred cost |
| Trial run (during execution) | — | *the normal 5 JE families unchanged* | | |
| Trial close — cost ownership (D-23, per `md/35` §1.1/§3) | `production_trial_absorb` / `production_trial_defer` / `production_trial_apply` / `production_trial_writeoff` | R&D Expense / Deferred Trial Cost / Order cost / R&D Expense | WIP / WIP / Deferred / Deferred | trial WIP residual per policy `{Bill, Absorb, CreditOnWin}` (Bill = a normal Sales invoice line via #20, no special entry_type). These four entry_types and the two accounts (Deferred Trial Cost, R&D Expense) ARE new and ratified here — the earlier "no new JE family" wording was wrong and is retired. |
| Toll FG receipt — on-behalf material relief (U-1) | `production_toll_material_cogs` | Toll material COGS (recharged) | WIP | material portion of WIP for factory-bought-on-behalf components |
| Final toll invoice (via #20) | (Sales invoice → EInvoicing) | AR | Toll-service revenue + **Material pass-through revenue** (+ trial line if Bill) | conversion fee + on-behalf material at cost (+ handling %) |
| Customer-supplied (free-issue) | — | **no JE ever** — tracking-only consignment movements (intake M-12, issue, return, scrap D-30); lots in genealogy | | |

---

## 3. Roadmap re-phasing (delta to `md/23`)

The client is a **real prospective launch customer** — toll/MTO with the commercial funnel in
front. Revised phase order, sizing (PHASE-level legend unchanged) and rationale per move:

| Phase | Was | Becomes | Size | Rationale |
|---|---|---|---|---|
| **0 — Hardening** | S | S (scope +) | S | **ADD:** ratify the authoritative decision register **D-23..D-36 + OQ-9** (§4) inside the spec-addenda track (same one-week timebox, same hard gate); R&D role + the two permission namespaces (`production.estimates.*` / `production.case.*`, M-19); `ApprovalDocumentType` production cases (M-15); `Product.lifecycle=Prospect` (M-18). No new build — decisions + perms only. |
| **1 — Master Data v2 + Real Execution** | L | L (top edge) | L | **ADD:** `order_type=Trial` enum value + D-32 draft-BOM carve-out guard (M-05); `sales_order_id`/`case_id` on `production_orders` (M-06); **consignment pulled from Phase 6** — `warehouses.is_consignment` + `owner_partner_id` + per-line `ownership` + tracking-only issue branch **+ the free-issue intake/return documents (M-12)** (C-07/D-27: a toll factory cannot run a single real order without them); pharma GR default flip `OnHold` + quarantine warehouse (M-16); `batch_no` stamp at Release (M-17). **Risk control:** Phase 1 was already aggressive-L; the published 1a/1b fallback split stays, and the consignment slice is the first candidate to slip into 1b — never back to Phase 6. |
| **2 — Routing, Confirmations, Conversion Costing** | L | L (scope +) | L | **ADD (the deposit gate moves UP — it had no scheduled home at all):** `mfg_order_cases` + the case machine core (M-03) + `mfg_customer_advances` keyed to the case + Customer-Advances account/setting (M-07/M-08) + `ReceiptVoucher` wiring under **LA-1** (M-09) + the `ReleaseProductionOrder` advance precondition + **PR-raising moved behind the gate** (§2.2 last row) + `CaseDepositSatisfied` event; consignment-bucket **reservation branch** completing M-13 (core `reserve()/release()` itself stays Phase 1, unchanged); `mfg_order_stage_gates` (case-keyed) + **pipeline-board MVP** (D-28). Deposit gate needs the Phase 1 Release rail — earliest correct slot. |
| **2.5 — Customer Front (NEW phase)** | — (was "1.5" in `md/30`/`md/31` proposals) | **NEW** | **M** | `mfg_cost_estimates` + `mfg_cost_estimate_versions` + `SimulateCostEstimate` (simulation-only — **pulls the `RollUpStandardCost` CORE forward from Phase 3**, estimate mode only, no standards, no variances, no GL); `mfg_trial_batches` + the trial flow over the Phase-1 Trial rail incl. the **evaluation bucket + sample hand-over delivery** (#26); `UpsertQuotationPricing` + `ConvertQuotationToOrder` + `CreateServiceInvoice` Sales tie-ins; **the gate-critical case workbenches ship HERE, not Phase 5** (D-28 amended): trial-bench, trial-review, bom-bench, planning-bench, deposit-desk, reserve-desk + the Cycle-1 screens (`md/34` §4) — Phase 5 only polishes and completes the set. **Why after Phase 2, not before:** exploratory costing prices labor/OH from work-center rates + `cost_driver`, which land in Phase 2 — quoting before that means material-only estimates. Sits cleanly before Planning (needs no MRP). |
| **3 — Costing Engine** | M | M (slightly lighter) | M | Unchanged scope: standards, 7 variances, settlement. The roll-up core arrives already built (Phase 2.5) — Phase 3 productionizes it (standards versioning, variance engine, period jobs). **ADD:** Trial orders excluded from standard-cost baselining until trial `Passed` (M-05); trial-policy JEs (D-23) wired. |
| **4 — Planning (MRP/CRP/MPS-lite)** | L | L | L | Mostly unchanged. **ADD:** MRP POs for own-procured material on a customer case get a **billable-to-customer tag** (C-06b — purchase on the customer's behalf) and **are raised only after `CaseDepositSatisfied`** (U-6 rule; the Phase-2 case-local planning obeys the same gate); readiness screen joins the stage pipeline as the Planning workbench. Toll branch (never raise PO for customer material) was already in scope. |
| **5 — Shop Floor Control** | M | M | M | Unchanged engine. **ADD:** completes/polishes the per-stage back-office workbenches begun in Phase 2.5 (C-08 complete) + production-monitor per-operation views. Operator terminals unchanged. **OQ-9 dependency:** if the client's "واجهات لكل مرحلة إنتاجية" means per-MANUFACTURING-stage screens (mixing/granulation/compression/packaging), the answer lands here as per-operation workbenches over routing operations — see OQ-9. |
| **6 — Deep hooks** | L | **M–L (shrinks)** | M–L | Toll/consignment core **moved out** (to Phases 1–2). Remains: batch/lot first-class + genealogy + FEFO (A4/D-14 — **flagged pull-forward candidate for the pharma go-live release**, `md/33` P3), full QMS gates (the Phase-1 default-flip becomes a real inspection-resolved gate), HRM piecework, BMR assembly/print (P1), retained samples (P5), yield-reconciliation gate (P8). |

**Explicitly unchanged:** D-01..D-22 all stand; module identity (EXTEND `Modules/Production`,
`mfg_*` prefix); **the production-order state machine and its Phase-1 migration (truly untouched —
the commercial stages live on `mfg_order_cases`)**; the 5 JE families + month-end absorption; the
dependency law (§5) **except the single ratified amendment LA-1/D-33**; MRP/CRP/variance engine
designs; SFC terminal design; Phase-0 hard gate discipline. Revised sequence:
**0 → 1 → 2 → 2.5 → 3 → 4 → 5 → 6** (0:S · 1:L · 2:L · 2.5:M · 3:M · 4:L · 5:M · 6:M–L).

---

## 4. THE authoritative decision register (D-23..D-36 + OQ-9)

> **Single numbering (ends the three-scheme collision):** this table is THE D-register. `md/30` §6
> and `md/31` §4 now point here; `md/31`'s former "D-23 pre-sales scope" is **D-31** below;
> `md/30`'s OQ-x → D-x mapping is given per row. Requirement numbering: R-01..R-21 = `md/30`'s
> scheme everywhere; `md/31`'s derived list is renumbered IR-1..IR-9.

| # | Decision / Risk | Options | Recommendation |
|---|---|---|---|
| **D-23** | **Trial cost policy** — who pays the trial batch (and re-trials on failure)? (OQ-1/OQ-3) | Bill at cost+markup vs absorb vs defer-and-credit-on-win | Configurable per case: **`trial_cost_policy ∈ {Bill, Absorb, CreditOnWin}`** (per `md/35` §1.1 — the earlier two-valued `{Customer, Absorbed}` table is superseded); posting per §2.3 (Absorb ⇒ `production_trial_absorb` reclass; CreditOnWin ⇒ defer/apply/writeoff family); failed-trial cost inherits the same policy; N failed trials ⇒ case auto-flagged for review |
| **D-24** | Trial modeling | `order_type=Trial` enum vs `is_trial` flag vs separate module | **`order_type=Trial` ratified** (M-05) — first-class reporting, zero new rail (supersedes the `is_trial` lean in `md/31`) |
| **D-25** | **Deposit / advance percentage** (OQ-2) | Fixed % vs per-customer vs per-order vs material-cost-based | Setting `production.default_advance_percent` + per-case override; **v1 basis = % of own-procured material cost** (matches "factory buys on the customer's behalf" — and the invoice now carries the matching material pass-through line, U-1); hard block at Release AND on PR-raising; `Waived` only via `production.case.deposit_waive`, audited |
| **D-26** | Draft BOM for prospect product | nullable `product_id` vs `Product.lifecycle=Prospect` | **`lifecycle=Prospect`** — keeps BOM FK integrity, promotes on Won |
| **D-27** | Toll/consignment timing | Phase 6 vs pull forward | **Pull forward to Phase 1/2 — ratified** (D-07 trigger fired: toll client is the launch customer) |
| **D-28** | Stage-screen scope (OQ-7 granularity folded in) | Full workbenches early vs pipeline-board MVP first | **Board MVP in Phase 2; gate-critical workbenches in Phase 2.5; completion/polish Phase 5.** Per-stage permission slices (`production.case.*`); supervisor spans all (OQ-7 default) |
| **D-29** | Quote / deposit document ownership | New `mfg_*` docs vs reuse Sales + Accounting | **Reuse verified:** `SalesQuotation` (commercial doc) + `ReceiptVoucher` (cash); `mfg_*` tables carry only the mfg-side estimate/case/costing/gate data |
| **D-30** | **Customer-material scrap ownership** (OQ-4) | Own scrap expense vs return vs bill-back | **Never an own-scrap JE** (customer property, bailment): tracking-only consignment scrap movement; reconcile/return/bill per contract; surfaced in the consignment movement report and the M-12 case-close reconciliation |
| **D-31** | **Pre-sales costing funnel scope** (was `md/31`'s "D-23") | Build the full RFQ→DraftBOM→estimate→quote funnel (C-01..C-03) vs minimal spreadsheet | **Build it** (`md/34` design) — it is this client's entry point; without it Cycle-1 is unserved |
| **D-32** | **Trial Draft-BOM snapshot carve-out** (amends `md/20` row 2) | Forbid (trial deadlocks before BOM finalization) vs ratified narrow exception | **Ratify the exception:** ONLY `order_type=Trial` orders may snapshot an `Engineering/Draft` BOM, behind setting `production.trial_allow_draft_bom`; Standard orders never. Explicit amendment — `md/20` row 2's guard text gains this one carve-out |
| **D-33** | **Advance posting rail — law amendment LA-1** (amends `md/22` §5.4) | Bare `CreateJournalEntry` vs `ReceiptVoucher` rail | **Voucher rail ratified:** the advance cash-in posts via `ApproveReceiptVoucher` (account-agnostic, verified `md/32` §2) and the voucher's own JE is the single advance posting (`entry_type='production_customer_advance'` stamped via the voucher reference); no parallel `CreateJournalEntry` call (no double posting). Every other production posting remains `CreateJournalEntry`-only |
| **D-34** | Quote validity policy (OQ-5) | Free vs enforced expiry | `production.quote_validity_days` (v1 = 30); expired quote blocks Won/conversion until re-costed |
| **D-35** | Advance refund / forfeiture on cancellation (OQ-6) | Forfeit vs refund vs net | Per-contract; default = applied to incurred cost, balance refundable (`production_advance_refund`) |
| **D-36** | Trial-vs-order quantity & true-up (OQ-8) | Fixed ratio vs independent | Trial qty independent; full-order cost re-estimated from `mfg_trial_batches.actual_unit_cost` before conversion (mandatory true-up) |
| **OQ-9** | **What does «واجهات لكل مرحلة إنتاجية» mean?** — commercial CASE stages (the 14-stage board + workbenches, the reading designed in `md/35` §4) or per-MANUFACTURING-stage screens (mixing/granulation/compression/packaging — pharma-plausible)? | ask the client | Both are servable: case workbenches land Phases 2–2.5; if the manufacturing-stage reading is confirmed, per-operation workbenches over routing operations land with Phase 5 SFC (operators already get per-WC terminals) and an early read-only per-operation monitor can ship in Phase 2.5. **Must be answered at the Phase-0 gate.** |
| R-a | **Phase-1 slip risk** grows with the consignment pull-forward | — | Keep the published 1a/1b split as the pre-agreed fallback; consignment slice slips into 1b only |
| R-b | **Estimate-vs-actual divergence** on quotes (priced off Draft BOM + estimated prices) | — | Mandatory true-up from `mfg_trial_batches.actual_unit_cost` before conversion (D-36); `valid_until` enforcement (D-34) |
| R-c | **Advance liability accounting** (advance ≠ revenue, ≠ AR settlement) | — | Accountant sign-off on the Customer-Advances child account + the settlement JE + the material pass-through lines (U-1) before Phase 2 exit; refund per D-35 |
| R-d | **Gate-bypass pressure** (commercial urgency vs the deposit hard block) | — | `Waived` exists precisely for this — permission-guarded, audited, reported; no silent bypass path in code (and the PR gate obeys the same waiver) |

---

**Bottom line:** +6 `mfg_*` tables (24→30: estimates, estimate versions, order cases, trial
batches, customer advances, stage gates — `md/34`/`md/35` naming, no parallel `mfg_quotations`),
the `md/34` estimate events + `md/35` case events as the single vocabulary, +3 thin Sales Actions
(`UpsertQuotationPricing`, `ConvertQuotationToOrder`, `CreateServiceInvoice`), the four
previously-missing documents designed (free-issue intake/return, trial-sample hand-over,
quarantine-release transfer, material pass-through billing), one new phase (2.5 Customer Front,
M), Phase 6 shrinks as toll moves into Phases 1–2, two explicit ratified amendments (D-32
draft-BOM carve-out, D-33/LA-1 voucher rail) and the authoritative register D-23..D-36 + OQ-9
joins the Phase-0 ratification gate. Nothing else published is undone — the client's funnel bolts
onto the front of the existing rails.

---


# 37 — Adversarial Review of the Client-Workflow Addendum Set (md/30..36)

> Reviewed: `md/30` (requirements), `md/31` (coverage audit), `md/32` (ERP evidence),
> `md/33` (pharma domain), `md/34` (Cycle-1 design), `md/35` (Cycle-2 design),
> `md/36` (consolidated plan amendments) — against each other, against the client interview
> (verbatim summary), and against the published `md/20-mapping.md`, `md/22-contracts.md`,
> `md/23-roadmap.md`.
>
> Verdict up front: the addendum set covers the interview's spine well, but **md/34, md/35 and
> md/36 are NOT one design — they are three partially incompatible designs**, and md/36 (the
> "single consolidated delta") silently discards md/34's canonical naming and md/35's central
> entity. Several findings are blocking for the Phase-0 ratification gate.

---

## 1. Interview requirements with NO (or materially incomplete) coverage

Walking the interview line by line, the funnel (RFQ → draft BOM → preliminary cost → quote),
the trial cycle, the formal order, BOM finalization, planning/readiness, the deposit gate,
reservation, MO → issue → manufacture → FG receipt, order-pegged lineage, and stage screens are
all addressed somewhere in 30–36. The following are the holes:

| # | Interview line | Gap |
|---|---|---|
| U-1 | "the factory usually **purchases raw materials on behalf of the customer**" | Only the *gate* is designed. The **billing-back mechanics of on-behalf-procured materials are designed nowhere**: 31 C-06 / 36 Phase-4 add only a "billable-to-customer tag" on MRP POs, while 30 R-16 / 22 §4 bill the customer **conversion fee only** (DR Customer / CR Toll-service revenue). Who owns the RM bought on the customer's behalf (own stock? customer property on arrival?), whether material cost is passed through on the final invoice or consumed by the advance, and the JE for the pass-through are all unspecified in all 7 docs. The advance (sized as "% of own-procured material cost", 36 D-25) settles against an invoice that, per the only documented toll JE, contains no material line — the cash math does not close. |
| U-2 | "the customer may ALSO supply his own raw materials which remain HIS property" | Consignment *holding/issuing* is covered (D-15, M-10, tracking-only issue). **The intake document is not**: no Action, no `mfg_*` document, no `InventoryReceipt` path is defined anywhere for *receiving* customer free-issue material into the consignment warehouse. md/35 has only a screen button (`reserve-desk`: "Receive customer free-issue") and a permission (`production.case.consignment_receive`) with no rail behind them — see V-2. Likewise the **return / reconciliation of UNUSED customer material at order close** is uncovered (D-30/OQ-4 covers scrap only). |
| U-3 | "a test/sample batch is produced **for the customer to evaluate**" | The trial production run is fully designed, but the **physical hand-over of the trial sample to the customer** is not: no delivery document, no warehouse/ownership treatment of trial output (31 C-05's "trial/evaluation bucket, not sellable FG" never reappears in 34/35/36). The trial closes with a normal FG receipt and then the sample teleports to the customer. |
| U-4 | "issue materials … finish … receive into finished-goods warehouse" then bill | The **toll conversion-fee invoice creation rail is missing**: 35 row 11 ("final invoice posted") and 30 R-16 require Production to trigger a Sales service invoice, but no forward Sales Action exists for it — 36 §2.2 adds `UpsertQuotationPricing` and `ConvertQuotationToOrder` but **no `CreateInvoice`-type Action**, and direct controller/table use is forbidden (22 §0.1). B18 names the need; no addendum closes it. |
| U-5 | "dedicated SCREENS for **every production stage** (واجهات لكل مرحلة إنتاجية)" | All addenda interpret "stage" as *commercial case stages*; **in-production manufacturing stages remain a single `InProduction` stage** whose screens are the Phase-5 operator terminals. If the client meant per-manufacturing-stage screens (mixing/granulation/compression/packaging — plausible for pharma), nothing before Phase 5 serves him, and the interpretation was never logged as an open question (it is absent from OQ-1..OQ-8 and D-23..D-30). Additionally 36 ships only a "pipeline-board MVP" in Phase 2 and defers all per-stage workbenches to Phase 5 — 35's ten workspaces have no committed early phase in the consolidated plan. |
| U-6 | "customer MUST pay a DEPOSIT … **because** the factory purchases raw materials on behalf of the customer" | The causal rule (deposit gates the *purchasing*) is broken in md/35 — see C-5. The interview ties the deposit to procurement, not only to reservation/release. |

---

## 2. Contradictions between md/34, md/35, md/36 (and back into 30/31)

| # | Contradiction | Detail |
|---|---|---|
| C-1 | **Canonical table names reversed without notice** | md/34 opens with "Naming reconciliation (canonical, **supersedes** provisional names)": `mfg_cost_estimates` + `mfg_cost_estimate_versions`. md/35 §0.1 still uses `quotation_id FK mfg_quotations` and counts `mfg_quotations`/`mfg_quotation_costings` in its tally; md/36 M-01/M-02 (the *final consolidated sheet*) ratifies `mfg_quotations` + `mfg_quotation_costings` and never mentions 34's pair. Same two entities, two competing canonical names, with the "supersedes" arrow pointing both ways. Every FK, sequence key (`cost_estimate` vs `quotation`), permission slice (`production.estimates.*`) and event name downstream forks on this. |
| C-2 | **md/35's central entity is absent from md/36** | md/35 §0 spends a page ratifying `mfg_order_cases` as THE entity decision (14-stage case machine; "7 of 14 stages exist when no production order exists — columns would have no row to live on"; `mfg_order_stage_gates` **re-keyed to `case_id`**; advances keyed to case). md/36 contains **no `mfg_order_cases` at all**: M-09 keys stage gates back to `production_order_id` (nullable → `quotation_id`) and grafts 3 pre-states `{Quotation, Trial, AwaitingAdvance}` onto the ratified order machine — exactly the design md/35 explicitly rejected as "would corrupt the Phase-1 state machine". Mutually exclusive architectures, both published the same day. |
| C-3 | **Table-count math disagrees three ways** | md/34: Cycle-1 net-new = 2. md/35: +6 (24 → 30, includes `mfg_order_cases`). md/36: +5 (24 → 29, no case table). At least one of 35/36 is wrong by construction. |
| C-4 | **Deposit anchor & voucher reference diverge** | md/30 R-11: advance keyed `sales_order_id`/`production_order_id`, JE `source_type='production_order'`. md/35: advance keyed **`case_id`**, voucher `reference_type='mfg_order_case'`. md/36 M-06/M-08: keyed `sales_order_id`/`production_order_id`, voucher `reference_type='sales_order'`. md/32 itself had proposed `'sales_order'` *or* `'mfg_production_order'`. Three live answers to "what does the deposit hang off?" — the gate query (Σ vouchers ≥ required) cannot be written until one wins. |
| C-5 | **Deposit-gate vs purchasing sequence** | md/30 R-11 and md/31 R-07: NO purchasing-on-behalf until the advance is `Received/Waived` ("received and verified BEFORE materials are reserved/**purchased**"). md/35 row 5 raises `CreatePurchaseRequest` PRs for own shortages at **`PlanningChecked` — before `DepositGate`** (rows 6–7). md/36 gates only snapshot+reserve (§2.2 last row) and schedules the billable-PO tag in Phase 4 with no gate statement. The consolidated design lets the factory buy on the customer's behalf before the deposit the client said exists *precisely to fund that buying*. |
| C-6 | **Trial accounting: four new JE types vs "no new JE family"** | md/35 §1.1/§3 defines `trial_cost_policy {Bill, Absorb, CreditOnWin}` with four new entry_types (`production_trial_absorb/_defer/_apply/_writeoff`) and two new accounts (Deferred Trial Cost, R&D Expense). md/36 §2.3 states for the trial run: "**No new JE family** … changes only the final Sales-invoice line, never the production JEs", and D-23 ratifies only `{Customer, Absorbed}` (CreditOnWin dropped). But `Absorbed` *requires* at least the reclass JE md/35 specified — md/36's D-23 even says "(Absorbed ⇒ R&D expense JE reclass)" while §2.3 denies any new family two tables up. |
| C-7 | **Settlement entry_type name** | md/35: `production_advance_settlement`. md/36 §2.3: `production_advance_application`. Same JE (DR Customer Advances / CR AR), two names. |
| C-8 | **Three colliding D-23..D-30 schemes** | md/30 §6: D-23..D-30 = OQ-1..OQ-8 in order. md/31 §4: D-23 = pre-sales scope, D-24 = trial modeling, D-25 = deposit enforcement, … D-29 = reuse. md/36 §4: D-23 = trial cost policy, D-25 = deposit %, D-30 = scrap ownership. md/34 cites "D-23 (build the funnel)" — md/31's meaning, which under md/36's final table denotes *trial cost policy*. Any board member ratifying "D-23" is signing three different decisions depending on which doc they read. (Same disease at R-level: md/30 defines R-01..R-18, md/31 redefines R-01..R-09 differently — R-07 is "trial evaluation" in 30 and "deposit gate" in 31; 35/36 cite both schemes.) |
| C-9 | **Phase placement & a false prerequisite claim** | md/30 §6: "Phase 1.5 … after Phase 1 *and* Phase 2". md/34 §8: Phase 1.5 needs only "Phase 1 master data (BOM lifecycle, **WC rates**)" — **factually wrong against md/23**: `labor_cost_rate`/`machine_cost_rate`/`cost_driver` land in **Phase 2** (23-roadmap L89). md/35 §7: "Phase 1.5 exactly as scheduled in md/30 §6". md/36 renames it Phase 2.5 and gives the correct rationale (rates arrive in Phase 2). 34/35 therefore schedule the costing funnel before its labor/OH inputs exist. |
| C-10 | **Draft-BOM trial carve-out vs the "never" rule** | md/30 R-02 and md/34 invariant #2 (both citing ratified md/20 row 2): an `Engineering/Draft` BOM can **never** be snapshotted into a production order. md/35 row 2 introduces a carve-out: Trial orders MAY snapshot a Draft BOM (`production.trial_allow_draft_bom`). md/36 is silent — yet its own M-03/M-04 sequence (trial runs *before* BOM finalization at M-09's `Trial` pre-state) is **impossible** under the un-amended rule. Either the carve-out is ratified (amending md/20 row 2, which 35's own preamble claims it never does) or the trial flow deadlocks. |
| C-11 | **Disjoint event vocabularies for the same milestones** | md/34: `CostEstimateRequested, DraftBomReady, EstimateQuoted, EstimateWon, EstimateLost`. md/35: 12 `Case*` events (`CaseTrialDecided`, `CaseDepositSatisfied`, …). md/36 §2.1: 5 *different* events (`QuotationCosted, TrialEvaluated, CustomerAdvanceReceived, StageAdvanced, GoodsReceiptQualityResolved`). Zero overlap between the consolidated sheet and either design doc — e.g. quote-priced is `EstimateQuoted` (34) vs `QuotationCosted` (36); deposit-satisfied is `CaseDepositSatisfied` (35) vs `CustomerAdvanceReceived` (36). |
| C-12 | **Permission slice taxonomy** | md/34: `production.estimates.*`; md/35: `production.case.*` (waive = `production.case.deposit_waive`); md/36 M-17: `production.rnd.bom.*`, `production.trial.*`, waive = `production.advance.waive`. Same actions, three namespaces — the Phase-0 seeder cannot be written from these docs. |
| C-13 | Minor: md/32 §3 offers `WarehouseType::Consignment` as option 1, ignoring that D-15 already ratified the `warehouses.is_consignment` flag (md/20 row 17, md/22 §1); md/33 and md/36 M-10 correctly follow D-15. Also md/32 §7's proposed chain has `MaterialsReserved → Released` while md/20 row 13 ratifies reservation as a *side-effect of* Release (md/30 R-12/R-13 has the same circular ordering: R-12's actor is `ReleaseProductionOrder`, yet R-13 "Release" is triggered by "reservation done"). |

---

## 3. Violations of the ratified rules

Rails: EXTEND `Modules/Production` · `production.*` permissions · `mfg_*` tables · GL **only** via
`CreateJournalEntry` (22 §5.4) · stock **only** via `ApproveIssue`/`ApproveReceipt` documents
(22 §1 Inventory row) · no module reads `mfg_*` (22 §5.2). Module identity, prefixes and the
no-reads law are respected everywhere (36 §2 even adds push-Actions to keep Sales out of `mfg_*` —
good). The breaches:

| # | Violation | Detail |
|---|---|---|
| V-1 | **New GL path bypassing `CreateJournalEntry`** | The deposit posts its DR Cash / CR Customer-Advances **inside `ApproveReceiptVoucher`** (35 §3; 36 M-08 + §2.2 #16), i.e. a Production-initiated GL posting that does not ride `CreateJournalEntry`. 22 §5.4 says "GL exclusively via `CreateJournalEntry`" and 36's preamble claims "no dependency rule is re-opened" — the law needed an explicit amendment and didn't get one. Worse, 36 lists BOTH #16 (voucher posts the advance) and #17 (`CreateJournalEntry` with `entry_type='production_customer_advance'` "with the voucher") — as written, the advance is posted **twice**, or the entry_type is stamped on a JE the voucher engine creates (capability unverified in md/32). |
| V-2 | **Stock movement without an Approve* document** | Customer free-issue intake into the consignment warehouse (U-2) has no `InventoryReceipt`/`ApproveReceipt` (or mfg doc) anywhere — only a screen button and a permission in md/35. Any implementation would have to touch balances directly, which 22 §1 forbids ("increaseStock/decreaseStock are NEVER called directly"). Similarly 36 §2.1's `GoodsReceiptQualityResolved` consumer performs a "Quarantine→FG warehouse transfer (Inventory transfer rail)" — a rail that appears in no ratified contract (22 §3 lists only Issue/Receipt/StockService). |
| V-3 | **Ratified order state machine quietly widened** | 36 M-09 adds pre-states `{Quotation, Trial, AwaitingAdvance}` to "the ratified order machine" while §3 declares "the order state machine … explicitly unchanged". If the pre-states live on `production_orders.status`, the Phase-1 enum/migration (20 row 13, 23 Phase 1) is re-opened; if not, they have no table — md/35 solved exactly this with `mfg_order_cases`, which md/36 dropped (C-2). The consolidated sheet is self-contradictory on its own "nothing re-opened" guarantee. |
| V-4 | **Controller called as a forward entry point** | md/34 §6 step 1 routes conversion through `SalesOrderController::convertFromQuotation()` — Production may only call foreign **Actions** (22 §0.1). md/36 #19 fixes this by extracting `Modules\Sales\Actions\ConvertQuotationToOrder`, but md/34 (the binding Cycle-1 design) still names the controller. |
| V-5 | **Draft-BOM snapshot guard** | md/35's `production.trial_allow_draft_bom` carve-out breaches the ratified md/20 row 2 guard (see C-10) without a decision row; absent the carve-out, the ratified flow itself is unimplementable for trials. Needs an explicit D-row either way. |
| V-6 | (Minor) The toll fee invoice (U-4) currently has no compliant creation path — implementing 35 row 11 as written would force either a controller call or direct Sales table writes, both illegal under 22 §0/§5. |

---

## 4. Reuse misses (md/32 proved it exists; designed net-new anyway)

| # | Miss | Detail |
|---|---|---|
| RM-1 | **`mfg_quotations` duplicates the `SalesQuotation` funnel** | md/32 §1 proved the full lifecycle exists (`Draft→Sent→Accepted→Rejected/Expired→Converted` + `duplicate` + `convertFromQuotation`) and §6 explicitly recommended "Reuse `SalesQuotation` as the RFQ record — cheapest". md/36 M-01 nevertheless ratifies a NEW `mfg_quotations` with its own parallel status machine `{Requested, InRnD, InCosting, Quoted, Converted, Expired, Rejected}` — `Quoted/Converted/Expired/Rejected` mirror `QuotationStatus` 1:1, creating a dual-state-machine sync problem md/34 had reduced (its estimate header carries only `{Draft, Estimated, Quoted, Won, Lost}` with the commercial states delegated to Sales). The mfg-side costing data needs a home; the duplicated commercial funnel states do not. |
| RM-2 | **Bespoke `mfg_order_stage_gates` beside the Core approval engine** | md/32 §5 proved `ApprovalWorkflow`/`ApprovalLog` exist and recommended reusing them "instead of building bespoke approval logic". md/36 does BOTH: M-13 registers `ApprovalModule::Production` + 4 document types, AND M-09 builds the bespoke gate/audit table. Two overlapping sign-off/audit mechanisms for the same transitions, with no rule on which gates use which. |
| RM-3 | **`mfg_trial_batches` as a separate entity** | md/32 §4's evidence-based recommendation: "model the trial as a `ProductionOrder` with `kind=trial` + `sales_order_id`, so the trial inherits all costing/stock plumbing" — i.e. *no new entity*. 30/35/36 add the `order_type=Trial` enum (good) **and** a separate `mfg_trial_batches` table whose payload (`trial_quantity`, `actual_unit_cost`, `result`, `evaluated_at`) is one quantity column, one rolled-up cost the order already carries, and an evaluation verdict that RM-2's approval engine could record. Defensible, but never argued against the md/32 recommendation. |
| RM-4 | **md/30's advance design missed `ReceiptVoucher`** | md/30 R-11/§4 books the advance via bare `CreateJournalEntry` and its `mfg_customer_advances` has only `journal_entry_id` — unaware of the `ReceiptVoucher`+`ApproveReceiptVoucher` cash-in rail md/32 §2 proved exists. 35/36 corrected (added `receipt_voucher_id`), but md/30 §4 was never amended and now contradicts the consolidated sheet it feeds. |
| RM-5 | md/34 reuses the conversion *logic* but not the house **extraction recipe** (controller→Action, the `CreatePurchaseRequest` pattern) — see V-4; 36 #19 supplies the fix 34 should have specified. |

---

## 5. What is solid

- md/32 is the strongest doc: every claim file-and-line cited, verdicts honest, and its
  ReceiptVoucher/2104, `reserved_quantity`-dormant, CRM-is-ticketing and CMMS-is-not-R&D findings
  prevented four bad designs.
- md/33 correctly reclassifies severities without inventing engines, and its P2 default-flip +
  P3 pull-forward both landed in md/36 (M-14, Phase 6 note).
- The deposit-as-liability treatment (never `SalesPayment`, child of 2104, settlement JE at
  invoicing, refund per OQ-6) is consistently right across 32/35/36.
- The no-`mfg_*`-reads law is actively protected (36's `UpsertQuotationPricing` push, HRM-style).
- Trial = real costed order on the unchanged Release→Issue→Confirm→Receive rail is the right call
  and consistent in 30/32/35/36 (modeling mechanism aside, C-8/D-24 resolved it explicitly).

## 6. Required actions before the Phase-0 ratification gate

1. **Pick one naming + entity model** (C-1, C-2, C-3): either md/34+md/35 (estimates + cases) or
   md/36 (quotations + pre-states) — then regenerate the loser docs. Recommendation: 34's
   estimate pair (avoids RM-1's dual funnel) + 35's case umbrella (avoids V-3), and rewrite 36.
2. **Renumber decisions and requirements once** (C-8): single authoritative D-23..D-3x and a
   single R-numbering; fix md/34's citations.
3. **Resolve the deposit posting** (V-1, C-4, C-7): one anchor, one reference_type, one
   entry_type, and an explicit law amendment for the voucher rail (or wrap it so the JE still
   flows through `CreateJournalEntry`).
4. **Move PR-raising behind the deposit gate** or get the owner's explicit waiver (C-5, U-6).
5. **Design the four missing documents**: consignment free-issue receipt (U-2/V-2), trial-sample
   hand-over (U-3), toll-fee invoice Action (U-4/V-6), material pass-through billing (U-1).
6. **Ratify the trial Draft-BOM carve-out** as a D-row (C-10/V-5).
7. **Ask the client what مرحلة إنتاجية means** (U-5) and log it as OQ-9.

---
