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

Measurements Domain

Overview

The Measurements domain manages units of measure, unit classes, measurement systems, and unit conversions for the inventory system. It provides the foundational measurement infrastructure that enables flexible quantity tracking, unit conversions, and multi-system support (Metric, Imperial) across all inventory operations.

Core Concepts

Three-Tier Measurement Architecture

The Measurements domain uses a three-tier structure for maximum flexibility:

Measurement System (Metric, Imperial, Custom)

Unit Class (Weight, Length, Volume, Count, Time)

Unit of Measure (kg, lb, m, ft, L, gal, EA, hr)

Measurement System

Purpose: Top-level categorization of units (Metric vs Imperial vs Custom)

Examples:

  • Metric: Kilograms, meters, liters
  • Imperial: Pounds, feet, gallons
  • Custom: Business-specific units (pallets, cases, boxes)

Unit Class

Purpose: Categorizes units by what they measure (dimension of measurement)

Examples:

  • Weight: kg, lb, g, oz, ton
  • Length: m, ft, cm, in, km
  • Volume: L, gal, mL, fl oz, m³
  • Count: Each, Dozen, Pair
  • Time: Hour, Day, Week

Key Feature: Items reference UnitClass (not specific units), allowing transaction-time unit flexibility.

Unit of Measure

Purpose: Specific measurement unit with symbol, name, and precision

Properties:

  • Symbol: Short code (kg, lb, m)
  • Name: Full name (Kilogram, Pound, Meter)
  • Decimal Precision: Number of decimal places (0-10)
  • Unit Class: Which class it belongs to
  • Measurement System: Which system it belongs to

Unit Conversions

Purpose: Define conversion factors between units in the same class

Structure: Direct conversions between unit pairs

  • kg → lb: 2.20462
  • lb → kg: 0.453592
  • m → ft: 3.28084
  • ft → m: 0.3048

Business Rules:

  • Only convert within same unit class
  • Bidirectional conversions typically both defined
  • Conversion factors can be updated
  • No conversion to self

Domain Structure

Aggregates

measurements/
├── aggregates/
│ ├── unit-of-measure.aggregate.md # UnitOfMeasure aggregate
│ ├── unit-class.aggregate.md # UnitClass aggregate
│ └── measurement-system.aggregate.md # MeasurementSystem aggregate

Entities

measurements/
├── entities/
│ ├── unit-of-measure.md # Unit entity
│ ├── unit-class.md # Class entity
│ ├── unit-conversion.md # Conversion entity
│ └── measurement-system.md # System entity

Key Aggregates

UnitOfMeasure

Aggregate Root: UnitOfMeasure

Purpose: Represents a specific unit of measurement with conversion capabilities.

Key Characteristics:

  • Has unique symbol (kg, lb, m)
  • Belongs to one UnitClass
  • Belongs to one MeasurementSystem
  • Contains conversion collection
  • Defines decimal precision

Relationships:

  • Belongs to one UnitClass
  • Belongs to one MeasurementSystem
  • Has many UnitConversion instances (from this unit)
  • Referenced by many Items, Transactions, BOM lines

Core Operations:

  • Create unit with class and system
  • Update unit properties
  • Add conversion to another unit
  • Remove conversion
  • Update conversion factor
  • Get conversion to specific unit

UnitClass

Entity: UnitClass

Purpose: Categorizes units by dimension of measurement.

Key Characteristics:

  • Groups related units (all weight units, all length units, etc.)
  • Defines base unit for the class
  • Defines preferred display unit
  • Contains collection of units in class

Core Operations:

  • Create unit class
  • Set base unit of measure
  • Set preferred display unit
  • Update class information

MeasurementSystem

Entity: MeasurementSystem

Purpose: Top-level classification (Metric, Imperial, Custom).

Key Characteristics:

  • Organizational categorization
  • Supports multiple measurement systems
  • Contains collection of units in system

UnitConversion

Entity: UnitConversion

Purpose: Defines conversion factor between two units in same class.

Key Properties:

  • FromUnitId: Source unit
  • ToUnitId: Target unit
  • Factor: Multiplication factor for conversion
  • UnitClassId: Must be same class for both units

Conversion Formula:

Target Quantity = Source Quantity × Factor

Example:

10 kg × 2.20462 = 22.0462 lb

Business Rules

Unit Creation Rules

  1. Symbol Required: Unit symbol cannot be empty
  2. Name Required: Unit name cannot be empty
  3. Unit Class Required: Every unit must belong to a class
  4. Measurement System Required: Every unit must belong to a system
  5. Precision Range: Decimal precision must be 0-10
  6. Symbol Uniqueness: Symbols should be unique (validated at app level)

Unit Class Rules

  1. Name Required: Unit class name cannot be empty
  2. Base Unit: Base unit must belong to the class
  3. Preferred Unit: Preferred display unit must belong to the class
  4. Unit Collection: Class maintains collection of its units

Conversion Rules

  1. Same Class Only: Can only convert between units in same class
  2. No Self-Conversion: Cannot create conversion from unit to itself
  3. Positive Factors: Conversion factors must be positive
  4. Bidirectional: Typically define both directions (kg→lb and lb→kg)
  5. Update Support: Conversion factors can be updated

Precision Rules

  1. Range: Decimal precision 0-10
  2. Application: Applied when displaying quantities
  3. Rounding: Quantities rounded to specified precision
  4. Storage: Full precision stored, rounded on display

Integration Points

With Item Module

  • Unit Class Reference: Items reference UnitClass (not specific unit)
  • Transaction Flexibility: Allows any unit in class for transactions
  • Default Unit: Items can specify preferred unit class
  • Validation: Ensures transaction units match item's unit class

With Transaction Module

  • Unit Specification: Every transaction line specifies unit
  • Unit Validation: Validates unit matches item's unit class
  • Conversion Support: Future support for automatic conversions
  • Precision Application: Quantities rounded per unit precision

With BOM Module

  • Component Units: BOM lines specify component units
  • Produced Unit: BOMs specify produced unit
  • Explosion: Units preserved through explosion
  • Unit Compatibility: Validates units match item classes

With Inventory Module

  • Quantity Storage: Inventory quantities stored with units
  • Unit Consistency: Tracks quantities in specified units
  • Conversion Queries: Future support for unit-converted queries
  • Display Preferences: Respects preferred display units

Common Patterns

Creating Measurement Infrastructure

// 1. Create measurement systems
var metric = MeasurementSystem.Create("Metric",
"International System of Units (SI)");
var imperial = MeasurementSystem.Create("Imperial",
"Imperial/US customary units");

// 2. Create unit classes
var weightClass = UnitClass.Create("Weight",
"Units of mass/weight");
var lengthClass = UnitClass.Create("Length",
"Units of distance/length");
var countClass = UnitClass.Create("Count",
"Counting units");

// 3. Save systems and classes
await measurementSystemRepo.AddAsync(metric);
await measurementSystemRepo.AddAsync(imperial);
await unitClassRepo.AddAsync(weightClass);
await unitClassRepo.AddAsync(lengthClass);
await unitClassRepo.AddAsync(countClass);
await unitOfWork.CommitAsync();

Creating Units of Measure

// Create metric weight units
var kilogram = UnitOfMeasure.Create(
symbol: "kg",
name: "Kilogram",
unitClass: weightClass,
measurementSystem: metric,
decimalPrecision: 3);

var gram = UnitOfMeasure.Create(
symbol: "g",
name: "Gram",
unitClass: weightClass,
measurementSystem: metric,
decimalPrecision: 2);

// Create imperial weight units
var pound = UnitOfMeasure.Create(
symbol: "lb",
name: "Pound",
unitClass: weightClass,
measurementSystem: imperial,
decimalPrecision: 2);

var ounce = UnitOfMeasure.Create(
symbol: "oz",
name: "Ounce",
unitClass: weightClass,
measurementSystem: imperial,
decimalPrecision: 2);

// Create count unit (system-agnostic)
var each = UnitOfMeasure.Create(
symbol: "EA",
name: "Each",
unitClass: countClass,
measurementSystem: metric, // Or custom system
decimalPrecision: 0);

await unitRepo.AddAsync(kilogram);
await unitRepo.AddAsync(gram);
await unitRepo.AddAsync(pound);
await unitRepo.AddAsync(ounce);
await unitRepo.AddAsync(each);
await unitOfWork.CommitAsync();

Setting Up Unit Class Defaults

// Set base unit for weight class (kilogram)
weightClass.SetBaseUnitOfMeasure(kilogram);

// Set preferred display unit (kilogram)
weightClass.SetPreferredDisplayUnit(kilogram);

await unitOfWork.CommitAsync();

Creating Bidirectional Conversions

// kg ↔ lb conversions
kilogram.AddConversion(pound, 2.20462m); // 1 kg = 2.20462 lb
pound.AddConversion(kilogram, 0.453592m); // 1 lb = 0.453592 kg

// kg ↔ g conversions
kilogram.AddConversion(gram, 1000m); // 1 kg = 1000 g
gram.AddConversion(kilogram, 0.001m); // 1 g = 0.001 kg

// lb ↔ oz conversions
pound.AddConversion(ounce, 16m); // 1 lb = 16 oz
ounce.AddConversion(pound, 0.0625m); // 1 oz = 0.0625 lb

await unitOfWork.CommitAsync();

// Domain events fired for each conversion creation

Using Unit Conversions

// 1. Load source unit
var kg = await unitRepo.GetBySymbolAsync("kg");

// 2. Get conversion to target unit
var poundUnitId = await unitRepo.GetIdBySymbolAsync("lb");
var conversion = kg.GetConversionTo(poundUnitId);

if (conversion != null)
{
// 3. Convert quantity
decimal sourceQuantity = 50m; // 50 kg
decimal targetQuantity = sourceQuantity * conversion.Factor;

// Result: 50 × 2.20462 = 110.231 lb
Console.WriteLine($"{sourceQuantity} kg = {targetQuantity} lb");
}
else
{
throw new ConversionNotFoundException(
"No conversion found from kg to lb");
}

Getting Compatible Units for Item

// 1. Load item
var item = await itemRepo.GetByIdAsync(itemId);

if (item.DefaultUnitClassId.HasValue)
{
// 2. Load unit class
var unitClass = await unitClassRepo.GetByIdAsync(
item.DefaultUnitClassId.Value);

// 3. Get all units in class
var compatibleUnits = unitClass.UnitsInClass;

// 4. Present to user for transaction
foreach (var unit in compatibleUnits)
{
Console.WriteLine($"{unit.Symbol} - {unit.Name}");
}

// User can select any unit in the class for transaction
// Example output for Weight class:
// kg - Kilogram
// g - Gram
// lb - Pound
// oz - Ounce
// ton - Ton
}

Updating Conversion Factor

// Load unit
var kg = await unitRepo.GetBySymbolAsync("kg");

// Update existing conversion
var lbUnitId = await unitRepo.GetIdBySymbolAsync("lb");
kg.UpdateConversion(lbUnitId, 2.20463m); // Updated factor

await unitOfWork.CommitAsync();

// Conversion factor updated for future conversions

Repository Pattern

IUnitOfMeasureRepository

Query Methods:

  • GetByIdAsync(Guid id): Get unit with conversions
  • GetAllAsync(): Get all active units
  • GetBySymbolAsync(string symbol): Find unit by symbol
  • GetByClassAsync(Guid unitClassId): Get units in class
  • GetBySystemAsync(Guid systemId): Get units in system

Conversion Methods:

  • GetConversionAsync(Guid fromUnitId, Guid toUnitId): Get specific conversion
  • GetAllConversionsForUnitAsync(Guid unitId): Get all conversions from unit
  • HasConversionAsync(Guid fromUnitId, Guid toUnitId): Check conversion exists

Validation Methods:

  • ExistsBySymbolAsync(string symbol): Check symbol exists
  • IsSymbolUniqueAsync(string symbol, Guid? excludeId): Validate uniqueness

Command Methods:

  • AddAsync(UnitOfMeasure unit): Create unit
  • Update(UnitOfMeasure unit): Update unit
  • Archive(UnitOfMeasure unit): Soft delete
  • UnArchive(UnitOfMeasure unit): Restore

IUnitClassRepository

Query Methods:

  • GetByIdAsync(Guid id): Get class with units
  • GetAllAsync(): Get all classes
  • GetByNameAsync(string name): Find by name
  • GetWithUnitsAsync(Guid id): Load class with all units

Command Methods:

  • AddAsync(UnitClass unitClass): Create class
  • Update(UnitClass unitClass): Update class
  • Archive(UnitClass unitClass): Soft delete

IMeasurementSystemRepository

Query Methods:

  • GetByIdAsync(Guid id): Get system
  • GetAllAsync(): Get all systems
  • GetByNameAsync(string name): Find by name

Command Methods:

  • AddAsync(MeasurementSystem system): Create system
  • Update(MeasurementSystem system): Update system

Domain Services

UnitConversionService (Future Enhancement)

Purpose: Provides advanced conversion operations including multi-step conversions.

Planned Methods:

Task<decimal> ConvertAsync(
decimal quantity,
Guid fromUnitId,
Guid toUnitId)

Task<ConversionPath> FindConversionPathAsync(
Guid fromUnitId,
Guid toUnitId)

Task<IEnumerable<UnitOfMeasure>> GetCompatibleUnitsAsync(
Guid unitId)

Domain Events

Unit Events

  • UnitOfMeasureCreatedEvent: Raised when new unit created
  • UnitOfMeasureUpdatedEvent: Raised when unit properties updated
  • UnitOfMeasureArchivedEvent: Raised when unit archived

Conversion Events

  • UnitConversionCreatedEvent: Raised when conversion added
    • Contains: FromUnit, ToUnit, Factor, UnitClass
    • Enables: Conversion cache updates, validation triggers

Class Events

  • BaseUnitSetEvent: Raised when base unit assigned to class
  • BaseUnitUnsetEvent: Raised when base unit removed from class

Testing Strategy

Unit Tests

  • Factory Method Tests: Unit creation with various combinations
  • Conversion Tests: Add/update/remove conversions
  • Validation Tests: Business rule enforcement
  • Precision Tests: Decimal precision handling

Integration Tests

  • Repository Tests: Database operations
  • Conversion Persistence: Conversions correctly saved/loaded
  • Class Relationships: Unit class associations
  • System Relationships: Measurement system associations

Performance Considerations

Optimization Strategies

  • Conversion Caching: Cache frequently used conversions
  • Unit Class Loading: Eager load units in class
  • Symbol Lookups: Indexed symbol searches
  • Conversion Graphs: Pre-compute conversion paths (future)

Database Optimization

  • Primary Keys: Clustered indexes on IDs
  • Symbol Index: Unique index on Symbol
  • Conversion Indexes: Composite index on (FromUnit, ToUnit)
  • Class Indexes: Foreign key indexes for queries

Future Enhancements

Planned Features

  1. Multi-Step Conversions: kg → g → oz (through intermediate units)
  2. Conversion Validation: Verify conversion accuracy
  3. Historical Conversions: Track conversion factor history
  4. Currency Units: Support for monetary units
  5. Custom Formulas: Complex conversions beyond simple factors
  6. Unit Localization: Display units in user's locale

Domain Documentation

API Documentation

Concept Documentation


Last Updated: 2025-10-24 | Status: Production Ready | Version: 1.0