docs(changelog): #8455 并存订单接送机看板门禁与改期残留按需求自身窗口判定(PR #8470)
changelog-filename-gate / validate (push) Failing after 2s

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
这个提交包含在:
API Changelog Bot
2026-09-28 13:11:37 +08:00
共同撰写人 Claude Opus 5.5
父节点 617c16d0db
当前提交 a43cb158b8
@@ -0,0 +1,356 @@
---
schema: "hl-changelog/v2"
ticket: "8455"
title: "并存订单接送机看板门禁与改期残留按需求自身窗口判定"
consumer: "admin"
author: "wx(GIT)"
change_type: "修改接口"
backend_status: "deployed"
gateway_status: "verified"
frontend_status: "pending"
frontend_owner: ""
frontend_ref: ""
target_release: ""
verified_at: ""
status_note: ""
updated_at: "2026-09-28"
base: "dev-v3"
---
# fleet: 并存订单接送机看板门禁与改期残留按需求自身窗口判定
**服务**: hl-fleet-service
**PR**: #8470(已合入 `dev-v3`,squash `82444e3f0`)
**Issue**: #8455
**日期**: 2026-09-28
**影响范围**: 管理后台车务看板订单详情 `GET /admin/fleet/board/orders/{orderId}`
---
## ⚠️ 关键变化
**只影响「并存订单」**:同一订单同时有一条有效的行程用车需求(TRAVEL)和一条有效的接送机需求(TRANSFER),且订单详情可取到(`relatedDetailReady=true`)。字段名、类型、响应结构都不变,变的是下面几个字段的取值口径。
- `dailyVehiclePlan[].rescheduleResidue` 与 `vehicleSlots[].rescheduleResidue`:行程用车与接送机两条需求并存的订单中,属于接送机需求的派车行改为按接送机需求自身的服务日判断是否越窗,不再按行程用车的日期窗判断,因此合法接送车不再被误标「改期残留/待清理」;接送机需求没有任何服务日期时,这些行不判残留。其余派车行和非并存订单的判定不变。
- `pickupDropoffGate`(接送机门禁)与逐日 `dailyVehiclePlan[].pickupRequired / dropoffRequired`:改为按 TRANSFER 需求自己的服务日窗口、以 TRANSFER 需求名下的派车行为证据计算,与写侧发布门禁同口径。改前按行程用车窗口算:接机日早于行程首日时被窗口滤掉、兜底成行程首日,看板报一个写侧不存在的「缺接机」。
- `requirementIdentities[].dispatchPlanGeneration` 与 `driverConfirmationSummary.dispatchPlanGeneration`:已完结(COMPLETED)的派车行不再参与代际判定。行程中途重排后,旧代已完结行不再把代际挤成 null。
- `progressSteps`:当前需求存在过期定稿方案、`activeAssignments` 已回拨成待派车时,进度条也按待派车计算,不再显示「排车已完成」。
---
## 一、背景
核心订单和团期订单都会同时有行程用车和接送机两条需求,接送机日可以早于行程首日(客人提前一天到)。看板详情此前用行程用车的日期窗判接送机,出现两类误报:
1. 已派好的接机车被报「缺接机」,进度条停在接送机一步;
2. 行程窗外的接机派车行被标「改期残留/待清理,可取消释放占用」,车务照着取消就会误伤合法派车。
写侧(最终方案发布门禁)本来就按接送机需求自身窗口判,两侧口径不一致。本单把读侧改成与写侧同口径。
---
## 二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|------|------|------|----------|------|
| 1 | 车务看板订单详情 | GET | `/admin/fleet/board/orders/{orderId}` | 修改 | 并存订单的接送机门禁、逐日接送标记、改期残留、派车方案代际、进度条取值口径变更 |
---
## 三、接口详情
### 1. 车务看板订单详情 `GET /admin/fleet/board/orders/{orderId}`
**VO**: `BoardOrderDetailVO`
#### 使用场景
车务打开看板订单详情,查看接送机门禁、逐日派车计划、槽位改期残留徽标、派车方案代际(拼需求级确认的 `expectedPlanGeneration`)与进度条。
#### 入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| orderId | Path | Long | 是 | 订单主键 | 看板列表行里的订单 ID |
#### 出参字段表
只列取值口径有变化的字段;其余字段与改前一致。
| 字段 | 类型 | 说明 |
|------|------|------|
| pickupDropoffGate.arrivalRequiredDates | Array&lt;String(yyyy-MM-dd)&gt; | 需要接机的日期。并存订单按 TRANSFER 需求服务日窗口计算 |
| pickupDropoffGate.departureRequiredDates | Array&lt;String(yyyy-MM-dd)&gt; | 需要送机的日期。并存订单按 TRANSFER 需求服务日窗口计算 |
| pickupDropoffGate.missingPickupDates | Array&lt;String(yyyy-MM-dd)&gt; | 缺接机的日期。并存订单只拿 TRANSFER 需求名下的派车行(含已完结行,用于完结日豁免)做证据 |
| pickupDropoffGate.missingDropoffDates | Array&lt;String(yyyy-MM-dd)&gt; | 缺送机的日期。证据行口径同上 |
| pickupDropoffGate.declared | Boolean | 是否存在任一方向的接送机要求(两个要求日期集合任一非空) |
| pickupDropoffGate.satisfied | Boolean | 两个缺失集合都为空即为 true |
| dailyVehiclePlan[].pickupRequired | Boolean | 当天是否要求接机。并存订单与顶层门禁用同一个 TRANSFER 窗口 |
| dailyVehiclePlan[].dropoffRequired | Boolean | 当天是否要求送机。并存订单与顶层门禁用同一个 TRANSFER 窗口 |
| dailyVehiclePlan[].rescheduleResidue | Boolean | 是否改期残留旧日期行。并存订单中归 TRANSFER 需求的行按 TRANSFER 需求窗口判,其余行按订单当前有效用车需求窗口判;窗口为空时不判残留 |
| vehicleSlots[].rescheduleResidue | Boolean | 是否改期残留旧日期槽位,口径同上;已被新需求取代的保留槽位仍按当前有效用车需求窗口判 |
| requirementIdentities[].dispatchPlanGeneration | String(Long) | 该需求在途定稿行的唯一派车方案代际;已完结行不参与;代际不唯一、未定稿或全部已完结时为 null |
| driverConfirmationSummary.dispatchPlanGeneration | String(Long) | 整单在途定稿行的唯一派车方案代际;已完结行不参与;历史无代际、代际不唯一或全部已完结时为 null |
| progressSteps[].status | String | 当前需求存在过期定稿方案、且锚点派车行可进入配车流程时,进度按「待派车」计算,与 `activeAssignments[].assignmentStatus` 回拨后的值一致 |
#### 请求示例
```http
GET /admin/fleet/board/orders/2104416028559835138
Authorization: Bearer <REDACTED-JWT>
```
#### 响应示例
2026-09-28 测试服实测节选(核心订单夹具,行程用车 2027-11-20 ~ 2027-11-26,接送机:11-19 接机、11-26 送机;只保留与本单有关的字段,夹具验收后已取消):
```json
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"requirementIdentities": [
{
"kind": "TRAVEL",
"requirementId": "2104416559248375810",
"requirementVersion": 1,
"requirementSha256": "46cb40c594edb2704ffc27b41acc3273041e42db3a599aadbef4f9d07e777501",
"dispatchPlanGeneration": null
},
{
"kind": "TRANSFER",
"requirementId": "2104416585181724674",
"requirementVersion": 1,
"requirementSha256": "62ab87d408692a438880d1e6e3497563a15e5e5adc081ad6ed9b24d3692d0097",
"dispatchPlanGeneration": "362806657824198656"
}
],
"pickupDropoffGate": {
"arrivalRequiredDates": [
"2027-11-19"
],
"departureRequiredDates": [
"2027-11-26"
],
"missingPickupDates": [],
"missingDropoffDates": [],
"declared": true,
"satisfied": true
},
"dailyVehiclePlan": [
{
"serviceDate": "2027-11-19",
"assignmentId": "2104416840996564994",
"assignmentStatus": "assigned",
"pickupRequired": true,
"dropoffRequired": false,
"rescheduleResidue": false
},
{
"serviceDate": "2027-11-26",
"assignmentId": "2104416841218863105",
"assignmentStatus": "assigned",
"pickupRequired": false,
"dropoffRequired": true,
"rescheduleResidue": false
}
],
"vehicleSlots": [
{
"assignmentId": "2104416840996564994",
"requirementId": "2104416585181724674",
"superseded": false,
"rescheduleResidue": false
},
{
"assignmentId": "2104416841218863105",
"requirementId": "2104416585181724674",
"superseded": false,
"rescheduleResidue": false
}
],
"driverConfirmationSummary": {
"dispatchPlanGeneration": "362806657824198656"
},
"progressSteps": [
{
"step": 1,
"code": "ORDER_DETAIL",
"status": "DONE",
"statusLabel": "已完成"
},
{
"step": 2,
"code": "DISPATCH",
"status": "DONE",
"statusLabel": "已完成"
},
{
"step": 3,
"code": "PICKUP_DROPOFF",
"status": "DONE",
"statusLabel": "已完成"
},
{
"step": 4,
"code": "CONFIRM_EXECUTE",
"status": "DONE",
"statusLabel": "已完成"
}
]
}
}
```
#### 空数据 / 降级响应
- 非并存订单(只有 TRAVEL,或只有 TRANSFER):取值口径与改前完全相同。
- 并存订单的 TRANSFER 需求没有服务日期、也没有自身起止日:接送机窗口为空,不回落到行程用车日期或订单起止日。此时 `arrivalRequiredDates`、`departureRequiredDates` 为空数组,`declared=false`,`satisfied=true`,归 TRANSFER 需求的派车行 `rescheduleResidue=false`。写侧发布门禁对这种 TRANSFER 同样给空门禁。
- 订单详情取不到(`relatedDetailReady=false`):不按并存订单处理,门禁与逐日接送标记按改前的降级口径返回,本单未改动。
- 派车行全部已完结:`dispatchPlanGeneration` 为 null(没有可确认的在途派车组)。
#### 错误响应
```json
{
"code": 605311,
"message": "当前需求存在多个不透明派车方案代际,请联系车务核对派车方案后重试",
"data": null,
"success": false
}
```
| 错误码 | 触发条件 |
|---|---|
| 401 | 未登录 |
| 605311 | 同一条需求下派车方案代际不唯一,看板不猜当前代际,重试无效,须联系车务核对派车方案。本单未改动该错误码 |
#### 业务边界
- 并存订单的判定条件:订单详情可取到,且同时有有效的 TRAVEL 需求与有效的 TRANSFER 需求。
- 派车行归属按行上的 `requirementId` 判断:等于 TRANSFER 需求 ID 的行用 TRANSFER 窗口,其余行(含 `requirementId` 对不上任何需求的行)用订单当前有效用车需求窗口。
- 门禁证据行取本订单全部原始派车行中归 TRANSFER 需求的行,包括已完结行;已完结的日期按原规则豁免,不会被误报缺失。
- `driverConfirmationSummary.dispatchPlanGeneration` 按整单算:两条需求都有在途定稿行且代际不同时为 null;要拼接送机需求的 `expectedPlanGeneration`,请取 `requirementIdentities[]` 里 `kind=TRANSFER` 那一项的 `dispatchPlanGeneration`。
- 进度条回拨只改响应里的计算副本,不改派车行落库状态。
---
## 四、契约约束与正确调用方式
| 场景 | 门禁与逐日接送标记用的窗口 | 门禁证据行 | 改期残留用的窗口 |
|------|-----|-----|-----|
| 并存订单(TRAVEL + TRANSFER) | TRANSFER 需求服务日 | TRANSFER 需求名下全部原始行 | TRANSFER 行用 TRANSFER 窗口,其余行用当前有效需求窗口 |
| 只有 TRAVEL | 与改前相同 | 与改前相同 | 与改前相同 |
| 只有 TRANSFER | 与改前相同(#8430 口径) | 与改前相同 | 与改前相同 |
- 前端展示「缺接机 / 缺送机」请直接用 `pickupDropoffGate.missingPickupDates / missingDropoffDates`,不要自己拿行程起止日重算。
- 「改期残留/待清理」徽标直接用 `rescheduleResidue`,不要按行程日期自行判断接送机行是否越窗。
---
## 五、数据库行为
无表结构变更,无写库。只改 fleet `BoardOrderService` 组装详情时的内存判定。
---
## 六、边界行为
- 同一响应内顶层门禁与逐日 `pickupRequired / dropoffRequired` 用同一个窗口变量,二者不会出现「逐日说要接机、门禁说不需要」的矛盾。
- 过期定稿方案的进度回拨判据与 `activeAssignments` 回拨判据相同(锚点行可进入配车流程);锚点行不满足时进度按原状态计算。
### 向后兼容
响应结构不变,无新增、无删除字段。非并存订单取值不变。
---
## 六.5、枚举 / 数据字典
本单不新增、不修改枚举。
---
## 六.6、修改前后对比
测试服同一张并存核心订单(行程用车 2027-11-20 ~ 2027-11-26;接送机 11-19 接机、11-26 送机;两条接送派车行均已派)修前修后实测读数:
| 字段 | 修前 | 修后 |
|------|------|------|
| `pickupDropoffGate.arrivalRequiredDates` | `["2027-11-20"]` | `["2027-11-19"]` |
| `pickupDropoffGate.missingPickupDates` | `["2027-11-20"]` | `[]` |
| `pickupDropoffGate.satisfied` | `false` | `true` |
| `pickupDropoffGate.departureRequiredDates` | `["2027-11-26"]` | `["2027-11-26"]` |
| `pickupDropoffGate.missingDropoffDates` | `[]` | `[]` |
| `dailyVehiclePlan[11-19].pickupRequired` | `false` | `true` |
| `dailyVehiclePlan[11-19].rescheduleResidue` | `true` | `false` |
| `vehicleSlots[11-19 那一行].rescheduleResidue` | `true` | `false` |
| `dailyVehiclePlan[11-26].rescheduleResidue` | `false` | `false` |
| `progressSteps` 中 `PICKUP_DROPOFF` / `CONFIRM_EXECUTE` | `PROCESSING` / `WAITING` | `DONE` / `DONE` |
---
## 六.7、影响评估
- **是否破坏向后兼容**: 否,只有取值口径变化。
- **前端是否必须同步上线**: 否。只展示这些字段的页面无需改动。
- **前端 workaround 清理点**: 如果前端为规避「缺接机」误报或「改期残留」误标加过按行程日期过滤接送机行的逻辑,可以删掉,直接用后端字段。
---
## 七、不影响范围
- 非并存订单(只有 TRAVEL 或只有 TRANSFER)的全部字段。
- 看板列表接口。
- 写侧接口(最终方案发布、需求级确认)逻辑未改;本单只让读侧与写侧口径对齐。
---
## 八、测试环境已验证
部署:测试服 `hl-fleet-service` 两个实例于 2026-09-28 12:19:44 滚到 `dev-v3 d10aada4d`(包含本单合并提交 `82444e3f0`),deploy-status 显示落后 0。
### 测试服网关实测(`GET /admin/fleet/board/orders/{orderId}`,测试专用账号与自造夹具)
| 场景 | 检验点 | 结果 |
|------|--------|------|
| 并存核心订单,接机日 11-19 早于行程首日 11-20 | `arrivalRequiredDates=["2027-11-19"]`、`missingPickupDates=[]`、`satisfied=true` | ✅ 读数见六.6 |
| 同上 | 11-19 行 `pickupRequired` 由 false 变 true | ✅ |
| 同上 | 11-19 接机行在 `dailyVehiclePlan[]` 与 `vehicleSlots[]` 的 `rescheduleResidue` 由 true 变 false;11-26 送机行保持 false | ✅ |
| 同上 | 门禁满足后 `progressSteps` 的接送机与确认执行两步变为 DONE | ✅ |
### 单测覆盖(`BoardOrderServiceTest`,修前红、修后绿)
| 场景 | 用例 |
|------|------|
| 并存订单 TRANSFER 行在行程窗外、在接送机窗内:不判残留 | `queryOrderDetail_coexistingTransferRowOutsideTravelWindow_notRescheduleResidue` |
| TRANSFER 行只在行程窗内、不在接送机窗内:判残留 | `queryOrderDetail_coexistingTransferRowInsideTravelWindowOnly_rescheduleResidueByTransferWindow` |
| TRANSFER 行两个窗口都不在:仍判残留 | `queryOrderDetail_coexistingTransferRowOutsideBothWindows_stillRescheduleResidue` |
| 只有 TRAVEL 的订单:残留判定不变(阴性对照) | `queryOrderDetail_travelOnlyOrder_rescheduleResidueUnchanged` |
| 行程中途重排,旧代已完结、新代在途:两个 `dispatchPlanGeneration` 都给新代 | `queryOrderDetail_travelMidTripReplan_completedOldGenerationExcludedFromGeneration` |
| 过期定稿方案:`DISPATCH` 为 PROCESSING,`PICKUP_DROPOFF`、`CONFIRM_EXECUTE` 为 WAITING | `queryOrderDetail_staleFinalizedPlan_progressStepsFollowRebasedStatus` |
中途重排与过期定稿两个场景没有在测试服造数,只由单测覆盖。
---
## 十、相关文档
- 工单 #8455: https://git.1814.love/wx/HL/issues/8455
- 同族工单 #8430(纯接送机订单看板详情按 TRANSFER 取数):https://git.1814.love/wx/HL/issues/8430
- 同族工单 #7990(接送机需求确认链路):https://git.1814.love/wx/HL/issues/7990
---
## 关联 / 联系人
### 链接
- **Issue**: [#8455](https://git.1814.love/wx/HL/issues/8455)
- **PR**: [#8470](https://git.1814.love/wx/HL/pulls/8470)
- **Merge commit**: [82444e3f0](https://git.1814.love/wx/HL/commit/82444e3f0)
### 联系人
- **后端负责人**: @wx