文件
hl-api-changelog/changelogs-v2/2026-09/26_8372_团期派车总览新增乘车分组编码字段-修改接口-管理后台.md
T
Mimingguang 1a396aece5
changelog-filename-gate / validate (push) Failing after 1s
chore(changelog): #8372 前端回写 verified(mmg,ddd49b07,v2.1)
2026-09-26 19:39:17 +08:00

348 行
20 KiB
Markdown
原始文件 Blame 文件历史

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
---
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: "verified"
frontend_owner: "mmg"
frontend_ref: "ddd49b07d332b753a4513f01f63ed2fc9495f888"
target_release: "v2.1"
verified_at: "2026-09-26"
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)与删行/加行往返。 前端 2026-09-26 已交付(hl-admin ddd49b07,用户拍板回显闭环本期做):PlanEditor 新增 overview prop 按 groupCode+tripDate 回显现行计划(车辆/司机/备注,标签取车牌司机名);groupCode=null 历史行点名列出+提交前二次确认(全量替换语义);OverviewDrawer 传入 overview;api JSDoc 补 groupCode/窗外行口径。PlanEditor spec 11 例全绿(新增回显 3 例)。"
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`)。
- **总览不含服务日窗外的配车行**:`days[]` 只按 `serviceDates` 逐日铺开(`GroupDispatchQueryService.java:558`),`tripDate` 不在 `serviceDates` 内的存活配车行(例如需求改期后遗留在旧日期上的行)不会出现在总览里,因此按总览重组的 reconfigure 请求也不含这些行。reconfigure 是全量替换语义,回提后这些行会被软删;若本次提交处于重开窗口授权下,则会被 602013「本次配车改动越出重开窗口授权范围: {0}」拒绝(`GroupDispatchService.java:432`)。这是既有的全量替换语义与总览铺日口径叠加的结果,本次未改动。
---
## 四、契约约束与正确调用方式
### 与 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