Skip to main content

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 NameTypeRequiredDescription
CommodityIdintYesUnique identifier for the commodity (primary key)
OrganizationIdintYesOrganization owning this commodity (multi-tenancy)
DescriptionstringYesCommodity description/name
PiecesintYesNumber of pieces/packages
CommodityTypeIdint?NoType classification (e.g., Electronics, Textiles)
CommodityTypeCommodityTypeNoNavigation to type definition
CommodityStatusIdint?NoCurrent status (e.g., In Warehouse, Shipped)
CommodityStatusCommodityStatusNoNavigation to status definition
PackageTypeIdint?NoPackage type (carton, pallet, crate, etc.)
PackageTypePackageTypeNoNavigation to package type
ContainerCommodityIdint?NoParent container ID for hierarchical nesting
ContainerCommodityCommodityNoParent container (self-reference)
ContainerCommoditiesICollection<Commodity>NoChild commodities in this container
Lengthdecimal?NoLength dimension
Widthdecimal?NoWidth dimension
Heightdecimal?NoHeight dimension
DimensionsUnitDimensionsUnitYesUnit for dimensions (Inch, Centimeter, Meter)
Weightdecimal?NoWeight per piece (or total if WeightByTotal=true)
WeightTotaldecimal?NoTotal weight (auto-calculated: Weight × Pieces)
WeightByTotalboolYesIf true, Weight field is total (not per piece)
WeightUnitWeightUnitYesUnit for weight (Pound, Kilogram, Ton)
VolumePiecedecimal?NoVolume per piece (auto-calculated from dimensions)
VolumeTotaldecimal?NoTotal volume (auto-calculated: VolumePiece × Pieces)
VolumeUnitVolumeUnitYesUnit for volume (CubicFoot, CubicMeter, Liter)
Quantityint?NoQuantity (distinct from Pieces - e.g., units within packages)
Unitstring?NoUnit of measure for Quantity (e.g., "EA", "BOX")
UnitaryValuedecimal?NoValue per unit
UnitaryValueTotaldecimal?NoTotal value (auto-calculated)
ValueByTotalboolYesIf true, UnitaryValue is total value
BillToContactIdint?NoContact to bill for this commodity (charge filtering)
BillToContactContactNoNavigation to billing contact
WarehouseLocationIdint?NoCurrent warehouse location
WarehouseLocationWarehouseLocationNoNavigation to warehouse location
JobIdGuid?NoAssociated job
JobJobNoNavigation to job
InventoryItemIdint?NoLinked inventory/SKU item
InventoryItemInventoryItemNoNavigation to inventory item
SerialNumberstring?NoSerial number for the commodity
Notestring?NoAdditional notes/comments
IsDeletedbool?NoSoft delete flag (default: false)
CustomValuesDictionary<string, object?>NoExtensible custom properties dictionary
SearchVectorNpgsqlTsVectorNoFull-text search vector (PostgreSQL)
CreatedDateTimeYesCreation timestamp (inherited from AuditableEntity)
CreatedBystringYesUser ID who created (inherited)
LastModifiedDateTimeYesLast modification timestamp (inherited)
LastModifiedBystringYesUser ID who last modified (inherited)

Relationships (Navigation Properties)

RelationshipTypeDescription
OrderCommoditiesICollection<OrderCommodity>Junction to orders containing this commodity
CommodityTrackingNumbersICollection<CommodityTrackingNumber>Multiple tracking numbers
CommodityTagsICollection<CommodityTag>Tags assigned to this commodity
AllTagsICollection<CommodityAllTagsView>View of all tags (flattened)
ContainerCommodityCommodityParent container (self-reference)
ContainerCommoditiesICollection<Commodity>Child commodities in this container
OrganizationOrganizationParent organization
CommodityTypeCommodityTypeType classification
CommodityStatusCommodityStatusCurrent status
PackageTypePackageTypePackage classification
WarehouseLocationWarehouseLocationCurrent warehouse location
JobJobAssociated job
InventoryItemInventoryItemLinked inventory SKU
BillToContactContactBilling 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 totals
  • RemoveItemFromContainer(Commodity) - Remove item from container, updates container totals
  • MoveToContainer(Commodity) - Move this commodity to different container
  • ChangeContainerCommodityId(int?) - Set/change parent container

Tracking Numbers

  • AddCommodityTrackingNumber(string, string?, bool) - Add tracking number with type and primary flag
  • ChangeCommodityTrackingNumbers(IEnumerable<CommodityTrackingNumber>) - Replace tracking number list

Dimension & Weight Management

  • ChangeLength(decimal?) - Update length, recalculates volume, flags charge recalc
  • ChangeWidth(decimal?) - Update width, recalculates volume, flags charge recalc
  • ChangeHeight(decimal?) - Update height, recalculates volume, flags charge recalc
  • ChangeDimensionsUnit(DimensionsUnit) - Change measurement system
  • ChangeWeight(decimal?) - Update weight, recalculates total, flags charge recalc
  • ChangeWeightUnit(WeightUnit) - Change weight measurement system
  • ChangePieces(int) - Update pieces, recalculates all totals, flags charge recalc

Totals Calculation

  • RefreshVolumeTotal() - Recalculate volume from dimensions/pieces or container contents
  • RefreshWeightTotal() - Recalculate total weight from pieces or container contents
  • RefreshValueTotal() - Recalculate total value
  • RefreshValues() - Recalculate all totals

Charge Recalculation

  • RequiresChargeRecalculation() - Check if changes require charge recalculation
  • ResetChargeRecalculationFlag() - Reset flag after recalculating charges

Status & Type Management

  • ChangeCommodityStatusId(int?) - Update status (cascades to container items)
  • ChangeCommodityStatus(CommodityStatus) - Update status by entity
  • ChangeCommodityTypeId(int?) - Change type, flags charge recalc
  • ChangePackageTypeId(int?) - Change package type, flags charge recalc

Billing & Charge Filtering

  • ChangeBillToContactId(int?) - Set billing contact, flags charge recalc
  • ChangeBillToContact(Contact?) - Set billing contact by entity

Other Property Updates

  • ChangeDescription(string) - Update description
  • ChangeWarehouseLocationId(int?) - Update location (cascades to container items)
  • ChangeInventoryItemId(int?) - Link to inventory SKU
  • ChangeJobId(Guid?) - Associate with job
  • ChangeCustomValues(Dictionary<string, object?>) - Update custom values
  • ChangeIsDeleted(bool?) - Soft delete

Utility Methods

  • Copy() - Create deep copy including container contents
  • CopyChanges(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: true for 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