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 Name | Type | Required | Description |
|---|---|---|---|
| JobId | Guid | Yes | Globally unique identifier (GUID/UUID) - primary key |
| JobNumber | string | Yes | Human-readable job number (unique within organization) |
| OrganizationId | int | Yes | Organization owning this job (multi-tenancy) |
| DivisionId | int? | No | Division within organization for data partitioning |
| CustomerId | int? | No | Customer contact for this job |
| Customer | Contact | No | Navigation property to customer contact |
| EmployeeId | int? | No | Employee responsible for this job |
| Employee | Contact | No | Navigation property to employee contact |
| JobStatusId | int? | No | Current status of the job |
| JobStatus | JobStatus | No | Navigation property to status definition |
| Description | string? | No | Job description or notes |
| IsDraft | bool | Yes | Whether job is in draft state (default: false) |
| CustomValues | Dictionary<string, object?> | No | Extensible custom properties dictionary |
| Created | DateTime | Yes | Creation timestamp (inherited from AuditableEntity) |
| CreatedBy | string | Yes | User ID who created the job (inherited) |
| LastModified | DateTime | Yes | Last modification timestamp (inherited) |
| LastModifiedBy | string | Yes | User ID who last modified (inherited) |
Relationships (Navigation Properties)
| Relationship | Type | Description |
|---|---|---|
| JobOrders | ICollection<JobOrder> | Junction table to orders in this job |
| Orders | ICollection<Order> | Orders grouped in this job (via JobOrders) |
| JobAccountingTransactions | ICollection<JobAccountingTransaction> | Junction table to accounting transactions |
| AccountingTransactions | ICollection<AccountingTransaction> | Transactions associated with this job |
| Commodities | ICollection<Commodity> | Commodities directly associated with job |
| Division | Division | Parent division |
| Customer | Contact | Customer for this job |
| Employee | Contact | Employee managing this job |
| JobStatus | JobStatus | Current job status |
Key Differences from Order
| Aspect | Job | Order |
|---|---|---|
| Primary Key | Guid (GUID/UUID) | int |
| Purpose | Group multiple orders/transactions | Single operational shipment |
| Aggregate Scope | Multiple orders + transactions | Single order with commodities/charges |
| Status Management | JobStatus (simpler) | OrderStatus (lifecycle-driven) |
| Domain Events | None currently | OrderStatusChangedEvent |
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 numberChangeCustomerId(int?)- Change customerChangeEmployeeId(int?)- Assign employeeChangeDivisionId(int?)- Change divisionChangeJobStatusId(int?)- Update job statusChangeDescription(string?)- Update descriptionChangeIsDraft(bool)- Toggle draft mode
Custom Values Management
ChangeCustomValues(Dictionary<string, object?>)- Bulk update custom valuesChangeCustomValue(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: truefor 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
Related Topics
- JobOrder Entity - Junction table for job-order relationships
- JobAccountingTransaction Entity - Junction for job-transaction links
- JobStatus Entity - Status definitions for jobs
- Order Entity - Individual shipments grouped by jobs
- AccountingTransaction Entity - Invoices and bills
- Commodity Entity - Freight items
- Contact Entity - Customers and employees
- Division Entity - Organizational structure
- Entity System Overview - Aggregate root patterns and architecture