Production Formulas
Overview
Production formulas define reusable bill-of-materials (BOM) recipes for manufacturing or assembly processes. Each formula specifies:
- Output product — what is being produced
- Batch quantity — how many units the formula produces per batch
- Input lines — the raw materials or components consumed
- Production run type — the nature of the process (assembly, mixing, baking, etc.)
Formulas can be "expanded" to scale all quantities for a target output, making it easy to create production runs without manually recalculating input requirements.
Architecture
The module follows hexagonal architecture:
production-formulas/
├── production-formulas.module.ts # NestJS module with DI wiring
├── domain/
│ └── production-formulas-repository.domain.ts # Port interface + domain types
├── application/
│ └── production-formulas.service.ts # Business logic / use cases
├── infrastructure/
│ └── production-formulas.repository.ts # Kysely database adapter
└── interfaces/
├── production-formulas.controller.ts # HTTP endpoints
├── dtos/
│ ├── create-production-formula.dto.ts
│ └── update-production-formula.dto.ts
└── query/
├── paginate-production-formulas.query.ts
├── get-production-formula.query.ts
└── expand-formula.query.ts
Dependency flow: Controller → Service → Repository (via injection token)
The service depends on IProductionFormulasRepository (port interface), not the concrete Kysely implementation. The module binds the concrete class via PRODUCTION_FORMULAS_REPOSITORY token.
Domain Concepts
Production Formula
| Field | Type | Description |
|---|---|---|
id | UUID | Auto-generated primary key |
businessId | UUID | Multi-tenant scope |
name | string | Human-readable formula name |
description | string? | Optional description |
outputProductId | UUID | Product being produced |
outputQuantityPerBatch | numeric | Units produced per batch |
outputUomId | UUID? | Unit of measure for output |
productionRunType | enum | Process type (see below) |
isActive | boolean | Soft-delete flag |
createdBy / updatedBy | UUID | Audit trail |
Production Formula Input
| Field | Type | Description |
|---|---|---|
id | UUID | Auto-generated primary key |
productionFormulaId | UUID | Parent formula |
productId | UUID | Input material product |
quantity | numeric | Quantity per batch |
uomId | UUID? | Unit of measure |
sortOrder | integer | Display ordering |
detail | JSON? | Extensible metadata |
Production Run Types
collection | processing | mixing | baking | packing | assembly | harvest | transfer
API Endpoints
All endpoints require Bearer authentication (Firebase ID token).
POST /production-formulas
Creates a new formula with optional input lines.
Body:
{
"businessId": "uuid",
"createdBy": "uuid",
"name": "Bicycle Assembly",
"description": "Assemble 1 bicycle from frame and wheels",
"outputProductId": "uuid",
"outputQuantityPerBatch": 1,
"productionRunType": "assembly",
"inputs": [
{ "productId": "uuid", "quantity": 1, "uomId": null, "sortOrder": 0 }
]
}
Response: 201 — Created formula with inputs.
GET /production-formulas
Lists active formulas for a business with pagination and search.
Query params: businessId (required), search, page (default 1), size (default 10)
Response: 200 — { formulas: [...], total: number }
GET /production-formulas/:id
Returns a single formula with inputs. Validates business ownership.
Query params: businessId (required)
Response: 200 — Formula with inputs | 404 — Not found | 403 — Wrong business
PATCH /production-formulas/:id
Partially updates a formula. If inputs array is provided, it replaces all existing inputs.
Query params: businessId (required)
Body: All fields optional except updatedBy. Same shape as create.
Response: 200 — Updated formula with inputs.
GET /production-formulas/:id/expand
Scales formula quantities for a target output amount. Returns inputs and outputs ready for production run creation.
Query params: businessId (required), plannedOutputQuantity (required, > 0), locationId (required)
Response: 200
{
"inputs": [
{ "productId": "uuid", "locationId": "uuid", "quantity": 10, "uomId": null, "unitCost": 0, "sortOrder": 0 }
],
"outputs": [
{ "productId": "uuid", "quantity": 10, "uomId": null, "unitCost": 0, "sortOrder": 0 }
]
}
Scale formula: scaledQuantity = inputQuantity × (plannedOutputQuantity / outputQuantityPerBatch)
Integration with Production Runs
The ProductionFormulasModule is exported and consumed by ProductionRunsModule. When creating a production run with a productionFormulaId and plannedOutputQuantity, the runs service calls:
getProductionFormulaById()— fetch and validate the formulaexpandFormulaToRunData()— scale inputs/outputs
This allows users to create runs either manually (with explicit inputs/outputs) or from a formula (auto-expanded).
Design Decisions
- Delete-and-replace for inputs on update — When updating formula inputs, all existing inputs are deleted and re-inserted. This simplifies diffing and maintains sortOrder consistency.
- Soft-delete via
isActive— Formulas are never hard-deleted. The list endpoint filtersisActive = true. - No cascading to runs — Changing a formula does not affect existing production runs. Runs snapshot their inputs/outputs at creation time.