文件
hl-api-changelog/changelogs-v2/2026-09/30_8560_矩阵未派订单卡下发用车需求类别可区分行程用车与接送机-修改接口-管理后台.md
T
API Changelog Bot和Claude Opus 5 52548a02a1
changelog-filename-gate / validate (push) Failing after 2s
docs(changelog): 团期配车三项契约变更交接件(#8543 #8548 #8549 #8560)
- #8543 团期订单 Tab 用车状态三处读口按需求类别拆分并统一文案(PR #8592)
- #8548/#8549 整团免车放行户级接送机需求,换组重开团期补待审户读数(PR #8586)
- #8560 矩阵未派订单卡下发 requirementKind,可区分行程用车与接送机(PR #8589)

三份均已在测试网关实测取证,两道门禁(frontmatter 校验 + changelog_workflow lint)全绿。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-30 03:57:16 +08:00

238 行
16 KiB
Markdown
原始文件 Blame 文件历史

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
---
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` 字段取值会相同,但 `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;取订单侧单值(#5667),两类需求并存时恒指向 TRAVEL 那条,**不能**用它推导 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` 未登录。
#### 业务边界
- **同一订单两类需求并存时,两张卡的 `requirementId` 字段值完全相同(均指向 TRAVEL 那条),但 `requirementKind`/`requirementKindLabel` 不同**——单元测试 `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)
- 消费端:管理后台(派单矩阵页)