--- author: "wx(GIT)" schema: "hl-changelog/v2" ticket: "5364" title: "连续执行段司机通知、回复与整组确认隔离" consumer: "admin" change_type: "修改接口" backend_status: "deployed" gateway_status: "not_required" frontend_status: "verified" frontend_owner: "Pi" frontend_ref: "hl-admin@6e4c6b224b204cd792e02f29a6e61689b0d9a588" target_release: "v2.1" verified_at: "2026-08-03" status_note: "后端 PR #5420 已 squash 合并到 dev-v3(e543a41035);最终本地 Fleet 2820 项与 Order 7261 项 reactor verify 均通过。本工单冻结范围不要求运行时部署或网关验证;管理后台已由 Pi 领取,正在适配。" updated_at: "2026-08-04" base: "dev-v3" --- # 连续执行段司机通知、回复与整组确认隔离(#5364) > **服务**:`hl-fleet-service`、`hl-order-service-v3` > > **后端 PR**:[#5420](https://git.1814.love:8443/wx/HL/pulls/5420) > > **Backend Issue**:[#5364](https://git.1814.love:8443/wx/HL/issues/5364) > > **Frontend tracking**:[#5366](https://git.1814.love:8443/wx/HL/issues/5366) ## 变更接口 | 接口 | 方法 | 路径 | 变更类型 | |---|---|---|---| | 查询车务派车订单详情 | `GET` | `/admin/fleet/board/orders/:orderId` | additive 响应字段 | | 登记司机确认 | `POST` | `/admin/fleet/assignments/:assignmentId/driver-confirmation` | additive 请求字段与段级约束 | | 用车需求整组原子确认 | `POST` | `/admin/fleet/assignments/requirements/:requirementId/confirm` | 新增接口 | | 旧单派车确认 | `POST` | `/admin/fleet/assignments/:assignmentId/confirm` | multi-group 行为收紧,singleton 兼容 | | 司机 H5 行程单 JSON | `GET` | `/app/h5/itinerary/:token` | additive/versioned 响应字段与段级裁剪 | ## 1. Board 详情字段 `GET /admin/fleet/board/orders/:orderId` 的既有字段保持不变,新增字段均为 additive/null-safe。 ### 1.1 顶层需求快照 | 字段 | JSON 类型 | 可空 | 说明 | |---|---|---|---| | `requirementId` | `String(Long)` | 是 | 当前有效用车需求 ID | | `requirementVersion` | `Integer` | 是 | 当前需求版本 | | `requirementSha256` | `String` | 是 | 当前需求 canonical SHA-256;整组确认时原样回传 | | `driverConfirmationSummary` | `Object` | 否 | 当前 generation 的服务端守恒汇总 | `driverConfirmationSummary`: | 字段 | JSON 类型 | 说明 | |---|---|---| | `dispatchPlanGeneration` | `String(Long)` / `null` | 当前最终派车方案代际 | | `requiredSegmentCount` | `Integer` | 当前需司机确认的 HOLD 执行段数 | | `confirmedSegmentCount` | `Integer` | 已满足有效确认谓词的执行段数 | | `pendingSegmentCount` | `Integer` | 待确认且非歧义段数 | | `rejectedSegmentCount` | `Integer` | 当前拒绝且待改派段数 | | `ambiguousSegmentCount` | `Integer` | 通知结果歧义段数 | | `allDriverConfirmed` | `Boolean` | 所有 required 段是否均确认 | | `allExecutionConfirmed` | `Boolean` | 全部实际用车切片是否均确认执行 | 守恒规则:`confirmedSegmentCount + pendingSegmentCount + rejectedSegmentCount + ambiguousSegmentCount = requiredSegmentCount`。消费者不得使用 `currentAssignment` 或任一代表司机自行计算整组完成。 ### 1.2 `activeAssignments[]` 执行段字段 每项按独立 `assignmentGroupId` 返回;同槽多段、同司机非连续多段不得合并。 | 字段 | JSON 类型 | 可空 | 语义 | |---|---|---|---| | `assignmentGroupId` | `String(Long)` | 否 | 当前连续执行段身份 | | `assignmentSlotId` | `String(Long)` | 否 | 稳定车辆槽位身份 | | `serviceDates[]` | `String(date)[]` | 否 | 本段精确服务日,不补日期洞 | | `holdNotificationGeneration` | `String(Long)` | 是 | 当前段通知代际 | | `holdNotificationStatus` | `String` | 否 | `NOT_REQUIRED/PENDING/SENT/FAILED/AMBIGUOUS/INVALIDATED` | | `holdSentAt` | `String(date-time)` | 是 | 有可信发送成功事实时才返回 | | `driverConfirmedAt` | `String(date-time)` | 是 | 当前段司机确认时间 | | `driverReplyNote` | `String` | 是 | 回复摘要 | | `driverReplySource` | `String` | 是 | `NOTIFICATION/MANUAL_CONTACT`;历史未知保持 `null` | | `driverReplyOperatorId` | `String(Long)` | 是 | 人工登记操作人;历史未知可空 | | `driverReplyRecordedAt` | `String(date-time)` | 是 | 回复事实登记时间 | | `driverConfirmationEvidencePresent` | `Boolean` | 否 | 是否存在确认凭证 | | `driverConfirmationEvidenceCount` | `Integer` | 否 | 凭证数量,不新增文件元数据 | `SENT` 只表示系统发送成功,不表示通道已提供送达回执;前端不得展示为 `DELIVERED`。 ## 2. 登记司机确认 `POST /admin/fleet/assignments/:assignmentId/driver-confirmation` 请求新增: | 字段 | 类型 | 必填 | 说明 | |---|---|---|---| | `driverReplySource` | `String` | 否 | 仅允许 `NOTIFICATION` 或 `MANUAL_CONTACT`;人工电话/微信确认必须显式传 `MANUAL_CONTACT` | 既有 `requestId`、`driverReplyNote`、`evidenceFileIds[]` 保持兼容。登记只更新 `assignmentId` 所属当前 group;旧 group、旧 generation、已改派/取消或资源快照漂移均 fail closed,不得跟随 slot 更新替代组。 ## 3. 用车需求整组原子确认 新增 `POST /admin/fleet/assignments/requirements/:requirementId/confirm`。 请求体: ```json { "orderId": "2080000000000000001", "requestId": "requirement-confirm-5364-001", "expectedRequirementVersion": 7, "expectedRequirementSha256": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", "expectedPlanGeneration": "12", "groups": [ { "assignmentGroupId": "2080000000000000101", "sendItinerarySms": true } ] } ``` - `groups[]` 必须是 Board 当前 generation 中全部 used execution groups 的精确集合;按 `assignmentGroupId` 去重。 - 服务端重读 exact slot×service-date topology 并一次提交;失败不产生部分状态推进、receipt 或重复 Outbox。 - `(requirementId, requestId)` 是 durable 幂等键:同 canonical 请求返回首次稳定结果;同键不同载荷返回 `605059`。 - 旧 `/:assignmentId/confirm` 在 multi-group 当前 generation 返回 `605057`;singleton 继续委托同一原子引擎。 主要业务错误: | code | 含义 | |---|---| | `605055` | 派车方案 generation 已变化 | | `605056` | 执行段精确集合已变化 | | `605057` | multi-group 必须使用需求级整组确认 | | `605058` | 至少一个当前执行段不满足最终确认条件 | | `605059` | 幂等 requestId 已用于不同确认载荷 | ## 4. H5 段级最小化 `GET /app/h5/itinerary/:token` 新增/versioned 字段: | 字段 | 类型 | 说明 | |---|---|---| | `contractVersion` | `String` | 当前为 `V2` | | `segmentDays` | `Integer` | token 所绑定执行段天数 | | `serviceDates[]` | `String(date)[]` | 本段精确服务日期 | | `segmentRoute` | `String` / `null` | 仅由本段逐日标题生成 | 既有 `days`、`route`、`daily`、`transport` 字段不删除且原语义不静默改写。V2 token 同时绑定 group、slot、plan generation、notification generation、driver、vehicle、精确日期摘要和 audience snapshot;任一漂移都返回失效,禁止按 slot 跳转到新司机或新 group。 ## 5. 前端消费动作 1. Board 按 `activeAssignments[].assignmentGroupId` 渲染独立执行段,不以 `currentAssignment`、司机 ID 或连续日期猜测合并。 2. 整组确认提交 Board 返回的 `requirementVersion/requirementSha256/dispatchPlanGeneration` 与全部 group 精确集合;收到 `605055/605056` 后刷新,不做部分重试。 3. 人工确认显式传 `driverReplySource=MANUAL_CONTACT`;展示通知状态与确认来源为两个独立事实。 4. H5 使用 `serviceDates/segmentDays/segmentRoute` 展示本段范围;保留对既有字段的兼容读取。 5. 所有 JSON Long 按字符串处理,禁止转 JavaScript `Number`。 ## 6. Internal Feign / shared Java Fleet 与 order-v3 新增内部 reservation/release 契约: - `POST /v3/internal/order/orders/:orderId/requirement/vehicle/final-confirmation-reservations` - `POST /v3/internal/order/orders/:orderId/requirement/vehicle/final-confirmation-reservations/release` 共享 DTO 采用 additive 字段,Fleet reservation、事务内 durable receipt、release Outbox 与 Order holder/fence 共同保证失败回滚和可重放释放。oasdiff 与 Spring Cloud Contract 均未配置,不能标记为工具 PASS;本次以 Controller JSON、producer/consumer 源码对比及两个 reactor verify 作为人工回退证据。 ## 验证证据 - Fleet:`mvn -pl hl-fleet-service -am verify`,2,820 tests,0 failures,0 errors,5 skipped,BUILD SUCCESS。 - Order:`mvn -pl hl-order-service-v3 -am verify`,7,261 tests,0 failures,0 errors,36 skipped,BUILD SUCCESS。 - Fleet Spotless:BUILD SUCCESS;`git diff --check dev-v3`:PASS。 - 最终独立 P0–P2 review:GO,无可复现阻断项。 - 后端 PR #5420 已 squash 合并到 `dev-v3@e543a41035`;按 changelog 发布门禁记录 `backend_status=deployed`,本工单不包含运行时测试部署。 - 本工单冻结范围不执行部署或网关调用,`gateway_status=not_required`。 - 前端未领取,`frontend_status=pending`;changelog 发布不代表前端已实现、发布或页面验证。 ## 关联 / 联系人 ### 链接 - **Issue**: [#5364](https://git.1814.love:8443/wx/HL/issues/5364) - **PR**: [#5420](https://git.1814.love:8443/wx/HL/pulls/5420) - **Merge commit**: [e543a41035ae](https://git.1814.love:8443/wx/HL/commit/e543a41035ae) ### 联系人 - **后端负责人**: @wx