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 OUTStockService.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.createScrapRecordcallspostScrapRecording(and bumpsproductionOrder.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 updatesquantityIssued/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.
5. Sub-module inventory (with links)
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/deleteScrapRecord → GL |
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)andconvertResult(resultId, user)live onMrpServicebut are invoked from themrp-run/mrp-resultresolvers (or admin actions);exploseBomis 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 virtualoperations[](joined fromrouting_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)
ProductionOrdercarries both planned (plannedMaterial/Labor/Overhead/TotalCost) and actual (actualMaterial/Labor/Overhead/TotalCost) cost buckets;quantityPlanned/Completed/Scrapped;source(MANUAL/SALES_ORDER/MRP) +salesOrderIdfor make-to-order.orderNumberdefaults toPO-<nanoId>when not supplied.release/completeare service methods (backflush triggers) not exposed as GraphQL mutations — status changes go throughupdateProductionOrderand 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 > 0triggers Stock IN (§3.3).ScrapRecord— scrap byreasonCode; writes GL (Scrap Expense ↔︎ WIP) and increments the order'squantityScrapped(§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
- Accounting —
ManufacturingJournalService(the GL poster, §3.4) +ManufacturingAccountingResolverexposingwipDashboard(open RELEASED/IN_PROGRESS orders' actual WIP value) andcostVarianceReport(COMPLETED orders:actualTotalCost − plannedTotalCost, % variance). - Dashboard —
manufacturingDashboardaggregate 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
createMrpRun(runNumber, planningHorizonDays, includeAllItems / itemIds) →executeMrpRun: statusRUNNING; per item computenetRequirement = max(0, grossReq − supply). (In current codecalculateGrossRequirement/calculateCurrentSupplyand the item fetch are placeholders returning dummy values — the explosion logic is real, the demand/supply sourcing is a TODO.)- If the item has an ACTIVE + APPROVED BOM → emit
MrpResult(CREATE_PRODUCTION_ORDER)andexploseBom; else →MrpResult(CREATE_PURCHASE_ORDER). - Run completes (
COMPLETED/FAILED) withtotalPlannedProductionOrders/PurchaseOrders/Exceptions. convertResult(resultId):CREATE_PRODUCTION_ORDER→ creates aProductionOrder(source=MRP);CREATE_PURCHASE_ORDER→ marked converted (purchase-order creation is an integration point, not implemented). Idempotent — throws ifisConverted.
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 placeBomType.PHANTOMis 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*Pagequeries 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(...)(modulesbom/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 theMANUFACTURINGfeature (GqlFeatureGuard; adminROUTE_TO_FEATURE_MAP+hasFeature('MANUFACTURING')hides the nav section). - RBAC per action:
@ApGqlPermission({ action, subject })per method against 8 newApModulesentries: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), withRoleActions(CREATE/READ/UPDATE/DELETE/MANAGE). - The plan also explicitly removes routing's
authNotRequired: trueand 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.issueMaterialis a stub; manual issuance does not move stock. Components leave stock only viarelease()/complete()→ backflush (§3.2). release/completeare not GraphQL mutations. They exist onProductionOrderServicebut are not exposed on the resolver, andupdateProductionOrdersettingstatusdoes 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
ManufacturingJournalServicebut 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
0at 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≠ BOMtotalCost. 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.PHANTOMonly matters in MRP explosion (§6.3); the BOM module itself treats it inertly.- MRP demand/supply is stubbed.
executeMrpRunruns the BOM-explosion logic for real butcalculateGrossRequirement/calculateCurrentSupplyreturn dummy values and item selection is a TODO — not production-ready as a planning engine. - README drift.
manufacturing/README.mdlists collectionsboms/bom_linesand abom/line/layout that don't match the code (manufacturing_boms/manufacturing_bom_lines). Trust the schemas. - Branch/company scoping everywhere, but
*Pagelists are company-wide (includeBranchQuery: false). Stock balances produced/consumed are per-branch (the order'sbranchId).