docs(changelog): #8061 解除共用关系只清成员占用,同槽非成员不动
changelog-filename-gate / validate (push) Failing after 1s

fleet 已部署测试服 00930f5d6,网关实测取得阳性与阴性两条对照:
阳性 releasedSourceIds 恰为两个成员且库内真变 unassigned;
阴性 非成员 C 的 assignment_status/vehicle_id/driver_id 解除前后逐一相等。

两条缺一不可——只有阳性的话,「C 没变」与「解除没跑起来」观测相同,
且阳性在修复前同样成立(旧实现一样释放成员,只是顺手把 C 也清了)。

frontend_status=pending:请求/响应字段零增删,但派车操作日志新增
operation_type 取值 share_release_cleared,前端若按白名单过滤会静默
丢掉这条记录,而那正是本次补留痕要解决的问题。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
这个提交包含在:
API Changelog Bot
2026-09-20 23:21:18 +08:00
共同撰写人 Claude Opus 5
父节点 1883b39f8f
当前提交 6ade6e1332
@@ -0,0 +1,371 @@
---
schema: "hl-changelog/v2"
ticket: "8061"
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: "backend_status=deployed:PR #8067 squash 为 00930f5d6,2026-09-20 22:42 已部署到测试服 192.168.100.236,deploy-status.sh 复核落点 = 00930f5d6,Nacos 两实例(8087/8187)healthy=true。gateway_status=verified:2026-09-21 在测试服网关上以全新三户夹具(团期 2101690789438570497、服务日 2026-10-30)取得阳性与阴性两条对照——阳性:releasedSourceIds 恰为两个成员且库内真变 unassigned;阴性:非成员 C 的 assignment_status/vehicle_id/driver_id 解除前后逐一相等。两条缺一不可:只有阳性的话,「C 没变」与「解除没跑起来」观测相同,且阳性在修复前同样成立。详见正文「八、测试环境已验证」。frontend_status=pending:本字段记的是「网关验证有没有做过」,不是「网关路由要不要改」——按 BACKEND_CHANGELOG_DELIVERY_GUIDE 的标准搭配 deployed/verified,以及 2026-08 #5405/#5407 的先例(『测试环境部署和网关验证尚未执行,因此 backend_status、gateway_status 保持 pending』)。本次确实不新增端点、不改路由形状,但网关上一次真实调用尚未打过,故 pending,不是 not_required。frontend_status=pending:请求/响应字段确实零增删,但派车操作日志时间线新增了 operation_type 取值 share_release_cleared——前端若按 operation_type 白名单过滤,这条会被静默丢掉,用户就看不到『车和司机是被哪次共用关系解除清掉的』,而那正是本次补留痕要解决的问题。⇒ 前端至少需要确认自己有没有这层过滤、必要时加上,这是动作不是知会,故 pending。另按指南,not_required 会禁止 mmg 回写 frontend_owner 等认领字段,填错反而挡住认领。"
updated_at: "2026-09-20"
base: "dev-v3"
---
# fleet: 解除共用关系只处置本关系的成员,同槽非成员行保留(工单 #8061 + #8051 衍生)
> **存放目录**: 二期(order-v3 标签工单)→ `changelogs-v2/2026-09/`
>
> **服务**: hl-fleet-service
> **PR**: #8067(squash `00930f5d6`,已合入 dev-v3) | **Issue**: #8061
> **日期**: 2026-09-20
> **影响范围**: 管理后台「团期配车」页解除车辆或司机共用关系这一个动作,及其对派车账本的影响
---
## ⚠️ 关键变化
**三个可观测行为都变了,前端必须知道:**
### ① 处置范围收窄
改动前:解除某一个共用关系,会把该车/该司机**在整个资源日槽上的全部在途派车行**一起软清成「待派车」,同槽的**非成员行也被清**。
改动后:只处置**该关系的成员占用**。同一车/同一司机、**同一服务日**上那些**从不属于本关系**的在途派车行,**一行不动**。
⇒ 响应里的 `releasedSourceIds` / `pendingReassignSourceIds` 会比以前短。同一个服务日、同一辆车上那些**与本关系无关**的在途派单,不再被连带清空。
**典型场景**:接送机的接、送一对。它们本来就该在同一天占同一辆车,但不一定属于同一个共用关系;现在解除一个关系不再误伤另一对。
### ② 错误码触发条件变了
- **602111**(`KEEP_LEGAL` 下仍有非法组合)与 **602112**(收口断言)现在只在「**至少一侧是本关系成员**」的组合上触发。
- ⇒ 以前会因为两条**互不相干的第三方派单**存量冲突而整单失败的情况,**现在不会再失败了**。前端若有「解除失败请联系管理员」这类兜底文案,触发频率会下降。
### ③ 🔴 派单操作日志时间线新增 `share_release_cleared` 记录
新增操作类型值 `share_release_cleared`,中文标签「共用关系解除释放占用」。`detail_json` 包含以下字段(所有 ID 都是**字符串**,防雪花 ID 过 JS 掉精度):
| 字段 | 类型 | 说明 |
|------|------|------|
| shareGroupId | String | 被解除的共用关系 ID |
| groupBatchId | String | 团 ID |
| serviceDate | String | 服务日期(YYYY-MM-DD) |
| resourceType | String | 资源维度:`VEHICLE` 或 `DRIVER` |
| resourceId | String | 车辆或司机 ID |
| releaseReason | String | 解除原因 |
| survivorPolicy | String | 本次采用的处置策略(`RELEASE` / `REASSIGN` / `KEEP_LEGAL`) |
| vehicleIdBefore | String | 派单清之前挂的车辆 ID(解除后被清成 NULL)|
| driverIdBefore | String | 派单清之前挂的司机 ID(解除后被清成 NULL) |
**⚠️ 关键警告**:如果前端在时间线上按 `operation_type` 白名单过滤,这条新记录会被静默丢掉。用户就看不到「车和司机是被哪次共用关系解除清掉的」——而这正是本次补留痕要解决的问题。
---
## 一、背景
### 原 #8051 与本单的关系
#8051 修了「解除共用关系会**跨服务日、跨团**软清在途派车行」这个 bug,已由 PR #8058(`ac9efb735`)修复并部署。
本单从 #8051 分出,因为在修复过程中又发现了一个**同一服务日**范围内的残留问题:`RELEASE` 策略清的是该资源日槽上**全部在途 claim**,与"是否属于本关系的成员"之间没有任何约束。结果:同槽上的非成员行也被一起清了,且同样静默。
### 两处文档说了对的话,实现没跟上
- `ShareSurvivorPolicy.RELEASE` 的 javadoc:释放**本关系的成员**的占用
- `GroupDispatchShareController` 的 `@ApiOperation.notes`:同样说释放**本关系的成员**
两处独立写成同一个意思——这代表**意图**,而不是"实现当前的行为"。现状不是「文档没跟上实现」,是**实现从来没有对齐过契约**。
### 实测误清 6 行真实数据
2026-09-20 14:04:12 解除共用关系 `359915397639704576`(服务日 2026-10-03、车辆 `2065329519232720897`)时,日志记的是 `释放=[7 个派单 ID]`,经库内逐行核对:
| 派单 ID | 服务日 | 团 | 是否成员 | 预期 | 实际 |
|---|---|---|---|---|---|
| 359887954036002816 | 2026-10-18 | 2101496576453275649 | ❌ | 不该动 | 被清 |
| 359887954396712960 | 2026-10-18 | 2101496576453275649 | ❌ | 不该动 | 被清 |
| 359888468400279552 | 2026-10-25 | 2101498606508994561 | ❌ | 不该动 | 被清 |
| 359888468916178944 | 2026-10-25 | 2101498606508994561 | ❌ | 不该动 | 被清 |
| 359888884127109120 | 2026-10-25 | 2101498606508994561 | ❌ | 不该动 | 被清 |
| 359896486663819264 | 2026-11-20 | 2099073597908647938 | ❌ | 不该动 | 被清 |
| 359915650245857280 | 2026-10-03 | 2101524283048263681 | ✅ | 该清 | 被清 |
**6 行误清**:横跨 3 个其它服务日、3 个其它团;现状全部 `assignment_status=unassigned`、`vehicle_id`/`driver_id` 置 NULL。
---
## 二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|------|------|------|----------|------|
| 1 | 解除团期车辆/司机共用关系 | DELETE | `/admin/fleet/group-dispatch/share-groups/{shareGroupId}` | **行为修复**(字段、错误码、网关路由均不变) | 处置范围从「整个资源日槽」收窄为「本关系的成员」;错误码触发条件同步收窄 |
| 2 | — | — | — | **日志侧新增** | 派单操作日志新增 `share_release_cleared` 记录类型 |
---
## 三、接口详情
### 1. 解除团期车辆/司机共用关系 `DELETE /admin/fleet/group-dispatch/share-groups/{shareGroupId}`
**VO**: `ShareGroupReleaseRespVO`(**字段零增删改**)
#### 使用场景
管理后台「团期配车」页,车务点「解除共用」。解除同时要在同一事务内完成幸存占用的实际处置,`survivorPolicy` 决定怎么处置。
#### 入参(**本次无变化**)
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| shareGroupId | Path | Long | ✅ | — | 共用关系 ID |
| survivorPolicy | Query | String | 业务必填 | `KEEP_LEGAL` / `REASSIGN` / `RELEASE` | 幸存占用处置策略;不传返业务码 `602110` |
#### 出参(**本次无变化**)
| 字段 | 类型 | 说明 |
|------|------|------|
| shareGroupId | String | 共用关系 ID(雪花 ID,按字符串序列化) |
| status | String | 固定 `RELEASED` |
| survivorPolicy | String | 本次采用的处置策略 |
| keptSourceIds | List\<String\> | 保留占用的来源 ID(本关系成员中被保留的) |
| releasedSourceIds | List\<String\> | **已释放占用的来源 ID**(**仅本关系成员,不含非成员**) |
| pendingReassignSourceIds | List\<String\> | 需人工改派的来源 ID(仅本关系成员) |
#### 请求示例
```http
DELETE /admin/fleet/group-dispatch/share-groups/{shareGroupId}?survivorPolicy=RELEASE HTTP/1.1
Host: api.test.1814.love:9443
Authorization: Bearer {token}
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"data": {
"shareGroupId": "{shareGroupId}",
"status": "RELEASED",
"survivorPolicy": "RELEASE",
"keptSourceIds": ["2101524548279238658"],
"releasedSourceIds": ["360022177073991680"],
"pendingReassignSourceIds": []
},
"success": true
}
```
#### 空数据 / 降级响应
该资源日上仅剩 0-1 个存活 claim 时,三个清单都可能是空数组 `[]`——这是正常终态,不是异常。
#### 错误响应
```json
{
"code": 602111,
"message": "保留合法共用失败,幸存成员之间不满足衔接规则: GROUP_DISPATCH#2101524548279238658 ↔ ASSIGNMENT#360022539151478784",
"data": null,
"success": false
}
```
#### 业务边界
- 鉴权:需管理后台已登录且具备 `fleet:group-dispatch:write` 权限,未登录/无权限按网关与全局鉴权规则处理。
- 幂等性:关系一旦解除即置 `RELEASED`;重复发起 DELETE 会拿到 602108。
- 失败零写入:602111 / 602112 都在同一事务内回滚——关系不标 RELEASED,占用状态不变。
- `survivorPolicy` 必须传值;不传返 602110,不是 400。
- 🔴 已知未覆盖边界(**不在本次修复范围**):同一资源同一服务日上的**非成员**在途行仍会被 `RELEASE` 一并软清且同样静默——处置逻辑不区分"共用关系的成员"与"路过的其它派车行";最典型场景是接送机的接、送一对。已在工单 #8061 本单纳入。
#### 错误码(**本次不新增、语义不改**)
| code | 含义 |
|------|------|
| 602108 | 共用关系不存在或已解除 |
| 602110 | 未指定处置策略 |
| 602111 | `KEEP_LEGAL` 下**本关系的幸存成员**之间存在非法组合;整请求回滚 |
| 602112 | 收口断言不成立:处置后账本里**至少一侧是本关系成员的组合**中仍有非法;回滚 |
---
## 四、契约约束与正确调用方式
> 本节只写后端接受/拒绝 payload 的规则。接口契约本身**没有变化**——本节写的是既有契约,帮消费方确认自己一直调对了。
### ✅ 正确 / ❌ 错误 payload 对照
| 场景 | payload | 结果 |
|------|---------|------|
| ✅ 释放本关系全部幸存成员占用 | `DELETE .../share-groups/{id}?survivorPolicy=RELEASE` | 200,成员占用全释,同槽非成员不动 |
| ✅ 仅在成员间两两合法时整槽保留 | `DELETE .../share-groups/{id}?survivorPolicy=KEEP_LEGAL` | 成员间有冲突时 602111 拒绝,整请求回滚 |
| ✅ 真释放冲突成员并标记待改派 | `DELETE .../share-groups/{id}?survivorPolicy=REASSIGN` | 200,释放冲突成员;成员与非成员冲突时成员让位 |
| ❌ 不传 `survivorPolicy` | `DELETE .../share-groups/{id}`(无查询参数) | 602110,不是 400 |
| ❌ `shareGroupId` 指向不存在或已解除的关系 | 任意 `survivorPolicy` | 602108 |
### 切换状态时的必要动作
- `survivorPolicy` 必须始终传值——不传拿到 602110,前端应按此逻辑引导用户补选。
- 若修改派单分配方案,必须显式传递三选一的大写字面量,不要自行拼接或做大小写转换。
---
## 五、数据库行为
- **本服务日范围内**:解除共用关系后,该关系名下、在**这一个服务日**上仍存活的**成员派车行**会被置为「待派车」,车辆与司机清空(`RELEASE` / `REASSIGN` 都触发;`KEEP_LEGAL` 冲突时整请求回滚)。
- **修复点(本次变化)**:**同一资源、同一服务日上那些**不属于本关系****的派车行不再受影响——即便它们挂在同一辆车/同一名司机上。
- **新增操作日志**:软清每一条成员行时,往 `fleet_assignment_operation_log` 追加一行 `share_release_cleared` 记录,带 `shareGroupId` / `groupBatchId` / `vehicleIdBefore` / `driverIdBefore` 等字段(见下节)。
---
## 六、边界行为
- 未登录 → 401(网关拦截)
- 无 `fleet:group-dispatch:write` 权限 → 403
- `shareGroupId` 不存在或已解除 → 602108(不是 404)
- 未传 `survivorPolicy` → 602110(不是 400)
- `survivorPolicy` 不是三个合法字面量之一 → 不报错,落入隐式默认分支
- 本接口不依赖外部服务(不查 Feign、不查 MQ),没有"下游降级"路径
- 🔴 本次修复范围之外的已知缺陷:同一资源同一服务日上的**非成员**在途行仍会被 `RELEASE` 一并软清且同样静默,本次不改
---
## 六.5、枚举 / 数据字典
### survivorPolicy(`ShareSurvivorPolicy`)
**所属字段**: `survivorPolicy`(Query 入参)| **类型**: `String`
| 值 | 中文 | 说明 |
|----|------|------|
| `RELEASE` | 全部释放 | 释放本关系的**全部幸存成员**的占用,置「待派车」;同槽非成员不动 |
| `KEEP_LEGAL` | 仅合法保留 | 本关系幸存成员间两两全合法时整槽保留;有冲突则 602111 拒绝回滚 |
| `REASSIGN` | 释放并待改派 | 真释放冲突成员的占用,派单置「待改派」;成员与非成员冲突时成员让位 |
### operation_type(`AssignmentOperationTypeEnum`)
**新增值** `share_release_cleared`:共用关系解除释放占用
| 值 | 中文 | 说明 |
|----|------|------|
| `share_release_cleared` | 共用关系解除释放占用 | 解除共用关系时,该派单作为成员被释放占用并置「待改派」;详见 detail_json |
**detail_json 结构**(JSON 字段类型):
```json
{
"shareGroupId": "359915397639704576",
"groupBatchId": "2101524283048263681",
"serviceDate": "2026-10-03",
"resourceType": "VEHICLE",
"resourceId": "2065329519232720897",
"releaseReason": "共用关系解除",
"survivorPolicy": "RELEASE",
"vehicleIdBefore": "G",
"driverIdBefore": "D001"
}
```
---
## 六.6、修改前后对比
### 字段级对比
| 字段 | 改前 | 改后 |
|------|------|------|
| (无)请求 / 响应字段 | 不变 | 不变——本次未增删改任何字段 |
### 行为级对比
| 行为 | 改前 | 改后 |
|------|------|------|
| `RELEASE` 处置的派车行范围 | 该车/该司机**同一服务日**上**全部**在途行 | 仅**本关系的成员** |
| `releasedSourceIds` 里可能出现非成员行 | 会 | 不会 |
| 同槽非成员在途行是否被清成「待派车」 | 会,且无任何信号 | 不会 |
| 错误码 602111 / 602112 的判定范围 | 整槽全部 claim | 仅「至少一侧是本关系成员」的组合 |
| 派单操作日志 | 无 `share_release_cleared` 记录 | 新增记录,包含 `vehicleIdBefore` / `driverIdBefore` |
## 六.7、影响评估
- **是否破坏向后兼容**: 否——请求/响应字段、错误码、网关路由全部不变。
- **前端是否必须同步上线**: 否——管理后台无需改任何代码;但**操作日志展示侧需要注意新字段**。
- **前端 workaround 清理点**: 无新增 workaround。但请转告使用方——测试环境中那些跨服务日、跨团、从不属于本关系的派单,如果曾因为解除共用关系而被误清,不再会发生这种事了。
- **给 mmg 的知会重点**:
- 「解除共用关系」这个动作的**影响范围**变了——同一车/司机同一服务日上的非成员行不再被波及。
- **操作日志时间线上会看到新的 `share_release_cleared` 记录**——必须把这条记录类型加进白名单,否则用户看不到派单是被哪次共用关系解除清掉的。
- **存量处理**:无需任何 SQL 回滚。测试库中的误清行正常通过改派写口恢复。
---
## 七、不影响范围
- **仅影响**: 管理后台「团期配车」页解除车辆/司机共用关系这一个动作的副作用范围
- **零影响**:
- 请求/响应字段结构、错误码(全部不变,前端无需改代码)
- 网关路由配置(既有路由无需改动)
- 确认共用关系 `POST .../batches/{groupBatchId}/share-groups`、查询共用关系 `GET .../batches/{groupBatchId}/share-groups` 两个端点
- 同一资源同一服务日上**非成员**在途行的处置口径(仍是旧行为,已在本单纳入)
---
## 八、测试环境已验证
**部署**:fleet 于 2026-09-20 22:42 部署到测试服 `192.168.100.236`,落点 `00930f5d6`(`deploy-status.sh` 复核),Nacos 两实例(8087 / 8187)`healthy=true`。
### 网关实测(2026-09-21,真实请求 + 库回读)
夹具:全新团期 `2101690789438570497`,服务日 **2026-10-30**,共用车 G `2065329515961163778`;三户 A / B / C 与团车行均实占该车该日,其中 **A、B 是共用关系 `360081477972660224` 的成员,C 与团车行不是成员**。
```
DELETE /admin/fleet/group-dispatch/share-groups/360081477972660224?survivorPolicy=RELEASE
→ 200
{"data":{"status":"RELEASED","survivorPolicy":"RELEASE",
"keptSourceIds":["2101691129806340098","360081478538891264"],
"releasedSourceIds":["360081478098489344","360081478333370368"]}}
```
**① 阳性对照(证明解除真的执行了)**:`releasedSourceIds` 恰为成员 A `360081478098489344` 与 B `360081478333370368`;库回读两行 `assignment_status: assigned → unassigned`,`vehicle_id` / `driver_id` → `NULL`。
**② 阴性对照(本次修复的核心断言)**:非成员 C `360081478538891264` 三字段解除前后**逐一相等**,且不在 `releasedSourceIds` 里:
| | assignment_status | vehicle_id | driver_id |
|---|---|---|---|
| 解除前 | assigned | 2065329515961163778 | 2065272147277758465 |
| 解除后 | assigned | 2065329515961163778 | 2065272147277758465 |
> ⚠️ **为什么必须两条一起看**:只有 ① 的话,「C 没变」与「解除根本没跑起来」在观测上完全相同;而 ① 本身在**修复前也成立**(旧实现同样会释放成员,只是顺手把 C 也清了)——单看 ① 是一个在「修好了」和「没修」两个世界里都为真的读数。
### 后端单元测试 / 集成测试
- `GroupDispatchShareSurvivorServiceTest`:15 条(原 11,**+4**)
- `GroupDispatchShareReleaseDayScopeIntegrationTest`:4 条(原 2,**+2**,H2 真 Flyway DDL + 真 Mapper)
- 变异证明:把处置范围回滚成「清整槽」后 **5 条红且全部是新增用例**,既有 71 条全绿;复原后全绿
---
## 十、相关文档
- 关联 Issue: [wx/HL#8061](https://git.1814.love:8443/wx/HL/issues/8061)
- 关联 Issue(前置修复): [wx/HL#8051](https://git.1814.love:8443/wx/HL/issues/8051)
- 关联 PR: [wx/HL#8067](https://git.1814.love:8443/wx/HL/pulls/8067)
- 关联 PR(前置修复): [wx/HL#8058](https://git.1814.love:8443/wx/HL/pulls/8058)
## 关联 / 联系人
### 链接
- **Issue**: [#8061](https://git.1814.love:8443/wx/HL/issues/8061)
- **PR**: [#8067](https://git.1814.love:8443/wx/HL/pulls/8067)
- **Merge commit**: [00930f5d6](https://git.1814.love:8443/wx/HL/commit/00930f5d681e3c3ffb5a6de80c070a5061cae7b2)
### 联系人
- **后端负责人**: @wx