انتقل إلى المحتوى الرئيسي

Bill of Materials (BOM) Domain

Overview

The Bill of Materials (BOM) domain manages the structural relationships between parent items and their component items, defining the "recipes" used in manufacturing and assembly operations. It provides the foundation for material planning, component explosion, assembly transactions, and production costing.

Core Concepts

Bill of Materials (BOM)

A BOM is a formal definition of the components required to produce one unit of a parent item. It answers the question: "What do I need to make this product?"

Key Characteristics:

  • Defines parent-child item relationships
  • Specifies exact quantities of each component
  • Associates with specific unit of measure for output
  • Used for material requirements planning
  • Drives assembly transaction validation

Component Explosion

Component explosion is the process of calculating total component requirements based on desired production quantity:

BOM Definition:
Parent: Widget (1 EA)
├── Steel Plate: 2.5 kg
├── Bolt M10: 4 EA
└── Paint: 0.1 L

Production Order: 10 Widgets

Exploded Requirements:
├── Steel Plate: 25 kg (2.5 × 10)
├── Bolt M10: 40 EA (4 × 10)
└── Paint: 1.0 L (0.1 × 10)

Single-Level vs Multi-Level BOMs

Current Implementation: Single-Level BOMs

  • Parent item has components
  • Components are leaf items (not further expanded)
  • Sufficient for most manufacturing scenarios

Future Enhancement: Multi-Level BOMs

  • Components can themselves have BOMs (sub-assemblies)
  • Recursive explosion through entire product structure
  • Full manufacturing hierarchy support

Domain Structure

Aggregates

bom/
├── aggregates/
│ └── bill-of-material.aggregate.md # BOM aggregate documentation

Entities

bom/
└── entities/
├── bill-of-material.md # BOM entity (aggregate root)
└── bill-of-material-line.md # Component line entity

Value Objects

bom/
└── value-objects/
└── required-component.md # Exploded component requirement

Key Aggregates

BillOfMaterial

Aggregate Root: BillOfMaterial

Purpose: Defines the component structure and quantities required to produce a parent item.

Key Characteristics:

  • Associates with one parent item (finished good/assembly)
  • Specifies produced unit of measure
  • Contains collection of component lines
  • Supports component explosion calculations
  • Validates component integrity

Relationships:

  • References one parent Item (required)
  • References one UnitOfMeasure for produced output (required)
  • Contains many BillOfMaterialLine entities (1 to many)
  • Each line references one component Item
  • Each line references one component UnitOfMeasure

Core Operations:

  • Create BOM for parent item
  • Add component items with quantities
  • Update BOM header information
  • Sync components (add/update/remove)
  • Explode components for production quantity
  • Validate component relationships

BillOfMaterialLine

Entity: Component line within a BOM

Purpose: Represents a single component requirement within the BOM.

Key Characteristics:

  • Immutable after creation
  • Defines component item and quantity
  • Specifies unit of measure for component
  • Part of parent BOM aggregate

Properties:

  • ComponentItemId: Reference to component item
  • Quantity: Amount required per unit of parent
  • UnitOfMeasureId: Unit for component quantity

Business Rules

BOM Creation Rules

  1. Parent Item Required: BOM must be associated with a valid parent item
  2. Produced Unit Required: BOM must specify which unit of measure is produced
  3. Name Required: BOM must have a descriptive name
  4. Description Optional: Additional details can be provided

Component Rules

  1. No Circular References: Parent item cannot be its own component
  2. Unique Components: Each component item can appear only once in a BOM
  3. Positive Quantities: Component quantities must be greater than zero
  4. Valid Units: Component units must be valid for the component item
  5. Component Item Required: Each line must reference a valid item

Explosion Rules

  1. Positive Production Quantity: Cannot explode for zero or negative quantity
  2. Empty BOM Handling: BOMs with no components return empty explosion
  3. Quantity Multiplication: Component quantity × production quantity = required quantity
  4. Unit Preservation: Exploded components retain their specified units

Update Rules

  1. Header Updates: Name, description, and produced unit can be updated
  2. Component Sync: SyncComponents() method handles add/update/remove in single operation
  3. Validation Consistency: All component validations apply during updates
  4. Immutable Lines: Lines are replaced rather than modified (immutable pattern)

Integration Points

With Item Module

  • Parent Item Validation: Verifies parent item exists and is valid
  • Component Item Validation: Verifies component items exist and are stockable
  • Unit Class Compatibility: Validates units match item's unit class
  • Stockable Requirement: Typically both parent and components are stockable items

With Transaction Module (Assembly)

  • Assembly Validation: Assembly transactions validate BOM exists for parent item
  • Component Verification: Verifies required components are available
  • Quantity Explosion: Calculates component requirements for assembly quantity
  • Inventory Consumption: Assembly transactions consume components per BOM
  • Production Output: Assembly transactions produce parent item per BOM

With Inventory Module

  • Availability Checking: Verifies sufficient component inventory before assembly
  • Component Reservation: Future enhancement for reserving components
  • Multi-Location Support: Components can be sourced from multiple locations
  • Backflush Processing: Future enhancement for automated component consumption

With Finance Module (Future)

  • Standard Costing: BOM used to calculate standard product costs
  • Variance Analysis: Actual vs standard component usage
  • COGS Calculation: Component costs roll up to finished goods cost
  • Material Overhead: BOM quantities drive overhead allocations

Common Patterns

Creating a Simple BOM

// 1. Create BOM header
var bom = BillOfMaterial.Create(
parentItemId: finishedWidgetId,
producedUnitOfMeasureId: eachUnitId,
name: "Standard Widget Assembly",
description: "Primary assembly process for standard widget production");

// 2. Add component items
bom.AddComponent(
componentItemId: steelPlateItemId,
quantity: 2.5m,
unitOfMeasureId: kilogramUnitId);

bom.AddComponent(
componentItemId: boltItemId,
quantity: 4m,
unitOfMeasureId: eachUnitId);

bom.AddComponent(
componentItemId: paintItemId,
quantity: 0.1m,
unitOfMeasureId: literUnitId);

// 3. Save BOM
await bomRepository.AddAsync(bom);
await unitOfWork.CommitAsync();

Component Explosion for Production

// 1. Load BOM
var bom = await bomRepository.GetByParentItemAsync(widgetItemId);

// 2. Explode for production quantity
decimal productionQuantity = 100m; // Produce 100 widgets
var requiredComponents = bom.Explode(productionQuantity);

// 3. Process exploded requirements
foreach (var component in requiredComponents)
{
Console.WriteLine($"Need {component.Quantity} {component.UnitOfMeasureId} " +
$"of item {component.ComponentItemId}");

// Check inventory availability
var available = await inventoryService
.GetAvailableQuantityAsync(component.ComponentItemId, locationId);

if (available < component.Quantity)
{
throw new InsufficientInventoryException(
$"Insufficient inventory for component {component.ComponentItemId}");
}
}

// Result for 100 widgets:
// - Steel Plate: 250 kg (2.5 × 100)
// - Bolt M10: 400 EA (4 × 100)
// - Paint: 10 L (0.1 × 100)

Updating BOM Components (Sync Pattern)

// 1. Load existing BOM
var bom = await bomRepository.GetByIdAsync(bomId);

// 2. Define desired component state
var desiredComponents = new[]
{
(steelPlateItemId, 3.0m, kilogramUnitId), // Updated quantity
(boltItemId, 4m, eachUnitId), // Unchanged
(paintItemId, 0.15m, literUnitId), // Updated quantity
(newLabelItemId, 1m, eachUnitId) // New component
// Old component removed (not in desired state)
};

// 3. Sync components in single operation
bom.SyncComponents(desiredComponents);

// 4. Save changes
await unitOfWork.CommitAsync();

// Result:
// - Updates existing components with new quantities
// - Adds new components
// - Removes components not in desired state
// - All in single atomic operation

Updating BOM Header

var bom = await bomRepository.GetByIdAsync(bomId);

bom.UpdateHeader(
name: "Updated Widget Assembly - Version 2",
description: "Revised assembly process with efficiency improvements",
producedUnitOfMeasureId: null); // Keep existing unit

await unitOfWork.CommitAsync();

Assembly Transaction Using BOM

// 1. Load BOM for assembly
var bom = await bomRepository.GetByParentItemAsync(parentItemId);

// 2. Explode for desired production quantity
var componentsNeeded = bom.Explode(assemblyQuantity);

// 3. Create assembly transaction
var transaction = InventoryTransaction.CreateAssembly(
transactionDate: DateTime.UtcNow,
reason: "Production run #1234",
billOfMaterialId: bom.Id,
producedItemId: parentItemId,
producedQuantity: assemblyQuantity,
producedUnitOfMeasureId: bom.ProducedUnitOfMeasureId,
assemblyLocationId: productionLocationId);

// 4. Add transaction lines for each component
foreach (var component in componentsNeeded)
{
transaction.AddLineItem(
itemId: component.ComponentItemId,
quantity: component.Quantity,
unitOfMeasureId: component.UnitOfMeasureId);
}

// 5. Process transaction
await transactionRepository.AddAsync(transaction);
await unitOfWork.CommitAsync();

// Result:
// - Components consumed from inventory
// - Finished goods added to inventory
// - Complete audit trail maintained

Repository Pattern

IBillOfMaterialRepository

Query Methods:

  • GetByIdAsync(Guid id): Get BOM with all lines and related items
  • GetAllAsync(): Get all active BOMs
  • GetByParentItemAsync(Guid parentItemId): Find BOM for specific parent item
  • GetByComponentItemAsync(Guid componentItemId): Find BOMs using specific component

Specialized Query Methods:

  • GetBOMsWithComponentsAsync(): Get BOMs with full component details loaded
  • ExistsByParentItemAsync(Guid parentItemId): Check if parent item has BOM
  • GetParentItemsWithBOMsAsync(): Get list of items that have BOMs defined

Validation Methods:

  • HasBOMAsync(Guid parentItemId): Verify BOM exists for item
  • IsComponentUsedAsync(Guid componentItemId): Check if item is used as component
  • CanDeleteBOMAsync(Guid bomId): Validate BOM can be safely deleted

Command Methods:

  • AddAsync(BillOfMaterial bom): Create new BOM
  • Update(BillOfMaterial bom): Update existing BOM
  • Archive(BillOfMaterial bom): Soft delete BOM
  • UnArchive(BillOfMaterial bom): Restore archived BOM

Domain Services

IBOMService (Future Enhancement)

Purpose: Coordinates complex BOM operations and multi-level explosions.

Planned Methods:

Multi-Level Explosion:

Task<IEnumerable<RequiredComponent>> ExplodeMultiLevelAsync(Guid parentItemId, decimal quantity)
  • Recursively explodes BOMs through entire product structure
  • Handles sub-assemblies automatically
  • Aggregates component requirements across levels

Validation Services:

Task<ValidationResult> ValidateBOMIntegrityAsync(Guid bomId)
  • Validates all components exist and are stockable
  • Checks for circular references in multi-level BOMs
  • Verifies unit compatibility

Cost Roll-Up:

Task<decimal> CalculateStandardCostAsync(Guid parentItemId)
  • Calculates total component cost
  • Includes material overhead
  • Supports standard costing

Testing Strategy

Unit Tests

  • Factory Method Tests: BOM creation with various configurations
  • Component Addition Tests: Add components with validation
  • Explosion Tests: Verify explosion calculations for various quantities
  • Circular Reference Tests: Prevent parent as component
  • Duplicate Component Tests: Prevent same component twice
  • Sync Tests: Verify add/update/remove logic in SyncComponents

Integration Tests

  • Repository Tests: Database operations and query correctness
  • Line Persistence: BOM lines correctly saved and loaded
  • Item Relationships: Parent and component item navigation
  • Explosion Integration: Full BOM explosion with database data
  • Transaction Integration: Assembly transactions use BOM correctly

Assembly Integration Tests

  • BOM-Based Assembly: Create assembly transaction using BOM
  • Component Validation: Verify component availability
  • Inventory Updates: Verify inventory correctly updated
  • Multi-Component Assembly: Test BOMs with multiple components

Performance Considerations

Optimization Strategies

  • Eager Loading: Load BOM lines and items in single query
  • Explosion Caching: Cache exploded results for repeated assemblies
  • Query Optimization: Indexed queries on parent and component items
  • Component Lookup: Efficient component existence checks

Scalability Metrics

  • BOM Explosion: <10ms for single-level BOM with 10 components
  • Multi-Level Explosion: <50ms for 3-level BOM (future)
  • Component Search: <5ms to find BOMs using component
  • Memory Footprint: ~1KB per BOM + 200 bytes per line

Database Optimization

  • Primary Keys: Clustered index on BOM Id
  • Foreign Keys: Indexed on ParentItemId, ComponentItemId
  • Line Queries: Composite index on BOMId + ComponentItemId
  • Explosion Support: Optimized for read-heavy explosion operations

Future Enhancements

Planned Features

  1. Multi-Level BOMs: Recursive component explosion through sub-assemblies
  2. BOM Versions: Version control for BOM changes over time
  3. Effective Dating: Start/end dates for BOM validity
  4. Alternate BOMs: Multiple BOMs for same parent item (different production methods)
  5. Co-Products: Support for processes that produce multiple outputs
  6. By-Products: Handle processes with secondary outputs
  7. Scrap Factor: Built-in scrap/waste calculations
  8. Operation Routing: Link BOMs to production routings
  9. Substitutions: Define component substitutes

Multi-Level BOM Design

// Future: Recursive explosion
public class BOMService
{
public async Task<IEnumerable<RequiredComponent>> ExplodeMultiLevelAsync(
Guid parentItemId,
decimal quantity,
int maxDepth = 10)
{
var components = new List<RequiredComponent>();
await ExplodeRecursiveAsync(parentItemId, quantity, components, 0, maxDepth);

// Aggregate duplicate components
return components
.GroupBy(c => new { c.ComponentItemId, c.UnitOfMeasureId })
.Select(g => new RequiredComponent(
g.Key.ComponentItemId,
g.Sum(c => c.Quantity),
g.Key.UnitOfMeasureId));
}

private async Task ExplodeRecursiveAsync(...)
{
// Recursive explosion logic
}
}

BOM Versioning Design

// Future: Version control
public class BillOfMaterial
{
public int Version { get; private set; }
public DateTime EffectiveFrom { get; private set; }
public DateTime? EffectiveTo { get; private set; }
public bool IsActive { get; private set; }

public BillOfMaterial CreateNewVersion(string reason)
{
// Creates new version, marks current as inactive
}
}

Exceptions

BOMCircularReferenceException

Purpose: Thrown when attempting to add parent item as its own component.

Usage:

throw new BOMCircularReferenceException(
"A BOM's parent item cannot be one of its own components.");

Prevention: Validate componentItemId ≠ parentItemId before adding component.

Domain Documentation

API Documentation

  • BOM API - REST endpoints for BOM management

Concept Documentation

Architecture Documentation


Last Updated: 2025-10-24 | Status: Active Development | Version: 1.0