---
schema: "hl-changelog/v2"
ticket: "5712"
title: "车辆核单 FLEET 行核算金额放开可编辑（amount 不再撞 584109）"
consumer: "admin"
change_type: "修改接口"
author: "yaosutu(GIT)"
backend_status: "deployed"
gateway_status: "verified"
frontend_status: "implemented"
frontend_owner: "mmg"
frontend_ref: "a9ddbd48"
target_release: ""
verified_at: ""
status_note: "PR #5715 已合并 dev-v3；PUT /v3/admin/order/{orderId}/settlement/step3/vehicles 对 FLEET 来源行的 amount 字段从只读放开为可编辑，结构字段仍受 584109 保护。前端若把 amount 渲染为只读输入框，应放开为可编辑。"
updated_at: "2026-08-08"
base: "dev-v3"
---

# 【修改接口·管理后台】车辆核单 FLEET 行核算金额放开可编辑（#5712）

> **PR**: [#5715](https://git.1814.love:8443/wx/HL/pulls/5715) | **服务**: hl-order-service-v3 | **更新时间**: 2026-08-08

## 1. 接口背景

核单 Step3 车辆 Tab 的全量保存接口此前对 **FLEET（车务）来源行** 的 `amount`（核算金额）做只读保护：前端把 GET 回显的金额改大/改小后回传，会被错误码 `584109`「车务来源字段不可直接修改或删除」拦截，接口虽然返回 200 但 message 提示、金额实际不变。

业务诉求：核单人员需要能对车务来源的核算金额做手工修正（实际结算价与车务快照价不一致的场景，与住宿/门票核单的人工调价口径一致）。本次放开 FLEET 行 `amount` 可编辑，**改完即生效、不留审计痕**。

## 2. 变更清单

| # | 接口 | 方法 | 路径 | 变更类型 | 前端动作 |
|---|---|---|---|---|---|
| 1 | 全量保存车辆核单草稿 | PUT | `/v3/admin/order/{orderId}/settlement/step3/vehicles` | 行为放开（字段零变化） | FLEET 行 `amount` 输入框由只读改可编辑 |
| 2 | 查询车辆核单草稿 | GET | `/v3/admin/order/{orderId}/settlement/step3/vehicles` | 出参语义变化 | `items[].amount` 现返回覆盖层修正后金额 |

## 3. 接口详情

- **使用场景**：核单人员在订单核单 Step3 车辆 Tab 维护车辆核单明细（全量覆盖式保存）。
- **认证**：需要管理后台登录态（Bearer Token）。
- **幂等性**：是，按订单维度全量覆盖式保存，重复提交相同载荷结果一致。
- **限流**：未声明接口专属限流。

## 4. 接口入参

### 4.1 路径参数

| 字段 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| `orderId` | Path | Long | 是 | 订单 ID |

### 4.2 请求体字段

| 字段 | 类型 | 必填 | 校验规则 | 本次是否放开 |
|---|---|---|---|---|
| `items` | Array | 是 | 全量明细 | — |
| `items[].id` | Long | 条件必填 | 既有行必传；新增手工行为 null | 否（仍受 584109 保护） |
| `items[].sourceType` | String | 是 | `FLEET` / `MANUAL` | 否 |
| `items[].serviceDate` | String(date) | 是 | `yyyy-MM-dd` | 否（仍受 584109 保护） |
| `items[].vehicleId` | Long | 条件必填 | FLEET 行必传 | 否（仍受 584109 保护） |
| `items[].vehiclePlate` | String | 否 | 最长 64 | 否（仍受 584109 保护） |
| `items[].vehicleModelId` | Long | 否 | — | 否（仍受 584109 保护） |
| `items[].vehicleModelName` | String | 否 | 最长 128 | 否（仍受 584109 保护） |
| `items[].driverId` | Long | 否 | — | 否（仍受 584109 保护） |
| `items[].driverName` | String | 否 | 最长 64 | 否（仍受 584109 保护） |
| `items[].amount` | Decimal | 是 | 非负、最多 2 位小数 | **本次放开为可编辑** |
| `items[].paymentMethod` | String | 是 | `CASH_PAID` / `SIGNED` / `COMPANY_PAID` | 否（仍受 584109 保护） |
| `items[].settlementConfirmStatus` | String | 是 | `UNCONFIRMED` / `CONFIRMED` | 否（本就可改） |
| `items[].remark` | String | 否 | 最长 500 | 否（本就可改） |
| `items[].voucherUrls` | String[] | 否 | 最多 9 个 URL | 否（本就可改） |

> **FLEET 行编辑约定**：`vehicleFeeLineId`（即 GET 回显的 `items[].id`）、`serviceDate`、`vehicleId`、`vehiclePlate`、`vehicleModelId`、`vehicleModelName`、`driverId`、`driverName`、`paymentMethod` 这 9 个**车务来源结构字段不允许改**，前端应把这些字段**原样回传**（来自 GET step3/vehicles 的回显值），只允许改 `amount`（以及 `remark` / `voucherUrls` / `settlementConfirmStatus`）。改了结构字段仍撞 `584109`。

## 5. 出参字段

成功响应为统一 `Result` 结构，`code=200`。`data` 出参字段（`orderId` / `totalAmount` / `allConfirmed` / `settlementReady` / `blockReasonCode` / `items` / `frozen`）结构无变化。

**唯一语义变化**：GET `items[].amount` 现在返回**覆盖层修正后的金额**（改完再查就是新值），不再被 fleet 快照价覆盖。

## 6. 枚举 / 数据字典

本次不涉及枚举新增或改值。`paymentMethod`（CASH_PAID/SIGNED/COMPANY_PAID）、`settlementConfirmStatus`（UNCONFIRMED/CONFIRMED）、`sourceType`（FLEET/MANUAL）取值不变。

## 7. 错误码

| code | 含义 | 触发场景 |
|---:|---|---|
| `584109` | 车务来源字段不可直接修改或删除 | 改了 FLEET 行的结构字段（serviceDate/vehicleId/vehiclePlate/vehicleModelId/vehicleModelName/driverId/driverName/paymentMethod/vehicleFeeLineId）或试图删除 FLEET 行 |
| `400` | 请求数据格式错误 | `amount` 为负数 / 小数位超过 2 位 / 数值超出范围 / 其他 Bean 校验失败 |

## 8. 示例

### 8.1 典型成功 —— FLEET 行 amount 由 800 改为 950

**请求**：

```http
PUT /v3/admin/order/2084000000000002978/settlement/step3/vehicles
Authorization: Bearer <token>
Content-Type: application/json
```

```json
{
  "items": [
    {
      "id": "9001",
      "sourceType": "FLEET",
      "serviceDate": "2026-08-08",
      "vehicleId": "301",
      "vehiclePlate": "蒙A88888",
      "vehicleModelId": "21",
      "vehicleModelName": "丰田汉兰达",
      "driverId": "45",
      "driverName": "张师傅",
      "amount": "950.00",
      "paymentMethod": "COMPANY_PAID",
      "settlementConfirmStatus": "UNCONFIRMED",
      "remark": "实际结算价高于快照",
      "voucherUrls": []
    }
  ]
}
```

**响应**：

```json
{"code": 200, "message": "success", "data": {"saved": true}, "success": true}
```

随后 GET 同一订单 step3/vehicles，`items[].amount` 返回 `"950.00"`。

### 8.2 边界情况 —— amount 改为 0.00 仍合法

**场景说明**：金额下限为 `0.00`，0 元合法（不是"未填"）。

**请求**：

```http
PUT /v3/admin/order/2084000000000002978/settlement/step3/vehicles
Authorization: Bearer <token>
Content-Type: application/json
```

```json
{
  "items": [
    {
      "id": "9001",
      "sourceType": "FLEET",
      "serviceDate": "2026-08-08",
      "vehicleId": "301",
      "vehiclePlate": "蒙A88888",
      "vehicleModelId": "21",
      "vehicleModelName": "丰田汉兰达",
      "driverId": "45",
      "driverName": "张师傅",
      "amount": "0.00",
      "paymentMethod": "COMPANY_PAID",
      "settlementConfirmStatus": "UNCONFIRMED",
      "remark": null,
      "voucherUrls": []
    }
  ]
}
```

**响应**：

```json
{"code": 200, "message": "success", "data": {"saved": true}, "success": true}
```

### 8.3 业务失败 —— 改了 FLEET 行结构字段 vehiclePlate 撞 584109

**场景说明**：仅放开 `amount`；结构字段（如车牌）仍受保护。

**请求**：

```http
PUT /v3/admin/order/2084000000000002978/settlement/step3/vehicles
Authorization: Bearer <token>
Content-Type: application/json
```

```json
{
  "items": [
    {
      "id": "9001",
      "sourceType": "FLEET",
      "serviceDate": "2026-08-08",
      "vehicleId": "301",
      "vehiclePlate": "蒙A99999",
      "vehicleModelId": "21",
      "vehicleModelName": "丰田汉兰达",
      "driverId": "45",
      "driverName": "张师傅",
      "amount": "950.00",
      "paymentMethod": "COMPANY_PAID",
      "settlementConfirmStatus": "UNCONFIRMED",
      "remark": null,
      "voucherUrls": []
    }
  ]
}
```

**响应**：

```json
{"code": 584109, "message": "车务来源字段不可直接修改或删除", "data": null, "success": false}
```

## 9. 业务边界

- **适用场景**：FLEET 行 `amount` 可任意改（非负、最多 2 位小数）；改完即生效，**不留审计痕**，与住宿/门票核单人工调价口径一致。
- **不适用场景**：FLEET 行的结构字段（车牌 / 司机 / 服务日期 / 车型 / 付款方式 / 行 id）仍不允许改；如需换车换司机请走车务派单链路，不要在核单页改。
- **特殊边界**：`amount` 改为 `0.00` 是合法值，不是"清空"；MANUAL 行的所有字段本就可改，本次无变化。

## 10. 修改前后对比

### 10.1 字段级对比

| 字段 | 原来 | 现在 |
|---|---|---|
| FLEET 行 `amount` | 只读；改值撞 `584109`（PUT 返回 200 但 message 提示、金额不变） | **可编辑**；值合法即落库 |
| FLEET 行结构字段（车牌/司机/日期/车型/付款方式） | 只读，撞 `584109` | 仍只读，撞 `584109`（不变） |
| GET `items[].amount` | 返回 fleet 快照价 | 返回**覆盖层修正后**金额 |

### 10.2 行为级对比

| 行为 | 原来 | 现在 |
|---|---|---|
| 前端把 FLEET 行 amount 改大/改小回传 | 被 `584109` 拦截，金额不变 | 正常保存，GET 回读新值 |
| 前端改 FLEET 行 vehiclePlate | `584109` | `584109`（不变） |

## 11. 影响评估 / 回滚

### 11.1 影响评估

- **是否破坏向后兼容**：否。接口签名、字段、错误码零变化；仅放开一个字段的编辑限制。
- **前端是否必须同步上线**：否。旧前端继续把 amount 渲染为只读输入框不影响功能；但建议尽快放开为可编辑以匹配新口径。

### 11.2 回滚方案

- 回滚即恢复 amount 只读保护，旧前端（amount 只读）天然兼容；已改为可编辑的新前端在旧后端上保存会重新撞 `584109`，需前后端同批回滚。

## 12. 注意事项

- 本次仅放开 `amount`；**不要把 FLEET 行其他结构字段也放开为可编辑**。
- 前端在 FLEET 行编辑表单里，`amount` 用 number input 即可，提交前按"非负、最多 2 位小数"做一次本地校验可减少 400。
- 若前端历史上有"FLEET 行 amount 改了被静默回滚"的 workaround（如改完强刷 GET 重新渲染），本次后可保留也可简化，无破坏性。

## 13. 关联 / 联系人

### 13.1 链接

- **Issue**: [#5712](https://git.1814.love:8443/wx/HL/issues/5712)
- **PR**: [#5715](https://git.1814.love:8443/wx/HL/pulls/5715)
- **Merge commit**: [d4fce1230](https://git.1814.love:8443/wx/HL/commit/d4fce12309dd3c2ad040ee5d71e9734cb1b8ad5b)

### 13.2 联系人

- **后端负责人**: @yaosutu (yst)

## 前端实证确认（2026-08-08 mmg，hl-admin@a9ddbd48）

- 前端 FLEET 行 amount 此前确为只读：`returnDetailAdapter.js adaptVehicleRows` 对 FLEET 行 `editableFields=['confirmStatus','note']`（缺 amount），CategoryTable `rowFieldEditable` 据此把 amount 渲染为只读。
- 修复：FLEET 行 editableFields 改为 `['amount','confirmStatus','note']`，amount 输入框放开可编辑；结构字段（车牌/司机/日期/车型/付款方式/id）仍不在 editableFields，保持只读（与后端 584109 保护一致）。凭证列由 `canEditRowVoucher` 独立判定（不看 editableFields），本就开放，无需改。
- 保存链路 `buildVehicleSaveRequest` 本就把 `row.amount` 透传进 `items[].amount`、结构字段从 `source.*` 原样回传，无需改——金额改完随全量保存生效。
- 验证：settlement 全量定向 vitest 93/93 通过（含更新 `editableFields` 断言）；checkpoint 全绿。
