Skip to main content

Contact

Introduction

The Contact entity is a polymorphic aggregate root that represents all business relationships in the system - customers, carriers, vendors, employees, agents, and more. Unlike most entities that represent a single concept, Contact uses the ContactType enum to differentiate between 15 distinct business roles, each with specific behaviors and validation rules.

Contact supports hierarchical relationships (parent/child contacts), multi-division access control, contact linking (representing relationships between contacts), address management, payment terms, credit limits, and extensible custom properties. Contacts can have associated system users for portal access, carrier equipment for logistics tracking, and discounts for pricing.

Entity Structure

Properties

Property NameTypeRequiredDescription
ContactIdintYesPrimary key - unique contact identifier
NamestringYesCompany or individual name
ContactTypeContactTypeYesType of contact (Customer, Carrier, Vendor, etc.)
OrganizationIdintYesOrganization owning this contact (multi-tenancy)
DivisionIdintYesPrimary division for this contact
DivisionDivisionNoNavigation property to primary division
AccountNumberstring?NoCustomer/vendor account number
ContactFirstNamestring?NoFirst name of primary contact person
ContactLastNamestring?NoLast name of primary contact person
EmailAddressstring?NoPrimary email address
PhoneNumberstring?NoPrimary phone number (required for most types)
MobilePhoneNumberstring?NoMobile phone number
FaxNumberstring?NoFax number
Websitestring?NoCompany website URL
IdNumberstring?NoTax ID, EIN, DUNS, or passport number
IdNumberTypeIDNumberType?NoType of ID number (EIN, DUNS, ForeignEntityId, Other)
CreditLimitdecimal?NoCredit limit for customers
PaymentTermIdint?NoDefault payment terms (Net 30, etc.)
PaymentTermPaymentTermNoNavigation property to payment terms
PaidAsPaidAs?NoPrepaid or Collect (freight payment method)
IsACorporationbool?NoWhether contact is a corporation
IsDeletedboolYesSoft delete flag (default: false)
ParentContactIdint?NoParent contact for hierarchical relationships
ContactStatusIdint?NoCurrent status of contact
ContactStatusContactStatusNoNavigation property to status
EntityTypeIdint?NoCustom entity type classification
EntityTypeEntityTypeNoNavigation property to entity type
CustomValuesDictionary<string, object?>NoExtensible custom properties dictionary
TagsList<string>NoTag list for categorization
SearchVectorNpgsqlTsVectorNoFull-text search vector (managed by database)
CreatedDateTimeYesCreation timestamp (inherited from AuditableEntity)
CreatedBystringYesUser ID who created the contact (inherited)
LastModifiedDateTimeYesLast modification timestamp (inherited)
LastModifiedBystringYesUser ID who last modified (inherited)

ContactType Enum

The ContactType determines validation rules, available features, and business logic:

ValueDescriptionCan Have AccountingCan Have EquipmentCan Have UserMust Have Phone
CustomerCustomer receiving servicesYesNoYes (portal)Yes
CarrierTransportation carrierYesYesNoYes
VendorSupplier of servicesNoNoNoYes
ContactIndividual contact (must link to another contact)NoNoNoNo
DriverTruck driver or operatorNoNoNoYes
EmployeeCompany employeeNoNoYes (internal)Yes
SalesPersonSales representativeNoNoNoYes
ForwardingAgentFreight forwarder or agentNoNoNoYes
FactoringCompanyFactoring/financing companyNoNoNoYes
LeadPotential customerNoNoNoNo
PoolPointFreight pool or cross-dockNoNoYesNo
DistributionCenterDistribution centerNoNoNoNo
StoreRetail store or locationNoNoYesNo
ContactUserUser associated with contactNoNoNoNo
USPPIUS Principal Party in Interest (export)NoNoNoNo

IDNumberType Enum

ValueDescription
EINEmployer Identification Number (US tax ID)
DUNSD-U-N-S Number (Dun & Bradstreet)
ForeignEntityIdForeign entity identifier (e.g., passport number)
OtherOther identification number type

PaidAs Enum

ValueDescription
PrepaidFreight charges paid by shipper
CollectFreight charges paid by consignee

Relationships (Navigation Properties)

RelationshipTypeDescription
ContactAddressesICollection<ContactAddress>Addresses for this contact (billing, shipping, physical)
ContactLinksICollection<ContactLink>Links from this contact to other contacts
LinkToContactLinksICollection<ContactLink>Links to this contact from other contacts
ContactsICollection<Contact>Contacts linked to this contact
LinksToContactsICollection<Contact>Contacts this contact is linked to
OrdersICollection<Order>Orders associated with this contact
OrderEntitiesICollection<OrderEntity>Dynamic role assignments (shipper, consignee, etc.)
OrderCarriersICollection<OrderCarrier>Carrier assignments on orders
RatesICollection<Rate>Rates applicable to this contact
DiscountsICollection<Discount>Discounts through ContactDiscount junction
ContactDiscountsICollection<ContactDiscount>Junction table for contact-discount relationships
AdditionalDivisionsICollection<Division>Additional divisions this contact can access
ContactDivisionsICollection<ContactDivision>Junction table for multi-division access
UserEmployeeAspNetUserEmployeeUser account for portal/employee access
EquipmentTypesICollection<EquipmentType>Equipment types (for Carrier contacts)
CarrierEquipmentsICollection<CarrierEquipment>Carrier equipment tracking (for Carrier contacts)
DivisionDivisionPrimary division
PaymentTermPaymentTermDefault payment terms
ContactStatusContactStatusCurrent status
EntityTypeEntityTypeCustom classification

YAML Configuration

Creating a Customer Contact

# Create a new customer with billing address
- task: Contact/Create@1
name: createCustomer
inputs:
organizationId: ${organizationId}
contact:
name: "Acme Manufacturing Inc."
contactType: Customer
divisionId: ${divisionId}
accountNumber: "CUST-2025-001"
contactFirstName: "John"
contactLastName: "Smith"
emailAddress: "[email protected]"
phoneNumber: "+1-555-0123"
mobilePhoneNumber: "+1-555-0124"
website: "https://www.acme.com"
idNumber: "12-3456789"
idNumberType: EIN
creditLimit: 50000.00
paymentTermId: ${net30TermId}
paidAs: Prepaid
isACorporation: true
tags:
- "preferred-customer"
- "west-coast"
- "manufacturing"
customValues:
industryCode: "NAICS-332"
accountManager: "Sarah Johnson"
preferredCarrier: "FedEx"
outputs:
- contact: customer

# Add billing address
- task: ContactAddress/Create@1
name: addBillingAddress
inputs:
organizationId: ${organizationId}
contactAddress:
contactId: ${customer.contactId}
addressType: Billing
addressLine: "123 Industrial Blvd"
addressLine2: "Suite 200"
cityName: "Los Angeles"
stateCode: "CA"
postalCode: "90001"
countryCode: "US"

Creating a Carrier Contact with Equipment

# Create carrier contact
- task: Contact/Create@1
name: createCarrier
inputs:
organizationId: ${organizationId}
contact:
name: "Coast to Coast Trucking LLC"
contactType: Carrier
divisionId: ${divisionId}
accountNumber: "CARR-500"
emailAddress: "[email protected]"
phoneNumber: "+1-555-TRUCK"
website: "https://coasttrucking.com"
idNumber: "98-7654321"
idNumberType: EIN
paymentTermId: ${net15TermId}
isACorporation: true
tags:
- "LTL"
- "refrigerated"
customValues:
dotNumber: "1234567"
mcNumber: "MC-987654"
insuranceCoverage: 1000000
safetyRating: "Satisfactory"
outputs:
- contact: carrier

# Assign equipment to carrier
- task: Contact/Update@1
name: assignCarrierEquipment
inputs:
organizationId: ${organizationId}
contactId: ${carrier.contactId}
contact:
carrierEquipments:
- equipmentTypeId: ${truck53ftId}
equipmentNumber: "TRUCK-001"
year: 2023
- equipmentTypeId: ${reeferTrailerId}
equipmentNumber: "TRAILER-R01"
year: 2024

Creating an Employee Contact with User Access

# Create employee contact
- task: Contact/Create@1
name: createEmployee
inputs:
organizationId: ${organizationId}
contact:
name: "Martinez, Carlos"
contactType: Employee
divisionId: ${divisionId}
contactFirstName: "Carlos"
contactLastName: "Martinez"
emailAddress: "[email protected]"
phoneNumber: "+1-555-0199"
mobilePhoneNumber: "+1-555-0198"
tags:
- "operations"
- "manager"
customValues:
department: "Operations"
title: "Operations Manager"
hireDate: "2020-01-15"
employeeId: "EMP-1234"
outputs:
- contact: employee

# Assign user account for system access
- task: Contact/Update@1
name: assignUserAccount
inputs:
organizationId: ${organizationId}
contactId: ${employee.contactId}
contact:
userId: ${aspNetUserId}

Hierarchical Contacts - Parent/Child Relationship

# Create parent company
- task: Contact/Create@1
name: createParentCompany
inputs:
organizationId: ${organizationId}
contact:
name: "Global Logistics Corporation"
contactType: Customer
divisionId: ${divisionId}
accountNumber: "CUST-PARENT-001"
phoneNumber: "+1-555-0100"
outputs:
- contact: parentCompany

# Create subsidiary
- task: Contact/Create@1
name: createSubsidiary
inputs:
organizationId: ${organizationId}
contact:
name: "Global Logistics - West Coast Division"
contactType: Customer
divisionId: ${divisionId}
parentContactId: ${parentCompany.contactId}
accountNumber: "CUST-SUB-001"
phoneNumber: "+1-555-0101"
customValues:
billingConsolidation: "parent"
region: "West"
outputs:
- contact: subsidiary

Contact Linking - Representing Relationships

# Create primary decision maker contact
- task: Contact/Create@1
name: createDecisionMaker
inputs:
organizationId: ${organizationId}
contact:
name: "Jane Doe"
contactType: Contact # Generic contact type
divisionId: ${divisionId}
contactFirstName: "Jane"
contactLastName: "Doe"
emailAddress: "[email protected]"
phoneNumber: "+1-555-0150"
outputs:
- contact: decisionMaker

# Link this contact to the customer company
- task: Contact/Update@1
name: linkToCustomer
inputs:
organizationId: ${organizationId}
contactId: ${decisionMaker.contactId}
contact:
contactLinks:
- linkedContactId: ${customer.contactId}
relationshipType: "Decision Maker"

Multi-Division Access Control

# Give customer access to multiple divisions
- task: Contact/Update@1
name: assignMultipleDivisions
inputs:
organizationId: ${organizationId}
contactId: ${customer.contactId}
contact:
contactDivisions:
- divisionId: ${eastCoastDivisionId}
- divisionId: ${westCoastDivisionId}
- divisionId: ${centralDivisionId}

Assigning Discounts to Contact

# Apply volume discounts to customer
- task: Contact/Update@1
name: assignDiscounts
inputs:
organizationId: ${organizationId}
contactId: ${customer.contactId}
contact:
contactDiscounts:
- discountId: ${volumeDiscount10PercentId}
- discountId: ${earlyPaymentDiscount2PercentId}
- discountId: ${preferredCustomerDiscountId}

Querying Contact with All Relationships

# Query customer with complete data using GraphQL
- task: Query/GraphQL
name: getCustomerWithRelationships
inputs:
organizationId: ${organizationId}
query: |
query GetContact($contactId: ID!) {
contact(id: $contactId) {
contactId
name
contactType
accountNumber
emailAddress
phoneNumber
creditLimit
contactAddresses { addressType addressLine cityName stateCode }
contactLinks { linkedContact { name } relationshipType }
division { divisionName }
contactDivisions { division { divisionName } }
paymentTerm { termName }
contactStatus { statusName }
userEmployee { user { userName email } }
contactDiscounts { discount { name percentage } }
orders { orderNumber orderStatus { statusName } }
rates { originPort { name } destinationPort { name } amount }
}
}
variables:
contactId: ${customerId}
outputs:
- response.contact: fullCustomer

Updating Contact Properties

# Update customer credit limit, payment terms, and contact information
- task: Contact/Update@1
name: updateCustomerInfo
inputs:
organizationId: ${organizationId}
contactId: ${customerId}
contact:
creditLimit: 100000.00
paymentTermId: ${net45TermId}
emailAddress: "[email protected]"

Working with Custom Values and Tags

# Update custom values and tags
- task: Contact/Update@1
name: updateCustomData
inputs:
organizationId: ${organizationId}
contactId: ${customerId}
contact:
customValues:
salesRepId: ${repId}
preferredShippingDay: "Tuesday"
specialInstructions: "Requires 24hr advance notice"
lastReviewDate: "2025-01-15"
creditCheckDate: "2025-01-01"
tags:
- "preferred-customer"
- "high-volume"
- "west-coast"
- "temperature-sensitive"

Key Methods

The Contact entity provides extensive methods for managing the aggregate:

Property Updates (Basic Information)

  • ChangeName(string) - Update contact name
  • ChangeAccountNumber(string?) - Update account number
  • ChangeContactFirstName(string?) - Update first name
  • ChangeContactLastName(string?) - Update last name
  • ChangeEmailAddress(string?) - Update email address
  • ChangePhoneNumber(string?) - Update phone number
  • ChangeMobilePhoneNumber(string?) - Update mobile number
  • ChangeFaxNumber(string?) - Update fax number
  • ChangeWebsite(string?) - Update website URL

Contact Type and Classification

  • ChangeContactType(ContactType) - Change contact type (Customer, Carrier, etc.)
  • ChangeIdNumber(string?) - Update ID number (EIN, DUNS, etc.)
  • ChangeIdNumberType(IDNumberType?) - Update ID number type
  • ChangeIsACorporation(bool?) - Set corporation flag
  • ChangeContactStatusId(int?) - Update contact status

Financial Settings

  • ChangeCreditLimit(decimal?) - Update credit limit
  • ChangePaymentTermId(int?) - Update payment terms
  • ChangePaymentTerm(PaymentTerm) - Set payment term object
  • ChangePaidAs(PaidAs?) - Set prepaid or collect

Organizational Structure

  • ChangeDivisionId(int) - Change primary division
  • ChangeDivision(Division) - Set division object
  • ChangeAdditionalDivisions(List<DivisionObjectId>?) - Set multi-division access (publishes ContactChangedDivisions event)
  • ChangeParentContactId(int?) - Set parent contact for hierarchy

Address and Contact Management

  • ChangeContactAddresses(IEnumerable<ContactAddress>?) - Manage addresses (merge logic: updates existing, adds new)
  • ChangeContactLinks(IEnumerable<ContactLink>) - Set contact links (relationships to other contacts)
  • ChangeLinksToContacts(IEnumerable<Contact>) - Set linked contacts
  • ChangeContacts(ICollection<Contact>) - Set contact collection

User and Access Management

  • AssignUserId(string) - Create user account for employee or customer portal
  • ChangeUserEmployee(AspNetUserEmployee?) - Set user employee mapping

Carrier-Specific Features

  • ChangeEquipmentTypes(ICollection<EquipmentType>) - Set equipment types (for carriers)
  • ChangeCarrierEquipments(ICollection<CarrierEquipment>) - Manage carrier equipment

Pricing and Discounts

  • ChangeDiscounts(List<DiscountObjectId>) - Set applicable discounts (publishes ContactChangedDiscounts event)

Extensibility

  • ChangeCustomValues(Dictionary<string, object?>?) - Update custom properties (merges with existing)
  • ChangeTags(List<string>?) - Update tag list

Order Relationships (Internal Use)

  • ChangeOrders(IEnumerable<Order>) - Set order collection
  • ChangeOrderCarriers(IEnumerable<OrderCarrier>) - Set order carrier assignments

Soft Delete

  • ChangeIsDeleted(bool) - Soft delete or restore contact

Helper Methods

  • GetBillingAddressId() - Retrieve billing address ID

Static Validation Methods

These methods determine what features are available based on ContactType:

  • CanHaveAccounting(ContactType) - Returns true for Carrier, Customer
  • CanHaveLinkedContact(ContactType) - Returns true for Contact, Carrier, Employee, Customer, Lead
  • MustHaveLinkedContact(ContactType) - Returns true for Contact type only
  • MustHavePhoneNumber(ContactType) - Returns true for most contact types
  • CanHaveUserEmployee(ContactType) - Returns true for Employee, Customer, PoolPoint, Store
  • CanHaveEquipments(ContactType) - Returns true for Carrier only

Domain Events

The Contact entity publishes two domain events:

ContactChangedDiscounts

Published when ChangeDiscounts() is called. Allows discount assignment logic to be handled asynchronously (e.g., updating junction table, recalculating pricing).

Event Properties:

  • _Contact - The contact being updated
  • DiscountIds - List of discount IDs to associate with this contact

ContactChangedDivisions

Published when ChangeAdditionalDivisions() is called. Allows multi-division access control to be managed asynchronously (e.g., updating junction table, propagating permissions).

Event Properties:

  • _Contact - The contact being updated
  • DivisionIds - List of division IDs this contact can access

Use Cases

1. Customer Onboarding with Complete Profile

# Create customer with full profile
- task: Contact/Create@1
name: onboardNewCustomer
inputs:
organizationId: ${organizationId}
contact:
name: "Tech Innovations LLC"
contactType: Customer
divisionId: ${divisionId}
accountNumber: "CUST-${autoIncrement}"
contactFirstName: "Michael"
contactLastName: "Chen"
emailAddress: "[email protected]"
phoneNumber: "+1-555-TECH"
website: "https://techinnovations.com"
idNumber: "45-6789012"
idNumberType: EIN
creditLimit: 75000.00
paymentTermId: ${net30TermId}
isACorporation: true
customValues:
salesRepName: "Jessica Martinez"
industryVertical: "Technology Hardware"
annualRevenue: 5000000
accountTier: "Gold"
outputs:
- contact: newCustomer

# Add multiple addresses
- task: ContactAddress/Create@1
name: addBillingAddress
inputs:
organizationId: ${organizationId}
contactAddress:
contactId: ${newCustomer.contactId}
addressType: Billing
addressLine: "500 Tech Center Dr"
cityName: "San Jose"
stateCode: "CA"
postalCode: "95110"
countryCode: "US"

- task: ContactAddress/Create@1
name: addShippingAddress
inputs:
organizationId: ${organizationId}
contactAddress:
contactId: ${newCustomer.contactId}
addressType: Shipping
addressLine: "1000 Warehouse Way"
cityName: "Fremont"
stateCode: "CA"
postalCode: "94538"
countryCode: "US"

- task: ContactAddress/Create@1
name: addPhysicalAddress
inputs:
organizationId: ${organizationId}
contactAddress:
contactId: ${newCustomer.contactId}
addressType: Physical
addressLine: "500 Tech Center Dr"
cityName: "San Jose"
stateCode: "CA"
postalCode: "95110"
countryCode: "US"

# Apply customer discounts
- task: Contact/Update@1
name: applyDiscounts
inputs:
organizationId: ${organizationId}
contactId: ${newCustomer.contactId}
contact:
contactDiscounts:
- discountId: ${goldTierDiscountId}
- discountId: ${volumeDiscount15PercentId}

2. Carrier Network Management

# Create carrier with DOT/MC numbers
- task: Contact/Create@1
name: createCarrierProfile
inputs:
organizationId: ${organizationId}
contact:
name: "Nationwide Freight Services"
contactType: Carrier
divisionId: ${divisionId}
accountNumber: "CARR-${sequence}"
phoneNumber: "+1-800-FREIGHT"
emailAddress: "[email protected]"
website: "https://nationwidefreight.com"
idNumber: "11-2233445"
idNumberType: EIN
paymentTermId: ${quickPayTermId}
customValues:
dotNumber: "7654321"
mcNumber: "MC-123456"
scacCode: "NWFS"
insuranceProvider: "Truckers Insurance Co"
insurancePolicyNumber: "POL-987654"
insuranceExpiry: "2026-12-31"
safetyRating: "Satisfactory"
serviceArea: ["CA", "NV", "AZ", "OR", "WA"]
outputs:
- contact: carrier

# Track carrier equipment
- task: Contact/Update@1
name: assignCarrierFleet
inputs:
organizationId: ${organizationId}
contactId: ${carrier.contactId}
contact:
carrierEquipments:
- equipmentTypeId: ${dryVan53ftId}
equipmentNumber: "TRL-001"
year: 2023
make: "Great Dane"
vin: "1GRAA06285P123456"
- equipmentTypeId: ${dryVan53ftId}
equipmentNumber: "TRL-002"
year: 2024
make: "Wabash"
- equipmentTypeId: ${tractorId}
equipmentNumber: "TRAC-100"
year: 2022
make: "Freightliner"

3. Employee Directory with Portal Access

# Create employee
- task: Contact/Create@1
name: createSalesRep
inputs:
organizationId: ${organizationId}
contact:
name: "Johnson, Emily"
contactType: Employee
divisionId: ${salesDivisionId}
contactFirstName: "Emily"
contactLastName: "Johnson"
emailAddress: "[email protected]"
phoneNumber: "+1-555-0188"
mobilePhoneNumber: "+1-555-0189"
customValues:
employeeId: "EMP-5678"
department: "Sales"
title: "Senior Account Executive"
hireDate: "2018-03-15"
manager: "Robert Smith"
commissionRate: 0.05
outputs:
- contact: salesRep

# Grant system access and multi-division permissions
- task: Contact/Update@1
name: configureEmployeeAccess
inputs:
organizationId: ${organizationId}
contactId: ${salesRep.contactId}
contact:
userId: ${ejohnsonUserId}
contactDivisions:
- divisionId: ${eastDivisionId}
- divisionId: ${westDivisionId}

Best Practices

1. Contact Type Selection

  • Choose the appropriate ContactType based on business role
  • Customer, Carrier types enable accounting transactions
  • Employee type enables internal system access
  • Contact type (generic) must always link to another contact

2. Phone Number Validation

  • Most ContactTypes require a phone number (use static MustHavePhoneNumber() to check)
  • Provide PhoneNumber for Carrier, Customer, Driver, Employee, FactoringCompany, ForwardingAgent, SalesPerson, Vendor

3. Address Management

  • Use ChangeContactAddresses() for address updates - it intelligently merges existing addresses
  • Provide AddressType (Billing, Shipping, Physical) for proper classification
  • Billing address is critical for invoicing - use GetBillingAddressId() helper

4. Hierarchical Contacts

  • Use ParentContactId for corporate hierarchies (parent company → subsidiaries)
  • Enable consolidated billing, reporting, and credit management at parent level
  • Store region or division-specific data in child contact CustomValues

5. Contact Linking

  • Use ContactLink for representing relationships between contacts (decision makers, authorized signers, etc.)
  • Generic Contact type is designed specifically for individuals linked to companies
  • ContactLinks are directional - consider creating bidirectional links when needed

6. Multi-Division Access

  • Use ChangeAdditionalDivisions() for contacts that span multiple divisions
  • Primary DivisionId is mandatory, additional divisions are optional
  • Event-driven architecture ensures junction table updates are handled consistently

7. Carrier-Specific Features

  • Only Carrier type can have equipment (enforced by CanHaveEquipments())
  • Store DOT number, MC number, SCAC code, insurance info in CustomValues
  • Track carrier equipment for capacity planning and dispatch

8. User Portal Access

  • Use AssignUserId() for employees or customer portal users
  • Only Employee, Customer, PoolPoint, Store types can have user access (see CanHaveUserEmployee())
  • Portal access enables self-service tracking, booking, document download

9. Custom Values Strategy

  • Use CustomValues for industry-specific fields (DOT numbers, NAICS codes, etc.)
  • Store extended contact information that doesn't fit standard fields
  • CustomValues support complex objects - use for storing arrays, nested data

10. Discount and Pricing Management

  • Apply customer-specific discounts using ChangeDiscounts()
  • Domain event architecture ensures discount changes trigger pricing recalculations
  • Combine contact discounts with other discount types (mode, product, etc.) for flexible pricing

11. Tags for Categorization

  • Use Tags for flexible categorization ("preferred-customer", "high-volume", "temperature-sensitive")
  • Tags enable filtering and reporting without rigid schema changes
  • Tags are searchable - use meaningful, consistent tag names

12. Soft Delete Over Hard Delete

  • Use ChangeIsDeleted(true) instead of deleting contact records
  • Preserves historical data for audit trails and reporting
  • Soft-deleted contacts can be filtered in queries while maintaining referential integrity