Commodity
Introduction
The Commodity entity represents individual freight items, packages, or cargo pieces in the TMS. As an aggregate root, Commodity supports sophisticated features including hierarchical container structures (commodities containing other commodities), multiple tracking numbers, warehouse location tracking, and automatic charge recalculation triggers.
Commodities are highly flexible, supporting various measurement systems (imperial/metric), package types, inventory integration, and custom values for extensibility. The entity automatically maintains calculated totals for weight, volume, and value, cascading changes to parent containers when nested items are modified.
Entity Structure
Properties
| Property Name | Type | Required | Description |
|---|---|---|---|
| CommodityId | int | Yes | Unique identifier for the commodity (primary key) |
| OrganizationId | int | Yes | Organization owning this commodity (multi-tenancy) |
| Description | string | Yes | Commodity description/name |
| Pieces | int | Yes | Number of pieces/packages |
| CommodityTypeId | int? | No | Type classification (e.g., Electronics, Textiles) |
| CommodityType | CommodityType | No | Navigation to type definition |
| CommodityStatusId | int? | No | Current status (e.g., In Warehouse, Shipped) |
| CommodityStatus | CommodityStatus | No | Navigation to status definition |
| PackageTypeId | int? | No | Package type (carton, pallet, crate, etc.) |
| PackageType | PackageType | No | Navigation to package type |
| ContainerCommodityId | int? | No | Parent container ID for hierarchical nesting |
| ContainerCommodity | Commodity | No | Parent container (self-reference) |
| ContainerCommodities | ICollection<Commodity> | No | Child commodities in this container |
| Length | decimal? | No | Length dimension |
| Width | decimal? | No | Width dimension |
| Height | decimal? | No | Height dimension |
| DimensionsUnit | DimensionsUnit | Yes | Unit for dimensions (Inch, Centimeter, Meter) |
| Weight | decimal? | No | Weight per piece (or total if WeightByTotal=true) |
| WeightTotal | decimal? | No | Total weight (auto-calculated: Weight × Pieces) |
| WeightByTotal | bool | Yes | If true, Weight field is total (not per piece) |
| WeightUnit | WeightUnit | Yes | Unit for weight (Pound, Kilogram, Ton) |
| VolumePiece | decimal? | No | Volume per piece (auto-calculated from dimensions) |
| VolumeTotal | decimal? | No | Total volume (auto-calculated: VolumePiece × Pieces) |
| VolumeUnit | VolumeUnit | Yes | Unit for volume (CubicFoot, CubicMeter, Liter) |
| Quantity | int? | No | Quantity (distinct from Pieces - e.g., units within packages) |
| Unit | string? | No | Unit of measure for Quantity (e.g., "EA", "BOX") |
| UnitaryValue | decimal? | No | Value per unit |
| UnitaryValueTotal | decimal? | No | Total value (auto-calculated) |
| ValueByTotal | bool | Yes | If true, UnitaryValue is total value |
| BillToContactId | int? | No | Contact to bill for this commodity (charge filtering) |
| BillToContact | Contact | No | Navigation to billing contact |
| WarehouseLocationId | int? | No | Current warehouse location |
| WarehouseLocation | WarehouseLocation | No | Navigation to warehouse location |
| JobId | Guid? | No | Associated job |
| Job | Job | No | Navigation to job |
| InventoryItemId | int? | No | Linked inventory/SKU item |
| InventoryItem | InventoryItem | No | Navigation to inventory item |
| SerialNumber | string? | No | Serial number for the commodity |
| Note | string? | No | Additional notes/comments |
| IsDeleted | bool? | No | Soft delete flag (default: false) |
| CustomValues | Dictionary<string, object?> | No | Extensible custom properties dictionary |
| SearchVector | NpgsqlTsVector | No | Full-text search vector (PostgreSQL) |
| Created | DateTime | Yes | Creation timestamp (inherited from AuditableEntity) |
| CreatedBy | string | Yes | User ID who created (inherited) |
| LastModified | DateTime | Yes | Last modification timestamp (inherited) |
| LastModifiedBy | string | Yes | User ID who last modified (inherited) |
Relationships (Navigation Properties)
| Relationship | Type | Description |
|---|---|---|
| OrderCommodities | ICollection<OrderCommodity> | Junction to orders containing this commodity |
| CommodityTrackingNumbers | ICollection<CommodityTrackingNumber> | Multiple tracking numbers |
| CommodityTags | ICollection<CommodityTag> | Tags assigned to this commodity |
| AllTags | ICollection<CommodityAllTagsView> | View of all tags (flattened) |
| ContainerCommodity | Commodity | Parent container (self-reference) |
| ContainerCommodities | ICollection<Commodity> | Child commodities in this container |
| Organization | Organization | Parent organization |
| CommodityType | CommodityType | Type classification |
| CommodityStatus | CommodityStatus | Current status |
| PackageType | PackageType | Package classification |
| WarehouseLocation | WarehouseLocation | Current warehouse location |
| Job | Job | Associated job |
| InventoryItem | InventoryItem | Linked inventory SKU |
| BillToContact | Contact | Billing contact for charge filtering |
Enumerations
DimensionsUnit:
Inch(0)Centimeter(1)Meter(2)
WeightUnit:
Pound(0)Kilogram(1)Ton(2)
VolumeUnit:
CubicFoot(0)CubicMeter(1)Liter(2)
Key Concepts
1. Hierarchical Container System
Commodities can contain other commodities through the ContainerCommodityId relationship:
Container Commodity (ContainerCommodityId = null)
├── Commodity 1 (ContainerCommodityId = Container.CommodityId)
├── Commodity 2 (ContainerCommodityId = Container.CommodityId)
└── Commodity 3 (ContainerCommodityId = Container.CommodityId)
Use Cases:
- Palletized shipments (pallet contains cartons)
- Containerized cargo (container contains packages)
- Consolidated shipments (master package contains individual items)
2. Automatic Totals Calculation
The entity automatically calculates and maintains totals:
- VolumeTotal = VolumePiece × Pieces
- WeightTotal = Weight × Pieces (unless WeightByTotal=true)
- UnitaryValueTotal = UnitaryValue × Pieces/Quantity (unless ValueByTotal=true)
Totals are refreshed automatically when dimensions, weight, pieces, or container contents change.
3. Charge Recalculation Tracking
An internal flag (_requireChargeRecalculation) tracks when properties affecting charges change:
- Weight, dimensions, pieces, quantity
- Package type, commodity type
- BillToContactId (charge filtering)
Use RequiresChargeRecalculation() to check the flag and ResetChargeRecalculationFlag() after recalculating.
4. Warehouse Location Cascading
When a container's warehouse location changes, the change automatically cascades to all nested commodities:
Container.ChangeWarehouseLocationId(newLocationId);
// All ContainerCommodities automatically updated to same location
YAML Configuration
Creating a Simple Commodity
# Create individual commodity
- task: Commodity/Create@1
name: createElectronicsCommodity
inputs:
organizationId: ${organizationId}
commodity:
description: "Electronic Components - PCB Boards"
pieces: 10
length: 24
width: 18
height: 6
dimensionsUnit: Inch
weight: 15.5
weightByTotal: false # 15.5 lbs per piece
weightUnit: Pound
volumeUnit: CubicFoot
packageTypeId: ${cartonTypeId}
commodityTypeId: ${electronicsTypeId}
commodityStatusId: ${inStockStatusId}
quantity: 100
unit: "EA"
unitaryValue: 250
valueByTotal: false # $250 per piece
customValues:
sku: "PCB-2025-A1"
lotNumber: "LOT-001"
expiryDate: "2027-12-31"
hazmat: false
outputs:
- commodity: commodity
Creating a Container with Nested Items
# Step 1: Create container commodity (pallet)
- task: Commodity/Create@1
name: createPalletContainer
inputs:
organizationId: ${organizationId}
commodity:
description: "Pallet of Electronics"
pieces: 1
length: 48
width: 40
height: 60
dimensionsUnit: Inch
weight: 0 # Will auto-calculate from contents
weightByTotal: true
weightUnit: Pound
volumeUnit: CubicFoot
packageTypeId: ${palletTypeId}
warehouseLocationId: ${warehouseLocId}
outputs:
- commodity: pallet
# Step 2: Create items and add to container
- task: Commodity/Create@1
name: createCarton1
inputs:
organizationId: ${organizationId}
commodity:
description: "Carton 1 - Laptops"
pieces: 5
weight: 8.5
weightByTotal: false
dimensionsUnit: Inch
weightUnit: Pound
volumeUnit: CubicFoot
parentCommodityId: ${pallet.commodityId}
outputs:
- commodity: carton1
- task: Commodity/Create@1
name: createCarton2
inputs:
organizationId: ${organizationId}
commodity:
description: "Carton 2 - Monitors"
pieces: 3
weight: 12.0
weightByTotal: false
dimensionsUnit: Inch
weightUnit: Pound
volumeUnit: CubicFoot
parentCommodityId: ${pallet.commodityId}
outputs:
- commodity: carton2
# Pallet's weight and volume now auto-calculated from contents
Adding Tracking Numbers
# Add multiple tracking numbers to commodity
- task: CommodityTrackingNumber/Create@1
name: addUPSTracking
inputs:
organizationId: ${organizationId}
trackingNumber:
commodityId: ${commodityId}
trackingNumber: "1Z999AA10123456784"
trackingNumberType: "UPS"
isPrimary: true
- task: CommodityTrackingNumber/Create@1
name: addFedExTracking
inputs:
organizationId: ${organizationId}
trackingNumber:
commodityId: ${commodityId}
trackingNumber: "FEDEX-987654321"
trackingNumberType: "FedEx"
isPrimary: false
Updating Commodity Dimensions (Triggers Charge Recalculation)
# Update dimensions - automatically recalculates volume and flags for charge recalc
- task: Commodity/Update@1
name: updateCommodityDimensions
inputs:
organizationId: ${organizationId}
commodityId: ${commodityId}
commodity:
length: 30
width: 20
height: 8
# Domain logic automatically:
# - Recalculates volume from new dimensions
# - Sets requiresChargeRecalculation flag
# - Triggers CommodityDimensionsChangedEvent
Moving Commodity to Different Container
# Move commodity from one container to another
- task: Commodity/Update@1
name: moveCommodityToNewContainer
inputs:
organizationId: ${organizationId}
commodityId: ${itemId}
commodity:
parentCommodityId: ${newContainerId}
# Alternatively, remove from container (set parent to null)
- task: Commodity/Update@1
name: removeFromContainer
inputs:
organizationId: ${organizationId}
commodityId: ${itemId}
commodity:
parentCommodityId: null
Querying Commodity with Relationships
# Query commodity with all nested data using GraphQL
- task: Query/GraphQL
name: getCommodityWithRelationships
inputs:
organizationId: ${organizationId}
query: |
query GetCommodity($commodityId: ID!) {
commodity(id: $commodityId) {
commodityId
description
pieces
length
width
height
weight
volume
containerCommodities {
commodityId
description
commodityType { name }
commodityStatus { statusName }
commodityTrackingNumbers { trackingNumber trackingNumberType }
}
commodityTrackingNumbers { trackingNumber trackingNumberType isPrimary }
commodityTags { tag { name color } }
orderCommodities { order { orderNumber } }
packageType { name }
commodityType { name }
commodityStatus { statusName }
warehouseLocation { locationName warehouseZone { zoneName } }
inventoryItem { sku }
job { jobNumber }
billToContact { name }
}
}
variables:
commodityId: ${commodityId}
outputs:
- response.commodity: fullCommodity
Warehouse Location Management
# Assign commodity to warehouse location (cascades to nested items)
- task: Commodity/Update@1
name: assignWarehouseLocation
inputs:
organizationId: ${organizationId}
commodityId: ${containerCommodityId}
commodity:
warehouseLocationId: ${newLocationId}
# Domain logic automatically updates all nested ContainerCommodities to same location
Key Methods
The Commodity entity provides rich business logic:
Container Management
AddItemToContainer(params Commodity[])- Add items to container, updates container totalsRemoveItemFromContainer(Commodity)- Remove item from container, updates container totalsMoveToContainer(Commodity)- Move this commodity to different containerChangeContainerCommodityId(int?)- Set/change parent container
Tracking Numbers
AddCommodityTrackingNumber(string, string?, bool)- Add tracking number with type and primary flagChangeCommodityTrackingNumbers(IEnumerable<CommodityTrackingNumber>)- Replace tracking number list
Dimension & Weight Management
ChangeLength(decimal?)- Update length, recalculates volume, flags charge recalcChangeWidth(decimal?)- Update width, recalculates volume, flags charge recalcChangeHeight(decimal?)- Update height, recalculates volume, flags charge recalcChangeDimensionsUnit(DimensionsUnit)- Change measurement systemChangeWeight(decimal?)- Update weight, recalculates total, flags charge recalcChangeWeightUnit(WeightUnit)- Change weight measurement systemChangePieces(int)- Update pieces, recalculates all totals, flags charge recalc
Totals Calculation
RefreshVolumeTotal()- Recalculate volume from dimensions/pieces or container contentsRefreshWeightTotal()- Recalculate total weight from pieces or container contentsRefreshValueTotal()- Recalculate total valueRefreshValues()- Recalculate all totals
Charge Recalculation
RequiresChargeRecalculation()- Check if changes require charge recalculationResetChargeRecalculationFlag()- Reset flag after recalculating charges
Status & Type Management
ChangeCommodityStatusId(int?)- Update status (cascades to container items)ChangeCommodityStatus(CommodityStatus)- Update status by entityChangeCommodityTypeId(int?)- Change type, flags charge recalcChangePackageTypeId(int?)- Change package type, flags charge recalc
Billing & Charge Filtering
ChangeBillToContactId(int?)- Set billing contact, flags charge recalcChangeBillToContact(Contact?)- Set billing contact by entity
Other Property Updates
ChangeDescription(string)- Update descriptionChangeWarehouseLocationId(int?)- Update location (cascades to container items)ChangeInventoryItemId(int?)- Link to inventory SKUChangeJobId(Guid?)- Associate with jobChangeCustomValues(Dictionary<string, object?>)- Update custom valuesChangeIsDeleted(bool?)- Soft delete
Utility Methods
Copy()- Create deep copy including container contentsCopyChanges(Commodity)- Copy properties from another commodity
Domain Events
The Commodity entity publishes domain events:
CommodityStatusChangedByNameEvent
Published when commodity status is changed.
Use Cases:
- Trigger workflows on status changes (e.g., "Shipped" → send notification)
- Update external warehouse systems
- Cascade status to related entities
CommodityTrackingNumbersChangedEvent
Published when tracking numbers are added, removed, or modified.
Use Cases:
- Sync tracking numbers to carrier systems
- Notify customers of tracking availability
- Update order tracking information
Aggregate Boundary
As an aggregate root, Commodity controls:
Direct Children:
- CommodityTrackingNumber - Managed through commodity methods
- CommodityTag - Tag assignments
- ContainerCommodities - Nested items (hierarchical self-reference)
External References:
- OrderCommodity - Junction to orders (Order is separate aggregate)
- CommodityType, CommodityStatus, PackageType - Reference data
- WarehouseLocation - Reference to location
- InventoryItem - Reference to inventory SKU
- Job - Reference to job aggregate
- BillToContact - Reference to contact
Consistency Rules:
- Always add/remove container items through
AddItemToContainer()/RemoveItemFromContainer() - Totals are automatically maintained - don't set directly
- Status changes cascade to nested container items
- Warehouse location changes cascade to nested items
- Charge recalculation flag tracks relevant changes
Use Cases
1. Palletized Shipment
# Create pallet container
- task: Commodity/Create@1
name: createPallet
inputs:
organizationId: ${organizationId}
commodity:
description: "Pallet #1"
pieces: 1
packageTypeId: ${palletTypeId}
weightByTotal: true
dimensionsUnit: Inch
weightUnit: Pound
volumeUnit: CubicFoot
outputs:
- commodity: pallet
# Add cartons to pallet by setting their parent
- task: Loops/ForEach
name: addCartonsToPallet
inputs:
items: ${cartonList}
actions:
- task: Commodity/Update@1
name: addCartonToContainer
inputs:
organizationId: ${organizationId}
commodityId: ${item.commodityId}
commodity:
parentCommodityId: ${pallet.commodityId}
# Pallet weight/volume auto-calculated from cartons
2. Inventory Item Tracking
# Create commodity linked to inventory SKU
- task: Commodity/Create@1
name: createInventoryCommodity
inputs:
organizationId: ${organizationId}
commodity:
description: "Widget Model A"
inventoryItemId: ${widgetSkuId}
pieces: 50
warehouseLocationId: ${binLocationId}
commodityStatusId: ${inStockStatusId}
customValues:
batchNumber: "BATCH-2025-01"
receivedDate: ${today}
outputs:
- commodity: inventoryCommodity
3. Multi-Customer Consolidation with Charge Filtering
# Container with items for different customers
- task: Commodity/Create@1
name: createConsolidatedContainer
inputs:
organizationId: ${organizationId}
commodity:
description: "Consolidated Container"
pieces: 1
outputs:
- commodity: container
# Customer A's items (BillToContactId set)
- task: Commodity/Create@1
name: createCustomerAItems
inputs:
organizationId: ${organizationId}
commodity:
description: "Customer A Items"
pieces: 10
billToContactId: ${customerAId}
parentCommodityId: ${container.commodityId}
outputs:
- commodity: customerAItems
# Customer B's items
- task: Commodity/Create@1
name: createCustomerBItems
inputs:
organizationId: ${organizationId}
commodity:
description: "Customer B Items"
pieces: 5
billToContactId: ${customerBId}
parentCommodityId: ${container.commodityId}
outputs:
- commodity: customerBItems
# Charges with ApplyToContactId will only count matching commodities
Best Practices
1. Container Hierarchy
- Use containers for pallets, master cartons, or consolidations
- Always use
AddItemToContainer()/RemoveItemFromContainer()methods - Don't set ContainerCommodityId directly
- Container totals auto-calculate from contents
2. Measurement Units
- Be consistent with units within an organization
- Use DimensionsUnit, WeightUnit, VolumeUnit enums
- System handles conversions internally
3. Charge Recalculation
- Check
RequiresChargeRecalculation()before recalculating charges - Reset flag with
ResetChargeRecalculationFlag()after recalculation - Prevents unnecessary recalculations
4. Tracking Numbers
- Use CommodityTrackingNumber for house bills, parcel tracking
- Set
isPrimary: truefor main tracking number - Support multiple tracking numbers per commodity
5. Warehouse Integration
- Link to WarehouseLocation for location tracking
- Changes cascade to nested items automatically
- Use InventoryItemId for SKU linkage
6. Bill To Contact Filtering
- Set BillToContactId when commodity belongs to specific customer in consolidation
- Null BillToContactId means commodity counts for all customers
- Affects charge calculations (only counts for matching ApplyToContactId)
7. Custom Values Usage
- Store SKU, lot numbers, batch codes in custom values
- Use for flexible properties varying by commodity type
- Good for: serial numbers, expiry dates, hazmat flags, manufacturer info
8. Status Cascading
- Status changes automatically cascade to nested container items
- Use for bulk status updates (e.g., marking entire pallet as "Shipped")
- CommodityStatusChangedByNameEvent published for each change
9. Soft Delete
- Use IsDeleted flag instead of hard deletes
- Maintains referential integrity
- Allows historical reporting
10. Performance
- Load ContainerCommodities selectively (can be deeply nested)
- Use specific includes for needed relationships only
- Consider pagination for large commodity lists
Related Topics
- CommodityType Entity - Commodity classifications
- CommodityStatus Entity - Status definitions and lifecycle
- CommodityTrackingNumber Entity - Tracking number management
- CommodityTag Entity - Tagging system
- OrderCommodity Entity - Junction to orders
- PackageType Entity - Package classifications
- WarehouseLocation Entity - Location tracking
- InventoryItem Entity - Inventory/SKU integration
- Order Entity - Orders containing commodities
- Job Entity - Job aggregation
- Contact Entity - Billing contacts
- Entity System Overview - Aggregate patterns and architecture