--- schema: "hl-changelog/v2" ticket: "8061" title: "解除共用关系只处置本关系的成员——同一服务日上的非成员在途行不再被连带清空(行为修复,接口形状不变)" consumer: "admin" author: "wx(GIT)" change_type: "修改接口" backend_status: "deployed" gateway_status: "verified" frontend_status: "not_required" 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 没变」与「解除没跑起来」观测相同,且阳性在修复前同样成立。详见正文「八、测试环境已验证」。(📌 gateway_status 这个字段记的是「网关验证有没有做过」,不是「网关路由要不要改」——先例见 2026-08 #5405/#5407:『测试环境部署和网关验证尚未执行,因此 backend_status、gateway_status 保持 pending』。本次不新增端点、不改路由形状,但那不构成填 not_required 的理由,填 verified 的理由是上面那次真实调用。)frontend_status:后端起初填 pending,理由是——请求/响应字段确实零增删,但派车操作日志时间线新增了 operation_type 取值 share_release_cleared——前端若按 operation_type 白名单过滤,这条会被静默丢掉,用户就看不到『车和司机是被哪次共用关系解除清掉的』,而那正是本次补留痕要解决的问题。⇒ 前端至少需要确认自己有没有这层过滤、必要时加上,这是动作不是知会,故 pending。另按指南,not_required 会禁止 mmg 回写 frontend_owner 等认领字段,填错反而挡住认领。mmg 前端实证 2026-09-20:fleet 操作日志读口 getOrderOperationLog(api/fleet/board.js:58)在全仓零调用方,src/views/fleet 无 operationType 命中——后端担心的「时间线按 operation_type 白名单过滤会静默丢 share_release_cleared」过滤层在前端不存在(该时间线 UI 尚未建),故前端零改动,翻 not_required。后续接入操作日志时间线时 operation_type 渲染必须包含 share_release_cleared(detail_json 全字段字符串 ID 透传)。share-groups 解除入口前端未接入,属 #7444 挂起域,同 #8051 口径。" 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\ | 保留占用的来源 ID(本关系成员中被保留的) | | releasedSourceIds | List\ | **已释放占用的来源 ID**(**仅本关系成员,不含非成员**) | | pendingReassignSourceIds | List\ | 需人工改派的来源 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