Skip to content

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:

  1. Materials: Σ(group_quantity × main_ingredient.guide_price per group)
  2. Waste: Σ(waste_value × main_ingredient.guide_price per BOM line where waste_type = percentage or fixed_amount)
  3. 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.
  4. Other costs: Named fixed cost entries per recipe — each entry has a name (free text, e.g. "Packaging", "Line Setup") and amount_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: DraftTestPublishedArchived

  • 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: DraftPlannedInProgressCompleted | 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 (ZPCancelled event 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