本文探讨油品运输监控系统中调度计划模块的设计思路,覆盖核心实体建模、多对多关系处理、状态机设计以及自动推荐方案框架。
业务背景
油品运输的核心流程是:油站客户下订单 → 系统生成调度计划 → 车辆执行提油、运输、卸油。
只有调度计划内授权的车辆,才可以合法地执行整个配送任务。这一机制既是业务流程的起点,也是安全管控的关键节点。
在设计之前,先明确几条关键业务约束:
- 一个调度计划 = 一辆车,车辆跑完所有配送点后返回
- 一个订单 = 一种油品,订单可按车次拆分到多个调度
- 订单与调度是多对多关系,通过明细层桥接
- 油罐车有多个仓(舱),每仓容量固定,不同仓容量可不同
- 同仓可混装来自不同订单的同种油品
- 调度变更时作废原计划,新建替代计划,保留完整历史
核心实体设计
整体分为三层:
1
| 订单层(Order) → 调度层(DispatchPlan) → 执行层(Task)
|
调度计划主表 dispatch_plan
1 2 3 4 5 6 7 8 9 10 11 12
| dispatch_plan ├── id ├── plan_no ├── depot_id ├── vehicle_id ├── plan_date ├── status ├── plan_type ├── parent_plan_id ├── remark ├── created_by └── created_at
|
变更处理原则:调度计划变更时,作废原计划并新建新计划,parent_plan_id 指向被替代的旧计划,形成完整变更链,便于审计追溯。
仓位装载表 dispatch_compartment
描述”这辆车的哪个仓,装了什么油,装多少“,与订单无关:
1 2 3 4 5 6 7
| dispatch_compartment ├── id ├── plan_id ├── compartment_id ├── oil_type ├── planned_load_qty └── actual_load_qty
|
订单配送明细表 dispatch_order_item
描述”哪个订单、分配多少量、从哪个仓卸、送到哪个站、第几站卸“:
1 2 3 4 5 6 7 8 9 10
| dispatch_order_item ├── id ├── plan_id ├── compartment_id ├── order_id ├── station_id ├── oil_type ├── planned_qty ├── actual_unload_qty └── delivery_seq
|
为什么要拆成两张明细表?
这是设计的关键决策。允许”同仓混装不同订单的同种油品”后,仓位和订单之间形成了一对多关系:
1 2 3 4 5 6
| dispatch_compartment(仓1,92号,5000L) ├── dispatch_order_item(订单A,A油站,3000L,第1站) └── dispatch_order_item(订单B,B油站,2000L,第2站)
dispatch_compartment(仓2,95号,5000L) └── dispatch_order_item(订单C,C油站,5000L,第3站)
|
如果只用一张明细表,混装场景会导致仓位信息冗余存储,计划量和实际量的统计也会出现歧义。拆成两张表后,仓位层管理装载,明细层管理配送,职责清晰。
车辆与车仓表
1 2 3 4 5 6 7 8 9 10 11
| vehicle ├── id ├── plate_no ├── total_capacity └── status
vehicle_compartment ├── id ├── vehicle_id ├── compartment_no └── capacity
|
订单表
1 2 3 4 5 6 7 8
| order ├── id ├── order_no ├── station_id ├── oil_type ├── ordered_qty ├── dispatched_qty └── status
|
dispatched_qty 随调度创建和取消实时更新,用于快速判断订单是否还有待调度量,避免每次都联表聚合计算。
订单与调度的多对多拆分示例
用一个具体例子说明拆分和合并场景:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 订单A:A油站,92号汽油,8000L 订单B:B油站,92号汽油,3000L 订单C:C油站,95号汽油,5000L
车辆X:3仓 → 仓1=5000L,仓2=5000L,仓3=3000L 车辆Y:2仓 → 仓1=5000L,仓2=3000L
━━━ 调度计划1(车辆X)━━━ 仓1(92号,5000L) ├── 订单A → A油站,3000L,第1站 └── 订单B → B油站,2000L,第2站 ← 同仓混装不同订单 仓2(95号,5000L) └── 订单C → C油站,5000L,第3站 仓3(92号,3000L) └── 订单B → B油站,1000L,第2站 ← 订单B分布在两个仓
━━━ 调度计划2(车辆Y)━━━ 仓1(92号,5000L) └── 订单A → A油站,5000L,第1站 ← 订单A被拆到两个调度
|
订单A的 8000L 被拆到调度1(3000L)和调度2(5000L),两条 dispatch_order_item 都关联同一个 order_id,order.dispatched_qty 累加为 8000L 即标记为全量调度。
状态机设计
调度计划状态流转
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| DRAFT(草稿) │ ├─[调度员审核确认]──→ APPROVED(已审批) │ │ │ ├─[车辆出发/开始提油]──→ IN_PROGRESS(执行中) │ │ │ │ │ ┌─────────┴──────────┐ │ │ [正常完成] [异常终止] │ │ ↓ ↓ │ [变更/取消] COMPLETED ABNORMAL │ ↓ └──────────────────→ CANCELLED(已取消) │ └─[触发]──→ 新建替代计划(parent_plan_id 指向本计划)
|
几个关键设计决策:
- 审批后才锁定车辆和仓位分配,草稿阶段可以自由调整
- 执行中的计划不允许直接修改,需走取消流程后重新调度
- 自动生成的计划只创建草稿,调度员始终保有最终决策权
订单状态联动
订单状态随调度计划的创建和取消实时联动:
1 2 3 4
| PENDING(待调度) → PARTIAL(部分调度) dispatched_qty > 0 且 < ordered_qty → DISPATCHED(全量调度) dispatched_qty = ordered_qty → COMPLETED(已完成) 所有关联调度均为 COMPLETED 状态
|
回退逻辑:关联调度被 CANCELLED 时,dispatched_qty 需回退,订单状态可能从 DISPATCHED 退回 PARTIAL 或 PENDING。
自动推荐方案框架
调度员触发”系统推荐”时,系统执行以下三步:
第一步:需求聚合
1 2 3
| 收集所有 status = PENDING 或 PARTIAL 的订单 按油品类型分组 计算每种油品的待调度总量和油站分布
|
第二步:车辆匹配
1 2 3 4 5 6
| 筛选 status = 可用 的车辆 按仓位容量和油品组合尝试装载方案 优先原则: ① 尽量装满车(减少空仓浪费) ② 同路线订单合并(减少趟次) ③ 大仓对大订单(减少跨仓拆分)
|
第三步:生成草稿
1 2 3
| 生成若干个 DRAFT 状态的调度计划 调度员在草稿基础上手动调整仓位分配和配送顺序 确认后提交审批,进入 APPROVED 状态
|
自动推荐只是”辅助决策”,不替代调度员的最终判断。这一设计平衡了系统效率与人工灵活性。
整体实体关系总览
1 2 3 4 5 6 7 8 9 10
| order ──────────────────────────────────────────────┐ (station_id, oil_type, ordered_qty) │ │ 多对多(通过明细桥接) dispatch_plan │ ├── vehicle ──→ vehicle_compartment │ ├── depot │ └── dispatch_compartment │ (仓位 + 油品 + 计划量) │ └──→ dispatch_order_item ──────────────┘ (订单 + 站点 + 分配量 + 配送顺序)
|
小结
本方案的几个核心设计思路:
- 两张明细表分离关注点:
dispatch_compartment 管装载,dispatch_order_item 管配送,清晰应对混装场景
parent_plan_id 实现变更链:作废重建而非原地修改,保留完整历史
dispatched_qty 冗余字段:空间换时间,避免高频联表聚合
- 自动推荐只生成草稿:人机协同,系统提效但不替代人工决策
delivery_seq 显式记录配送顺序:支持调度员手动调整,也为后续路线优化预留扩展点
后续可进一步扩展的方向包括:路线优化算法、实际量与计划量的偏差处理、跨库调度支持等。