Integrations & AdvancedAllocations — Technical Implementation
Integrations & Advanced

Allocations — Technical Implementation

How Jules implements the allocation matching system — data model, creation flow, fulfilment state machines, document generation, and ERP sync.

Allocations — Technical Implementation

Developer documentation — Deep dive into the allocation system: data model, creation/batch flows, fulfilment step state machines, stepper configuration, document generation, and integration hooks.


Table of Contents

  1. Overview
  2. Data Model
  3. Creation Flow
  4. Batch Creation
  5. Blacklist Validation
  6. Fulfilment Step State Machines
  7. Stepper Configuration
  8. Document Generation
  9. ERP Synchronization
  10. Warehouse Allocations
  11. Searates Tracking
  12. GraphQL API Surface

Overview

An allocation is the join entity between a buy-side OperationQuality and a sell-side OperationQuality at the container level. It is the primary entity driving:

  • Commercial margin calculation
  • Logistics fulfilment tracking
  • Document generation (Annex 7, load report, custom invoice)
  • Tracking and delivery confirmation

Data Model

Allocation

model Allocation {
  id                       String   @id @default(uuid())
  haroldNumber             String   @unique
  status                   AllocationStatus  // DRAFT | CONFIRMED

  // Commercial links
  buyOperationQualityId    String
  buyOperationQuality      OperationQuality @relation("buy", fields: [buyOperationQualityId], references: [id])
  sellOperationQualityId   String?
  sellOperationQuality     OperationQuality? @relation("sell", fields: [sellOperationQualityId], references: [id])

  // Optional warehouse link (instead of sell operation)
  stockpileId              String?
  warehouseId              String?

  // References
  referenceNumber          String?
  comment                  String?

  // Tracking
  trackingStatus           AllocationTrackingStatus?

  // Fulfilment steps (stored as JSON or related records)
  annex7Status             Annex7Status
  bookingFulfilmentStatus  BookingFulfilmentStatus
  customsStatus            CustomsStatus
  loadReportStatus         LoadReportStatus
  isVGMSubmitted           Boolean  @default(false)
  loadDetailsCount         Int      @default(0)

  // Financial
  marginPerTon             Decimal?
  marginCurrency           String?

  // ERP
  erpId                    String?
  erpValue                 String?

  // Metadata
  createdById              String
  createdAt                DateTime @default(now())
  updatedAt                DateTime @updatedAt

  // Relations
  containers               ContainerToAllocation[]
  watchers                 AllocationWatcher[]
  photos                   AllocationPhoto[]
  documents                GeneratedDocument[]
}

AllocationTrackingStatus enum

LOADED | GATED_IN | SHIPPED | ARRIVED | GATED_OUT | RETURNED | RECOVERY_NOTE_SENT | RECOVERED

Fulfilment step enums

enum Annex7Status {
  PENDING
  PREPARED_IN_ERP
  SENT_TO_COMPLIANCE
  SENT_TO_SUPPLIER
  SIGNED_AND_UPLOADED
}

enum BookingFulfilmentStatus {
  PENDING
  PREPARED_IN_ERP
  PRECARRIAGE_BOOKING_OK
  FREIGHT_BOOKING_OK
  ALL_BOOKING_OK
}

enum CustomsStatus {
  PENDING
  SENT_TO_AGENT
  SENT_TO_CARRIER
}

enum LoadReportStatus {
  PENDING
  PREPARED
  SENT_TO_DOCS_TEAM
}

Creation Flow

Single allocation creation

Input validation:

  1. buyOperationQualityId must reference a confirmed buy operation quality
  2. sellOperationQualityId must reference a confirmed sell operation quality (or be null for warehouse allocations)
  3. Container IDs must belong to the buy operation
  4. No container can be allocated twice to the same sell operation quality

Initialization:

  • haroldNumber generated (sequence: ALLOC-{YEAR}-{SEQ})
  • All fulfilment step statuses initialized to PENDING
  • isVGMSubmitted = false
  • status = DRAFT (or CONFIRMED if passed in input)

Side effects on confirmation:

  • Container followUpStatus may transition
  • Margin calculation triggered
  • Watchers notified

Batch Creation

createManyAllocations creates one allocation per container in a single operation:

// Input
{
  buyOperationQualityId: string
  sellOperationQualityId: string
  containerIds: string[]         // one allocation per container ID
  status: AllocationStatus
  referenceNumber?: string
  comment?: string
}

// Output: Allocation[]  (one per containerIds entry)

Implementation:

  1. Validate the buy/sell quality pair once
  2. Run blacklist check once (same buy site → same check applies to all)
  3. For each containerId, run createAllocation with the same parameters
  4. Return all created allocations

Atomicity: the entire batch runs in a single Prisma transaction — either all allocations are created or none are.


Blacklist Validation

Before an allocation can be confirmed (status = CONFIRMED), Jules checks the site blacklist:

The check queries SiteToBlacklistedSite where:

  • siteId = the sell operation's customer site ID
  • blacklistedSiteId = the buy operation's supplier site ID

If a match is found, the mutation throws a ValidationError with a descriptive message.


Fulfilment Step State Machines

Each of the 6 fulfilment steps has its own state machine. They are independent — advancing one step does not automatically advance others.

Annex 7 state machine

Booking fulfilment state machine

Updating step status

Each step is updated via a dedicated mutation field:

StepMutationInput
Annex 7updateAllocationAnnex7Status{ id, annex7Status }
BookingupdateAllocationBookingStatus{ id, bookingFulfilmentStatus }
CustomsupdateAllocationCustomsStatus{ id, customsStatus }
Load ReportupdateAllocationLoadReportStatus{ id, loadReportStatus }
VGMupdateAllocationVGM{ id, isVGMSubmitted }
Load DetailsupdateAllocationLoadDetails{ id, loadDetailsCount }

Or via updateAllocation with the step field included in the update input.


Stepper Configuration

Organizations can customize which fulfilment steps are visible and in what order via AllocationStepperConfig:

// stepperConfigs: which steps are enabled
type AllocationStepperConfig = {
  annex7: boolean
  booking: boolean
  customs: boolean
  loadReport: boolean
  vgm: boolean
  loadDetails: boolean
  order: string[]  // e.g. ['annex7', 'booking', 'customs', 'loadReport', 'vgm']
}

// stepperValues: current values of each step (the status fields above)
type AllocationStepperValues = {
  annex7Status: Annex7Status
  bookingFulfilmentStatus: BookingFulfilmentStatus
  customsStatus: CustomsStatus
  loadReportStatus: LoadReportStatus
  isVGMSubmitted: boolean
  loadDetailsCount: number
}

The stepper config is stored per organization in the settings and applied at query time. The frontend reads stepperConfigs to determine which steps to render.


Document Generation

Allocations are the primary source for document generation:

DocumentMutationOutput
Annex 7generateAnnex7PDF — environmental compliance form
Custom InvoicegenerateCustomInvoicePDF — commercial invoice for customs
Load ReportgenerateLoadReportPDF — container weights and contents
Recovery NotegenerateRecoveryNotePDF — confirmation of material recovery

Generation pipeline

GeneratedDocument model

model GeneratedDocument {
  id           String   @id
  allocationId String
  type         DocumentType  // ANNEX7 | CUSTOM_INVOICE | LOAD_REPORT | RECOVERY_NOTE
  fileUrl      String
  generatedAt  DateTime
  generatedById String
}

Photos

model AllocationPhoto {
  id           String   @id
  allocationId String
  type         PhotoType  // ANNEX7 | GENERAL
  fileUrl      String
  uploadedAt   DateTime
}

ERP Synchronization

synchronizeAllocationToErp pushes the allocation to the external ERP:

  1. Load allocation with buy/sell operation data and container list
  2. Validate: allocation must be CONFIRMED
  3. Build ERP payload (organization-specific mapping)
  4. POST to ERP integration endpoint
  5. Store ERP response: erpId, erpValue on the allocation

ERP sync status can be checked by the presence/absence of erpId.


Warehouse Allocations

When an allocation targets a warehouse (instead of a sell operation):

// Create warehouse allocation
createAllocation({
  buyOperationQualityId: "...",
  sellOperationQualityId: null,  // no sell operation
  stockpileId: "...",            // target stockpile
  warehouseId: "...",            // target warehouse site
  containerIds: [...]
})

Key differences from standard allocations:

  • No sell operation quality linked
  • marginPerTon is not available at creation (margin is calculated when material is sold from stockpile)
  • trackingStatus still applies (containers are physically moved to the warehouse)
  • Document generation (load report) still works

Searates Tracking

Each allocation can have a Searates tracking record:

type SearatesTracking = {
  allocationId: string
  latitude: number
  longitude: number
  locationName: string
  countryCode: string
  events: SearatesEvent[]
  vessel: string
  voyage: string
  updatedAt: DateTime
}

type SearatesEvent = {
  eventType: string
  location: string
  eventDate: DateTime
  description: string
}

Tracking data is fetched via the Searates API using the container reference number and updated on a scheduled basis or on demand.


GraphQL API Surface

Queries

QueryDescription
filteredAllocationsList allocations with filters (status, tracking, ops, date ranges)
allocation(id)Single allocation by ID
allocationByHaroldNumber(haroldNumber)Lookup by Harold number

Mutations

MutationDescription
createAllocationSingle allocation
createManyAllocationsBatch creation (one per container)
updateAllocationUpdate any fields
updateManyAllocationsBatch update by IDs
updateAllocationsBLByIdsSet BL number on multiple allocations
updateAllocationsBLByReferenceNumbersSet BL by reference numbers
deleteAllocationDelete a DRAFT allocation
generateAnnex7Generate Annex 7 PDF
generateCustomInvoiceGenerate custom invoice PDF
generateLoadReportGenerate load report PDF
generateRecoveryNoteGenerate recovery note PDF
synchronizeAllocationToErpSync to external ERP
addAllocationWatcherAdd a watcher
removeAllocationWatcherRemove a watcher