docs(changelog): 新增 #8202 车队字典校验与团期详情页3项前端待办的 changelog
changelog-filename-gate / validate (push) Failing after 2s
changelog-filename-gate / validate (push) Failing after 2s
#8202/PR #8224 已合并 dev-v3(b439565be)并部署 TEST:用车需求提交/调整两条写路径 共用的车型大类校验新增车队活字典比对(582032/582033),存量码582022不变; mmg v2.1(7df292624)已提前接入车型字典下拉。 另补团期详情页用车/用房面板3项纯前端待办(乘车户全选汇总/确认按钮布局/对话入口), 经核实②④两项已在 dev-v3+v2.1 端到端实现,故仅收窄为①③⑤三项。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
这个提交包含在:
@@ -0,0 +1,381 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8202"
|
||||
title: "子订单行程用车车型大类接入车队字典校验(新增 582032/582033)"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "7df292624"
|
||||
target_release: ""
|
||||
verified_at: "2026-09-23"
|
||||
status_note: "PR #8224 已合并 dev-v3(b439565be)并滚动部署 TEST 双实例,Nacos 注册均 healthy:true。用车需求提交/调整两条写路径共用的车型大类校验链新增车队活字典比对,新增 582032/582033 两码;存量码 582022(填法不合法)不变。真实网关实测:两端点各以 vehicleType=\"__NOT_A_REAL_CATEGORY__\" 提交均返回 582022(填法层拦截优先于字典层)。前端 v2.1 已提前于 7df292624(09-23 09:27)改为车型字典下拉,新提交不会撞新码;仅存量已落库的非字典值在编辑态回显仍需前端显式提示。"
|
||||
updated_at: "2026-09-23"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 用车需求模块: 子订单行程用车车型大类接入车队字典校验
|
||||
|
||||
> **存放目录**: 二期(v3) → `changelogs-v2/2026-09/`
|
||||
>
|
||||
> **服务**: hl-order-service-v3
|
||||
> **PR**: #8224
|
||||
> **Issue**: #8202
|
||||
> **日期**: 2026-09-23
|
||||
> **影响范围**: 管理后台子订单用车需求提交/修改、订单调整统一提交中的用车/接送机车型校验
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化(非必须,本版与上版行为不同 / 纠错 / 撤销时必写)
|
||||
|
||||
- 本次变化:`vehicleType` 归一成功(如 `大巴`→`bus`)后,新增一次**车队活字典**比对;该大类若已整类下线(车队活字典无一行在架代表),提交被拒。
|
||||
- 前端以前以为的:只要 `vehicleType` 能归一为 `suv/mpv/bus/sedan` 之一(或直接传规范 key),提交就会成功。
|
||||
- 实际新行为:归一成功只是必要条件,还须该大类**当前在车队活字典内**;否则报新码 `582032`。**存量码 `582022`(这串字符连大类都归一不出来)保持不变、且仍是校验链第一关**——按当前测试库数据,前端实际会撞上的是 `582022`(自由文本/拼写问题),`582032` 只在大类被整类下线时触发(详见下方六.5/六.6)。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 提交/修改/调整用车需求 | PUT | `/v3/admin/order/{id}/vehicle-requirement` | 校验链新增字典比对 | `fleet[].vehicleType` 归一后追加车队活字典校验 |
|
||||
| 2 | 调整订单统一提交 | POST | `/v3/admin/order/{id}/adjustment/submit` | 校验链新增字典比对 | `updates.vehicleRequirement.fleet[]`/`updates.transferRequirement.fleet[]` 复用同一条校验链 |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 提交/修改/调整用车需求 `PUT /v3/admin/order/{id}/vehicle-requirement`
|
||||
|
||||
**VO**: `VehicleRequirementReqVO → VehicleRequirementRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
管理端子订单详情页提交/编辑用车需求(TRAVEL 行程用车 / TRANSFER 接送机)时调用;三分支由后端自动判断(无 active 需求=INIT_SUBMIT,PENDING=PENDING_EDIT,DONE=DONE_ADJUST),前端不用区分分支、始终整份提交。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| Authorization | Header | String | ✅ | - | 管理端登录令牌 |
|
||||
| id | Path | Long | ✅ | 订单 ID | 目标订单 |
|
||||
| kind | Body | String | ❌ | 正则 `TRAVEL\|TRANSFER` | 需求类别,缺省按订单类型推断 |
|
||||
| fleet | Body | Array\<FleetItem\> | ✅ | 非空 | 车型组合列表 |
|
||||
| fleet[].vehicleType | Body | String | ✅ | 须能归一为 `suv/mpv/bus/sedan`(582022),归一后须在车队活字典内(**582032,本次新增**) | 车型大类,示例 `mpv`,可传中文别名(如"大巴") |
|
||||
| fleet[].seats | Body | Integer | ✅ | ≥0;须在该大类真实车型的座位选项内(582024) | 单车座位数 |
|
||||
| fleet[].count | Body | Integer | ✅ | >0 | 该车型数量 |
|
||||
| specialTags | Body | Array\<String\> | ❌ | 须在 `vehicle_special_demand` 字典内 | 通用特殊诉求 |
|
||||
| pickupRequired / dropoffRequired | Body | Boolean | ❌ | - | 接送机方向标记(兼容字段) |
|
||||
| remark | Body | String | ❌ | ≤500 字 | 备注 |
|
||||
|
||||
#### 出参 `Result<VehicleRequirementRespVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | Long | 需求记录 ID |
|
||||
| kind | String | TRAVEL / TRANSFER |
|
||||
| version | Integer | 版本号 |
|
||||
| status | String | 需求状态 |
|
||||
| branchTaken | String | 本次实际走的分支:INIT_SUBMIT / PENDING_EDIT / DONE_ADJUST |
|
||||
| passengerCount | Integer | 出行人数 |
|
||||
| vehicleCount | Integer | 车辆总数 |
|
||||
| totalSeatCount | Integer | 总座位数 |
|
||||
| driverSeatCount | Integer | 司机座位数(= vehicleCount) |
|
||||
| passengerSeatCapacity | Integer | 载客座位容量 |
|
||||
| remainingPassengerSeats | Integer | 剩余可载客座位 |
|
||||
| previousVersion | Integer | DONE_ADJUST 分支下的上一版本号 |
|
||||
| assignmentDeletedCount | Integer | DONE_ADJUST 分支下被清理的派单数 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
{
|
||||
"kind": "TRAVEL",
|
||||
"fleet": [
|
||||
{ "vehicleType": "大巴", "seats": 7, "count": 1 }
|
||||
],
|
||||
"remark": "接机后直达酒店"
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"id": 2098765432109876543,
|
||||
"kind": "TRAVEL",
|
||||
"version": 2,
|
||||
"status": "PENDING",
|
||||
"branchTaken": "PENDING_EDIT",
|
||||
"vehicleCount": 1,
|
||||
"totalSeatCount": 7
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
写接口无空数据场景;提交成功即返回最新需求快照,无降级分支。
|
||||
|
||||
```json
|
||||
{ "code": 200, "data": { "branchTaken": "INIT_SUBMIT" }, "success": true }
|
||||
```
|
||||
|
||||
#### 错误响应
|
||||
|
||||
`fleet[].vehicleType` 无法归一为 `suv/mpv/bus/sedan` 之一(含全部自由文本,如「别克GL8」)——**当前测试库数据下前端会实际撞上的错误**(存量码 582022,非本次新增,且排在校验链第一关):
|
||||
|
||||
```json
|
||||
{ "code": 582022, "message": "车型必须是车型大类(SUV/MPV/大巴/轿车)", "success": false, "data": null }
|
||||
```
|
||||
|
||||
本次新增两码,仅在 `vehicleType` 归一**成功之后**才有机会触发:
|
||||
|
||||
```json
|
||||
{ "code": 582032, "message": "车型 bus 不在车型字典内(不存在或已下线),请从下拉项中选择", "success": false, "data": null }
|
||||
```
|
||||
|
||||
582032 仅当归一得到的规范大类在车队活字典(`fleet_vehicle_type`)中已整类下线(该大类下所有型号均软删)时触发;测试库当前四个大类各有 1 行在架代表,本码在现有测试数据下不可达。
|
||||
|
||||
```json
|
||||
{ "code": 582033, "message": "车型字典不可用,请稍后重试", "success": false, "data": null }
|
||||
```
|
||||
|
||||
582033 仅当车队字典服务不可用(Feign 兜底返回空 Map)时触发,与本次具体填了什么车型无关,空字典按 fail-closed 处理。
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 鉴权:未登录 → 业务码 `401`。
|
||||
- 校验顺序:`fleet` 非空(582021)→ 逐项归一 + 座位数/数量非负校验(582022/582023)→ **车队活字典比对(582032/582033,本次新增)** → 座位选项校验(582024)→ 特殊诉求校验(582025/582026)。任一步失败即拒,零写入。
|
||||
- 存量已落库、含已下线大类的需求原样再次提交同一接口同样会被 582032 拒绝——这是有意设计(放行已下线大类等同于让下线动作失效),与团期批量车务 809119 同口径。
|
||||
- 车型下拉数据源固定为 `GET /admin/fleet/vehicle-types/list`(车队服务只读端点,网关已放行订单域只读调用,无需新增路由配置)。
|
||||
|
||||
---
|
||||
|
||||
### 2. 调整订单统一提交 `POST /v3/admin/order/{id}/adjustment/submit`
|
||||
|
||||
**VO**: `AdjustmentSubmitReqVO → AdjustmentSubmitRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
管理端订单调整弹窗统一提交入口;前端在内存里收集出行人/改期/行程/房需求/用车需求/接送机需求等多个子领域的改动后一次性提交。本次改动只影响 `updates.vehicleRequirement.fleet[]`(行程用车)与 `updates.transferRequirement.fleet[]`(接送机)两个子领域各自的车型大类校验,其余子领域字段与行为不受影响。
|
||||
|
||||
#### 入参
|
||||
|
||||
> 仅列与本次改动相关的字段;其余子领域(`people`/`schedule`/`itinerary`/`hotelRequirement` 等)字段结构未变,不在此重复列出。
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| Authorization | Header | String | ✅ | - | 管理端登录令牌 |
|
||||
| id | Path | Long | ✅ | 订单 ID | 目标订单 |
|
||||
| updates | Body | Object | ✅ | 至少一个子领域非 null | 各子领域修改内容容器 |
|
||||
| updates.vehicleRequirement.fleet | Body | Array\<FleetItem\> | ❌(该子领域非 null 时必传) | 非空则整组重新走校验链;结构与端点 1 的 `fleet` 完全相同 | 行程用车车型组合 |
|
||||
| updates.vehicleRequirement.fleet[].vehicleType | Body | String | 同端点 1 | 须归一成功(582022)且归一后在车队活字典内(**582032,本次新增**) | 车型大类 |
|
||||
| updates.transferRequirement.fleet | Body | Array\<FleetItem\> | ❌(该子领域非 null 时必传) | 与 `vehicleRequirement.fleet` 走同一条校验方法,服务日由大交通信息派生 | 接送机车型组合 |
|
||||
| updates.transferRequirement.fleet[].vehicleType | Body | String | 同端点 1 | 同上 | 车型大类 |
|
||||
|
||||
#### 出参 `Result<AdjustmentSubmitRespVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| success | Boolean | 提交是否成功;失败由全局异常处理器返回 `Result{code,message,data:null}`,不会出现该字段为 `false` 的情形 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
{
|
||||
"updates": {
|
||||
"vehicleRequirement": {
|
||||
"fleet": [
|
||||
{ "vehicleType": "mpv", "seats": 7, "count": 1 }
|
||||
],
|
||||
"remark": "新增 2 名成员,需更大车型"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{ "code": 200, "message": "成功", "data": { "success": true }, "success": true }
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
写接口无空数据场景;`updates` 内某子领域传 `null` 表示该子领域本次不改,不参与本次校验也不产生该子领域的变更记录。
|
||||
|
||||
```json
|
||||
{ "code": 200, "data": { "success": true }, "success": true }
|
||||
```
|
||||
|
||||
#### 错误响应
|
||||
|
||||
与端点 1 完全同码同序(`AdjustmentService.validateVehicleSeatOptionsBeforeWrite` 从 `updates.vehicleRequirement`/`updates.transferRequirement` 的 `fleet` 字段构造 `VehicleRequirementReqVO` 后,调用与端点 1 相同的 `validateVehicleRequirementSeatOptions`)。当前测试库数据下前端会实际撞上的同样是 582022:
|
||||
|
||||
```json
|
||||
{ "code": 582022, "message": "车型必须是车型大类(SUV/MPV/大巴/轿车)", "success": false, "data": null }
|
||||
```
|
||||
|
||||
```json
|
||||
{ "code": 582032, "message": "车型 bus 不在车型字典内(不存在或已下线),请从下拉项中选择", "success": false, "data": null }
|
||||
```
|
||||
|
||||
```json
|
||||
{ "code": 582033, "message": "车型字典不可用,请稍后重试", "success": false, "data": null }
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 鉴权:未登录 → 业务码 `401`。
|
||||
- `submit` 整体在一个 `@Transactional(rollbackFor = Exception.class)` 事务内;`updates.vehicleRequirement` 与 `updates.transferRequirement` 两个子领域各自独立校验一次,任一子领域校验失败即整单回滚,**已通过校验的其他子领域改动也不会落库**。
|
||||
- `updates.vehicleRequirement`/`updates.transferRequirement` 为 `null` 时该子领域完全跳过(既不校验也不改动),不受本次变更影响。
|
||||
- 存量已落库、含已下线大类的需求原样再次经本接口提交同样会被 582032 拒绝,同端点 1。
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式(接口类必写)
|
||||
|
||||
> 本节只写**后端接受/拒绝 payload 的规则**,不写 UI 渲染建议。
|
||||
|
||||
### ✅ 正确 / ❌ 错误 payload 对照
|
||||
|
||||
| 场景 | payload | 结果 |
|
||||
|------|---------|------|
|
||||
| ✅ 从 `GET /admin/fleet/vehicle-types/list` 下拉选值提交 | `vehicleType` 取字典返回的规范 key(`suv/mpv/bus/sedan`) | 通过归一 + 字典校验 |
|
||||
| ✅ 提交中文别名 | `vehicleType: "大巴"` | 归一为 `bus` 后再查字典,与直接传 `bus` 等价 |
|
||||
| ❌ 自由文本/型号名 | `vehicleType: "别克GL8"` | 582022,归一失败,校验链第一关即拒 |
|
||||
| ❌ 大类已被车队整类下线 | `vehicleType: "bus"` 但活字典无 `bus` 在架代表 | 582032,归一成功但字典比对不通过 |
|
||||
| ❌ 车队字典服务不可用 | 任意合法 `vehicleType` | 582033,fail-closed,不做逐项放行 |
|
||||
|
||||
- `vehicleType` 一律以**归一后的规范 key**(非原始填法)落库;重新查看已提交需求时看到的也是规范 key,不是用户当时填的中文别名。
|
||||
- 端点 1(`vehicle-requirement`)与端点 2(`adjustment/submit`)中的 `fleet[]` 结构与校验规则完全一致,前端不需要为两个入口分别处理错误码。
|
||||
|
||||
### 前端已就绪的状态
|
||||
|
||||
mmg `v2.1` 分支 `7df292624`(2026-09-23 09:27)已将车型录入从自由文本改为 `GET /admin/fleet/vehicle-types/list` 字典下拉,新提交的 `vehicleType` 均取自字典返回值,不会触发本次新增的 582032/582033。
|
||||
|
||||
尚待前端覆盖的场景:**存量已落库需求的编辑态回显**——若某行 `vehicleType` 不在当前字典返回值内(历史遗留或大类刚被下线),下拉组件默认会渲染成空选项且用户无法感知原因;此时应显式提示"当前值不在字典内,请重新选择",而非静默清空。
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库行为(涉及写操作时必写)
|
||||
|
||||
- 无 Flyway migration、无 DDL、无表结构变更。
|
||||
- 本次校验是**写前置校验**,不涉及任何新增列或索引;`vehicle_requirement` 表落库字段与校验前完全一致(仅 `vehicle_type` 列存的值继续是归一后的规范 key)。
|
||||
- 校验失败(582022/582032/582033/582024 等)的请求**零写入**——包括 `vehicle_requirement` 主表、审计记录、派单联动,一律不产生任何行。
|
||||
- 车队活字典本身(`fleet_vehicle_type` 表)不因本次改动新增/修改任何行,字典维护仍走车队服务既有入口。
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 未登录 → 业务码 `401`(网关包装 HTTP 200)。
|
||||
- `fleet` 为空数组或缺失 → 582021。
|
||||
- `vehicleType` 无法归一 → 582022(存量码,未变)。
|
||||
- `vehicleType` 归一成功但大类已整类下线 → **582032(本次新增)**。
|
||||
- 车队字典服务不可用(Feign 兜底返回空 Map)→ **582033(本次新增)**,对所有请求 fail-closed,不逐项放行。
|
||||
- `seats`/`count` 非法 → 582023;座位数不在该大类真实型号座位选项内 → 582024(582024 判定排在字典校验之后,因此大类已下线时不会先看到一个指错方向的座位错误)。
|
||||
- 存量已落库、含已下线大类的需求原样重新提交同一接口 → 同样被 582032 拒绝,零写入,不因"内容未变"豁免。
|
||||
- `adjustment/submit` 路径下,`validateVehicleTypesAgainstFleetDict` 内的 Feign 调用运行在 `AdjustmentService.submit` 的物理事务内(`PROPAGATION_REQUIRED` 挂入);这是该路径已有的事务边界,本次改动未改变这一行为,也不在本单修复范围。
|
||||
|
||||
---
|
||||
|
||||
## 六.5、枚举 / 数据字典
|
||||
|
||||
### fleet[].vehicleType(车型大类规范 key)
|
||||
|
||||
**所属字段**: `VehicleRequirementReqVO.fleet[].vehicleType` / `AdjustmentSubmitReqVO.updates.vehicleRequirement.fleet[].vehicleType` / `...transferRequirement.fleet[].vehicleType` | **类型**: String
|
||||
|
||||
| 值 | 说明 |
|
||||
|----|------|
|
||||
| `suv` | SUV |
|
||||
| `mpv` | MPV / 商务车 |
|
||||
| `bus` | 大巴 |
|
||||
| `sedan` | 轿车 |
|
||||
|
||||
- 归一表(含中文别名,如"大巴"→`bus`)由 `VehicleCategoryNormalizer` 维护,四值闭集,本次未新增第五类。
|
||||
- 实际**是否可选**取决于车队活字典当前有哪些大类在架,前端应以 `GET /admin/fleet/vehicle-types/list` 的实时返回为准,不要硬编码这四个 key 一定全部可用。
|
||||
|
||||
---
|
||||
|
||||
## 六.6、修改前后对比(修改/删除类接口必写,新增跳过)
|
||||
|
||||
### 字段级对比
|
||||
|
||||
| 字段 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| `fleet[].vehicleType` | 只要能归一为 `suv/mpv/bus/sedan` 之一即接受,不管该大类当前是否在车队还在营 | 归一成功后额外要求该大类在车队活字典内有 ≥1 行在架代表,否则拒 |
|
||||
|
||||
### 行为级对比
|
||||
|
||||
| 行为 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| 提交一个已被车队整类下线的大类(如运营已下线 `bus`) | 校验通过,正常落库 | 582032 拒绝,零写入 |
|
||||
| 车队字典服务不可用 | 不影响本校验链(该服务原本不参与车型大类校验) | 582033 拒绝,fail-closed |
|
||||
| 存量含已下线大类的需求原样重新提交 | 通过 | 582032 拒绝(与团期批量车务 809119 同口径) |
|
||||
|
||||
---
|
||||
|
||||
## 六.7、影响评估(修改/删除类必写)
|
||||
|
||||
- **是否破坏向后兼容**: 部分破坏,且是有意为之。请求/响应结构不变;仅"归一成功但大类已被下线"这一种此前被接受的取值,改为拒绝。影响面严格限定在"大类已整类下线"这一种运营主动下线的场景,不影响任何仍在营的大类。
|
||||
- **前端是否必须同步上线**: 否,mmg 已提前于本次后端上线(`7df292624` 早于 `b439565be`)改为字典下拉,新提交路径已规避新码。
|
||||
- **前端 workaround 清理点**: 存量已落库需求在编辑态若命中已下线大类,需前端显式提示"当前值不在字典内,请重新选择"(见"四、契约约束"节),避免下拉渲染成空白且用户无法定位原因。
|
||||
|
||||
---
|
||||
|
||||
## 七、不影响范围(显式声明, 帮前端/QA 缩小排查面)
|
||||
|
||||
- **仅影响**: 两个端点中 `fleet[].vehicleType` 的字典比对这一步;其余校验(座位数、数量、特殊诉求、座位选项)逻辑不变。
|
||||
- **零影响**:
|
||||
- `VehicleCategoryNormalizer` 的四值归一表与别名规则本身(未新增/删减大类)。
|
||||
- `adjustment/submit` 中 `people`/`schedule`/`itinerary`/`hotelRequirement` 等与用车无关的子领域。
|
||||
- 团期批量车务(`GroupVehicleRequirementService`)校验链,809119/809120 已是同口径独立实现,本单未改动团级代码。
|
||||
- 派单、车队分配、司机端等下游链路的既有行为。
|
||||
- `GET /admin/fleet/vehicle-types/list` 端点本身(既有只读端点,本单未修改其实现或路由)。
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
单元测试(`RequirementServiceVehicleTypeDictValidateTest`):
|
||||
- `validateVehicleTypes_retiredCategoryRejectedAndLiveCategoryAccepted`:同一桩字典(刻意不含 `bus`)下,`大巴`(归一为 `bus`)被拒且断言错误码为 582032、非 582022;同一字典下 `suv` 正常放行。
|
||||
- `validateVehicleTypes_emptyDict_throws582033NotFail582032`:空字典(模拟车队服务不可用)下,`suv` 被拒且断言错误码为 582033、非 582032,报文不含用户填的车型值。
|
||||
|
||||
真实网关(TEST 环境)实证:
|
||||
|
||||
```
|
||||
PUT /v3/admin/order/{id}/vehicle-requirement vehicleType=__NOT_A_REAL_CATEGORY__ → 582022 ✓
|
||||
POST /v3/admin/order/{id}/adjustment/submit vehicleType=__NOT_A_REAL_CATEGORY__ → 582022 ✓
|
||||
```
|
||||
|
||||
两端点对同一非法值返回一致的错误码(582022),验证校验链在两个入口上等价生效、归一失败判定优先于字典比对。
|
||||
|
||||
部署: PR #8224 已合并 `dev-v3`(合并提交 `b439565be`),TEST 环境双实例滚动部署完成,Nacos 注册均 `healthy:true`。
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 关联 Issue: [wx/HL#8202](https://git.1814.love:8443/wx/HL/issues/8202)
|
||||
- 关联 PR: [wx/HL#8224](https://git.1814.love:8443/wx/HL/pulls/8224)
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#8202](https://git.1814.love:8443/wx/HL/issues/8202)
|
||||
- **PR**: [#8224](https://git.1814.love:8443/wx/HL/pulls/8224)
|
||||
- **Merge commit**: [b439565be](https://git.1814.love:8443/wx/HL/commit/b439565be)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
@@ -0,0 +1,122 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "frontend-groupbatch-vehicle-house-panel-gaps"
|
||||
title: "团期详情页「用车/用房」面板:乘车户全选汇总人数 + 确认按钮布局 + 对话入口缺失(3 项前端待办)"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "前端缺陷"
|
||||
backend_status: "not_required"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "pending"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "wx 团期详情页用车/用房面板走查反馈,3 项均为纯前端待办:①乘车户全选+人数自动汇总(数据 participantCount 已在 orders 数组里,未被读取)②接送机逐户确认按钮布局(未右对齐)③子订单行缺与定制师对话入口(已有可用的既有后端会话接口 open-group,未接入)。三项互相独立,backend_status=not_required 是因为本文件不改任何后端契约。"
|
||||
updated_at: "2026-09-23"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 团期详情页「用车/用房」面板:乘车户全选汇总人数 + 确认按钮布局 + 对话入口缺失(3 项前端待办)
|
||||
|
||||
> **服务**: 无后端服务变更(纯前端 UI;第 3 项引用的 `hl-user-service` `/admin/message/chat/open-group` 是已存在的能力,本次不新增/不修改)
|
||||
> **PR**: 无(前端尚未提交)
|
||||
> **Issue**: 无(wx 口头/会话反馈,未建 Gitea 工单)
|
||||
> **日期**: 2026-09-23
|
||||
> **影响范围**: 管理后台团期详情页「用车」「用房」两个 Tab 面板
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
本文件是**待补齐清单**,不是"已交付变更"说明——三项各自独立、互不依赖,可分别排期,涉及文件都在 `hl-ui` `origin/v2.1` 分支。
|
||||
|
||||
---
|
||||
|
||||
## 一、乘车户「全选」+ 按所选户自动汇总人数
|
||||
|
||||
**涉及文件**: `src/views/order-v2/batch/detail/components/GroupVehicleRequirementEditModal.vue`(「正式用车需求编辑」弹窗,逐日行区块,第 165-201 行)
|
||||
|
||||
**现状(实测代码)**:
|
||||
- 「人数」是 `<n-input-number v-model:value="d.headcount">`(第 177-186 行),`headcountRule`(第 375-382 行)只校验"非空且 ≥1",没有联动逻辑。
|
||||
- 「乘车户」是 `<n-select multiple filterable :options="memberOptions">`(第 190-198 行),`memberIdsRule`(第 383-388 行)只校验"非空数组"。
|
||||
- 两个控件之间没有任何联动,也没有"全选"控件。
|
||||
|
||||
**可用但未被读取的数据**:`memberOptions`(第 323-333 行)目前只从 `props.orders` 取 `orderId`/`customerName`/`contactName`/`teamNo` 组装 `{ label, value }`,**没有带出 `participantCount`**。`participantCount` 其实已经在同一个 `orders` 数组里(团期订单列表 GB-ADM-003 契约字段),同级的 `VehicleHouseholdsSection.vue:71` 与 `RequirementTab.vue:476` 已经在直接读它展示"N 人"——不需要新接口,只是这个弹窗的 `memberOptions` computed 没把它带出来。
|
||||
|
||||
**需要的改动(供参考,不限定实现方式)**:
|
||||
1. 全选:一个按钮/开关把 `d.memberOrderIds` 置为 `memberOptions.value` 的全量 `value`。
|
||||
2. 自动汇总:`memberOptions` 补出 `participantCount`(或另建 `orderId → participantCount` 的 Map),选中集合变化时把 `d.headcount` 自动置为已选各户 `participantCount` 之和;`participantCount` 为 `null` 的户按 0 计。
|
||||
3. `headcount` 输入框保留手动可编辑——自动填充只是给个起点,若某户实际乘车人数与 `participantCount`(报名人数)不一致(如小孩不占位、分批乘车),管理员仍需手动改。
|
||||
|
||||
**业务边界**:
|
||||
- 这里自动算出的人数只是**填表辅助**,不是权威口径——整团/整日的用车人数汇总另有独立的后端工单在跟(wx/HL#8220,backend),本项做完之后 `d.headcount` 仍然只是这张表单自己的字段,不代表后端汇总接口上线后的口径一定与这里手填/自动填的值相同。
|
||||
- `participantCount` 为 `null` 的户参与"全选"时按 0 计入自动汇总,不阻断全选本身(该户仍会被选中,只是不贡献人数)。
|
||||
|
||||
---
|
||||
|
||||
## 二、接送机需求逐户「确认」按钮挪到行右侧
|
||||
|
||||
**涉及文件**: `src/views/order-v2/batch/detail/components/VehicleHouseholdsSection.vue`
|
||||
|
||||
**现状(实测代码 + 样式)**:
|
||||
- 「确认」按钮(`canConfirmReq(req)` 为真时渲染,第 122-129 行)与车型/状态标签、"XX 人"文字同处一行——`.vehicle-households__req-head`(第 92 行起)。
|
||||
- 该行样式(第 495-500 行):`display: flex; align-items: center; gap: 8px; flex-wrap: wrap;`,没有 `justify-content` 或对按钮单独设 `margin-left: auto`,按钮紧跟在"XX 人"文字后面,不在行的最右侧。
|
||||
|
||||
**需要的改动**:给 `.vehicle-households__req-head` 加 `justify-content: space-between`(把按钮包进一个独立的尾部容器),或直接给「确认」按钮加 `margin-left: auto`,把它推到行右侧。
|
||||
|
||||
**业务边界**:
|
||||
- 只影响 `.vehicle-households__req-head` 这一处的 TRANSFER 行确认按钮;用房面板 `RoomHouseholdsSection.vue` 全文没有等价的逐户「确认」按钮(已核查,仅第 127/289 行的注释提到"整团确认",属另一套交互,不在本项范围)。
|
||||
|
||||
---
|
||||
|
||||
## 三、用车/用房子订单行缺「和定制师发起对话」入口
|
||||
|
||||
**涉及文件**:
|
||||
- `VehicleHouseholdsSection.vue` 的 `.vehicle-households__card-head`(第 56-77 行)
|
||||
- `RoomHouseholdsSection.vue` 的 `.room-households__card-head`(第 44-68 行)
|
||||
|
||||
两处的 `v-for` 都已经在 `household` 上下文里拿到 `household.orderId`,但都没有任何对话/聊天入口(全文 grep「对话」「chat」「Chat」在这两个组件里零命中,`RoomHouseholdsSection.vue` 里唯一相关的是两处注释提及"整团确认",与聊天无关)。
|
||||
|
||||
**已就绪的后端契约(本次核实,非新增,backend_status=not_required 是因为接口本身没有变化,只是首次要接给这两个面板用)**:
|
||||
|
||||
`POST /admin/message/chat/open-group`(`hl-user-service` `ChatMessageController.openGroup`,工单 #7211)
|
||||
|
||||
- 请求体 `ChatOpenGroupReqVO`:`{ "orderId": "70123" }`——`orderId` 必填(`@NotNull`),团期子订单 id,雪花值按字符串传(与本仓其它 19 位 ID 字段一致,避免 JS 精度丢失)。
|
||||
- 响应体 `ChatOpenFullRespVO`(继承 `ChatOpenRespVO`):`conversationKey`(如 `GROUP:70123`,不含 adminId 对)、`peerAdminId`、`peerName`、`peerRole`、`peerRoleLabel`、`peerOnline`、`unreadCount`、`isNew`、`order`(订单卡)、`thread`(首屏 20 条消息)、`unreadTotal`(合并未读,本次调用会把该会话标记已读之后的值)。
|
||||
- 团期管理员是团队制,不指派到人:定制师这一侧打开会话时,对端固定是「团期管理员」团队占位——`peerRole="GROUP_ADMIN"`、`peerRoleLabel="团期管理员"`,不是某一个具体的管理员账号。
|
||||
- 准入:该订单当前定制师(CUSTOMIZER),或当前角色持权限码 `group-batch:demand:confirm`(超管天然通过);其余角色统一拒绝且零写入。
|
||||
- 错误码:缺 `orderId` → **281012**「缺少订单上下文」;不满足上述准入身份 → **281002**「无权访问该会话」;该订单不是团期子订单(团期摘要 `groupBatchId` 为空)→ **281015**「该订单不是团期子订单,无法联系团期管理员」。
|
||||
- 打开会话之后,同一个 `conversationKey`(`GROUP:{orderId}`)继续走通用的两个端点,不区分模块:`GET /admin/message/chat/{conversationKey}/messages`(上滑翻页取更早历史)、`POST /admin/message/chat/{conversationKey}/messages`(发消息)。如果车务/房务面板已有复用的聊天组件,直接换 `open` 端点为 `open-group` 即可接入,不需要重新对接翻页/发送逻辑。
|
||||
- 网关:`hl-user-service` 的 `/admin/message/**` 路由已覆盖,不需要新配路由。
|
||||
|
||||
**业务边界**:
|
||||
- 用车、用房两个面板的子订单行请求体字段完全一样(都只传 `orderId`),可以共用同一套"发起对话"组件。
|
||||
- 会话按 `GROUP:{orderId}` 维度建:同一个子订单如果在用车、用房两个面板里都出现,两边打开的是同一条会话,不会产生重复会话或未读数分叉。
|
||||
|
||||
---
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- **仅影响**:管理后台团期详情页「用车」「用房」两个 Tab 组件(模板/脚本/样式,纯前端文件)。
|
||||
- **零影响**:
|
||||
- 本文件三项都不改后端契约;第三项引用的 `POST /admin/message/chat/open-group` 是首次被这两个面板消费,接口本身(CUSTOMIZER↔团期管理员团队场景)未做任何变更。
|
||||
- 其它已在用 `open-group`/`open-fleet`/`open-house` 的既有调用方不受影响。
|
||||
- 团期用车需求的后端保存/校验接口(`GroupVehicleRequirementSaveReqVO` 等)不受影响——第一项只改前端表单填充逻辑,提交给后端的字段结构不变。
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- `open-group` 契约来源:`hl-user-service/src/main/java/com/hulalv/user/notification/chat/controller/ChatMessageController.java`(工单 #7211)。
|
||||
- `participantCount` 字段来源:`GB-ADM-003` 团期订单列表契约(`src/api/orderV2GroupBatch.js` 第 143-160 行注释)。
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- 无 PR / 无 Issue(本文件为纯前端待办清单,未建 Gitea 工单)。
|
||||
|
||||
### 联系人
|
||||
|
||||
- 第三项接口如有疑问:@wx
|
||||
在新工单中引用
屏蔽一个用户