Skip to main content

Job

Introduction

The Job entity is an aggregate root that serves as a container for grouping multiple related orders, commodities, and accounting transactions. Jobs enable consolidation scenarios where several orders need to be tracked together, such as master bill consolidations, project-based shipments, or multi-leg freight movements.

Unlike Order which uses integer IDs, Job uses a Guid (GUID/UUID) as its primary identifier, providing globally unique identification suitable for distributed systems and external integrations. Jobs support draft mode, custom values for extensibility, and maintain relationships to customers, employees, and organizational divisions.

Entity Structure

Properties

Property NameTypeRequiredDescription
JobIdGuidYesGlobally unique identifier (GUID/UUID) - primary key
JobNumberstringYesHuman-readable job number (unique within organization)
OrganizationIdintYesOrganization owning this job (multi-tenancy)
DivisionIdint?NoDivision within organization for data partitioning
CustomerIdint?NoCustomer contact for this job
CustomerContactNoNavigation property to customer contact
EmployeeIdint?NoEmployee responsible for this job
EmployeeContactNoNavigation property to employee contact
JobStatusIdint?NoCurrent status of the job
JobStatusJobStatusNoNavigation property to status definition
Descriptionstring?NoJob description or notes
IsDraftboolYesWhether job is in draft state (default: false)
CustomValuesDictionary<string, object?>NoExtensible custom properties dictionary
CreatedDateTimeYesCreation timestamp (inherited from AuditableEntity)
CreatedBystringYesUser ID who created the job (inherited)
LastModifiedDateTimeYesLast modification timestamp (inherited)
LastModifiedBystringYesUser ID who last modified (inherited)

Relationships (Navigation Properties)

RelationshipTypeDescription
JobOrdersICollection<JobOrder>Junction table to orders in this job
OrdersICollection<Order>Orders grouped in this job (via JobOrders)
JobAccountingTransactionsICollection<JobAccountingTransaction>Junction table to accounting transactions
AccountingTransactionsICollection<AccountingTransaction>Transactions associated with this job
CommoditiesICollection<Commodity>Commodities directly associated with job
DivisionDivisionParent division
CustomerContactCustomer for this job
EmployeeContactEmployee managing this job
JobStatusJobStatusCurrent job status

Key Differences from Order

AspectJobOrder
Primary KeyGuid (GUID/UUID)int
PurposeGroup multiple orders/transactionsSingle operational shipment
Aggregate ScopeMultiple orders + transactionsSingle order with commodities/charges
Status ManagementJobStatus (simpler)OrderStatus (lifecycle-driven)
Domain EventsNone currentlyOrderStatusChangedEvent

YAML Configuration

Creating a Job

# Create a new job for consolidation
- task: Job/Create
name: createConsolidationJob
inputs:
organizationId: ${organizationId}
job:
jobNumber: "JOB-2025-00123"
divisionId: ${divisionId}
customerId: ${customerId}
employeeId: ${employeeId}
jobStatusId: ${activeJobStatusId}
description: "Shanghai to Los Angeles Consolidation - Jan 2025"
isDraft: false
customValues:
projectCode: "PROJ-ASIA-001"
containerType: "40HC"
estimatedCommodities: 45
notes: "Temperature controlled shipment"
outputs:
- job: newJob

Adding Orders to a Job

# Associate existing orders with a job
- task: Job/Update
name: assignOrdersToJob
inputs:
organizationId: ${organizationId}
jobId: ${jobId}
job:
jobOrders:
- orderId: ${order1Id}
- orderId: ${order2Id}
- orderId: ${order3Id}
- orderId: ${order4Id}

Adding Accounting Transactions to Job

# Link invoices and bills to the job
- task: Job/Update
name: linkInvoicesToJob
inputs:
organizationId: ${organizationId}
jobId: ${jobId}
job:
jobAccountingTransactions:
- accountingTransactionId: ${masterInvoiceId}
- accountingTransactionId: ${freightBillId}
- accountingTransactionId: ${customsBillId}

Querying Job with All Relationships

# Query job with complete data using GraphQL
- task: Query/GraphQL
name: getFullJobData
inputs:
organizationId: ${organizationId}
query: |
query GetJob($jobId: ID!) {
job(id: $jobId) {
jobId
jobNumber
description
jobOrders {
order {
orderNumber
orderCommodities {
commodity { description commodityType { name } }
}
orderStatus { statusName }
billToContact { name }
}
}
jobAccountingTransactions {
accountingTransaction {
accountingTransactionNumber
charges { description amount }
accountingItem { code name }
}
}
commodities { description commodityType { name } commodityStatus { statusName } }
customer { name contactAddresses { address1 city } }
employee { name }
division { divisionName }
jobStatus { statusName }
}
}
variables:
jobId: ${jobId}
outputs:
- response.job: fullJob

Updating Job Properties

# Update job description and status
- task: Job/Update
name: updateJobInfo
inputs:
organizationId: ${organizationId}
jobId: ${jobId}
job:
description: "Updated: Container departed Shanghai on 2025-01-15"
jobStatusId: ${inTransitStatusId}

Working with Custom Values

# Add/update custom job properties
- task: Job/Update
name: updateJobCustomValues
inputs:
organizationId: ${organizationId}
jobId: ${jobId}
job:
customValues:
masterBL: "MAEU1234567890"
vesselName: "MAERSK ESSEX"
voyageNumber: "V2025-001W"
ETD: "2025-01-15"
ETA: "2025-02-05"
totalCBM: 67.5
totalGrossWeight: 12500
consolidationType: "FCL"
actualArrivalDate: "2025-02-04"

Draft Job Workflow

# Step 1: Create draft job for planning
- task: Job/Create
name: createDraftJob
inputs:
organizationId: ${organizationId}
job:
jobNumber: "JOB-DRAFT-${timestamp}"
customerId: ${customerId}
isDraft: true
description: "Planning consolidation - not yet confirmed"
outputs:
- job: draftJob

# Step 2: Add tentative orders
- task: Job/Update
name: addTentativeOrders
inputs:
organizationId: ${organizationId}
jobId: ${draftJob.jobId}
job:
jobOrders: ${tentativeOrderIds}

# Step 3: Later, finalize the job
- task: Job/Update
name: finalizeJob
inputs:
organizationId: ${organizationId}
jobId: ${draftJob.jobId}
job:
isDraft: false
jobNumber: "JOB-2025-${finalSequence}"

Key Methods

The Job entity provides methods for managing its aggregate:

Order Management

  • ChangeJobOrders(List<int>) - Set orders in job (adds new, removes unspecified)

Accounting Transaction Management

  • ChangeJobAccountingTransactions(List<int>) - Set accounting transactions (adds new, removes unspecified)

Property Updates

  • ChangeJobNumber(string) - Update job number
  • ChangeCustomerId(int?) - Change customer
  • ChangeEmployeeId(int?) - Assign employee
  • ChangeDivisionId(int?) - Change division
  • ChangeJobStatusId(int?) - Update job status
  • ChangeDescription(string?) - Update description
  • ChangeIsDraft(bool) - Toggle draft mode

Custom Values Management

  • ChangeCustomValues(Dictionary<string, object?>) - Bulk update custom values
  • ChangeCustomValue(string, object?) - Update single custom value (removes if value is null)

Aggregate Boundary

As an aggregate root, Job enforces the following boundaries:

Direct Children (managed by Job):

  • JobOrder - Junction entries managed through ChangeJobOrders()
  • JobAccountingTransaction - Junction entries managed through ChangeJobAccountingTransactions()

External References (independent entities):

  • Order - Referenced through JobOrder junction, but Orders exist independently
  • AccountingTransaction - Referenced through JobAccountingTransaction junction
  • Commodity - Direct collection relationship
  • Contact (Customer, Employee) - Referenced but not owned
  • Division, JobStatus - Reference data

Consistency Rules:

  • Use ChangeJobOrders() to maintain order associations - method handles adds and removals
  • Use ChangeJobAccountingTransactions() for transaction associations
  • Both methods implement "replace" semantics - unlisted items are removed
  • Custom values can be updated in bulk or individually

Use Cases

1. Master Bill Consolidation

# Step 1: Create consolidation job
- task: Job/Create
name: createConsolidation
inputs:
organizationId: ${organizationId}
job:
jobNumber: "MAWB-2025-LAX-001"
customerId: ${freightForwarderId}
description: "Master AWB consolidation to LAX"
customValues:
masterAWB: "020-12345678"
flightNumber: "AA1234"
flightDate: "2025-02-01"
outputs:
- job: consolJob

# Step 2: Add house shipments
- task: Job/Update
name: addHouseShipments
inputs:
organizationId: ${organizationId}
jobId: ${consolJob.jobId}
job:
jobOrders:
- orderId: ${houseOrder1} # HAWB1
- orderId: ${houseOrder2} # HAWB2
- orderId: ${houseOrder3} # HAWB3

# Step 3: Create master invoice
- task: AccountingTransaction/Generate
name: createMasterInvoice
inputs:
organizationId: ${organizationId}
accountingTransaction:
transactionType: Invoice
applyToContactId: ${freightForwarderId}
description: "Master AWB charges"
outputs:
- accountingTransaction: masterInvoice

# Step 4: Link invoice to job
- task: Job/Update
name: linkInvoiceToJob
inputs:
organizationId: ${organizationId}
jobId: ${consolJob.jobId}
job:
jobAccountingTransactions:
- accountingTransactionId: ${masterInvoice.accountingTransactionId}

2. Project-Based Tracking

# Step 1: Create project job
- task: Job/Create
name: createProjectJob
inputs:
organizationId: ${organizationId}
job:
jobNumber: "PROJ-FACTORY-RELOCATION"
customerId: ${manufacturerId}
description: "Factory Relocation Project - Multiple Shipments"
customValues:
projectManager: "John Smith"
projectStartDate: "2025-01-01"
projectEndDate: "2025-06-30"
budget: 500000
estimatedShipments: 50
outputs:
- job: projectJob

# Step 2: As shipments are created, add them to the project
- task: Job/Update
name: addShipmentsToProject
inputs:
organizationId: ${organizationId}
jobId: ${projectJob.jobId}
job:
jobOrders: ${currentOrderList} # Dynamically grows

3. Multi-Leg Freight Movement

# Step 1: Create multi-leg job
- task: Job/Create
name: createMultiLegJob
inputs:
organizationId: ${organizationId}
job:
jobNumber: "MULTI-ASIA-EUR-001"
description: "Asia to Europe via Trans-Siberian Rail"
customValues:
origin: "Shanghai, China"
destination: "Hamburg, Germany"
transitPoints: ["Vladivostok", "Moscow"]
estimatedDays: 18
outputs:
- job: multiLegJob

# Step 2: Add each leg as separate order
- task: Job/Update
name: addMultipleLegs
inputs:
organizationId: ${organizationId}
jobId: ${multiLegJob.jobId}
job:
jobOrders:
- orderId: ${oceanLegId} # Shanghai to Vladivostok
- orderId: ${railLeg1Id} # Vladivostok to Moscow
- orderId: ${railLeg2Id} # Moscow to Hamburg
- orderId: ${deliveryLegId} # Final delivery

Examples

Complete Job Workflow with Invoicing

# Step 1: Create job
- task: Job/Create
name: createConsolidationJob
inputs:
organizationId: ${organizationId}
job:
jobNumber: "JOB-2025-${sequence}"
divisionId: ${divisionId}
customerId: ${customerId}
employeeId: ${currentUserId}
jobStatusId: ${newJobStatusId}
description: "Container consolidation - Q1 2025"
outputs:
- job: job

# Step 2: Add orders to job
- task: Job/Update
name: addOrdersToJob
inputs:
organizationId: ${organizationId}
jobId: ${job.jobId}
job:
jobOrders: ${selectedOrderIds}

# Step 3: Update job custom values with consolidation details
- task: Job/Update
name: updateConsolidationDetails
inputs:
organizationId: ${organizationId}
jobId: ${job.jobId}
job:
customValues:
containerNumber: "CSNU1234567"
sealNumber: "SEAL-789456"
loadingDate: ${currentDate}
totalPackages: ${packageCount}
totalWeight: ${totalWeight}

# Step 4: Create master invoice for the job
- task: AccountingTransaction/Generate
name: createMasterInvoice
inputs:
organizationId: ${organizationId}
accountingTransaction:
transactionType: Invoice
applyToContactId: ${customerId}
description: "Consolidation charges - ${job.jobNumber}"
outputs:
- accountingTransaction: invoice

# Step 5: Link invoice to job
- task: Job/Update
name: linkInvoiceToJob
inputs:
organizationId: ${organizationId}
jobId: ${job.jobId}
job:
jobAccountingTransactions:
- accountingTransactionId: ${invoice.accountingTransactionId}

# Step 6: Update job status to active
- task: Job/Update
name: activateJob
inputs:
organizationId: ${organizationId}
jobId: ${job.jobId}
job:
jobStatusId: ${activeJobStatusId}

Best Practices

1. Use Jobs for Grouping

  • Use Job for consolidations, projects, or multi-order tracking
  • Use individual Orders for single shipments
  • Don't create jobs for every order - only when grouping adds value

2. Guid Primary Key

  • JobId is a Guid, not an integer
  • Use Guid for external system integrations and API references
  • JobNumber provides human-readable identification

3. Order Association

  • ChangeJobOrders() implements replace semantics
  • Orders not in the new list are removed from the job
  • Orders can belong to multiple jobs (many-to-many via JobOrder)

4. Accounting Transaction Association

  • Link master invoices/bills to jobs for consolidated billing
  • Each order can have its own transactions, plus job-level transactions
  • Use job-level transactions for consolidation charges

5. Custom Values Strategy

  • Store consolidation-specific data (master BL, container numbers) in custom values
  • Use ChangeCustomValue() for single updates (avoids reading entire dictionary)
  • Null values remove keys from dictionary

6. Draft Jobs

  • Create with isDraft: true for planning and what-if scenarios
  • Draft jobs can have incomplete data
  • Finalize with ChangeIsDraft(false) when confirmed

7. Job Numbers

  • Use meaningful job number patterns (e.g., "CONSOL-2025-001", "PROJ-FACTORY-01")
  • Job numbers can be changed with ChangeJobNumber() before finalization
  • Ensure uniqueness within your organization

8. Status Management

  • Define JobStatus values appropriate to your workflow
  • Unlike Order, Job doesn't publish domain events on status changes
  • Consider workflow triggers if status change notifications are needed

9. Commodity Association

  • Jobs can have direct commodity relationships
  • Typically, commodities belong to orders, which belong to jobs
  • Direct job-commodity relationship useful for warehouse scenarios

10. Reporting and Analytics

  • Jobs provide natural aggregation boundary for reporting
  • Query job with all relationships for comprehensive consolidation view
  • Custom values enable flexible reporting dimensions