---
schema: "hl-changelog/v1"
ticket: "5186"
title: "排车中订单恢复派车派人入口并补齐改派上下文"
consumer: "admin"
backend: "verified"
gateway: "verified"
frontend: "pending"
base: "dev-v3"
generated: "2026-07-23T16:10:00+08:00"
---

# Fleet：排车中订单恢复“派车派人”入口

> **服务**: hl-fleet-service  
> **Issue**: #5186  
> **日期**: 2026-07-23  
> **影响范围**: 管理后台车务派单看板及订单派车流程

---

## ⚠️ 关键变化

排车状态为 `holding` 或 `holding_urgent` 时，订单并非不可操作：后端现在返回 `canAssign=true`，并在 `availableActionCodes` 中下发 `CHANGE_ASSIGNMENT`，允许车务继续进入“派车派人”流程调整司机或车辆。

2026-07-23 契约修订：改派候选查询需要用 `orderId + requirementId + fleetItemIndex` 精确定位当前订单的当前用车需求槽位。看板列表、详情顶层和详情内每个有效派车组现均稳定返回字符串形式的 `requirementId`，前端直接透传，不自行推导。

## 变更接口

| 接口 | 方法 | 路径 | 变更类型 |
|------|------|------|----------|
| 派单看板列表 | GET | `/admin/fleet/board/orders` | 响应字段取值扩展、新增字段 |
| 派单看板详情 | GET | `/admin/fleet/board/orders/:orderId` | 新增字段 |

请求参数和写接口路径均未改变。

## 二、响应契约

`records[]` 中以下字段按服务端返回值处理：

| `assignmentStatus` | `canAssign` | `availableActionCodes` | 前端行为 |
|---|---:|---|---|
| `unassigned` | `true` | 包含 `ASSIGN` | 展示“派车派人”，提交既有创建派单接口 |
| `unassigned_urgent` | `true` | 包含 `ASSIGN` | 展示“派车派人”，提交既有创建派单接口 |
| `holding` | `true` | 包含 `CHANGE_ASSIGNMENT` | 展示“改派”，提交既有 `changeAssignment` 改派接口 |
| `holding_urgent` | `true` | 包含 `CHANGE_ASSIGNMENT` | 展示“改派”，提交既有 `changeAssignment` 改派接口 |
| `assigned` / `completed` / `canceled` | `false` | 不包含上述可派动作 | 不展示入口 |

前端不要再用 `assignmentStatus === 'unassigned'` 自行推断入口，也不要因为订单已有司机或车辆就隐藏按钮。入口以 `canAssign === true` 为第一判断，具体提交模式以 `availableActionCodes` 为准。

排车中进入流程属于调整当前有效派单，不是新增第二条有效派单；继续复用现有改派请求、基线差异提示、司机车辆档期冲突提示和刷新逻辑。

### 改派候选上下文

| 响应位置 | 新增字段 | 类型 | 用途 |
|---|---|---|---|
| 列表 `data.records[]` | `requirementId` | `string` | 当前卡片所属用车需求 ID |
| 详情 `data` | `requirementId` | `string` | 当前有效用车需求 ID |
| 详情 `data.currentAssignment` | `requirementId` | `string` | 当前派车组所属用车需求 ID |
| 详情 `data.activeAssignments[]` | `requirementId` | `string` | 每个有效派车组所属用车需求 ID |

进入改派候选查询时：

- `orderId` 取列表返回的数字订单 ID；
- `requirementId` 优先取详情顶层同名字段，按具体派车组操作时可取该组的同名字段；
- `fleetItemIndex` 取当前卡片或当前派车组字段；
- 三者必须原样透传给候选接口，不得使用团号、订单号或数组位置替代；
- `orderId`、`requirementId` 均按字符串处理，避免 JavaScript 大整数精度丢失。

当前 `v2.1` 候选请求组装已经读取 `order.requirementId`；后端部署后，从列表进入并合并详情时会获得该字段，无需前端猜测需求 ID。

### 排车中入口与向导状态

测试环境现状仍有一处前端状态错位：看板卡片已经显示“排车中”，点击“派车派人”后虽然按 `CHANGE_ASSIGNMENT` 进入 `reassign` 模式，但 `resolveAssignFlowRestoreState(order, mode)` 对所有非 `confirmHold` 模式固定返回 `step: 1`，导致向导错误高亮“订单详情”，底部也显示“下一步 · 排车”。

前端需要统一按后端状态和动作码恢复入口语义：

- `assignmentStatus=holding/holding_urgent` 且动作码包含 `CHANGE_ASSIGNMENT` 时，卡片按钮文案显示“改派”，不要继续显示“派车派人”；
- 从该入口打开时保持 `mode=reassign`，向导直接进入第 2 步“排车”，第 1 步“订单详情”显示已完成；
- 第 2 步带出当前司机、车辆，允许只更换其中一项；提交继续调用既有 `changeAssignment`，不得新增第二条有效派单；
- “司机已确认/查看待确认”入口仍使用 `confirmHold` 并恢复第 3 或第 4 步，不能被本次改派逻辑影响；
- 首次待派车订单仍从第 1 步开始，按钮仍为“派车派人”。

以上仅是前端状态机和展示文案调整，后端不新增接口或字段。

### `holding` 的用户可见状态文案

`holding` 是后端技术状态码，表示车辆和司机已经锁定、派单通知已经发出，当前正在等待司机回复。面向车务人员时不能继续显示“排车中”，应统一显示为“待确认”：

- 看板卡片主状态：`holding` 显示“待确认”，`holding_urgent` 显示“待确认即将超时”；
- 状态筛选、数量汇总和图例使用同一套“待确认”文案；
- 卡片上的司机回执徽标可显示“待回复”，用于补充说明，不能与主状态“排车中”形成两个不同口径；
- 技术值仍保持 `holding/holding_urgent`，接口请求参数、状态判断、颜色和改派动作码均不改变；
- 首次尚未锁定车辆和司机的 `unassigned` 继续显示“待派车”，确认完成后的 `assigned` 继续显示“已派车”。

当前 `v2.1` 的 `ORDER_STATUS_META`、`FILTER_STATUS_OPTIONS` 以及后端 `assignmentStatusLabel` 仍含“排车中”旧文案。管理后台应以本节用户口径覆盖展示；如直接消费后端 label，前端需按状态码归一，避免同页出现“排车中”和“待确认”两套名称。

### 待确认订单的“继续派车”入口

待确认订单必须同时保留“继续派车”和“改派”两个入口：

- `availableActionCodes` 包含 `RECORD_DRIVER_CONFIRMATION` 时显示主按钮“继续派车”，点击复用现有 `onConfirmHold(order)`，以 `mode=confirmHold` 打开派单弹窗；
- `confirmHold` 根据后端 `stageCode/driverConfirmedAt` 恢复流程：等待司机回复时进入第 3 步“待确认”，已登记司机确认时进入第 4 步“确认执行”；
- `availableActionCodes` 包含 `CHANGE_ASSIGNMENT` 时另行显示“改派”，点击进入第 2 步重新选择车辆或司机；
- 两个按钮不得互相替代：“继续派车”推进当前有效派单，“改派”修改当前有效派单；
- “复制行程单链接”是独立只读能力，复制后端为当前有效派单签发的司机 H5 链接，不参与派单状态流转。

当前 `v2.1@6e6a11bf` 的 `resolveBoardRowActions()` 仍检查已经废弃的 `CONFIRM` 动作码，而后端生命周期实际下发 `RECORD_DRIVER_CONFIRMATION`，因此截图中“继续派车/司机已确认”按钮没有渲染。前端改为消费真实动作码即可，无需后端增加兼容别名。

### 看板复制司机 H5 行程单链接

看板不再维护独立的“发行程单”抽屉，也不在前端模拟发送成功。这里不是打开订单详情的“打印行程单”，而是把后端已经为当前有效派单签发的 H5 链接复制到剪贴板，车务再通过微信等渠道发给司机。司机可在手机浏览器中独立打开，不需要登录管理后台。

- 将看板按钮文案由“发行程单”改为“复制行程单链接”，事件名同步改为 `copy-itinerary-link`，避免继续表达成“发送”；
- 点击时先调用既有看板详情接口 `GET /admin/fleet/board/orders/{orderId}`，不要调用订单打印接口；
- 默认复制 `currentAssignment.itineraryUrl`；如果入口明确针对某一条有效派单，则复制对应 `activeAssignments[].itineraryUrl`；
- 链接必须直接使用后端返回值，前端不得自行拼接 H5 地址、token、订单 ID 或派单 ID；
- 优先使用 `navigator.clipboard.writeText(itineraryUrl)`；当前管理后台可能运行在 HTTP 内网环境，必须同时提供临时 `textarea + document.execCommand('copy')` 降级实现；
- 复制成功提示“行程单链接已复制，可发送给司机”；
- `itineraryUrl` 为空时不得复制或提示成功，应提示“行程单链接暂不可用，请确认已派车且 H5 配置正常”；
- 删除/停用看板自己的 `ItinerarySendSheet.vue`、消息模板和本地 `message.success('已发送行程单')` 假流程；
- 不复用 `PrintItineraryModal.vue`，不调用 `GET /v3/admin/order/{orderId}/print-itinerary`；订单详情的“打印行程单”继续作为面向车务的独立打印能力保留；
- 链接包含签名 token，前端日志、埋点和错误提示不得记录或展示完整 URL；
- 改派成功后必须重新拉取详情并使用新派单的 `itineraryUrl`，不能继续缓存或复制旧派单链接。

后端链接已绑定当前订单与具体派单，有效期至行程结束后 7 天；公开 H5 接口会校验签名、有效期和派单归属，并实时读取行程数据。复制动作本身不改变派单状态，也不产生“已发送”记录。

建议前端按以下逻辑落地（函数名可按现有工程调整）：

```ts
async function onCopyItineraryLink(order: BoardOrder) {
  const orderId = resolveBoardOrderId(order)
  const detail = await getBoardOrderDetail(orderId)
  const itineraryUrl = detail.currentAssignment?.itineraryUrl?.trim()

  if (!itineraryUrl) {
    message.error('行程单链接暂不可用，请确认已派车且 H5 配置正常')
    return
  }

  try {
    if (navigator.clipboard && window.isSecureContext) {
      await navigator.clipboard.writeText(itineraryUrl)
    } else {
      copyTextByTextarea(itineraryUrl)
    }
    message.success('行程单链接已复制，可发送给司机')
  } catch {
    message.error('复制失败，请稍后重试')
  }
}

function copyTextByTextarea(text: string) {
  const textarea = document.createElement('textarea')
  textarea.value = text
  textarea.setAttribute('readonly', '')
  textarea.style.position = 'fixed'
  textarea.style.opacity = '0'
  document.body.appendChild(textarea)
  textarea.select()
  const copied = document.execCommand('copy')
  document.body.removeChild(textarea)
  if (!copied) throw new Error('copy failed')
}
```

## 三、不影响范围

- `canRejectRequirement` 仍只在未派阶段可能为 `true`；排车中不得重新开放“驳回用车需求”。
- 已派车、已完成、已取消状态不会因本次变更开放派车入口。
- 无数据库、Redis、MQ、候选请求字段或错误码变更。

## 四、前端自测清单

- [ ] `holding` 订单显示“改派”按钮，点击后带出当前司机、车辆并进入调整流程。
- [ ] `holding_urgent` 同样显示“改派”并进入调整流程。
- [ ] `holding/holding_urgent + CHANGE_ASSIGNMENT` 卡片按钮显示“改派”，打开后直接高亮第 2 步“排车”，第 1 步为已完成。
- [ ] 首次待派车仍从第 1 步开始；`confirmHold` 仍恢复第 3/4 步，三种入口互不串态。
- [ ] `holding/holding_urgent` 在卡片、筛选、汇总和图例统一显示“待确认/待确认即将超时”，页面不再出现“排车中”旧文案。
- [ ] 技术状态值仍为 `holding`，改派、司机确认和超时判断不因文案变化而改变。
- [ ] 待确认订单在 `RECORD_DRIVER_CONFIRMATION` 可用时显示“继续派车”，点击以 `confirmHold` 恢复第 3/4 步。
- [ ] “继续派车”和“改派”同时存在且职责分离；前端不再检查不存在的 `CONFIRM` 动作码。
- [ ] 待确认或已派车且存在当前有效派单时，看板显示“复制行程单链接”。
- [ ] 点击后通过看板详情取得并复制 `currentAssignment.itineraryUrl`，不调用订单打印接口、不在前端拼接链接。
- [ ] HTTPS/localhost 使用 Clipboard API，HTTP 内网环境可通过降级方案正常复制。
- [ ] 复制成功提示“行程单链接已复制，可发送给司机”；链接为空或复制失败时给出明确错误且不误报成功。
- [ ] 复制出的链接可由司机在手机浏览器中独立打开，无需登录管理后台，并展示当前派单的实时行程。
- [ ] 看板不再使用 `ItinerarySendSheet` 或模拟“已发送行程单”，改派后不会复制旧派单链接。
- [ ] 打开排车步骤时，候选请求同时携带字符串 `orderId`、`requirementId` 和当前 `fleetItemIndex`，不再出现“改派候选查询必须携带当前订单ID、用车需求ID和车型项索引”。
- [ ] 提交时调用既有 `changeAssignment`，不调用创建派单接口。
- [ ] `assigned`、`completed`、`canceled` 不误显示入口。
- [ ] 调整成功后刷新看板，页面只保留一组当前有效派单。

## 验证证据

- 后端提交：`aaeb01242`；PR：[wx/HL#5190](https://git.1814.love:8443/wx/HL/pulls/5190)，已合并 `dev-v3`。
- 状态矩阵、生命周期动作、改派上下文及排车中改派保护已有定向测试覆盖；`BoardOrderServiceTest` 与 `BoardControllerTest` 共 49 项通过。
- `mvn -f hl-fleet-service/pom.xml spotless:check` 通过。
- `mvn -pl hl-fleet-service -am verify` 通过。
- 测试环境 Fleet 滚动部署任务 `3458b3ab` 成功，8087、8187 两实例健康。
- 网关按团号 `26-7042` 验证：列表与详情 HTTP/code 200，`requirementId` 在列表、详情顶层、`currentAssignment` 和 `activeAssignments[]` 均存在；携带 `orderId + requirementId + fleetItemIndex + excludeAssignmentId` 调用候选接口 HTTP/code 200，返回 19 辆车、19 名司机候选。
- 当前前端 `v2.1` 已从 `order.requirementId` 组装候选请求；但截至 `v2.1@6e6a11bf`，`resolveAssignFlowRestoreState` 对 `reassign` 仍固定恢复第 1 步，且排车中入口文案仍为“派车派人”，需要按上方状态规则调整。

## 六、相关文档

- [wx/HL#5186](https://git.1814.love:8443/wx/HL/issues/5186)

