Skip to content

Bounded Contexts

Seven bounded contexts. Each context owns its aggregates exclusively. Inter-context communication is via domain events only — no direct imports or shared repositories between modules. A module publishes events through the transactional outbox; another module subscribes by registering @on_event handlers, which the in-process dispatcher invokes after the publishing transaction commits. Kafka is deferred until a module is extracted to its own service. See conventions.md § Transactional Outbox and § Event Handlers (@on_event).

For full business rules and use cases per module: docs/business/modules/{module}.md


1. Identity

Owns: Tenant, User, Role, AuditLogEntry, AuditorToken, Subscription

Does NOT own: any business domain data; it only provides the security context

Publishes: - TenantProvisioned — new tenant registered - UserCreated — new user added - UserDeactivated — user deactivated - SubscriptionSuspended — tenant subscription suspended/cancelled

Subscribes (via @on_event): nothing from other modules

Cross-cutting: All modules enforce tenant isolation via tenant_id from the JWT. PostgreSQL RLS is configured with app.current_tenant set by the Identity middleware on every request.


2. ItemCatalog

Owns: Item (itemId, code, name, item_type, storage_unit, active, is_system, description, tags), ItemGroup (organisational grouping per item_type), ItemGroupMembership

Does NOT own: allergens, nutritional values, stock levels, prices, descriptions for customers, recipe associations, IngredientGroups (recipe substitution groups) — these are extension data owned by other modules linked by item_id

Publishes: - ItemCreated — new item registered in catalog - ItemDeactivated — item marked inactive

Subscribes (via @on_event): - Nothing (source of truth for item identity)

Key rules: - storage_unit (kg / l / piece) is immutable once set - item_type is immutable from creation — cannot be changed under any circumstances - An item may belong to at most one ItemGroup; ItemGroups are scoped to a single item_type - System items (is_system = true) are tenant-agnostic and read-only for tenants


3. Manufacturing

Owns: - Recipe (recipe_code, version, status, product_id, yield_unit, yield_quantity, BOM) - BOMLine (group_quantity, is_main, waste settings) - IngredientGroup (recipe substitution groups; auto-created on ItemCreated for ingredient/intermediate items) - IngredientGroupMembership (item ↔ group assignment, is_main flag per recipe context) - ManufacturingItemExtension (allergens, nutritional values, density, guide_price — linked by item_id) - ProductionOrder / ZP (status, recipe_version_ref, planned_quantity, planned_date) - ActualConsumption (per ZP per LOT) - ProductionCostRecord (immutable actual cost on ZP completion) - RecipeNutritionSnapshot (immutable) - TaskDefinition, TaskGroup (routing catalog) - LaborEntry (Phase 2: worker time per ZP per task)

Does NOT own: stock levels, LOT creation, stock movements — Manufacturing tells Warehouse what happened (via events); Warehouse owns the ledger

Publishes: - ZPApproved — ZP transitioned to Planned; payload includes ingredient reservations needed - ZPCancelled — reservations to be released - ZPCompleted — ZP done; production LOT created; payload includes output LOT, item_id, quantity, consumed input LOTs - ZPRequested — triggered by CRM SalesOrderConfirmed when stock shortfall detected (Phase 2) - RecipePublished — recipe version transitioned to Published - ProductionCostRecorded — actual cost saved (Phase 2)

Subscribes (via @on_event): - ItemCreated (ItemCatalog) — register default ManufacturingItemExtension record; auto-create IngredientGroup for ingredient and intermediate item types - ItemDeactivated (ItemCatalog) — warn if item is in any Draft recipe BOM - SalesOrderConfirmed (CRM) — check FG stock; emit ZPRequested if shortfall (Phase 2) - StockLevelQueried (internal — synchronous query to Warehouse for ZP creation screen)

Key rules: - Recipe cannot exist without product_id (must reference an ItemCatalog item with type product or intermediate) - Recipe is immutable once in Test, Published, or Archived status - ZP is pegged to recipe version at creation — immutable - Maximum BOM depth: 5 levels; circular references forbidden - ProductionCostRecord is immutable once saved


4. Warehouse

Owns: - StockEntry (item_id, lot_id, quantity, reserved_quantity, location) - LOT (lot_number, item_id, expiry_date, status, is_trial, purchase_price, supplier_lot) - PZ / GoodsReceipt (inbound delivery document) - WZ / ShipmentDocument (outbound delivery document) - InventorySession and InventoryCorrection - StockAdjustment - WarehouseItemExtension (storage rules, reorder levels, dimensions — linked by item_id) - Supplier, SupplierCertificate - FEFODeviationLog

Does NOT own: batch genealogy (input→output LOT links), recall management, traceability queries — those belong to Traceability module

Publishes: - StockReceived — PZ approved, new LOT created; payload: lot_id, item_id, quantity, expiry_date, purchase_price, supplier_id - StockReserved — materials reserved for an approved ZP - StockReleased — reservation released (ZP cancelled or completed) - MaterialConsumed — actual consumption registered during ZP execution; payload: lot_id, item_id, quantity, zp_id - StockDispatched — WZ approved, LOT(s) shipped; payload: lot_id, item_id, quantity, customer_id, wz_id - LotHeld — LOT placed on quarantine - LotReleased — LOT released from quarantine - StockLow — stock fell below minimum threshold

Subscribes (via @on_event): - ZPApproved (Manufacturing) — reserve materials per BOM - ZPCancelled (Manufacturing) — release reservations - ZPCompleted (Manufacturing) — record production output LOT (FG/SF), finalize actual consumption records - ItemCreated (ItemCatalog) — create default WarehouseItemExtension record - ItemDeactivated (ItemCatalog) — warn if item has non-zero stock

Key rules: - Stock quantity can never go negative - FEFO mandatory for food raw materials; deviations logged - LOT formats: GS-YYYYMMDD-NNNNN (raw materials/inbound), FG-YYYYMMDD-NNNNN (finished goods), SF-YYYYMMDD-NNNNN (semi-finished) - Purchase price on PZ line is immutable — it is the cost basis for that LOT - Held LOTs cannot be shipped; Trial LOTs cannot be shipped (no override)


5. Traceability

Owns: - BatchGenealogyLink (input_lot_id, output_lot_id, zp_id, quantity, recorded_at — immutable) - LotMovementRecord (lot_id, movement_type, quantity, counterparty, timestamp — immutable) - Recall (recall_id, root_lot_id, severity, status, affected lots list) - RecallAction (action log entries per recall)

Does NOT own: stock quantities, LOT creation — only reads events from Warehouse and Manufacturing to build the genealogy graph

Publishes: - RecallInitiated — recall started; payload: recall_id, root_lot_id, affected_lot_ids, severity - RecallClosed — recall completed

Subscribes (via @on_event): - StockReceived (Warehouse) — record LOT entry event - MaterialConsumed (Warehouse) — record input LOT usage against ZP - ZPCompleted (Manufacturing) — record input→output LOT genealogy links - StockDispatched (Warehouse) — record LOT dispatch to customer

Key rules: - All genealogy records are immutable once written - Forward/backward trace query response: < 5 seconds - Maximum backward trace depth: 10 BOM levels - Recalls cannot be deleted, only progressed through lifecycle


6. CRM

Owns: - Customer (customer_id, name, NIP, addresses, contacts) - SalesOrder / ZS (status, lines with item_id, quantity, price_snapshot) - Complaint (linked to lot_id, customer_id, wz_id) - CRMItemExtension (selling_price, vat_rate, pricing_tiers, description, images, orderable — linked by item_id) - APIKey, WebhookConfig

Does NOT own: production orders, stock levels, invoice documents — CRM triggers them via events and reads status updates

Publishes: - SalesOrderConfirmed — ZS confirmed; payload: zs_id, lines (item_id, quantity, required_by_date) - SalesOrderCancelled - SalesOrderFulfilled — all lines shipped; triggers Invoicing (Phase 2) - ComplaintCreated — complaint with LOT reference (QA may trigger recall)

Subscribes (via @on_event): - ZPCompleted (Manufacturing) — update ZS status toward Ready - StockDispatched (Warehouse) — update ZS line to Shipped; trigger SalesOrderFulfilled if all lines shipped - StockLow (Warehouse) — surface as alert in CRM stock availability view - RecallInitiated (Traceability) — surface recall flag on affected customer orders (Phase 2) - ItemCreated (ItemCatalog) — create default CRMItemExtension record - ItemDeactivated (ItemCatalog) — set orderable = false

Key rules: - ZS price per line is snapshoted at creation — immutable after ZS confirmation - Only product and resale items are orderable (can appear in ZS) - Auto-ZP trigger is via event (ZPRequested), not a direct Manufacturing call - REST API (Must Have, Phase 2): OpenAPI 3.0, 100 req/min rate limit, API key auth


7. Invoicing

Owns: - Invoice / FA (invoice_id, zs_ref, customer_snapshot, lines, vat_breakdown, payment_status) - CreditNote / KOR (linked to invoice_id) - PaymentRecord

Does NOT own: sales order lifecycle, customer data (snapshots it at invoice time)

Publishes: - InvoiceIssued — invoice created and immutable - InvoicePaid — payment status reached Paid

Subscribes (via @on_event): - SalesOrderFulfilled (CRM) — surface as "ready to invoice" notification for accountant (does not auto-create invoice) - CustomerUpdated (CRM) — no effect on issued invoices (customer data is snapshoted)

Key rules: - Invoice is immutable once issued; corrections require a credit note - Phase 2: basic PDF invoice generation - Phase 4: KSeF (e-invoicing) submission (async, does not block local invoice issuance) - VAT rates (5% / 8% / 23%) are read from CRMItemExtension per line item


Event Communication Map

ItemCatalog ──ItemCreated/Deactivated──────────────────► Manufacturing
                                                        ► Warehouse
                                                        ► CRM

Manufacturing ──ZPApproved────────────────────────────► Warehouse
              ──ZPCancelled──────────────────────────► Warehouse
              ──ZPCompleted────────────────────────────► Warehouse
                                                       ► Traceability

Warehouse ──StockReceived───────────────────────────► Traceability
          ──MaterialConsumed────────────────────────► Traceability
          ──StockDispatched───────────────────────────► Traceability
                                                      ► CRM
          ──StockLow────────────────────────────────► CRM
          ──ZPApproved response (StockReserved)──────► (internal)

CRM ──SalesOrderConfirmed──────────────────────────► Manufacturing
    ──SalesOrderFulfilled──────────────────────────► Invoicing

Traceability ──RecallInitiated─────────────────────► CRM (Phase 2)

Identity ──(cross-cutting RLS context, not events)──► All modules