---
schema: "hl-changelog/v2"
ticket: "5364"
title: "连续执行段司机通知、回复与整组确认隔离"
consumer: "admin"
change_type: "修改接口"
backend_status: "deployed"
gateway_status: "not_required"
frontend_status: "implemented"
frontend_owner: "Pi"
frontend_ref: "v2.1@193575171d817f1042040baf8e0d9d28e7206048"
target_release: ""
verified_at: "2026-08-03"
status_note: "后端 PR #5420 已 squash 合并到 dev-v3（e543a41035）；最终本地 Fleet 2820 项与 Order 7261 项 reactor verify 均通过。本工单冻结范围不要求运行时部署或网关验证；管理后台已由 Pi 领取，正在适配。"
updated_at: "2026-08-03"
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 发布不代表前端已实现、发布或页面验证。
