dodil/erp-brick-manufacturing

DODIL ERP brick: manufacturing

src/brick_manufacturing/application/process.yaml

# The process this brick serves: manufacturing version 1, SIGNED 2026-10-25 (erp-method signoff/manufacturing/). Copied from the spec's
# actors and workflows, step ids frozen (Task_<key>): tests/test_process.py holds it equal to the spec AND the BPMN it draws equal to
# the signed files' hashes. A change goes through `erp-compose change propose` and a new signature first, then here.
id: manufacturing
name: Production (bills of materials and production orders)
version: 1
actors:
- id: process_engineer
  name: Process engineer / head roaster
  does: writes bills of materials and their versions, routings and work centres, the planned yield
  sees: every BOM, routing and work centre
  permissions:
  - mfg:bom:read
  - mfg:bom:write
  - mfg:bom:release
  - mfg:routing:write
  - mfg:work_centre:write
  - mfg:order:read
- id: planner
  name: Production planner
  does: runs MRP-lite, firms planned orders, creates and releases production orders, enters forecasts
  sees: every plan and order
  permissions:
  - mfg:bom:read
  - mfg:plan:run
  - mfg:plan:firm
  - mfg:forecast:write
  - mfg:order:read
  - mfg:order:create
  - mfg:order:release
  - mfg:order:cancel
- id: operator
  name: Roaster / production operator
  does: issues components, records operation times, reports output, by-products and scrap with a reason, reverses their own wrong entries the
    same day
  sees: released orders of their work centres
  permissions:
  - mfg:bom:read
  - mfg:order:read
  - mfg:order:issue
  - mfg:order:confirm
  - mfg:order:report_output
  - mfg:order:scrap
  - mfg:order:reverse
- id: cost_accountant
  name: Cost accountant
  does: runs and approves cost roll-ups, releases standard costs to inventory, reviews and closes orders, explains variances
  sees: every order's cost
  permissions:
  - mfg:bom:read
  - mfg:order:read
  - mfg:cost:run
  - mfg:cost:approve
  - mfg:order:complete
  - mfg:order:close
  - mfg:report:read
  - mfg:migration:read
  note: 'never approves a roll-up they ran, nor closes an order whose variance approval they asked for: email AND sub'
- id: quality
  name: Quality / food safety lead
  does: blocks a batch, runs a trace back to the ingredients and forward to the customers and shops
  sees: every lot's genealogy
  permissions:
  - mfg:bom:read
  - mfg:order:read
  - mfg:trace:read
- id: owner
  name: The owner (or whoever the owner names)
  does: holds the controls (over-issue tolerance, release with shortages, variance approval threshold, AI step modes), signs the migration reconciliation
  sees: everything
  permissions:
  - mfg:order:read
  - mfg:report:read
  - mfg:settings:controls
  - mfg:migration:read
  - mfg:migration:sign
  note: anyone else holding mfg:settings:controls is a configuration error the access test refuses
- id: system
  name: The system
  does: runs MRP-lite on request or from the worker, explodes BOMs for pos and the planner, sends issues and receipts to inventory and postings
    to gl from pending rows, marks plans, roll-ups and unreleased orders stale, derives genealogy, loads an Odoo migration
  sees: everything, through its own service account
  permissions:
  - mfg:bom:explode
  - mfg:plan:read_requirements
  - mfg:plan:derive
  - mfg:outbox:dispatch
  - mfg:genealogy:derive
  - mfg:number_series:claim
  - mfg:migration:load
  - mfg:migration:reconcile
  note: exact grants to manufacturing's service client, never a wildcard; mfg:bom:explode is also granted to pos's client and mfg:plan:read_requirements
    to inventory's; the client holds product:product:read, inv:availability:read, inv:valuation:read, inv:stock:read, gl:journal:post, gl:cost_centre:read,
    gl:period:read
workflows:
- entity: bom_version
  states:
  - draft
  - released
  - obsolete
  terminal:
  - obsolete
  transitions:
  - key: write_bom
    name: Write a BOM version
    from: none
    to: draft
    by: process_engineer
    gate: quantities convert exactly; no cycle (graph walk); components stocked or phantom
  - key: release_bom
    name: Release the version
    from: draft
    to: released
    by: process_engineer
    gate: valid_from after today; no overlap with another released version of the BOM
    effect: cost roll-ups and unreleased orders using the BOM marked stale (MFG-18)
  - key: obsolete_bom
    name: Make it obsolete
    from: released
    to: obsolete
    by: process_engineer
    requires: valid_to
    gate: no unreleased order or firmed plan names it after valid_to
- entity: production_order
  states:
  - created
  - released
  - in_progress
  - completed
  - closed
  - cancelled
  terminal:
  - closed
  - cancelled
  transitions:
  - key: create_order
    name: Create the order
    from: none
    to: created
    by:
    - planner
    - system
    gate: a released BOM and routing valid on the due date
    effect: production_order_id claimed from the company's yearly series
  - key: release_order
    name: Release the order
    from: created
    to: released
    by: planner
    gate: components available (or release_with_shortage); BOM and routing valid on the release date
    effect: BOM and routing copied onto the order and frozen
  - key: issue_components
    name: Issue components
    from:
    - released
    - in_progress
    to: in_progress
    by: operator
    gate: within over_issue_tolerance; the lot available
    effect: inventory issue from a pending row (MFG-11)
  - key: confirm_operation
    name: Confirm an operation
    from:
    - released
    - in_progress
    to: in_progress
    by: operator
    effect: activity journal from a pending row (MFG-12)
  - key: report_output
    name: Report output
    from:
    - released
    - in_progress
    to: in_progress
    by: operator
    gate: at most 110% of plan unless confirmed
    effect: inventory receipt of the output lot and by-products (MFG-13)
  - key: reverse_entry
    name: Reverse a wrong entry
    from: in_progress
    to: in_progress
    by: operator
    requires: reason
    gate: the entry not reversed already; the operator's own entry on the same business date, else a planner
    effect: a mirror movement with its own ref (…:reverse)
  - key: complete_order
    name: Technically complete
    from: in_progress
    to: completed
    by:
    - operator
    - cost_accountant
    effect: no further issues or outputs; costs may still arrive
  - key: close_order
    name: Close and settle
    from: completed
    to: closed
    by: cost_accountant
    gate: every pending outbox row of the order sent; a variance above variance_approval_threshold approved by another person (email AND sub)
    effect: scrap and variances posted, WIP 0.00 (MFG-15)
  - key: cancel_order
    name: Cancel
    from:
    - created
    - released
    to: cancelled
    by: planner
    requires: reason
    gate: no movement or confirmation recorded
  readiness:
  - transition: release_order
    gate: components_available
    reason: 'short: named with quantities'
    fix: wait for the receipt, or the owner allows release with shortages
  - transition: release_order
    gate: bom_valid
    reason: no released BOM version valid on the date
    fix: release a version
  - transition: close_order
    gate: outbox_empty
    reason: 'movements not yet confirmed by inventory or gl: named'
    fix: wait, or retry the failed ones
  - transition: close_order
    gate: variance_approved
    reason: the variance {amount} is above {threshold}
    fix: another cost accountant approves it
  note: a completed order never returns to in_progress; a cost that arrives after close is a post-close settlement (settle:n)
- entity: planned_order
  states:
  - proposed
  - firmed
  - converted
  - superseded
  terminal:
  - converted
  - superseded
  transitions:
  - key: propose_planned_order
    name: Proposed by MRP-lite
    from: none
    to: proposed
    by: system
  - key: firm_planned_order
    name: Firm
    from: proposed
    to: firmed
    by: planner
    effect: kept by later runs
  - key: convert_planned_order
    name: Convert to a production order
    from:
    - proposed
    - firmed
    to: converted
    by: planner
    effect: production order created (MFG-09)
  - key: supersede_planned_order
    name: Superseded by a later run
    from: proposed
    to: superseded
    by: system
- entity: cost_rollup
  states:
  - computed
  - approved
  - released
  - stale
  terminal:
  - released
  - stale
  transitions:
  - key: run_rollup
    name: Run the roll-up
    from: none
    to: computed
    by: cost_accountant
    gate: every component has a standard price on the effective date
  - key: approve_rollup
    name: Approve
    from: computed
    to: approved
    by: cost_accountant
    approval: true
    gate: the approver did not run it (email AND sub)
  - key: release_rollup
    name: Release to inventory
    from: approved
    to: released
    by: cost_accountant
    gate: not stale; effective date after today
    effect: standard prices handed to inventory from pending rows
  - key: mark_rollup_stale
    name: Stale
    from:
    - computed
    - approved
    to: stale
    by: system
    gate: an input changed (MFG-18)
  readiness:
  - transition: approve_rollup
    gate: second_person
    reason: you ran this roll-up
    fix: ask another cost accountant
- entity: mfg_migration_run
  states:
  - loaded
  - reconciled
  - signed
  - discarded
  terminal:
  - signed
  - discarded
  transitions:
  - key: load_mfg_from_odoo
    name: Load Odoo's manufacturing
    from: none
    to: loaded
    by: system
    gate: product and inventory migrations signed
  - key: reconcile_mfg_migration
    name: Reconcile against Odoo
    from: loaded
    to: reconciled
    by: system
  - key: sign_mfg_reconciliation
    name: Sign the reconciliation
    from: reconciled
    to: signed
    by: owner
    approval: true
    gate: every check equal or explained
  - key: discard_mfg_migration
    name: Discard the run
    from:
    - loaded
    - reconciled
    to: discarded
    by: owner
    requires: reason