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