---
schema: "hl-changelog/v2"
ticket: "5263"
title: "最终确认按车选择发送行程短信"
consumer: "admin"
change_type: "修改接口"
backend_status: "deployed"
gateway_status: "verified"
frontend_status: "implemented"
frontend_owner: "hl-ui-pi"
frontend_ref: "mmg/hl-ui@138136e50c4bbf9931ee020bd28d40977264b64e"
target_release: ""
verified_at: ""
status_note: "前端已在 138136e5 接入 HOLD 最终确认逐车短信必选、权威状态查询和 FAILED 受控重试；定向 7 个文件 62 项及 verify:changed 全量验证通过。尚未发布或完成真实页面联调。"
updated_at: "2026-07-27"
base: "dev-v3"
generated: "2026-07-26T17:33:00+08:00"
---

# 最终确认按车选择发送行程短信

## 关联

- Issue: [wx/HL#5263](https://git.1814.love:8443/wx/HL/issues/5263)
- Changelog PR: [wx/hl-api-changelog#41](https://git.1814.love:8443/wx/hl-api-changelog/pulls/41)
- 服务：`hl-fleet-service`、`hl-user-service`
- 前端仓库：`mmg/hl-ui`（本文仅交接，不代表已修改前端）
- 前置车费契约：[#5262 派车逐日车费、只读总价与核单实时接口](./26_5262_派车逐日车费与核单实时接口-修改接口-前端待处理-管理后台.md)

## 关键变化

1. 仅在 HOLD 排车的最终确认阶段，每个车辆组必须显式选择“发送短信”或“不发送短信”，没有默认值。
2. 选择发送时，确认事务只落可靠发送意图；短信异步发送失败不会回滚已完成的派车确认。
3. 短信只发给该车辆组当前师傅，包含订单摘要、接送摘要和签名行程短链，不包含客户手机号。
4. 选择不发送时只完成派车确认，不创建行程短信事件。
5. 多车订单逐车独立选择、独立投递、独立查询状态和受控重试。
6. 已派定后的订单人数基线复核只能沿用原选择，不允许借复核修改选择或重复发送。
7. 直接派车流程不受影响；#5262 已废弃的手工车辆总价入参仍然禁止提交。

## 变更接口

| 方法 | 路径 | 变化 |
| --- | --- | --- |
| `POST` | `/admin/fleet/assignments/:assignmentId/confirm` | 请求新增必填 `sendItinerarySms`；响应新增短信选择、事件与状态 |
| `GET` | `/admin/fleet/assignments/:assignmentId/itinerary-sms` | 新增单车/车辆组短信审计状态查询 |
| `POST` | `/admin/fleet/assignments/:assignmentId/itinerary-sms/retry` | 新增明确失败后的车务受控重试 |

## 最终确认

### `POST /admin/fleet/assignments/:assignmentId/confirm`

#### 请求字段

| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `sendItinerarySms` | `Boolean` | 是 | `true` 发送；`false` 不发送；省略或 `null` 返回参数错误 |
| `requestId` | `String` | 是 | 最长 64 字符的幂等请求标识 |
| `vehicleFeeTotal` | `Decimal` | 否 | 历史兼容字段；非空即拒绝，最终总车费继续由 #5262 逐日车费只读合计 |
| `vehicleFeeAdjustmentReason` | `String` | 否 | 历史兼容字段；非空即拒绝 |

请求示例：

```json
{
  "sendItinerarySms": true,
  "requestId": "fleet-final-confirm-26-8411-car-1"
}
```

#### 新增响应字段

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `sendItinerarySms` | `Boolean` | 本车辆组最终确认时保存的选择 |
| `itinerarySmsEventId` | `String/null` | 可靠短信事件 ID；不发送时为空 |
| `itinerarySmsStatus` | `String` | 首次确认返回 `PENDING` 或 `NOT_SENT`；已派定复核回显真实状态 |

## 短信状态

### `GET /admin/fleet/assignments/:assignmentId/itinerary-sms`

响应 `data`：

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `assignmentId` | `String` | 派单 ID |
| `assignmentGroupId` | `String` | 跨服务日车辆组 ID |
| `assignmentSlotId` | `String` | 稳定车辆槽位 ID |
| `sendItinerarySms` | `Boolean/null` | 未最终确认或历史数据时为 `null` |
| `status` | `String` | `NOT_APPLICABLE` / `NOT_SENT` / `PENDING` / `SENT` / `FAILED` / `CANCELED` |
| `eventId` | `String/null` | 可靠短信事件 ID |
| `retryCount` | `Integer` | 已发生的失败重试次数 |
| `canRetry` | `Boolean` | 当前是否允许受控重试 |
| `sentAt` | `LocalDateTime/null` | 供应商确认的真实发送时间 |
| `lastError` | `String/null` | 已脱敏的最近失败或待对账原因 |

前端以 `status` 为权威，不得仅凭最终确认接口成功就显示“短信已发送”。

## 受控重试

### `POST /admin/fleet/assignments/:assignmentId/itinerary-sms/retry`

请求：

```json
{
  "reason": "短信通道配置已恢复，车务确认重发",
  "requestId": "retry-sms-26-8411-car-1"
}
```

规则：

- 仅 `FAILED` 且 `canRetry=true` 时展示并调用重试。
- `SENT`、`PENDING`、待供应商对账、`CANCELED`、未选择发送时禁止重试。
- 重试复用原事件与供应商幂等键，不新建并行短信事件。
- 司机或车辆组身份已变化时，旧事件收敛为 `CANCELED`，不得发给旧师傅。

## 短信与隐私约束

短信模板参数固定为：

| 参数 | 内容 |
| --- | --- |
| `summary` | 脱敏订单摘要 |
| `transfer` | 接送摘要 |
| `code` | 签名行程短链 |

短信正文及模板参数不得包含客户手机号。真实联系人信息仅在既有签名行程 H5 中按授权展示。

## 页面展示矩阵

| 区域/状态 | 数据源 | 展示 | 空态/禁用 | 颜色 | 守恒规则 |
| --- | --- | --- | --- | --- | --- |
| 最终确认车辆卡片 | 本地待提交选择 | “发送短信”/“不发送短信”二选一 | 未选择时禁止确认并提示必选 | 发送蓝色，不发送中性灰 | 每个车辆组恰好一个选择 |
| 确认后状态 | `GET .../itinerary-sms.status` | 待发送/已发送/发送失败/已取消/未发送 | 历史数据为“不适用” | 待发送蓝、已发送绿、失败红、取消灰、未发送中性灰 | 不以确认成功冒充发送成功 |
| 失败操作 | `canRetry` | “重试短信” | `canRetry=false` 时隐藏或禁用 | 可重试橙色 | 同一事件串行重试 |
| 多车订单 | 每个 `assignmentGroupId` | 每车独立选择与状态 | 不做订单级统一默认 | 各卡片独立 | 一车选择不得覆盖另一车 |

## 前端处理清单

- [ ] 最终确认页按车辆组渲染无默认值的短信二选一。
- [ ] 未完成选择时不提交确认请求，并展示明确校验提示。
- [ ] 确认请求始终显式提交 `sendItinerarySms`，不再依赖后端默认值。
- [ ] 确认后通过状态接口展示真实投递状态。
- [ ] 仅在 `FAILED && canRetry=true` 时允许填写原因并调用重试。
- [ ] 已派定复核回显原选择并保持只读，不提供改选入口。
- [ ] 短信状态按展示矩阵处理空态、颜色及多车独立性。
- [ ] 确认和复核请求继续不提交 #5262 已废弃的手工总车费字段。

## 验证证据

- OpenAPI/oasdiff：`not_configured`；已完成 Controller/VO 源码比对和接口测试回退证据。
- 消费者契约：`not_required`；未修改 internal Feign 或共享 Java DTO。
- 代码与测试：PR `wx/HL#5270` 已合入 `dev-v3`，merge commit 为 `f0a96c10124c3188e60e1291e7f28d768af50e3a`；`mvn -pl hl-user-service,hl-fleet-service -am test`、`mvn -pl hl-fleet-service spotless:check`、`mvn -pl hl-fleet-service -am verify` 均通过。
- 测试部署：`hl-user-service` 与 `hl-fleet-service` 已从合并后的 `dev-v3` 完成双实例滚动部署并通过健康检查。
- 网关：显式“不发送”业务验收 11/11 通过；合并后只读复验 3/3 通过，最终状态为 `assigned + NOT_SENT`，无短信事件且不可重试。
- `frontend_status`：`pending`；真实领取后再迁移为 `claimed` 并填写 `frontend_owner`。
