docs(changelog-v2): 派单看板下发用车需求类别 requirementKind 前端交接(#8518)
changelog-filename-gate / validate (push) Failing after 1s

同一订单行程用车+接送机并存时两张卡逐字段相同、前端无法分辨哪张是接送机;
本次为 GET /admin/fleet/board/orders 的 records 补充 requirementKind/requirementKindLabel,
并给列表与汇总两个读口各加同名可选筛选参数。测试服 hl-fleet-service @ dfb5db832 已实测。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
这个提交包含在:
API Changelog Bot
2026-09-29 18:37:25 +08:00
共同撰写人 Claude Opus 5
父节点 734d06d7b5
当前提交 397bac4fbc
@@ -0,0 +1,379 @@
---
schema: "hl-changelog/v2"
ticket: "8518"
title: "派单看板列表与汇总下发用车需求类别 requirementKind,支持按类别筛选"
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 #8534(fix(fleet): 派单看板列表下发用车需求类别并支持按类别筛,关联 #8518)已合并 dev-v3,滚动部署测试服 hl-fleet-service @ dfb5db832(2026-09-29 17:48:30)。requirementKind/requirementKindLabel 两字段与同名可选筛选参数已实测:基线 47 条,TRAVEL 37/TRANSFER 10,两档相加等于基线且交集为空,requirementId 集合与基线一致;非法值返 100001。"
updated_at: "2026-09-29"
base: "dev-v3"
---
# hl-fleet-service: 派单看板下发用车需求类别 `requirementKind`
**服务**: hl-fleet-service
**PR**: #8534(已合入 `dev-v3`,squash `dfb5db832`)
**Issue**: #8518
**日期**: 2026-09-29
**影响范围**: 管理后台车务「派单看板」的列表与汇总两个读口
---
## ⚠️ 关键变化
- 派单看板「团期订单 / 全部订单」列表上,同一订单若同时存在行程用车与接送机两条用车需求,会出**两张卡**——两张卡的订单号、团号、客户、定制师、行程日期、人数**逐字相同**,此前没有任何字段能分辨哪张是接送机。
- `GET /admin/fleet/board/orders` 的 `data.records[]` **新增 `requirementKind` / `requirementKindLabel` 两个字段**,恒成对非空:`requirementKind` 取值 `TRAVEL`(行程用车)/ `TRANSFER`(接送机),`requirementKindLabel` 是对应中文标签,由后端下发,前端不要自己做 `kind → 中文` 的映射。
- `GET /admin/fleet/board/orders` 与 `GET /admin/fleet/board/summary` **新增同名可选查询参数 `requirementKind`**,两个接口共用同一入参 VO。不传或传空串 = 不过滤,两类都返。
- `requirementKind` 与既有的 `orderKind` 是**两个互不相交的维度**:`orderKind` 分订单归属(`ALL`/`NORMAL`/`GROUP`),`requirementKind` 分需求类别。两者可同传按 AND 组合,串用不会报错,只会筛出错误的行数,详见「四、契约约束与正确调用方式」。
- **非法取值不被静默容忍**:非 `TRAVEL`/`TRANSFER` 且非空一律 **HTTP 200 + body `code=100001`**,`data=null`、`success=false`。
- `GET /admin/fleet/board/summary` 的 `statusCounts` 与 `statusOptions[].count` 随 `requirementKind` **一起收窄**;`idleVehicleCount` / `idleDriverCount` 是全局物理资源指标,不受该筛选影响。
- **`statusCounts` 里的 `unassignedUrgent` / `holdingUrgent` 是 `unassigned` / `holding` 的子集**(`statusOptions` 里以 `urgentCount` 形式出现),不是独立状态桶,前端加总时不要重复计入。
---
## 一、背景
车务派单看板存在同订单出两张卡、字段完全相同、无法分辨哪张是接送机的问题(wx/HL#8518)。本次为每条 record 补充需求类别下发(`requirementKind`/`requirementKindLabel`),并给列表与汇总两个读口各加一个同名可选筛选参数,用于把两类需求分列展示或过滤。
---
## 二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|------|------|------|----------|------|
| 1 | 派单看板列表 | GET | `/admin/fleet/board/orders` | 新增出参字段 + 可选入参 | 新增 `requirementKind`/`requirementKindLabel` 出参字段;新增可选筛选参数 `requirementKind`,非法值返 `100001` |
| 2 | 派单看板汇总 | GET | `/admin/fleet/board/summary` | 新增可选入参 | 新增可选筛选参数 `requirementKind`(与列表共用同一入参 VO),`statusCounts`/`statusOptions[].count` 随之收窄 |
---
## 三、接口详情
### 1. 派单看板列表 `GET /admin/fleet/board/orders`
**VO**: `BoardOrderPageReqVO → BoardOrderRecordVO`
#### 使用场景
车务「派单看板」主列表。同一订单同时存在行程用车与接送机两条用车需求时会各出一张卡,此前两卡逐字段相同、无法分辨。本次每条 record 补充需求类别,前端可据此区分两张卡,或用新增的 `requirementKind` 查询参数直接按类别筛选。
#### 入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| requirementKind | Query | String | 否 | `TRAVEL` / `TRANSFER`;其余非空值返 `100001` | 🆕 本次新增。用车需求类别筛选:`TRAVEL`=只看行程用车,`TRANSFER`=只看接送机。不传或传空串=不过滤,两类都返。与既有 `orderKind`(订单归属维度)互不相交,可同传按 AND 组合 |
#### 出参字段表
| 字段 | 类型 | 说明 |
|------|------|------|
| data.records[].orderId | String | 订单 ID |
| data.records[].requirementId | String | 用车需求 ID |
| data.records[].teamNo | String | 团号 |
| data.records[].virtualPending | Boolean | 是否为还没有任何派车行的虚拟待派卡片 |
| data.records[].requirementKind | String | 🆕 本次新增。用车需求类别:`TRAVEL` 行程用车 / `TRANSFER` 接送机。取本条记录**所属需求自身**的类别,恒非空 |
| data.records[].requirementKindLabel | String | 🆕 本次新增。类别中文标签:`行程用车` / `接送机`。由后端下发,前端不要自己做 `kind→中文` 的映射,与 `requirementKind` 恒成对非空 |
#### 请求示例
```http
GET /admin/fleet/board/orders?orderKind=ALL&pageSize=100&requirementKind=TRANSFER HTTP/1.1
Host: <测试服网关>
Authorization: Bearer <token>
(GET 无请求体)
```
#### 响应示例
以下为按 `requirementKind=TRANSFER` 过滤后(实测命中 10 条)摘录其中 1 条,仅列本次相关字段与几个已知存在的字段(完整响应还含既有其余字段,本文档未逐一核对不重复列出);`orderId` 为 2026-09-29 17:48 测试服实测命中的真实并存订单之一(该订单同时存在 TRAVEL、TRANSFER 两条记录),`requirementId`/`teamNo` 为示意值:
```json
{
"code": 200,
"message": "成功",
"data": {
"records": [
{
"orderId": "2101566624467419137",
"requirementId": "<示意值,真实用车需求 ID>",
"teamNo": "<示意值,真实团号>",
"virtualPending": false,
"requirementKind": "TRANSFER",
"requirementKindLabel": "接送机"
}
],
"total": 10,
"page": 1,
"pageSize": 100
},
"traceId": null,
"success": true
}
```
#### 空数据 / 降级响应
本次测试窗口内 `requirementKind=TRAVEL`(37 条)与 `requirementKind=TRANSFER`(10 条)均非空,未专门验证 0 命中场景。`requirementKind` 非法取值走「错误响应」,不属于本节的空数据场景。
order-v3 整体不可达、取不到需求身份时的降级口径:`requirementKind` 回退 `TRAVEL`,与该场景下特殊诉求/备注回退快照同属既有降级口径。
#### 错误响应
`requirementKind` 非法(非 `TRAVEL`/`TRANSFER` 且非空),2026-09-29 测试服实测原文:
```json
{
"code": 100001,
"message": "参数非法: requirementKind 仅支持 TRAVEL/TRANSFER,传入非法值:BOGUS",
"data": null,
"traceId": null,
"success": false
}
```
HTTP 状态行仍是 **200**,判据在 body 的 `code` / `success`,不要只看状态码。
#### 业务边界
- 真实卡与**虚拟待派卡**(`virtualPending=true`)一律非空——实测 47 条里 10 条虚拟待派卡两字段全部有值。
- **纯接送机订单**(没有 active 行程用车需求)如实返 `TRANSFER`,不受「顶层 `requirementId` 恒指 `TRAVEL`」那条既有契约影响。
- 类别取自卡片自身归属的那条需求,不读订单级单值字段——单值恒取身份列表首项(两类并存时是 `TRAVEL`)。
- order-v3 降级取不到需求身份时回退 `TRAVEL`(与该场景下特殊诉求/备注回退快照同属既有降级口径)。
- `requirementKind` 与 `orderKind` 是两个互不相交的维度,串用不会报错,只会筛出错误的行数,详见「四、契约约束与正确调用方式」。
---
### 2. 派单看板汇总 `GET /admin/fleet/board/summary`
**VO**: `BoardOrderPageReqVO → BoardSummaryVO`
#### 使用场景
派单看板顶部的状态页签计数来源,与列表接口共用同一套筛选参数(同一入参 VO)。切换需求类别筛选时要和列表接口同步传同一个 `requirementKind`,否则会出现「列表条数与状态页签计数对不上」的界面表现。
#### 入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| requirementKind | Query | String | 否 | `TRAVEL` / `TRANSFER`;其余非空值返 `100001` | 🆕 本次新增,与列表接口同名同取值、同缺省语义,两接口共用同一入参 VO `BoardOrderPageReqVO` |
#### 出参字段表
| 字段 | 类型 | 说明 |
|------|------|------|
| data.statusCounts | Object | 各状态桶计数,随 `requirementKind` 一起收窄 |
| data.statusCounts.unassigned | Integer | 待派车状态桶计数,随 `requirementKind` 收窄 |
| data.statusCounts.unassignedUrgent | Integer | `unassigned` 的**子集**(加急),不是独立状态桶 |
| data.statusCounts.holding | Integer | 排车中状态桶计数,随 `requirementKind` 收窄 |
| data.statusCounts.holdingUrgent | Integer | `holding` 的**子集**(加急),不是独立状态桶 |
| data.statusOptions[].count | Integer | 状态下拉选项计数,随 `requirementKind` 一起收窄,与 `statusCounts` 同口径 |
| data.statusOptions[].urgentCount | Integer | 该状态下的加急子集计数 |
| data.idleVehicleCount | Integer | 空闲车辆数:全局物理资源指标,**不受** `requirementKind` 影响 |
| data.idleDriverCount | Integer | 空闲司机数:全局物理资源指标,**不受** `requirementKind` 影响 |
#### 请求示例
```http
GET /admin/fleet/board/summary?orderKind=ALL&requirementKind=TRAVEL HTTP/1.1
Host: <测试服网关>
Authorization: Bearer <token>
(GET 无请求体)
```
#### 响应示例
以下为字段结构示意,`statusCounts` 内部逐桶数值为**示意拆分**(本次仅验证 `requirementKind=TRAVEL` 六桶之和 = 37,与列表 `total=37` 对齐,未逐桶记录具体读数;真实数值见「八、测试环境已验证」):
```json
{
"code": 200,
"message": "成功",
"data": {
"statusCounts": {
"unassigned": "<示意值,六桶之和已实测=37>",
"unassignedUrgent": "<示意值,unassigned 的子集>",
"holding": "<示意值>",
"holdingUrgent": "<示意值,holding 的子集>"
},
"statusOptions": [
{ "count": "<示意值>", "urgentCount": "<示意值>" }
],
"idleVehicleCount": "<全局值,不随 requirementKind 变化>",
"idleDriverCount": "<全局值,不随 requirementKind 变化>"
},
"traceId": null,
"success": true
}
```
#### 空数据 / 降级响应
本次测试窗口内 `requirementKind=TRAVEL`/`TRANSFER` 两档六桶合计均非空(37/10)。降级口径与列表接口相同:order-v3 整体不可达时 `requirementKind` 判定回退 `TRAVEL`。
#### 错误响应
`requirementKind` 非法取值时与列表接口同一错误码,2026-09-29 测试服实测原文:
```json
{
"code": 100001,
"message": "参数非法: requirementKind 仅支持 TRAVEL/TRANSFER,传入非法值:BOGUS",
"data": null,
"traceId": null,
"success": false
}
```
#### 业务边界
- **`statusCounts` 的 `unassignedUrgent` / `holdingUrgent` 是 `unassigned` / `holding` 的子集**(`statusOptions` 里以 `urgentCount` 出现),**不是独立状态桶**,前端加总统计时不要重复计入。
- `idleVehicleCount` / `idleDriverCount` 是全局物理资源指标,切换 `requirementKind` 时这两个数字不受影响。
- 切换需求类别筛选页签时,务必与列表接口同步传同一个 `requirementKind`,否则会出现列表条数与状态页签计数对不上的情况。
- `requirementKind` 非法取值的错误码、报文格式与列表接口完全一致。
---
## 四、契约约束与正确调用方式
### `requirementKind` 与 `orderKind` 是两个互不相交的维度
| 维度 | `orderKind` | `requirementKind` |
|------|-------------|--------------------|
| 问题 | 这个订单当前属不属于某个运营团期 | 这条用车需求本身是行程用车还是接送机 |
| 取值 | `ALL` / `NORMAL` / `GROUP` | `TRAVEL` / `TRANSFER` |
| 缺省 | 不传或空串 = `ALL`(不过滤) | 不传或空串 = 不过滤(两类都返) |
| 组合方式 | 与 `requirementKind` 按 AND 组合 | 同左 |
两者语义完全独立,**串用不会报错,只会筛出错误的行数**——例如把 `requirementKind` 误传成了 `orderKind` 的取值(如 `orderKind=TRANSFER`),不会命中任何非法校验(`TRANSFER` 不在 `orderKind` 枚举内,会被 `orderKind` 自己的校验拦成 `100001`),但如果误把 `orderKind` 的取值传给 `requirementKind`(如 `requirementKind=GROUP`),同样会被 `requirementKind` 自己的校验拦截,报文里的字段名与传入值都能定位到问题,不会静默放行成一个"看似合理"的过滤结果。
### 非法取值处理
`requirementKind` 非 `TRAVEL`/`TRANSFER` 且非空 → `code=100001`,报文格式固定为 `参数非法: requirementKind 仅支持 TRAVEL/TRANSFER,传入非法值:<原始传入值>`,HTTP 状态行仍是 200,判据在 body。
### 两接口需同步传参
`GET /admin/fleet/board/orders` 与 `GET /admin/fleet/board/summary` 共用同一入参 VO(`BoardOrderPageReqVO`)。切换需求类别页签时必须把 `requirementKind` 同时传给两个接口,否则会出现「列表 10 条、状态页签写着 47 条」这类界面对不上的情况。
---
## 五、数据库行为
本次变更的两个接口都是只读 `GET`,**零数据库写入**,不产生任何落库副作用。`requirementKind` 只影响查询结果的过滤范围,不改写任何行。
---
## 六、边界行为
- 真实卡与虚拟待派卡(`virtualPending=true`)在 `requirementKind`/`requirementKindLabel` 两个新字段上**一律非空**——实测 47 条里 10 条虚拟待派卡两字段全部有值。
- 纯接送机订单(没有 active 行程用车需求)如实返 `TRANSFER`,不受「顶层 `requirementId` 恒指 `TRAVEL`」那条既有契约影响。
- 需求类别取自卡片自身归属的那条需求,不读订单级单值字段——订单级单值字段恒取身份列表首项(两类并存时是 `TRAVEL`)。
- order-v3 整体不可达、取不到需求身份时,`requirementKind` 回退 `TRAVEL`,与该场景下特殊诉求/备注回退快照同属既有降级口径。
- `requirementKind` 大小写敏感:只有精确的 `TRAVEL`/`TRANSFER` 合法。
- `requirementKind` 与既有全部筛选条件(含 `orderKind`、`groupBatchId`、`statuses`、日期、车型、`consultantId`、`keyword`)都是 AND 组合。
---
## 六.5、枚举 / 数据字典
### requirementKind
**所属字段**: `requirementKind`(两个接口共用的查询参数,同名出现在列表响应的 `data.records[].requirementKind`) | **类型**: `String`
| 值 | 中文标签(`requirementKindLabel`) | 说明 |
|----|-----------------------------------|------|
| `TRAVEL` | 行程用车 | 常规行程用车需求 |
| `TRANSFER` | 接送机 | 接送机用车需求 |
不传、传空串 = 不过滤,两类都返。其余任何取值(含大小写不符)返 `100001`。
---
## 六.6、修改前后对比
### 字段级对比
| 字段 | 改前 | 改后 |
|------|------|------|
| `requirementKind`(两个接口的 query) | 不存在,传了被忽略 | 可选参数,`TRAVEL`/`TRANSFER`,缺省不过滤,非法值返 `100001` |
| `data.records[].requirementKind`(列表响应) | 不存在 | 🆕 新增字段,恒非空,取该卡片自身归属需求的类别 |
| `data.records[].requirementKindLabel`(列表响应) | 不存在 | 🆕 新增字段,中文标签,与 `requirementKind` 恒成对非空 |
| `statusCounts` / `statusOptions[].count`(汇总响应) | 不随需求类别过滤 | 随 `requirementKind` 一起收窄 |
### 行为级对比
| 行为 | 改前 | 改后 |
|------|------|------|
| 同订单行程用车+接送机并存 | 两张卡逐字段相同,前端无法分辨哪张是接送机 | 两张卡各自携带 `requirementKind`/`requirementKindLabel`,可据此区分 |
| 按需求类别筛选看板 | 不支持 | 支持 `requirementKind=TRAVEL`/`TRANSFER` 直接筛 |
| 传了不识别的 `requirementKind` | 参数不存在,被忽略 | 返 `code=100001`,不静默放行 |
---
## 六.7、影响评估
- **是否破坏向后兼容**: 否。不传 `requirementKind` 的旧调用行为与改前完全一致(不过滤,两类都返),响应只新增字段,不删改任何既有字段。
- **前端是否必须同步上线**: 视需求而定——若要解决「同订单两张卡无法区分接送机」这个问题,需要前端读取新字段渲染区分,或使用新参数筛选;不读取新字段时界面行为与改动前完全一致,不会报错。
- **前端 workaround 清理点**: 此前前端没有任何字段可用于区分两类需求;若曾用行程备注、行程日期或别的间接线索猜测哪张卡是接送机,可以改用 `requirementKind` 精确判断。
---
## 七、不影响范围
- **仅影响**: `GET /admin/fleet/board/orders` 与 `GET /admin/fleet/board/summary` 两个读口。
- **零影响**:
- `orderKind` 维度及其既有筛选行为(本次未改动该维度任何逻辑);
- 派车、改派、取消等所有写口(本次改动只涉及看板列表与汇总两个读口);
- `idleVehicleCount` / `idleDriverCount` 全局物理资源指标;
- 小程序端全部接口(派单看板为管理后台专属能力)。
---
## 八、测试环境已验证
测试服 hl-fleet-service 已部署 `dfb5db832`(2026-09-29 17:48:30)。窗口 `orderKind=ALL&pageSize=100` 实测:
```
GET /admin/fleet/board/orders?orderKind=ALL&pageSize=100 → 200,基线 47 条
requirementKind 分布 {TRAVEL: 37, TRANSFER: 10}
requirementKindLabel 分布 {行程用车: 37, 接送机: 10}
两字段 47 条全部非空(含 10 条虚拟待派卡)
GET /admin/fleet/board/orders?orderKind=ALL&pageSize=100&requirementKind=TRAVEL → 200,返回 37 条,全为 TRAVEL
GET /admin/fleet/board/orders?orderKind=ALL&pageSize=100&requirementKind=TRANSFER → 200,返回 10 条,全为 TRANSFER
37 + 10 = 47(两档相加等于基线,requirementId 集合与基线完全一致)
GET /admin/fleet/board/orders?orderKind=ALL&pageSize=100&requirementKind= → 200,返回 47 条,与不传一致
GET /admin/fleet/board/orders?orderKind=ALL&pageSize=100&requirementKind=BOGUS → 200 + code 100001
GET /admin/fleet/board/summary?orderKind=ALL → 六个状态桶合计 47
GET /admin/fleet/board/summary?orderKind=ALL&requirementKind=TRAVEL → 六个状态桶合计 37
GET /admin/fleet/board/summary?orderKind=ALL&requirementKind=TRANSFER → 六个状态桶合计 10
三档与列表 total 逐一对齐(47/37/10)
```
同一订单出两张卡(TRAVEL + TRANSFER 并存)的订单实测 4 个,例如 `2101566624467419137`、`2101146798373339137`。
---
## 十、相关文档
- 关联 Issue: [wx/HL#8518](https://git.1814.love/wx/HL/issues/8518)
- 关联 PR: [wx/HL#8534](https://git.1814.love/wx/HL/pulls/8534)
## 关联 / 联系人
### 链接
- **Issue**: [#8518](https://git.1814.love/wx/HL/issues/8518)
- **PR**: [#8534](https://git.1814.love/wx/HL/pulls/8534)
### 联系人
- **后端责任人**: wx(GIT)
- **问题反馈**: wx/HL Issue #8518 评论区