docs(changelog): #8371 改期残留行单行取消 + #8372 团期总览 groupCode;20_5935 加订正指针
changelog-filename-gate / validate (push) Failing after 2s
changelog-filename-gate / validate (push) Failing after 2s
- 26_8371:DELETE /admin/fleet/assignments/{assignmentId} 命中改期残留行只取消该行、豁免 605027/605047/605028;
CancelRespVO.evidenceFileIds 改为按组累计(本次未传凭证时为空);取窗失败 605710 失败关闭。
- 26_8372:GET 团期派车总览 days[].vehicles[] 新增 groupCode,可原样回提 reconfigure 的 assignments[].groupId。
- 20_5935:旧件各【需确认】/错误说法处加 #8371 订正指针,指向新件。
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
这个提交包含在:
@@ -10,10 +10,10 @@ gateway_status: "not_required"
|
|||||||
frontend_status: "verified"
|
frontend_status: "verified"
|
||||||
frontend_owner: "mmg"
|
frontend_owner: "mmg"
|
||||||
frontend_ref: "28d4482c636dbfeba1803c64ef263f09c3656934"
|
frontend_ref: "28d4482c636dbfeba1803c64ef263f09c3656934"
|
||||||
target_release: ""
|
target_release: "hl-ui@28d4482c"
|
||||||
verified_at: "2026-09-21"
|
verified_at: "2026-09-21"
|
||||||
status_note: "前端 2026-09-20 反馈清单第4项引用了一个不存在的写口 POST .../slots/{slotId}/clear-residue-dates(该端点已随 #7067 去槽位化整体删除,origin/dev-v3 全仓零命中),并要求后端在读侧新增 residueServiceDates 字段。核实:读侧 cells[].rescheduleResidue 已能推导出该日期子集,写侧 DELETE /{assignmentId} 与 POST /batch 两条既有端点已覆盖单日/整批清理,本次零后端代码改动。POST /batch 对已过去的残留日期有硬门禁会拦截,DELETE 是否放行未取证,本文档已如实标注【需确认】。前端 2026-09-21 已交付:Step2 逐日栅格恢复残留格「取消残留」入口(canDelete!==false 且 assignmentId 时渲染,只读行禁用,deleteBlockReason 透传),逐日清理走 DELETE /admin/fleet/assignments/{assignmentId}(cancelReason/driverNotified/requestId 按契约显式传,requestId=createRequestId('residue-clear'));整批 POST /batch 100001「已过去或已完结」识别后提示改走逐日清理。DELETE 对过去日期是否放行(605028)仍未实证,由拦截器统一透后端 message 兜底。"
|
status_note: "前端 2026-09-20 反馈清单第4项引用了一个不存在的写口 POST .../slots/{slotId}/clear-residue-dates(该端点已随 #7067 去槽位化整体删除,origin/dev-v3 全仓零命中),并要求后端在读侧新增 residueServiceDates 字段。核实:读侧 cells[].rescheduleResidue 已能推导出该日期子集,写侧 DELETE /{assignmentId} 与 POST /batch 两条既有端点已覆盖单日/整批清理,本次零后端代码改动。POST /batch 对已过去的残留日期有硬门禁会拦截,DELETE 是否放行未取证,本文档已如实标注【需确认】。前端 2026-09-21 已交付:Step2 逐日栅格恢复残留格「取消残留」入口(canDelete!==false 且 assignmentId 时渲染,只读行禁用,deleteBlockReason 透传),逐日清理走 DELETE /admin/fleet/assignments/{assignmentId}(cancelReason/driverNotified/requestId 按契约显式传,requestId=createRequestId('residue-clear'));整批 POST /batch 100001「已过去或已完结」识别后提示改走逐日清理。DELETE 对过去日期是否放行(605028)仍未实证,由拦截器统一透后端 message 兜底。2026-09-26 订正(#8371):DELETE 命中改期残留行已改为单行取消,并豁免 605027/605047/605028 三道日期门,见 26_8371_改期残留行单行取消与累计凭证回显-修改接口-管理后台.md。"
|
||||||
updated_at: "2026-09-20"
|
updated_at: "2026-09-26"
|
||||||
base: "dev-v3"
|
base: "dev-v3"
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -41,6 +41,10 @@ base: "dev-v3"
|
|||||||
- 前端要求新增的读侧字段 `residueServiceDates`**不需要新增**:`AssignmentCandidateRespVO.CanonicalSnapshotVO.cells[]`(`AssignmentCandidateRespVO.java:108-150`)里每个 cell 已经逐日携带 `rescheduleResidue`(布尔)+ `serviceDate` + `assignmentId`,前端自己过滤即可得到这个数组。
|
- 前端要求新增的读侧字段 `residueServiceDates`**不需要新增**:`AssignmentCandidateRespVO.CanonicalSnapshotVO.cells[]`(`AssignmentCandidateRespVO.java:108-150`)里每个 cell 已经逐日携带 `rescheduleResidue`(布尔)+ `serviceDate` + `assignmentId`,前端自己过滤即可得到这个数组。
|
||||||
- ⚠️ **`POST /batch` 整批清理对"已过去的残留日期"有硬门禁会拦截**(详见「四、契约约束」),前端做整批清理时必须预期这种场景下的 400。逐日清理 `DELETE /{assignmentId}` 是否放行过去日期,本轮**未取证**,标记为【需确认】。
|
- ⚠️ **`POST /batch` 整批清理对"已过去的残留日期"有硬门禁会拦截**(详见「四、契约约束」),前端做整批清理时必须预期这种场景下的 400。逐日清理 `DELETE /{assignmentId}` 是否放行过去日期,本轮**未取证**,标记为【需确认】。
|
||||||
|
|
||||||
|
> **2026-09-26 订正(#8371)**:
|
||||||
|
> - `DELETE /{assignmentId}` 逐日清理残留行现已**单行取消**(不再整组取消),并豁免 605027/605047/605028 三道日期门。
|
||||||
|
> - 改期残留行判定、单行处理、错误码豁免的细节,见 26_8371_改期残留行单行取消与累计凭证回显-修改接口-管理后台.md。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 一、背景
|
## 一、背景
|
||||||
@@ -72,6 +76,8 @@ base: "dev-v3"
|
|||||||
| 2 | 取消派单(逐日清理残留) | DELETE | `/admin/fleet/assignments/{assignmentId}` | 复用不改 | 按 cell 的 `assignmentId` 单日取消,条件释放占用 |
|
| 2 | 取消派单(逐日清理残留) | DELETE | `/admin/fleet/assignments/{assignmentId}` | 复用不改 | 按 cell 的 `assignmentId` 单日取消,条件释放占用 |
|
||||||
| 3 | 批量创建派单(整批清理残留) | POST | `/admin/fleet/assignments/batch` | 复用不改 | 提交时不带残留日期项,后端按 diff 精确取消 |
|
| 3 | 批量创建派单(整批清理残留) | POST | `/admin/fleet/assignments/batch` | 复用不改 | 提交时不带残留日期项,后端按 diff 精确取消 |
|
||||||
|
|
||||||
|
> **2026-09-26 订正(#8371)**:上表第 2 行「按 cell 的 `assignmentId` 单日取消」在写作当时并不成立——彼时 `DELETE` 命中改期残留行仍按**整组**取消(还会连带取消同组窗内的其它日期行,这正是 #8371 要修的缺陷)。#8371 已实测确认:`DELETE` 现在对「改期残留行」做真正的单行取消,窗内其它行不受影响;非残留行仍按原整组逻辑处理。细节与实测证据见 `26_8371_改期残留行单行取消与累计凭证回显-修改接口-管理后台.md`。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 三、接口详情
|
## 三、接口详情
|
||||||
@@ -197,6 +203,8 @@ base: "dev-v3"
|
|||||||
|
|
||||||
**VO**: `CancelReqVO → CancelRespVO`
|
**VO**: `CancelReqVO → CancelRespVO`
|
||||||
|
|
||||||
|
> **2026-09-26 订正(#8371)**:本小节标题「逐日清理残留」写作时是前端诉求的期望描述,当时源码并不支持——`DELETE` 命中改期残留行时仍按整组取消。#8371 已实测确认标题所述行为现已成立:目标行若判定为「改期残留行」,走单行取消分支,操作日志 `operationLog[].detailJson` 会带 `scope="RESIDUE_ROW"` 可供核验;非残留行不受影响,仍是整组取消。细节见 `26_8371_改期残留行单行取消与累计凭证回显-修改接口-管理后台.md`。
|
||||||
|
|
||||||
#### 使用场景
|
#### 使用场景
|
||||||
|
|
||||||
车务在 Step2 栅格里勾选一个或多个 `rescheduleResidue=true` 的日期后,对每个勾选日期取其 cell 的 `assignmentId`,逐日调用本端点单独取消,条件释放该行占用的车辆/司机。适合「只清理某几天」「部分勾选」场景。
|
车务在 Step2 栅格里勾选一个或多个 `rescheduleResidue=true` 的日期后,对每个勾选日期取其 cell 的 `assignmentId`,逐日调用本端点单独取消,条件释放该行占用的车辆/司机。适合「只清理某几天」「部分勾选」场景。
|
||||||
@@ -287,9 +295,13 @@ base: "dev-v3"
|
|||||||
|
|
||||||
其它错误码(`AssignmentController.java:469-471`):400(`cancelReason`/`driverNotified`/`requestId` 请求校验)/ 605009(派单不存在)/ 605020(当前状态不允许取消)/ 605026(无凭证未二次确认)/ 605027(已出发禁止整组取消)。
|
其它错误码(`AssignmentController.java:469-471`):400(`cancelReason`/`driverNotified`/`requestId` 请求校验)/ 605009(派单不存在)/ 605020(当前状态不允许取消)/ 605026(无凭证未二次确认)/ 605027(已出发禁止整组取消)。
|
||||||
|
|
||||||
|
> **2026-09-26 订正(#8371)**:目标行判定为「改期残留行」时,605027(连同 605047/605028)**不再触发**,改走单行取消;新增 605710「订单服务不可用:{0}」——残留行判定依赖 order-v3 查询窗口数据,查询失败时整笔 fail-closed,不取消任何行。非残留行的错误码集合不变。见 `26_8371_改期残留行单行取消与累计凭证回显-修改接口-管理后台.md`。
|
||||||
|
|
||||||
#### 业务边界
|
#### 业务边界
|
||||||
|
|
||||||
- 【需确认】**本端点对"服务日期已过去的改期残留行"是否放行取消,本轮未取证**:`CancelReqVO.cutoffDate` 注释写明「服务端仅接受当天,空值按当天处理,不支持预约未来取消」,而 605028 的语义是「取消生效日不在派单服务日期范围内」——若某残留行 `serviceDate` 是 3 天前、`cutoffDate` 按当天处理,"当天"是否落在该行的服务日期范围内、会不会触发 605028,未经实测确认。读侧 `canDelete` 字段的设计意图是「改期残留旧行恒 `true`,受只读窗口约束时 `false`」(`AssignmentCandidateRespVO.java:149-150`),但这是响应快照上的展示态,不等于运行时 DELETE 调用本身在过去日期上必然放行。**若实测发现本端点对已过去日期同样拦截,那才是真正需要后端开一张单的地方**(放开残留行对过去日期的取消)。
|
- 【需确认】**本端点对"服务日期已过去的改期残留行"是否放行取消,本轮未取证**:`CancelReqVO.cutoffDate` 注释写明「服务端仅接受当天,空值按当天处理,不支持预约未来取消」,而 605028 的语义是「取消生效日不在派单服务日期范围内」——若某残留行 `serviceDate` 是 3 天前、`cutoffDate` 按当天处理,"当天"是否落在该行的服务日期范围内、会不会触发 605028,未经实测确认。读侧 `canDelete` 字段的设计意图是「改期残留旧行恒 `true`,受只读窗口约束时 `false`」(`AssignmentCandidateRespVO.java:149-150`),但这是响应快照上的展示态,不等于运行时 DELETE 调用本身在过去日期上必然放行。**若实测发现本端点对已过去日期同样拦截,那才是真正需要后端开一张单的地方**(放开残留行对过去日期的取消)。
|
||||||
|
|
||||||
|
> **2026-09-26 订正(#8371)**:已实测坐实——修复前,改期残留行(服务日期已过去)确实被拦截,实测复现 605027;#8371 修复后,同一残留行 `DELETE` 直接取消成功,不再命中 605027/605047/605028。取消后该行状态落 `exception`(不是 `canceled`,因取消前处于 `assigned`/`holding`,与通用取消 #6152 同口径)。判定逻辑与全链路证据见 `26_8371_改期残留行单行取消与累计凭证回显-修改接口-管理后台.md`。
|
||||||
- `assignmentId` 必须取自对应 cell(残留清理场景下即 `rescheduleResidue=true` 的那一项),传错会命中另一天的派车行。
|
- `assignmentId` 必须取自对应 cell(残留清理场景下即 `rescheduleResidue=true` 的那一项),传错会命中另一天的派车行。
|
||||||
- `driverNotified` 传 `false` 也允许取消,只是前端需要在未告知司机时给出强提示(后端只做存证,不代为通知)。
|
- `driverNotified` 传 `false` 也允许取消,只是前端需要在未告知司机时给出强提示(后端只做存证,不代为通知)。
|
||||||
|
|
||||||
@@ -416,6 +428,8 @@ base: "dev-v3"
|
|||||||
|
|
||||||
前端在实现"整批清理"按钮时,遇到 `code=100001` 且消息含"已过去或已完结",应识别为"残留日期已过去,本接口无法清理",引导车务改走逐日 `DELETE` 路径(而不是当成普通校验失败重试原样提交)。
|
前端在实现"整批清理"按钮时,遇到 `code=100001` 且消息含"已过去或已完结",应识别为"残留日期已过去,本接口无法清理",引导车务改走逐日 `DELETE` 路径(而不是当成普通校验失败重试原样提交)。
|
||||||
|
|
||||||
|
> **2026-09-26 订正(#8371)**:上表「✅ 逐日清理(含过去日期,未取证是否放行)」与本段「引导车务改走逐日 `DELETE`」在写作时均未经实测;实测结果是**当时同样会被拦截**(改期残留行过去日期 `DELETE` 命中 605027),引导车务改走逐日 `DELETE` 在当时并不能真正解决问题。#8371 已修复:残留行现在 `DELETE` 直接取消成功,完全跳过整组门禁,上表与本段的方向自此才成立。见 `26_8371_改期残留行单行取消与累计凭证回显-修改接口-管理后台.md`。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 五、数据库行为
|
## 五、数据库行为
|
||||||
@@ -429,6 +443,8 @@ base: "dev-v3"
|
|||||||
|
|
||||||
两条路径都不会物理删除行,只做状态流转;`assignmentId` 一旦取消不可复用为新方案的匹配键。
|
两条路径都不会物理删除行,只做状态流转;`assignmentId` 一旦取消不可复用为新方案的匹配键。
|
||||||
|
|
||||||
|
> **2026-09-26 订正(#8371)**:上表「状态置 `canceled`」是简化写法。测试服实测:取消前处于 `assigned`/`holding` 的行,落库状态是 `exception`(与通用取消 #6152 同口径),仅 `unassigned` 行取消才直接落 `canceled`。改期残留行走单行取消分支时同样遵循这条状态机,不因残留身份而特殊化。见 `26_8371_改期残留行单行取消与累计凭证回显-修改接口-管理后台.md`。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 六、边界行为
|
## 六、边界行为
|
||||||
@@ -437,6 +453,8 @@ base: "dev-v3"
|
|||||||
- `assignmentId` 不存在 → `DELETE` 返 605009
|
- `assignmentId` 不存在 → `DELETE` 返 605009
|
||||||
- `requirementId` 缺失或需求上下文不可用 → `candidates` 的 `canonicalSnapshot` 为 `null`,不报错
|
- `requirementId` 缺失或需求上下文不可用 → `candidates` 的 `canonicalSnapshot` 为 `null`,不报错
|
||||||
- 已出发/已完结的派单 → 605027(整组取消禁止)/「该日已完结不可改派」400(`AssignmentService.java:2714`)
|
- 已出发/已完结的派单 → 605027(整组取消禁止)/「该日已完结不可改派」400(`AssignmentService.java:2714`)
|
||||||
|
|
||||||
|
> **2026-09-26 订正(#8371)**:上条仅对**非改期残留行**成立。目标行判定为改期残留行时,605027/605047/605028 三道日期门全部豁免,按单行取消处理;order-v3 查询窗口失败时新增 605710 fail-closed(不取消任何行)。见 `26_8371_改期残留行单行取消与累计凭证回显-修改接口-管理后台.md`。
|
||||||
- 老数据兼容:历史行无 `assignmentGroupId` 时,`cells[].groupId` 回退返回 `assignmentId`,对任何真实行恒非空
|
- 老数据兼容:历史行无 `assignmentGroupId` 时,`cells[].groupId` 回退返回 `assignmentId`,对任何真实行恒非空
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -498,6 +516,8 @@ grep clear-residue-dates / clearResidueDates / cancel-residue → 全仓零命
|
|||||||
|
|
||||||
**【需确认】未取证项**:`DELETE /{assignmentId}` 对服务日期已过去的改期残留行是否放行取消(涉及 605028 与只读窗口判定),需要一次真实测试服调用才能确认,本文档不代为下结论。
|
**【需确认】未取证项**:`DELETE /{assignmentId}` 对服务日期已过去的改期残留行是否放行取消(涉及 605028 与只读窗口判定),需要一次真实测试服调用才能确认,本文档不代为下结论。
|
||||||
|
|
||||||
|
> **2026-09-26 订正(#8371)**:已取证,不再是【需确认】。改前基线:确实拦截,命中 605027。改后(#8371 测试服全链路 6 步实测):改期残留行判定为真时,`DELETE` 直接放行取消,不命中 605027/605047/605028;判定失败(order-v3 不可达)时新码 605710 fail-closed。见 `26_8371_改期残留行单行取消与累计凭证回显-修改接口-管理后台.md`。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 十、相关文档
|
## 十、相关文档
|
||||||
@@ -506,6 +526,8 @@ grep clear-residue-dates / clearResidueDates / cancel-residue → 全仓零命
|
|||||||
- 退役背景:#7067 去槽位化重构(`06_7067_派单去槽位化按行程日配车-接送机独立配置-修改接口-管理后台.md`)
|
- 退役背景:#7067 去槽位化重构(`06_7067_派单去槽位化按行程日配车-接送机独立配置-修改接口-管理后台.md`)
|
||||||
- 后续计划:若【需确认】项实测发现 `DELETE` 对过去日期同样拦截,需另开工单放开残留行的过去日期取消
|
- 后续计划:若【需确认】项实测发现 `DELETE` 对过去日期同样拦截,需另开工单放开残留行的过去日期取消
|
||||||
|
|
||||||
|
> **2026-09-26 订正(#8371)**:该「另开工单」已完成,即 #8371——已实测坐实过去拦截、已修复并验证放行。见 `26_8371_改期残留行单行取消与累计凭证回显-修改接口-管理后台.md`。
|
||||||
|
|
||||||
## 关联 / 联系人
|
## 关联 / 联系人
|
||||||
|
|
||||||
### 链接
|
### 链接
|
||||||
|
|||||||
@@ -0,0 +1,274 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v2"
|
||||||
|
ticket: "8371"
|
||||||
|
title: "取消派单:改期残留行单行取消,evidenceFileIds 改为按组累计"
|
||||||
|
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: "PR #8380 已合并 dev-v3(commit c2bc5c3d4),测试服 hl-fleet-service 已部署该 commit(本单不改 order-v3),已在测试服完成 6 步全链路真实网关调用验证:残留行单行取消(不再命中 605027)、非残留组取消保持原逻辑、批量重提窗内行、需求确认成功。前端接入本次不需要自行判断「该行是否残留」——后端已在服务端自动判定并豁免日期门;唯一需要适配的是 CancelRespVO.evidenceFileIds 语义变化,见下文「六.6」。"
|
||||||
|
updated_at: "2026-09-26"
|
||||||
|
base: "dev-v3"
|
||||||
|
---
|
||||||
|
|
||||||
|
# 取消派单:改期残留行单行取消,evidenceFileIds 改为按组累计
|
||||||
|
|
||||||
|
> **存放目录**: 二期(v3) → `changelogs-v2/2026-09/`
|
||||||
|
>
|
||||||
|
> **服务**: hl-fleet-service
|
||||||
|
> **PR**: #8380
|
||||||
|
> **Issue**: #8371
|
||||||
|
> **日期**: 2026-09-26
|
||||||
|
> **影响范围**: 管理后台车务派单「取消派车」弹窗与取消成功后的凭证展示
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚠️ 关键变化
|
||||||
|
|
||||||
|
`DELETE /admin/fleet/assignments/{assignmentId}` 取消派车接口两处行为改动(路径、方法、入参字段不变):
|
||||||
|
|
||||||
|
1. **改期残留行单行取消**(`AssignmentController.java:464-481`、`AssignmentService.java:5308-5419`):订单改期后,落在「该行 `requirementId` 所指当前用车需求日期窗口」之外的旧逐日派车行(称「改期残留行」),调用本接口取消时现改为**只取消这一行**,不再连带同派车组内其他行。同时豁免 605027(已出发禁止整组取消)、605047(行程已结束只读)、605028(生效日不在派单服务日期范围内)三道日期门;`effectiveDate` 恒为今天,入参 `cutoffDate` 对残留行不生效。**非残留行(含全程行)行为不变,仍按派车组整组处理**。
|
||||||
|
- 残留行判定依赖实时查询 order-v3 侧的用车需求日期窗(`orderQueryFacade.getFleetDetailContextStrict`),查询失败时**整笔取消 fail-closed,一行都不取消**,返回新错误码 605710「订单服务不可用:{0}」。
|
||||||
|
2. **`CancelRespVO.evidenceFileIds` 语义变更**(`CancelRespVO.java:35`):改前是本次请求入参携带的凭证 ID 原样返回;改后是**该派车组自创建以来累计的全部取消凭证 ID**(追加式只增不丢,旧的在前新的在后),本次未传凭证时返回空数组,不代表没有历史凭证——历史凭证不会因为本次没传就消失。
|
||||||
|
|
||||||
|
另有一个非接口改动:夜间自动完结定时任务(`AssignmentCompleteJob` → `AssignmentService.listScheduleCompletableBefore`)现在会跳过判定为改期残留行的 `assigned` 状态派车,不再把它们自动置 `completed`;残留行会一直停在原状态,直到车务手工调用本接口取消。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、变更接口清单
|
||||||
|
|
||||||
|
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||||
|
|---|------|------|------|----------|------|
|
||||||
|
| 1 | 取消派单 | DELETE | `/admin/fleet/assignments/{assignmentId}` | 行为变更(出参字段语义变更,字段本身不变) | 改期残留行单行取消并豁免三道日期门;`evidenceFileIds` 改为按组累计 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、接口详情
|
||||||
|
|
||||||
|
### 1. 取消派单 `DELETE /admin/fleet/assignments/{assignmentId}`
|
||||||
|
|
||||||
|
**VO**: `CancelReqVO → CancelRespVO`
|
||||||
|
|
||||||
|
#### 使用场景
|
||||||
|
|
||||||
|
管理后台车务派单管理页面,对某一行派车点击「取消」调用本接口,按条件释放该行占用的车辆/司机资源。目标行若被判定为「改期残留行」(服务日期落在当前用车需求日期窗之外),只取消这一行,不影响同派车组内窗口以内的其它日期行;否则仍按整组处理。
|
||||||
|
|
||||||
|
#### 入参字段表
|
||||||
|
|
||||||
|
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||||
|
|------|------|------|------|------|------|
|
||||||
|
| assignmentId | Path | Long | 是 | 派单须存在,否则 605009 | 待取消的派单行 ID |
|
||||||
|
| cancelReason | Body | String | 是 | `@NotBlank` | 取消原因 |
|
||||||
|
| driverNotified | Body | Boolean | 是 | `@NotNull` | 是否已告知司机(人工存证,true/false 均可取消) |
|
||||||
|
| notifyNote | Body | String | 否 | - | 告知方式/摘要备注 |
|
||||||
|
| cutoffDate | Body | LocalDate | 否 | 服务端仅接受当天,空值按当天处理 | 取消生效日;**改期残留行本参数不生效,effectiveDate 恒为今天** |
|
||||||
|
| evidenceFileIds | Body | Array\<Long\> | 否 | `@Size` 最多 20 个 | 本次新增的取消凭证文件 ID |
|
||||||
|
| confirmWithoutEvidence | Body | Boolean | 否 | - | 无凭证时的二次确认标记 |
|
||||||
|
| requestId | Body | String | 是 | `@NotBlank`,最长 64 | 幂等请求 ID |
|
||||||
|
|
||||||
|
#### 出参字段表
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| data.assignmentStatus | String | 取消后落库状态:来源行是 `unassigned` 落 `canceled`;来源行是 `assigned`/`holding`(在途)落 `exception`(与通用取消 #6152 同口径,非残留身份特有) |
|
||||||
|
| data.assignmentGroupId | String(Long) | 派车组 ID;历史行无该字段时回退 assignmentId |
|
||||||
|
| data.assignmentSlotId | String(Long) | 稳定车辆槽位 ID(历史字段) |
|
||||||
|
| data.effectiveDate | LocalDate | 实际取消生效日;改期残留行恒为今天 |
|
||||||
|
| data.evidenceFileIds | String(Long)[] | **本次改动**:该派车组自创建以来累计的全部取消凭证 ID(追加式只增不丢,旧在前新在后);本次未传凭证时为空数组,不代表没有历史凭证 |
|
||||||
|
| data.confirmedWithoutEvidence | Boolean | 无凭证时是否已二次确认 |
|
||||||
|
| data.sideEffects.vehicleStatusUpdated | String | 车辆状态更新结果(`idle`/`busy`/`maint`;不涉及车为 `null`) |
|
||||||
|
| data.sideEffects.driverStatusUpdated | String | 司机状态更新结果(`busy`/`idle`;不涉及司机为 `null`) |
|
||||||
|
| data.sideEffects.reconPrepRowsCreated | Integer | 对账预备单创建条数(M1 降级可能为 `null` 或 0) |
|
||||||
|
| data.sideEffects.reconPrepMarkedCanceled | Integer | 对账预备单标记取消条数(M1 降级恒 0) |
|
||||||
|
|
||||||
|
#### 请求示例
|
||||||
|
|
||||||
|
> 测试服网关实测原文,目标行为改期残留行,服务日期已过去。
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"cancelReason": "8371-verify-residue-delete",
|
||||||
|
"driverNotified": true,
|
||||||
|
"notifyNote": "8371-verify-residue-delete",
|
||||||
|
"confirmWithoutEvidence": true,
|
||||||
|
"requestId": "8371-verify-delete-1"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 响应示例
|
||||||
|
|
||||||
|
> 测试服网关实测原文。取消前该行处于 `assigned`,故落库为 `exception` 而非 `canceled`;`reconPrepRowsCreated` 实测返回 `null`,前端勿据它做判断。
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"data": {
|
||||||
|
"assignmentStatus": "exception",
|
||||||
|
"assignmentGroupId": "362105579390504961",
|
||||||
|
"assignmentSlotId": "362105579390504961",
|
||||||
|
"effectiveDate": "2026-09-26",
|
||||||
|
"evidenceFileIds": [],
|
||||||
|
"confirmedWithoutEvidence": true,
|
||||||
|
"sideEffects": {
|
||||||
|
"vehicleStatusUpdated": "busy",
|
||||||
|
"driverStatusUpdated": "busy",
|
||||||
|
"reconPrepRowsCreated": null,
|
||||||
|
"reconPrepMarkedCanceled": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 空数据 / 降级响应
|
||||||
|
|
||||||
|
本端点是单条写操作,无空数据态。目标行判定改期残留行失败(依赖的 order-v3 查询不可达)时,不返回部分结果、不猜测是否残留,一行都不取消,直接走错误响应(见下方 605710)。
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "code": 605710, "message": "订单服务不可用:取改期残留判定所需的用车需求日期窗失败", "success": false }
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 错误响应
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "code": 605009, "message": "派单不存在", "success": false }
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "code": 605026, "message": "未上传取消凭证,请二次确认后再取消", "success": false }
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "code": 605027, "message": "行程已出发,不能整组取消,请使用修改派单", "success": false }
|
||||||
|
```
|
||||||
|
|
||||||
|
**其它错误码**:400(`cancelReason`/`driverNotified`/`requestId` 请求校验)/ 605020「当前派单状态不允许此操作」/ 605028「生效日期不在派单服务日期范围内」/ 605047「行程已结束,派车信息只读,不能修改或改派」(`AssignmentService.java:5410` `assertTripMutable`)。**605027/605047/605028 三码仅在目标行判定为非改期残留行时才可能命中**;判定为改期残留行时豁免这三码,改走单行取消分支。
|
||||||
|
|
||||||
|
#### 业务边界
|
||||||
|
|
||||||
|
- **改期残留行判定**(`AssignmentDailyPlanFacts.isRescheduleResidue`,经 `AssignmentService.resolveCancelResidueWindow`/`isCancelResidueRow` 调用):仅逐日行(非全程行)参与判定;判定要在加锁前实时查询 order-v3 侧当前用车需求的日期窗,查询失败直接抛 605710,不对残留身份做任何假设,不取消任何行。
|
||||||
|
- **单行取消**:残留行确认后,只取消这一行;同派车组内落在窗口以内的其它日期行不受影响、不校验、不触发状态机。
|
||||||
|
- **整组取消**:非残留行维持原逻辑——取消会按派车组整体处理(`resolveEffectiveGroupRows` 圈定的全部可取消行),605027/605047/605028 三道日期门原样校验,`effectiveDate` 仍取 `cutoffDate`(默认今天,只接受今天)。
|
||||||
|
- **落库状态并非恒为 `canceled`**:来源行状态是 `unassigned` 时落 `canceled`;是 `assigned`/`holding`(在途)时落 `exception`(#6152 既有分流规则,不因残留身份而不同)。
|
||||||
|
- **凭证累积**:命中残留分支时同组会分多次调用逐行取消,若整组替换式返回本次入参会丢掉前几次的凭证;因此 `evidenceFileIds` 统一改为按组读取累计清单(`assignmentEvidenceService.listCancellationFileIds(groupId)`),前端应直接以响应值为准,不要自行拼接历史值。
|
||||||
|
- **操作日志**:仅命中残留分支时,`assignmentOperationLog` 的 `CANCEL_REQUESTED` 记录 `detailJson` 会带 `"scope":"RESIDUE_ROW"`;非残留(整组)分支不写这个键(不是写 `"GROUP"`,而是完全没有该字段)。可通过 `GET /admin/fleet/board/orders/{orderId}` 的 `operationLog[].detailJson` 读取核对。
|
||||||
|
- **司机通知**:`driverNotified=false` 仍允许取消,仅作人工存证,后端不代为通知。
|
||||||
|
- **幂等性**:`requestId` 走 `@Idempotent`,10 秒窗口内重复提交被拦截(提示「派单取消处理中,请勿重复提交」)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、契约约束与正确调用方式
|
||||||
|
|
||||||
|
### 残留行判定是服务端自动完成的,前端不需要预判
|
||||||
|
|
||||||
|
前端调用取消接口时无需先判断目标行是否为「改期残留行」——服务端在处理请求时会自动查询、自动分流。前端只需按现有交互正常收集 `cancelReason`/`driverNotified`/`requestId` 等必填参数发起请求:
|
||||||
|
- 命中残留行分支时,响应正常返回 200,`effectiveDate` 恒为今天,同派车组内其它行不受影响;前端无需额外处理。
|
||||||
|
- 判定依赖的 order-v3 查询失败时,返回 605710,此时**一整行都没有取消**,前端应按普通失败提示处理并允许重试,不需要区分"是不是残留行"。
|
||||||
|
|
||||||
|
### evidenceFileIds 的正确使用方式
|
||||||
|
|
||||||
|
本次请求**携带凭证**时,`CancelRespVO.evidenceFileIds` 返回该派车组截至本次累计的全部取消凭证 ID,旧在前新在后(而不是本次请求入参那几个;`AssignmentService.java:5526-5527`)。前端展示「该派车组已上传哪些取消凭证」时,应直接使用响应值整体覆盖式渲染,不要再把它与本地缓存的历史值做拼接,否则会出现重复 ID。
|
||||||
|
|
||||||
|
若本次请求未携带任何凭证(`evidenceFileIds` 为空、以 `confirmWithoutEvidence=true` 放行 605026),响应的 `evidenceFileIds` 为空数组——这不代表该派车组没有历史凭证,只是本次响应未重新拉取累计列表(`hasEvidence` 为 false 分支不会调用 `listCancellationFileIds`)。因此前端不要用空数组覆盖已展示的历史凭证;需要展示累计凭证时,以最近一次**携带凭证**的取消响应为准。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、数据库行为
|
||||||
|
|
||||||
|
- **`fleet_assignment`**:目标行 `assignment_status` 更新——来源行是 `unassigned` 写 `canceled`,是 `assigned`/`holding`(在途)写 `exception`(#6152 既有分流规则,不因是否残留行而不同);`updated_by` 显式落取消操作人。无表结构变更、无新增列。
|
||||||
|
- **`fleet_assignment_evidence`**:`hasEvidence=true` 时追加新行(`assignmentEvidenceService.appendCancellationEvidenceValidated`),不删除、不覆盖已有记录。
|
||||||
|
- **`fleet_assignment_operation_log`**:追加一条 `CANCEL_REQUESTED` 记录;`detail_json` 命中残留行分支时带 `"scope":"RESIDUE_ROW"`,非残留(整组)分支**不写 `scope` 键**(不是写 `"GROUP"`)。`hasEvidence=true` 时另追加一条 `CANCEL_EVIDENCE_RECORDED` 记录。
|
||||||
|
- **无新增表、无新增列**:改期残留行的判定结果本身不落库,每次取消请求都会重新实时查询 order-v3。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、边界行为
|
||||||
|
|
||||||
|
- **未登录** → 401(网关拦截)。
|
||||||
|
- **派单不存在** → 605009。
|
||||||
|
- **状态不允许**(如已取消、已完结) → 605020。
|
||||||
|
- **order-v3 不可达** → 605710 fail-closed,一行都不取消,不对残留身份做任何猜测。
|
||||||
|
- **无凭证未二次确认** → 605026。
|
||||||
|
- **非残留行已出发** → 605027;**非残留行程已结束** → 605047;**非残留行生效日不在服务日期范围内** → 605028——这三码只在非残留分支可能命中,残留行分支豁免。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六.6、修改前后对比
|
||||||
|
|
||||||
|
### 行为对比
|
||||||
|
|
||||||
|
| 行为 | 改前 | 改后 |
|
||||||
|
|------|------|------|
|
||||||
|
| 取消改期残留行 | 按整组处理,605027/605047/605028 三道日期门正常校验,同组内已出发的其它行会导致整组取消被拒 | **新增单行分支**:只取消该行本身,豁免三道日期门,`effectiveDate` 固定今天;同组其它行不受影响 |
|
||||||
|
| order-v3 查询失败时的处理 | 不涉及(改前无此依赖) | 新增 fail-closed:605710,一行都不取消 |
|
||||||
|
| 夜间自动完结 | 残留行与其它行一样,到期后被 `AssignmentCompleteJob` 自动置 `completed` | 残留行被跳过,停留在原状态直到车务手工调用本接口取消 |
|
||||||
|
|
||||||
|
### 出参字段对比
|
||||||
|
|
||||||
|
| 字段 | 改前 | 改后 |
|
||||||
|
|------|------|------|
|
||||||
|
| `evidenceFileIds` | 本次请求入参携带的凭证 ID 原样返回 | **语义变更**:该派车组自创建以来累计的全部取消凭证 ID(`hasEvidence=true` 时返回累计列表,为 false 时返回空数组) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六.7、影响评估
|
||||||
|
|
||||||
|
- **是否破坏向后兼容**:否。改期残留行的单行取消是新增的判定分支,非残留行(含全程行)的整组取消逻辑、三道日期门校验逐字不变。`evidenceFileIds` 字段名、类型(`Array<Long>`/序列化为字符串数组)均未变,只是同一个字段返回的内容集合变了。
|
||||||
|
- **前端是否必须同步上线**:否。前端不做任何改动,取消接口对非残留行的调用行为与改前完全一致;对残留行的调用会自动享受到"不再报 605027/605047/605028"的效果,不需要前端识别或适配。**唯一需要前端注意的**:如果前端此前把 `evidenceFileIds` 当作"本次上传的文件"做本地拼接展示,接入后应改为直接使用响应值整体替换,否则会展示出重复的凭证 ID。
|
||||||
|
- **覆盖边界**:本次改动不影响 `POST /admin/fleet/assignments/batch`、`POST /admin/fleet/assignments/requirements/{requirementId}/confirm` 等相邻写口自身的契约,只是这些写口在残留行被移出组后不再对该组报改前的连带错误(100001/605055),详见「八、测试环境已验证」步骤 4/6 的实测记录。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 七、不影响范围
|
||||||
|
|
||||||
|
- **仅影响**:`DELETE /admin/fleet/assignments/{assignmentId}` 对改期残留行的判定分支,以及 `evidenceFileIds` 的取值口径。
|
||||||
|
- **零影响**:
|
||||||
|
- 非残留行(含全程行)的整组取消规则,三道日期门校验逐字不变。
|
||||||
|
- 派车创建、批量重提、需求确认等其它写口自身的入参/出参契约。
|
||||||
|
- 车辆/司机占用释放的条件判断逻辑(`sideEffects` 字段结构不变)。
|
||||||
|
- #6152 既定的取消状态分流规则(`unassigned`→`canceled`,`assigned`/`holding`→`exception`)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 八、测试环境已验证
|
||||||
|
|
||||||
|
部署:PR #8380 已合并 `dev-v3`(`c2bc5c3d4`),测试服 hl-fleet-service 已部署该 commit;本单不改 order-v3。
|
||||||
|
|
||||||
|
以下 6 步为同一夹具的全链路真实网关调用(夹具:`orderId=2103714435619303425`,改期后需求窗 2026-10-02~10-04,派车组 3 行,其中 2026-09-20 一行落在窗外):
|
||||||
|
|
||||||
|
1. **改前状态**:`GET /admin/fleet/board/orders/{orderId}` 确认三行初始状态均为 `assigned`。
|
||||||
|
2. **DELETE 残留行**:对 2026-09-20 这一行发起取消,改前同一请求体命中 605027(拒绝整组取消);改后单行取消直接成功,返回 `assignmentStatus="exception"`(该行取消前是 `assigned`,按 #6152 分流落 `exception`)、`evidenceFileIds=[]`。
|
||||||
|
3. **复读验证**:窗内两行逐字段(vehicleId/driverId/assignmentPrice/priceSource/assignmentStatus 等)与改前基线完全一致,未受影响;操作日志新增一条 `detailJson` 含 `"scope":"RESIDUE_ROW"` 的记录。
|
||||||
|
4. **批量重提窗内行**:`POST /admin/fleet/assignments/batch` 提交窗内两行,改前同一请求体命中 100001("已过去或已完结,不能修改逐日派车配置");改后不再报错(残留行已单独取消,不再纳入本次提交的校验范围)。
|
||||||
|
5. **取新 generation**:复读 `dispatchPlanGeneration` 供确认步骤使用。
|
||||||
|
6. **需求确认**:`POST /admin/fleet/assignments/requirements/{requirementId}/confirm` 成功,改前基线在此步命中 605055("派车方案已变化,请刷新后重试"),改后成功确认。
|
||||||
|
|
||||||
|
全链路(DELETE 残留行 → 批量重提窗内行 → 需求确认)打通,改前基线记录的三处拒绝(605027/100001/605055)在改后代码下不再复现。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 十、相关文档
|
||||||
|
|
||||||
|
- 关联 Issue: [wx/HL#8371](https://git.1814.love/wx/HL/issues/8371)
|
||||||
|
- 关联 PR: [wx/HL#8380](https://git.1814.love/wx/HL/pulls/8380)
|
||||||
|
- 上游工单: #5935(改期残留清理,见 `20_5935_改期残留清理用现有读写口即可-前端缺陷-管理后台.md` 内 9 处订正指针)
|
||||||
|
|
||||||
|
## 关联 / 联系人
|
||||||
|
|
||||||
|
### 链接
|
||||||
|
|
||||||
|
- **Issue**: [#8371](https://git.1814.love/wx/HL/issues/8371)
|
||||||
|
- **PR**: [#8380](https://git.1814.love/wx/HL/pulls/8380)
|
||||||
|
- **Merge commit**: [c2bc5c3d4](https://git.1814.love/wx/HL/commit/c2bc5c3d4)
|
||||||
|
|
||||||
|
### 联系人
|
||||||
|
|
||||||
|
- **后端负责人**: @wx
|
||||||
|
- **前端负责人**: @mmg
|
||||||
@@ -0,0 +1,346 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v2"
|
||||||
|
ticket: "8372"
|
||||||
|
title: "团期派车总览 days[].vehicles[] 新增 groupCode 字段回显乘车分组编码"
|
||||||
|
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: "PR #8378 已合并 dev-v3(f8b546251),测试服 hl-fleet-service 已部署 c2bc5c3d4(含 f8b546251),本单不改 order-v3。测试环境完成两批实测:既存真实批次(groupBatchId=2101880394750328833)确认 9 条 vehicles 均正确回显 groupCode;自建全新批次(groupBatchId=2103733823122690049)完整跑通零变更重提(addedCount/removedCount/updatedCount 均为 0,idempotentShortCircuit=true)与删行/加行往返。"
|
||||||
|
updated_at: "2026-09-26"
|
||||||
|
base: "dev-v3"
|
||||||
|
---
|
||||||
|
|
||||||
|
# 团期派车:总览新增乘车分组编码回显
|
||||||
|
|
||||||
|
> **存放目录**: 二期(v3) → `changelogs-v2/2026-09/`
|
||||||
|
>
|
||||||
|
> **服务**: hl-fleet-service
|
||||||
|
> **PR**: #8378
|
||||||
|
> **Issue**: #8372
|
||||||
|
> **日期**: 2026-09-26
|
||||||
|
> **影响范围**: 管理后台「团期配车 → 待配车团期 → 总览」页面逐车行显示
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚠️ 关键变化
|
||||||
|
|
||||||
|
`GET /admin/fleet/group-dispatch/batches/{groupBatchId}/overview` 响应的 `days[].vehicles[]` 每行新增字段 `groupCode`(String,乘车分组键,如 `"A"`/`"BUS"`,历史未分组行为 `null`)。该字段原样取自 `fleet_group_dispatch.group_id`(`GroupDispatchOverviewVehicleVO.java:59-70`),供前端在「重新配置」表单里把总览原样重组成 reconfigure 请求:`groupCode` 对应 reconfigure 入参 `demands[].assignments[].groupId`,`vehicleId`/`driverId`/`remark` 同样原样回填。
|
||||||
|
|
||||||
|
reconfigure 是全量替换语义,请求里缺席的 (日期, 车) 会被软删;历史未分组行 `groupCode` 为 `null` 时,回提前必须先引导车务补选分组,否则 `groupId` 的 `@NotBlank` 校验会拒绝(400「乘车分组不能为空」)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、变更接口清单
|
||||||
|
|
||||||
|
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||||
|
|---|------|------|------|----------|------|
|
||||||
|
| 1 | 团期派车总览 | GET | `/admin/fleet/group-dispatch/batches/{groupBatchId}/overview` | 出参新增字段 | `days[].vehicles[].groupCode` 回显乘车分组键 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、接口详情
|
||||||
|
|
||||||
|
### 1. 团期派车总览 `GET /admin/fleet/group-dispatch/batches/{groupBatchId}/overview`
|
||||||
|
|
||||||
|
**VO**: `(无请求体,路径参数 groupBatchId) → GroupDispatchOverviewRespVO`
|
||||||
|
|
||||||
|
#### 使用场景
|
||||||
|
|
||||||
|
管理后台「团期配车」页面,进入某团点击「总览」查看该团逐日已排车、空洞日与逐户接送机缺口。前端把这份总览原样重组即可拼出 reconfigure 的「重新配置」请求,实现「查看 → 调整 → 重新提交」的闭环;本次新增的 `groupCode` 就是这条回提链路里此前缺失的一环。
|
||||||
|
|
||||||
|
#### 入参字段表
|
||||||
|
|
||||||
|
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||||
|
|------|------|------|------|------|------|
|
||||||
|
| groupBatchId | Path | Long | 是 | 团期须存在 | 团期主订单 ID(雪花) |
|
||||||
|
|
||||||
|
#### 出参字段表
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| groupBatchId | String(Long) | 团期主订单 ID |
|
||||||
|
| batchNo | String | 团号 |
|
||||||
|
| departDate | String(LocalDate) | 出团日 |
|
||||||
|
| endDate | String(LocalDate) | 返团日 |
|
||||||
|
| serviceDates | Array\<String\> | 权威服务日集合(order-v3 下发,fleet 不自算) |
|
||||||
|
| requirementConfirmed | Boolean | 整团需求是否已确认 |
|
||||||
|
| vehicleReady | Boolean | 配车是否已就绪 |
|
||||||
|
| days | Array\<Object\> | 逐日行(按 serviceDates 顺序铺满,空洞日也占一行) |
|
||||||
|
| days[].tripDate | String(LocalDate) | 行程日 |
|
||||||
|
| days[].vehicles | Array\<Object\> | 当日已排车项(无则空数组) |
|
||||||
|
| days[].vehicles[].dispatchId | String(Long) | 团期配车行 ID |
|
||||||
|
| days[].vehicles[].vehicleId | String(Long) | 车辆 ID |
|
||||||
|
| days[].vehicles[].vehiclePlate | String | 车牌(车已删则为 null) |
|
||||||
|
| days[].vehicles[].vehicleModel | String | 车型名(车已删则为 null) |
|
||||||
|
| days[].vehicles[].driverId | String(Long) | 司机 ID(仅排车未排司机时为 null) |
|
||||||
|
| days[].vehicles[].driverName | String | 司机姓名(未排司机或司机已删则为 null) |
|
||||||
|
| days[].vehicles[].driverPhone | String | 司机手机(脱敏) |
|
||||||
|
| days[].vehicles[].status | String | 派车状态:`ASSIGNED`/`CONFIRMED` |
|
||||||
|
| days[].vehicles[].remark | String | 备注 |
|
||||||
|
| days[].vehicles[].groupCode | String | **新增**:乘车分组键,原样取自 `fleet_group_dispatch.group_id`,存量未分组行为 `null` |
|
||||||
|
| days[].vehicleCount | Integer | 当日已排车辆数 |
|
||||||
|
| days[].dispatched | Boolean | 当日是否已排车(vehicleCount > 0) |
|
||||||
|
| missingDates | Array\<String\> | 空洞日(serviceDates 中没有任何存活派车行的日期) |
|
||||||
|
| orders | Array\<Object\> | 逐户行 |
|
||||||
|
| orders[].orderId | String(Long) | 子订单 ID |
|
||||||
|
| orders[].orderNo | String | 订单号 |
|
||||||
|
| orders[].customerName | String | 客户姓名 |
|
||||||
|
| orders[].headcount | Integer | 出行人数 |
|
||||||
|
| orders[].vehicleControlStatus | String | 用车管控状态(订单侧口径) |
|
||||||
|
| orders[].travelRequirementId | String(Long) | 该户当前有效用车需求 ID(无则为 null) |
|
||||||
|
| orders[].travelRequirementStatus | String | 该户当前有效用车需求状态(无则为 null) |
|
||||||
|
| orders[].transferDeclared | Boolean | 是否声明接送机 |
|
||||||
|
| orders[].transferArrivalDates | Array\<String\> | 接机声明日期(原始值,可在行程日窗外) |
|
||||||
|
| orders[].transferDepartureDates | Array\<String\> | 送机声明日期(原始值,可在行程日窗外) |
|
||||||
|
| orders[].transferPickupCoveredDates | Array\<String\> | 已派接机车的服务日 |
|
||||||
|
| orders[].transferDropoffCoveredDates | Array\<String\> | 已派送机车的服务日 |
|
||||||
|
| orders[].transferPendingCount | Integer | 该户接送机未配计数 |
|
||||||
|
| transferPendingTotal | Integer | 全团接送机未配计数(= orders[].transferPendingCount 之和) |
|
||||||
|
| conversationKey | String | 团期车务会话键,形如 `GROUP_FLEET:{groupBatchId}` |
|
||||||
|
|
||||||
|
#### 请求示例
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /admin/fleet/group-dispatch/batches/2103733823122690049/overview HTTP/1.1
|
||||||
|
Host: api.test.1814.love
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 响应示例
|
||||||
|
|
||||||
|
> 测试服网关实测原文(自建夹具,3 天 × 2 组共 6 条 vehicles,逐条均带 groupCode)。
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"data": {
|
||||||
|
"groupBatchId": "2103733823122690049",
|
||||||
|
"batchNo": "Q202703152103733770773524481",
|
||||||
|
"departDate": "2027-03-15",
|
||||||
|
"endDate": "2027-03-17",
|
||||||
|
"serviceDates": ["2027-03-15", "2027-03-16", "2027-03-17"],
|
||||||
|
"requirementConfirmed": true,
|
||||||
|
"vehicleReady": false,
|
||||||
|
"days": [
|
||||||
|
{
|
||||||
|
"tripDate": "2027-03-15",
|
||||||
|
"vehicles": [
|
||||||
|
{
|
||||||
|
"dispatchId": "2103736241809952770",
|
||||||
|
"vehicleId": "2065329514971308033",
|
||||||
|
"vehiclePlate": "蒙C02E02",
|
||||||
|
"vehicleModel": "丰田考斯特",
|
||||||
|
"driverId": "2089691297869651969",
|
||||||
|
"driverName": "P3测试司机18",
|
||||||
|
"driverPhone": "139****0018",
|
||||||
|
"status": "ASSIGNED",
|
||||||
|
"remark": "8372-verify",
|
||||||
|
"groupCode": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dispatchId": "2103736241814147074",
|
||||||
|
"vehicleId": "2065329516997156865",
|
||||||
|
"vehiclePlate": "蒙C08E08",
|
||||||
|
"vehicleModel": "丰田考斯特",
|
||||||
|
"driverId": "2089691294107361282",
|
||||||
|
"driverName": "P3测试司机17",
|
||||||
|
"driverPhone": "139****0017",
|
||||||
|
"status": "ASSIGNED",
|
||||||
|
"remark": "8372-verify",
|
||||||
|
"groupCode": "B"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"vehicleCount": 2,
|
||||||
|
"dispatched": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tripDate": "2027-03-16",
|
||||||
|
"vehicles": [
|
||||||
|
{"dispatchId": "2103736241818341377", "vehicleId": "2065329514971308033", "vehiclePlate": "蒙C02E02", "vehicleModel": "丰田考斯特", "driverId": "2089691297869651969", "driverName": "P3测试司机18", "driverPhone": "139****0018", "status": "ASSIGNED", "remark": "8372-verify", "groupCode": "A"},
|
||||||
|
{"dispatchId": "2103736241822535682", "vehicleId": "2065329516997156865", "vehiclePlate": "蒙C08E08", "vehicleModel": "丰田考斯特", "driverId": "2089691294107361282", "driverName": "P3测试司机17", "driverPhone": "139****0017", "status": "ASSIGNED", "remark": "8372-verify", "groupCode": "B"}
|
||||||
|
],
|
||||||
|
"vehicleCount": 2,
|
||||||
|
"dispatched": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tripDate": "2027-03-17",
|
||||||
|
"vehicles": [
|
||||||
|
{"dispatchId": "2103736241826729986", "vehicleId": "2065329514971308033", "vehiclePlate": "蒙C02E02", "vehicleModel": "丰田考斯特", "driverId": "2089691297869651969", "driverName": "P3测试司机18", "driverPhone": "139****0018", "status": "ASSIGNED", "remark": "8372-verify", "groupCode": "A"},
|
||||||
|
{"dispatchId": "2103736241830924290", "vehicleId": "2065329516997156865", "vehiclePlate": "蒙C08E08", "vehicleModel": "丰田考斯特", "driverId": "2089691294107361282", "driverName": "P3测试司机17", "driverPhone": "139****0017", "status": "ASSIGNED", "remark": "8372-verify", "groupCode": "B"}
|
||||||
|
],
|
||||||
|
"vehicleCount": 2,
|
||||||
|
"dispatched": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"missingDates": [],
|
||||||
|
"orders": [
|
||||||
|
{"orderId": "2103733822850060289", "orderNo": "HL20260926143003310", "customerName": "8372verify客户A", "headcount": 2, "vehicleControlStatus": "PENDING", "travelRequirementId": "2103735427204894722", "travelRequirementStatus": "PENDING", "transferDeclared": false, "transferArrivalDates": [], "transferDepartureDates": [], "transferPickupCoveredDates": [], "transferDropoffCoveredDates": [], "transferPendingCount": 0},
|
||||||
|
{"orderId": "2103733866504335361", "orderNo": "HL20260926143013865", "customerName": "8372verify客户B", "headcount": 2, "vehicleControlStatus": "PENDING", "travelRequirementId": "2103735431386574850", "travelRequirementStatus": "PENDING", "transferDeclared": false, "transferArrivalDates": [], "transferDepartureDates": [], "transferPickupCoveredDates": [], "transferDropoffCoveredDates": [], "transferPendingCount": 0}
|
||||||
|
],
|
||||||
|
"transferPendingTotal": 0,
|
||||||
|
"conversationKey": "GROUP_FLEET:2103733823122690049"
|
||||||
|
},
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 空数据 / 降级响应
|
||||||
|
|
||||||
|
某行程日没有任何存活派车行时,该日在 `days[]` 里仍占一行,`vehicles` 为空数组、`dispatched=false`,并计入 `missingDates`;不会因此报错,也不影响其它日期的正常返回。团期整体不可达(团期不存在、order-v3 侧基线查询失败或降级)时整口失败关闭,返回 600012「团期配车基线不可达,请稍后重试」(`GroupDispatchErrorCode.java:95`),不会返回空 `days[]`;前端须按失败提示处理,**不要渲染成「该团没有配车需求」**(`GroupDispatchQueryController.java:90`)。此行为本次未改动。
|
||||||
|
|
||||||
|
#### 错误响应
|
||||||
|
|
||||||
|
> 团期配车基线不可达(`GroupDispatchErrorCode.java:95`,失败关闭,HTTP 状态 200)。
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"code": 600012, "message": "团期配车基线不可达,请稍后重试", "data": null, "success": false}
|
||||||
|
```
|
||||||
|
|
||||||
|
> 网关鉴权失败的通用响应(`JwtAuthFilter.java:394`,HTTP 状态按项目铁律固定 200,业务码 401)。
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"code": 401, "message": "缺少有效的 Authorization 头", "data": null, "success": false}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 业务边界
|
||||||
|
|
||||||
|
- `groupCode` 原样取自 `fleet_group_dispatch.group_id`,不做转换或聚合;写口入参字段名是 `groupId`,读口字段名是 `groupCode`,同值同源(`GroupDispatchOverviewVehicleVO.java:59-70`)。
|
||||||
|
- 存量未分组行 `groupCode` 为 `null`,不回填默认组;这类行提交 reconfigure 时若未先补选分组,会在参数校验层被 `@NotBlank` 拦截(400「乘车分组不能为空」),不会进入服务层业务码判断。
|
||||||
|
- 回提映射经测试服实测:按 overview 原样重组回提,差量为零(`addedCount`/`removedCount`/`updatedCount` 均为 0,`idempotentShortCircuit=true`)。
|
||||||
|
- 若回提时删除某乘车分组在某日仅有的一辆车、导致该组该日出现覆盖缺口,会触发 reconfigure 既有的覆盖校验,返回 602003「乘车分组 X 的服务日未排满, 缺失: YYYY-MM-DD」(测试服实测)——这是 reconfigure 早已存在的门禁,不是本次改动新增的行为。
|
||||||
|
- `serviceDates` 权威口径来自 order-v3 单团覆盖口,fleet 不按 `departDate..endDate` 自行铺日期,避免总览与 reconfigure 的覆盖校验形成两套口径(`GroupDispatchOverviewRespVO.java:14-19`)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、契约约束与正确调用方式
|
||||||
|
|
||||||
|
### 与 reconfigure 的正确回提映射
|
||||||
|
|
||||||
|
overview 每行 `days[].vehicles[].groupCode` 对应 reconfigure 入参 `demands[].assignments[].groupId`(`GroupDispatchAssignmentReqVO.java:20-24`,`@NotBlank`);`vehicleId`/`driverId`/`remark` 同样原样回填。reconfigure 是全量替换语义,请求里缺席的 (日期, 车) 会被判定为软删——前端必须把 overview 读到的每一行都带回,漏一行就是删一行。
|
||||||
|
|
||||||
|
实测原样回提请求(对应上文「响应示例」同一批夹具,`requirementId`/`requirementVersion` 取自 `GET .../readiness`):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"requirementId": 2103735470083264513,
|
||||||
|
"requirementVersion": 2,
|
||||||
|
"clearAll": false,
|
||||||
|
"demands": [
|
||||||
|
{
|
||||||
|
"tripDate": "2027-03-15",
|
||||||
|
"assignments": [
|
||||||
|
{"groupId": "A", "vehicleId": 2065329514971308033, "driverId": 2089691297869651969, "remark": "8372-verify"},
|
||||||
|
{"groupId": "B", "vehicleId": 2065329516997156865, "driverId": 2089691294107361282, "remark": "8372-verify"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tripDate": "2027-03-16",
|
||||||
|
"assignments": [
|
||||||
|
{"groupId": "A", "vehicleId": 2065329514971308033, "driverId": 2089691297869651969, "remark": "8372-verify"},
|
||||||
|
{"groupId": "B", "vehicleId": 2065329516997156865, "driverId": 2089691294107361282, "remark": "8372-verify"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tripDate": "2027-03-17",
|
||||||
|
"assignments": [
|
||||||
|
{"groupId": "A", "vehicleId": 2065329514971308033, "driverId": 2089691297869651969, "remark": "8372-verify"},
|
||||||
|
{"groupId": "B", "vehicleId": 2065329516997156865, "driverId": 2089691294107361282, "remark": "8372-verify"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
对应响应确认零差量:`addedCount=0`/`removedCount=0`/`updatedCount=0`/`keptCount=6`/`aliveCount=6`/`idempotentShortCircuit=true`。
|
||||||
|
|
||||||
|
若 overview 中某行 `groupCode` 为 `null`,需先引导车务选择分组再回填 `groupId`,否则 reconfigure 返回 400「乘车分组不能为空」(`@NotBlank` 校验先于服务层业务码触发)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、数据库行为
|
||||||
|
|
||||||
|
无本次 DDL 变更。`groupCode` 直接读取既有列 `fleet_group_dispatch.group_id`(该列由更早的迁移引入,历史行未回填、值为 `NULL`),本次改动只是让这个已存在的列首次通过 overview 出参外露,不涉及表结构变更或数据回填。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、边界行为
|
||||||
|
|
||||||
|
- 网关鉴权失败(缺少/失效 Authorization)→ 业务码 401,HTTP 状态仍为 200。
|
||||||
|
- 团期不存在或 order-v3 基线不可达 → 600012「团期配车基线不可达,请稍后重试」,不返回空数据。
|
||||||
|
- `groupCode` 为 `null` 的行按 `null` 原样返回,不做默认值兜底(`GroupDispatchOverviewVehicleVO.java:64`:「存量行此列为 NULL 且不回填,这里原样透出 null,不补空串或默认组」)。
|
||||||
|
- 同一辆车在不同日可属不同分组,按行独立取值,overview 不做跨日聚合或去重。
|
||||||
|
- 空洞日(无存活派车行的服务日)在 `days[]` 里仍占一行,`vehicles=[]`、`dispatched=false`,同时计入 `missingDates`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六.6、修改前后对比
|
||||||
|
|
||||||
|
### 出参字段对比
|
||||||
|
|
||||||
|
| 字段 | 改前 | 改后 |
|
||||||
|
|------|------|------|
|
||||||
|
| `days[].vehicles[].groupCode` | 无此字段 | **新增**:String,乘车分组键,原样取自 `fleet_group_dispatch.group_id`,存量未分组行为 `null` |
|
||||||
|
|
||||||
|
### 行为对比
|
||||||
|
|
||||||
|
| 行为 | 改前 | 改后 |
|
||||||
|
|------|------|------|
|
||||||
|
| overview → reconfigure 回提 | 前端拿不到分组键,需要自行维护「车辆→分组」映射表才能拼出 `assignments[].groupId` | 直接用 `groupCode` 原样回填,无需自建映射表 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六.7、影响评估
|
||||||
|
|
||||||
|
- **是否破坏向后兼容**:否。仅新增一个出参字段,既有字段语义、类型均未变,历史客户端可直接忽略新字段。
|
||||||
|
- **前端是否必须同步上线**:否。`groupCode` 是可选回显字段,前端不接入仍可正常工作;若前端此前已自行维护一份「车辆→分组」映射表来拼 reconfigure 请求,接入后可直接用响应字段替代那份映射表,避免两处维护不同步的风险。
|
||||||
|
- **覆盖范围**:本字段只出现在总览读口的出参里,reconfigure 写口的入参/出参契约(含 `groupId` 字段本身的必填与长度校验)未发生变化。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 七、不影响范围
|
||||||
|
|
||||||
|
- **仅影响**:团期派车总览 `GET .../overview` 的出参新增一个字段。
|
||||||
|
- **零影响**:
|
||||||
|
- reconfigure 写口契约(入参/出参结构、错误码均未变)。
|
||||||
|
- 待配车团期清单 `GET .../pending-batches`、资源排班等其他读口。
|
||||||
|
- 车务其它模块(含 #8371 涉及的单车取消逻辑)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 八、测试环境已验证
|
||||||
|
|
||||||
|
部署:PR #8378 已合并 `dev-v3`(`f8b546251`),测试服 hl-fleet-service 已部署 `c2bc5c3d4`(含 `f8b546251`);本单不改 order-v3。
|
||||||
|
|
||||||
|
**批次一(既存真实数据)**:`groupBatchId=2101880394750328833`,GET overview 9 条 vehicles(3 天 × 3 组)逐条核对均带 `groupCode`(GA/GB/GC),与改前基线(9 条均无该字段)对照修复生效;该批次底层需求已到 `DONE` 终态,零变更重提子项在此批次上不可达(非缺陷,既存数据状态限制)。附加 null 探测:`groupId=null` 返回 400「乘车分组不能为空」,先于 602006 业务码触发。
|
||||||
|
|
||||||
|
**批次二(自建全新夹具)**:`groupBatchId=2103733823122690049`,全程停留在 `CONFIRMED`(未推进到 `DONE`),补齐批次一未能验证的子项:
|
||||||
|
- 零变更重提:`addedCount`/`removedCount`/`updatedCount` 均为 0,`idempotentShortCircuit=true`。
|
||||||
|
- 删行/加行往返:先给组 A 追加 1 辆备用车(`addedCount=1`),再删除该行(`removedCount=1`,2027-03-15 的 `vehicleCount` 从 3 降为 2,其余两日不受影响),最后加回(`addedCount=1`,`aliveCount` 恢复到 7)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 十、相关文档
|
||||||
|
|
||||||
|
- 关联 Issue: [wx/HL#8372](https://git.1814.love/wx/HL/issues/8372)
|
||||||
|
- 关联 PR: [wx/HL#8378](https://git.1814.love/wx/HL/pulls/8378)
|
||||||
|
|
||||||
|
## 关联 / 联系人
|
||||||
|
|
||||||
|
### 链接
|
||||||
|
|
||||||
|
- **Issue**: [#8372](https://git.1814.love/wx/HL/issues/8372)
|
||||||
|
- **PR**: [#8378](https://git.1814.love/wx/HL/pulls/8378)
|
||||||
|
- **Merge commit**: [f8b546251](https://git.1814.love/wx/HL/commit/f8b546251)
|
||||||
|
|
||||||
|
### 联系人
|
||||||
|
|
||||||
|
- **后端负责人**: @wx
|
||||||
|
- **前端负责人**: @mmg
|
||||||
在新工单中引用
屏蔽一个用户