Manufacturing Module¶
Purpose¶
Owns everything related to how products are made: recipes (BOM, allergens, nutrition, versioning, approval), production orders (workflow, material consumption, labor tracking, cost calculation), and the manufacturing-specific extension data for items (allergens, nutritional values, yield parameters). This module is the core differentiator of GrinSystem — where food production knowledge lives.
Actors¶
| Actor | Interaction |
|---|---|
| Admin | Full access to recipes and production orders |
| Manager (Kierownik) | Create/edit recipes, create/approve ZPs, view costs |
| Worker (Pracownik) | Execute ZPs; view Published recipes only; cannot see costs |
| Technologist (Technolog) | Create/edit recipes and BOM, manage allergens/nutrition, manage Task Groups and Tasks, test recipes; cannot create ZPs; has full cost breakdown visibility (same as Manager, read-only on costs) |
| QA Specialist | View all recipe statuses; register CCP results during ZP |
Use Cases — Recipes¶
MFG-UC-01: Create Recipe¶
Actor: Admin, Manager, Technologist Phase: 1
User creates a new recipe linked to an existing product (item_id with type product or intermediate). A recipe cannot be saved without a product_id.
Required fields: recipe_code (unique per tenant), name, linked product, yield_quantity, yield_unit (kg | g | l | ml | piece). If yield_unit = piece, average_piece_weight_g is required.
Recipe starts in Draft status. Draft recipes are mutable; all other statuses are immutable.
Business rules:
1. Recipe cannot exist without a product. A product can exist without a recipe (R&D state).
2. recipe_code is unique per tenant and immutable once saved
3. yield_unit must match the product's storage_unit
4. All quantities on BOM lines use the ingredient's storage_unit from ItemCatalog — no automatic conversion
5. Recipe publication is BLOCKED if any BOM line ingredient with storage_unit=l lacks density_g_per_ml — requires setting density to enable nutrition calculation
MFG-UC-02: Build Multi-Level BOM¶
Actor: Admin, Manager, Technologist Phase: 1
User adds IngredientGroups to the recipe BOM. IngredientGroups are owned by the Manufacturing module (auto-created when a new ingredient or intermediate item is registered in ItemCatalog). Each group has a total quantity (in the group's storage unit). Within a group, one ingredient is marked is_main = true — its nutrition values are used for cost and nutrition calculations. Allergens are calculated as a logical OR across ALL ingredients in the group (not just is_main), since any substitute may be used in production. Other ingredients in the group are substitution alternatives.
BOM can include items of type intermediate — this creates a multi-level BOM. The referenced intermediate item must itself have a Published recipe.
Each BOM line (IngredientGroup) supports optional waste configuration:
- waste_type: percentage | fixed_amount
- waste_value: the number (e.g. 5 for 5% or 0.2 for 0.2 kg)
Both fields are optional. When set, they define the alert threshold for that ingredient's waste in ZP execution (see MFG-UC-17). A BOM line with no waste config generates no waste alert.
Business rules:
1. Maximum BOM depth: 5 levels
2. A recipe cannot reference itself (circular BOM detection required)
3. Quantity is specified at group level, not ingredient level — the is_main ingredient receives the full group quantity for cost and nutrition calculations
4. When a BOM line references an intermediate item: at ZP creation time, Manager chooses whether to produce the intermediate item (auto-creates a child ZP) or consume from existing warehouse stock
5. service type items cannot appear in BOM
6. Produce vs consume decision is locked at ZP creation — cannot be changed during ZP execution
7. Waste config fields (waste_type, waste_value) are editable on Draft recipes only — frozen on Test/Published transition alongside all other BOM data
MFG-UC-03: Manage Item Allergens and Nutrition (Manufacturing Extension)¶
Actor: Admin, Manager, Technologist Phase: 1
For each item in ItemCatalog, the Manufacturing module owns an extension record containing:
- 14 EU allergens, each with status: contains | may_contain | none
- Nutritional values per 100g/ml: Energy (kcal and kJ), Fat, Saturated fat, Carbohydrates, Sugars, Protein, Salt; optional: Fibre
- density_g_per_ml (required if storage_unit = l, for nutrition calculations)
System items (is_system = true) have pre-populated extension data managed by GrinSystem — read-only for tenants.
Business rules:
1. Validation: saturated_fat ≤ fat; sugars ≤ carbohydrates
2. If storage_unit = l, density_g_per_ml is required for nutrition calculation; without it, recipe nutrition shows "incomplete data" warning
3. When a main ingredient's allergens or nutrition change, all recipes using that ingredient in an is_main position are flagged for recalculation; a new RecipeNutritionSnapshot is auto-created for Published recipes
4. Changing nutrition/allergens does not alter existing immutable ZP history (ZPs snapshot at execution time)
MFG-UC-04: Recipe Cost Calculation¶
Actor: Admin, Manager Phase: 1
Recipe displays a 4-category cost breakdown:
- Materials: Σ(group_quantity × main_ingredient.guide_price per group)
- Waste: Σ(waste_value × main_ingredient.guide_price per BOM line where waste_type = percentage or fixed_amount)
- Labor: Σ(recipe-selected task expected_duration_minutes) / 60 × tenant.labor_hourly_rate. Tasks are selected from tenant-level Task Groups during recipe authoring (see MFG-UC-22). If no tasks are assigned to the recipe, or if tenant.labor_hourly_rate is not configured, the Labor category shows a warning — not a blocking error.
- Other costs: Named fixed cost entries per recipe — each entry has a
name(free text, e.g. "Packaging", "Line Setup") andamount_pln(fixed cost per batch). Defined in Draft; frozen on Test/Published transition. Summed into total recipe cost.
Unit cost = total_cost / yield_quantity. Margin = (selling_price − unit_cost) / selling_price × 100% (requires product to have a selling price in CRM).
guide_price is a manual estimate per ingredient set by Manager/Admin. It is NOT automatically pulled from Warehouse purchase prices (separation of planned cost from actual cost).
Business rules: 1. guide_price change triggers immediate recipe cost recalculation across all recipes using that ingredient 2. labor_hourly_rate not configured → Labor category shows warning, not a blocking error 3. Cost calculation is read-only for recipes in Test, Published, or Archived status
MFG-UC-05: Recipe Versioning and Multiple Active Recipes¶
Actor: Admin, Manager Phase: 1
Recipe identity = recipe_code + version. Version is set by the published_at timestamp (not a manual number). New version created via: blank form or clone of any existing recipe.
Multiple recipes for the same product can be simultaneously Published (e.g., "summer" vs "winter" formulation). One is marked is_default = true.
Business rules:
1. Published recipe is fully immutable — any change requires creating a new version (clone → edit → publish)
2. is_default can be changed between Published recipes at any time; does not affect already-created ZPs
3. ZPs are pegged to a specific recipe_code + version at creation; they do not automatically update if is_default changes
4. No "revert to older version" — clone an Archived recipe instead
5. Changing is_default does not retroactively affect any open or completed ZP
MFG-UC-06: Recipe Approval Workflow (Test → Published)¶
Actor: Admin, Manager Phase: 1
Recipe lifecycle: Draft → Test → Published → Archived
- Draft: editable, not usable in regular ZPs
- Test: immutable; eligible for Trial ZPs only (see MFG-UC-13); can be Approved or Rejected
- Approve → Published (available for regular ZPs)
- Reject → Archived (not usable; clone to iterate)
- Published: immutable; available for regular ZPs
- Archived: read-only; not usable in new ZPs; clonable
Transition to Test is triggered by explicit "Submit for Testing" action. Approving a Test recipe requires Admin or Manager role.
Business rules: 1. Only Admin and Manager can transition Draft → Test and Test → Published/Archived 2. Rejected Test recipes go directly to Archived — cannot be re-submitted; clone instead 3. A recipe cannot be directly archived from Draft — must go through Test first (ensures minimum review) 4. Attaching version-specific files (e.g., lab results) is allowed in Draft and Test; frozen on transition to Published 5. Lineage-level files (shared across all versions of a recipe_code) are always editable regardless of status 6. When a Test recipe is rejected → Archived: any in-progress Trial ZPs continue and can complete; only new Trial ZP creation is blocked from the Archived recipe
MFG-UC-07: Recipe Automatic Nutrition Calculation¶
Actor: System (auto) Phase: 1
For each recipe, the system calculates the nutrition table per 100g of finished product:
value_per_100g = Σ(group_quantity × is_main_ingredient.value_per_100g / 100) / yield_weight_g × 100
Multi-level BOM: semi-finished items' nutrition is recursively calculated from their own recipe.
A RecipeNutritionSnapshot is created automatically: on first Draft save, on every recalculation trigger, and on each status transition. Old snapshots are preserved immutably. ZP history references the snapshot active at ZP completion time.
Business rules: 1. Missing density_g_per_ml on any liquid ingredient in is_main position → recipe publication is BLOCKED (hard block, consistent with BR-MFG-12) 2. Missing nutrition data on any is_main ingredient → recipe shows "Partial nutrition — missing ingredients listed" 3. Snapshots are immutable once created
MFG-UC-08: Allergen Calculation¶
Actor: System (auto) Phase: 1
Recipe allergen profile = union of allergen statuses from ALL ingredients in ALL IngredientGroups across all BOM levels (not just is_main). This ensures the allergen label covers any possible substitution.
Precedence: contains > may_contain > none — if any ingredient in any group contributes contains, the recipe shows contains.
Auto-alert sent when recipe allergen profile changes due to ingredient modification.
MFG-UC-09: Export Nutrition & Allergen Labels¶
Actor: Admin, Manager Phase: 1
Export to PDF (EU format) or Excel (tabs: Nutrition, Allergens, Ingredients). Label preview before export. Company logo on documents.
PDF allergen format: "Contains: gluten, milk. May contain: nuts."
MFG-UC-10: Recipe Experimentation Mode¶
Actor: Admin, Manager Phase: 2
Fully ephemeral — no database writes. Allows: edit ingredients (add/remove/swap), adjust quantities, change waste, add routing tasks, change other costs. Only existing catalog items usable (no inline creation during experimentation).
Create up to 3 variants (A, B, C) with real-time cost, nutrition, and allergen comparison. Differences highlighted (green = beneficial, red = adverse). Exit: "Discard" or "Apply to recipe" (creates new Draft version).
MFG-UC-11: Ingredient Substitution in Experimentation¶
Actor: Admin, Manager Phase: 2
Within experimentation mode, swap the is_main ingredient with another from the same IngredientGroup. On swap: - Quantity preserved (storage_unit must match; if different, warning + manual entry required) - System shows before/after comparison: cost, allergens, nutrition
MFG-UC-12: Cost and Portfolio Simulator¶
Actor: Admin, Manager Phase: 2
Simulate impact of a guide_price change across the full recipe portfolio. Input: ingredient + new price. Output: all affected recipes with old vs new unit cost and margin. No persistence — what-if calculator only. Export to PDF/Excel.
MFG-UC-13: Recipe Attachments¶
Actor: Admin, Manager Phase: 1
Attach version-specific files (PDF, JPG, PNG, DOCX, XLSX; max 20 MB each) to a recipe. Files frozen (non-editable/non-deletable) when recipe transitions to Test or Published.
Lineage-level files are attached at recipe_code level, shared across all versions, always editable (e.g., shared line SOPs, cleaning procedures).
Use Cases — Production Orders (ZP)¶
MFG-UC-14: Create Production Order (ZP)¶
Actor: Admin, Manager Phase: 1
User selects a product → system auto-selects the is_default Published recipe. User can change to any Published recipe for that product.
ZP fields: recipe + version (immutable after creation), planned quantity, planned date, deadline, responsible person (any Admin, Manager, Technologist, or Worker — the person accountable for the ZP completing on time).
Worker visibility: All Workers can see and work on ANY Planned or InProgress ZP on their tablet — the responsible person is an accountability field, not an access filter.
System shows ingredient availability: per group → available stock vs required → green/yellow/red indicator.
ZP states: Draft → Planned → InProgress → Completed | Cancelled
Auto-assigned unique number: ZP-YYYY-MM-NNNNN
Business rules:
1. Only Published recipes can be used in regular ZPs
2. ZP is pegged to specific recipe_code + version at creation — cannot change recipe after creation
3. Transitioning Draft → Planned triggers material reservation in Warehouse (sends ZPApproved event)
4. Material shortage on approval → warning shown, does not block ZP creation
5. ZP can only transition InProgress → Completed when produced quantity is confirmed
MFG-UC-15: Multi-Level BOM at ZP Creation¶
Actor: Admin, Manager Phase: 1
When the selected recipe contains semi-finished items in the BOM, for each semi-finished ingredient the system asks: "Produce in-house or consume from stock?"
- Produce in-house: system creates a child ZP for the semi-finished item (linked to parent ZP); child ZP must complete before parent ZP can proceed to InProgress
- Consume from stock: system checks warehouse stock; if insufficient, warning shown
Business rules: 1. Child ZPs inherit the deadline of the parent ZP 2. Child ZP cancellation blocks the parent ZP from proceeding to InProgress (user must resolve manually)
MFG-UC-16: ZP Execution¶
Actor: Worker, Manager Phase: 1
Workers view assigned ZPs. Per ZP: recipe name, planned quantity, ingredient groups with planned quantities.
Execution flow:
1. START → ZP transitions to InProgress, timestamp recorded
2. Tasks checklist: Worker sees an ordered checklist of the recipe's selected tasks (from MFG-UC-22). Each task shows its name, optional instructions, and expected duration. Worker ticks tasks off as they are completed. Phase 2 (MFG-UC-20) adds actual time entry per task.
3. Per ingredient group: worker selects which ingredient(s) to use and splits quantity (100% one ingredient, or arbitrary split between group members)
4. Worker selects ingredient LOT from available lots (system suggests LOT via FEFO from Warehouse module); manual entry as fallback
5. Register actual consumption per LOT (can adjust quantity from plan). LOT availability and reservation logic is handled by Warehouse module
6. FINISH → enter produced quantity; if deviation > 5% from planned (hardcoded threshold), a mandatory free-text reason field appears before FINISH is allowed
7. Manufacturing emits ZPCompleted event. Warehouse module consumes this event and creates the production output LOT with expiry date calculated as production_date + product.shelf_life_days (from ItemCatalog)
MFG-UC-17: Register Production Waste¶
Actor: Worker, Manager Phase: 1
Add waste entry to ZP: type (normal loss | defective product | spill | other), quantity, unit. Waste reduces effective yield (produced_quantity = nominal − waste). Alert if waste exceeds recipe-configured threshold %.
Waste entry fields: - Waste type: normal loss | defective product | spill | other (required) - Quantity and unit (required, must match ingredient storage_unit) - Reason/notes (optional free text)
Business rules: 1. Any Worker can register any amount of waste; no Manager approval required 2. Excess waste (above recipe threshold) triggers alert visible to Manager but does not block ZP completion 3. Reason field is optional for all waste entries
MFG-UC-18: Cancel Production Order¶
Actor: Admin, Manager Phase: 1
Cancel allowed from Draft, Planned, or InProgress status. Cannot cancel from Completed.
- From Draft: status change only — no stock impact
- From Planned: releases all material reservations (
ZPCancelledevent sent to Warehouse) - From InProgress: Manager must specify per consumed ingredient:
- Return to warehouse: quantity to return (stock increase event sent to Warehouse)
- Wasted: quantity to write off (waste/disposal event sent to Warehouse)
- Both actions create formal stock adjustment records via events. Full audit trail preserved.
Multi-level ZP cancellation: If the ZP has any child ZPs (for intermediate items from multi-level BOM), Manager is prompted to decide: - Cancel child ZPs as well - Leave child ZPs running (allow them to complete independently)
Business rules:
1. Cancellation reason is optional (free text field)
2. Cancellation is logged in audit trail (who, when, reason if provided)
3. Cancelled ZP remains visible with Cancelled status — not deleted
4. Child ZP fate is Manager's decision at cancellation time
MFG-UC-19: Trial Production Order¶
Actor: Admin, Manager Phase: 1
A Trial ZP can only be created from a Test recipe (is_test = true). Trial ZP has is_trial = true flag.
On completion: generates a LOT with is_trial = true. Trial LOTs are blocked from WZ (cannot be shipped to customers).
After trial, manager/admin reviews and either: - Approves recipe → Test recipe transitions to Published (MFG-UC-06) - Rejects recipe → Test recipe transitions to Archived
Business rules: 1. Regular ZPs cannot be created from Test recipes; Trial ZPs cannot be created from Published recipes 2. Trial ZP costs are tracked separately in cost reports
MFG-UC-20: Labor Tracking (Time Registration)¶
Actor: Worker, Manager Phase: 2
Workers register actual time worked on a ZP per task. Time entries include: worker, task, start time, end time.
Actual labor cost at ZP completion = Σ(actual_time × worker.hourly_rate for the worked day).
Worker class definitions (Operator, Specialist, Manager) and hourly rates (PLN/hour) are configured by Admin. Rate changes are stored historically — closed ZPs use the rate that was active on the day of work.
Workers (Pracownik role) do not see cost figures — only time entries.
MFG-UC-21: ZP Actual Cost Calculation¶
Actor: System (auto on ZP completion) Phase: 1
When ZP transitions to Completed:
- Material cost = Σ(actual_consumption × purchase_price from linked PZ LOT at time of receipt)
- Labor cost = Σ(actual_work_time × worker hourly_rate on work day) — Phase 2 if labor tracking active, otherwise uses planned task durations × tenant.labor_hourly_rate
- Overhead = configurable formula per tenant
- Saves as immutable ProductionCostRecord — not affected by future price changes
Task Groups and Tasks¶
Task Groups are a tenant-level concept used to define the production steps (routing) for recipes. They are distinct from recipe-specific configuration — Task Groups are a shared library that recipe authors select from.
Structure¶
Tenant Task Groups:
├── Baking
│ ├── Mixing (instructions: ...)
│ ├── Proofing (instructions: ...)
│ ├── Baking (instructions: ...)
│ └── Cooling (instructions: ...)
└── Packaging
├── Labeling
├── Wrapping
└── Boxing
MFG-UC-22: Manage Task Groups and Tasks¶
Actor: Admin, Manager, Technologist Phase: 1
Task Group management: - Create Task Group: name (required, unique per tenant, max 100 chars) - Edit Task Group: name only - Reorder Task Groups: drag-and-drop ordering (display order only) - Delete Task Group: only permitted when no recipe references the group
Task management (within a group): - Add Task: name (required, max 100 chars), instructions (optional free text, max 500 chars) - Edit Task: name and instructions - Reorder Tasks within group: drag-and-drop (defines default execution order shown on ZP tablet) - Remove Task: only permitted when no recipe references that specific task with a time assignment
Using Task Groups in a recipe (Draft editing):
- Recipe has a "Routing" section
- Author selects one or more Task Groups to include in the recipe
- For each selected group, author sees the group's tasks and assigns expected_duration_minutes to each task they want to include (tasks without a time assignment are excluded from the recipe's routing)
- Duration is required per included task (cannot add a task to routing without a duration)
Business rules: 1. Task Group names are unique per tenant (case-insensitive) 2. Task Group cannot be deleted while any recipe (in any status) references it 3. A Task cannot be removed from a group while any recipe references it with a time assignment — user must first remove it from all referencing recipes 4. Changes to Task Group or Task definitions (rename, reorder, add/remove tasks) do NOT affect Published recipes — recipes are immutable once Published; the routing is snapshotted at publication 5. ZP execution (MFG-UC-16): Worker sees the recipe's selected tasks as an ordered checklist, with expected duration shown. Worker ticks off tasks as completed. Phase 2 (MFG-UC-20) adds actual time entry per task.
IngredientGroups¶
IngredientGroups are a Manufacturing-domain concept used exclusively within recipes and BOMs for ingredient substitution. They are distinct from ItemGroups (which are an ItemCatalog organisational concept for browsing and filtering the item catalog).
MFG-UC-IG-01: Auto-Create IngredientGroup on Item Registration¶
Actor: System (triggered by ItemCreated event from ItemCatalog)
Phase: 1
When ItemCatalog publishes an ItemCreated event for an item of type ingredient or intermediate, Manufacturing automatically creates a new IngredientGroup with:
- name = item.name (editable later)
- members = [that item], with is_main = true
For other item types (product, packaging, consumable, service, resale), no IngredientGroup is created.
MFG-UC-IG-02: Manage IngredientGroup¶
Actor: Admin, Manager Phase: 1
Users can:
- Rename an IngredientGroup
- Add an additional item to a group (item must share the same storage_unit as the group's existing members — if different, a warning is shown and entry requires confirmation)
- Remove an item from a group (cannot remove if it is the only member)
- Change which item is is_main
Business rules:
1. An item (ingredient or intermediate type) belongs to exactly one IngredientGroup.
2. IngredientGroup name is unique per tenant.
3. System items belong to system-managed IngredientGroups — tenants cannot reorganise system groups.
4. Removing an item from an IngredientGroup is not permitted if the item is currently in is_main position in any Published recipe — the user must first change is_main in all affected recipes.
5. Deleting an IngredientGroup is not permitted while any recipe references it.
Business Rules¶
| # | Rule |
|---|---|
| BR-MFG-01 | Recipe cannot exist without a linked product (product_id required; must reference an item of type product or intermediate). |
| BR-MFG-02 | Recipe is immutable once in Test, Published, or Archived status. Any change requires a new version. |
| BR-MFG-03 | Multiple recipes for the same product can be simultaneously Published. One must be is_default = true. |
| BR-MFG-04 | ZP is pegged to a specific recipe version at creation — recipe change on open ZP is not allowed. |
| BR-MFG-05 | Maximum BOM depth is 5 levels. Circular references are not permitted. |
| BR-MFG-06 | All BOM quantities use the ingredient's storage_unit from ItemCatalog. No automatic unit conversion. |
| BR-MFG-07 | Allergen calculation uses ALL ingredients in each IngredientGroup (logical OR), not just is_main. Nutrition calculation uses is_main only. |
| BR-MFG-08 | RecipeNutritionSnapshot records are immutable once created. ZP history references the snapshot active at completion time. |
| BR-MFG-09 | ProductionCostRecord (actual cost) is immutable once saved. Not affected by future ingredient price changes. |
| BR-MFG-10 | Trial LOTs cannot be shipped (WZ hard blockade). |
| BR-MFG-11 | FEFO deviation during ZP execution must include a reason — logged to audit trail. |
| BR-MFG-12 | Recipe publication is blocked if any liquid ingredient in BOM lacks density_g_per_ml setting. |
| BR-MFG-13 | Multi-level BOM produce/consume decision is made by Manager at ZP creation and locked in. |
| BR-MFG-14 | Rejected Test recipe → Archived does not affect running Trial ZPs; only new Trial ZP creation blocked. |
| BR-MFG-15 | Waste entry reason is optional; excess waste triggers alert but does not require Manager approval. |
Out of Scope¶
- Item identity (code, name, type, UOM) → owned by ItemCatalog module
- Item organisational grouping (ItemGroup) → owned by ItemCatalog module
- Stock levels, LOT creation, LOT genealogy → owned by Warehouse and Traceability modules
- Sales price, product descriptions for customers → owned by CRM module
- Invoice generation → owned by Invoicing module
- Supplier management → owned by Warehouse module
- HACCP/CCP configuration and compliance logs → future Quality module (Phase 3)
Open Questions¶
| # | Question | Impact |
|---|---|---|
| OQ-MFG-01 | Resolved: Always explicit. Manager must choose produce vs consume for every intermediate item. No default. | UX/flow |
| OQ-MFG-02 | Resolved: Any Worker can register any amount of waste. No Manager sign-off required. Excess waste triggers alert only. | Process control |
| OQ-MFG-03 | Resolved: Block publication. Hard block if any liquid ingredient in BOM lacks density_g_per_ml. | Blocking |
| OQ-MFG-04 | Resolved: Not a Manufacturing concern. LOT availability and reservation logic lives in Warehouse module. Manufacturing provides UI and delegates to Warehouse APIs. | Architecture |
| OQ-MFG-05 | Resolved: Remove category field from recipe entirely. Not needed. | Removed |
| OQ-MFG-06 | Resolved: Waste reason is always optional regardless of quantity. Keep current spec. | Compliance |