Files
docs/头程到尾程分阶段流程/阶段四-海运目的港与尾程计划.md

393 lines
20 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 阶段四:海运、目的港与尾程入仓计划
> 对应全景流程:F24~F27。
> 阶段起点:柜次已完成送港/进港或甩柜处理,并产生承运船舶实际 `VESSEL_DEPARTED`/`DEPARTED_ORIGIN_PORT` 节点;真实 `DEPARTED_WAREHOUSE` 和 `PORT_RECEIVED` 属于阶段三送港线事实。
> 阶段终点:跨区尾程订单完整生成,整柜每票入仓计划及托盘/库位资源已生成或有明确资源失败待办,订单进入 `INBOUND_PLANNED`。
> 本阶段同时维护船舶/货柜轨迹和尾程业务准备,但两类状态不得互相冒充。
## 1. 阶段目标和边界
本阶段完成四条相互关联但独立的链路:
1. 从承运船舶实际开航开始查询并保存十个标准船节点;
2. 管理目的港清关、海外码头车队、提柜、预计到仓和还空;
3. 将国内最终实装快照可靠投递到海外区域并创建尾程订单;
4. 按整柜维护每票计划并转换为可执行的海外入仓计划和资源。
阶段四不执行海外仓实收、库存入账、库内服务、排车或出库。`PICKUP_READY` 只表示货柜已清关可提,并开放海外 PDA 待收柜候选;它不表示已经到仓或已经产生库存。
## 2. 角色、终端和系统边界
| 角色/系统 | 终端 | 主要职责 |
|---|---|---|
| 船期操作员 | Admin | 查看查询任务、标准节点、异常与查询历史,必要时人工补录 |
| 目的港操作员 | Admin | 维护清关/DO/PU、选择海外车队、发提柜邮件、确认提柜和还柜 |
| 海外仓计划员 | Admin | 维护整柜预计到仓、每票托数与作业属性、执行柜级转换 |
| 企业成员 | Client | 查看本企业尾程订单和允许展示的船期/计划进度 |
| Firstleg | 后端 | 拥有查询任务、标准节点、目的港码头拖车和装柜完成 Outbox |
| Integration Service | 后端 | 调用船司/轨迹供应商,解析并返回标准化结果,不保存业务主数据 |
| Event Bridge | 跨区通道 | 版本化、认证地投递业务事件 |
| Lastleg | 海外后端 | Inbox 幂等消费、尾程订单、计划、在途库存和海外业务状态 |
| Warehouse | 国内主数据 | 海外共享仓、FBA 目的仓和供应商主档权威 |
| Warehouse Overseas | 海外资源 | 海外库位、实物托盘和资源锁 |
所有浏览器海外接口经 `gateway-overseas/`。国内 Auth 仍是账号和 Token 签发方;跨区业务写入通过内部身份、Inbox/Outbox 和事件契约完成,不允许浏览器伪造跨区事件。
## 3. 阶段前置检查
- 柜次存在真实集装箱号、订舱、提单号、送港/进港事实和实际开船节点;
- 装柜完成事件包含最终实装订单、入仓单、目的类型、箱数及必要托盘/SKU/箱唛快照;
- 国内五类装柜证据和锁码文本已经冻结;
- 接柜海外仓是 Warehouse 中 `ACTIVE + OVERSEAS + RECEIVING` 的平台共享物理仓;
- FBA 目的仓是 Warehouse 有效公共主档,不能用接柜仓或国内仓代替;
- 船司/轨迹查询标识足以唯一指向目标订舱和柜次;
- 同柜订单的预计到仓事实必须能够收敛为一个柜级日期。
## 4. F24 海运十节点
### 4.1 查询任务创建
承运船舶实际开航事务幂等创建 `CarrierScheduleQuery`。任务必须绑定真实订舱、柜次以及可用的船司、船名航次、提单号或柜号,不创建脱离业务单据的孤立轮询任务。送港、进港、甩柜和客户报关确认在阶段三分别记录,不能用预计 ETD 提前创建“海运中”展示。
标准节点保留国内送港事实,但海运查询的起点是实际开船:
`DEPARTED_WAREHOUSE`、`AT_ORIGIN_PORT` 和 `LOADED_ON_VESSEL` 可以作为送港/装船历史补录;客户的“海运中”节点和定时船节点查询以真实 `DEPARTED_ORIGIN_PORT`/`VESSEL_DEPARTED` 为起点。
```text
10 DEPARTED_WAREHOUSE 已离开国内仓
20 AT_ORIGIN_PORT 已到起运港
30 LOADED_ON_VESSEL 已装船
40 DEPARTED_ORIGIN_PORT 已离开起运港
50 IN_TRANSIT 海运途中
60 ARRIVED_DESTINATION_PORT 已到目的港
70 DISCHARGED_FROM_VESSEL 已卸船
80 PICKUP_READY 已清关、可提柜
90 PICKED_UP 已提柜
100 EMPTY_RETURNED 已还空柜
```
### 4.2 自动查询操作与频率
入口:Admin → 船期/货柜详情 → 船节点查询。
平台查看:查询任务状态、当前最新实际节点、预计节点、最近成功时间、下次允许时间、供应商、失败原因、异常事实和每次尝试历史。
正常查询槽位按北京时间每天 `09:00`、`12:30`、`16:00`。调度器每分钟只扫描已到期任务,不代表每分钟调用船司。
- 成功、无变化、需人工或永久失败:进入下一固定槽位;
- 技术性可重试失败:当前槽内按 15/30 分钟退避,含首次最多三次;
- 重试不得跨越下一正常槽位;
- 服务恢复后错过的槽位直接顺延,不集中补打供应商;
- 实际 `EMPTY_RETURNED` 后任务停止。
### 4.3 节点时间和推进规则
| 时间 | 含义 | 推进最新实际节点 |
|---|---|---|
| `estimatedAt` | 预计发生时间 | 否 |
| `actualAt` | 已确认实际时间 | 是 |
| `observedAt` | 本次观测到事实的时间 | 否 |
| `createdAt` | 平台写入时间 | 否 |
只有带 `actualAt` 的标准节点可以推进。节点按序号单调前进;迟到的较早节点可作为历史保存,但不能回退当前节点。供应商缺少中间节点时保持缺失,不能自动补齐。
同一供应商事件按稳定 `providerEventId` 幂等;无原生 ID 时用稳定字段组合生成哈希。每条事实保留原始节点编码、来源、地点、观测时间和原始响应哈希,但业务表不保存含密钥的完整网页。
### 4.4 异常事实
以下异常与正常节点并行保存:
- `CUSTOMS_HOLD`:海关扣留;
- `TERMINAL_HOLD`:码头/堆场扣留;
- `DOCUMENT_HOLD`:单证问题;
- `UNKNOWN_HOLD`:其他无法分类扣留。
异常不能覆盖最近正常节点。解除时新增解除事实,不删除原异常。
### 4.5 人工补录
供应商缺节点、网页需要人工验证或业务已有可信凭证时,有权限人员可在查询详情补录:
1. 选择标准节点;
2. 填写实际时间,预计事实不能冒充实际时间;
3. 填写地点、来源说明和必要凭证;
4. 核对节点不会回退当前实际进度;
5. 携带版本和幂等键提交;
6. 查看历史中来源标识为人工。
人工补录 `PICKUP_READY` 会通过跨区事实开放海外 PDA 柜号候选;人工补录 `EMPTY_RETURNED` 会停止任务,因此必须使用真实还柜事实。
## 5. F25 目的港清关、车队、到仓和还空
### 5.1 清关和文件
入口:Admin → 清关提柜。
1. 按柜号、提单号、目的港和节点筛选;
2. 核对目的港清关资料、DO 和 PU 当前版本;
3. 上传或替换合法文件引用;
4. 将线下清关结果反映为可信业务事实;
5. 清关完成、码头明确可提后,确保实际 `PICKUP_READY` 已进入标准节点链。
清关资料状态和标准船节点有关联但不互相覆盖。只有明确可提事实才能推进 `PICKUP_READY`。
### 5.2 选择海外码头车队
1. 选择 Warehouse 中 `ACTIVE/TRUCKING/OVERSEAS` 供应商;
2. 服务端读取供应商名称和邮箱快照,不能用自由文本绕过主档;
3. 首次选择车队只保存选择,不自动发信;
4. 操作员确认后发送提柜请求邮件;
5. 邮件进入顺序发送队列,`SENT` 只表示 SMTP 接受,不代表车队已读或已执行;
6. 失败邮件保留原因,人工确认后重试。
更换车队时,事务内同时冻结旧车队取消邮件和新车队提柜邮件,并按顺序投递。同一车队重复选择不重复发信。
### 5.3 实际提柜
车队从码头取得重箱后,目的港操作员确认实际提柜时间。服务端推进实际 `PICKED_UP`,但不创建海外库存、不替代 PDA 到仓收货。
### 5.4 预计到仓
清关提柜列表中的“预计到仓”由平台维护:
1. 输入整柜预计抵达接柜仓的日期/时间;
2. 按 `sourceContainerBookingId` 锁定整柜;
3. 原子同步柜内全部尾程订单的 `estimated_warehouse_at`;
4. 记录版本、操作人和幂等历史;
5. 任一入仓计划已生成后锁定,不再允许修改。
预计到仓不能使用订单创建时间、预计到港时间或当前时间替代。同柜订单缺失或日期不一致时,阶段四的计划转换必须阻断。
### 5.5 实际到仓与还空
实际到仓不由 Admin 手填,也不取预约提柜时间;只取海外 PDA 首次开始收货会话的 `receiving_at`。因此本阶段只能展示“待实际到仓”,真实时间在阶段五产生。
海外 PDA 完成卸柜后,跨区 `OVERSEAS_CONTAINER_UNLOADED` 事件回到 Firstleg。预计报空/还空截止按卸柜完成时间加 72 小时计算并产生预警。
真实还柜可由供应商节点或有权限人员确认:
- 时间不得早于卸柜完成时间;
- 若已有实际提柜时间,也不得早于提柜;
- 无需为了还柜补造车队、预约或提柜事实;
- 首次命令可以版本 0 创建记录,车队字段允许为空;
- 成功后推进实际 `EMPTY_RETURNED`、解除预警并停止查询任务。
目的港码头拖车、国内订舱级拖车和海外配送排车是三张不同业务单,不得复用状态或费用。
## 6. F26 跨区尾程建单
### 6.1 国内生产事件
Firstleg 在装柜完成后按最终实装生成 `FIRST_LEG_LOADING_PLAN_COMPLETED` V3:
- 信封包含事件 ID、版本、来源区域、聚合版本和 Payload 哈希;
- Payload 包含来源柜次/计划/真实柜号和每票最终实装;
- FBA 可以按实装箱数冻结,不伪造逐箱作业账;
- `OVERSEAS` 必须逐箱冻结箱码事实;
- 一件代发必须携带一个或多个不可变来源托盘,以及每托 SKU、箱数、数量和箱唛;
- 物理柜五类国内装柜照片引用一并冻结。
Outbox 可重复投递,不能因网络失败直接丢事件或在事务外临时拼装 Payload。
### 6.2 Event Bridge 投递
Event Bridge 校验允许的事件类型、版本、来源/目标区域和内部服务身份后投递 Lastleg。IAM 安全事件使用独立入口,不能交给装柜完成消费者。
### 6.3 Lastleg Inbox 消费
Lastleg 消费步骤:
1. 常量时间校验内部令牌;
2. 校验事件 ID、版本、来源区域、客户和必要字段;
3. 校验 Payload 哈希;
4. 以事件 ID、哈希和来源入仓单唯一约束防重;
5. 在一个事务内处理整柜全部订单;
6. 任一票失败时整柜回滚,不留下半套订单;
7. 重放相同事件返回既有结果。
### 6.4 尾程订单和在途库存
成功消费后:
- 同一实际集装箱共享业务尾程单号;
- 每个 `sourceInboundId` 独立生成一条 `ll_order`;
- 保存来源订单、入仓码、客户、接柜仓、目的仓和货物冻结快照;
- 主状态为 `PENDING_ASSIGNMENT`;
- 生成 `FIRST_LEG` 在途库存;
- 计划生成前以真实来源入仓码作为库存展示快照;
- 事件只建订单,不自动生成入仓计划。
一件代发额外生成来源托盘、SKU、箱唛档案和在途库存事实;海外实物托盘资源仍由 Warehouse Overseas 分配,不覆盖国内来源托盘快照。
## 7. F27 尾程计划维护和整柜转换
### 7.1 柜级工作台
入口:Admin → 海外仓 → 尾程订单 → 柜级工作台。
列表使用 `/container-summaries` 按稳定 `sourceContainerBookingId` 服务端聚合,不允许用当前订单分页在浏览器拼柜。列表至少展示:
- 柜号、来源柜次和预计到仓;
- 订单数、已建计划数、待建计划数;
- `allPlansReady`;
- `resourcePendingPlanCount`;
- 接柜仓、目的仓分组和异常摘要。
已完成转换的柜仍保留供查询和允许范围内维护。详情接口返回柜内全部订单和计划,不受列表分页截断。
### 7.2 单票计划维护
对柜内每票填写:
- 计划托数;
- 托盘类型;
- 是否已分配;
- 是否允许合托;
- 是否加急;
- 现场备注。
入仓码、实际接柜仓和预计到仓只读。保存时校验订单版本、客户范围、Warehouse 当前仓库主档和幂等键。
“可合托”只给阶段五现场提供选择,不是实收或完成门禁:合托时扫描同目的仓已使用托盘,独立收货扫描新托盘。
### 7.3 普通货与一件代发托盘规则
普通散货:
- 保留头程已经冻结并跨区传入的托盘码;
- 海外计划托数不得少于已有托盘码数量;
- 海外装托密度不同导致托数增加时,在同柜、同 FBA 仓连续补码;
- 预览分别展示头程已有数、海外补充数和最终托数。
一件代发:
- 必须逐托沿用国内封托的 `DP` 托盘码和每托箱数;
- 不允许重算托数、重分配箱子或改写 SKU/箱唛;
- 无法绑定真实资源时禁止保存半成品计划。
### 7.4 自动触发与柜级预览
单票柜保存完整计划后立即尝试生成资源;多票柜在最后一票计划齐全时自动触发整柜资源生成。
转换前读取服务端预览,核对:
- FBA、一件代发、私人地址等分组;
- 每票来源托数、补充托数和最终托数;
- 柜内计划是否全部就绪;
- 接柜仓、预计到仓是否一致;
- 阻断项;
- `snapshotVersion` 和 `snapshotHash`。
`pendingPlanCount` 表示尚未建立完整计划;`resourcePendingPlanCount` 只表示自动物化资源失败,需要按原柜级入口重试,不能把它当作正常人工步骤。
### 7.5 确认转换
系统执行柜级 Saga:
1. 以 `sourceContainerBookingId` 锁柜;
2. 用版本/哈希重新计算预览;
3. 向 Warehouse Overseas 批量预留真实托盘、库位和资源锁;
4. 为柜内每票独立生成入仓计划;
5. 本地事务确认计划并绑定资源;
6. 尾程订单进入 `INBOUND_PLANNED`;
7. `FIRST_LEG` 在途库存展示编号从来源入仓码收口为各自 `planNo`。
整批转换原子且幂等:相同命令重放不生成第二批计划或资源;本地失败只补偿尚未确认的远端资源;远端确认失败的重放只重试确认。
## 8. 状态、编号与数据归属
| 维度 | 关键状态/编号 | 权威系统 |
|---|---|---|
| 船节点任务 | ACTIVE/人工/失败/`STOPPED` | Firstleg |
| 最新实际节点 | 十节点之一 | Firstleg |
| 目的港码头拖车 | 车队、提柜、卸柜、还柜 | Firstleg |
| 尾程主单 | `PENDING_ASSIGNMENT → INBOUND_PLANNED` | Lastleg |
| 入仓计划 | `planNo`、计划属性、资源绑定 | Lastleg |
| 在途库存 | `FIRST_LEG` | Lastleg |
| 海外实物资源 | 托盘、库位、锁 | Warehouse Overseas |
| 仓库/目的仓/供应商 | 业务主档 | Warehouse |
`containerNo` 是真实集装箱号;`sourceContainerBookingId` 是跨区稳定柜聚合键;`inboundCode` 永远保存真实来源入仓码;`planNo` 只在尾程入仓计划生成后产生。任何页面不得以内建数据库 ID 回退展示业务编号。
## 9. 异常与恢复
| 场景 | 系统行为 | 操作处理 |
|---|---|---|
| 船司无变化 | 保存查询尝试,进入下一槽位 | 无需伪造节点 |
| 船司技术失败 | 15/30 分钟受控重试 | 查看失败原因,必要时人工 |
| 只有预计到港 | 保存预计事实,不推进实际节点 | 等待真实节点 |
| 较早节点迟到 | 保存历史,不回退当前节点 | 无需修改最新进度 |
| 清关/单证扣留 | 单独保存异常 | 处理后新增解除事实 |
| 车队邮箱无效 | 阻断发信/换队 | 修正 Warehouse 供应商资料 |
| 事件重复投递 | 返回既有消费结果 | 不人工删 Inbox |
| 事件哈希不同 | 冲突失败 | 核对生产端版本和 Payload |
| 柜内任一票事件失败 | 整柜消费回滚 | 修复数据后重放原事件 |
| 预计到仓缺失/不一致 | 阻断计划生成 | 在计划前按柜统一维护 |
| 计划版本过期 | 转换冲突 | 刷新预览,重新确认 |
| 资源预留失败 | `resourcePendingPlanCount` 暴露 | 用原柜级入口定向重试 |
| 重试结果不确定 | 查询计划和资源事实 | 复用原幂等键,不另建资源 |
## 10. 阶段完成判定
现有测试环境综合证据 `baiyue-firstleg/reports/admin-full-postdeploy-66c2651b.json` 中,`phase4-carrier-nodes`、`phase4-overseas-pda`、`phase4-overseas-pda-operations`、`phase4-overseas-inbound-operations`、`phase4-overseas-dispatch`、`phase4-tail-order-controls` 和 `phase5-overseas-fulfillment` 均为通过;每个模块的失败数为 0。F23.3 的最新船期客户端投影另见 `baiyue-firstleg/reports/transport-projection-after-schedule-update-20260927.json`。
- [ ] 国内离仓产生的船节点任务绑定真实订舱和柜次
- [ ] 节点预计/实际/观测时间没有混用
- [ ] 异常事实与正常节点分开保存
- [ ] 实际 `PICKUP_READY` 已到达或已有明确待处理状态
- [ ] 装柜完成 V3 事件已被 Lastleg 幂等消费
- [ ] 柜内每个来源入仓单只生成一张尾程订单
- [ ] 尾程订单冻结客户、入仓码、接柜仓、目的仓和最终实装事实
- [ ] `FIRST_LEG` 在途库存存在且归属正确
- [ ] 整柜预计到仓完整且一致
- [ ] 每票入仓计划属性完整
- [ ] 柜级转换后每票独立 `planNo` 存在
- [ ] 托盘、库位和资源锁已绑定,或存在可定位的资源失败待办
- [ ] 尾程订单状态为 `INBOUND_PLANNED`
- [ ] 重放事件/转换不会重复建单、建计划或占资源
## 11. 易用性与数据流评审问题
1. 船节点、目的港清关和尾程计划是否需要整合为一条柜级时间线,同时保留三个权威状态?
2. 预计节点与实际节点的视觉差异是否足够明显?
3. 船司失败、需人工、扣留异常分别由谁负责,是否需要责任人和 SLA?
4. 发车队邮件后,页面是否明确区分“已入队、SMTP 已接受、车队已执行”?
5. 预计到仓锁定前是否应显示会影响哪些订单和计划?
6. 跨区事件失败是否需要业务友好的“缺哪一票/哪一字段”诊断,而不是只显示事件错误?
7. 柜级工作台的 `pendingPlanCount` 与 `resourcePendingPlanCount` 是否容易混淆?
8. 普通散货的“头程已有/海外补充/最终托数”是否需要图形化对比?
9. 一件代发不可重排托盘的约束是否在编辑前就锁定控件?
10. 转换前预览是否需要展示资源占用和预计库位,减少确认后人工调整?
11. `PICKUP_READY` 开放 PDA 候选后,若预计到仓和资源计划尚未完成,现场应看到什么阻断提示?
12. 还柜预警当前同时存在“两天展示”和“72 小时业务规则”的历史描述,正式页面应统一采用服务端 72 小时口径。
## 12. 阶段五交接包
交给阶段五“海外接柜、实收、库存和库内服务”的输入包括:
- 来源柜次、真实柜号、订舱和最新实际船节点;
- `PICKUP_READY` 及以后产生的 PDA 候选门禁;
- 实际接柜海外仓和柜级预计到仓;
- 柜内全部尾程订单、来源入仓码、目的类型和冻结货物;
- 每票独立入仓计划及 `assigned/mergeAllowed/urgent/mergeHint`;
- 已预留并确认的托盘、库位和资源锁;
- 普通货头程托盘码与海外补码;
- 一件代发不可变 `DP` 托盘、箱唛和 SKU;
- 国内装柜五类照片引用及跨区事件审计历史。
## 13. 事实来源
- `docs/海外仓业务/README.md`
- `docs/海外仓业务/船节点标准化契约.md`
- `docs/海外仓业务/虫虫出海全流程人工验收手册.md`
- `docs/跨区域认证与服务通信契约.md`
- `baiyue-firstleg/firstleg/AGENTS.md`
- `baiyue-firstleg/firstleg/src/main/java/com/baiyue/firstleg/portschedule/`
- `baiyue-firstleg/firstleg/src/main/java/com/baiyue/firstleg/terminaldrayage/`
- `baiyue-firstleg/common-event-contract/AGENTS.md`
- `baiyue-firstleg/lastleg-service/AGENTS.md`
- `baiyue-firstleg/lastleg-service/src/main/java/com/baiyue/lastleg/service/LoadingCompletedConsumer.java`
- `baiyue-firstleg/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/`
- `apps/admin-web/src/modules/port-schedules/`
- `apps/admin-web/src/modules/overseas-warehouse/`