changelog-filename-gate / validate (push) Failing after 2s
原文按真实行路径过度概括成「两张卡 requirementId 恒相同」。实测代码:MatrixService.java:497-498 真实行优先取订单侧单值(两卡相同),:637-638 虚拟待派条目优先取候选自身需求 ID(两卡不同),而未派订单最常见的形态正是后者。四处(正文口径、出参表、业务边界、测试说明)一并按路径分档,判类别只认 requirementKind 的结论不变、且更必要。 Refs #8560 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
238 行
16 KiB
Markdown
238 行
16 KiB
Markdown
---
|
||
schema: "hl-changelog/v2"
|
||
ticket: "8560"
|
||
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: "PR #8589 合并 dev-v3(8b045a321b);测试网关部署确认:hl-fleet-service 现部署 @ 99fb369ba(deploy-status.sh 实测,状态 ok,该 SHA 经 git merge-base --is-ancestor 确认已包含 8b045a321b)。GET /admin/fleet/matrix/unassigned-orders 已用车务角色测试账号实测:2026-09 月拿到 TRAVEL 示例(订单 HL20260911193207642)、2026-11 月拿到 TRANSFER 示例(订单 HL20260929154809598),均为测试服真实响应;对 2026-06~2027-03 共 10 个月窗口扫描未发现 requirementKind=null 或同订单双卡的活跃实例,这两种边界行为当前仅由单元测试覆盖(BoardRequirementIdentitiesKindTest 5/5、MatrixServiceTest 新增 6 个 #8560 方法),尚未在测试服活数据上复现。"
|
||
updated_at: "2026-09-30"
|
||
base: "dev-v3"
|
||
---
|
||
|
||
# hl-fleet-service:矩阵未派订单清单响应新增用车需求类别字段,同订单双需求两张卡可区分
|
||
|
||
> **存放目录**: `changelogs-v2/2026-09/`
|
||
> **服务**: hl-fleet-service
|
||
> **PR**: #8589
|
||
> **Issue**: #8560
|
||
> **日期**: 2026-09-30
|
||
> **影响范围**: 1 个只读端点响应新增 2 字段(矩阵未派订单清单)
|
||
|
||
---
|
||
|
||
## ⚠️ 关键变化
|
||
|
||
- `GET /admin/fleet/matrix/unassigned-orders` 响应每条记录新增 `requirementKind`(`TRAVEL`/`TRANSFER`/`null`)与 `requirementKindLabel`(`行程用车`/`接送机`/`null`),两者恒成对(一个为 null 另一个必为 null)。
|
||
- 背景(#7439):同一订单可并存两条活跃用车需求(行程用车 TRAVEL + 接送机 TRANSFER),车务分别对两者派车,未派池会出现同一订单的两张卡。此前两张卡除车型/日期外没有任何字段能分辨谁是哪一类——既有字段 `vehicleCategory`/`categoryLabel` 是车型(suv/bus)不是需求类别。新增这两个字段就是用来分辨这两张卡的。
|
||
- 🔴 **`null` 不兜底成 `TRAVEL`**,这是本次修复的核心边界。判不出类别(跨服务降级 context=null;或派车行挂着 #5720 换版过渡窗里的上一版 `requirement_id`,命中不了任何当前活跃身份;或命中的身份自身 `kind` 为空白)时两个新字段均为 `null`,前端应不显示类别标签,**禁止自行按业务猜测补默认值**——尤其禁止把 `null` 当 `TRAVEL` 处理。
|
||
- 与看板列表(`BoardOrderRecordVO.requirementKind`,#8518 既有)**在判不出这一档口径不同**:看板列表的解析方法判不出时兜底返 `TRAVEL`(那里类别同时是筛选维度,返空会让卡片从筛选后的视图里彻底消失);矩阵未派卡判不出时返 `null`(那里类别只是展示标签,车务会照标签去排完全不同的活,标错比不标更危险)。**同一张实体卡在两个入口可能显示不一致的类别信息,这是刻意保留的差异**,不是缺陷。
|
||
- 真实未派行与虚拟待派条目(`virtualPending=true`,#7067)两类条目都携带这两个新字段,取值口径一致。
|
||
- 类别取的是**这张卡自身所属需求**(真实行用该行自己的 `requirement_id`,虚拟条目用该候选自己的 `requirementId`)解析出的类别,**不是**已有字段 `requirementId`(该字段取「订单侧单值」,#5667 口径,同一订单两类需求并存时恒指向身份列表首项、即恒为 TRAVEL 那条)。⚠️ `requirementId` 字段两张卡是否相同**取决于这张卡走哪条路径**:`virtualPending=true`(未派池的虚拟待派条目,未派订单最常见的形态)下它取该候选自身的需求 ID,两张卡**不相同**;`virtualPending=false`(已有派车行的真实行)下它优先取订单侧单值,两张卡**相同**。两条路径都不能拿 `requirementId` 判类别——相同时它分辨不出,不同时它也只是碰巧对得上。判类别一律只认 `requirementKind`/`requirementKindLabel`。
|
||
|
||
---
|
||
|
||
## 二、变更接口清单
|
||
|
||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||
|---|------|------|------|----------|------|
|
||
| 1 | 矩阵未派订单清单 | GET | `/admin/fleet/matrix/unassigned-orders` | 字段新增(非破坏性) | 响应新增 `requirementKind`/`requirementKindLabel` |
|
||
|
||
---
|
||
|
||
## 三、接口详情
|
||
|
||
### 1. 矩阵未派订单清单 `GET /admin/fleet/matrix/unassigned-orders`
|
||
|
||
**VO**: `MatrixUnassignedReqVO → List<MatrixUnassignedOrderVO>`
|
||
|
||
#### 使用场景
|
||
派单矩阵页右面板「未派订单窗口」,车务从此列表拖拽卡片到左面板某车某日完成派车;同一订单若同时有行程用车与接送机两条活跃需求,会在此列表出现两张卡,车务需要靠新增的类别字段区分要往哪类需求上派车,不能再靠车型/日期/备注这类间接信息猜。
|
||
|
||
#### 入参字段表
|
||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||
|------|------|------|------|------|------|
|
||
| year | query | Integer | 是 | 2020-2100,越界返 605076 | 年份 |
|
||
| month | query | Integer | 是 | 1-12,越界返 605010 | 月份 |
|
||
| typeKeys | query | String[] | 否 | 取值 suv/mpv/bus/sedan,规范小写 | 车型大类多选,空=全部;对虚拟待派条目按当前需求车型明细任一项归一后命中过滤 |
|
||
|
||
#### 出参字段表(仅列本次新增字段及理解其语义所需的上下文字段,VO 全量共 39 个字段)
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| requirementKind | String | **新增**。用车需求类别:`TRAVEL`=行程用车 / `TRANSFER`=接送机 / `null`=判不出(不兜底为 TRAVEL) |
|
||
| requirementKindLabel | String | **新增**。类别中文名:`行程用车`/`接送机`/`null`,与 requirementKind 恒成对 |
|
||
| requirementId | Long(字符串序列化) | 既有字段,这张卡对应的用车需求 ID。`virtualPending=false` 时优先取订单侧单值(#5667,两类并存时恒指向 TRAVEL 那条);`virtualPending=true` 时取该候选自身的需求 ID。**两条路径取值口径不同,一律不能用它推导 requirementKind** |
|
||
| assignmentId | Long(字符串序列化) | 既有字段,本行唯一主键;虚拟待派条目为 null |
|
||
| virtualPending | Boolean | 既有字段,true=虚拟待派条目(零派车行订单,按需求上下文补出) |
|
||
| vehicleCategory | String | 既有字段,规范小写车型 key(suv/mpv/bus/sedan),与需求类别是两个不同维度 |
|
||
| categoryLabel | String | 既有字段,车型中文标签(恒非 null) |
|
||
| orderId / orderNo | String | 既有字段,订单号 |
|
||
| teamNo | String | 既有字段,团号 |
|
||
|
||
#### 请求示例
|
||
```http
|
||
GET /admin/fleet/matrix/unassigned-orders?year=2026&month=11&typeKeys=mpv
|
||
```
|
||
|
||
#### 响应示例
|
||
实测取自测试服真实数据(2026-11 月,TRANSFER 示例):
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "success",
|
||
"data": [
|
||
{
|
||
"orderId": "HL20260929154809598",
|
||
"orderNumericId": "2104840641597030402",
|
||
"orderNo": "HL20260929154809598",
|
||
"teamNo": "26-3682",
|
||
"virtualPending": true,
|
||
"assignmentId": null,
|
||
"assignmentGroupId": null,
|
||
"requirementId": "2104844928733548545",
|
||
"requirementKind": "TRANSFER",
|
||
"requirementKindLabel": "接送机",
|
||
"vehicleCategory": "mpv",
|
||
"categoryLabel": "商务车",
|
||
"customerName": "董海涛",
|
||
"headcount": 2,
|
||
"headcountLabel": "2大",
|
||
"startDate": "2026-11-11",
|
||
"endDate": "2026-11-17",
|
||
"pickupAt": "阿尔山伊尔施机场",
|
||
"dropoffAt": "阿尔山伊尔施机场",
|
||
"assignmentStatus": "unassigned",
|
||
"urgentBadge": null,
|
||
"vehicleAdvice": null,
|
||
"parallelAssignments": []
|
||
}
|
||
]
|
||
}
|
||
```
|
||
对照:2026-09 月同一账号实测取到的 TRAVEL 示例(订单 `HL20260911193207642`),响应结构完全相同,仅 `requirementKind="TRAVEL"`、`requirementKindLabel="行程用车"`、`requirementId="2098950695167148034"`。两个示例均为测试服真实取数,未做任何字段改写。
|
||
|
||
#### 空数据 / 降级响应
|
||
- 当月无未派条目:`data: []`,非错误。
|
||
- `vehicleAdvice` 恒为 `null`(M2 数据源未建,既有降级行为,与本次改动无关)。
|
||
- Nacos `fleet.board.virtual-candidates-enabled=false` 或 order-v3 候选服务不可用时:只丢虚拟待派条目,真实未派行原样返回(fail-open);真实行的 `requirementKind` 解析走独立的上下文查询,不受此开关影响。
|
||
- `requirementKind`/`requirementKindLabel` 判不出时为 `null`(见「⚠️ 关键变化」),这不是接口异常,是正常的降级取值,前端应按无标签渲染,不得折算为 `TRAVEL`。
|
||
|
||
#### 错误响应
|
||
```json
|
||
{
|
||
"code": 605010,
|
||
"message": "月份超出范围",
|
||
"data": null
|
||
}
|
||
```
|
||
```json
|
||
{
|
||
"code": 605076,
|
||
"message": "年份超出范围(仅支持 2020-2100 年)",
|
||
"data": null
|
||
}
|
||
```
|
||
- `100001` 参数非法:year/month 缺失(框架校验)。
|
||
- `401` 未登录。
|
||
|
||
#### 业务边界
|
||
- **同一订单两类需求并存时,两张卡的 `requirementKind`/`requirementKindLabel` 必不相同;而 `requirementId` 字段是否相同取决于路径**——真实行(`virtualPending=false`)下两张卡相同(均取订单侧单值),虚拟待派条目(`virtualPending=true`)下两张卡各取自身需求 ID、并不相同。单元测试 `MatrixServiceTest#queryUnassignedOrders_orderWithBothKinds_twoCardsCarryDifferentKinds` 断言的是**真实行**那条路径。两条路径都不能拿 `requirementId` 反推类别,前端也不能这么做。
|
||
- `requirementKind=null` 时前端**禁止**折算成 `TRAVEL`;这既是判不出的真实状态,也是修复前的错误行为,回退等于复发。
|
||
- 矩阵未派卡与看板列表对同一张孤儿行(#5720 换版过渡窗)的类别展示口径不同(前者 null、后者兜底 TRAVEL),这是刻意保留的差异,不要据此判断某一端有 bug。
|
||
- 真实未派行与虚拟待派条目两种类型都下发这两个字段,前端不需要按 `virtualPending` 分支处理类别逻辑。
|
||
|
||
---
|
||
|
||
## 四、契约约束与正确调用方式
|
||
|
||
- 判类别只认 `requirementKind`/`requirementKindLabel` 这两个新字段,不要用 `requirementId` 做二次推导。
|
||
- `requirementKind` 取值集合当前为 `{TRAVEL, TRANSFER, null}`,前端不应写死「非 TRANSFER 即 TRAVEL」的二值判断——若未来 order-v3 新增第三类需求,后端会同步扩展该字段取值与中文映射,二值判断会把新类别误标成 TRAVEL。
|
||
- 类别中文名由后端下发,前端不需要、也不应该自行维护 `TRAVEL`/`TRANSFER` 到中文的映射表。
|
||
|
||
---
|
||
|
||
## 五、数据库行为
|
||
|
||
无数据库结构变更。本次改动只是查询层新增两次内存解析(基于已查出的订单需求上下文按 `requirement_id` 匹配),不新增表、不新增列、不新增索引,无 Flyway 迁移。
|
||
|
||
---
|
||
|
||
## 六、边界行为
|
||
|
||
- 上下文降级(跨服务 Feign 调用失败,`OrderFleetBoardContextDTO` 为 `null`):类别字段为 `null`。
|
||
- 派车行/候选自身的 `requirement_id` 命中不到订单当前任何活跃需求身份(#5720 换版过渡窗孤儿行):类别字段为 `null`,不回退用订单上下文单值猜测。
|
||
- 命中的身份自身 `kind` 字段为空白:类别字段为 `null`(防御性分支;order-v3 当前写路径恒写枚举 `.name()`,正常不触发,仅覆盖历史/异常数据)。
|
||
- 以上三种 `null` 场景均只有单元测试覆盖(见八节),本次实测扫描未在测试服活数据中观测到对应真实记录。
|
||
|
||
---
|
||
|
||
## 六.5、枚举 / 数据字典
|
||
|
||
| 取值 | 中文标签 | 说明 |
|
||
|------|----------|------|
|
||
| TRAVEL | 行程用车 | 行程用车需求 |
|
||
| TRANSFER | 接送机 | 接送机需求(#7439 引入) |
|
||
| null | (不显示标签) | 判不出类别,前端不得兜底为 TRAVEL |
|
||
|
||
---
|
||
|
||
## 六.6、修改前后对比
|
||
|
||
- **字段层面**:`MatrixUnassignedOrderVO` 新增 `requirementKind`(String)、`requirementKindLabel`(String),VO 字段总数由 37 增至 39。
|
||
- **行为层面**:改动前,同一订单的两张未派卡在字段层面完全无法区分类别,只能靠车型/日期/备注人工判断,判断错了会把车派到错误的需求线上;改动后两张卡各自携带准确的类别标识,且判不出时明确返回 null 而非静默给出错误猜测。
|
||
|
||
---
|
||
|
||
## 六.7、影响评估
|
||
|
||
- 破坏性:无。两个新增字段为可选新增,未删除/未重命名/未改变任何既有字段的类型或取值口径。
|
||
- 涉及消费端:仅管理后台派单矩阵页。
|
||
- 前端无需为此做兼容降级处理:未取到新字段(`undefined`)与取到 `null` 应做同等处理——均不显示类别标签。
|
||
|
||
---
|
||
|
||
## 七、不影响范围
|
||
|
||
- 矩阵主数据端点 `GET /admin/fleet/matrix/grid`、年度月度统计 `GET /admin/fleet/matrix/month-counts`、当天订单清单 `GET /admin/fleet/matrix/day-orders`:均未改动。
|
||
- 看板列表端点(`BoardOrderRecordVO.requirementKind`,#8518):未改动,其判不出类别时仍兜底 TRAVEL 的既有行为不变。
|
||
- 写操作(拖拽派车、改派、取消等):本次改动只涉及查询响应字段新增,不涉及任何写路径。
|
||
- `MatrixUnassignedReqVO` 请求参数:未新增/未修改(year/month/typeKeys 均为既有字段,越界错误码路由此前已分别由 #8561/#8571 调整完成)。
|
||
|
||
---
|
||
|
||
## 八、测试环境已验证
|
||
|
||
- **部署确认**:`hl-fleet-service` 现部署 SHA `99fb369ba`(`deploy-status.sh` 实测,状态 `ok`),经 `git merge-base --is-ancestor 8b045a321b 99fb369ba8` 确认已包含本次改动的合并提交 `8b045a321b`(PR #8589)。
|
||
- **实测(真实请求,非构造数据)**:使用车务角色测试账号(切至 VEHICLE_MANAGER 角色)对 `GET /admin/fleet/matrix/unassigned-orders` 发起真实请求:
|
||
- 2026-09 月:2 条记录,`requirementKind` 均为 `TRAVEL`,含示例订单 `HL20260911193207642`(见响应示例节)。
|
||
- 2026-11 月:1 条记录,`requirementKind` 为 `TRANSFER`,订单 `HL20260929154809598`(见响应示例节)。
|
||
- 对 2026-06 ~ 2027-03 共 10 个月窗口的扫描(合计 14 条记录)未发现 `requirementKind=null` 的记录,也未发现同一订单出现两条不同类别记录的活跃实例——测试服当前业务数据里暂未出现这两种边界场景,实测未覆盖,靠下面的单元测试兜底。
|
||
- **单元测试覆盖(源码单测验证,未在测试服活数据上复现)**:
|
||
- `BoardRequirementIdentitiesKindTest`(5/5 通过):覆盖双身份按需求 ID 各取各类别、上下文降级返 null(对照既有方法仍兜底 TRAVEL)、陈旧需求 ID 不猜返 null、身份自身类别空白返 null、灰度上下文合成 TRAVEL 身份仍可取到。
|
||
- `MatrixServiceTest` 新增 6 个 `#8560` 测试方法(均通过):同订单两类需求两张卡类别互不相同(含反向对照:**真实行路径**下两张卡 `requirementId` 字段完全相同;虚拟待派路径不适用该对照)、需求身份类别空白返 null 不兜底 TRAVEL、上下文降级返 null 不兜底 TRAVEL、派车行挂陈旧需求 ID(#5720)返 null 不兜底 TRAVEL、虚拟待派条目携带类别、虚拟待派条目无身份列表时类别为 null。
|
||
- 聚合结果(`mvn -pl hl-fleet-service -am test`):`Tests run: 365, Failures: 0, Errors: 0, Skipped: 0`,`BUILD SUCCESS`;含 `VehicleRequirementKindsTest` 4、`BoardOrderServiceTest` 233、`FleetRedLineArchTest` 18(架构守护门禁绿)。
|
||
- 嵌套用例选择器守卫(`nested_selector_census`):通过,内层名比对无缺组。
|
||
- `spotless:check`:`BUILD SUCCESS`,916 文件全部合规。
|
||
|
||
---
|
||
|
||
## 十、相关文档
|
||
|
||
- Issue #8560
|
||
- PR #8589(合并提交 `8b045a321b`)
|
||
- 相关既有机制:#7439(TRAVEL/TRANSFER 双需求引入)、#8518(看板列表既有类别字段)、#7067(虚拟待派条目/去槽位化)、#5667(`requirementId` 订单侧单值口径)、#5720(换版过渡窗孤儿行)
|
||
|
||
---
|
||
|
||
## 关联 / 联系人
|
||
|
||
- 后端:wx(GIT)
|
||
- 消费端:管理后台(派单矩阵页)
|