Перейти до змісту

Logistic — Architecture (Multimodal Freight & Forwarding)

Імплементаційна сторона платного плагіна Logistic. Операційну модель (bussiness entities, journals, scenarios) — див. operations.md; короткий огляд плагіна — README.md.

Module: logistic (платний плагін, поза community backend) Date: 2026-02-24 Status: Architecture approved, implementation in progress


Problem Statement

Support multimodal freight forwarding where a single shipment travels via multiple transport modes (rail → road → sea), involves multiple subcontractors per leg, and belongs either to a spot order or a long-term contract. Two canonical scenarios:

Scenario A — Container, Kiev → Toronto 1. Rail: Kyiv-Tovarna → Odessa-Port (contractor: UZ) 2. Road: Odessa station → Port Chornomorsk (own vehicle) 3. Sea: Port Chornomorsk → Toronto (chartered / liner)

Scenario B — Bulk Grain Consolidation, Ukraine → Egypt - 10–20 elevators across Ukraine ship grain (rail + road) to Odessa port - All grain consolidated at port into one vessel - Sea: Odessa → Alexandria/Damietta - One ConsolidationPlan groups N TransportOrders → one Shipment (sea leg)


Core Entity Model

"Multimodal freight forwarding: transport orders, shipments, consolidation planning, vessel bookings and route templates.",

RouteTemplate ──────────────────────────────────────┐
   └── RouteTemplateSegment × N (legs config)        │
FreightContract (long-term, volume, spot) ───────────┤
   └── FreightContractRate × N                       │
TransportOrder (client request) ──────────► Shipment (execution)
                                               └── ShipmentLeg × N
ConsolidationPlan ──────────────────────────►    └── TransportDocument × N
   └── ConsolidationCollectionOrder × N
                                           VesselBooking (1 booking → N plans)

Django Models

Master Data (logistic/models/master_data.py)

CargoType

Field Type Notes
name / name_ua CharField
cargo_class choice bulk, container, breakbulk, tanker, reefer
requires_container BooleanField
hs_code_prefix CharField ТН ЗЕД prefix

ContainerType

Field Type Notes
iso_code CharField 22G1, 45G1…
length_ft IntegerField 20, 40, 45
max_payload_kg DecimalField
max_volume_m3 DecimalField

LocationPoint (logistic-extended)

Field Type Notes
location_type choice city, sea_port, river_port, railway_station, warehouse, elevator, customs, airport
country FK(Country)
city CharField
un_locode CharField UN/LOCODE for ports
latitude / longitude DecimalField
timezone CharField

FreightContractor

Field Type Notes
client OneToOne(essentials.Client) reuses client record
contractor_type choice carrier, agent, stevedore, customs_broker, surveyor
handles_road/rail/sea/air BooleanField
scac_code CharField maritime ID

RouteTemplate

Field Type Notes
name / name_ua CharField e.g. "Ukraine → Toronto (Container 40HC)"
origin_country FK(Country)
destination_country FK(Country)
cargo_type FK(CargoType)
is_consolidation_route BooleanField for grain-type scenarios
consolidation_point FK(LocationPoint) e.g. Odessa port
estimated_transit_days IntegerField
_subtables ['segments']

RouteTemplateSegment

Field Type Notes
route_template FK
sequence IntegerField ordering
transport_mode choice road, rail, sea, air
from_location FK(LocationPoint)
to_location FK(LocationPoint)
executor_type choice own, subcontractor
default_contractor FK(FreightContractor) nullable
transit_days_min / max IntegerField
generates_document choice cmr, railway_bill, bl, awb

Freight Contract (logistic/models/freight_contract.py)

FreightContract (extends TransactionModel)

Field Type Notes
client FK(Client)
route_template FK(RouteTemplate)
contract_type choice spot, period, volume
valid_from / valid_to DateField
contracted_volume Decimal for volume contracts
contracted_unit FK(Unit) tons, TEU, m³
shipped_volume Decimal auto-updated
currency FK(Currency)
_subtables ['rates']

FreightContractRate

Field Type Notes
contract FK
segment FK(RouteTemplateSegment) null = whole route
rate_type choice per_ton, per_container, lumpsum, per_cbm
rate Decimal
currency FK(Currency)
valid_from / valid_to DateField
min_quantity Decimal volume discount threshold

Transport Order (logistic/models/transport_order.py)

TransportOrder (extends TransactionModel)

Field Type Notes
client FK(Client)
contract FK(FreightContract) nullable for spot
route_template FK(RouteTemplate) nullable, can override
origin FK(LocationPoint)
destination FK(LocationPoint)
required_loading_date DateField
required_delivery_date DateField
cargo_type FK(CargoType)
cargo_description TextField
gross_weight_kg Decimal
volume_m3 Decimal
container_count IntegerField
container_type FK(ContainerType)
is_hazardous BooleanField ADR/IMDG
requires_customs BooleanField
temperature_regime CharField for reefer
priority choice normal, high, urgent
consolidation_plan FK(ConsolidationPlan) assigned after plan created
shipment FK(Shipment) set when shipment created
_subtables ['cargo_items']

TransportOrderCargoItem

Field Type Notes
order FK
item FK(essentials.Item) nullable
description CharField
quantity / unit Decimal + FK
weight_kg Decimal
hs_code CharField ТН ЗЕД
country_of_origin FK(Country)

Shipment (logistic/models/shipment.py)

Shipment (extends TransactionModel)

Status workflow: draft → confirmed → in_progress → completed | cancelled

Field Type Notes
route_template FK nullable
consolidation_plan FK nullable
total_weight_kg Decimal
total_volume_m3 Decimal
container_count IntegerField
container_type FK(ContainerType)
total_freight_cost Decimal auto-sum of legs
currency FK(Currency)
planned/actual_departure DateField
planned/actual_arrival DateField
customs_declaration_number CharField
incoterms choice EXW, FOB, CIF, DAP, DDP
_subtables ['legs', 'documents']

ShipmentLeg

Field Type Notes
shipment FK
sequence IntegerField ordering
transport_mode choice road, rail, sea, air
from_location FK(LocationPoint)
to_location FK(LocationPoint)
executor_type choice own, subcontractor
contractor FK(FreightContractor) null if own
vehicle FK(fleet.Vehicle) null unless own
driver FK(fleet.Driver) null unless own
waybill FK(fleet.Waybill) null unless own
vessel_name CharField sea leg
voyage_number CharField sea leg
wagon_numbers TextField rail leg (comma separated)
container_numbers TextField
truck_number CharField road leg
planned/actual_departure DateTimeField
planned/actual_arrival DateTimeField
freight_cost Decimal
currency FK(Currency)
cost_basis choice per_ton, per_container, lumpsum
status choice planned, confirmed, cargo_ready, departed, arrived, completed, delayed
tracking_reference CharField external tracking number
delay_reason TextField
_subtables ['documents']

TransportDocument

Field Type Notes
shipment_leg FK(ShipmentLeg) nullable
shipment FK(Shipment) nullable (shipment-level docs)
doc_type choice cmr, ttn, railway_bill, bl, awb, customs_decl, phyto, quality_cert
document_number CharField
issue_date DateField
issued_by CharField
file FileField S3 / local

Consolidation (logistic/models/consolidation.py)

ConsolidationPlan (extends TransactionModel)

Field Type Notes
route_template FK
consolidation_point FK(LocationPoint) e.g. Odessa port
destination FK(LocationPoint) e.g. Alexandria
vessel_booking FK(VesselBooking) assigned after booking
cargo_type FK(CargoType)
target_quantity Decimal e.g. 8 000 tons
target_unit FK(Unit)
collected_quantity Decimal auto-updated
collection_window_from/to DateField window for collection legs
planned_loading_start/end DateField port loading dates
requires_phyto BooleanField grain export
requires_quality_cert BooleanField
_subtables ['collection_orders']

ConsolidationCollectionOrder

Field Type Notes
plan FK
origin FK(LocationPoint) elevator / warehouse
transport_order FK(TransportOrder) optional link
planned_quantity Decimal
unit FK(Unit)
transport_mode choice road, rail
contractor FK(FreightContractor)
planned_departure DateField
planned_arrival_to_port DateField
actual_quantity Decimal null until arrived
status choice planned, in_transit, arrived

Vessel Booking (logistic/models/vessel_booking.py)

VesselBooking (extends TransactionModel)

Field Type Notes
vessel_name CharField
imo_number CharField
voyage_number CharField
booking_type choice liner, charter
shipping_line FK(FreightContractor)
port_of_loading FK(LocationPoint)
port_of_discharge FK(LocationPoint)
etd / eta DateTimeField estimated
atd / ata DateTimeField actual
booked_capacity Decimal
capacity_unit FK(Unit) tons / TEU
actual_loaded Decimal
freight_rate Decimal
rate_basis choice per_ton, per_teu, lumpsum
total_freight Decimal
currency FK(Currency)
laytime_hours Decimal charter: allowed port time
demurrage_rate Decimal penalty per day over laytime
dispatch_rate Decimal bonus per day under laytime
bill_of_lading_number CharField

Status Flow

TransportOrder      Shipment            ShipmentLeg
───────────────     ──────────          ─────────────────────
new                 draft               planned
   ↓ assign           ↓ confirm            ↓ contractor confirms
confirmed           confirmed           confirmed
                       ↓ first leg          ↓ cargo ready
                    in_progress         cargo_ready
                       ↓ all legs           ↓ vehicle departs
                    completed           departed
                                           ↓ arrives at next point
                                        arrived
                                           ↓ docs received
                                        completed
                                        delayed (any time, resumes)

Scenario Walkthrough: Kiev → Toronto

RouteTemplate "Ukraine → Canada (Container 40HC)"
  Seg 1: RAIL  Kyiv-Tovarna → Odessa-Port       SUB  UZ       doc=railway_bill
  Seg 2: ROAD  Odessa stn → Port Chornomorsk    OWN  own      doc=ttn
  Seg 3: SEA   Chornomorsk → Toronto            SUB  MSC Agnt doc=bill_of_lading

FreightContract #FC-001  client=ABC Corp  type=period  valid 2026-2028

TransportOrder #TO-042  contract=FC-001  cargo=Electronics 18t  1×40HC
  CargoItems: [{ item=Electronics, qty=500, weight=18000kg, hs=8471 }]

Shipment #SH-042
  Leg 1: RAIL  Kyiv→Odessa     UZ       wagon=984512  ЗН=94821   1200 USD
  Leg 2: ROAD  Odessa→Port     OWN      Volvo FH №АА1234ОО  WB-156   120 USD
  Leg 3: SEA   Odessa→Toronto  MSC Agnt MSC Alina  B/L=MSCU1234  8500 USD
  total_freight = 9820 USD

  Documents:
    railway_bill  №94821      leg=1
    ttn           №0042/26    leg=2
    bill_of_lading №MSCU1234  leg=3
    customs_decl  №UA/2026/.. shipment-level

Scenario Walkthrough: Grain Ukraine → Egypt

ConsolidationPlan #CP-008  route=Ukraine→Egypt  point=Odessa Port
  target=8000 tons wheat  vessel_booking=#VB-021  window=1-28 Feb

  CollectionOrders:
    Elevator Voznesensk  1200t  RAIL  UZ
    Elevator Mykolaiv     800t  RAIL  UZ
    Elevator Kryvyi Rih  1500t  RAIL  UZ
    Elevator Dnipro      2000t  RAIL  UZ
    Elevator Zaporizhzhia 1000t ROAD  carrier X
    Elevator Kherson      1500t ROAD  carrier Y

VesselBooking #VB-021  MV Grain Star  voyage=GS-0312
  Port of loading=Odessa  Port of discharge=Damietta
  ETD=5 Mar  ETA=12 Mar  8000t  charter  demurrage=3500 USD/day

Shipment #SH-043  plan=CP-008  vessel=VB-021
  Leg 1: SEA  Odessa → Damietta  MV Grain Star  120000 USD
  Documents: bill_of_lading, phyto, quality_cert

Frontend: Process Components

ShipmentTracker (components/Logistic/ShipmentTracker.tsx)

Visual stepper/timeline for a single Shipment: - Each ShipmentLeg = one step with icon (🚂 🚛 🚢 ✈️) - Color-coded by status (grey=planned, blue=in_progress, green=completed, red=delayed) - Shows: contractor, reference numbers, planned vs actual dates, cost per leg - Total cost summary at bottom

ConsolidationBoard (components/Logistic/ConsolidationBoard.tsx)

Table view for ConsolidationPlan: - Each row = one CollectionOrder (elevator) - Columns: origin, planned qty, actual qty, mode, contractor, ETA to port, status badge - Progress bar header: collected/target tons + % fill - Vessel ETD countdown timer


File Structure (implementation)

backend/logistic/models/
├── __init__.py              (update exports)
├── logistic_order.py        (keep, rename later)
├── route_sheet.py           (keep)
├── master_data.py           ← NEW
├── freight_contract.py      ← NEW
├── transport_order.py       ← NEW
├── shipment.py              ← NEW
├── consolidation.py         ← NEW
└── vessel_booking.py        ← NEW

frontend/erp/src/
├── config/logistic.ts       ← UPDATED (full multimodal structure)
└── components/Logistic/
    ├── ShipmentTracker.tsx  ← NEW Process component
    └── ConsolidationBoard.tsx ← NEW Process component

Key Design Decisions

Decision Rationale
RouteTemplate separate from Shipment Enables recurring shipments; one template → N executions
executor_type=own/sub on each ShipmentLeg Our vehicle on leg 2, subcontractors on legs 1 and 3
ConsolidationPlan as first-class entity Not just "group of orders" — has its own lifecycle, vessel link, collection window
FreightContractor wraps essentials.Client Reuses client accounting records; adds transport-specific fields
Document per leg, not per shipment Railway bill belongs to rail leg; B/L belongs to sea leg
VesselBooking independent of ConsolidationPlan One vessel booking may serve multiple consolidation plans (groupage)
Cost tracked per leg, aggregated to Shipment Enables per-mode cost analytics and contractor invoicing

Distribution Logistics (Last-Mile Delivery)

Concept

Inverse of consolidation. ConsolidationPlan is N → 1 (many origins → one port). DistributionPlan is 1 → N (one warehouse → many delivery points).

Scenario: company accepted 100 orders → 87 delivery points in 3 Ukrainian regions (Kyiv City, Kyiv Oblast, Cherkasy Oblast) → 8 own vehicles → 8 delivery routes.

Warehouse (Boryspil)
  ├── Region: Kyiv City         4 routes  36 stops  own fleet
  │    ├── R-01  Volvo FH  Petrov    Obolon/Podil         12 stops  ✅
  │    ├── R-02  DAF XF    Koval     Shevchenkivsky       10 stops  🔵 8/10
  │    ├── R-03  MAN TGX   Moroz     Dniprovskyi           8 stops  ✅
  │    └── R-04  Mercedes  Shevchenko Svyatoshynsky        6 stops  ⏳ dispatched
  ├── Region: Kyiv Oblast       2 routes  28 stops
  │    ├── R-05  Renault T Tkach     Brovary/Boryspil     15 stops  🔵 12/15
  │    └── R-06  Volvo FH  Bondar.   Vasylkiv/Fastiv      13 stops  🔵 6/13
  └── Region: Cherkasy Oblast   2 routes  23 stops
       ├── R-07  DAF XF    Lysenko   Cherkasy/Smila       12 stops  🔵 10/12
       └── R-08  MAN TGX   Prokop.   Uman/Khrystynivka   11 stops  🔵 4/11

Django Models (logistic/models/distribution.py)

DistributionPlan (extends TransactionModel)

Field Type Notes
source_warehouse FK(LocationPoint) origin warehouse
delivery_date DateField planned delivery day
orders_total IntegerField total client orders grouped
stops_total IntegerField total delivery points
vehicles_count IntegerField computed
status choice planning, dispatched, in_progress, completed
_subtables ['routes']

DistributionRoute (extends TenantAwareModel)

Field Type Notes
plan FK(DistributionPlan)
number CharField R-01, R-02...
vehicle FK(fleet.Vehicle) own fleet only
driver FK(fleet.Driver)
region CharField e.g. "Kyiv City"
area CharField e.g. "Obolon / Podil"
planned_departure DateTimeField
actual_departure DateTimeField null
planned_return DateTimeField
actual_return DateTimeField null
stop_count IntegerField total stops
delivered_count IntegerField auto-updated
total_weight_kg DecimalField
waybill FK(fleet.Waybill) generated on dispatch
status choice planned, dispatched, in_progress, completed
_subtables ['stops']

DistributionStop (extends TenantAwareModel)

Field Type Notes
route FK(DistributionRoute)
sequence IntegerField stop order
client FK(essentials.Client)
address CharField full delivery address
district CharField city district / area
orders_count IntegerField orders grouped at this stop
weight_kg DecimalField
planned_arrival DateTimeField
actual_arrival DateTimeField null
status choice pending, in_transit, delivered, failed
failure_reason TextField blank
driver_notes TextField blank

Difference vs ConsolidationPlan

Aspect ConsolidationPlan DistributionPlan
Direction N → 1 (collect) 1 → N (deliver)
Transport Mixed (rail/road) Own fleet (road)
Contractors External + own Own fleet only
Key document Bill of Lading Delivery receipt
Vessel link VesselBooking
Split logic By origin point By region + route optimization
Stop type Collection point Delivery point

Frontend: DistributionBoard Process Component

Located at: components/Logistic/DistributionBoard.tsx

3-level drill-down layout:

PlanHeader
  ├── Overall progress bar (delivered/total stops)
  └── Stats: orders, stops, vehicles, in-progress, completed, failed

RegionSection × N  (collapsible, open by default)
  ├── Region header: name, route count, stop count, mini progress bar
  └── Routes table (compact rows, clickable to expand)
       └── RouteRow (on click → expands StopsTable)
            └── StopsTable: seq | client | address | orders | weight | planned | actual | status

UI highlights: - Progress bars change color: red (<30%) → orange (30-60%) → blue (60-90%) → green (90%+) - Route R-02 expanded by default (shows mixed statuses: delivered/in_transit/pending) - Stops show actual arrival time in green when delivered, delay notes in orange - Region filter dropdown to focus on one oblast - Driver can see ETD of their own route in header

Status Flow

DistributionPlan        DistributionRoute         DistributionStop
────────────────        ──────────────────        ────────────────
planning                planned                   pending
   ↓ finalize              ↓ vehicle assigned
dispatched              dispatched                pending
   ↓ first departure       ↓ driver departs           ↓ en route to stop
in_progress             in_progress               in_transit
   ↓ all routes done       ↓ all stops done           ↓ arrives
completed               completed                 delivered
                                                  └── or: failed