---
schema: "hl-changelog/v2"
ticket: "5292"
title: "车务按服务日逐车配置用车、接机与价格"
consumer: "admin"
change_type: "修改接口"
backend_status: "deployed"
gateway_status: "verified"
frontend_status: "implemented"
frontend_owner: "hl-ui-pi"
frontend_ref: "0a35da1e3b88f9c375d3e79516780e5a66f2b8ad"
target_release: ""
verified_at: "2026-07-28T14:38:26+08:00"
status_note: "hl-admin 已修复可编辑 UNPLANNED/新槽位默认用车、ARRIVAL 参与状态保持及新增槽位候选有界加载；最终提交 0a35da1e 已在 origin/v2.1 可达，全量 checkpoint 通过"
updated_at: "2026-07-28"
base: "dev-v3"
---

# Fleet：按服务日逐车配置用车、接机与价格

> **服务**：`hl-fleet-service`
> **后端 PR**：[wx/HL#5296](https://git.1814.love:8443/wx/HL/pulls/5296)
> **Issue**：#5292
> **日期**：2026-07-27
> **影响范围**：管理后台车务看板派车弹窗和订单派车详情

---

## ⚠️ 关键变化

新派车页面不再提交“收取车费日期”。派车保存改为提交完整的“服务日期 × 稳定车辆槽位”矩阵，每个单元格独立说明是否用车、车辆、司机、ARRIVAL 接机参与和当天实际价格。

旧 `items` 输入暂时保留用于滚动发布兼容；旧收费日期字段只能随旧 `items` 使用，`dailyPlan` 模式提交这些字段会被拒绝。

## 2026-07-28 产品口径补充：默认全部行程用车

新建派车草稿进入“排车”步骤时，当前需求的**全部可编辑服务日、全部新建稳定车辆槽位均默认勾选“当天用车”**。车务只需要选择车辆、司机和逐日价格；只有主动取消某日勾选或点击“明确不用车”，才表示该日明确不用车。

### 初始化规则

1. 后端 `planState=UNPLANNED` 表示尚未形成最终方案，不等于 `NOT_USED`。前端首次把可编辑 `UNPLANNED` 日格转换为草稿时应初始化 `used=true`，复选框默认选中，并显示“待选车辆/司机”或等价规划中状态。
2. 新增车辆槽位时，该槽位覆盖的全部可编辑服务日同样默认 `used=true`，不得全部初始化为未勾选。
3. 已有 `planFinalized=true` 的 `USED/NOT_USED`、只读日格、已关账日格和已有有效派车必须按服务端事实保留；已经明确保存为 `NOT_USED` 的日期重新打开时仍保持未选中，不得重新默认用车。
4. 默认选中只是前端草稿口径，不代表已经完成派车。未选择车辆、司机或逐日价格时仍不得提交，也不得伪造 `planFinalized=true`。
5. 用户主动取消“当天用车”时才写入草稿 `used=false/planState=NOT_USED`；某日所有槽位均不用车时继续执行 `confirmNoVehicleServiceDates=true` 二次确认。

### 当前前端偏差

前端提交 `7b99fe7d35530073176af1a0376cba7b33503319` 中，`createDailyVehiclePlan()` 在详情提供 `dailyVehiclePlan` 时把所有未最终确认日格按 `UNPLANNED + used=false` 直接用于草稿；`appendDailyVehiclePlanSlot()` 也把新槽位日格初始化为 `used=false`。因此页面出现整段行程“尚未规划”、所有“当天用车”均未选中的状态，与本次明确口径不符。

前端应区分“服务端基线状态”和“当前编辑草稿默认值”，不能通过把 `UNPLANNED` 改成服务端 `USED` 来伪造最终事实；仅在可编辑的新草稿层默认选中。

### 前端验收清单

- [ ] 4 天行程首次进入排车步骤时，4 个可编辑日格的“当天用车”全部默认选中。
- [ ] 候选尚未选定时显示待选车辆/司机而非“明确不用车”，且下一步仍因车辆、司机或价格缺失而受阻。
- [ ] 新增车辆槽位后，该槽位全部可编辑服务日也默认选中。
- [ ] 车务取消其中一天后，仅该日变为明确不用车，其余日期保持选中和已编辑草稿。
- [ ] 已保存的 `NOT_USED`、已有有效派车及只读日期重新打开后保持服务端事实，不被默认逻辑覆盖。
- [ ] 补充 `createDailyVehiclePlan()`、`appendDailyVehiclePlanSlot()` 和真实挂载 `FleetAssignModal` 的回归测试，覆盖首次进入、增加槽位、重新打开以及主动取消用车。
- [ ] 提交请求仍满足：每个 `used=true` 日格具备车辆、司机和价格；全天不用车时带明确二次确认。

## 2026-07-28 页面阻断：ARRIVAL 接机参与无法选中

真实页面复验中，服务日 **2026-08-04** 已标记“要求接机”，该日格已勾选“当天用车”、已选车辆和司机、无只读或锁定提示，但点击“参与 ARRIVAL 接机 / 接站”后复选框无法保持选中。该操作发生在派车草稿提交前，属于前端日格交互与状态同步阻断，不是后端保存接口拒绝。

当前 `origin/v2.1@e744c909` 中：

- `DailyVehiclePlanMatrix.vue` 的复选框仅发出 `update:pickup(key, checked)`；
- `AssignModal.vue` 的 `handleDailyPlanPickupUpdate()` 只将结果写入本地 `dailyPlan`；
- 现有 `daily-vehicle-plan-matrix.spec.js` 只断言子组件已发出事件，没有挂载 `FleetAssignModal` 验证父层接收后、候选状态 watcher 运行后以及切换日格后的值是否仍为 `true`。

因此，子组件事件测试通过不能作为页面可用证据。前端需要从浏览器事件开始逐段核对 `NCheckbox → update:pickup → handleDailyPlanPickupUpdate → dailyPlan → 提交 payload`，找出勾选值被丢弃或覆盖的位置；不得通过跳过逐日方案校验或伪造后端字段规避。

### 前端验收清单

- [ ] 对 `pickupRequired=true`、`used=true` 且可编辑的日格，点击后复选框立即选中，并在多轮 `nextTick`、候选状态刷新以及价格编辑后保持选中。
- [ ] 切换到其他服务日再返回，接机参与状态仍保留；再次点击可以明确取消。
- [ ] 更新只作用于当前 `serviceDate + fleetItemIndex`，不得串改同日其他车辆或其他服务日。
- [ ] 将该日改为不用车时自动清除 `pickupParticipant`；重新用车后由车务再次明确选择，不沿用陈旧值。
- [ ] 提交前的 `dailyPlan` 以及最终请求体均包含该日格 `pickupParticipant=true`；后端返回成功后详情回显一致。
- [ ] 要求接机的服务日至少一辆实际用车标记参与接机；未选择时继续显示既有校验提示，选择后提示收敛。
- [ ] 新增 `FleetAssignModal` 父子联动回归测试，覆盖事件接收、watcher 稳定、日格切换和 payload；不能只断言 `DailyVehiclePlanMatrix` 发出了事件。
- [ ] 页面 Console 无 `Maximum recursive updates exceeded` 或 `unhandledrejection`，复选框操作不得重新触发无界候选请求。

在该页面交互修复并完成真实浏览器复验前，`frontend_status` 保持 `claimed`，不得流转为 `implemented/released/verified`。

## 变更接口

| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|---|---|---|---|---|
| 1 | 原子提交最终派车方案 | POST | `/admin/fleet/assignments/batch` | 请求体扩展与校验调整 | 新增完整 `dailyPlan`，保留旧 `items` 兼容 |
| 2 | 车务看板订单详情 | GET | `/admin/fleet/board/orders/<orderId>` | 响应字段新增 | 返回逐日逐车计划、每车小计和订单车辆总计 |

## 二、原子提交最终派车方案

### `POST /admin/fleet/assignments/batch`

**请求 VO**：`BatchCreateAssignmentReqVO`

### 新增顶层字段

| 字段 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|
| `dailyPlan` | `DailyPlanItem[]` | 新页面必填 | 与旧 `items` 二选一；最多 4000 项 | 完整“服务日期 × 稳定车辆槽位”矩阵 |
| `confirmNoVehicleServiceDates` | `Boolean` | 条件必填 | 某日全部槽位 `used=false` 时必须为 `true` | 全天无需用车二次确认 |

`orderId`、`requirementId` 和所有 Long ID 继续按 JSON 字符串传输。

### `DailyPlanItem`

| 字段 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|
| `fleetItemIndex` | `Integer` | 是 | `>= 0` | 稳定车辆槽位序号 |
| `serviceDate` | `String(date)` | 是 | `yyyy-MM-dd`，必须属于当前需求服务日 | 服务日期 |
| `used` | `Boolean` | 是 | - | 当天是否实际用车 |
| `vehicleId` | `String(Long)` | 条件必填 | `used=true` 必填；不用车必须为空 | 当天车辆 |
| `driverId` | `String(Long)` | 条件必填 | `used=true` 必填；不用车必须为空 | 当天司机 |
| `pickupParticipant` | `Boolean` | 否 | 仅表示 ARRIVAL 接机/接站；不用车不得为 `true` | 当天是否参与接机 |
| `assignmentPrice` | `String(decimal)` | 条件必填 | `used=true` 必填；非负、整数最多 10 位、小数最多 2 位 | 本车当天实际价格，可为 `0.00` |
| `priceAdjustmentReason` | `String` | 条件必填 | 最多 256 字；实际价偏离价格日历时必填 | 改价原因 |
| `confirmCrossResident` | `Boolean` | 否 | 跨常驻车时按既有规则确认 | 跨常驻车确认 |

### 正确请求示例

```json
{
  "orderId": "789",
  "orderNo": "26-4165",
  "requirementId": "790",
  "startDate": "2026-08-04",
  "endDate": "2026-08-05",
  "headcount": 3,
  "holdMode": 0,
  "fromEntry": "from-board",
  "requestId": "daily-plan-26-4165-v1",
  "confirmNoVehicleServiceDates": false,
  "dailyPlan": [
    {
      "fleetItemIndex": 0,
      "serviceDate": "2026-08-04",
      "used": true,
      "vehicleId": "701",
      "driverId": "801",
      "pickupParticipant": true,
      "assignmentPrice": "800.00",
      "priceAdjustmentReason": "首日短途优惠"
    },
    {
      "fleetItemIndex": 1,
      "serviceDate": "2026-08-04",
      "used": true,
      "vehicleId": "702",
      "driverId": "802",
      "pickupParticipant": false,
      "assignmentPrice": "900.00",
      "priceAdjustmentReason": null
    },
    {
      "fleetItemIndex": 0,
      "serviceDate": "2026-08-05",
      "used": false,
      "vehicleId": null,
      "driverId": null,
      "pickupParticipant": false,
      "assignmentPrice": null,
      "priceAdjustmentReason": null
    },
    {
      "fleetItemIndex": 1,
      "serviceDate": "2026-08-05",
      "used": true,
      "vehicleId": "702",
      "driverId": "802",
      "pickupParticipant": false,
      "assignmentPrice": "900.00",
      "priceAdjustmentReason": null
    }
  ]
}
```

### 提交规则

- 每个保留槽位必须覆盖当前需求的全部服务日；同一 `fleetItemIndex + serviceDate` 不能重复。
- 同一服务日不能重复使用同一车辆或同一司机。
- `used=false` 不占用车辆/司机、不投保、不计费；车辆、司机、接机和价格必须为空或 false。
- 某日所有槽位均不用车时允许提交，但必须设置 `confirmNoVehicleServiceDates=true`。
- 大交通 ARRIVAL 要求平台接机时，当日至少一辆 `used=true` 的车辆必须 `pickupParticipant=true`。
- 大交通未要求接机时，仍允许人工标记一辆或多辆使用中的车辆参与接机。
- 连续日期使用相同车辆和司机时后端自动合并派车组；逐日价格仍分别冻结。
- 过去日期、已完结日期或已关账对账期不能修改。
- 新 `dailyPlan` 不得提交 `chargeableServiceDates`、`vehicleFeeWaiverReason`、`confirmAllServiceDatesFree`。

### 参数错误响应

本项目参数校验失败沿用 HTTP 200 + 业务 `code=400`：

```json
{
  "code": 400,
  "message": "dailyPlan 与旧 items 必须二选一，dailyPlan 不得提交旧收费日期字段",
  "data": null,
  "success": false
}
```

## 三、车务看板订单详情

### `GET /admin/fleet/board/orders/<orderId>`

**响应 VO**：`Result<BoardOrderDetailVO>`

### 新增响应字段

| 字段 | 类型 | 空值规则 | 说明 |
|---|---|---|---|
| `dailyVehiclePlan` | `DailyVehiclePlanVO[]` | 无记录返回 `[]` | 按服务日期、槽位序号稳定排序 |
| `vehicleFeeSummaries` | `VehicleFeeSummaryVO[]` | 无实际用车返回 `[]` | 按实际车辆汇总小计 |
| `vehicleFeeTotal` | `String(decimal)` | 无费用返回 `"0.00"` | 当前需求全部实际用车日总计 |

### `DailyVehiclePlanVO`

| 字段 | 类型 | 说明 |
|---|---|---|
| `serviceDate` | `String(date)` | 服务日期 |
| `fleetItemIndex` | `Integer` | 稳定槽位序号 |
| `assignmentSlotId` | `String(Long)` | 稳定槽位 ID |
| `assignmentId` | `String(Long)` | 每日切片 ID；显式不用车也返回占位 ID |
| `assignmentGroupId` | `String(Long)` | 当前连续派车组 ID |
| `planFinalized` | `Boolean` | 该日格是否已经业务最终确认 |
| `planState` | `String` | `UNPLANNED`（建议占位）/ `NOT_USED`（明确不用车）/ `USED`（实际用车） |
| `used` | `Boolean` | 当天是否实际用车；必须结合 `planFinalized/planState` 区分未规划与明确不用车 |
| `pickupParticipant` | `Boolean` | 当天车辆是否参与 ARRIVAL 接机 |
| `pickupRequired` | `Boolean` | 大交通当天是否要求接机 |
| `vehicleId` / `driverId` | `String(Long)` | 不用车时为 `null` |
| `vehiclePlate` / `vehicleModel` / `driverName` | `String` | 不用车时为 `null` |
| `driverPhone` | `String` | 脱敏手机号；不用车时为 `null` |
| `calendarPrice` | `String(decimal)` | 价格日历参考价；缺价或不用车时为 `null` |
| `assignmentPrice` | `String(decimal)` | 本车当天实际价；不用车时为 `null` |
| `priceSource` | `String` | `CALENDAR` / `OVERRIDE` / `MISSING` / `NOT_USED` |
| `priceAdjustmentReason` | `String` | 改价原因，无则 `null` |
| `assignmentStatus` | `String` | 基础派单状态；显式不用车为 `unassigned` |
| `readOnly` | `Boolean` | 过去、已完结或已关账日期为 `true` |
| `readOnlyReason` | `String` | `服务日期已过去` / `派单已完结` / `对账期已关账`，可编辑时为 `null` |

### `VehicleFeeSummaryVO`

| 字段 | 类型 | 说明 |
|---|---|---|
| `vehicleId` | `String(Long)` | 实际车辆 ID |
| `vehiclePlate` | `String` | 车牌 |
| `vehicleModel` | `String` | 车型 |
| `amount` | `String(decimal)` | 该车辆全部实际用车日小计 |

金额守恒：`vehicleFeeTotal = sum(vehicleFeeSummaries[].amount) = sum(dailyVehiclePlan[used=true].assignmentPrice)`。

## 四、前端改造清单

1. 删除派车弹窗中的“收取车费日期”和免费日期提交逻辑。
2. 按服务日渲染稳定车辆槽位矩阵；每格维护 `used`、车辆、司机、ARRIVAL 接机参与和当天实际价格。
3. 某日全不用车时显示二次确认，并提交 `confirmNoVehicleServiceDates=true`。
4. 默认价格使用详情/报价返回的日历价；修改价格时强制填写原因，允许 `0.00`。
5. 详情展示每辆车小计和订单车辆总计；金额以字符串解析，不能用浮点累计。
6. `readOnly=true` 的日格禁止编辑，并展示 `readOnlyReason`。
7. 新页面只提交 `dailyPlan`，不得同时提交旧 `items` 或收费日期字段。

## 五、数据库和历史数据

- `fleet_assignment.daily_vehicle_used`：`1` 表示当天实际用车，`0` 表示明确不用车；滚动发布期间允许 `NULL`，读取侧按车辆/司机事实回退，避免旧节点新写入被误判。
- `fleet_assignment.pickup_participant`：`1` 表示当天该车参与 ARRIVAL 接机/接站；滚动发布期间允许 `NULL` 并按 `false` 兼容。
- 历史已派日迁移为实际用车；原免费日实际价格迁为 `0.00`；未派占位迁为不用车；历史接机参与默认 `false`。
- 全程明确不用车可由 Fleet 以 `vehicleCount=0` 完成需求；Order 端车辆和司机快照均为空。

## 六、不影响范围

- 不修改 `hl-ui` 仓库，由本 changelog 交接前端。
- 不改变单派接口及旧 `items` 滚动兼容输入。
- 不把 DEPARTURE 送机/送站映射到 `pickupParticipant`。
- 不改变订单或产品价格日历接口。

## 验证证据

- 后端 PR #5296 已 squash 合并至 `dev-v3`，`hl-order-service-v3` 与 `hl-fleet-service` 已滚动部署测试环境，双实例健康检查通过。
- Fleet 最新 `dev-v3` reactor verify：2452 项测试，0 failures，0 errors，1 skipped；Spotless、Jar、JaCoCo 均成功。
- Order 全量：6759 项测试，0 failures，0 errors，29 skipped；零车辆回调生产者/消费者及迁移定向回归通过。
- 真实测试网关 `GET /admin/fleet/board/orders/<orderId>`：HTTP 200、业务 code 200；返回 3 条 `dailyVehiclePlan`，包含 `planFinalized`、`planState`、`used`、接机、价格和只读字段；逐车汇总数组及字符串总计存在。
- 真实测试网关 `POST /admin/fleet/assignments/batch` 安全负例：`dailyPlan` 携带旧收费日期字段时 HTTP 200、业务 code 400，确认新旧模式互斥；该探针不产生业务写入。
- OpenAPI diff 与 Spring Cloud Contract 未配置；已通过源码字段对比、Controller/Service 及 Fleet → Order 双端普通测试提供人工回退证据，未冒充工具通过。

当前状态：

- `backend_status: deployed`
- `gateway_status: verified`
- `frontend_status: claimed`

关联 Issue：[wx/HL#5292](https://git.1814.love:8443/wx/HL/issues/5292)
