mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-27 15:30:56 +00:00
1873 lines
83 KiB
Markdown
1873 lines
83 KiB
Markdown
# Contract–Booking Separation & Global Logistics Design
|
||
|
||
**Version:** 1.1
|
||
**Date:** 2026-06-26
|
||
**Revision:** Customs-clearance-first path — GL owns booking creation; customer pays only.
|
||
**Scope:** Contracts, bookings, scheduling, allocation, and Global Logistics (GL) workflows
|
||
**References:**
|
||
- [ITLMS Operation Workflow V2.pdf](./ITLMS%20Operation%20Workflow%20V2.pdf)
|
||
- [ITMLS Customer and Marketing User Stories V3.0 (2).pdf](./ITMLS%20Customer%20and%20Marketing%20User%20Stories%20V3.0%20(2).pdf)
|
||
- [GlobaL Logistics- Unimodal Import and Export and Multimodal Import V02.docx.pdf](./GlobaL%20Logistics-%20Unimodal%20Import%20and%20Export%20and%20Multimodal%20Import%20V02.docx.pdf)
|
||
- Codebase: `edr-platform/apps/edr-freight-api`, `edr-platform/apps/edr-freight-web`
|
||
|
||
---
|
||
|
||
## Table of Contents
|
||
|
||
1. [Executive Summary](#1-executive-summary)
|
||
2. [Design Principles](#2-design-principles)
|
||
3. [Current Architecture](#3-current-architecture)
|
||
4. [Target Architecture](#4-target-architecture)
|
||
5. [Database Schema](#5-database-schema)
|
||
6. [Status Machines](#6-status-machines)
|
||
7. [Contract Wizard Specification](#7-contract-wizard-specification)
|
||
8. [Booking Creation Specification](#8-booking-creation-specification)
|
||
9. [Pricing Model](#9-pricing-model)
|
||
10. [Renewal and Expiry Rules](#10-renewal-and-expiry-rules)
|
||
11. [Global Logistics — Import Unimodal](#11-global-logistics--import-unimodal)
|
||
12. [Global Logistics — Export Unimodal](#12-global-logistics--export-unimodal)
|
||
13. [Customs Clearance Path — GL-Owned Execution](#13-customs-clearance-path--gl-owned-execution)
|
||
14. [Gap Analysis Matrix](#14-gap-analysis-matrix)
|
||
15. [API Endpoint Mapping](#15-api-endpoint-mapping)
|
||
16. [Frontend Route & Component Mapping](#16-frontend-route--component-mapping)
|
||
17. [Migration Plan](#17-migration-plan)
|
||
18. [Out of Scope / Future Work](#18-out-of-scope--future-work)
|
||
19. [Open Items for Sign-Off](#19-open-items-for-sign-off)
|
||
|
||
---
|
||
|
||
## 1. Executive Summary
|
||
|
||
The EDR freight platform currently **collapses the legal contract and the operational shipment into a single `freight.bookings` row**. The customer wizard collects container quantities, weights, and binding shipment details at intake; clearance documents, contract signatures, batch scheduling, and payment all attach to that same row.
|
||
|
||
Stakeholder requirements and the reference PDFs describe a **two-phase lifecycle**:
|
||
|
||
1. **Contract phase** — customer selects operation type, contract kind (general / one-time), service, cargo *scope* (20ft/40ft sizes or bulk commodity — **no quantities**), routes, hazard/reefer flags, estimated date, intake documents, and **unit-rate pricing**. Marketing approves, both parties sign. No booking exists yet.
|
||
2. **Execution phase** — behaviour splits by whether the contract includes **customs clearance (Global Logistics service)**:
|
||
|
||
**Path A — Transport only (no customs):** After contract is fully executed, the **customer** creates a shipment booking (schedule date, quantities, container numbers, VGM, hazard/reefer counts), then enters batch → payment → allocation → transit as today.
|
||
|
||
**Path B — Transport with customs clearance:** After contract is fully executed, the **customer uploads clearance documents to GL** (no booking yet). Ethiopian GL reviews and approves customer docs, uploads GL output documents, and runs the clearance workflow. **GL Ethiopia exclusively creates the booking** — entering schedule date, quantities, container numbers, VGM, bulk ton/item counts, and all other shipment details. The **customer does not use the booking wizard**. From booking creation onward, batch selection, wagon allocation, milestones, and transit proceed as today; the **only customer action in the operational pipeline is payment** (freight PNR, and duty/tax slips when advised during clearance).
|
||
|
||
This document specifies a **minimal-change evolution**: introduce a first-class `freight.contracts` table by extracting contract-phase columns from `bookings`, add `contract_id` to bookings, and preserve all existing scheduling, payment, and allocation mechanics.
|
||
|
||
**Confirmed stakeholder decisions:**
|
||
- **Customs clearance path (Path B):** Clearance documents are collected **after contract signing and before any booking row exists**. GL owns booking creation and data entry; customer pays only.
|
||
- **Transport-only path (Path A):** Customer creates bookings under the contract as normal.
|
||
- Each shipment cycle under a customs contract follows: sign contract → customer clearance docs → GL approval → GL creates booking → customer pays → ops pipeline.
|
||
- One-time contracts allow **one active booking at a time**; if payment expires the booking expires but the contract stays valid for a new clearance cycle + GL re-booking until contract validity ends.
|
||
- General contracts allow **multiple shipment cycles** over the validity window (each cycle: clearance docs → GL booking → payment) with no quantity pool caps at contract level.
|
||
|
||
---
|
||
|
||
## 2. Design Principles
|
||
|
||
| Principle | Rationale |
|
||
|-----------|-----------|
|
||
| **Extract, don't rewrite** | Keep `payments`, `train_schedules`, `wagon_booking_allocations`, and batch engine untouched; link via `bookings.contract_id`. |
|
||
| **Unit rates at contract, totals at booking** | Contract pricing shows per-container / per-ton / per-item rates; booking computes actual totals from entered quantities. |
|
||
| **Scope vs. execution** | Contract defines *what is allowed* (sizes, commodities, routes, flags); booking defines *what is shipped* (qty, container numbers, dates). |
|
||
| **Pre-booking clearance (customs path)** | For contracts with customs clearance, customer uploads clearance docs **after signing, before booking exists** — stored on `contract_id` via `contract_document_review`. |
|
||
| **GL-owned booking (customs path)** | When `customs_clearing_enabled = true`, only Ethiopian GL creates bookings and enters all shipment fields; customer portal has no booking wizard. |
|
||
| **Customer pays only (customs path)** | After GL creates the booking and it reaches the payment gate, the customer pays freight PNR (and duty/tax slips during clearance when advised). All other steps are GL/Ops. |
|
||
| **Contract intake docs separate** | Wizard step 6 documents (commercial framework / onboarding attachments) attach to `contract_id` at submission — distinct from post-sign clearance docs. |
|
||
| **Preserve approval hierarchy** | Line Staff → Director → CEO routing from US-06 moves to `contract_approval_steps`; booking operations review stays on booking. |
|
||
| **Dual GL teams** | Ethiopian GL (`edr_gl_ethiopia`) and Djibouti GL (`edr_gl_djibouti`) with region-scoped queues and milestone ownership. |
|
||
| **Backward-compatible payments** | Payment webhooks continue using `ref_id = booking.id`; no payment microservice change. |
|
||
|
||
---
|
||
|
||
## 3. Current Architecture
|
||
|
||
### 3.1 Data Model (Today)
|
||
|
||
All entities live in PostgreSQL schema **`freight`**.
|
||
|
||
```mermaid
|
||
erDiagram
|
||
companies ||--o{ bookings : owns
|
||
bookings ||--o{ booking_container : has_qty_at_intake
|
||
bookings ||--o{ booking_contract_signatures : signs
|
||
bookings ||--o{ booking_document_review : clearance
|
||
bookings ||--o{ booking_approval_step : approves
|
||
bookings ||--o{ booking_rate_snapshot : rates
|
||
bookings ||--o{ payments : pays
|
||
bookings ||--o| train_schedule_bookings : scheduled
|
||
bookings ||--o{ booking_orders : general_contract_drawdown
|
||
booking_orders ||--o| bookings : spawns_child_ONE_TIME
|
||
bookings ||--o{ contract_route_lines : multi_route_qty_pools
|
||
```
|
||
|
||
**Key observation:** `booking_type = GENERAL_CONTRACT` bookings are contracts; `booking_orders` spawns child `ONE_TIME` bookings for each drawdown. One-time bookings are simultaneously the contract and the shipment.
|
||
|
||
### 3.2 Current API Modules
|
||
|
||
| Module | Path | Role |
|
||
|--------|------|------|
|
||
| Bookings | `src/modules/bookings/` | Create, price, submit, approve, sign, clearance, operation review |
|
||
| Booking Orders | `src/modules/booking-orders/` | General contract drawdown (`POST /booking-orders`) |
|
||
| Payment | `src/modules/payment/` | PNR, webhook finalization |
|
||
| Train Scheduling | `src/modules/train-scheduling/` | Batch pool, wagon allocation |
|
||
| Contracts (PDF) | `src/contracts/` | Handlebars templates, PDF generation — operates on booking today |
|
||
|
||
### 3.3 Current Booking API Endpoints
|
||
|
||
Base path: `/bookings`
|
||
|
||
| Method | Path | Phase |
|
||
|--------|------|-------|
|
||
| POST | `/` | Create (contract + shipment combined) |
|
||
| POST | `/:id/generate-price` | Pricing |
|
||
| POST | `/:id/submit` | Submit for approval |
|
||
| POST | `/:id/staff/accept` | Staff intake + contract validity |
|
||
| POST | `/:id/approval-steps/:stepId/approve` | Approval chain |
|
||
| POST | `/:id/contract/generate` | Generate contract PDF |
|
||
| POST | `/:id/contract/sign` | Customer / staff signature |
|
||
| POST | `/:id/clearance/*` | GL document gate |
|
||
| POST | `/:id/clearance/proceed` | Customer picks shipment day |
|
||
| POST | `/:id/operation/review` | Operations accept |
|
||
| POST | `/:id/payment/pay` | Payment |
|
||
| GET | `/queues/:queue` | Staff queues |
|
||
|
||
Booking orders: `POST /booking-orders`, `GET /booking-orders/contract/:id/pool`
|
||
|
||
### 3.4 Current Frontend Flow
|
||
|
||
**Portal** (`edr-freight-web/portal/`):
|
||
|
||
| Route | Component | Purpose |
|
||
|-------|-----------|---------|
|
||
| `/bookings/new` | `NewBookingPage.tsx` | 7-step wizard (operation → contract type → service → cargo → route → docs → review) |
|
||
| `/bookings/:id` | `BookingDetailPage` | Lifecycle actions |
|
||
| `/bookings/:id/contract` | `BookingContractPage` | Sign contract |
|
||
| `/contracts` | `ContractsList.tsx` | Lists `GENERAL_CONTRACT` bookings |
|
||
| `/contracts/:id` | `ContractDetailPage` | Pool + `PlaceOrderDialog` drawdown |
|
||
|
||
**Backoffice** (`edr-freight-web/backoffice/`):
|
||
|
||
| Route | Component | Purpose |
|
||
|-------|-----------|---------|
|
||
| `/dashboard/booking-requests` | `BookingRequestsPage` | Marketing approval |
|
||
| `/dashboard/clearance` | `DocumentClearanceListPage` | Single GL queue |
|
||
|
||
### 3.5 Current Lifecycle (One-Time Booking)
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
A[DRAFT wizard with qty and weight] --> B[generate-price total amount]
|
||
B --> C[SUBMITTED]
|
||
C --> D[PENDING_APPROVAL staff accept sets validity]
|
||
D --> E[Approval chain APPROVED]
|
||
E --> F[CONTRACT_READY generate PDF]
|
||
F --> G[SIGNED_CUSTOMER]
|
||
G --> H[Staff counter-sign FULLY_EXECUTED or AWAITING_DOCUMENTS]
|
||
H --> I{Customs service?}
|
||
I -->|Yes IMPORT/EXPORT| J[Clearance on booking]
|
||
I -->|No DOMESTIC| K[Operation request]
|
||
J --> K[OPERATION_REQUEST_PENDING pick day]
|
||
K --> L[Ops review FULLY_EXECUTED]
|
||
L --> M[Batch pool SELECTED_FOR_BATCH]
|
||
M --> N[Payment PAID]
|
||
N --> O[Wagon allocation IN_TRANSIT]
|
||
O --> P[COMPLETED]
|
||
```
|
||
|
||
**General contract today:** Same through signing → `CONTRACT_ACTIVE` → `PlaceOrderDialog` spawns child booking via `booking_orders` → child enters clearance/batch pipeline. Quantity pools tracked in `contract_route_lines.quantity` and `booking_container.quantity`.
|
||
|
||
### 3.6 Current Clearance Model
|
||
|
||
- Utility: `clearance.util.ts` resolves setting codes from `trade_direction`, `freight_type`, `includesCustoms`.
|
||
- Review rows: `booking_document_review` keyed by `(booking_id, setting_code, file_key)`.
|
||
- Single IAM role: `edr_global_logistics` with `bookings:clearance_view`, `bookings:review_documents`, `bookings:upload_clearance_output`, `bookings:finalize_clearance`.
|
||
- Three booking statuses for clearance gate: `AWAITING_DOCUMENTS` → `DOCUMENTS_UNDER_REVIEW` → `CLEARANCE_READY`.
|
||
|
||
---
|
||
|
||
## 4. Target Architecture
|
||
|
||
### 4.1 Conceptual Split
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
subgraph contractPhase [Contract Phase]
|
||
C[contracts]
|
||
CR[contract_routes]
|
||
CCS[contract_cargo_scope]
|
||
CRS[contract_rate_snapshots]
|
||
CAS[contract_approval_steps]
|
||
CSIG[contract_signatures]
|
||
CDOC[contract intake files]
|
||
end
|
||
|
||
subgraph bookingPhase [Booking Phase per Shipment]
|
||
B[bookings]
|
||
BC[booking_container + container_units]
|
||
BDR[booking_document_review]
|
||
BRS[booking_rate_snapshots]
|
||
PAY[payments]
|
||
TSB[train_schedule_bookings]
|
||
WBA[wagon_booking_allocations]
|
||
end
|
||
|
||
C --> CR
|
||
C --> CCS
|
||
C --> CRS
|
||
C --> CAS
|
||
C --> CSIG
|
||
C --> CDOC
|
||
C --> B
|
||
B --> BC
|
||
B --> BDR
|
||
B --> BRS
|
||
B --> PAY
|
||
B --> TSB
|
||
B --> WBA
|
||
```
|
||
|
||
### 4.2 Target Lifecycle — Dual Execution Paths
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
subgraph contract [Contract Lifecycle — both paths]
|
||
C1[DRAFT contract wizard unit rates only]
|
||
C1 --> C2[SUBMITTED approval sign]
|
||
C2 --> C3[FULLY_EXECUTED or CONTRACT_ACTIVE]
|
||
end
|
||
|
||
C3 --> Fork{Customs clearance service?}
|
||
|
||
subgraph pathA [Path A — Transport only]
|
||
A1[Customer creates booking wizard]
|
||
A1 --> A2[Operation request batch]
|
||
A2 --> A3[Customer pays]
|
||
A3 --> A4[Allocation transit COMPLETED]
|
||
end
|
||
|
||
subgraph pathB [Path B — Transport with customs clearance]
|
||
B1[Customer uploads clearance docs on contract]
|
||
B1 --> B2[GL ET reviews approves customer docs]
|
||
B2 --> B3[GL ET and GL DJ upload output docs milestones]
|
||
B3 --> B4[GL ET creates booking all shipment data]
|
||
B4 --> B5[Batch pool customer pays freight only]
|
||
B5 --> B6[GL Ops milestones allocation transit]
|
||
B6 --> B7[COMPLETED or EXPIRED payment]
|
||
end
|
||
|
||
Fork -->|No| A1
|
||
Fork -->|Yes| B1
|
||
|
||
B7 -->|ONE_TIME or GENERAL next cycle| B1
|
||
A4 -->|GENERAL next shipment| A1
|
||
```
|
||
|
||
**Path B summary (customs clearance contracts):**
|
||
|
||
| Step | Actor | Action |
|
||
|------|-------|--------|
|
||
| 1 | Customer | Sign contract (Marketing counter-sign → `FULLY_EXECUTED` / `CONTRACT_ACTIVE`) |
|
||
| 2 | Customer | Upload clearance documents on **contract** (BL, invoice, license, etc.) — **no booking row yet** |
|
||
| 3 | GL Ethiopia | Review, approve, or query each document |
|
||
| 4 | GL Ethiopia / Djibouti | Upload customs output docs, advance clearance milestones |
|
||
| 5 | GL Ethiopia | **Create booking** — enter route, binding schedule date, container qty/numbers/VGM, bulk ton/item count, hazard/reefer counts |
|
||
| 6 | System / Ops | Batch selection, PNR generation |
|
||
| 7 | **Customer** | **Pay freight** (and duty/tax slips when advised during step 4) |
|
||
| 8 | GL / Ops | Wagon allocation, loading, departure, arrival, remaining milestones |
|
||
| 9 | Customer | Track shipment; no further data entry unless queried docs |
|
||
|
||
**Path A summary (transport-only contracts):** Unchanged from prior design — customer uses booking wizard after contract sign; customer pays at batch gate.
|
||
|
||
### 4.2.1 Shipment Cycles on General Contracts (Customs)
|
||
|
||
For `contract_kind = GENERAL` with customs clearance, each new shipment repeats the Path B clearance cycle on the same contract:
|
||
|
||
```
|
||
CONTRACT_ACTIVE
|
||
→ AWAITING_CLEARANCE_DOCUMENTS (customer uploads for this shipment)
|
||
→ CLEARANCE_UNDER_REVIEW (GL ET reviews)
|
||
→ CLEARANCE_READY_FOR_BOOKING (GL cleared to create booking)
|
||
→ [GL creates booking] (contract may show ACTIVE_BOOKING_IN_PROGRESS)
|
||
→ [booking completes / expires]
|
||
→ CONTRACT_ACTIVE (ready for next shipment cycle)
|
||
```
|
||
|
||
Use optional `contract_clearance_cycles` (see §5.16) to distinguish multiple clearance rounds on one contract.
|
||
|
||
### 4.3 What Stays Unchanged
|
||
|
||
- `freight.payments` — `ref_id` remains booking UUID
|
||
- `freight.train_schedules`, `train_schedule_bookings`
|
||
- `freight.wagon_booking_allocations`, `wagon_allocation_container_items`, `wagon_allocation_bulk_loads`
|
||
- `BookingBatchService` — day-level pool, payment window, `EXPIRED` on booking
|
||
- `RuleEngineService` — rates, modifiers, weight limits (applied at booking with actual qty)
|
||
- `file_upload_settings` seeder pattern — extend with phased GL codes
|
||
- Contract PDF templates in `src/contracts/templates/` — resolver reads `contracts` instead of `bookings`
|
||
|
||
### 4.4 What Is Deprecated (Phased)
|
||
|
||
| Current | Replacement |
|
||
|---------|-------------|
|
||
| `bookings.booking_type = GENERAL_CONTRACT` | `contracts.contract_kind` |
|
||
| `booking_orders` ledger | Direct `bookings.contract_id` FK |
|
||
| `contract_route_lines.quantity` | Removed — routes only |
|
||
| `general-contract.service.getQuantityLines()` pool math | Validity window + optional soft limits only |
|
||
| `POST /bookings` for contract creation | `POST /contracts` |
|
||
| Contract fields on `bookings` | Migrated to `contracts` |
|
||
|
||
---
|
||
|
||
## 5. Database Schema
|
||
|
||
### 5.1 Entity Relationship (Target)
|
||
|
||
```mermaid
|
||
erDiagram
|
||
companies ||--o{ contracts : owns
|
||
contracts ||--o{ contract_routes : has
|
||
contracts ||--o{ contract_cargo_scope : defines_scope
|
||
contracts ||--o{ contract_rate_snapshots : unit_rates
|
||
contracts ||--o{ contract_approval_steps : has
|
||
contracts ||--o{ contract_signatures : has
|
||
contracts ||--o{ contract_review_notes : has
|
||
contracts ||--o{ contract_document_review : pre_booking_clearance
|
||
contracts ||--o{ contract_clearance_cycles : shipment_cycles
|
||
contracts ||--o| contracts : renewal_of
|
||
contracts ||--o{ bookings : spawns
|
||
bookings ||--o{ booking_container : has
|
||
bookings ||--o{ booking_container_units : container_numbers
|
||
bookings ||--o{ booking_document_review : clearance
|
||
bookings ||--o{ booking_rate_snapshot : computed_total
|
||
bookings ||--o{ clearance_milestones : gl_tracking
|
||
bookings ||--o{ payments : pays
|
||
bookings ||--o| train_schedule_bookings : scheduled
|
||
yards ||--o{ contract_routes : origin_dest
|
||
service_types ||--o{ contracts : defines
|
||
cargo_types ||--o{ contract_cargo_scope : commodity
|
||
```
|
||
|
||
---
|
||
|
||
### 5.2 `freight.contracts` (NEW)
|
||
|
||
Primary legal/commercial agreement. Replaces the contract-phase portion of `bookings`.
|
||
|
||
```sql
|
||
CREATE TABLE freight.contracts (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
reference VARCHAR(64) NOT NULL UNIQUE, -- CTR-2026-00001
|
||
|
||
-- Ownership
|
||
company_id UUID REFERENCES freight.companies(id),
|
||
company_profile_id UUID REFERENCES freight.company_profiles(id),
|
||
is_government BOOLEAN NOT NULL DEFAULT FALSE,
|
||
government_institution VARCHAR(255),
|
||
|
||
-- Classification
|
||
contract_kind VARCHAR(20) NOT NULL, -- ONE_TIME | GENERAL
|
||
renewal_of_id UUID REFERENCES freight.contracts(id),
|
||
trade_direction VARCHAR(10) NOT NULL, -- IMPORT | EXPORT | DOMESTIC
|
||
freight_type VARCHAR(20) NOT NULL, -- CONTAINER | BULK
|
||
|
||
-- Service & commercial
|
||
service_type_id UUID NOT NULL REFERENCES freight.service_types(id),
|
||
payment_currency VARCHAR(5) NOT NULL, -- ETB | USD
|
||
customs_clearing_enabled BOOLEAN NOT NULL DEFAULT FALSE,
|
||
customs_clearing_agent VARCHAR(200),
|
||
equipment_return VARCHAR(20), -- with_return | without_return
|
||
|
||
-- First / last mile (copied from booking.entity.ts)
|
||
first_mile_pickup_address TEXT,
|
||
first_mile_pickup_lat NUMERIC(10,7),
|
||
first_mile_pickup_lng NUMERIC(10,7),
|
||
last_mile_delivery_address TEXT,
|
||
last_mile_delivery_lat NUMERIC(10,7),
|
||
last_mile_delivery_lng NUMERIC(10,7),
|
||
|
||
-- Cargo flags at contract level (NOT derived from container type)
|
||
is_hazardous BOOLEAN NOT NULL DEFAULT FALSE,
|
||
is_reefer BOOLEAN NOT NULL DEFAULT FALSE,
|
||
|
||
-- Dates
|
||
estimated_shipment_date TIMESTAMPTZ, -- non-binding estimate from wizard
|
||
contract_validity_days INT,
|
||
contract_valid_from TIMESTAMPTZ,
|
||
contract_valid_until TIMESTAMPTZ,
|
||
expires_at TIMESTAMPTZ, -- GENERAL ordering window end
|
||
|
||
-- Workflow
|
||
status VARCHAR(40) NOT NULL DEFAULT 'DRAFT',
|
||
|
||
-- Pricing (unit rates only at contract phase)
|
||
pricing_breakdown JSONB, -- displayMode: UNIT_RATES
|
||
pricing_display_mode VARCHAR(20) DEFAULT 'UNIT_RATES',
|
||
|
||
-- Contract document generation
|
||
contract_type VARCHAR(20), -- SPOT etc.
|
||
contract_template_key VARCHAR(128),
|
||
contract_generated_at TIMESTAMPTZ,
|
||
contract_summary TEXT,
|
||
version_number INT NOT NULL DEFAULT 1,
|
||
financial_terms JSONB,
|
||
|
||
-- Signature timestamps (denormalized for queries)
|
||
approved_by_staff_id UUID,
|
||
approved_by_staff_at TIMESTAMPTZ,
|
||
signed_by_director_id UUID,
|
||
signed_by_director_at TIMESTAMPTZ,
|
||
signed_by_ceo_id UUID,
|
||
signed_by_ceo_at TIMESTAMPTZ,
|
||
customer_signed_at TIMESTAMPTZ,
|
||
fully_executed_at TIMESTAMPTZ,
|
||
locked_at TIMESTAMPTZ,
|
||
|
||
-- Audit
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
deleted_at TIMESTAMPTZ
|
||
);
|
||
|
||
CREATE INDEX idx_contracts_company ON freight.contracts(company_id);
|
||
CREATE INDEX idx_contracts_status ON freight.contracts(status);
|
||
CREATE INDEX idx_contracts_kind ON freight.contracts(contract_kind);
|
||
CREATE INDEX idx_contracts_valid_until ON freight.contracts(contract_valid_until);
|
||
```
|
||
|
||
**Columns migrated FROM `bookings`:** `contract_validity_days`, `contract_valid_from`, `contract_valid_until`, `contract_type`, `contract_template_key`, `contract_generated_at`, `contract_summary`, `version_number`, `financial_terms`, signature timestamp columns, `expires_at` (GENERAL only), `is_hazardous`, `is_reefer`, service/mile/customs fields, `estimated_shipment_date`.
|
||
|
||
**Columns NOT on contracts:** `scheduled_date`, `payment_status`, `pnr_code`, `train_schedule_id`, `scheduling_status`, `priority_score`, `total_amount` (booking computed total).
|
||
|
||
**Columns ADDED for customs path on contracts:**
|
||
|
||
| Column | Type | Purpose |
|
||
|--------|------|---------|
|
||
| `clearance_status` | varchar | Pre-booking clearance gate: `NOT_APPLICABLE`, `AWAITING_DOCUMENTS`, `DOCUMENTS_UNDER_REVIEW`, `CLEARANCE_READY_FOR_BOOKING`, `ACTIVE_SHIPMENT_IN_PROGRESS` |
|
||
| `clearance_cycle_number` | int | Incremented per shipment cycle on GENERAL contracts |
|
||
|
||
---
|
||
|
||
### 5.3 `freight.contract_routes` (EVOLVE `contract_route_lines`)
|
||
|
||
Defines allowed origin/destination pairs. **No quantity.**
|
||
|
||
```sql
|
||
CREATE TABLE freight.contract_routes (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
contract_id UUID NOT NULL REFERENCES freight.contracts(id) ON DELETE CASCADE,
|
||
origin_yard_id UUID NOT NULL REFERENCES freight.yards(id),
|
||
destination_yard_id UUID NOT NULL REFERENCES freight.yards(id),
|
||
km NUMERIC(10,2), -- road billing distance; null for rail-only
|
||
sort_order SMALLINT NOT NULL DEFAULT 0,
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
|
||
UNIQUE (contract_id, origin_yard_id, destination_yard_id)
|
||
);
|
||
|
||
CREATE INDEX idx_contract_routes_contract ON freight.contract_routes(contract_id);
|
||
```
|
||
|
||
**Migration:** `ALTER TABLE freight.contract_route_lines RENAME TO contract_routes; ALTER ... RENAME COLUMN contract_booking_id TO contract_id; DROP COLUMN quantity; DROP COLUMN container_type_id;`
|
||
|
||
**Rules:**
|
||
- `ONE_TIME`: exactly 1 route row (enforce via application or CHECK)
|
||
- `GENERAL`: 1..N route rows
|
||
|
||
---
|
||
|
||
### 5.4 `freight.contract_cargo_scope` (NEW)
|
||
|
||
Defines what cargo sizes/types are in scope without quantities.
|
||
|
||
```sql
|
||
CREATE TABLE freight.contract_cargo_scope (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
contract_id UUID NOT NULL REFERENCES freight.contracts(id) ON DELETE CASCADE,
|
||
|
||
-- Container scope: one row per enabled size
|
||
container_size VARCHAR(10), -- '20ft' | '40ft'; NULL for bulk
|
||
|
||
-- Bulk scope
|
||
cargo_type_id UUID REFERENCES freight.cargo_types(id),
|
||
cargo_free_text VARCHAR(200),
|
||
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
|
||
-- Container: unique per size per contract
|
||
CONSTRAINT uq_contract_container_size
|
||
UNIQUE NULLS NOT DISTINCT (contract_id, container_size)
|
||
);
|
||
|
||
CREATE INDEX idx_contract_cargo_scope_contract ON freight.contract_cargo_scope(contract_id);
|
||
```
|
||
|
||
**Validation rules:**
|
||
- `freight_type = CONTAINER`: at least one row with `container_size IN ('20ft','40ft')`; `cargo_type_id` optional (commodity label for contract PDF)
|
||
- `freight_type = BULK`: exactly one row with `cargo_type_id` required; `container_size` must be NULL
|
||
- No `quantity`, `vgm`, or detailed `container_type_id` (no "20ft Reefer" — reefer is `contracts.is_reefer` boolean)
|
||
|
||
---
|
||
|
||
### 5.5 `freight.contract_signatures` (RENAME `booking_contract_signatures`)
|
||
|
||
```sql
|
||
CREATE TABLE freight.contract_signatures (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
contract_id UUID NOT NULL REFERENCES freight.contracts(id) ON DELETE CASCADE,
|
||
role VARCHAR(20) NOT NULL, -- CUSTOMER | STAFF | DIRECTOR | CEO
|
||
signer_display_name VARCHAR(255) NOT NULL,
|
||
signature_file_id UUID REFERENCES freight.files(id),
|
||
consent_text TEXT,
|
||
signed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||
);
|
||
|
||
CREATE INDEX idx_contract_signatures_contract ON freight.contract_signatures(contract_id);
|
||
```
|
||
|
||
---
|
||
|
||
### 5.6 `freight.contract_approval_steps` (CLONE pattern from `booking_approval_step`)
|
||
|
||
Same structure as `booking_approval_step` but FK → `contract_id`. Instantiated at staff accept from `approval_rules` based on cargo classification (US-06: Standard Container → Line Staff + Director; Bulk → Directors + CEO).
|
||
|
||
---
|
||
|
||
### 5.7 `freight.contract_rate_snapshots` (CLONE from `booking_rate_snapshot`)
|
||
|
||
Frozen **unit rates** at contract submit time. One row per rate line with `unit_of_measure` (`per_container`, `per_ton`, `per_item`, `per_km`).
|
||
|
||
```sql
|
||
CREATE TABLE freight.contract_rate_snapshots (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
contract_id UUID NOT NULL REFERENCES freight.contracts(id) ON DELETE CASCADE,
|
||
rate_id UUID REFERENCES freight.rates(id),
|
||
rate_code VARCHAR(64) NOT NULL,
|
||
description VARCHAR(255),
|
||
unit_price NUMERIC(14,2) NOT NULL,
|
||
unit_of_measure VARCHAR(32) NOT NULL,
|
||
currency VARCHAR(5) NOT NULL,
|
||
container_size VARCHAR(10), -- 20ft | 40ft when applicable
|
||
is_surcharge BOOLEAN DEFAULT FALSE,
|
||
conditional_on VARCHAR(32), -- is_hazardous | is_reefer
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||
);
|
||
```
|
||
|
||
---
|
||
|
||
### 5.8 `freight.contract_review_notes` (CLONE from `booking_review_note`)
|
||
|
||
Staff/customer notes during contract approval (`CHANGES_REQUESTED`, rejection reasons).
|
||
|
||
---
|
||
|
||
### 5.9 `freight.bookings` (MODIFY — shipment only)
|
||
|
||
```sql
|
||
ALTER TABLE freight.bookings
|
||
ADD COLUMN contract_id UUID REFERENCES freight.contracts(id),
|
||
ADD COLUMN contract_route_id UUID REFERENCES freight.contract_routes(id),
|
||
ADD COLUMN created_by_role VARCHAR(20) DEFAULT 'CUSTOMER', -- CUSTOMER | GL_ET | STAFF
|
||
ADD COLUMN created_by_user_id UUID;
|
||
|
||
-- Partial unique: one active booking per ONE_TIME contract
|
||
CREATE UNIQUE INDEX uq_one_active_booking_per_one_time_contract
|
||
ON freight.bookings (contract_id)
|
||
WHERE status NOT IN ('EXPIRED', 'CANCELLED', 'COMPLETED', 'REJECTED')
|
||
AND contract_id IN (
|
||
SELECT id FROM freight.contracts WHERE contract_kind = 'ONE_TIME'
|
||
);
|
||
```
|
||
|
||
**Keep on bookings:** `reference`, `scheduled_date`, `status`, `scheduling_status`, `train_schedule_id`, `payment_status`, `pnr_code`, `total_amount`, `adjusted_total_amount`, `priority_score`, `payment_deadline`, `selected_for_batch_at`, `hold_started_at`, `hold_expires_at`, `wagons_required`, `consolidation_partner_id`, operational timestamps.
|
||
|
||
**Remove from bookings (after migration):** `booking_type`, `contract_validity_*`, `contract_template_key`, `contract_generated_at`, `contract_summary`, `previous_contract_id`, `expires_at`, `estimated_shipment_date`, `is_hazardous`, `is_reefer` (move to contract; booking stores counts), primary `origin_yard_id`/`destination_yard_id` (use `contract_route_id` or denormalize at booking create).
|
||
|
||
**Denormalize for performance:** Copy `origin_yard_id`, `destination_yard_id`, `trade_direction`, `freight_type` onto booking at creation from contract + selected route.
|
||
|
||
---
|
||
|
||
### 5.10 `freight.booking_container` (ENRICH)
|
||
|
||
Existing table extended for per-unit detail at booking time.
|
||
|
||
```sql
|
||
ALTER TABLE freight.booking_container
|
||
ADD COLUMN container_size VARCHAR(10), -- 20ft | 40ft
|
||
ADD COLUMN hazardous_quantity SMALLINT DEFAULT 0,
|
||
ADD COLUMN reefer_quantity SMALLINT DEFAULT 0;
|
||
```
|
||
|
||
**New child table for individual container numbers:**
|
||
|
||
```sql
|
||
CREATE TABLE freight.booking_container_units (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
booking_container_id UUID NOT NULL REFERENCES freight.booking_container(id) ON DELETE CASCADE,
|
||
container_number VARCHAR(64) NOT NULL,
|
||
seal_number VARCHAR(64),
|
||
vgm_tons NUMERIC(10,3) NOT NULL,
|
||
is_hazardous BOOLEAN DEFAULT FALSE,
|
||
is_reefer BOOLEAN DEFAULT FALSE,
|
||
sort_order SMALLINT NOT NULL DEFAULT 0,
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
|
||
UNIQUE (booking_container_id, container_number)
|
||
);
|
||
```
|
||
|
||
**Bulk bookings:** use existing `cargo_total_weight_vgm` on booking for tons, or new `item_count` column when `cargo_types.unit_of_measure = PER_ITEM`. Add `hazardous_quantity` / bulk-specific counts on booking row or `booking_bulk_lines` if multiple bulk lines needed.
|
||
|
||
---
|
||
|
||
### 5.11 `freight.booking_document_review` (KEEP — per booking clearance)
|
||
|
||
No structural change. Add optional denormalized column:
|
||
|
||
```sql
|
||
ALTER TABLE freight.booking_document_review
|
||
ADD COLUMN contract_id UUID REFERENCES freight.contracts(id);
|
||
```
|
||
|
||
Populated at insert from `bookings.contract_id` for GL read-only contract viewer queries.
|
||
|
||
---
|
||
|
||
### 5.12 `freight.clearance_milestones` (NEW — GL gap)
|
||
|
||
Tracks the 18–23 milestones from GL PDF that are not represented by the current 3 clearance statuses.
|
||
|
||
```sql
|
||
CREATE TABLE freight.clearance_milestones (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
booking_id UUID NOT NULL REFERENCES freight.bookings(id) ON DELETE CASCADE,
|
||
milestone_code VARCHAR(64) NOT NULL,
|
||
milestone_label VARCHAR(255) NOT NULL,
|
||
status VARCHAR(20) NOT NULL DEFAULT 'PENDING', -- PENDING | COMPLETED | SKIPPED
|
||
owner_region VARCHAR(5), -- ET | DJ | OPS | CUST
|
||
triggered_by_doc BOOLEAN DEFAULT FALSE,
|
||
triggered_at TIMESTAMPTZ,
|
||
triggered_by_user_id UUID,
|
||
notes TEXT,
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
|
||
UNIQUE (booking_id, milestone_code)
|
||
);
|
||
|
||
CREATE INDEX idx_clearance_milestones_booking ON freight.clearance_milestones(booking_id);
|
||
CREATE INDEX idx_clearance_milestones_region ON freight.clearance_milestones(owner_region, status);
|
||
```
|
||
|
||
---
|
||
|
||
### 5.13 `freight.clearance_document_phases` (NEW — phased upload slots)
|
||
|
||
Extends `file_upload_settings` with phase metadata for GL document matrix.
|
||
|
||
```sql
|
||
CREATE TYPE freight.clearance_doc_phase AS ENUM (
|
||
'CUSTOMER_INTAKE', -- customer uploads before GL review
|
||
'GL_ET_REVIEW', -- ET GL internal
|
||
'GL_DJ_COLLECTION', -- DJ GL collects RO, DO
|
||
'GL_ET_OUTPUT', -- IM4, EX3, EX8, T1 uploaded by ET GL
|
||
'CUSTOMER_DUTY', -- duty/tax payment slips
|
||
'GL_ET_POST_CLEARANCE', -- import release, T1 closure
|
||
'GL_DJ_LOADING', -- gatepass, loading docs
|
||
'POST_TRANSIT' -- demurrage slips, final declaration
|
||
);
|
||
|
||
-- Extend file_upload_fields with:
|
||
-- phase clearance_doc_phase NOT NULL
|
||
-- owner_region VARCHAR(5) -- ET | DJ | CUST
|
||
-- trade_direction VARCHAR(10)
|
||
-- triggers_milestone_code VARCHAR(64)
|
||
```
|
||
|
||
---
|
||
|
||
### 5.14 Files / MinIO Resource Tagging
|
||
|
||
| Resource | Entity | Examples |
|
||
|----------|--------|----------|
|
||
| `contracts` | Contract PDF, intake docs, **post-sign clearance docs (Path B)** | Commercial invoice at wizard; BL, packing list after sign |
|
||
| `bookings` | Post-booking ops docs, copied GL outputs | VGM on booking row, IM4 linked after GL creates booking |
|
||
| `contract_signatures` | Signature images | PNG from signature pad |
|
||
|
||
---
|
||
|
||
### 5.15 IAM Roles (GL Split)
|
||
|
||
| Role code | Permissions |
|
||
|-----------|-------------|
|
||
| `edr_gl_ethiopia` | View contract, **review pre-booking clearance docs on contract**, upload IM4/IM5/EX3/EX8/T1, **create booking with full shipment data**, request wagon, assign station staff, post-booking ET milestones |
|
||
| `edr_gl_djibouti` | View contract + booking (read-only rates), upload Release Order / DO / gatepass, loading milestones, damage reports, handoff triggers |
|
||
| `edr_global_logistics` | **Deprecated** — split into ET/DJ; keep temporarily with union permissions for migration |
|
||
|
||
Station routing (GL US-02): on booking create by GL, set `bookings.gl_station_yard_id` from contract route origin yard; queue filters by station assignment.
|
||
|
||
---
|
||
|
||
### 5.16 Pre-Booking Clearance (Path B — customs contracts)
|
||
|
||
When `customs_clearing_enabled = true`, clearance documents and review happen **on the contract before any booking exists**.
|
||
|
||
#### `freight.contract_document_review`
|
||
|
||
Same structure as `booking_document_review`, keyed on `contract_id` (and optionally `clearance_cycle_id`):
|
||
|
||
```sql
|
||
CREATE TABLE freight.contract_document_review (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
contract_id UUID NOT NULL REFERENCES freight.contracts(id) ON DELETE CASCADE,
|
||
clearance_cycle_id UUID REFERENCES freight.contract_clearance_cycles(id),
|
||
setting_code VARCHAR(128) NOT NULL,
|
||
file_key VARCHAR(128) NOT NULL,
|
||
file_record_id UUID,
|
||
status VARCHAR(20) NOT NULL DEFAULT 'PENDING', -- PENDING | APPROVED | QUERIED
|
||
note TEXT,
|
||
uploaded_by_role VARCHAR(20) NOT NULL DEFAULT 'CUSTOMER', -- CUSTOMER | GL_ET | GL_DJ
|
||
reviewed_by_staff_id UUID,
|
||
reviewed_at TIMESTAMPTZ,
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
|
||
UNIQUE (contract_id, clearance_cycle_id, setting_code, file_key)
|
||
);
|
||
|
||
CREATE INDEX idx_contract_doc_review_contract ON freight.contract_document_review(contract_id);
|
||
CREATE INDEX idx_contract_doc_review_status ON freight.contract_document_review(status);
|
||
```
|
||
|
||
Customer uploads attach here after contract sign. GL approves/queries here. When all required docs are `APPROVED` and GL output docs for the pre-booking phase are uploaded, contract moves to `CLEARANCE_READY_FOR_BOOKING` — GL may then create the booking.
|
||
|
||
#### `freight.contract_clearance_cycles` (GENERAL multi-shipment)
|
||
|
||
```sql
|
||
CREATE TABLE freight.contract_clearance_cycles (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
contract_id UUID NOT NULL REFERENCES freight.contracts(id) ON DELETE CASCADE,
|
||
cycle_number INT NOT NULL,
|
||
status VARCHAR(40) NOT NULL DEFAULT 'AWAITING_DOCUMENTS',
|
||
booking_id UUID REFERENCES freight.bookings(id), -- set when GL creates booking
|
||
started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
clearance_ready_at TIMESTAMPTZ,
|
||
completed_at TIMESTAMPTZ,
|
||
|
||
UNIQUE (contract_id, cycle_number)
|
||
);
|
||
```
|
||
|
||
ONE_TIME contracts use a single implicit cycle (cycle_number = 1, no separate row required — or always row 1).
|
||
|
||
#### `freight.clearance_milestones` — split ownership
|
||
|
||
| Phase | Attached to | When |
|
||
|-------|-------------|------|
|
||
| Pre-booking milestones (docs uploaded, declared, DO collected, etc. before wagon request) | `contract_id` + `clearance_cycle_id` | Before booking exists |
|
||
| Post-booking milestones (wagon allocated, loaded, departed, offloaded, etc.) | `booking_id` | After GL creates booking |
|
||
|
||
Add nullable `contract_id` and `clearance_cycle_id` to `clearance_milestones` alongside `booking_id`.
|
||
|
||
---
|
||
|
||
## 6. Status Machines
|
||
|
||
### 6.1 Contract Statuses
|
||
|
||
```
|
||
DRAFT
|
||
→ SUBMITTED
|
||
→ PRICE_CHANGED_PENDING_CONFIRM (if rates changed on resubmit)
|
||
→ CHANGES_REQUESTED
|
||
→ PENDING_APPROVAL (after staff accept + validity window set)
|
||
→ APPROVED
|
||
→ APPROVED_PENDING_SIGNATURE
|
||
→ CONTRACT_READY (PDF generated)
|
||
→ SIGNED_CUSTOMER
|
||
→ FULLY_EXECUTED (ONE_TIME, transport-only: customer may book)
|
||
→ CONTRACT_ACTIVE (GENERAL, transport-only: customer may book)
|
||
-- Path B only (customs_clearing_enabled = true), after counter-sign:
|
||
→ AWAITING_CLEARANCE_DOCUMENTS (customer uploads clearance docs — no booking yet)
|
||
→ CLEARANCE_UNDER_REVIEW (GL ET reviews customer docs)
|
||
→ CLEARANCE_READY_FOR_BOOKING (GL cleared to create booking; output docs in place)
|
||
→ ACTIVE_SHIPMENT_IN_PROGRESS (GL created booking; cycle in ops pipeline)
|
||
→ CONTRACT_CLOSED (GENERAL: validity ended or manually closed)
|
||
→ EXPIRED (contract_valid_until passed)
|
||
→ REJECTED
|
||
→ CANCELLED
|
||
```
|
||
|
||
For **transport-only** contracts (`customs_clearing_enabled = false`), counter-sign goes directly to `FULLY_EXECUTED` / `CONTRACT_ACTIVE` with `clearance_status = NOT_APPLICABLE`.
|
||
|
||
For **customs** contracts, counter-sign goes to `AWAITING_CLEARANCE_DOCUMENTS` with `clearance_status = AWAITING_DOCUMENTS`.
|
||
|
||
**Renewal branch:**
|
||
|
||
```
|
||
RENEWAL_DRAFT
|
||
→ RENEWAL_SUBMITTED
|
||
→ RENEWAL_PENDING_APPROVAL
|
||
→ AMENDMENTS_PROPOSED (staff changed price/terms)
|
||
→ (customer Accept) → CONTRACT_READY
|
||
→ (customer Reject) → ARCHIVED
|
||
```
|
||
|
||
### 6.2 Contract Transition Table
|
||
|
||
| From | Action | Actor | To |
|
||
|------|--------|-------|-----|
|
||
| DRAFT | Submit | Customer | SUBMITTED |
|
||
| SUBMITTED | Accept | Line Staff | PENDING_APPROVAL |
|
||
| SUBMITTED | Request changes | Line Staff | CHANGES_REQUESTED |
|
||
| SUBMITTED | Reject | Line Staff | REJECTED |
|
||
| PENDING_APPROVAL | Approve step | Line Staff / Director / CEO | APPROVED (when all steps done) |
|
||
| APPROVED | Generate contract | System | CONTRACT_READY |
|
||
| CONTRACT_READY | Sign | Customer | SIGNED_CUSTOMER |
|
||
| SIGNED_CUSTOMER | Counter-sign | Staff/Director/CEO | FULLY_EXECUTED / CONTRACT_ACTIVE (transport-only) **or** AWAITING_CLEARANCE_DOCUMENTS (customs) |
|
||
| AWAITING_CLEARANCE_DOCUMENTS | Upload all required docs | Customer | CLEARANCE_UNDER_REVIEW (auto when complete) |
|
||
| CLEARANCE_UNDER_REVIEW | Approve all docs | GL ET | CLEARANCE_READY_FOR_BOOKING |
|
||
| CLEARANCE_UNDER_REVIEW | Query doc | GL ET | AWAITING_CLEARANCE_DOCUMENTS (customer re-upload) |
|
||
| CLEARANCE_READY_FOR_BOOKING | Create booking | GL ET | ACTIVE_SHIPMENT_IN_PROGRESS |
|
||
| ACTIVE_SHIPMENT_IN_PROGRESS | Booking completes/expired | System | CONTRACT_ACTIVE / FULLY_EXECUTED (ready for next cycle) |
|
||
| CONTRACT_ACTIVE | Validity ends | System cron | CONTRACT_CLOSED or EXPIRED |
|
||
| FULLY_EXECUTED | Validity ends | System cron | EXPIRED |
|
||
|
||
**Contract carries pre-booking clearance statuses (Path B).** Batch and freight payment statuses remain on `bookings` only.
|
||
|
||
### 6.3 Booking Statuses (Shipment Pipeline)
|
||
|
||
Bookings are **only created after** contract signing. For Path B (customs), GL creates the booking only after contract clearance reaches `CLEARANCE_READY_FOR_BOOKING`.
|
||
|
||
**Path A (transport-only)** — customer creates booking; may start at `OPERATION_REQUEST_PENDING` or `DRAFT`:
|
||
|
||
```
|
||
[Customer creates booking]
|
||
→ OPERATION_REQUEST_PENDING (customer picked binding scheduled_date)
|
||
→ OPERATION_CHANGES_REQUESTED
|
||
→ OPERATION_PRICE_PENDING_CONFIRM
|
||
→ FULLY_EXECUTED (enters batch holding pool)
|
||
→ SELECTED_FOR_BATCH
|
||
→ EXPIRED (payment window — contract unaffected)
|
||
→ PNR_GENERATED / PAID
|
||
→ IN_TRANSIT
|
||
→ COMPLETED
|
||
→ CANCELLED / REJECTED
|
||
→ ROAD_DISPATCH_PENDING (road services)
|
||
```
|
||
|
||
**Path B (customs — GL creates booking)** — booking skips pre-booking doc gates; GL has already entered schedule date and cargo details at creation:
|
||
|
||
```
|
||
[GL ET creates booking with full shipment data]
|
||
→ FULLY_EXECUTED or OPERATION_REQUEST_PENDING (based on whether ops review required)
|
||
→ SELECTED_FOR_BATCH
|
||
→ EXPIRED (payment window — customer action: pay only)
|
||
→ PNR_GENERATED / PAID (customer pays freight)
|
||
→ IN_TRANSIT (GL/Ops milestones continue)
|
||
→ COMPLETED
|
||
```
|
||
|
||
Post-booking GL milestones (wagon allocated, loaded, departed, offloaded, T1 closed, etc.) attach to `booking_id` via `clearance_milestones` as today.
|
||
|
||
**Gate to create booking (Path A — customer):**
|
||
|
||
```sql
|
||
contract.status IN ('FULLY_EXECUTED', 'CONTRACT_ACTIVE')
|
||
AND contract.customs_clearing_enabled = false
|
||
AND contract.contract_valid_until > NOW()
|
||
AND (contract_kind = 'GENERAL'
|
||
OR NOT EXISTS active booking for this contract)
|
||
```
|
||
|
||
**Gate to create booking (Path B — GL only):**
|
||
|
||
```sql
|
||
contract.clearance_status = 'CLEARANCE_READY_FOR_BOOKING'
|
||
AND contract.customs_clearing_enabled = true
|
||
AND contract.contract_valid_until > NOW()
|
||
AND (contract_kind = 'GENERAL'
|
||
OR NOT EXISTS active booking for this contract)
|
||
AND caller.role = 'edr_gl_ethiopia'
|
||
```
|
||
|
||
Where `active booking` = status NOT IN (`EXPIRED`, `CANCELLED`, `COMPLETED`, `REJECTED`).
|
||
|
||
### 6.4 Booking Creation Rules
|
||
|
||
| Path | Who creates booking | UI | Initial booking status |
|
||
|------|---------------------|-----|------------------------|
|
||
| **A — Transport only** | Customer | Portal booking wizard (§8.1) | `OPERATION_REQUEST_PENDING` or `DRAFT` |
|
||
| **B — Customs clearance** | **GL Ethiopia only** | Backoffice GL booking form (§8.2) | `FULLY_EXECUTED` or `OPERATION_REQUEST_PENDING` (schedule date + cargo already filled by GL) |
|
||
|
||
**Customer portal (Path B):** After contract sign, show **Upload Clearance Documents** on contract detail — not a booking wizard. After GL creates booking, customer sees shipment on `/bookings/:id` with **Pay** as primary action when batch-selected.
|
||
|
||
**No customer confirmation step** before GL booking goes live — GL enters authoritative shipment data; customer is notified when booking is created and when payment is due.
|
||
|
||
---
|
||
|
||
## 7. Contract Wizard Specification
|
||
|
||
**Route:** `/contracts/new`
|
||
**Replaces:** `/bookings/new` for contract creation
|
||
|
||
### Step 0 — Operation Type
|
||
|
||
| Field | Type | Required | Notes |
|
||
|-------|------|----------|-------|
|
||
| `operationType` | enum | Yes | `import`, `export`, `intercity`, `import_ff`, `export_ff` |
|
||
| | | | Gated by company profile types (existing logic from `step0-operation-type.tsx`) |
|
||
|
||
Maps to `contracts.trade_direction`: import/export → IMPORT/EXPORT; intercity → DOMESTIC.
|
||
|
||
---
|
||
|
||
### Step 1 — Contract Type
|
||
|
||
| Field | Type | Required | Notes |
|
||
|-------|------|----------|-------|
|
||
| `contractKind` | enum | Yes | `one_time` → ONE_TIME, `general_contract` → GENERAL |
|
||
| `renewalMode` | enum | No | `new` \| `renewal` |
|
||
| `renewalOfReference` | combobox | If renewal | Search prior contracts; pre-fill service, routes, cargo scope |
|
||
|
||
Maps to `contracts.contract_kind`, `contracts.renewal_of_id`.
|
||
|
||
---
|
||
|
||
### Step 2 — Service Type & Currency
|
||
|
||
| Field | Type | Required | Notes |
|
||
|-------|------|----------|-------|
|
||
| `serviceTypeId` | select | Yes | From reference data; `canBeBookedAlone` |
|
||
| `paymentCurrency` | enum | Yes | `USD` \| `ETB` |
|
||
| `firstMile.*` | toggle + map | If service includes first mile | Address, lat, lng |
|
||
| `lastMile.*` | toggle + map | If service includes last mile | Address, lat, lng |
|
||
| `equipmentReturn` | enum | If last mile | `with_return` \| `without_return` |
|
||
| `customsClearingAgent` | text | Optional | When customs not bundled |
|
||
|
||
Auto-set `customs_clearing_enabled` from `serviceType.includesCustoms`.
|
||
|
||
---
|
||
|
||
### Step 3 — Cargo Scope (NO quantities)
|
||
|
||
**Container freight:**
|
||
|
||
| Field | Type | Required | Notes |
|
||
|-------|------|----------|-------|
|
||
| `enabledContainerSizes` | checkbox[] | Yes, min 1 | `20ft`, `40ft` — creates `contract_cargo_scope` rows |
|
||
| `cargoCommodityId` | select | Optional | Commodity label for contract PDF (Coffee, etc.) |
|
||
|
||
**Do NOT collect:** quantity, VGM, container type detail (dry/reefer/high-cube), shipping line.
|
||
|
||
**Bulk freight:**
|
||
|
||
| Field | Type | Required | Notes |
|
||
|-------|------|----------|-------|
|
||
| `cargoTypePath` | cascader | Yes | Bulk group → commodity (Coffee, Fertilizer, …) |
|
||
| `cargoFreeText` | text | If "Others" | |
|
||
|
||
**Do NOT collect:** tonnage, item count, weight.
|
||
|
||
**Flags (both):**
|
||
|
||
| Field | Type | Notes |
|
||
|-------|------|-------|
|
||
| `isHazardous` | toggle | Sets `contracts.is_hazardous`; surcharge shown as unit rate |
|
||
| `isRefrigerated` | toggle | Sets `contracts.is_reefer`; bulk + container |
|
||
|
||
---
|
||
|
||
### Step 4 — Route & Estimated Date
|
||
|
||
| Field | Type | Required | Notes |
|
||
|-------|------|----------|-------|
|
||
| `originYardId` | select | Yes | Filtered by operation type |
|
||
| `destinationYardId` | select | Yes | |
|
||
| `extraRoutes[]` | repeater | GENERAL only | Additional origin/destination pairs → `contract_routes` |
|
||
| `estimatedShipmentDate` | date | Yes | Non-binding; maps to `contracts.estimated_shipment_date` |
|
||
|
||
**Do NOT collect:** `scheduledDate` (binding date is at booking step).
|
||
|
||
---
|
||
|
||
### Step 5 — Contract Intake Documents
|
||
|
||
| Field | Type | Required | Notes |
|
||
|-------|------|----------|-------|
|
||
| `documents` | file map | Per setting | Attach to `contract_id` via FilesService `resource=contracts` |
|
||
|
||
Setting codes (new or repurpose):
|
||
- `contract_intake_documents_import_container`
|
||
- `contract_intake_documents_export_bulk`
|
||
- etc. (mirror clearance pattern by direction × freight × customs)
|
||
|
||
These are **framework / commercial documents** at contract submission — distinct from **post-sign clearance documents** (BL, import license, etc.) uploaded on `/contracts/:id/clearance` after signing (Path B).
|
||
|
||
---
|
||
|
||
### Step 6 — Review & Submit
|
||
|
||
| Field | Type | Notes |
|
||
|-------|------|-------|
|
||
| `notes` | textarea | Optional special instructions |
|
||
| Pricing panel | read-only | **Unit rates only** — see §9 |
|
||
| Actions | buttons | Save draft, Generate price, Submit |
|
||
|
||
**Post-submit:** existing approval + contract sign flow on `/contracts/:id/contract`.
|
||
|
||
**Post-sign (Path B only):** Customer is routed to `/contracts/:id/clearance` to upload clearance documents — not to a booking wizard.
|
||
|
||
---
|
||
|
||
## 8. Booking Creation Specification
|
||
|
||
Booking creation is **split by execution path** (see §4.2). Only **Path A (transport-only)** uses the customer portal booking wizard. **Path B (customs clearance)** uses a **GL-only backoffice form** — the customer never enters shipment data.
|
||
|
||
---
|
||
|
||
### 8.1 Customer Booking Wizard (Path A — transport only)
|
||
|
||
**Route:** `/contracts/:contractId/bookings/new`
|
||
**Actor:** Customer
|
||
**Preconditions:**
|
||
- `contract.customs_clearing_enabled = false`
|
||
- `contract.status IN ('FULLY_EXECUTED', 'CONTRACT_ACTIVE')`
|
||
- Contract validity not expired
|
||
- ONE_TIME: no other active booking
|
||
|
||
#### Step 1 — Route Selection
|
||
|
||
| Field | Type | Required | Notes |
|
||
|-------|------|----------|-------|
|
||
| `contractRouteId` | select | If GENERAL multi-route | From `contract_routes`; ONE_TIME auto-selected |
|
||
|
||
Denormalize origin/destination onto booking.
|
||
|
||
#### Step 2 — Schedule Date
|
||
|
||
| Field | Type | Required | Notes |
|
||
|-------|------|----------|-------|
|
||
| `scheduledDate` | date | Yes | Binding day; validated against open train departures |
|
||
|
||
#### Step 3 — Cargo Details
|
||
|
||
**Container** (for each enabled size from `contract_cargo_scope`):
|
||
|
||
| Field | Type | Required |
|
||
|-------|------|----------|
|
||
| `containers[].size` | 20ft/40ft | Yes |
|
||
| `containers[].quantity` | int ≥ 1 | Yes |
|
||
| `containers[].units[].containerNumber` | text | Yes, one per container |
|
||
| `containers[].units[].sealNumber` | text | Optional |
|
||
| `containers[].units[].vgmTons` | decimal | Yes, per unit |
|
||
| `containers[].hazardousQuantity` | int | If `contract.is_hazardous` |
|
||
| `containers[].reeferQuantity` | int | If `contract.is_reefer` |
|
||
|
||
**Bulk:**
|
||
|
||
| Field | Type | Required |
|
||
|-------|------|----------|
|
||
| `cargoWeightTons` OR `itemCount` | decimal/int | Yes |
|
||
| `hazardousQuantity` | int | If hazardous |
|
||
|
||
#### Step 4 — Review & Submit
|
||
|
||
- Compute total from contract unit rates × quantities
|
||
- Submit → `OPERATION_REQUEST_PENDING` → ops review → batch → **customer pays**
|
||
|
||
---
|
||
|
||
### 8.2 GL Booking Form (Path B — customs clearance)
|
||
|
||
**Route:** `/dashboard/contracts/:contractId/create-booking` (backoffice)
|
||
**Actor:** `edr_gl_ethiopia` **only**
|
||
**Preconditions:**
|
||
- `contract.customs_clearing_enabled = true`
|
||
- `contract.clearance_status = 'CLEARANCE_READY_FOR_BOOKING'`
|
||
- Contract validity not expired; ONE_TIME has no active booking
|
||
|
||
GL enters **all shipment fields** that the customer would enter in §8.1:
|
||
|
||
| Field | Type | Required | Notes |
|
||
|-------|------|----------|-------|
|
||
| `contractRouteId` | select | If GENERAL multi-route | |
|
||
| `scheduledDate` | date | Yes | Binding schedule day |
|
||
| `containers[].size` | 20ft/40ft | If container contract | From contract scope |
|
||
| `containers[].quantity` | int | Yes | |
|
||
| `containers[].units[].containerNumber` | text | Yes | GL enters on behalf of customer |
|
||
| `containers[].units[].sealNumber` | text | Optional | |
|
||
| `containers[].units[].vgmTons` | decimal | Yes | |
|
||
| `containers[].hazardousQuantity` | int | If hazardous | |
|
||
| `containers[].reeferQuantity` | int | If reefer | |
|
||
| `cargoWeightTons` / `itemCount` | decimal/int | If bulk | Ton or item per cargo type |
|
||
| `notes` | text | Optional | Internal GL notes |
|
||
|
||
**On submit:**
|
||
1. Create `bookings` row with `created_by_role = 'GL_ET'`, `created_by_user_id = gl staff`
|
||
2. Compute `total_amount` from contract unit rates × GL-entered quantities
|
||
3. Set booking status → `FULLY_EXECUTED` or `OPERATION_REQUEST_PENDING`
|
||
4. Set contract → `ACTIVE_SHIPMENT_IN_PROGRESS`; link `contract_clearance_cycles.booking_id`
|
||
5. Notify customer: "Your shipment has been booked by Global Logistics — you will be notified when payment is due"
|
||
6. Continue post-booking pipeline (batch, milestones) — **customer action = pay only**
|
||
|
||
**Customer portal:** No booking wizard. Contract detail shows clearance upload; after GL books, `/bookings/:id` shows read-only shipment summary + Pay button.
|
||
|
||
---
|
||
|
||
### 8.3 Customer Post-Sign Clearance Upload (Path B)
|
||
|
||
**Route:** `/contracts/:id/clearance` (portal)
|
||
**Actor:** Customer
|
||
**When:** After contract counter-sign → `AWAITING_CLEARANCE_DOCUMENTS`
|
||
|
||
| Field | Type | Notes |
|
||
|-------|------|-------|
|
||
| Clearance documents | file map | Per `contract_clearance_*` file-upload setting (import/export × container/bulk) |
|
||
| Ad-hoc documents | name + file | Optional additional regulatory docs |
|
||
|
||
Documents stored in `contract_document_review` — **not** on a booking row (booking does not exist yet).
|
||
|
||
After all required uploads: contract auto-transitions to `CLEARANCE_UNDER_REVIEW`; GL ET queue receives the contract.
|
||
|
||
---
|
||
|
||
## 9. Pricing Model
|
||
|
||
### 9.1 Contract Phase — Unit Rate Display
|
||
|
||
Example UI (matches stakeholder requirement):
|
||
|
||
```
|
||
Pricing Schedule (estimated — final amount calculated at booking)
|
||
|
||
20ft container ...................... 2,000 ETB / container
|
||
40ft container ........................ 40,000 ETB / container
|
||
Hazardous surcharge ................. 3,000 ETB / container (if enabled)
|
||
Reefer surcharge .................... 5,000 ETB / container (if enabled)
|
||
Coffee (bulk) ....................... 1,500 ETB / ton
|
||
|
||
No total amount shown — quantities unknown at contract stage.
|
||
```
|
||
|
||
**JSON storage (`contracts.pricing_breakdown`):**
|
||
|
||
```json
|
||
{
|
||
"displayMode": "UNIT_RATES",
|
||
"currency": "ETB",
|
||
"lineItems": [
|
||
{
|
||
"code": "CONTAINER_20FT",
|
||
"label": "20ft container",
|
||
"unit": "per_container",
|
||
"unitPrice": 2000,
|
||
"containerSize": "20ft"
|
||
},
|
||
{
|
||
"code": "CONTAINER_40FT",
|
||
"label": "40ft container",
|
||
"unit": "per_container",
|
||
"unitPrice": 40000,
|
||
"containerSize": "40ft"
|
||
},
|
||
{
|
||
"code": "HAZARD_SURCHARGE",
|
||
"label": "Hazardous surcharge",
|
||
"unit": "per_container",
|
||
"unitPrice": 3000,
|
||
"conditionalOn": "is_hazardous"
|
||
},
|
||
{
|
||
"code": "REEFER_SURCHARGE",
|
||
"label": "Reefer surcharge",
|
||
"unit": "per_container",
|
||
"unitPrice": 5000,
|
||
"conditionalOn": "is_reefer"
|
||
},
|
||
{
|
||
"code": "BULK_COFFEE",
|
||
"label": "Coffee",
|
||
"unit": "per_ton",
|
||
"unitPrice": 1500,
|
||
"cargoTypeCode": "COFFEE"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
Contract PDF (US-07) embeds unit rate schedule, not totals.
|
||
|
||
### 9.2 Booking Phase — Total Calculation
|
||
|
||
```
|
||
booking_total =
|
||
Σ (container_qty[size] × unit_rate[size])
|
||
+ Σ (hazardous_qty × hazard_unit_rate)
|
||
+ Σ (reefer_qty × reefer_unit_rate)
|
||
+ bulk_qty × bulk_unit_rate
|
||
+ first_mile_km × per_km_rate (if applicable)
|
||
+ last_mile_km × per_km_rate (if applicable)
|
||
+ overweight surcharges (when VGM entered per US-11)
|
||
```
|
||
|
||
Algorithm reuses `BookingPricingService.computePriceForBooking()` but inputs come from booking quantities + frozen `contract_rate_snapshots` instead of contract-time qty.
|
||
|
||
Store result in `bookings.total_amount` and `booking_rate_snapshot` rows.
|
||
|
||
### 9.3 Quotation Approval (US-04)
|
||
|
||
At contract submit, customer sees unit-rate quotation → **Approve Quotation** → advances to approval/signing. Reject → `REJECTED` / archived.
|
||
|
||
This replaces the current total-amount confirmation modal in `NewBookingPage.tsx`.
|
||
|
||
---
|
||
|
||
## 10. Renewal and Expiry Rules
|
||
|
||
### 10.1 Contract Validity Expiry
|
||
|
||
| Event | Contract behavior | Booking behavior |
|
||
|-------|-------------------|------------------|
|
||
| `contract_valid_until` passed | Status → `EXPIRED` or `CONTRACT_CLOSED`; no new bookings | In-flight bookings continue to completion |
|
||
| Customer requests renewal | `RENEWAL_DRAFT` linked via `renewal_of_id` | N/A |
|
||
| Staff approves without changes | → `CONTRACT_READY` → sign | N/A |
|
||
| Staff proposes amendments | → `AMENDMENTS_PROPOSED`; customer accept/reject | N/A |
|
||
|
||
Renewal UI: extend current `step1-contract-type.tsx` renewal combobox to search `contracts` table.
|
||
|
||
### 10.2 Payment Window Expiry (Booking Only)
|
||
|
||
| Event | Contract | Booking |
|
||
|-------|----------|---------|
|
||
| Payment deadline passes (`BookingBatchService`) | **Unchanged** — stays `FULLY_EXECUTED` or `CONTRACT_ACTIVE` | Status → `EXPIRED`; wagons released |
|
||
| Customer re-books (Path A) | Same contract | Customer creates new booking |
|
||
| Customer re-ships (Path B) | Returns to `AWAITING_CLEARANCE_DOCUMENTS` for new cycle | GL creates new booking after clearance |
|
||
|
||
**UX Path A:** Contract detail shows "Create new booking" when prior booking expired.
|
||
|
||
**UX Path B:** Contract detail shows "Upload clearance documents" for next shipment cycle; customer never sees a booking creation form.
|
||
|
||
### 10.3 General Contract Ordering Window
|
||
|
||
`contracts.expires_at` (ordering window, from `general_contract_period` setting) is separate from `contract_valid_until` (legal validity). Both must be open for new bookings.
|
||
|
||
### 10.4 ONE_TIME Single Active Booking Rule
|
||
|
||
Enforced by partial unique index (§5.9). Terminal statuses free the slot:
|
||
|
||
- `EXPIRED` (payment)
|
||
- `CANCELLED`
|
||
- `COMPLETED`
|
||
- `REJECTED`
|
||
|
||
---
|
||
|
||
## 11. Global Logistics — Import Unimodal
|
||
|
||
### 11.1 Actors
|
||
|
||
| Actor | Role code | Primary responsibilities |
|
||
|-------|-----------|-------------------------|
|
||
| Customer | Portal user | **Path B:** Upload clearance docs on contract after sign; pay freight PNR and duty/tax slips when advised. **Does not create bookings or enter shipment data.** |
|
||
| GL Ethiopia | `edr_gl_ethiopia` | Review pre-booking clearance docs on contract; upload IM4/IM5/EX3/EX8/T1; **create booking with full shipment data**; wagon request; post-booking milestones |
|
||
| GL Djibouti | `edr_gl_djibouti` | DO collection, gatepass, loading milestones, damage reports, departure |
|
||
| Operations | `edr_operations` | Wagon allocation, train dispatch, marshalling |
|
||
| Port/Terminal | `edr_terminal` | Arrival, offload, yard assignment |
|
||
|
||
### 11.2 Import Document Matrix
|
||
|
||
| # | Customer uploads | GL Ethiopia uploads | GL Djibouti uploads | Phase |
|
||
|---|------------------|--------------------|--------------------|-------|
|
||
| 1 | Commercial Invoice* | | | `contract_id` — CUSTOMER_INTAKE (post-sign) |
|
||
| 2 | Packing List* | | | `contract_id` |
|
||
| 3 | Certificate of Origin* | | | `contract_id` |
|
||
| 4 | Bank Permit / Franco Valuta* | | | `contract_id` |
|
||
| 5 | Bill of Lading / SWB* | | | `contract_id` |
|
||
| 6 | Power of Attorney* | | | `contract_id` |
|
||
| 7 | Import License* | | | `contract_id` |
|
||
| 8 | Other regulatory docs | | | `contract_id` |
|
||
| 9 | | Import Declaration (IM4/IM5)* | | GL_ET_OUTPUT |
|
||
| 10 | | Transit permit screenshot | | GL_ET_OUTPUT |
|
||
| 11 | Duty/tax payment slip* | | | CUSTOMER_DUTY |
|
||
| 12 | | | Delivery Order | GL_DJ_COLLECTION |
|
||
| 13 | | | T1 transport document | GL_DJ_LOADING |
|
||
| 14 | | Import release* | | GL_ET_POST_CLEARANCE |
|
||
| 15 | | | Full out Interchange | GL_DJ_LOADING |
|
||
| 16 | | | Damage report photos | GL_DJ_LOADING (conditional) |
|
||
| 17 | Storage/demurrage payment slip | | | POST_TRANSIT |
|
||
|
||
*Mandatory per GL PDF Import Documents table.
|
||
|
||
**Document ownership (Path B):**
|
||
- Rows 1–8, 11, 17 (customer uploads): attach to **`contract_id`** via `contract_document_review` **before booking exists**
|
||
- Rows 9–10, 14 (GL ET output): attach to **`contract_id`** during pre-booking clearance, copied/referenced on `booking_id` when GL creates booking
|
||
- Rows 12–13, 15–16 (GL DJ): may attach to `contract_id` pre-booking or `booking_id` post-booking depending on milestone timing (see §5.16)
|
||
- After GL creates booking, post-booking milestones (wagon+, rows 9–23 in §11.3) track on **`booking_id`**
|
||
|
||
Contract terms visible read-only to GL throughout; customer pays freight at milestone 10 only.
|
||
|
||
### 11.3 Import Milestone Sequence
|
||
|
||
| # | Milestone | Owner | Triggered by doc upload? |
|
||
|---|-----------|-------|--------------------------|
|
||
| 1 | Import Documents Uploaded | Customer | No |
|
||
| 2 | Pending Document Review | GL-ET | Yes (all customer docs uploaded) |
|
||
| 3 | Documents Approved | GL-ET | No |
|
||
| 4 | Under Customs Clearance | GL-ET | No |
|
||
| 5 | Declared | GL-ET | Yes (IM4/IM5 uploaded) |
|
||
| 6 | Duty and Taxes Advised | GL-ET | No |
|
||
| 7 | Duty and Tax Paid | Customer | Yes (payment slip) |
|
||
| 8 | DO Collected | GL-DJ | Yes (DO uploaded) |
|
||
| 9 | Wagon Allocation Requested | GL-ET | No |
|
||
| 10 | Payment Settled (freight) | Customer | Yes (PNR paid) |
|
||
| 11 | Wagon Allocated | Operations | No |
|
||
| 12 | Gatepass Granted | GL-DJ | No |
|
||
| 13 | Ready for Loading | GL-DJ | No |
|
||
| 14 | Loaded | GL-DJ | No |
|
||
| 15 | Departed from Djibouti | GL-DJ | No |
|
||
| 16 | Arrived at Port in Ethiopia | Port/Terminal | No |
|
||
| 17 | Offloaded | Port/Terminal | No |
|
||
| 18 | T1 Closed | GL-ET | No |
|
||
| 19 | Risk Assigned (GREEN/YELLOW/RED) | GL-ET | No |
|
||
| 20 | Import Release Granted | GL-ET | Yes (release doc) |
|
||
| 21 | Import Process Completed | GL-ET | Yes |
|
||
| 22 | Storage Invoice Raised | System | No (demurrage engine) |
|
||
| 23 | Exit Note Generated | Port | Yes (storage paid) |
|
||
|
||
Map to `clearance_milestones.milestone_code` enum constants.
|
||
|
||
**Milestone phase split (Path B):**
|
||
|
||
| Milestones | Phase | Attached to |
|
||
|------------|-------|-------------|
|
||
| 1–8 | Pre-booking clearance | `contract_id` — **before booking exists** |
|
||
| 8a | **GL creates booking** (schedule, qty, container numbers entered by GL ET) | `booking_id` created |
|
||
| 9–23 | Post-booking operations | `booking_id` — batch, payment (customer), allocation, transit |
|
||
|
||
Customer actions in this table: **#1 upload**, **#7 duty slip**, **#10 pay freight**, **#17 storage slip**. All other steps are GL/Ops/System.
|
||
|
||
### 11.4 Import Sequence Diagram (Path B — customs clearance)
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant Cust as Customer
|
||
participant CTR as Contract
|
||
participant BK as Booking
|
||
participant GLET as GL_Ethiopia
|
||
participant GLDJ as GL_Djibouti
|
||
participant Ops as Operations
|
||
|
||
Cust->>CTR: Sign contract
|
||
Cust->>CTR: Upload clearance docs on contract
|
||
CTR->>GLET: CLEARANCE_UNDER_REVIEW
|
||
GLET->>CTR: Approve or query per document
|
||
GLET->>CTR: Upload IM4 IM5 output docs
|
||
GLET->>Cust: Duty and taxes advised
|
||
Cust->>CTR: Upload duty payment slip
|
||
GLDJ->>CTR: Upload DO collected
|
||
Note over GLET,CTR: CLEARANCE_READY_FOR_BOOKING
|
||
GLET->>BK: GL creates booking schedule qty container numbers
|
||
GLET->>Ops: Wagon allocation requested
|
||
Ops->>Cust: PNR pending payment
|
||
Cust->>BK: Pay freight only
|
||
Ops->>BK: Wagon allocated
|
||
GLDJ->>BK: Gatepass loading loaded
|
||
GLDJ->>BK: Departed from Djibouti
|
||
Ops->>BK: Arrived offloaded Ethiopia
|
||
GLET->>BK: T1 closed import release granted
|
||
```
|
||
|
||
### 11.5 ET ↔ DJ Handoff (Import US-09)
|
||
|
||
When milestone `DEPARTED_FROM_DJIBOUTI` completes:
|
||
- Primary owner → GL Ethiopia + Operations (read-only copy for GL Djibouti)
|
||
- Notifications: ATD to all; ETA to GL-ET and customer
|
||
|
||
---
|
||
|
||
## 12. Global Logistics — Export Unimodal
|
||
|
||
### 12.1 Export Document Matrix
|
||
|
||
| # | Customer uploads | GL Ethiopia uploads | GL Djibouti uploads |
|
||
|---|------------------|--------------------|--------------------|
|
||
| 1 | Booking Confirmation* | | |
|
||
| 2 | Verified Gross Mass (VGM)* | | |
|
||
| 3 | Shipping Instructions* | | |
|
||
| 4 | Train Waybill* | | |
|
||
| 5 | Commercial Invoice* | | |
|
||
| 6 | Packing List* | | |
|
||
| 7 | Bank Permit* | | |
|
||
| 8 | Sales Contract* | | |
|
||
| 9 | Power of Attorney* | | |
|
||
| 10 | Export License* | | |
|
||
| 11 | | Export declaration docs | |
|
||
| 12 | | Export Release | |
|
||
| 13 | | T1 transport document | |
|
||
| 14 | | | Release Order* |
|
||
| 15 | | | Full in Interchange |
|
||
| 16 | | Final Declaration | |
|
||
|
||
Commodity-specific (Coffee, Teff, etc.): additional regulatory docs per GL PDF US-03.
|
||
|
||
**Path B (customs export):** Customer uploads rows 1–10 on **`contract_id`** after contract sign (same pre-booking pattern as import §11.2). GL ET creates booking after `CLEARANCE_READY_FOR_BOOKING`. VGM and container numbers are entered by **GL ET at booking creation**, not by the customer at contract sign.
|
||
|
||
### 12.2 Export Milestone Sequence
|
||
|
||
| # | Milestone | Owner | Doc triggered? |
|
||
|---|-----------|-------|----------------|
|
||
| 1 | Export Documents Uploaded | Customer | No |
|
||
| 2 | Pending Document Review | GL-ET | Yes |
|
||
| 3 | Documents Approved | GL-ET | No |
|
||
| 4 | Release Order Secured | GL-DJ | Yes (RO upload) |
|
||
| 5 | Under Customs Clearance | GL-ET | No |
|
||
| 6 | Declared | GL-ET | Yes (EX3/EX8) |
|
||
| 7 | Export Released | GL-ET | No |
|
||
| 8 | Wagon Requested | GL-ET | No |
|
||
| 9 | Pending Payment | Customer | No |
|
||
| 10 | Payment Settled | Customer | Yes |
|
||
| 11 | Wagon Allocated | Operations | No |
|
||
| 12 | Cargo Arrived | Port/Terminal | No |
|
||
| 13 | Ready for Loading | Port/Terminal | No |
|
||
| 14 | Loaded | Port/Terminal | No |
|
||
| 15 | Departed to Djibouti | Port/Terminal | No |
|
||
| 16 | Arrived at Djibouti | GL-DJ | No |
|
||
| 17 | Gatepass Granted | GL-DJ | No |
|
||
| 18 | Offloaded | GL-DJ | Yes (Full-in Interchange) |
|
||
|
||
### 12.3 Export ET ↔ DJ Handoff (US-09 Export)
|
||
|
||
When `DEPARTED_TO_DJIBOUTI` from Ethiopia:
|
||
- Primary owner → GL Djibouti
|
||
- GL Ethiopia receives read-only copy + ATD notification
|
||
|
||
---
|
||
|
||
## 13. Customs Clearance Path — GL-Owned Execution
|
||
|
||
**Applies when:** `customs_clearing_enabled = true` (transport with Global Logistics / customs clearance service).
|
||
|
||
This is the **default execution path** for import, export, and intercity contracts that bundle customs clearance. The customer **never** creates a booking or enters shipment quantities, container numbers, or schedule dates.
|
||
|
||
### 13.1 End-to-End Flow
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
A[Contract signed FULLY_EXECUTED or CONTRACT_ACTIVE] --> B[Customer uploads clearance docs on contract]
|
||
B --> C[CLEARANCE_UNDER_REVIEW]
|
||
C --> D[GL ET approves or queries each document]
|
||
D --> E[GL ET and GL DJ upload output docs milestones]
|
||
E --> F[CLEARANCE_READY_FOR_BOOKING]
|
||
F --> G[GL ET creates booking — enters ALL shipment data]
|
||
G --> H[Batch pool PNR generated]
|
||
H --> I[Customer pays freight]
|
||
I --> J[GL Ops milestones wagon allocation transit]
|
||
J --> K[COMPLETED or EXPIRED payment]
|
||
K -->|GENERAL next shipment| B
|
||
```
|
||
|
||
### 13.2 Customer Responsibilities (Path B only)
|
||
|
||
| Action | When | Where |
|
||
|--------|------|-------|
|
||
| Sign contract | After Marketing approval | `/contracts/:id/contract` |
|
||
| Upload clearance documents | After contract counter-sign | `/contracts/:id/clearance` |
|
||
| Re-upload queried documents | When GL queries a specific doc | `/contracts/:id/clearance` |
|
||
| Upload duty/tax payment slip | When GL advises amount | `/contracts/:id/clearance` |
|
||
| **Pay freight PNR** | When booking selected for batch | `/bookings/:id` — **Pay** button |
|
||
| Upload storage/demurrage slip | If applicable post-transit | `/bookings/:id` |
|
||
| Track shipment | Read-only | `/bookings/:id`, `/tracking` |
|
||
|
||
**Customer does NOT:** create booking, pick schedule date, enter container numbers, enter quantities, enter VGM, or confirm GL-entered booking data.
|
||
|
||
### 13.3 GL Ethiopia Responsibilities
|
||
|
||
| Action | When |
|
||
|--------|------|
|
||
| Review/approve/query customer clearance docs | `CLEARANCE_UNDER_REVIEW` on contract |
|
||
| Upload IM4/IM5/EX3/EX8/T1 and other ET output docs | During pre-booking clearance |
|
||
| Request wagon / advance clearance milestones | Before or after booking create per import/export matrix |
|
||
| **Create booking** with route, `scheduledDate`, container qty/numbers/VGM, bulk ton/item count, hazard/reefer counts | When `CLEARANCE_READY_FOR_BOOKING` |
|
||
| Continue post-booking ET milestones | After booking exists |
|
||
|
||
### 13.4 API (Path B)
|
||
|
||
```
|
||
# Customer — post-sign clearance on contract (no booking yet)
|
||
GET /contracts/:id/clearance
|
||
POST /contracts/:id/clearance/documents
|
||
POST /contracts/:id/clearance/adhoc-documents
|
||
|
||
# GL ET — review pre-booking clearance
|
||
GET /contracts/clearance/queue?region=ET
|
||
POST /contracts/:id/clearance/review { fileKey, status, note }
|
||
POST /contracts/:id/clearance/output-documents
|
||
POST /contracts/:id/clearance/finalize → CLEARANCE_READY_FOR_BOOKING
|
||
|
||
# GL ET — create booking (exclusive; replaces customer POST)
|
||
POST /contracts/:contractId/bookings
|
||
Authorization: edr_gl_ethiopia
|
||
Body: { contractRouteId, scheduledDate, containers[], bulkLines[], notes }
|
||
Response: booking { id, reference, totalAmount, status }
|
||
Side effects:
|
||
- contract.status → ACTIVE_SHIPMENT_IN_PROGRESS
|
||
- contract_clearance_cycles.booking_id set
|
||
- customer notified (email + in-app)
|
||
|
||
# Customer — payment only (unchanged)
|
||
POST /bookings/:id/payment/pay
|
||
```
|
||
|
||
**Removed:** `POST /bookings/:id/customer/confirm-gl-booking` — GL booking is authoritative; no customer confirmation gate.
|
||
|
||
### 13.5 Field Parity
|
||
|
||
GL booking form (§8.2) collects the same shipment fields the customer would enter in Path A (§8.1). GL staff source container numbers and VGM from physical documents and port data — not from customer portal entry.
|
||
|
||
---
|
||
|
||
## 14. Gap Analysis Matrix
|
||
|
||
| # | Topic | PDF / Stakeholder requirement | Current implementation | Target design | Gap severity |
|
||
|---|-------|------------------------------|------------------------|---------------|--------------|
|
||
| 1 | Contract vs booking separation | US-03, US-07, US-08; ITLMS §1 "Upon contract signature, invite booking" | Single `bookings` row | `contracts` + `bookings` | **Critical** |
|
||
| 2 | Contract cargo = scope only | US-03 Case 2: no weight/qty at contract; 20ft/40ft only | Wizard step 3 collects qty, VGM | `contract_cargo_scope` | **Critical** |
|
||
| 3 | Unit-rate contract pricing | US-04; stakeholder step 8 | Total amount from qty at intake | `UNIT_RATES` display mode | **Critical** |
|
||
| 4 | Hazard/reefer at contract | US-03 Case 3: billing flags at contract | Route step on booking; reefer from container type | `contracts.is_hazardous/is_reefer`; counts at booking | **High** |
|
||
| 5 | Binding vs estimated date | US-03 one-time: departure at contract; stakeholder: estimate only at contract | `estimatedShipmentDate` + `scheduledDate` both on booking | Estimate on contract; binding on booking | **High** |
|
||
| 6 | Contract intake docs | Stakeholder step 7 → contract | Wizard docs + draft booking docs mixed | `resource=contracts` intake settings | **High** |
|
||
| 7 | Pre-booking clearance (Path B) | Stakeholder: sign → upload docs → GL approves → GL books | Clearance on booking after booking create | Customer docs on `contract_id` before booking; GL creates booking | **Critical** |
|
||
| 8 | General contract qty pool | Stakeholder: no qty at contract | `contract_route_lines.quantity` + pool math | Remove quantity; validity-only | **Critical** |
|
||
| 9 | ONE_TIME re-book after payment expiry | Stakeholder requirement | EXPIRED on booking; no contract link for re-book | `contract_id` FK + partial unique index | **High** |
|
||
| 10 | Contract renewal/amendment | US-03 renewal paths | `previous_contract_id` on booking | `contracts.renewal_of_id` + amendment statuses | **High** |
|
||
| 11 | GL ET vs DJ split | GL PDF throughout | Single `edr_global_logistics` role | `edr_gl_ethiopia`, `edr_gl_djibouti` | **Critical** |
|
||
| 12 | GL milestones | 18–23 milestones per direction | 3 clearance statuses | `clearance_milestones` table | **Critical** |
|
||
| 13 | Phased document uploads | GL PDF: before pay / after pay / after data | Flat clearance doc list | `clearance_document_phases` | **High** |
|
||
| 14 | Station routing | GL US-02 | Not implemented | `gl_station_yard_id` + queue routing | **High** |
|
||
| 15 | GL-owned booking (Path B) | Stakeholder + GL US-06: GL enters all shipment data; customer pays only | Customer-only booking creation | GL ET exclusive `POST /contracts/:id/bookings`; no customer booking wizard | **Critical** |
|
||
| 16 | Djibouti Release Order | Export milestone 4; unlocks ET clearance | Single `clearance_output_*` code | DJ upload slot + milestone trigger | **High** |
|
||
| 17 | Duty/tax payment slip | Import milestone 7 | Not modeled | CUSTOMER_DUTY phase slot | **Medium** |
|
||
| 18 | Damage/exception reporting | Import US-07 AC1 | Not implemented | Incident form on GL-DJ loading | **Medium** |
|
||
| 19 | Demurrage auto-calc | GL US-12; ITLMS §8 | Not in freight-api | Future — finance module | **Low** (out of scope) |
|
||
| 20 | Multimodal GL | GL PDF multimodal section | Not implemented | Future phase | **Low** (out of scope) |
|
||
| 21 | MSRN reference prefix | GL V01: MSRNI/MSRNE | Booking reference format differs | Optional: align reference prefixes | **Low** |
|
||
| 22 | Approval hierarchy on contract | US-06 container vs bulk routing | On booking today | Move to `contract_approval_steps` | **Medium** |
|
||
| 23 | Container numbers at booking | ITLMS §1; stakeholder booking step | `container_number` nullable on line; not per-unit | `booking_container_units` | **High** |
|
||
| 24 | Domestic/intercity clearance | No customs gate | `clearance.util` returns null for DOMESTIC | Unchanged | **None** |
|
||
| 25 | Batch/scheduling/allocation | ITLMS §1 priority, wagon calc | Implemented on booking | Unchanged — operates on booking after GL/customer creates it | **None** |
|
||
| 26 | Customer payment-only in customs path | Stakeholder: customer pays; GL does rest | Customer enters all data + pays | Customer: upload docs + pay PNR/duty only | **Critical** |
|
||
|
||
---
|
||
|
||
## 15. API Endpoint Mapping
|
||
|
||
### 15.1 New Contract Endpoints
|
||
|
||
| Method | Path | Replaces | Notes |
|
||
|--------|------|----------|-------|
|
||
| POST | `/contracts` | `POST /bookings` | Multipart; creates contract + routes + cargo scope |
|
||
| PATCH | `/contracts/:id` | `PATCH /bookings/:id` | Customer editable in DRAFT, CHANGES_REQUESTED |
|
||
| GET | `/contracts` | `GET /bookings?bookingType=GENERAL_CONTRACT` | List/filter |
|
||
| GET | `/contracts/my` | Portal scoped list | |
|
||
| GET | `/contracts/:id` | Contract detail | Includes routes, cargo scope, unit rates |
|
||
| POST | `/contracts/:id/documents` | `POST /bookings/:id/documents` | Intake docs |
|
||
| POST | `/contracts/:id/generate-price` | Same on booking | Returns unit rates |
|
||
| POST | `/contracts/:id/submit` | Same on booking | Freezes contract_rate_snapshots |
|
||
| POST | `/contracts/:id/confirm-submit` | Same | Price change confirm |
|
||
| POST | `/contracts/:id/staff/accept` | Same | Sets validity window |
|
||
| POST | `/contracts/:id/staff/request-changes` | Same | |
|
||
| POST | `/contracts/:id/staff/reject` | Same | |
|
||
| POST | `/contracts/:id/approval-steps/:stepId/approve` | Same | |
|
||
| POST | `/contracts/:id/contract/generate` | Same | PDF from contract |
|
||
| GET | `/contracts/:id/contract/view` | Same | |
|
||
| POST | `/contracts/:id/contract/sign` | Same | |
|
||
| POST | `/contracts/:id/renew` | Partial via previousContractRef | Renewal workflow |
|
||
| POST | `/contracts/:id/renew/accept-amendments` | New | Customer accepts |
|
||
| POST | `/contracts/:id/renew/reject-amendments` | New | Customer rejects |
|
||
|
||
### 15.2 Booking-under-Contract Endpoints
|
||
|
||
| Method | Path | Actor | Notes |
|
||
|--------|------|-------|-------|
|
||
| POST | `/contracts/:contractId/bookings` | **Customer** (Path A only) | `customs_clearing_enabled = false` |
|
||
| POST | `/contracts/:contractId/bookings` | **GL ET** (Path B only) | `clearance_status = CLEARANCE_READY_FOR_BOOKING` |
|
||
| GET | `/contracts/:contractId/bookings` | Customer / staff | List shipments under contract |
|
||
|
||
### 15.2.1 Contract Pre-Booking Clearance Endpoints (Path B)
|
||
|
||
| Method | Path | Actor | Notes |
|
||
|--------|------|-------|-------|
|
||
| GET | `/contracts/:id/clearance` | Customer / GL ET | Document grid on contract (no booking) |
|
||
| POST | `/contracts/:id/clearance/documents` | Customer | Upload clearance docs after sign |
|
||
| POST | `/contracts/:id/clearance/review` | GL ET | Approve/query per `fileKey` |
|
||
| POST | `/contracts/:id/clearance/output-documents` | GL ET / GL DJ | GL output uploads pre-booking |
|
||
| POST | `/contracts/:id/clearance/finalize` | GL ET | → `CLEARANCE_READY_FOR_BOOKING` |
|
||
| GET | `/contracts/clearance/queue` | GL ET | Contracts awaiting review (not bookings) |
|
||
|
||
### 15.3 Unchanged Booking Endpoints (operate on shipment)
|
||
|
||
All clearance, operation, payment, batch, transit endpoints remain on `/bookings/:id/*`:
|
||
|
||
- `/bookings/:id/clearance/*`
|
||
- `/bookings/:id/clearance/proceed`
|
||
- `/bookings/:id/operation/review`
|
||
- `/bookings/:id/payment/pay`
|
||
- `/bookings/:id/operations/start-transit`
|
||
- `/bookings/:id/operations/complete`
|
||
|
||
### 15.4 New GL Endpoints
|
||
|
||
| Method | Path | Actor |
|
||
|--------|------|-------|
|
||
| GET | `/contracts/clearance/queue?region=ET` | GL ET — **contracts** awaiting doc review |
|
||
| GET | `/bookings/clearance/queue?region=ET\|DJ` | GL ET/DJ — post-booking milestone queue |
|
||
| POST | `/bookings/:id/clearance/milestones/:code/complete` | GL/Ops/Terminal |
|
||
| POST | `/bookings/:id/clearance/incidents` | GL-DJ damage report |
|
||
| POST | `/bookings/:id/gl/station-assign` | GL station manager |
|
||
| GET | `/contracts/:id/read-only` | GL read-only contract view |
|
||
|
||
### 15.5 Deprecated Endpoints (Phase 4)
|
||
|
||
| Endpoint | Replacement |
|
||
|----------|-------------|
|
||
| `POST /bookings` (contract creation) | `POST /contracts` |
|
||
| `POST /booking-orders` | `POST /contracts/:id/bookings` |
|
||
| `GET /booking-orders/contract/:id/pool` | Removed — no qty pool |
|
||
|
||
---
|
||
|
||
## 16. Frontend Route & Component Mapping
|
||
|
||
### 16.1 Portal Routes (Target)
|
||
|
||
| Current | Target | Action |
|
||
|---------|--------|--------|
|
||
| `/bookings/new` | `/contracts/new` | New contract wizard (§7) |
|
||
| `/bookings/:id` (contract phase) | `/contracts/:id` | Contract detail + sign |
|
||
| `/bookings/:id/contract` | `/contracts/:id/contract` | Move `BookingContractPage` → `ContractPage` |
|
||
| `/contracts` | `/contracts` | Query `contracts` table (both kinds) |
|
||
| `/contracts/:id` | `/contracts/:id` | Show unit rates, validity, booking list |
|
||
| `PlaceOrderDialog` | `/contracts/:id/bookings/new` | Path A only — customer booking wizard (§8.1) |
|
||
| New | `/contracts/:id/clearance` | Path B — customer uploads clearance docs after sign |
|
||
| `/contracts/:id` | `/contracts/:id` | Path B: clearance CTA; Path A: "New booking" CTA |
|
||
| `/bookings/:id` (shipment) | `/bookings/:id` | Shipment detail — **Pay** (Path B primary action), track |
|
||
| `/bookings` | `/bookings` | My shipments list |
|
||
|
||
### 16.2 Portal Component Changes
|
||
|
||
| File | Change |
|
||
|------|--------|
|
||
| `new-booking-form/schema.ts` | Split into `new-contract-form/schema.ts` + `new-shipment-form/schema.ts` |
|
||
| `step5-cargo-details.tsx` | Contract: sizes/commodity only; Booking: qty + container numbers |
|
||
| `step4-route.tsx` | Contract: estimated date only; Booking: binding scheduled date |
|
||
| `step-documents.tsx` | Contract: intake docs → contract API |
|
||
| `NewBookingPage.tsx` | Rename/refactor → `NewContractPage.tsx` |
|
||
| New | `NewShipmentPage.tsx` under contract |
|
||
| `ContractDetailPage.tsx` | Path A: "New booking" CTA; Path B: "Upload clearance documents" + read-only booking list after GL books |
|
||
| New | `ContractClearanceFlow.tsx` | Path B customer clearance upload on contract (mirrors `ClearanceFlow` but targets `contract_id`) |
|
||
| `ClearanceFlow.tsx` | Path A post-booking only; Path B uses `ContractClearanceFlow` until booking exists |
|
||
| `BookingDetailPage/constants.ts` | Split `PROGRESS_STAGES` into contract vs booking |
|
||
|
||
### 16.3 Backoffice Routes (Target)
|
||
|
||
| Current | Target |
|
||
|---------|--------|
|
||
| `/dashboard/booking-requests` | `/dashboard/contract-requests` (contract approval queue) |
|
||
| `/dashboard/booking-requests/:id` | `/dashboard/contract-requests/:id` |
|
||
| `/dashboard/clearance` | `/dashboard/clearance/ethiopia` + `/dashboard/clearance/djibouti` (post-booking) |
|
||
| *(new)* | `/dashboard/contracts/clearance` — GL ET queue for contracts in `CLEARANCE_UNDER_REVIEW` |
|
||
| *(new)* | `/dashboard/contracts/:id/create-booking` — GL ET booking form (§8.2); **exclusive Path B booking creation** |
|
||
| *(new)* | `/dashboard/bookings/:id/milestones` — GL post-booking milestone panel |
|
||
|
||
### 16.4 Backoffice Component Changes
|
||
|
||
| File | Change |
|
||
|------|--------|
|
||
| `DocumentClearanceListPage.tsx` | Filter by `region=ET\|DJ`; show milestone progress |
|
||
| `ClearanceReviewSection.tsx` | Phase-grouped document tabs |
|
||
| New | `ClearanceMilestoneTimeline.tsx` |
|
||
| New | `ContractClearanceReviewSection.tsx` | GL review of docs on contract (pre-booking) |
|
||
| New | `GlCreateBookingForm.tsx` | GL ET enters schedule, qty, container numbers, bulk data (§8.2) |
|
||
| `BookingRequestsPage.tsx` | → `ContractRequestsPage.tsx` |
|
||
|
||
---
|
||
|
||
## 17. Migration Plan
|
||
|
||
### Phase 1 — Additive Schema (no breaking changes)
|
||
|
||
1. Create `contracts`, `contract_routes`, `contract_cargo_scope`, `contract_signatures`, `contract_approval_steps`, `contract_rate_snapshots`, `contract_review_notes`, **`contract_document_review`**, **`contract_clearance_cycles`**.
|
||
2. Create `clearance_milestones`, extend `file_upload_fields` with phase columns.
|
||
3. Add `bookings.contract_id`, `bookings.contract_route_id`, `booking_container_units`.
|
||
4. Dual-write: new contract wizard writes both `contracts` and legacy `bookings` row (feature flag `CONTRACT_SPLIT_DUAL_WRITE=true`).
|
||
5. Backfill: for each `booking_type = GENERAL_CONTRACT` booking, insert `contracts` row and link existing child bookings.
|
||
|
||
**Rollback:** Drop new tables; null `bookings.contract_id`.
|
||
|
||
### Phase 2 — UI Switch
|
||
|
||
1. Deploy contract wizard at `/contracts/new`; redirect `/bookings/new` → `/contracts/new`.
|
||
2. Deploy booking wizard at `/contracts/:id/bookings/new`.
|
||
3. Marketing queue reads from `contracts`.
|
||
4. Portal contract list reads `contracts` table.
|
||
|
||
**Rollback:** Feature flag revert to legacy wizard.
|
||
|
||
### Phase 3 — Read Switch + GL
|
||
|
||
1. GL queues split ET/DJ; milestone panel live.
|
||
2. Contract PDF generator reads `contracts`.
|
||
3. Stop dual-write; `POST /bookings` rejects contract creation (returns 410 with redirect hint).
|
||
4. Payment, batch, allocation verified on booking-with-contract_id in staging.
|
||
|
||
### Phase 4 — Cleanup
|
||
|
||
1. Drop `bookings.booking_type`, `previous_contract_id`, contract columns migrated to `contracts`.
|
||
2. Drop `booking_orders` table (after data migration to direct bookings).
|
||
3. Drop `quantity` from `contract_routes`.
|
||
4. Remove `edr_global_logistics` union role.
|
||
5. Remove dual-write flag and legacy code paths.
|
||
|
||
### Data Migration Script Outline
|
||
|
||
```sql
|
||
-- For each booking WHERE booking_type = 'GENERAL_CONTRACT':
|
||
INSERT INTO freight.contracts (...) SELECT ... FROM freight.bookings;
|
||
UPDATE freight.bookings SET contract_id = ... WHERE id = general_contract_id;
|
||
UPDATE freight.bookings SET contract_id = ... WHERE id IN (child booking ids via booking_orders);
|
||
|
||
-- For each ONE_TIME booking that went through full contract flow:
|
||
INSERT INTO freight.contracts (...);
|
||
INSERT INTO freight.bookings (contract_id, ...) -- new shipment row from operational fields;
|
||
-- Or: same row gets contract_id pointing to newly inserted contract parent.
|
||
```
|
||
|
||
Exact backfill strategy depends on whether historical ONE_TIME rows should split into contract + booking parent-child or coalesce (recommend: insert contract from row; same row becomes shipment with `contract_id` set).
|
||
|
||
---
|
||
|
||
## 18. Out of Scope / Future Work
|
||
|
||
| Item | Source | Notes |
|
||
|------|--------|-------|
|
||
| Demurrage auto-calculation | GL US-12, ITLMS §8 | Requires dwell-time engine + finance billing module |
|
||
| Storage invoice / exit note block | GL US-12 AC3-4 | Terminal module integration |
|
||
| Multimodal GL (sea/air legs) | GL PDF multimodal section | Email-handled steps (US 03–05) need separate design |
|
||
| Finance AP closure / Permanently Closed | GL US-10, US-13 | Corporate finance module |
|
||
| MSRN reference prefix (MSRNI/MSRNE) | GL V01 AC4 | Cosmetic alignment with booking reference generator |
|
||
| Wagon type selection (NW7, PW2, etc.) | ITLMS §1 | Operations scheduling enhancement |
|
||
| 3-hour holding period aggregation | ITLMS §1 | Batch engine enhancement |
|
||
| Priority scoring syntax | ITLMS §1 | Already partially in `priority_score`; full matrix TBD |
|
||
| Ministry of Trade TIN lookup | US-01 | Onboarding module |
|
||
| Grievance/ticketing | US-09 | Separate module |
|
||
| Marketing analytics dashboard | US-10 | Separate module |
|
||
|
||
---
|
||
|
||
## 19. Open Items for Sign-Off
|
||
|
||
| # | Item | Options | Recommendation |
|
||
|---|------|---------|----------------|
|
||
| 1 | Historical ONE_TIME backfill | Split row vs. insert contract sibling | Insert contract; same booking row gets `contract_id` |
|
||
| 2 | Contract reference prefix | CTR-, CON-, keep booking ref | `CTR-YYYY-NNNNN` |
|
||
| 3 | GL booking customer confirm | Required vs. informational | **Not required** — GL booking is authoritative; customer notified + pays when due |
|
||
| 4 | GENERAL contract soft cap | Unlimited bookings vs. optional max | Unlimited within validity (per stakeholder) |
|
||
| 5 | Contract intake doc setting codes | Reuse onboarding settings vs. new | New `contract_intake_*` codes |
|
||
| 6 | Intercity contract flow | Same split vs. simplified | Same split; skip clearance gate |
|
||
| 7 | Feature flag duration | Dual-write period | 2 sprints minimum |
|
||
| 8 | Bulk hazard count semantics | Per-ton vs. per-shipment | Per booking line matching container pattern |
|
||
|
||
---
|
||
|
||
## Appendix A — Contract Status Enum (Implementation)
|
||
|
||
```typescript
|
||
export const CONTRACT_STATUSES = [
|
||
'DRAFT',
|
||
'SUBMITTED',
|
||
'PRICE_CHANGED_PENDING_CONFIRM',
|
||
'CHANGES_REQUESTED',
|
||
'PENDING_APPROVAL',
|
||
'APPROVED',
|
||
'APPROVED_PENDING_SIGNATURE',
|
||
'CONTRACT_READY',
|
||
'SIGNED_CUSTOMER',
|
||
'FULLY_EXECUTED', // transport-only ONE_TIME
|
||
'CONTRACT_ACTIVE', // transport-only GENERAL
|
||
// Path B — customs clearance (pre-booking)
|
||
'AWAITING_CLEARANCE_DOCUMENTS',
|
||
'CLEARANCE_UNDER_REVIEW',
|
||
'CLEARANCE_READY_FOR_BOOKING',
|
||
'ACTIVE_SHIPMENT_IN_PROGRESS',
|
||
'CONTRACT_CLOSED',
|
||
'EXPIRED',
|
||
'REJECTED',
|
||
'CANCELLED',
|
||
// Renewal
|
||
'RENEWAL_DRAFT',
|
||
'RENEWAL_SUBMITTED',
|
||
'RENEWAL_PENDING_APPROVAL',
|
||
'AMENDMENTS_PROPOSED',
|
||
'ARCHIVED',
|
||
] as const;
|
||
```
|
||
|
||
## Appendix B — Clearance Milestone Codes (Import)
|
||
|
||
```typescript
|
||
export const IMPORT_MILESTONES = [
|
||
'IMPORT_DOCS_UPLOADED',
|
||
'PENDING_DOCUMENT_REVIEW',
|
||
'DOCUMENTS_APPROVED',
|
||
'UNDER_CUSTOMS_CLEARANCE',
|
||
'DECLARED',
|
||
'DUTY_TAXES_ADVISED',
|
||
'DUTY_TAX_PAID',
|
||
'DO_COLLECTED',
|
||
'WAGON_REQUESTED',
|
||
'FREIGHT_PAYMENT_SETTLED',
|
||
'WAGON_ALLOCATED',
|
||
'GATEPASS_GRANTED',
|
||
'READY_FOR_LOADING',
|
||
'LOADED',
|
||
'DEPARTED_FROM_DJIBOUTI',
|
||
'ARRIVED_ETHIOPIA',
|
||
'OFFLOADED',
|
||
'T1_CLOSED',
|
||
'RISK_ASSIGNED',
|
||
'IMPORT_RELEASE_GRANTED',
|
||
'IMPORT_PROCESS_COMPLETED',
|
||
'STORAGE_INVOICE_RAISED',
|
||
'EXIT_NOTE_GENERATED',
|
||
] as const;
|
||
```
|
||
|
||
## Appendix C — Clearance Milestone Codes (Export)
|
||
|
||
```typescript
|
||
export const EXPORT_MILESTONES = [
|
||
'EXPORT_DOCS_UPLOADED',
|
||
'PENDING_DOCUMENT_REVIEW',
|
||
'DOCUMENTS_APPROVED',
|
||
'RELEASE_ORDER_SECURED',
|
||
'UNDER_CUSTOMS_CLEARANCE',
|
||
'DECLARED',
|
||
'EXPORT_RELEASED',
|
||
'WAGON_REQUESTED',
|
||
'FREIGHT_PAYMENT_PENDING',
|
||
'FREIGHT_PAYMENT_SETTLED',
|
||
'WAGON_ALLOCATED',
|
||
'CARGO_ARRIVED',
|
||
'READY_FOR_LOADING',
|
||
'LOADED',
|
||
'DEPARTED_TO_DJIBOUTI',
|
||
'ARRIVED_AT_DJIBOUTI',
|
||
'GATEPASS_GRANTED',
|
||
'OFFLOADED',
|
||
] as const;
|
||
```
|
||
|
||
---
|
||
|
||
*End of document.*
|