The Material Requisition (MR) is the pick-list that the storeroom team uses to issue the exact raw-material quantities required for a planned production batch. Once the planner has raised a batch and validated it against live stock, the system snapshots every BOM line into production_batch_lines with a required quantity = qty_per_unit × planned_qty × (1 + scrap_pct/100). The MR is printed for the floor · picker walks it row-by-row, the storeman countersigns each pick, and the supervisor authorises the issue.
This document mirrors the Sage X3 "Material Issue Voucher", Syspro "Job Issue", SAP MIGO 261 goods-issue against a production order, and NetSuite "Inventory Issue" against a Work Order. Without an MR, no ISSUE inventory transactions can be posted against the batch — the floor cannot legally remove stock from the storeroom.
The MR is the bridge between the planning office and the production floor. Its line totals reconcile (a) what the BOM demands, (b) what the storeroom hands over, and (c) what the Job Card consumes during the run.
DRAFT → PLANNED after a successful BOM validation.The MaterialReq spec (frontend/js/erp-templates/material-requisition.js) is built from the BOM register and the snapshot taken at batch validation. Each row below maps to a real column.
| Field on document | API endpoint | DB table.column | Example value |
|---|---|---|---|
| MR Ref No. | generated client-side · placeholderRef() | (stamped on print) | MR-2026-05080842 |
| Batch ref | POST /api/production-batches | production_batches.batch_ref | BATCH-2026-0481 |
| BOM ref / version | GET /api/bom/:id | bom_headers.version | v3 |
| FG product code | GET /api/products | products.code | A4_72PG_FM |
| FG product name | GET /api/products | products.name | A4 72-page Feint Margin Exercise Book |
| Planned qty (units) | POST /api/production-batches | production_batches.planned_qty | 5 000 |
| RM code | GET /api/items/:id | items.code (FK bom_lines.rm_item_id) | MONDI 900 |
| RM description | GET /api/items | items.description | Mondi 900mm reel 80gsm offset |
| Qty / unit | GET /api/bom/:id | bom_lines.qty_per_unit | 0.0420 |
| Scrap % | GET /api/bom/:id | bom_lines.scrap_pct | 3.50 |
| Required qty | computed | production_batch_lines.required_qty | 217.350 |
| Available qty (validation) | POST /api/production-batches/:id/validate | production_batch_lines.available_qty | 240.000 |
| Shortfall qty | derived | production_batch_lines.shortfall_qty | 0.000 (OK) |
| UoM | master data | items.uom / bom_lines.uom | KG |
| From warehouse | GET /api/warehouses | warehouses.code | RM-MAIN / PSTAT |
| From bin | GET /api/inventory/lots | warehouse_locations.bin_code | RM-A12-03 |
| Picked qty (manual) | captured on paper | posted as inventory_transactions.qty when batch starts | 217.450 |
| Picker initials | captured on paper | not stored separately · cross-ref to employees.clock_no | P.M. (clock 1042) |
| Requested by | req.user | users.email (FK created_by) | planner@palmstat.co.za |
| Target shift | POST /api/production-batches | production_batches.shift | DAY / NIGHT |
POST /api/production-batches with fg_product_id, planned_qty and machine_id. Status starts as DRAFT. The system auto-resolves the active BOM via bom_headers.is_active = 1.POST /api/production-batches/:id/validate snapshots every BOM line into production_batch_lines and computes required_qty = qty_per_unit × planned_qty × (1 + scrap_pct/100). Status flips to PLANNED if all lines OK, or BLOCKED with blocked_reason populated when shortages exist.Print Material Requisition. The frontend calls MaterialReq.build(bom, planned_qty, batch).print(). The 9-column printable lists every BOM line with picked-qty + initials columns left blank for ink-on-paper sign-off.POST /api/production-batches/:id/start. The endpoint loops over production_batch_lines and posts an ISSUE inventory transaction per line (txn_type=ISSUE, from_warehouse_id = the picked-from WH, reference_type='production_batch', reference_id=batch.id). Stock leaves v_inventory_summary; batch status flips to IN_PROGRESS.production realtime channel signals: WIP Tracking page updates, dashboard tiles count up, and any open Tally Sheet view refreshes its eligible-batch grid.POST /api/documents with kind=OTHER; document_links ties it to entity_type='production_batch', entity_id=batch.id.| Action | Admin | Planner | Storeman / Warehouse | Supervisor | Operator |
|---|---|---|---|---|---|
| Generate this doc | ✓ | ✓ | ✓ | ✓ | – |
| Validate batch (snapshot lines) | ✓ | ✓ | – | ✓ | – |
| Pick & sign | ✓ | – | ✓ | – | – |
| Authorise & release | ✓ | – | – | ✓ | – |
| Reprint after batch start | ✓ | ✓ | ✓ | ✓ | – |
| Cancel batch (voids MR) | ✓ | – | – | – | – |
Permission gate enforced by requireRole('admin','planner','supervisor') on POST /api/production-batches. The validate endpoint accepts the same set; cancellation is admin-only.
Happy path: Validate produces all lines at OK status, MR prints cleanly, picker walks the floor, picks within tolerance, supervisor signs. Batch starts, ISSUE transactions post, IN_PROGRESS within 10 minutes of validation.
Happy path: Validate detects 50 KG short on adhesive. Status → BLOCKED with blocked_reason populated. Planner raises a top-up purchase or splits the batch. MR is regenerated only after a fresh GRN posts the missing stock.
Sad path: Planner ignores the BLOCKED status and forces the batch to start. The system rejects with a 422 because production_batch_lines still shows status='SHORT'. Recovery is to either resolve the shortage or re-validate after stock arrives.
Happy path: Planner has approved TNPL 1200 in lieu of MONDI 1200 for this run. The MR carries a hand-written note "Substitute approved · TNPL 1200" plus the planner's initials. The Job Card mirror is annotated. Inventory issue posts against the substitute item code.
Happy path: Picker over-picks 2.0 KG of paper because of an off-set on the scale. Storeman catches it, scribbles the corrected qty, both initial. Variance is recorded on the FGRN at close-out so unit-cost stays accurate.
Sad path: Over-pick goes unnoticed. Material consumed on the run; FGRN shows a higher RM cost variance. Recovery: post a balancing ADJUST transaction once cycle-count flags the discrepancy.
bom_headers, bom_lines)production_batches, status → PLANNED)inventory_transactions rows of type ISSUE| Stage | Target time | Owner | Escalation if breached |
|---|---|---|---|
| Validate → Print | 15 min | Planner | Production Manager (24h SLA) |
| Pick & weigh complete | 30 min from print | Storeroom | Warehouse Manager |
| Supervisor authorise | 10 min after pick | Supervisor | Production Manager |
| Batch start (auto-issue) | 5 min after authorise | Supervisor | Operations Director |
| Variance investigation (> 1%) | End of shift | Supervisor + Planner | Quality Manager |
bom_headers + bom_lines.