docs(changelog): 补 #8598 #8613+#8614 #8593 三份交接件
changelog-filename-gate / validate (push) Failing after 1s

- 8598 派单保险隔离事件新增人工终结出口 DISCARDED(新增接口)
- 8613+8614 605072 恢复动作文案订正 + 605002 座位强禁码下线口径澄清(修复)
- 8593 待配车团期清单 transferPendingCount 由硬编码 0 改真值,取不到给 null(修改接口)

三份均经 validate-changelog-frontmatter.mjs 与 changelog_workflow.py lint 双门禁 PASS,
并做过双向串味自检(他域关键词命中 0、本域关键词命中非 0)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
这个提交包含在:
API Changelog Bot
2026-09-30 06:29:16 +08:00
共同撰写人 Claude Opus 5
父节点 da562f36d4
当前提交 94fb72d79f
共修改 3 个文件,包含 635 行新增和 0 行删除
@@ -0,0 +1,267 @@
---
schema: "hl-changelog/v2"
ticket: "8593"
title: "待配车团期清单 transferPendingCount 由硬编码 0 改为真值,取不到给 null"
consumer: "admin"
author: "wx(GIT)"
change_type: "修改接口"
backend_status: "deployed"
gateway_status: "not_required"
frontend_status: "pending"
frontend_owner: ""
frontend_ref: ""
target_release: ""
verified_at: ""
status_note: "GET /admin/fleet/group-dispatch/pending-batches 响应体每行新增真值语义:records[].transferPendingCount 此前恒为硬编码 0,本次改为按团期实际接送机声明缺口计算的真值,且新增 null 语义——取不到时返回 null 而不是 0,前端必须把 null 渲染成未知态(如「—」),不得折算成 0;0 表示查过了确无缺口,null 表示本团有没有缺口未知。该字段与团期配车总览端点(GET /admin/fleet/group-dispatch/batches/{groupBatchId}/overview)的 transferPendingTotal 走同一判定方法,服务端保证两者恒等。声明数据经内部 Feign 端点(order-v3 提供,仅供服务间调用,非管理后台直接可调)按页批量整取,不做逐团 N+1 请求;该内部读口不可达时不会静默返回空列表,会返回失败结果,使 transferPendingCount 整页退化为 null,不影响该页其余字段(包括 unreadCount,是另一个独立软依赖,user-service 不可达时退化为 0)。字段类型未变(仍是 Integer),仅新增 null 作为合法取值;若前端此前对该字段做过兜底成 0 或完全未渲染,需要补上 null 分支与展示逻辑。清单本身的分页/过滤/排序、其余字段与错误码(600012/600013/401)均未变化。backend_status=deployed:hl-fleet-service 已部署测试服并与本条目所依据的源码提交一致(deploy-status.sh 实测 COMMIT=d57498d38,STATE=ok,2026-09-30 05:34:07 部署);该路由已实测可达(未登录态返 200 信封 code=401)。"
updated_at: "2026-09-30"
base: "dev-v3"
---
# 车务团期配车:待配车团期清单接送机未配计数改为真值
> **存放目录**: 二期(order-v3/fleet)→ `changelogs-v2/2026-09/`
>
> **服务**: hl-fleet-service(端口 8087)
> **PR**: [#8617](https://git.1814.love:8443/wx/HL/pulls/8617)
> **Issue**: [#8593](https://git.1814.love:8443/wx/HL/issues/8593)
> **日期**: 2026-09-30
> **影响范围**: 管理后台「待配车团期清单」列表页的 `transferPendingCount` 一列
---
## ⚠️ 关键变化
「待配车团期清单」(`pending-batches`)每行的 `transferPendingCount` 字段,此前**恒为硬编码 0**,不反映任何真实数据——前端如果曾据此判断「所有团都没有接送机缺口」,这个判断从一开始就是假的。本次改为**真实计算值**,并引入 **`null` 语义**:取不到声明数据时返回 `null` 而不是 `0`。**`0` 与 `null` 含义不同,不能互相折算**:`0` = 查过了、确无接送机缺口;`null` = 这一刻没查到、本团有没有缺口未知。前端如果沿用旧的「反正恒为 0,不用管」的假设,现在会看到非零真值和偶发 `null`,必须补上渲染逻辑。
---
## 二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|------|------|------|----------|------|
| 1 | 待配车团期清单 | GET | `/admin/fleet/group-dispatch/pending-batches` | 修改接口 | `transferPendingCount` 由硬编码 0 改为真值+null 语义 |
---
## 三、接口详情
### 1. 待配车团期清单 `GET /admin/fleet/group-dispatch/pending-batches`
**VO**: `GroupDispatchPendingBatchPageReqVO → PageResult<GroupDispatchPendingBatchRespVO>`
#### 使用场景
车务在「团期配车」列表页查看尚未完成配车(`requirementConfirmed=true` 且 `vehicleReady=false`)的团期,支持按出发日区间、团号/团名关键词、配车进度过滤。本次改动只影响列表行里的 `transferPendingCount` 一列,接口路径、分页参数、其余字段均未变化。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| departDateFrom | Query | LocalDate(ISO,`yyyy-MM-dd`) | - | 不传=不限 | 出发日下界(含) |
| departDateTo | Query | LocalDate(ISO,`yyyy-MM-dd`) | - | 不传=不限 | 出发日上界(含) |
| keyword | Query | String | - | ≤50 字符 | 团号/团名模糊关键词 |
| dispatchProgress | Query | String | - | 仅 `NOT_STARTED`/`PARTIAL`/`FULL` | 配车进度过滤(fleet 侧内存过滤,先分页后过滤) |
| page | Query | Integer | - | ≥1,默认 1 | 页码 |
| pageSize | Query | Integer | - | 1-100,默认 20 | 每页条数 |
#### 出参 `Result<PageResult<GroupDispatchPendingBatchRespVO>>`
| 字段 | 类型 | 说明 |
|------|------|------|
| records | Array | 团期行列表,见下 |
| records[].groupBatchId | String(Long 转字符串) | 团期主订单 ID |
| records[].batchNo | String | 团号 |
| records[].batchName | String | 团名 |
| records[].batchStatus | String | 团期状态 |
| records[].departDate | String(`yyyy-MM-dd`) | 出发日 |
| records[].endDate | String(`yyyy-MM-dd`) | 结束日 |
| records[].serviceDayCount | Integer | 服务日天数 |
| records[].enrolledOrders | Integer | 报名子订单数 |
| records[].enrolledPeople | Integer | 报名人数 |
| records[].requirementConfirmed | Boolean | 需求是否已确认 |
| records[].vehicleReady | Boolean | 车辆是否已就绪 |
| records[].dispatchedDayCount | Integer | 已排车天数 |
| records[].dispatchProgress | String | 配车进度:`NOT_STARTED`/`PARTIAL`/`FULL` |
| records[].**transferPendingCount** | Integer(可空) | **本次变更字段**:本团接送机未配计数;`null`=未取到(前端须渲染未知态),`0`=确无缺口 |
| records[].unreadCount | Integer | 团期车务会话团队未读数(软依赖,取不到退 0) |
| total | Integer | 总记录数(`dispatchProgress` 过滤前) |
| page | Integer | 当前页码 |
| pageSize | Integer | 每页条数 |
#### 请求示例
```http
GET /admin/fleet/group-dispatch/pending-batches?departDateFrom=2026-09-01&departDateTo=2026-09-30&keyword=T26-8867&dispatchProgress=PARTIAL&page=1&pageSize=20
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"data": {
"records": [
{
"groupBatchId": "1934567890123456789",
"batchNo": "T26-8867",
"batchName": "额吉的故乡 9/12 团",
"batchStatus": "RESOURCE_PREPARING",
"departDate": "2026-09-12",
"endDate": "2026-09-16",
"serviceDayCount": 5,
"enrolledOrders": 6,
"enrolledPeople": 17,
"requirementConfirmed": true,
"vehicleReady": false,
"dispatchedDayCount": 2,
"dispatchProgress": "PARTIAL",
"transferPendingCount": 2,
"unreadCount": 3
}
],
"total": 1,
"page": 1,
"pageSize": 20
},
"success": true
}
```
#### 空数据 / 降级响应
当没有满足过滤条件的团期时,返回 `records: [], total: 0`(HTTP 200,非错误)。当 order-v3 的接送机声明批量读口不可达时,**本页所有行的 `transferPendingCount` 一律返回 `null`**(不是 0,也不会让整个请求失败)——前端必须把 `transferPendingCount=null` 渲染成未知态(如「—」),不能当作「确认无缺口」折算成 0。`unreadCount` 是另一个独立的软依赖:user-service 不可达时退化为 0,清单其余字段照常返回,不受影响。
#### 错误响应
```json
{
"code": 600013,
"message": "参数非法: 页码必须≥1",
"success": false,
"data": null
}
```
其余可能返回的错误码:
| code | 触发条件 | message |
|---|---|---|
| 600012 | order-v3 团期候选基线不可达(降级/返错),**不会静默返空列表** | `团期配车基线不可达,请稍后重试` |
| 600013 | 日期区间倒置、分页越界、关键词超长(>50 字)、`dispatchProgress` 枚举非法 | `参数非法: {具体原因}` |
| 401 | 未登录(网关统一信封,HTTP 状态码仍是 200,信封内 `code=401`) | `缺少有效的 Authorization 头` |
#### 业务边界
- `dispatchProgress` 是 fleet 侧内存过滤(order-v3 侧没有配车事实,无法下推),是「先分页再过滤」——单页返回条数可能少于 `pageSize`,`total` 是过滤**前**的总数。
- `transferPendingCount` 与团期配车总览端点(`GET /admin/fleet/group-dispatch/batches/{groupBatchId}/overview`)的 `transferPendingTotal` 走**同一个判定方法**,服务端保证两者恒等——清单页与详情页的这个数字不会对不上。
- 声明数据经内部批量读口整页一次取齐(每页最多按团期数一次 Feign 调用),不做逐团 N+1 请求。
- `transferPendingCount` 的 `null` 与 `unreadCount` 的「退 0」是两种不同的降级策略,分别对应各自读口的可靠性设计,不要混用同一套判空逻辑处理。
- 主候选数据(团期本身)取不到时整个请求失败关闭(600012),不会把「后端没拿到」渲染成「该团没有需求」;这与 `transferPendingCount` 单列退化为 `null`(其余字段正常返回)是两个不同粒度的降级,不要合并处理。
---
## 四、契约约束与正确调用方式
### 正确渲染 `transferPendingCount` 的方式
| 取值 | 含义 | 渲染建议 |
|------|------|----------|
| `0` | 查过了,确无接送机缺口 | 正常展示 `0` |
| 正整数 | 查过了,有对应数量的缺口 | 正常展示数值,可高亮提醒 |
| `null` | 本次没有取到该团的声明数据,缺口未知 | 渲染成未知态(如「—」),**不要**当作 `0` |
❌ 错误用法:`transferPendingCount ?? 0` 或任何把 `null` 静默折算成 `0` 的写法——这会把「未知」误报成「已确认无缺口」,反而比改动前的硬编码 0 更危险(因为界面上看起来像是「查过了」)。
---
## 六、边界行为
- 未登录 → 网关统一信封 `code=401`(HTTP 状态码 200,非 HTTP 401)
- 无匹配团期 → `records: [], total: 0`,HTTP 200
- `dispatchProgress` 过滤导致单页为空 → `records: []`,但 `total` 仍是过滤前总数,不为 0
- order-v3 团期候选基线不可达 → 600012,整个请求失败,不返回部分数据
- order-v3 接送机声明批量读口不可达 → 请求仍然成功,仅 `transferPendingCount` 整页退化为 `null`
- user-service 不可达 → 请求仍然成功,仅 `unreadCount` 退化为 `0`
---
## 六.5、枚举 / 数据字典
### dispatchProgress(配车进度)
**所属字段**: `records[].dispatchProgress`(`GroupDispatchPendingBatchRespVO`),同名字段也用于入参过滤 | **类型**: `String`
| 值 | 中文 | 说明 |
|----|------|------|
| `NOT_STARTED` | 未开始 | 已排车天数为 0 |
| `PARTIAL` | 部分配车 | 已排车天数大于 0 但未盖满全部服务日 |
| `FULL` | 已配齐 | 已排车天数盖满全部权威服务日 |
---
## 六.6、修改前后对比
### 字段级对比
| 字段 | 改前 | 改后 |
|------|------|------|
| `transferPendingCount` | 恒为 `0`(硬编码占位符,从未反映真实缺口) | 真实计算值;取不到声明数据时为 `null`(不是 `0`) |
### 行为级对比
| 行为 | 改前 | 改后 |
|------|------|------|
| 声明数据获取方式 | 未获取,字段硬编码为 0 | 按页批量调用 order-v3 内部读口一次取齐(非逐团 N+1),取不到时该列整体退化为 `null` |
| 与 overview 端点的一致性 | 无法比较(清单侧恒 0,overview 侧是真值,两者结构性不可能相等) | 清单与 overview 走同一判定方法,服务端保证恒等 |
| 字段类型 | `Integer`,实际恒非空 | `Integer`,新增合法取值 `null` |
## 六.7、影响评估
- **是否破坏向后兼容**: 否——字段名与类型(`Integer`)未变,只是语义从「恒定占位符」变为「真实业务值 + 可空」。原本恒为 0 意味着这个字段此前对使用方没有任何信息量,语义上不存在"旧行为被依赖"的合理场景。
- **前端是否必须同步上线**: 是——如果前端此前完全没有渲染这个字段(因为它恒为 0、没有展示价值),现在需要补充展示逻辑,包括 `null` 的未知态处理;如果前端此前渲染了这个字段但做了 `?? 0` 之类的兜底,需要去掉这个兜底、改为区分 `0` 与 `null`。
- **前端 workaround 清理点**: 若前端此前因为「这个字段没用、永远是 0」而完全跳过读取或做了防御性兜底,需要重新接入并按上方「正确渲染方式」处理;无其它 workaround。
---
## 七、不影响范围
- **仅影响**: 「待配车团期清单」列表每行的 `transferPendingCount` 字段
- **零影响**:
- 清单接口的分页参数、过滤参数(`departDateFrom`/`departDateTo`/`keyword`/`dispatchProgress`)语义
- `records[]` 内除 `transferPendingCount` 外的其余字段
- `unreadCount` 字段的取值逻辑(软依赖降级策略本身未变)
- 团期配车总览端点 `GET /admin/fleet/group-dispatch/batches/{groupBatchId}/overview` 的路径、参数、响应结构(其 `transferPendingTotal` 此前就已经是真值,本次不涉及该端点改动,只是清单侧现在与它口径一致)
- 错误码 600012/600013/401 的触发条件与数值
---
## 八、测试环境已验证
- `deploy-status.sh`(测试服现状表)实测:`hl-fleet-service` COMMIT=`d57498d38`、STATE=ok,2026-09-30 05:34:07 部署——与本条目所依据的源码提交(含 #8593 合并提交 `d57498d381`)一致。
- 测试服内网 `curl` 实测路由已挂载且鉴权前置生效:
```
GET http://127.0.0.1:8080/admin/fleet/group-dispatch/pending-batches?page=1&pageSize=1 (无 Authorization 头)
→ HTTP 200
{"code":401,"message":"缺少有效的 Authorization 头","data":null,"traceId":"e0e9988b33394199","success":false}
```
---
## 十、相关文档
- 关联 Issue: [wx/HL#8593](https://git.1814.love:8443/wx/HL/issues/8593)
- 关联 PR: [wx/HL#8617](https://git.1814.love:8443/wx/HL/pulls/8617)
## 关联 / 联系人
### 链接
- **Issue**: [#8593](https://git.1814.love:8443/wx/HL/issues/8593)
- **PR**: [#8617](https://git.1814.love:8443/wx/HL/pulls/8617)
- **Merge commit**: [`d57498d381`](https://git.1814.love:8443/wx/HL/commit/d57498d38138fd37ce7e844b6f2b01c190706950)
### 联系人
- **后端负责人**: @wx
@@ -0,0 +1,228 @@
---
schema: "hl-changelog/v2"
ticket: "8598"
title: "派单保险隔离事件新增人工终结(DISCARDED)出口"
consumer: "admin"
author: "wx(GIT)"
change_type: "新增接口"
backend_status: "deployed"
gateway_status: "not_required"
frontend_status: "pending"
frontend_owner: ""
frontend_ref: ""
target_release: ""
verified_at: ""
status_note: "新增 POST /admin/fleet/insurance/assignment-events/{eventId}/discard,仅 SUPER_ADMIN 可调用,把处于 QUARANTINED 的派单保险隔离事件人工终结为新终态 DISCARDED,终结不可逆、无回退入口,也无批量接口,逐条操作且终结原因必填(非空、≤200字)。幂等窗口10秒(重复提交返100502),并发冲突返100503(CAS未命中)。DISCARDED 会让关联的行程短信状态查询/重发端点(GET及POST .../itinerary-sms[/retry])对该事件返回 status=FAILED、canRetry=false——这是已有取值组合,不引入新字段或新枚举值,前端已有的 FAILED 分支即可覆盖,不需要新增代码路径。gateway_status=not_required,复用既有 /admin/fleet/** 路由,未新增网关配置。backend_status=deployed:PR #8607(合并提交5afadf6c634)已合并,hl-fleet-service 已部署测试服并与本条目所依据的源码提交一致(deploy-status.sh 实测 COMMIT=d57498d38,STATE=ok,2026-09-30 05:34:07 部署);未登录态 curl 实测该路径已挂载并优先鉴权(返 200 信封 code=401,非 HTTP 401 状态码),路由与鉴权链路均已验证。"
updated_at: "2026-09-30"
base: "dev-v3"
---
# 车务保险: 隔离事件新增人工终结(DISCARDED)出口
> **存放目录**: 二期(order-v3)→ `changelogs-v2/2026-09/`
>
> **服务**: hl-fleet-service(端口 8087)
> **PR**: [#8607](https://git.1814.love:8443/wx/HL/pulls/8607)
> **Issue**: [#8598](https://git.1814.love:8443/wx/HL/issues/8598)
> **日期**: 2026-09-30
> **影响范围**: 超管对派单保险隔离 Outbox 事件的处置面板,新增一个终结动作;对既有重放端点与行程短信状态端点零结构变化
---
## ⚠️ 关键变化
隔离事件(`QUARANTINED`)此前**唯一的出口是重放**——重放会再隔离的事件(关联需求已删、内容审核不过、上游数据已按别的单清理),会让卡死告警永久为红,没有任何办法让它退出告警。本次新增一个**人工终结**动作,把这类确定性失败的事件显式标成新终态 `DISCARDED`,终结之后它退出卡死告警、不再被扫描器捞起、也不再阻塞同 `orderingKey` 的后继事件。**终结无回退入口,是单向不可逆操作**。
---
## 二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|------|------|------|----------|------|
| 1 | 终结已隔离的派单生命周期事件 | POST | `/admin/fleet/insurance/assignment-events/{eventId}/discard` | 新增接口 | 仅 SUPER_ADMIN,QUARANTINED → DISCARDED |
---
## 三、接口详情
### 1. 终结已隔离的派单生命周期事件 `POST /admin/fleet/insurance/assignment-events/{eventId}/discard`
**VO**: `AssignmentInsuranceOutboxDiscardReqVO → AssignmentInsuranceOutboxDiscardRespVO`
#### 使用场景
超管在派单保险 Outbox 卡死告警/隔离事件处置面板里,对一条已确认「重放多少次都会再隔离」的事件(例如关联需求已随团期撤销删除、内容审核不通过、上游数据已被另一张单清理)执行终结,承认这个业务动作确实不会再发生、也不再补,并把原因、操作人、时间留痕。与重放动作共用同一批隔离事件列表数据源,本次不新增查询端点。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| eventId | Path | Long | ✅ | - | Outbox 事件 ID |
| reason | Body | String | ✅ | 非空;≤200 字 | 确认不再重放的原因,须说明业务影响已如何处置 |
#### 出参 `Result<AssignmentInsuranceOutboxDiscardRespVO>`
| 字段 | 类型 | 说明 |
|------|------|------|
| eventId | String(Long 转字符串) | Outbox 事件 ID |
| previousStatus | String | 终结前状态,恒为 `QUARANTINED` |
| status | String | 终结后状态,恒为 `DISCARDED` |
| operatorId | String(Long 转字符串) | 操作人管理员 ID |
| discardedAt | String | 终结操作时间,格式 `yyyy-MM-dd HH:mm:ss` |
#### 请求示例
```json
{
"reason": "关联需求已随团期撤销删除,短信不再需要补发"
}
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"data": {
"eventId": "1934567890123456789",
"previousStatus": "QUARANTINED",
"status": "DISCARDED",
"operatorId": "88",
"discardedAt": "2026-09-30 10:20:30"
},
"success": true
}
```
#### 空数据 / 降级响应
本接口是单事件的状态迁移动作,没有「空数据」或部分成功的中间态——调用结果只有「成功迁移」或下方错误响应里的某一种拒绝,不存在降级返回。
#### 错误响应
```json
{
"code": 403001,
"message": "无权限,仅超级管理员可终结隔离事件",
"success": false,
"data": null
}
```
其余可能返回的错误码:
| code | 触发条件 | message |
|---|---|---|
| 400 | `reason` 为空白或超过 200 字(Bean Validation,先于业务逻辑拦截) | `终结原因不能为空` 或 `终结原因最多200字` |
| 100001 | `eventId` 对应事件不存在 | `参数非法: 隔离事件不存在` |
| 100001 | 事件当前状态不是 `QUARANTINED`(已是 SUCCESS/DISCARDED/PENDING/PROCESSING) | `参数非法: 仅允许终结 QUARANTINED 事件,当前状态为{实际状态}` |
| 100502 | 同一 `eventId` 10 秒幂等窗口内重复提交 | `隔离事件终结中,请勿重复提交` |
| 100503 | 并发命中 CAS 未命中(他人同时终结/重放,或处理器抢先处理) | `资源被占用,请稍后重试` |
| 401 | 未登录(网关统一信封,HTTP 状态码仍是 200,信封内 `code=401`) | `缺少有效的 Authorization 头` |
#### 业务边界
- 只接受当前状态为 `QUARANTINED` 的事件;其余状态一律 100001 拒绝。
- 终结是单向操作,没有「撤销终结」的接口。
- 无批量终结接口,只能逐条调用——设计上刻意如此:批量会把混在隔离事件里的真实业务缺口一次性静默抹掉。
- 幂等键为 `eventId`(10 秒窗口),并发保护为乐观锁 CAS;两者返回的错误码不同(100502 vs 100503),前端应分别处理:100502 提示稍候,100503 建议重新拉取该事件当前状态后再决定下一步。
- 终结成功后,该事件对应的行程短信状态查询/重发端点(`GET /admin/fleet/assignments/{assignmentId}/itinerary-sms`、`POST .../itinerary-sms/retry`)会返回 `status=FAILED, canRetry=false`——这是这两个端点已公开枚举值集合里已有的取值组合,不是新增字段或新增枚举值,前端已有的 `FAILED` 分支不需要改动即可正确渲染。
---
## 四、契约约束与正确调用方式
> 本节只写后端接受/拒绝 payload 的规则,不写 UI 渲染建议。
### ✅ 正确 / ❌ 错误 payload 对照
| 场景 | payload | 结果 |
|------|---------|------|
| ✅ 原因非空且 ≤200 字 | `{ "reason": "关联需求已随团期撤销删除,短信不再需要补发" }` | 200,事件迁移为 DISCARDED |
| ❌ 原因为空 | `{ "reason": "" }` 或 `{ "reason": " " }` | 400 `终结原因不能为空` |
| ❌ 原因超长 | `{ "reason": "<201 个字符>" }` | 400 `终结原因最多200字` |
| ❌ 对非 QUARANTINED 事件调用 | 任意合法 reason,但目标事件当前是 SUCCESS/DISCARDED/PENDING/PROCESSING | 100001,message 里点名当前状态 |
### 调用前置
调用前前端应确认目标事件当前处于「已隔离」状态(面板上通常是从隔离事件列表点进来),不要对已经终结过、已成功、或还在处理中的事件发起终结请求——这些情形不会被静默忽略,而是显式返回 100001。
---
## 五、数据库行为
- 终结成功后,该事件状态字段变为 `DISCARDED`;原因、操作人、操作时间会被记录(复用重放动作已有的三个字段承载,未新增列)。
- 终结成功后再对该事件调用既有的重放端点(`POST /admin/fleet/insurance/assignment-events/{eventId}/replay`),会返回 100001「仅允许重放 QUARANTINED 事件,当前状态为DISCARDED」。
- 终结不会产生任何下游消息重放或补发——它就是承认这件事不会再发生。
---
## 六、边界行为
- 未登录 → 网关统一信封 `code=401`(HTTP 状态码 200,非 HTTP 401)
- 非超管 → 403001
- `eventId` 不存在 → 100001
- 事件状态非 `QUARANTINED` → 100001,message 带当前实际状态
- `reason` 为空/超长 → 400(Bean Validation 先于业务逻辑拦截)
- 10 秒幂等窗口内重复提交同一 `eventId` → 100502
- 并发命中 CAS 未命中 → 100503
- 下游服务降级 → 不适用,本接口无下游读取,只做本域状态迁移
---
## 六.5、枚举 / 数据字典
### status / previousStatus(`AssignmentInsuranceOutboxStatusEnum`)
**所属字段**: `status` / `previousStatus`(`AssignmentInsuranceOutboxDiscardRespVO`) | **类型**: `String`
| 值 | 中文 | 说明 |
|----|------|------|
| `PENDING` | 待处理 | 尚未开始处理(本接口不接受此状态) |
| `PROCESSING` | 处理中 | 正在处理(本接口不接受此状态) |
| `QUARANTINED` | 已隔离 | 卡死告警状态,唯一可被本接口终结的状态;`previousStatus` 恒为此值 |
| `SUCCESS` | 成功 | 机器判定的成功终态(本接口不接受此状态) |
| `DISCARDED` | 已终结(本次新增) | 人工判定的放弃终态,只能由本接口产出,单向不可逆;`status` 恒为此值 |
---
## 七、不影响范围
- **仅影响**: 派单保险隔离 Outbox 事件处置面板,新增一个终结动作入口
- **零影响**:
- 既有重放端点 `POST /admin/fleet/insurance/assignment-events/{eventId}/replay` 的路径、参数、错误码
- 既有隔离事件列表/卡死告警统计查询
- 行程短信状态查询/重发端点的响应字段结构与已公开枚举值集合(新增的只是一条已有取值组合被触发的路径)
---
## 八、测试环境已验证
- `deploy-status.sh`(测试服现状表)实测:`hl-fleet-service` COMMIT=`d57498d38`、STATE=ok,2026-09-30 05:34:07 部署——与本条目所依据的源码提交(含 #8598 合并提交 `5afadf6c634`)一致。
- 测试服内网 `curl` 实测路由已挂载且鉴权前置生效:
```
POST http://127.0.0.1:8080/admin/fleet/insurance/assignment-events/1/discard (无 Authorization 头)
→ HTTP 200
{"code":401,"message":"缺少有效的 Authorization 头","data":null,"traceId":"f3e71e825f844525","success":false}
```
---
## 十、相关文档
- 关联 Issue: [wx/HL#8598](https://git.1814.love:8443/wx/HL/issues/8598)
- 关联 PR: [wx/HL#8607](https://git.1814.love:8443/wx/HL/pulls/8607)
## 关联 / 联系人
### 链接
- **Issue**: [#8598](https://git.1814.love:8443/wx/HL/issues/8598)
- **PR**: [#8607](https://git.1814.love:8443/wx/HL/pulls/8607)
- **Merge commit**: [`5afadf6c634`](https://git.1814.love:8443/wx/HL/commit/5afadf6c634997a31b0f912f80ab0b67c34a9c2c)
### 联系人
- **后端负责人**: @wx
@@ -0,0 +1,140 @@
---
schema: "hl-changelog/v2"
ticket: "8613"
title: "605072 恢复动作文案订正 + 605002 座位强禁码下线口径澄清(本条目同时覆盖 #8614)"
consumer: "admin"
author: "wx(GIT)"
change_type: "修复"
backend_status: "deployed"
gateway_status: "not_required"
frontend_status: "pending"
frontend_owner: ""
frontend_ref: ""
target_release: ""
verified_at: ""
status_note: "本条目合并覆盖 #8613 与 #8614(同一 PR #8616 一并修复,两者均为文案订正,未新增/删除/变更任何字段或路径)。#8613:605002「车型座位不足」自 #5810 起已是死码,create/change 均不再做座位强禁校验,全仓(含测试)零产出方;POST /admin/fleet/assignments 的 headcount、strictSeats 两个字段的 Swagger 文案已订正为如实描述——headcount 仅落库记录与下游统计,不再用于任何服务端座位判定;strictSeats 是历史兼容字段,服务端完全忽略,传 true 不会触发 605002,新代码不要依赖它做分支。座位不足的非阻断提示只在 POST /admin/fleet/assignments/precheck 以 warning(type=seats_short)形式给出,precheck 本身零变化。#8614:605072 错误码数值不变(仍是 605072),message 文案从「请先释放资源再处置完成」订正为「请对本单重新执行一次「一键清除已取消派单的占用」后再处置完成」——即撞上 605072 时正确的前端引导是让车务重新调用 POST /admin/fleet/assignments/clear-cancelled-occupancy,而不是原地反复重试 POST /admin/fleet/assignments/resolve-exception;服务端拦回的同时已在独立事务补写一次占用反算意图,按提示重新执行清除占用后再重试 resolve-exception 通常可以收敛。backend_status=deployed:hl-fleet-service 已部署测试服并与本条目所依据的源码提交一致(deploy-status.sh 实测 COMMIT=d57498d38,STATE=ok,2026-09-30 05:34:07 部署);resolve-exception 路由已实测可达(未登录态返 200 信封 code=401)。"
updated_at: "2026-09-30"
base: "dev-v3"
---
# 车务派单:605072 恢复动作文案订正 + 605002 座位强禁码下线口径澄清
> **存放目录**: 二期(order-v3/fleet)→ `changelogs-v2/2026-09/`
>
> **服务**: hl-fleet-service(端口 8087)
> **PR**: [#8616](https://git.1814.love:8443/wx/HL/pulls/8616)
> **Issue**: [#8613](https://git.1814.love:8443/wx/HL/issues/8613)、[#8614](https://git.1814.love:8443/wx/HL/issues/8614)
> **日期**: 2026-09-30
> **性质**: 两处均为 Swagger 描述文案 / 错误消息文案订正,**接口路径、方法、请求参数、响应字段结构、错误码数值均零变更**——不触发接口契约模板,本文档按「修复」类轻量格式书写。
---
## ⚠️ 关键变化
1. **605072(异常派单占用未释放)的错误消息文案变了,正确的恢复动作也变了**:旧文案「请先释放资源再处置完成」不点名具体动作,车务只能反复点「处置完成」(`resolve-exception`)本身,而这在「一车/一司机被多张单的异常行共用、逐单释放」的场景下会卡死——即便实际占用已经释放,缓存态仍可能停在旧读数上。新文案明确指向唯一有效的恢复动作:**重新调用一次「一键清除已取消派单的占用」(`clear-cancelled-occupancy`)**,再重试处置完成。**若前端在 605072 分支里硬编码过旧文案、或只做了「提示后原地重试」的处理,需要改成引导用户重新执行清除占用。**
2. **605002(车型座位不足)确认为死码,不会再从 create/change 派单接口抛出**:这不是本次改的行为,而是订正一处此前不准确的文档描述——该码自 #5810 起已无任何产出方。若前端此前在派车弹窗里对 605002 做过专门的错误分支处理,那段代码从未被触发过、以后也不会。座位不足唯一的提示渠道是 `precheck` 预检接口的非阻断 warning(`type=seats_short`),该渠道本身没有变化。
---
## 二、涉及接口(非接口契约变更,仅列出文案改动落点,供联调核对)
| 接口 | 方法 | 路径 | 改动内容 |
|------|------|------|----------|
| 异常派单处置完成 | POST | `/admin/fleet/assignments/resolve-exception` | 605072 错误消息文案改写(#8614) |
| 创建派单 | POST | `/admin/fleet/assignments` | `headcount`/`strictSeats` 两字段 Swagger 描述文案订正(#8613),字段本身未增删未改类型 |
---
## 三、逐项说明
### 1. `POST /admin/fleet/assignments/resolve-exception`(#8614)
**背景**:该接口把订单下全部 `exception` 状态派单行推进到 `completed`,前置条件是这些行的车辆/司机占用已经释放。占用是否释放,判定依据是车辆/司机的**缓存状态列**(`vehicle_status`/`driver_status`)是否为 `busy`。这个缓存列只在特定写口(如 `clear-cancelled-occupancy`)被触发时才会反算刷新。
**问题场景**:同一车辆或司机被多张单各自的 `exception` 行共用时,逐单释放会出现死局——释放 A 单时缓存态被判 `busy`(当时正确);随后处置完 A 单,B 单这边再没有任何写口触发反算 ⇒ 缓存态永远停在旧的 `busy`,即便实际已经没有在途占用支撑,B 单调用 `resolve-exception` 也会永远撞 605072。
**本次改动**:
- 错误消息文案(605072 数值不变):
| | 内容 |
|---|---|
| 旧 | `异常派单的车辆/司机占用尚未释放,请先释放资源再处置完成` |
| 新 | `异常派单的车辆/司机占用尚未释放,请对本单重新执行一次「一键清除已取消派单的占用」后再处置完成` |
- 服务端在拦回抛出 605072 的同时,已在独立事务里补写一次「按当前在途口径」的占用反算意图(不影响本次请求仍会失败,是为下一次重试铺路)。
- Swagger `@ApiOperation` 说明文本同步更新,明确写出 605072 的恢复动作。
**对前端的影响**:撞上 605072 时,正确引导是提示用户重新调用 `POST /admin/fleet/assignments/clear-cancelled-occupancy`(该接口路径/参数/行为本身未变),再重试 `resolve-exception`;不建议做「原地无限重试 resolve-exception」的兜底逻辑,因为缓存态陈旧这种情形下光重试 `resolve-exception` 本身不会让状态收敛(要靠 `clear-cancelled-occupancy` 触发反算)。若之前的前端文案直接透传了服务端 message 字符串,会自动拿到新文案,无需改代码;若前端针对 605072 有自己的本地化文案覆盖了服务端 message,建议同步这句新的恢复动作提示。
### 2. `POST /admin/fleet/assignments`(#8613)
**背景**:该接口的 `CreateAssignmentReqVO` 里有 `headcount`(人数)和 `strictSeats`(座位严格模式)两个历史字段。#5810 起,车型/座位差异已经不再阻断派车(`create`/`change` 均不做座位强禁校验),但这两个字段的 Swagger 描述当时没有同步更新,仍然写着「座位不足判定用」「true=座位不足强禁抛605002」,与实际行为不符。
**本次改动(仅 `@ApiModelProperty` 描述文案,字段名/类型/是否必填均未变)**:
| 字段 | 旧描述 | 新描述 |
|------|--------|--------|
| `headcount` | `人数(座位不足判定用,可空时不判座位)` | `人数(仅落库记录与下游统计;#5810 起 create 不做任何座位校验,车辆座位少于人数也照常派车、不会返回 605002。座位不足的非阻断提示只在 precheck 预检端点以 warning(seats_short) 形式返回,create 侧不产出该提示;本字段可空)` |
| `strictSeats` | (Java 层注释,非 Swagger 描述)`座位严格模式:true=座位不足强禁抛 605002 / false=仅 warning 不阻断(默认 false)` | `历史兼容字段,#5810 起服务端完全忽略:座位差异不再阻断派车,传 true 也不会抛 605002。全仓无读取方,仅装配侧恒写 false 以保持 BO 形状;新代码不要依赖本字段做任何分支` |
**对前端的影响**:
- 如果前端此前依赖「create 接口会因座位不足报 605002」做过任何拦截逻辑(例如提交前弹确认框、或捕获 605002 单独处理),这段逻辑**从未生效过**——create/change 从 #5810 起就不做这个校验,以后也不会恢复(605002 码位保留但不会复用给别的语义)。
- `strictSeats` 传什么值都不影响服务端行为,前端无需继续维护/传递这个字段的真实语义(可以继续传,服务端只是忽略)。
- 座位不足的唯一提示渠道是 `precheck`(`POST /admin/fleet/assignments/precheck`)响应里的 warning 数组,`type=seats_short`——这个渠道本身没有任何变化,仍照旧使用。
---
## 四、契约约束与正确调用方式
- `resolve-exception` 撞 605072 后的正确恢复序列:`POST clear-cancelled-occupancy` → 重试 `POST resolve-exception`。中间不需要额外等待,服务端的补写反算意图是同步在拦回请求的事务外完成的。
- `create`(`POST /admin/fleet/assignments`)不会因为座位不足返回任何错误码;如需在提交前给用户座位不足提示,唯一正确渠道是先调用 `precheck` 读取 warning 数组。
---
## 六、边界行为
- `resolve-exception` 605072 之外的错误码(401 未登录、其它业务校验失败码)均未变化,本次不涉及。
- `create` 接口除 Swagger 描述文本外,请求校验、成功路径、其余错误码均未变化。
---
## 七、不影响范围
- `POST /admin/fleet/assignments/precheck` 的请求/响应结构与 `seats_short` warning 的产生条件——零变化。
- `POST /admin/fleet/assignments/clear-cancelled-occupancy` 的路径、参数、返回结构——零变化。
- 605002、605072 两个错误码的**数值**本身——均未变化(只是 605072 的 message 文案变了,605002 的可触发性说明被订正,数值都没动)。
- 除本文档列出的 2 处 `@ApiModelProperty`/错误消息字符串外,`CreateAssignmentReqVO`、`ResolveExceptionReqVO`、响应 VO 均无字段增删或类型变更。
---
## 八、测试环境已验证
- `deploy-status.sh`(测试服现状表)实测:`hl-fleet-service` COMMIT=`d57498d38`、STATE=ok,2026-09-30 05:34:07 部署——与本条目所依据的源码提交(含 #8613/#8614 合并提交 `2bf98beb491`)一致。
- 测试服内网 `curl` 实测 `resolve-exception` 路由已挂载且鉴权前置生效:
```
POST http://127.0.0.1:8080/admin/fleet/assignments/resolve-exception (无 Authorization 头)
→ HTTP 200
{"code":401,"message":"缺少有效的 Authorization 头","data":null,"traceId":"f8da1e80a5e1400b","success":false}
```
- 源码级核对:全仓 grep `SEATS_NOT_ENOUGH` 仅命中 `AssignmentErrorCode.java` 的定义处一行,`hl-fleet-service` 主代码与测试代码中均无第二处引用,确认 605002 当前零产出方,与文案订正内容一致。
---
## 十、相关文档
- 关联 Issue: [wx/HL#8613](https://git.1814.love:8443/wx/HL/issues/8613)、[wx/HL#8614](https://git.1814.love:8443/wx/HL/issues/8614)
- 关联 PR: [wx/HL#8616](https://git.1814.love:8443/wx/HL/pulls/8616)
## 关联 / 联系人
### 链接
- **Issue**: [#8613](https://git.1814.love:8443/wx/HL/issues/8613)、[#8614](https://git.1814.love:8443/wx/HL/issues/8614)
- **PR**: [#8616](https://git.1814.love:8443/wx/HL/pulls/8616)
- **Merge commit**: [`2bf98beb491`](https://git.1814.love:8443/wx/HL/commit/2bf98beb49159de09087522d12b532cca720d5db)
### 联系人
- **后端负责人**: @wx