diff --git a/changelogs-v2/2026-09/30_8543_团期订单Tab用车状态按需求类别拆分并统一文案-修改接口-管理后台.md b/changelogs-v2/2026-09/30_8543_团期订单Tab用车状态按需求类别拆分并统一文案-修改接口-管理后台.md new file mode 100644 index 00000000..4d2b9c7e --- /dev/null +++ b/changelogs-v2/2026-09/30_8543_团期订单Tab用车状态按需求类别拆分并统一文案-修改接口-管理后台.md @@ -0,0 +1,329 @@ +--- +schema: "hl-changelog/v2" +ticket: "8543" +title: "团期订单 Tab 用车状态三处读口按需求类别拆分并统一文案" +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 #8592 合并 dev-v3(bc80606ac2);hl-order-service-v3 dev-v3 分支部署测试网关 @ 99fb369ba 并实测:配车芯片明细端点 items[] 按用车类别拆项、同一 orderId 出现两次(TRAVEL/TRANSFER 各一);totalCount/doneCount 由户数变为条目数(实测 5 项 3 完成,对应 3 户);子订单列表新增 vehicleRequirementKind 字段恒为 TRAVEL;文案分支待车队配/配车完成/待提交车务/待审核均已实测复现;团期列表页 chipStats.vehicle 与明细端点同步联动同一计数口径。" +updated_at: "2026-09-30" +base: "dev-v3" +--- + +# hl-order-service-v3: 团期订单 Tab 用车状态三处读口按需求类别拆分并统一文案 + +> **存放目录**: `changelogs-v2/2026-09/` +> **服务**: hl-order-service-v3 (端口 8086) +> **PR**: #8592 +> **Issue**: #8543 +> **日期**: 2026-09-30 +> **影响范围**: 团期配车芯片明细端点 `GET /v3/admin/order/group-batch/{groupBatchId}/chips/vehicle`、团期下子订单列表端点 `GET /v3/admin/order/group-batch/{groupBatchId}/orders`;团期列表/看板端点的 `chipStats.vehicle` 计数口径联动变化(无字段新增,纯语义联动) + +--- + +## ⚠️ 关键变化 + +- 🔴 **破坏性变更**:`GET .../chips/vehicle` 的 `items[]` 从**一户一项**改为**一户每类一项**——同一户同时有"行程用车"(TRAVEL)与"接送机"(TRANSFER)两类需求时,`items[]` 里会出现两条 `orderId` 相同、`kind` 不同的记录。**前端不得再按 `orderId` 去重**,去重会随机丢掉其中一类需求的状态。 +- `totalCount` / `doneCount` 的口径随之从"户数"变为"条目数":一户两类需求算两条,分母跟着变大。实测团期批次 T26-3963 为例:3 户、5 条需求行,`totalCount=5`(不是 3),`doneCount=3`。 +- `GroupBatchChipItemRespVO`(仅配车芯片会用到)新增 `kind`(TRAVEL/TRANSFER,可空)与 `kindName`(配对中文名,可空)两个字段;房/导/摄/约/保五个芯片的 `items[]` 里这两个字段恒为 `null`。 +- `GroupBatchOrderItemRespVO`(子订单列表 `.../orders` 出参)新增 `vehicleRequirementKind` 与 `vehicleRequirementKindName` 两个字段——是新增字段不是破坏性变更。这一列**当前恒为 TRAVEL**:子订单列表每户只占一行,装不下两类需求,上游按 TRAVEL 过滤取行;要看接送机需求要走配车芯片明细(一户两类各占一项)。 +- 用车状态文案本次统一(此前配车芯片明细端点的映射与子订单列表端点各写各的,现在两处一致):`PENDING`→**待车队配**、`PENDING_REVIEW` 按 `kind` 分叉(TRAVEL 出**待提交车务**、TRANSFER 出**待审核**)、`REJECTED_TO_CONSULTANT`→**已驳回定制师**、`REJECTED_TO_ADMIN`→**已驳回管理员**、`DONE`→**配车完成**。 +- 该户 `needsIt=true` 但尚无任何 active 车需求行时(已声明要用车、但一行都没提交),配车芯片明细该户仍占一项,`kind`/`status` 为 `null`、`statusName` 为**未提交**(源码逻辑与 PR 说明已确认,本轮实测样本未覆盖到该分支,样本团期均已提交需求行)。 +- 团期列表页(`GET /v3/admin/order/group-batch`)与看板端点每行的 `chipStats.vehicle.total`/`chipStats.vehicle.done` 与本次改动**共用同一套聚合算法**(`GroupBatchChipResolver.aggregateAll`),因此同步从"户数"变为"条目数"——实测同一团期批次两处读数逐位一致(5/3 与 3/1)。这两个端点本身**没有新增字段**,只是既有的 `total`/`done` 计数口径联动变了,前端若在列表页展示"配车 X/Y"这类徽标也要同步理解口径变化。 + +--- + +## 二、变更接口清单 + +| # | 接口 | 方法 | 路径 | 变更类型 | 说明 | +|---|------|------|------|----------|------| +| 1 | 团期配车芯片明细 | GET | `/v3/admin/order/group-batch/{groupBatchId}/chips/vehicle` | 🔴 破坏性变更 + 字段新增 | `items[]` 按用车类别拆项,同一 `orderId` 可出现两次;新增 `kind`/`kindName`;`totalCount`/`doneCount` 语义由户数变为条目数 | +| 2 | 团期下子订单列表 | GET | `/v3/admin/order/group-batch/{groupBatchId}/orders` | 字段新增 | 新增 `vehicleRequirementKind`/`vehicleRequirementKindName`(当前恒为 TRAVEL);`vehicleRequirementStatusName` 的 `PENDING_REVIEW` 文案按 `kind` 分叉 | + +--- + +## 三、接口详情 + +### 1. 团期配车芯片明细 `GET /v3/admin/order/group-batch/{groupBatchId}/chips/vehicle` + +**VO**: `(无请求体,仅路径参数)` → `GroupBatchChipDetailVO` + +#### 使用场景 + +车务/团期管理员在团期订单 Tab 展开"配车"芯片查看逐户用车状态明细时调用。六个芯片(房/车/导/摄/约/保)共用同一套 `GroupBatchChipDetailVO` 响应结构与六个并列端点,本条目专指配车芯片(其余五芯片本次未受影响,`kind`/`kindName` 在那五芯片下恒为 `null`)。 + +#### 入参字段表 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| groupBatchId | Path | Long | ✅ | - | 团期批次 ID | + +#### 出参字段表 + +以下是本次新增/变化的字段;`GroupBatchChipDetailVO` 顶层其余字段(`chipLabel`/`aggregateStatus`/`aggregateStatusName`/`staffList`)与 `items[]` 内未变化的字段(`orderId`/`orderNo`/`teamNo`/`customerName`/`peopleCount`/`needsIt`/`claimerId`/`claimerName`/`claimerSource`/`updateTime`/`staffs`)结构未变,不重复列出。 + +| 字段 | 类型 | 说明 | +|------|------|------| +| totalCount | Integer | 🔴 语义变化:改前是"计入统计的户数",改后是"条目数"(一户两类需求算两条) | +| doneCount | Integer | 🔴 语义变化:口径随 `totalCount` 同步改为条目数 | +| items[] | List | 🔴 数组长度语义变化:一户两类需求时占两个元素,`orderId` 相同 | +| items[].kind | String,可空 | 新增:用车类别(`TRAVEL` 行程用车 / `TRANSFER` 接送机);该户尚无任何 active 车需求行时为 `null`(此时该户仍占一项,`status` 为 `null`,`statusName` 为"未提交");房/导/摄/约/保五芯片恒为 `null` | +| items[].kindName | String,可空 | 新增:`kind` 配对中文名(行程用车/接送机);`kind` 为 `null` 或枚举外未知码时为 `null`——编码不回落当中文展示 | +| items[].statusName | String,可空 | 文案统一:`PENDING`→待车队配、`PENDING_REVIEW` 按 `kind` 分叉(TRAVEL→待提交车务,TRANSFER→待审核)、`REJECTED_TO_CONSULTANT`→已驳回定制师、`REJECTED_TO_ADMIN`→已驳回管理员、`DONE`→配车完成 | + +#### 请求示例 + +```http +GET /v3/admin/order/group-batch/2104839654727618562/chips/vehicle +``` + +#### 响应示例 + +真实实测(团期批次 T26-3963,3 户 5 项,含同一 `orderId` 两类需求各占一项): + +```json +{"code":200,"message":"成功","data":{"batchId":"2104839654727618562","groupBatchId":"2104839654727618562","chipLabel":"配车","aggregateStatus":"DOING","aggregateStatusName":"进行中","totalCount":5,"doneCount":3,"staffList":null,"items":[{"orderId":"2104839654652121090","orderNo":"HL20260929154414207","teamNo":"26-2313","contactName":"李文博","customerName":"李文博","peopleCount":2,"status":"DONE","statusText":"配车完成","statusName":"配车完成","needsIt":true,"kind":"TRAVEL","kindName":"行程用车","updateTime":null,"claimerId":null,"claimerName":null,"claimerSource":null,"staffs":null},{"orderId":"2104839654652121090","orderNo":"HL20260929154414207","teamNo":"26-2313","contactName":"李文博","customerName":"李文博","peopleCount":2,"status":"PENDING","statusText":"待车队配","statusName":"待车队配","needsIt":true,"kind":"TRANSFER","kindName":"接送机","updateTime":null,"claimerId":null,"claimerName":null,"claimerSource":null,"staffs":null},{"orderId":"2104839686486888449","orderNo":"HL20260929154421828","teamNo":"26-9436","contactName":"张丽娟","customerName":"张丽娟","peopleCount":2,"status":"DONE","statusText":"配车完成","statusName":"配车完成","needsIt":true,"kind":"TRAVEL","kindName":"行程用车","updateTime":null,"claimerId":null,"claimerName":null,"claimerSource":null,"staffs":null},{"orderId":"2104839729176514562","orderNo":"HL20260929154432061","teamNo":"26-6559","contactName":"那顺","customerName":"那顺","peopleCount":3,"status":"DONE","statusText":"配车完成","statusName":"配车完成","needsIt":true,"kind":"TRAVEL","kindName":"行程用车","updateTime":null,"claimerId":null,"claimerName":null,"claimerSource":null,"staffs":null},{"orderId":"2104839729176514562","orderNo":"HL20260929154432061","teamNo":"26-6559","contactName":"那顺","customerName":"那顺","peopleCount":3,"status":"PENDING_REVIEW","statusText":"待审核","statusName":"待审核","needsIt":true,"kind":"TRANSFER","kindName":"接送机","updateTime":null,"claimerId":null,"claimerName":null,"claimerSource":null,"staffs":null}]},"traceId":null,"success":true} +``` + +另一团期批次(T26-6001,含"待提交车务"文案分支,TRAVEL 类 `PENDING_REVIEW`)实测节选: + +```json +{"orderId":"2104840685708525570","orderNo":"HL20260929154820126","teamNo":"26-0805","contactName":"谢丽萍","customerName":"谢丽萍","peopleCount":2,"status":"PENDING_REVIEW","statusText":"待提交车务","statusName":"待提交车务","needsIt":true,"kind":"TRAVEL","kindName":"行程用车","updateTime":null,"claimerId":null,"claimerName":null,"claimerSource":null,"staffs":null} +``` + +#### 空数据 / 降级响应 + +- 团期批次不存在:返回业务错误(见"错误响应"),`data` 为 `null`。 +- 团期下暂无子订单:`items` 为空数组,`totalCount`/`doneCount` 均为 `0`。 +- 户已声明要用车(`needs_vehicle=true`)但尚未提交任何车需求行:该户仍占一项,`kind`/`status` 为 `null`,`statusName` 为"未提交"(源码逻辑已确认,本轮实测样本未覆盖到该分支)。 + +#### 错误响应 + +```json +{"code":589500,"message":"团期不存在","data":null,"traceId":null,"success":false} +``` + +#### 业务边界 + +- 🔴 `items[]` 按 `orderId` 去重是错误用法——同一户两类需求会被拆成两个数组元素,去重会随机丢掉其中一类的状态展示。 +- `totalCount`/`doneCount` 不再等于该团期的户数,若页面上另有独立的"户数"展示(如团期基础信息),不要复用这两个字段去推导户数。 +- `kind`/`kindName` 只在配车芯片有意义,其余五芯片(房/导/摄/约/保)该字段恒为 `null`,前端渲染这五个芯片时不需要处理 `kind` 分支。 +- `statusName` 的四个文案改动是全量替换(不是新增枚举值),旧文案(待车务配/驳回给定制师/驳回给管理员)不会再出现,前端若按旧文案字符串做过特殊判断需要同步更新。 + +### 2. 团期下子订单列表 `GET /v3/admin/order/group-batch/{groupBatchId}/orders` + +**VO**: `(无请求体,Path + Query 参数)` → `PageResult` + +#### 使用场景 + +团期订单 Tab 展示子订单摘要列表(客户姓名、各状态列、支付/结算信息)时调用,是该 Tab 的主表格数据源。 + +#### 入参字段表 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| groupBatchId | Path | Long | ✅ | - | 团期 ID | +| page | Query | Integer | ❌ | 缺省 1,<1 归一为 1 | 页码,从 1 起 | +| pageSize | Query | Integer | ❌ | 缺省 20,<1 归一为 20,>200 截断为 200 | 每页条数 | +| includeTravelers | Query | Boolean | ❌ | 缺省 true | 是否附出行人明细(证件号/手机号一律不返回),本次未变 | +| includeNeeds | Query | Boolean | ❌ | 缺省 true | 是否附房数/房型/特殊需求,本次未变 | +| includeCancelled | Query | Boolean | ❌ | 缺省 false | 是否含已取消子订单,本次未变 | + +#### 出参字段表 + +以下只列本次新增/变化的字段;`GroupBatchOrderItemRespVO` 其余既有字段(`orderId`/`customerName`/各状态码与状态中文名/金额字段/`travelers` 等)结构未变,不重复列出。 + +| 字段 | 类型 | 说明 | +|------|------|------| +| vehicleRequirementKind | String,可空 | 新增:本行车需求的用车类别(`TRAVEL`/`TRANSFER`)。**当前恒为 `TRAVEL`**——子订单列表每户只占一行,装不下两类,上游按 TRAVEL 过滤取行;接送机需求要看配车芯片明细(`.../chips/vehicle`,一户两类各占一项)。无 active 车需求行时为 `null`,与 `vehicleRequirementStatus` 同生同灭 | +| vehicleRequirementKindName | String,可空 | 新增:`vehicleRequirementKind` 配对中文名(行程用车/接送机);枚举外未知码不回落编码,直接给 `null` | +| vehicleRequirementStatusName | String,可空 | 既有字段,文案口径调整:`PENDING_REVIEW` 的措辞随同行的 `vehicleRequirementKind` 分叉——TRAVEL 下发"待提交车务"(该状态上没有逐户审核动作,推走它的是团期管理员整团一次的"提交车务"),TRANSFER 下发"待审核";由于本字段随行的 `vehicleRequirementKind` 当前恒为 TRAVEL,本端点实际只会出现"待提交车务"这一支 | + +#### 请求示例 + +```http +GET /v3/admin/order/group-batch/2104839654727618562/orders?page=1&pageSize=20 +``` + +#### 响应示例 + +真实实测(团期批次 T26-3963,节选第 1 条完整记录): + +```json +{"code":200,"message":"成功","data":{"records":[{"orderId":"2104839654652121090","orderNo":"HL20260929154414207","teamNo":"26-2313","customerName":"李文博","participantCount":2,"orderStatus":"CUSTOMIZING","orderStatusName":"定制中","flowStatus":"RESOURCE_PREPARING","flowStatusName":"资源准备","reviewStatus":null,"reviewStatusName":null,"settlementStatus":"NONE","settlementStatusName":"未结算","payStatus":"FULLY_PAID","payStatusName":"已付全款","contractStatus":null,"contractStatusName":null,"insuranceStatus":null,"insuranceStatusName":null,"paidAmount":"7360.00","balanceAmount":"0.00","hotelRequirementStatus":null,"hotelRequirementStatusName":null,"vehicleRequirementStatus":"DONE","vehicleRequirementStatusName":"配车完成","vehicleRequirementKind":"TRAVEL","vehicleRequirementKindName":"行程用车","consultantName":"cw_test_7443","totalPrice":"7360.00","tierCode":"2A","tierName":"2成人","travelerInfoComplete":true,"roomCount":1,"roomType":null,"roomTypeName":null,"specialNeeds":"夫妻同行,携带摄影器材较多,需预留后备箱空间","contactPhone":"138****3046","groupChatUnreadCount":0,"travelers":[{"name":"李文博","type":"ADULT","age":46,"birthdayInTrip":false},{"name":"赵梦琪","type":"ADULT","age":43,"birthdayInTrip":false}]}],"total":3,"page":1,"pageSize":20},"traceId":null,"success":true} +``` + +#### 空数据 / 降级响应 + +- 团期批次不存在:返回业务错误(见"错误响应")。 +- 团期下暂无子订单(或 `includeCancelled=false` 时全部已取消):`records` 为空数组,`total=0`。 +- 该户无 active 车需求行:`vehicleRequirementStatus`/`vehicleRequirementKind` 均为 `null`;`vehicleRequirementStatusName`/`vehicleRequirementKindName` 按该户 `needsVehicle` 分叉——`true` 出"未提交",否则为 `null`。 + +#### 错误响应 + +```json +{"code":589500,"message":"团期不存在","data":null,"traceId":null,"success":false} +``` + +#### 业务边界 + +- `vehicleRequirementKind` 在本端点当前恒为 `TRAVEL`,不能据此推断"该团期没有接送机需求"——接送机需求存在与否要看配车芯片明细端点。 +- `vehicleRequirementStatusName` 的 `PENDING_REVIEW` 分支文案取决于 `vehicleRequirementKind`,但本端点该列恒为 TRAVEL,因此实际只会看到"待提交车务",不会看到"待审核"(后者只出现在配车芯片明细的 TRANSFER 项上)。 +- 无 active 车需求行时 `vehicleRequirementStatus` 为 `null` 不回落 `PENDING`(#8249 起既有行为,本次未变)——`PENDING` 是需求行的真实状态之一,没有行时借用它会与"未提交"矛盾。 + +--- + +## 四、契约约束与正确调用方式 + +> 本节只写后端接受/拒绝的规则与语义边界,不写 UI 渲染建议。 + +### ✅ 正确 / ❌ 错误 payload 对照 + +| 场景 | payload / 响应 | +|------|-----------------| +| ✅ 渲染配车芯片明细列表 | 按数组下标或 `orderId`+`kind` 复合键渲染每一项,允许同一 `orderId` 出现多行 | +| ✅ 展示配车芯片"已完成 N/M" | 直接用 `doneCount`/`totalCount`,两者已经是同一口径(条目数),无需自行按户去重再算 | +| ❌ 用 `items[].orderId` 做 `Map` 的 key 或做 `Set` 去重 | 一户两类需求时后写入的会覆盖/顶掉先写入的那一条,界面上会静默丢失一类需求的状态 | +| ❌ 用子订单列表的 `vehicleRequirementKind` 判断该团期是否存在接送机需求 | 该列当前恒为 TRAVEL,对接送机需求零分辨力;接送机需求判断要调配车芯片明细 | + +### 切换状态时的必要动作 + +前端如果此前在配车芯片渲染层用 `orderId` 做过 `key`/去重/索引,本次上线前必须改为 `orderId + kind` 复合键;否则界面在两类需求并存的户上会稳定丢失一类需求的展示,且是静默丢失(不报错)。 + +--- + +## 五、数据库行为 + +两个端点均为只读查询,无数据库写操作。配车芯片明细与子订单列表内部均从 `order_vehicle_requirement`(车需求行表)按 `order_id` 聚合取最新一版需求行,`kind` 直接取自需求行的 `requirement_kind` 列,不做兜底改写;历史行 `requirement_kind` 为 `NULL` 时 `kind`/`kindName` 原样透出 `null`,不会被兜底成 `TRAVEL`。 + +--- + +## 六、边界行为 + +- 户尚未提交任何车需求行、但已声明要用车(`needs_vehicle=true`)→ 配车芯片该户仍占一项,`kind`/`status` 为 `null`,`statusName`="未提交" +- 户完全不需要用车(`needs_vehicle=false`)→ 该户在配车芯片 `items[]` 中不出现 +- 户同时有 TRAVEL 与 TRANSFER 两类 active 需求行 → 配车芯片占两项,`orderId` 相同、`kind` 不同 +- 户只有一类需求行 → 配车芯片只占一项,不会补一个空的另一类占位项 +- 需求行历史 `requirement_kind` 为 `NULL` → `kind`/`kindName` 原样为 `null`,不兜底为 `TRAVEL` +- 子订单列表 `.../orders` 每户恒只出一行、按 TRAVEL 过滤取需求行,不受该户是否有 TRANSFER 需求影响 + +--- + +## 六.5、枚举 / 数据字典 + +### 用车类别(`VehicleRequirementKind`,`items[].kind` / `vehicleRequirementKind`) + +**所属字段**: `GroupBatchChipItemRespVO.kind`、`GroupBatchOrderItemRespVO.vehicleRequirementKind` | **类型**: `String` + +| 值 | 中文 | 本次是否新增 | 说明 | +|----|------|------|------| +| `TRAVEL` | 行程用车 | 既有枚举值,本次新增到这两个字段 | 团期行程内的用车安排 | +| `TRANSFER` | 接送机 | 既有枚举值,本次新增到这两个字段 | 接送机场/车站,独立于行程用车 | +| `null` | (无展示) | - | 该户尚无 active 车需求行时的状态,不是第三个枚举值 | + +### 车需求状态中文名(`statusName` / `vehicleRequirementStatusName`,`PENDING_REVIEW` 按 `kind` 分叉) + +**所属字段**: `items[].statusName`(配车芯片)、`vehicleRequirementStatusName`(子订单列表) | **类型**: `String` + +| 状态码 | 中文(本次前) | 中文(本次后) | 说明 | +|--------|----------------|----------------|------| +| `PENDING` | 待车务配(仅配车芯片明细,子订单列表原已是"待车队配") | 待车队配 | 两处读口统一为同一文案 | +| `PROCESSING` | 配车中 | 配车中 | 未变 | +| `DONE` | 配车完成 | 配车完成 | 未变 | +| `PENDING_REVIEW`(TRAVEL) | 待审核(配车芯片明细此前不分类别) | 待提交车务 | 按 `kind` 新分叉 | +| `PENDING_REVIEW`(TRANSFER) | 待审核 | 待审核 | 未变(新分叉后仍是这个文案) | +| `REJECTED_TO_CONSULTANT` | 驳回给定制师(仅配车芯片明细) | 已驳回定制师 | 两处读口统一为同一文案 | +| `REJECTED_TO_ADMIN` | 驳回给管理员(仅配车芯片明细) | 已驳回管理员 | 两处读口统一为同一文案 | +| `null`(有需求但未提交) | 待审核(配车芯片明细此前误落到这一支) | 未提交 | 修正误报——"未提交"与"已提交等审核"是两个不同状态,此前混在一起 | + +## 六.6、修改前后对比 + +### 字段级对比 + +| 字段 | 改前 | 改后 | +|------|------|------| +| `GroupBatchChipItemRespVO.kind` | 不存在 | 新增,`String`,可空,仅配车芯片有值,其余五芯片恒 `null` | +| `GroupBatchChipItemRespVO.kindName` | 不存在 | 新增,`String`,可空 | +| `GroupBatchChipDetailVO.totalCount`(配车芯片) | 户数 | 条目数(一户两类算两条) | +| `GroupBatchChipDetailVO.doneCount`(配车芯片) | 已完成户数 | 已完成条目数 | +| `GroupBatchChipDetailVO.items[]`(配车芯片) | 一户一项 | 一户每类一项,`orderId` 可重复 | +| `GroupBatchOrderItemRespVO.vehicleRequirementKind` | 不存在 | 新增,`String`,可空,当前恒为 `TRAVEL` | +| `GroupBatchOrderItemRespVO.vehicleRequirementKindName` | 不存在 | 新增,`String`,可空 | +| 团期列表/看板 `chipStats.vehicle.total`/`.done` | 户数 | 条目数(共用配车芯片同一聚合算法,联动变化,字段本身未新增) | + +### 行为级对比 + +| 场景 | 改前 | 改后 | +|------|------|------| +| 户同时有 TRAVEL + TRANSFER 需求 | 配车芯片只显示其中一类,另一类无声消失 | 两类各占一项,均可见 | +| 户已声明用车但未提交需求行 | 落到"待审核",看起来像已提交 | 落到"未提交",与已提交待审核区分开 | +| `PENDING_REVIEW` 状态文案 | 配车芯片明细恒显示"待审核";子订单列表已按 kind 分叉(源于 #8218) | 配车芯片明细与子订单列表口径统一,均按 kind 分叉 | +| 配车芯片 `PENDING`/`REJECTED_TO_CONSULTANT`/`REJECTED_TO_ADMIN` 文案 | 待车务配/驳回给定制师/驳回给管理员 | 待车队配/已驳回定制师/已驳回管理员 | +| 配车芯片 `totalCount`/`doneCount` 与列表页 `chipStats.vehicle` 关系 | 两处各自独立计算,可能不一致 | 共用同一聚合算法,逐位一致 | + +## 六.7、影响评估 + +- **是否破坏向后兼容**: 是——`.../chips/vehicle` 的 `items[]` 数组长度与 `orderId` 唯一性假设改变,任何按 `orderId` 做 key/去重/索引的前端代码都会在两类需求并存的户上产生数据丢失;`totalCount`/`doneCount` 数值口径也变了,若前端拿它们除以户数算百分比会得到错误结果。 +- **前端是否必须同步上线**: 是(针对配车芯片展示场景)——只要页面渲染配车芯片明细,就必须按 `orderId+kind` 复合键处理 `items[]`;子订单列表的两个新字段是纯新增,不改也不会报错,但不展示就拿不到用车类别信息。 +- **前端 workaround 清理点**: 若此前为"同一户两类需求显示不全/互相覆盖"这类现象写过特殊兼容或只取第一条的逻辑,现在后端已按类别拆项,可以确认不再需要。 + +--- + +## 七、不影响范围 + +- **仅影响**: 配车芯片明细端点 `GET .../chips/vehicle` 的 `items[]` 结构与 `totalCount`/`doneCount` 语义;子订单列表端点 `GET .../orders` 新增两个字段;团期列表/看板端点 `chipStats.vehicle` 的计数口径(字段本身未变)。 +- **零影响**: + - 房/导/摄/约/保五个芯片明细端点(`GET .../chips/{hotel|guide|photo|contract|insurance}`)的字段结构与计数口径 + - 团期下子订单列表其余既有字段(金额、支付、结算、房需求等) + - 户级用车需求明细端点 `GET .../requirement/vehicle-households`(本次为纯内部代码去重,零响应契约变化,见下方"八、测试环境已验证"说明) + - 配车需求本身的写口(提交/确认/驳回等)不受本次改动影响 + +--- + +## 八、测试环境已验证 + +服务:`hl-order-service-v3`,dev-v3 分支部署测试网关 @ `99fb369ba`(含 #8543 所在提交 `bc80606ac2`),测试网关 `https://api.test.1814.love`;样本均为测试服现存真实业务数据: + +``` +✓ 团期批次 T26-3963(groupBatchId=2104839654727618562,3 户): + GET .../chips/vehicle → totalCount=5 doneCount=3 + 李文博(orderId=2104839654652121090):TRAVEL/DONE/配车完成 + TRANSFER/PENDING/待车队配(同一 orderId 两项) + 那顺(orderId=2104839729176514562):TRAVEL/DONE/配车完成 + TRANSFER/PENDING_REVIEW/待审核(同一 orderId 两项) + 张丽娟(orderId=2104839686486888449):仅 TRAVEL/DONE/配车完成(一项) + GET .../orders → 3 条记录,vehicleRequirementKind 均为 "TRAVEL"(与源码"本列当前恒为 TRAVEL"一致) +✓ 团期批次 T26-6001(groupBatchId=2104840641651556353,3 户): + GET .../chips/vehicle → totalCount=3 doneCount=1 + 董海涛:TRAVEL/DONE/配车完成 + TRANSFER/PENDING/待车队配 + 谢丽萍:TRAVEL/PENDING_REVIEW/待提交车务(复现"待提交车务"文案分支) +✓ 团期列表页 GET /v3/admin/order/group-batch 逐页扫描,同两个 groupBatchId 的 chipStats.vehicle: + {total:5, done:3} 与 {total:3, done:1},与明细端点 totalCount/doneCount 逐位一致(重复请求 5 次读数稳定) +✓ 错误响应:不存在的 groupBatchId → 两端点均返回 {"code":589500,"message":"团期不存在"} +``` + +注:`kind=null`(户已声明用车但未提交需求行,`statusName`="未提交")与配车芯片明细的 `REJECTED_TO_CONSULTANT`/`REJECTED_TO_ADMIN` 两个文案分支,本轮实测样本团期未覆盖到(样本户均已提交需求行且未被驳回);这三个分支的契约已在源码逐一核实(`GroupBatchChipResolver.vehicleItemsOf`、`GroupBatchConverter.resolveRequirementStatusName`),前端应按此契约实现,不依赖本轮是否观测到该取值。 + +--- + +## 十、相关文档 + +- 关联 Issue: [wx/HL#8543](https://git.1814.love/wx/HL/issues/8543) +- 关联 PR: [wx/HL#8592](https://git.1814.love/wx/HL/pulls/8592) + +## 关联 / 联系人 + +### 链接 + +- **Issue**: [#8543](https://git.1814.love/wx/HL/issues/8543) +- **PR**: [#8592](https://git.1814.love/wx/HL/pulls/8592) +- **Merge commit**: [bc80606ac2](https://git.1814.love/wx/HL/commit/bc80606ac2) + +### 联系人 + +- **后端负责人**: @wx diff --git a/changelogs-v2/2026-09/30_8548_整团免车放行户级接送机需求并补需求重开待审户读数-修改接口-管理后台.md b/changelogs-v2/2026-09/30_8548_整团免车放行户级接送机需求并补需求重开待审户读数-修改接口-管理后台.md new file mode 100644 index 00000000..cbc2e79b --- /dev/null +++ b/changelogs-v2/2026-09/30_8548_整团免车放行户级接送机需求并补需求重开待审户读数-修改接口-管理后台.md @@ -0,0 +1,474 @@ +--- +schema: "hl-changelog/v2" +ticket: "8548" +title: "团期需求重开待审提示接入 4 个只读端点,并修复整团免车团确认时接送机需求放行不到的问题" +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 #8586 合并 dev-v3(ddea7e710c);测试网关部署确认:hl-order-service-v3 @ ff6863754、hl-fleet-service @ 99fb369ba(deploy-status.sh 实测,两者均以 ddea7e710c 为祖先)。4 个只读端点中 3 个(团期详情 A2、房务看板详情 H2、fleet 配车总览)已用同一真实团期(groupBatchId=2104839654727618562,团号 T26-3963)实测捕获非空取值,三端一致;第 4 个(房务看板列表 H1)因该团未被房务认领、不出现在列表口,改用另一真实团期(groupBatchId=2104838272570245121)捕获到已确认团的双 null 基线,未能在本轮独立捕获 H1 的非空实例——这是列表口与详情口可见集合不同导致的结构性限制,不是契约缺口,H1 的字段契约与 H2/A2/总览完全同源同算法(同一个 GroupBatchRequirementReopenHintService)。#8548 的确认端点行为修复(整团免车放行接送机)本身是写操作,为避免误改测试服现存业务数据未做原子调用,已按源码逐行核实:GroupBatchRequirementService.java 505-593 行(doConfirm 内核 javadoc 与分支代码)、1161-1296 行(release 集合装配与 waivedVehicleSnapshot)。" +updated_at: "2026-09-30" +base: "dev-v3" +--- + +# hl-order-service-v3 / hl-fleet-service:团期需求重开待审提示接入 4 个只读端点,并修复整团免车团确认时接送机需求放行不到的问题 + +> **存放目录**: `changelogs-v2/2026-09/` +> **服务**: hl-order-service-v3(主,#8549 新提示的唯一产出口 + #8548 行为修复)、hl-fleet-service(透传消费方,无独立业务逻辑改动) +> **PR**: #8586 +> **Issue**: #8548、#8549(一个 PR 同时处理两张关联工单) +> **日期**: 2026-09-30 +> **影响范围**: 4 个只读端点响应新增 2 字段(团期详情 A2、房务看板列表 H1、房务看板详情 H2、fleet 团期配车总览);1 个写端点(团期整体确认需求)行为修复,响应结构不变 + +--- + +## ⚠️ 关键变化 + +- 新增 2 个响应字段:`requirementReopenPendingHouseholds`(`Integer`)、`requirementReopenResourceType`(`String`),出现在 4 个只读端点:`GET /v3/admin/order/group-batch/{groupBatchId}`、`GET /v3/admin/house/group-batches`、`GET /v3/admin/house/group-batches/{groupBatchId}`、`GET /admin/fleet/group-dispatch/batches/{groupBatchId}/overview`。四处取值同源同算法(`GroupBatchRequirementReopenHintService`,唯一产出口),不存在四套口径分叉的风险。 +- 🔴 **`null` 不代表 `0`,不要折算成 0 渲染「0 户需求待审核」**。字段只在同时满足 3 个条件时才非空:① 团级 `requirementConfirmed=false`;② 能查到「定制师改需求触发的自动重开」留痕(区别于管理员手动打回,后者无此留痕);③ 重开后该团确实还有在团户处于待审核状态。三者任一不满足,两个新字段都是 `null`,前端应继续渲染原有的「待管理员重新确认」文案。 +- **两个新字段不是「要么都有要么都无」的一对**:`requirementReopenPendingHouseholds` 非空时,`requirementReopenResourceType` 仍可能单独为 `null`(重开留痕的 `extra` JSON 解析失败/缺键时的降级),此时只渲染「N 户需求待审核」,不带 HOTEL/VEHICLE 类别文案,户数本身不受影响。 +- **不要与既有字段 `pendingReviewHouseholds` 混淆**(仅 H2 详情端点有此字段):`pendingReviewHouseholds` 是房务看板自己的统计口径,只数房需求,任何时候都下发;新增的 `requirementReopenPendingHouseholds` 是团级确认闸的提示,数的是房、车两类待审需求的户去重并集,且只在上述 3 道闸门都满足时才有值。同一个团这两个数字不一致是正常的,不能互相对账。 +- **#8548 行为修复(不涉及任何字段新增/删除)**:`POST /v3/admin/order/group-batch/{groupBatchId}/requirement/confirm` 整团确认时,若团期处于「整团免车」(管理员声明免车,`vehicleWaived=true`)状态,此前该分支对车侧释放集合恒返回空,导致该团后续补交的接送机(TRANSFER)需求永远放行不到、团级需求闸永久卡在待确认。修复后免车团确认会释放 **TRANSFER** 需求,但仍**不释放 TRAVEL**(整团免车声明的管辖范围只到 TRAVEL,释放 TRAVEL 等于替管理员推翻免车声明)。可观察的变化只是响应里既有字段 `transferDispatchedOrderIds`/`vehicleDispatchedCount` 现在对免车团也可能非空,响应 VO 结构本身零改动。 + +--- + +## 二、变更接口清单 + +| # | 接口 | 方法 | 路径 | 变更类型 | 说明 | +|---|------|------|------|----------|------| +| 1 | A2 团期详情 | GET | `/v3/admin/order/group-batch/{groupBatchId}` | 字段新增(非破坏性) | 响应新增 `requirementReopenPendingHouseholds`/`requirementReopenResourceType` | +| 2 | H1 房务团期看板列表 | GET | `/v3/admin/house/group-batches` | 字段新增(非破坏性) | 同上,列表项级别 | +| 3 | H2 房务团期看板详情 | GET | `/v3/admin/house/group-batches/{groupBatchId}` | 字段新增(非破坏性) | 同上 | +| 4 | 团期配车总览 | GET | `/admin/fleet/group-dispatch/batches/{groupBatchId}/overview` | 字段新增(非破坏性) | 同上,order-v3 原样透传,fleet 不自算 | + +--- + +## 三、接口详情 + +### 1. A2 团期详情 `GET /v3/admin/order/group-batch/{groupBatchId}` + +**VO**: `(无请求体,仅路径参数)` → `GroupBatchDetailRespVO` + +#### 使用场景 + +团期管理员在团期详情页查看需求确认状态。此前该页只能看到 `requirementConfirmed=false`,无法区分「等定制师第一次提交」「被管理员打回」「已重新提交但还有户没处理完」三种情况,一律渲染「待管理员重新确认」。本次起,属于第三种情况(定制师改需求触发的自动重开,且确实还有户在等审)时,响应额外带出具体待审户数与是谁(房/车)推倒了确认闸。 + +#### 入参字段表 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| groupBatchId | Path | Long | ✅ | - | 团期聚合主键 | + +#### 出参字段表 + +以下是本次新增/说明文案更新的字段;其余既有字段结构未变,不重复列出。 + +| 字段 | 类型 | 说明 | +|------|------|------| +| requirementConfirmed | Boolean | 需求整体确认标记(既有字段,说明文案本次更新):为 `false` 时不要直接渲染「待管理员重新确认」,先看 `requirementReopenPendingHouseholds` | +| requirementReopenPendingHouseholds | Integer | **新增**。需求待审核户数:`requirementConfirmed=false` 且是「定制师改需求触发的自动重开」时下发;为 `null` 表示不下发(已确认 / 管理员打回置 0 / 已无人待审),按原有文案渲染 | +| requirementReopenResourceType | String | **新增**。触发最近一次需求重开的资源类别 `HOTEL` / `VEHICLE`:只标「谁把确认闸推倒了」,与户数口径无关(户数是房、车两类的并集);取不到时为 `null`,此时文案不带类别 | + +#### 请求示例 + +```http +GET /v3/admin/order/group-batch/2104839654727618562 +``` + +#### 响应示例 + +真实实测(测试网关,业务 admin 身份)。以下为节选(仅摘录本次相关字段,其余既有字段结构未变,不重复列出): + +```json +{ + "code": 200, + "data": { + "groupBatchId": "2104839654727618562", + "batchNo": "T26-3963", + "requirementConfirmed": false, + "requirementReopenPendingHouseholds": 1, + "requirementReopenResourceType": "VEHICLE" + }, + "success": true +} +``` + +#### 空数据 / 降级响应 + +- 3 道闸门任一不满足(已确认 / 管理员手动打回 / 重开后已无人待审):两个新字段均为 `null`,前端按原有文案渲染。 +- 重开留痕的 `extra` JSON 解析失败或缺键:仅 `requirementReopenResourceType` 单独降级为 `null`,`requirementReopenPendingHouseholds` 不受影响照常下发(服务端 `readResourceType()` 的不对称降级,不会因为类别取不到而连户数一起丢)。 + +#### 错误响应 + +```json +{"code":589500,"message":"团期不存在","data":null,"traceId":null,"success":false} +``` + +其余既有错误码本次未变:589507(无操作权限:当前角色未授予团期权限,或该团期不在您名下)。 + +#### 业务边界 + +- 🔴 `requirementReopenPendingHouseholds` 为 `null` 不代表 0,不要折算成 0 渲染。 +- `requirementReopenResourceType` 可能单独为 `null`(即使户数非空),此时只显示户数、不带类别文案。 +- 判断是否要展示这两个字段,先看 `requirementConfirmed`;`requirementConfirmed=true` 时两个新字段恒为 `null`,不需要额外判断。 + +--- + +### 2. H1 房务团期看板列表 `GET /v3/admin/house/group-batches` + +**VO**: `HouseGroupBatchBoardPageReqVO` → `PageResult` + +#### 使用场景 + +房务在看板列表页浏览已认领的团期。列表项与详情页(见下)共用同一套团级字段判定逻辑,此前列表页同样只能看到 `requirementConfirmed=false` 一个布尔值,本次起可展示与详情页一致的待审户数与类别提示。 + +#### 入参字段表 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| scope | Query | String | ❌ | 最长 8 | 可见范围 `MINE`(默认,只看本人认领)/ `ALL`(看全部已认领团,#8491 起全体房务可传) | +| claimerAdminId | Query | Long | ❌ | - | 按认领人筛(仅 `scope=ALL` 生效) | +| planStatus | Query | String | ❌ | 最长 16 | 计划行状态 `PENDING` / `CONFIRMED`,不传=全部 | +| batchStatus | Query | String | ❌ | 最长 200 | 团期状态多选,逗号分隔;默认四态;`CANCELLED` 传入被忽略 | +| stayDateFrom | Query | LocalDate | ❌ | ISO 日期 | 住期区间起 | +| stayDateTo | Query | LocalDate | ❌ | ISO 日期 | 住期区间止 | +| departDateFrom | Query | LocalDate | ❌ | ISO 日期 | 出发日区间下界 | +| departDateTo | Query | LocalDate | ❌ | ISO 日期 | 出发日区间上界 | +| keyword | Query | String | ❌ | 最长 32 | 团期号或产品名包含匹配 | +| page | Query | Long | ❌ | ≥1,默认 1 | 页码 | +| pageSize | Query | Long | ❌ | 1~50,默认 20 | 每页条数 | + +#### 出参字段表 + +以下是本次新增/说明文案更新的字段(列表项级别);其余既有字段结构未变,不重复列出。 + +| 字段 | 类型 | 说明 | +|------|------|------| +| requirementConfirmed | Boolean | 需求整体确认标记(既有字段,说明文案本次更新):为 `false` 时先看 `requirementReopenPendingHouseholds` 再定文案 | +| requirementReopenPendingHouseholds | Integer | **新增**。语义与 A2 完全一致(同一产出口) | +| requirementReopenResourceType | String | **新增**。语义与 A2 完全一致 | + +#### 请求示例 + +```http +GET /v3/admin/house/group-batches?scope=ALL&pageSize=50 +``` + +#### 响应示例 + +真实实测(测试网关,房务角色)。列表口只显示**已被房务认领**的团,本次实测命中的这一条是已确认团(双 `null` 基线),节选: + +```json +{ + "code": 200, + "data": { + "list": [ + { + "groupBatchId": "2104838272570245121", + "requirementConfirmed": true, + "requirementReopenPendingHouseholds": null, + "requirementReopenResourceType": null + } + ], + "total": 1 + }, + "success": true +} +``` + +> 说明:本轮实测范围内命中的已认领团恰好都是已确认状态,未独立捕获非空实例;非空实例已在 A2/H2/fleet 总览三端用同一真实团期(T26-3963)交叉验证一致,H1 走的是同一个 `GroupBatchRequirementReopenHintService` 产出口,字段契约同源,只是列表口的可见集合(仅已认领团)与详情口不同。 + +#### 空数据 / 降级响应 + +- 无匹配团期:`list` 为空数组,`total` 为 0(既有行为未变)。 +- 候选集超 500 时 `total` 返回 -1 表示未统计(既有行为,本次未变,与新字段无关)。 +- 3 道闸门任一不满足:该行的两个新字段均为 `null`。 + +#### 错误响应 + +```json +{"code":808090,"message":"未登录或非房务角色,无权操作","data":null,"traceId":null,"success":false} +``` + +#### 业务边界 + +- 未认领的团不在本列表口,与团期抢单池页以「认领动作」为界互斥,新字段不改变这条边界。 +- 同上,`null` 不代表 0;`requirementReopenResourceType` 可单独为 `null`。 + +--- + +### 3. H2 房务团期看板详情 `GET /v3/admin/house/group-batches/{groupBatchId}` + +**VO**: `(无请求体,仅路径参数)` → `HouseGroupBatchBoardRespVO` + +#### 使用场景 + +房务点开具体团期查看逐日配房详情。全体房务可读任意团期(不校验认领归属),他人认领的团返回 `readOnly=true`(#8491,与本次改动无关)。 + +#### 入参字段表 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| groupBatchId | Path | Long | ✅ | - | 团期主订单 ID | + +#### 出参字段表 + +以下是本次新增/说明文案更新的字段;其余既有字段(`hotelReady`、`pendingReviewHouseholds`、`days[]` 等)结构未变,不重复列出。 + +| 字段 | 类型 | 说明 | +|------|------|------| +| requirementConfirmed | Boolean | 需求整体确认标记(既有字段,说明文案本次更新):为 `false` 时先看 `requirementReopenPendingHouseholds` 再定文案 | +| requirementReopenPendingHouseholds | Integer | **新增**。语义与 A2 完全一致;🔴 与既有字段 `pendingReviewHouseholds` **不是一回事**——后者只数房需求、任何时候都下发,前者是房车并集且只在 3 道闸门满足时下发,同一个团两者数字不一致是正常的,不能互相对账 | +| requirementReopenResourceType | String | **新增**。语义与 A2 完全一致 | + +#### 请求示例 + +```http +GET /v3/admin/house/group-batches/2104839654727618562 +``` + +#### 响应示例 + +真实实测(测试网关,房务角色)。节选: + +```json +{ + "code": 200, + "data": { + "groupBatchId": "2104839654727618562", + "requirementConfirmed": false, + "requirementReopenPendingHouseholds": 1, + "requirementReopenResourceType": "VEHICLE", + "pendingReviewHouseholds": 0 + }, + "success": true +} +``` + +> 与同一团在 A2、fleet 总览的实测结果逐字段一致(`requirementReopenPendingHouseholds=1`、`requirementReopenResourceType="VEHICLE"`),印证三端同源同算法;`pendingReviewHouseholds=0` 与 `requirementReopenPendingHouseholds=1` 在此例中不同,正是上表说明的两套口径不对账的真实样本。 + +#### 空数据 / 降级响应 + +- 3 道闸门任一不满足:两个新字段均为 `null`。 +- `requirementReopenResourceType` 单独降级为 `null` 时 `requirementReopenPendingHouseholds` 不受影响。 + +#### 错误响应 + +```json +{"code":589500,"message":"团期不存在","data":null,"traceId":null,"success":false} +``` + +```json +{"code":808090,"message":"未登录或非房务角色,无权操作","data":null,"traceId":null,"success":false} +``` + +#### 业务边界 + +- 🔴 `requirementReopenPendingHouseholds` 非空不要与 `pendingReviewHouseholds` 混淆或相加,两者统计口径不同。 +- `null` 不代表 0;`requirementReopenResourceType` 可单独为 `null`。 + +--- + +### 4. fleet 团期配车总览 `GET /admin/fleet/group-dispatch/batches/{groupBatchId}/overview` + +**VO**: `(无请求体,仅路径参数)` → `GroupDispatchOverviewRespVO` + +#### 使用场景 + +车务在配车总览页查看该团的需求确认状态、逐日排车与接送机缺口。`requirementReopenPendingHouseholds`/`requirementReopenResourceType` 由 order-v3 内部覆盖口(`GET /v3/internal/group-batch/{id}/vehicle-coverage`,Feign 专用,非前端可直接调用)原样透传,fleet 侧不做任何二次计算,与 A2/H2 保证同一口径。 + +#### 入参字段表 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| groupBatchId | Path | Long | ✅ | - | 团期主订单 ID | + +#### 出参字段表 + +以下是本次新增/说明文案更新的字段;其余既有字段(`serviceDates`、`vehicleReady`、`days[]`、`orders[]`、`transferPendingTotal`、`conversationKey` 等)结构未变,不重复列出。 + +| 字段 | 类型 | 说明 | +|------|------|------| +| requirementConfirmed | Boolean | 整团需求是否已确认(既有字段,说明文案本次更新):为 `false` 时先看 `requirementReopenPendingHouseholds` 再定文案 | +| requirementReopenPendingHouseholds | Integer | **新增**。order-v3 覆盖口原样透传,fleet 不自算;🔴 `null` 不代表 0,不要折算成 0 渲染 | +| requirementReopenResourceType | String | **新增**。order-v3 覆盖口原样透传,语义与 A2 完全一致 | + +#### 请求示例 + +```http +GET /admin/fleet/group-dispatch/batches/2104839654727618562/overview +``` + +#### 响应示例 + +真实实测(测试网关,车务角色,roleId=5)。节选: + +```json +{ + "code": 200, + "data": { + "groupBatchId": "2104839654727618562", + "batchNo": "T26-3963", + "requirementConfirmed": false, + "requirementReopenPendingHouseholds": 1, + "requirementReopenResourceType": "VEHICLE" + }, + "success": true +} +``` + +> 与同一团在 A2、H2 的实测结果逐字段一致。 + +#### 空数据 / 降级响应 + +- 3 道闸门任一不满足:两个新字段均为 `null`,与 A2/H2 同步(同一份覆盖口数据)。 +- order-v3 覆盖口不可达时,整个端点按既有降级规则返回 600012(团期配车基线不可达),不会出现「新字段单独降级、其余字段正常」的中间态——两个新字段与其余团级字段是同一次 Feign 调用的产物,不可能分开失败。 + +#### 错误响应 + +```json +{"code":600012,"message":"团期配车基线不可达,请稍后重试","data":null,"traceId":null,"success":false} +``` + +其余既有错误码本次未变:401(未登录)。 + +#### 业务边界 + +- 🔴 `requirementReopenPendingHouseholds` 为 `null` 不代表 0。 +- 该字段与 fleet 自己的 `transferPendingTotal`(接送机未配计数)是两回事:前者是「团级需求确认闸的提示」,后者是「已确认需求里还有多少接送机缺口没排车」,两者可以同时非空,互不覆盖。 + +--- + +## 四、契约约束与正确调用方式 + +> 本节只写后端响应字段的正确消费方式,不写 UI 渲染建议。 + +### ✅ 正确 / ❌ 错误 payload 对照 + +| 场景 | 说明 | +|------|------| +| ✅ 判断是否展示「N 户需求待审核」 | 先判 `requirementConfirmed===false`,再判 `requirementReopenPendingHouseholds != null` | +| ✅ 处理 `requirementReopenPendingHouseholds` 非空但 `requirementReopenResourceType` 为 `null` | 只渲染「N 户需求待审核」,不带类别文案,这是合法的降级态,不是异常 | +| ❌ 把 `requirementReopenPendingHouseholds` 为 `null` 折算成 0 渲染 | `null` 与「0 户待审」是两种不同状态:前者是「不适用/无需提示」,后者是「重开了但已处理完」——本次实现中「已处理完」同样落到 `null`(闸门 3 过滤),所以两者当前观察上是同一渲染结果,但契约上不保证永远如此,不要做数值折算 | +| ❌ 拿 H2 的 `pendingReviewHouseholds` 和 `requirementReopenPendingHouseholds` 相加或对账 | 两个字段统计口径不同(前者只数房、恒下发;后者数房车并集、条件下发) | + +### 切换状态时的必要动作 + +无。本次 4 个端点均为只读字段新增,不涉及任何请求体/入参变化,前端无需在调用序列上做任何调整。 + +--- + +## 五、数据库行为 + +`GroupBatchRequirementReopenHintService` 只读、不写任何表。查库固定 3 次批量查询(不随团期数量线性增长):① 从 `group_batch` 内存过滤未确认团;② 按团期 ID 批量查 `group_batch` 状态时间线,取最近一条 `BATCH_REQUIREMENT_REOPENED` 事件日志;③ 按事件命中的团批量取在团子订单 ID,再批量查待审核户。看板一页多团时同样是固定 3 次查询,不退化为 N+1。 + +#8548 修复:`doConfirm` 内核在整团免车分支新增一次车侧需求释放调用(`dispatchGroupTransferRequirements`),与既有的房侧放行在同一事务内,任一步失败整团零写入(既有的 809112 整团回滚保证不变)。 + +--- + +## 六、边界行为 + +- 团级 `requirementConfirmed=true`(已确认)→ 4 个端点的两个新字段恒为 `null`。 +- `requirementConfirmed=false` 但查不到 `BATCH_REQUIREMENT_REOPENED` 留痕(即管理员手动打回,而非定制师改需求触发的自动重开)→ 两个新字段恒为 `null`,前端渲染原有的「待管理员重新确认」文案。 +- `requirementConfirmed=false` 且有重开留痕,但重开后该团在团户已全部处理完(待审户数为 0)→ 两个新字段恒为 `null`。 +- 重开留痕的 `extra` JSON 缺失/为空/解析失败 → 仅 `requirementReopenResourceType` 单独为 `null`,`requirementReopenPendingHouseholds` 不受影响。 +- **(#8548,确认端点行为变更,非本次响应字段变化)** 整团免车团(`vehicleWaived=true`)确认时,此前车侧释放集合恒为空,接送机需求永远放行不到;修复后释放 **TRANSFER**(接送机)需求,仍不释放 **TRAVEL**(行程用车,整团免车声明的管辖范围仅限于此);同时不推进正式团级用车需求(`groupVehicleRequirementId`/`Status`/`Version` 三个既有字段在免车分支仍为 `null`,因为免车团本就没有需要推进的正式需求,此行为本次未变)。 + +--- + +## 六.5、枚举 / 数据字典 + +### 需求重开资源类别(`requirementReopenResourceType`) + +**所属字段**: `requirementReopenPendingHouseholds` 的伴生字段,4 个端点通用 | **类型**: `String`(取不到时为 `null`) + +| 值 | 含义 | 说明 | +|----|------|------| +| `HOTEL` | 定制师改住宿需求触发的自动重开 | | +| `VEHICLE` | 定制师改用车需求触发的自动重开 | | +| `null` | 取不到类别,或未落入需要下发的场景 | 户数字段仍可能非空,参见六、边界行为 | + +取值来源:团期状态时间线里最近一条 `BATCH_REQUIREMENT_REOPENED` 事件日志的 `extra` JSON 中 `resourceType` 键,由触发重开的那条业务逻辑写入;本次未新增写入路径,只新增读取与下发。 + +--- + +## 六.6、修改前后对比 + +### 字段级对比 + +| 字段 | 改前 | 改后 | +|------|------|------| +| `requirementReopenPendingHouseholds` | 不存在(4 个端点均无) | 新增,`Integer`,语义见上,4 端点同源同算法 | +| `requirementReopenResourceType` | 不存在(4 个端点均无) | 新增,`String`,语义见上 | + +### 行为级对比 + +| 场景 | 改前 | 改后 | +|------|------|------| +| `requirementConfirmed=false` 且是定制师改需求触发的自动重开、确实还有户在等审 | 4 个端点均只有 `requirementConfirmed=false`,前端一律渲染「待管理员重新确认」,无法与「管理员手动打回」区分 | 额外带出具体待审户数与资源类别,可渲染「N 户需求待审核」区分于打回场景 | +| 整团免车团确认(`POST .../requirement/confirm`) | 车侧释放集合恒为空,免车后补交的接送机需求永远放行不到,团级需求闸永久卡在待确认,无任何报错或日志提示 | 释放 TRANSFER 需求(不释放 TRAVEL),响应既有字段 `transferDispatchedOrderIds`/`vehicleDispatchedCount` 对免车团可能非空 | + +## 六.7、影响评估 + +- **是否破坏向后兼容**: 否——4 个只读端点均为纯字段新增,既有字段类型/取值/含义均未变;#8548 修复不改变响应 VO 结构,只改变部分既有字段(`transferDispatchedOrderIds`/`vehicleDispatchedCount`)在特定场景下的实际取值。旧前端忽略新字段不受任何影响。 +- **前端是否必须同步上线**: 否(不上线不会报错或丢功能);建议同步——上线后可以把「待管理员重新确认」与「N 户需求待审核」两种场景分开展示,减少运营/房务/车务误判为同一种阻塞。 +- **前端 workaround 清理点**: 若此前为区分「打回」与「重开待审」两种 `requirementConfirmed=false` 场景写过额外查询或猜测逻辑,现在可以直接用新字段替换。 + +--- + +## 七、不影响范围 + +- **仅影响**: 上表列出的 4 个只读端点的响应字段;`POST /v3/admin/order/group-batch/{groupBatchId}/requirement/confirm` 端点在整团免车场景下的车侧释放行为。 +- **零影响**: + - 4 个只读端点的请求参数与既有校验规则 + - `POST .../requirement/confirm` 的请求体、响应 VO 结构、非免车团的确认行为 + - `POST .../requirement/confirm` 在免车团场景下对 TRAVEL 需求的处理(仍不放行,逐单放行入口不受影响) + - `GET /v3/internal/group-batch/{id}/vehicle-coverage` 内部 Feign 端点之外的其它 internal 接口 + - `PUT /admin/fleet/assignments/pickup-dropoff-config` 等接送机配置端点 + +--- + +## 八、测试环境已验证 + +服务:`hl-order-service-v3` @ `ff6863754`、`hl-fleet-service` @ `99fb369ba`(deploy-status.sh 实测部署登记,测试网关 `https://api.test.1814.love`);`git merge-base --is-ancestor ddea7e710c ff6863754` 与 `... 99fb369ba` 均为真,确认本单所在提交已随两个服务的当前部署一并上线。 + +``` +✓ GET /v3/admin/order/group-batch/2104839654727618562(业务 admin):真实返回 + requirementConfirmed=false, requirementReopenPendingHouseholds=1, requirementReopenResourceType="VEHICLE" +✓ GET /v3/admin/house/group-batches/2104839654727618562(房务角色):同一团返回同一取值, + 三端一致;附带既有字段 pendingReviewHouseholds=0,印证两套口径不对账 +✓ GET /admin/fleet/group-dispatch/batches/2104839654727618562/overview(车务角色,roleId=5): + 同一团返回同一取值,三端一致 +✓ GET /v3/admin/house/group-batches?scope=ALL&pageSize=50(房务角色):真实返回列表, + 命中已认领团(groupBatchId=2104838272570245121)为已确认状态, + requirementReopenPendingHouseholds=null、requirementReopenResourceType=null,验证了 null 基线分支 +``` + +注:H1 列表口本轮未独立捕获非空实例——T26-3963(本次用于交叉验证的团)未被房务认领,不出现在 H1 的可见集合里;H1 与其余三端共用同一个 `GroupBatchRequirementReopenHintService` 产出口,字段契约同源,此限制是列表口「仅显示已认领团」这一既有边界导致的取样限制,不是实现差异。 + +`#8548` 确认端点在整团免车分支释放 TRANSFER 需求的修复,本轮未做真实原子调用验证(该端点为写端点,会推进团级需求状态,测试服现存数据上误调用有污染业务状态的风险);已按源码逐行核实:`GroupBatchRequirementService.java` 505-593 行(`doConfirm` 内核 javadoc 第三段与 569-572 行分支代码)、1161-1296 行(放行集合装配 javadoc 与 `waivedVehicleSnapshot` 方法 javadoc)。前端如需验证该行为,应在确认后核对响应里 `transferDispatchedOrderIds` 是否包含预期订单,而不是依赖某个新字段(该端点响应结构本次未变)。 + +--- + +## 十、相关文档 + +- 关联 Issue: [wx/HL#8548](https://git.1814.love/wx/HL/issues/8548)、[wx/HL#8549](https://git.1814.love/wx/HL/issues/8549) +- 关联 PR: [wx/HL#8586](https://git.1814.love/wx/HL/pulls/8586) + +## 关联 / 联系人 + +### 链接 + +- **Issue**: [#8548](https://git.1814.love/wx/HL/issues/8548)、[#8549](https://git.1814.love/wx/HL/issues/8549) +- **PR**: [#8586](https://git.1814.love/wx/HL/pulls/8586) +- **Merge commit**: [ddea7e710c](https://git.1814.love/wx/HL/commit/ddea7e710c) + +### 联系人 + +- **后端负责人**: @wx diff --git a/changelogs-v2/2026-09/30_8560_矩阵未派订单卡下发用车需求类别可区分行程用车与接送机-修改接口-管理后台.md b/changelogs-v2/2026-09/30_8560_矩阵未派订单卡下发用车需求类别可区分行程用车与接送机-修改接口-管理后台.md new file mode 100644 index 00000000..35822ed6 --- /dev/null +++ b/changelogs-v2/2026-09/30_8560_矩阵未派订单卡下发用车需求类别可区分行程用车与接送机-修改接口-管理后台.md @@ -0,0 +1,237 @@ +--- +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` + +#### 使用场景 +派单矩阵页右面板「未派订单窗口」,车务从此列表拖拽卡片到左面板某车某日完成派车;同一订单若同时有行程用车与接送机两条活跃需求,会在此列表出现两张卡,车务需要靠新增的类别字段区分要往哪类需求上派车,不能再靠车型/日期/备注这类间接信息猜。 + +#### 入参字段表 +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| 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) +- 消费端:管理后台(派单矩阵页)