Manufacturing — domain overview

The manufacturing domain reduces to one chain: a BOM says what a finished item is made of, a routing says how it is made, a production order executes a quantity of that item, material consumption pulls components out of stock (Stock OUT), finished output pushes the product back into stock (Stock IN), and — where GL accounts are configured — paired journal entries move value through WIP. Everything else (work centers, MRP, quality, scrap, dashboards, reports) supports or measures that chain.

Source: BE src/modules/manufacturing/* (top-level manufacturing.module.ts, README.md + bom, routing (+ routing/operation), work-center, production (+ material, operation, output, scrap), mrp (+ mrp-run, mrp-result), quality, accounting, dashboard, reports) · Admin src/modules/manufacturing/* + src/modules/bom (+ bom/item) · pages src/pages/manufacturing/*, src/pages/bom

This _overview.md is the entry point for the manufacturing domain. It maps the entities, states how BOM → routing → production → material-consumption (stock OUT) → finished-output (stock IN) → GL relate, lists every sub-module with links, captures the shared enums, the access-control / feature-gating model, and the end-to-end manufacture-to-stock flow. The stock ledger mechanics it depends on live in ../inventory/stock.md; BOM detail lives in ./bom.md.


1. The five layers of the domain

Layer Sub-modules What it does
Definition (master) bom, routing (+ routing operations), work-center Templates: what an item is made of, the operation sequence, the resources/cost rates. No transactions.
Execution production-order (+ material, operation, output, scrap) Runs a quantity of a finished item; the only layer that moves stock.
Planning mrp (mrp-run + mrp-result) Computes net requirements, suggests production/purchase orders, explodes BOMs.
Assurance quality (inspections + non-conformance disposition) Inspects output; records pass/fail, rework, scrap dispositions.
Costing / reporting accounting (journal + WIP/variance), dashboard, reports Posts manufacturing GL (WIP flow), shows WIP/variance, and 7 analytic reports.

ManufacturingModule (manufacturing.module.ts) wires all of these with forwardRef (heavy circular deps). It imports: WorkCenterModule, RoutingModule, BomModule, ProductionOrderModule, MrpModule, QualityModule, ManufacturingReportModule, ManufacturingDashboardModule, ManufacturingAccountingModule, ScrapRecordModule — and re-exports them all.


2. Entity map

   ┌────────────────────────── DEFINITION (templates) ──────────────────────────┐
   │                                                                             │
   │   Item (items, inventory)                                                   │
   │     ├─ isManufactured / manufacturingType (NONE|RAW_MATERIAL|SUB_ASSEMBLY|  │
   │     │   FINISHED_GOOD|WIP) · defaultBomId · defaultRoutingId                 │
   │     │   · manufacturingLeadTimeDays · reorderPoint · safetyStock            │
   │     ▼                                                                        │
   │   Bom (manufacturing_boms) ──itemId──▶ Item (parent / finished good)        │
   │     │  version · status(DRAFT|ACTIVE|OBSOLETE) · bomType(STANDARD|PHANTOM)   │
   │     │  approvalStatus · totalMaterial/Labor/Overhead/Cost                    │
   │     └─< BomLine (manufacturing_bom_lines)                                    │
   │           componentItemId ──▶ Item (component)                              │
   │           componentBomId  ──▶ Bom   (recursive: multi-level BOM)            │
   │           quantity · scrapPercent · uomId · warehouseId · flushingMethod     │
   │                                                                             │
   │   Routing (routings) ──itemId──▶ Item ;  status(ACTIVE|DRAFT|OBSOLETE)       │
   │     └─< RoutingOperation (routing_operations)                                │
   │           sequenceNo · workCenterId ──▶ WorkCenter · setupTimeHours ·        │
   │           runTimePerUnitHours · overlapPercent · scrapFactor                 │
   │                                                                             │
   │   WorkCenter (work_centers)  type(MACHINE|LABOR|SUBCONTRACTOR) ·             │
   │     capacityHoursPerDay · efficiencyPercent · costRatePerHour · calendar     │
   └─────────────────────────────────────────────────────────────────────────────┘
                                       │  (production order references item+bom+routing)
                                       ▼
   ┌────────────────────────── EXECUTION ──────────────────────────────────────┐
   │   ProductionOrder (production_orders)                                       │
   │     itemId · bomId · routingId · quantityPlanned/Completed/Scrapped         │
   │     status(DRAFT|RELEASED|IN_PROGRESS|COMPLETED|CANCELLED)                  │
   │     source(MANUAL|SALES_ORDER|MRP) · salesOrderId · planned/actual costs    │
   │       ├─< ProductionMaterial (production_materials)  itemId · qtyRequired/  │
   │       │     Issued/Returned · unitCost · status(PENDING|PARTIALLY|FULLY)    │
   │       ├─< ProductionOperation (production_operations) workCenterId ·         │
   │       │     planned/actual setup+run hours · laborCost · overheadCost       │
   │       ├─< ProductionOutput (production_outputs)  quantityGood/Scrap ·        │
   │       │     batchNumber · serialNumber · warehouseId   ──▶ Stock IN          │
   │       └─< ScrapRecord (scrap_records)  itemId · quantity · reasonCode ·      │
   │             scrapCost   ──▶ GL (Scrap Expense / WIP)                         │
   │                          │                          │                        │
   │   material backflush ────┘ (Stock OUT)              └── output (Stock IN)    │
   └────────────────────────────────────────────────────────────────────────────┘
        │                                                       │
        ▼  inventory/stock                                      ▼  inventory/stock
   Stock(type=OUT, kind=Stock)  components consumed     Stock(type=IN, kind=Stock) finished good
        │                                                       │
        └──────────────── value flow (when GL accounts set) ────┘
                                   ▼   manufacturing/accounting
   Journal entries: RawMat→WIP, Labor→WIP, Overhead→WIP, WIP→FinishedGoods, WIP→ScrapExpense, variances

   ┌── PLANNING ──┐      ┌── ASSURANCE ──┐
   MrpRun (mrp_runs)     QualityInspection (quality_inspections)
     └─< MrpResult         productionOrderId · operationId · itemId · result
        (mrp_results)       (PASS|FAIL|CONDITIONAL_PASS|PENDING) · disposition
        actionType ──▶ ProductionOrder / (PurchaseOrder integration point)

Collections (verbatim from schemas — note the README's boms/bom_lines names are stale): manufacturing_boms, manufacturing_bom_lines, routings, routing_operations, work_centers, production_orders, production_materials, production_operations, production_outputs, scrap_records, mrp_runs, mrp_results, quality_inspections. All extend BaseSchema, all mongoose-delete soft-deleted, all carry companyId + branchId (../../platform/multi-tenancy.md).


3. How the chain relates: BOM → routing → production → stock OUT → stock IN → GL

3.1 BOM → production (what to consume)

A ProductionOrder carries bomId. When material is auto-consumed (§3.2), the order reads its BOM's lines and consumes each component at quantity × quantityPlanned × (1 + scrapPercent/100). BOM is the recipe; production is one batch of that recipe scaled to quantityPlanned. Full BOM model: ./bom.md. (BOM cost roll-up — recalculateBomCost — is a definition-time calculation, separate from the execution-time planned/actual costs on the production order.)

3.2 Production → material consumption (Stock OUT) — backflush

Material is backflushed (auto-consumed), not manually issued. ProductionOrderService exposes release(orderId) and complete(orderId) service methods, each wrapped in withRetryTransaction:

// production.service.ts
release(orderId)  → update status=RELEASED  → autoConsumeMaterials(order, FlushingMethod.FORWARD)
complete(orderId) → update status=COMPLETED → autoConsumeMaterials(order, FlushingMethod.BACKWARD)

autoConsumeMaterials(order, method):
  for each BOM line where line.flushingMethod === method:
    totalQty = line.quantity × order.quantityPlanned × (1 + line.scrapPercent/100)
    stockSvc.backflushConsume(line.componentItemId, totalQty, order.branchId)   // → Stock OUT

StockService.backflushConsume(itemId, qty, branchId) (../inventory/stock.md §4.4) calls CostingService.postIssue for unit cost, then appends a Stock(type=OUT, kind=Stock, fixed=true) row — and is the only writer that also mutates the legacy denormalized item.stockOut/netQuantity counters. The line's flushingMethod (MANUAL / FORWARD / BACKWARD) decides when: FORWARD at release, BACKWARD at completion, MANUAL never auto-consumes.

3.3 Production → finished output (Stock IN)

Creating a ProductionOutput with quantityGood > 0 pushes the finished good into stock:

// output.service.ts  (create, in a retry transaction)
output = create(data)
if (data.quantityGood > 0 && order.itemId):
  costingSvc.postReceipt(order.itemId, data.quantityGood, 0, `production-output-${output._id}`, branchId)

CostingService.postReceipt appends a Stock(type=IN) row for the finished item (and, for FIFO items, pushes a cost layer). Unit cost is passed as 0 here — finished-good valuation relies on the costing method's later resolution rather than a per-output cost (see §8 gotchas).

3.4 Stock → GL (value flow through WIP)

ManufacturingJournalService (accounting/journal.service.ts) posts balanced GL entries via the finance JournalEntryService, resolving accounts by conventional name in the company chart of accounts (resolveAccountId). If an account is missing, the entry is skipped with a warning — GL posting never blocks production. The intended postings:

Event Debit Credit
Material issuance WIP Raw Materials Inventory
Labor recording WIP Manufacturing Overhead
Overhead absorption WIP Overhead Applied
Production completion Finished Goods Inventory WIP
Scrap recording Scrap Expense WIP
Variance (material/labor/overhead) Variance acct / WIP WIP / Variance acct (sign-driven)

Wiring reality (current zerp code): only scrap GL is actually wired — ScrapRecordService.createScrapRecord calls postScrapRecording (and bumps productionOrder.quantityScrapped). The other five posting methods (postMaterialIssuance, postLaborRecording, postOverheadAbsorption, postProductionCompletion, postVarianceRecognition) exist but are not called from any flow yet. The stock-side moves (backflush OUT, output IN) happen regardless; the corresponding RawMat/WIP/FinishedGoods journals are the integration gap. Treat the GL table above as the designed mapping, not fully-active behavior.

3.5 What does not move stock

  • ProductionMaterial.issueMaterial() is a stub ("Issue material logic would go here") — it only updates quantityIssued/status, it does not write a stock row. Material stock OUT happens solely via backflush (§3.2).
  • BOM / routing / work-center / MRP never write stock — they are definition/planning only.
  • Quality and scrap do not write stock rows (scrap only writes GL + bumps the order's scrap counter).

4. Shared enums

// bom/bom.scheme.ts
enum BomStatus         { DRAFT, ACTIVE, OBSOLETE }
enum BomType           { STANDARD, PHANTOM }          // PHANTOM = pass-through in MRP explosion (§6.3)
enum BomApprovalStatus { DRAFT, PENDING_APPROVAL, APPROVED, REJECTED }
// bom/line/line.scheme.ts
enum FlushingMethod    { MANUAL, FORWARD, BACKWARD }   // drives backflush timing (§3.2)

// routing/routing.schema.ts
enum RoutingStatus     { ACTIVE, DRAFT, OBSOLETE }
// work-center/work-center.scheme.ts
enum WorkCenterType    { MACHINE, LABOR, SUBCONTRACTOR }
enum WorkCenterStatus  { ACTIVE, INACTIVE }

// production/production.scheme.ts
enum ProductionOrderStatus { DRAFT, RELEASED, IN_PROGRESS, COMPLETED, CANCELLED }
enum ProductionOrderSource { MANUAL, SALES_ORDER, MRP }
// production/material/material.scheme.ts
enum ProductionMaterialStatus { PENDING, PARTIALLY_ISSUED, FULLY_ISSUED }
// production/operation/operation.scheme.ts
enum ProductionOperationStatus { PENDING, IN_PROGRESS, COMPLETED, SKIPPED }
// production/scrap/scrap-record.scheme.ts
enum ScrapReasonCode { MATERIAL_DEFECT, MACHINE_ERROR, OPERATOR_ERROR, TOOLING_WEAR,
                       SETUP_WASTE, QUALITY_REJECT, DAMAGED_IN_HANDLING, OTHER }

// mrp/mrp-run/mrp-run.scheme.ts
enum MrpRunStatus  { PENDING, RUNNING, COMPLETED, FAILED }
// mrp/mrp-result/mrp-result.scheme.ts
enum MrpActionType { CREATE_PRODUCTION_ORDER, CREATE_PURCHASE_ORDER, RESCHEDULE_IN,
                     RESCHEDULE_OUT, CANCEL, EXPEDITE }
enum MrpPriority   { HIGH, MEDIUM, LOW }

// quality/quality-inspection.scheme.ts
enum QualityResult            { PASS, FAIL, CONDITIONAL_PASS, PENDING }
enum InspectionType           { IN_PROCESS, FINAL, INCOMING }
enum InspectionStatus         { OPEN, CLOSED }
enum NonConformanceDisposition{ REWORK, SCRAP, ACCEPT_AS_IS, RETURN_TO_VENDOR, PENDING }

// accounting/account.types.ts
enum ManufacturingAccountType      { WIP, RAW_MATERIALS_INVENTORY, FINISHED_GOODS_INVENTORY,
  MANUFACTURING_OVERHEAD, OVERHEAD_APPLIED, MATERIAL_USAGE_VARIANCE, LABOR_EFFICIENCY_VARIANCE,
  OVERHEAD_VARIANCE, SCRAP_EXPENSE, SUBCONTRACTING_EXPENSE }
enum ManufacturingJournalEntryType { MATERIAL_ISSUANCE, LABOR_RECORDING, OVERHEAD_ABSORPTION,
  PRODUCTION_COMPLETION, SCRAP_RECORDING, VARIANCE_RECOGNITION }

// inventory/item/item.scheme.ts (manufacturing-relevant slice; see ../inventory/item.md)
enum ManufacturingItemType { NONE, RAW_MATERIAL, SUB_ASSEMBLY, FINISHED_GOOD, WIP }

Stock-side enums (StockTypes, StockKindTypes — manufacturing uses kind=Stock) live in ../inventory/stock.md.


BE root: src/modules/manufacturing. Admin: src/modules/manufacturing (+ src/modules/bom).

Sub-module BE path Collection(s) GraphQL ops (primary) Doc
BOM (+ line) bom, bom/line manufacturing_boms, manufacturing_bom_lines createBom/updateBom/findBom/bomPage/deleteBom(s), bomWhereUsed, recalculateBomCost, submitBomForApproval/approveBom/rejectBom; createBomLine/updateBomLine/bomLinePage/deleteBomLine(s) ./bom.md
Routing (+ operation) routing, routing/operation routings, routing_operations createRouting/updateRouting/deleteRouting/findOneRouting/routingPage (+ routing-operation CRUD) this overview
Work Center work-center work_centers createWorkCenter/updateWorkCenter/findWorkCenter/workCenterPage/deleteWorkCenter(s) this overview
Production Order production production_orders createProductionOrder/updateProductionOrder/findProductionOrder/productionOrderPage/deleteProductionOrder(s) this overview
↳ Material production/material production_materials createProductionMaterial/updateProductionMaterial/findProductionMaterial/productionMaterialPage/deleteProductionMaterial(s) this overview
↳ Operation production/operation production_operations createProductionOperation/updateProductionOperation/findProductionOperation/productionOperationPage/deleteProductionOperation(s) this overview
↳ Output production/output production_outputs createProductionOutput/updateProductionOutput/findProductionOutput/productionOutputPage/deleteProductionOutput(s)Stock IN this overview
↳ Scrap production/scrap scrap_records createScrapRecord/updateScrapRecord/findScrapRecord/scrapRecordPage/deleteScrapRecordGL this overview
MRP (run + result) mrp/mrp-run, mrp/mrp-result mrp_runs, mrp_results createMrpRun/updateMrpRun/findMrpRun/mrpRunPage/deleteMrpRun(s); same for MrpResult this overview
Quality quality quality_inspections createQualityInspection/updateQualityInspection/findQualityInspection/qualityInspectionPage/deleteQualityInspection(s) this overview
Accounting accounting (none — posts finance journals) wipDashboard, costVarianceReport this overview
Dashboard dashboard (none — aggregates) manufacturingDashboard this overview
Reports reports (none — aggregates) productionSummaryReport, wipAgingReport, bomCostReport, qualitySummaryReport, capacityUtilizationReport, yieldAnalysisReport, mrpExceptionReport this overview

MRP service methods executeMrpRun(runId, user) and convertResult(resultId, user) live on MrpService but are invoked from the mrp-run/mrp-result resolvers (or admin actions); exploseBom is the recursive BOM-explosion helper (§6.3).

5.1 Routing & work-center (definition detail)

  • Routing (routings): code, name, itemId (the produced item), status, description, and a virtual operations[] (joined from routing_operations). The routing resolver currently uses @ApGqlAuthorize({ authNotRequired: true })auth is bypassed (see §7 gotcha).
  • RoutingOperation (routing_operations): routingId, sequenceNo, operationName, workCenterId, setupTimeHours, runTimePerUnitHours, overlapPercent, scrapFactor, instructions, attachments[]. The ordered operation steps; each runs at a work center.
  • WorkCenter (work_centers): code (unique), name, type (MACHINE/LABOR/SUBCONTRACTOR), capacityHoursPerDay, efficiencyPercent, costRatePerHour, assetId, plus a calendar (workingDays, shiftsPerDay, hoursPerShift, holidays, calendarNotes). costRatePerHour × operation hours is the basis for labour/overhead costing (capacity-utilization & yield reports read it).

5.2 Production children (execution detail)

  • ProductionOrder carries both planned (plannedMaterial/Labor/Overhead/TotalCost) and actual (actualMaterial/Labor/Overhead/TotalCost) cost buckets; quantityPlanned/Completed/Scrapped; source (MANUAL/SALES_ORDER/MRP) + salesOrderId for make-to-order. orderNumber defaults to PO-<nanoId> when not supplied. release/complete are service methods (backflush triggers) not exposed as GraphQL mutations — status changes go through updateProductionOrder and the consumption side-effects are not auto-triggered by that update (see §8).
  • ProductionMaterial — planned/issued component lines (issue logic is a stub; §3.5).
  • ProductionOperation — per-order operation instances (mirrors routing operations), with planned vs actual hours, laborCost, overheadCost, workCenterId, status, attachments.
  • ProductionOutput — completion records; quantityGood > 0 triggers Stock IN (§3.3).
  • ScrapRecord — scrap by reasonCode; writes GL (Scrap Expense ↔︎ WIP) and increments the order's quantityScrapped (§3.4).

5.3 Quality

QualityInspection (quality_inspections): links productionOrderId / operationId / itemId; inspectionType (IN_PROCESS/FINAL/INCOMING), result (PASS/FAIL/CONDITIONAL_PASS/PENDING), quantityInspected/Passed/Failed, plus non-conformance disposition (REWORK/SCRAP/ACCEPT_AS_IS/RETURN_TO_VENDOR/PENDING), quarantineWarehouseId, reworkQuantity, scrapQuantity. Currently records outcomes; it does not auto-create scrap stock moves.

5.4 Accounting / dashboard / reports

  • AccountingManufacturingJournalService (the GL poster, §3.4) + ManufacturingAccountingResolver exposing wipDashboard (open RELEASED/IN_PROGRESS orders' actual WIP value) and costVarianceReport (COMPLETED orders: actualTotalCost − plannedTotalCost, % variance).
  • DashboardmanufacturingDashboard aggregate query.
  • Reports — 7 read aggregations: productionSummaryReport, wipAgingReport, bomCostReport (sums production-order planned costs by item — not the BOM roll-up; see ./bom.md §4.1), qualitySummaryReport (pass/fail rates), capacityUtilizationReport, yieldAnalysisReport, mrpExceptionReport.

6. Cross-module flows

6.1 End-to-end: manufacture-to-stock (happy path)

1. Define   Item(isManufactured, FINISHED_GOOD) ─ Bom(itemId, lines) ─ Routing(itemId, operations) ─ WorkCenters
            (optional) recalculateBomCost(bom) → totalCost roll-up
2. Plan     ProductionOrder(itemId, bomId, routingId, quantityPlanned, source=MANUAL|MRP|SALES_ORDER)
            status=DRAFT  → (update) RELEASED
3. Consume  release/complete → autoConsumeMaterials(method) → per BOM line (matching flushingMethod):
              stockSvc.backflushConsume → Stock(type=OUT, kind=Stock)  at order.branchId   ── components leave stock
              [designed GL: Dr WIP / Cr Raw Materials Inventory — not yet wired]
4. Operate  ProductionOperation rows track hours → actual labor/overhead cost; (Quality inspections optional)
5. Output   ProductionOutput(quantityGood) → costingSvc.postReceipt → Stock(type=IN) for finished item ── product enters stock
              [designed GL: Dr Finished Goods Inventory / Cr WIP — not yet wired]
6. Scrap    ScrapRecord(quantity, scrapCost) → quantityScrapped += ; GL Dr Scrap Expense / Cr WIP (WIRED)
7. Close    update status=COMPLETED ; costVarianceReport compares actual vs planned

Net inventory effect: components OUT, finished good IN, both per-branch, both via the single Stock ledger (../inventory/stock.md). On-hand is always Σ IN − Σ OUT; deleting an output/backflush row self-corrects the balance.

6.2 MRP-driven flow

  1. createMrpRun (runNumber, planningHorizonDays, includeAllItems / itemIds) → executeMrpRun: status RUNNING; per item compute netRequirement = max(0, grossReq − supply). (In current code calculateGrossRequirement/calculateCurrentSupply and the item fetch are placeholders returning dummy values — the explosion logic is real, the demand/supply sourcing is a TODO.)
  2. If the item has an ACTIVE + APPROVED BOM → emit MrpResult(CREATE_PRODUCTION_ORDER) and exploseBom; else → MrpResult(CREATE_PURCHASE_ORDER).
  3. Run completes (COMPLETED/FAILED) with totalPlannedProductionOrders/PurchaseOrders/Exceptions.
  4. convertResult(resultId): CREATE_PRODUCTION_ORDER → creates a ProductionOrder(source=MRP); CREATE_PURCHASE_ORDER → marked converted (purchase-order creation is an integration point, not implemented). Idempotent — throws if isConverted.

6.3 BOM explosion (MrpService.exploseBom, recursive)

For each BOM line: totalQty = line.quantity × parentQty × (1 + scrapPercent/100).

  • Line has componentBomId → recurse into the child BOM.
  • Leaf component → emit MrpResult(CREATE_PURCHASE_ORDER) for that component.
  • bomType === PHANTOM → do not create a production order; components bubble straight up to the parent (pass-through sub-assembly). This is the one place BomType.PHANTOM is actually honored.

6.4 Unhappy paths

  • BOM approval transitions reject illegal source states (./bom.md §4.3).
  • Backflush per item is wrapped in try/catch — a failed component issue is logged and skipped, the rest continue (no all-or-nothing rollback across components within autoConsumeMaterials).
  • Sales availability is not enforced on stock OUT generally (manufacturing backflush included) — see ../inventory/stock.md §9; consumption can drive on-hand negative.

7. Permissions, access control & feature gating

7.1 Current (as-implemented) state

  • Every manufacturing resolver is class-level @ApInitGqlAuthorize() (JWT-authenticated; see ../../platform/auth.md), and *Page queries add @ApGqlAuthorize({ includeBranchQuery: false }) — RBAC access-group check (../../platform/permissions-access.md) but with branch filtering disabled, so lists are company-wide.
  • BOM and production mutations carry @AuditMeta(...) (modules bom / production; ../../platform/audit-trail.md).
  • Gotcha: the routing resolver uses @ApGqlAuthorize({ authNotRequired: true }) — it is currently unauthenticated. The accounting resolver uses plain @ApGqlAuthorize().
  • There is currently no per-action CASL permission and no subscription feature flag enforced on manufacturing resolvers in zerp.

7.2 Designed access-control model (planned, not yet applied)

The plan zerp-be/docs/superpowers/plans/2026-04-09-manufacturing-access-control.md specifies the intended model (written against zyncount-be paths; the same module exists in zerp). It is not yet implemented in the current zerp code (no ApGqlFeature/ApGqlPermission on the resolvers):

  • Feature gate: a single @ApGqlFeature("MANUFACTURING") at every resolver class level — the whole domain is hidden unless the company's subscription includes the MANUFACTURING feature (GqlFeatureGuard; admin ROUTE_TO_FEATURE_MAP + hasFeature('MANUFACTURING') hides the nav section).
  • RBAC per action: @ApGqlPermission({ action, subject }) per method against 8 new ApModules entries: BOM (manufacturing-bom), PRODUCTION_ORDER (production-order), WORK_CENTER (work-center), ROUTING (routing), MRP (mrp), QUALITY_CONTROL (quality-control), MANUFACTURING_DASHBOARD (manufacturing-dashboard), MANUFACTURING_REPORT (manufacturing-report), with RoleActions (CREATE/READ/UPDATE/DELETE/MANAGE).
  • The plan also explicitly removes routing's authNotRequired: true and replaces it with auth + feature + RBAC. Until applied, treat §7.1 as the live behavior and §7.2 as the target.

8. Gotchas & domain-wide rules

  • Backflush is the only material stock-out path. ProductionMaterial.issueMaterial is a stub; manual issuance does not move stock. Components leave stock only via release()/complete() → backflush (§3.2).
  • release/complete are not GraphQL mutations. They exist on ProductionOrderService but are not exposed on the resolver, and updateProductionOrder setting status does not trigger consumption. A rebuild must decide how release/complete (and thus backflush/output) are invoked (dedicated mutations or status-change hooks). As wired, status can be edited without the stock side-effects firing.
  • Most manufacturing GL is not yet wired. Only scrap posts a journal. Material-issuance, labor, overhead, completion, and variance journals are implemented in ManufacturingJournalService but not called — the WIP value flow is largely a designed-but-dormant feature (§3.4).
  • GL posting fails open. Missing manufacturing accounts (resolved by name, not by configured id) are skipped with a warning, never an error — production proceeds without a journal.
  • Finished-output cost is 0 at receipt. postReceipt(..., unitCost=0, ...) — finished-good valuation depends on the item's costing method resolving cost later, not on a captured production cost.
  • Backflush mutates legacy item.stockOut/netQuantity. It is the one stock writer that touches the denormalized item counters; every other flow leaves them stale (../inventory/stock.md §9).
  • Routing is currently unauthenticated (authNotRequired: true) — security gap until the access-control plan (§7.2) is applied.
  • bomCostReport ≠ BOM totalCost. The report sums production-order planned costs by item; the BOM roll-up (recalculateBomCost) is a separate definition-time number (./bom.md §4.1).
  • BomType.PHANTOM only matters in MRP explosion (§6.3); the BOM module itself treats it inertly.
  • MRP demand/supply is stubbed. executeMrpRun runs the BOM-explosion logic for real but calculateGrossRequirement/calculateCurrentSupply return dummy values and item selection is a TODO — not production-ready as a planning engine.
  • README drift. manufacturing/README.md lists collections boms/bom_lines and a bom/line/ layout that don't match the code (manufacturing_boms/manufacturing_bom_lines). Trust the schemas.
  • Branch/company scoping everywhere, but *Page lists are company-wide (includeBranchQuery: false). Stock balances produced/consumed are per-branch (the order's branchId).