# 订单预支管理（创建/审批/驳回/撤回/列表）

- 端类型：管理后台
- 变更类型：新增接口
- 关联：Issue #4429 / #4440 / #4452 ｜ PR #4429 / #4442 / #4453
- 服务：hl-order-service-v3
- 日期：2026-06-26

> 本文为「订单预支借款」功能的最终态契约（合并 #4429 落地 + #4440 借款类型字典 + #4452 去草稿态），前端按本文对接即可，无需翻历史版本。

---

## ① 接口背景

常规订单详情提供「预支借款」能力：定制师为订单的**某位人员**（借款对象，从本单人员下拉选定，默认主报账人，#4465）申请一笔预支款，财务审批通过/驳回。预支金额受订单待收尾款上限约束，借款类型走数据字典。

**流程（去草稿后，#4452）**：创建即进「待审批」→ 财务通过（APPROVED，触发后置事件）/ 驳回（REJECTED）。财务审批前申请人可撤回。

---

## ② 变更清单

| # | 方法 | 路径 | 说明 |
|---|---|---|---|
| 1 | GET | `/v3/admin/order/{orderId}/advance/payee-candidates` | 借款对象候选下拉（全部人员，主报账人默认，#4465） |
| 2 | POST | `/v3/admin/order/{orderId}/advance` | 创建预支（创建即待审批 SUBMITTED；#4465 入参加 payeeStaffId） |
| 3 | PUT | `/v3/admin/order/advance/{advanceId}/approve` | 财务审批通过 |
| 4 | PUT | `/v3/admin/order/advance/{advanceId}/reject` | 财务审批驳回 |
| 5 | DELETE | `/v3/admin/order/advance/{advanceId}` | 撤回待审批预支（仅 SUBMITTED 可撤） |
| 6 | GET | `/v3/admin/order/{orderId}/advances` | 分页查询本单预支列表 |

**⚠️ 已删除端点（#4452，旧路径调用返回 code=404「接口不存在」）**：

| 方法 | 旧路径 | 处理 |
|---|---|---|
| PUT | `/v3/admin/order/advance/{advanceId}/submit` | 已删除。去草稿后创建即待审批，无需单独提交动作 |

---

## ③ 接口详情

统一响应包：`{ code, message, data, success }`，`code=200` 成功；业务异常 `code` 为对应错误码（见 ⑦），HTTP 状态恒 200。

### 0. 借款对象候选下拉 — GET `/v3/admin/order/{orderId}/advance/payee-candidates`（#4465）

返回本单全部人员（order 人员配置），供创建预支时选择借款对象。主报账人（reporter_rank=PRIMARY）排第一并标 `isDefault=true`，前端默认选中。候选项 `id` 即创建预支的 `payeeStaffId` 入参。返回 `PageResult`（单页全量，`records / total`）。

### 1. 创建预支 — POST `/v3/admin/order/{orderId}/advance`

入口门槛（不满足抛对应错误码）：
- 订单状态 ∈ {待出发 PENDING_DEPARTURE / 出行中 TRAVELLING}，否则 585001（#4462 收紧：已完成 COMPLETED、已取消 CANCELLED、定制中、待付款均不可创建预支）
- 订单结算状态 ≠ 已结算完成，否则 585001
- 借款对象 `payeeStaffId` 须为本单人员（取候选下拉的 id），否则 585007（#4465）
- 借款类型须为字典 `advance_type` 内的值，否则 585006
- 预支金额 0 < amount ≤ 可用上限（可用上限 = 待收尾款 − 本单在途预支之和），否则 585003 / 585004

借款对象由前端从候选下拉选定（#4465，不再强制主报账人，去掉原 585002 门槛），后端校验属本单后快照该人员姓名/角色写入。创建成功状态直接为 `SUBMITTED`。

### 2. 审批通过 — PUT `/v3/admin/order/advance/{advanceId}/approve`

仅 `SUBMITTED` 可通过，否则 585005。写 `approvedBy`/`approvedAt`，状态置 `APPROVED`，触发后置事件（出账逻辑后续补充）。

### 3. 审批驳回 — PUT `/v3/admin/order/advance/{advanceId}/reject`

仅 `SUBMITTED` 可驳回，否则 585005。须填驳回原因，状态置 `REJECTED`。

### 4. 撤回待审批 — DELETE `/v3/admin/order/advance/{advanceId}`

仅 `SUBMITTED` 可撤回（软删），财务已审批（APPROVED/REJECTED）不可撤回，否则 585005。

### 5. 分页查询 — GET `/v3/admin/order/{orderId}/advances`

按 create_time 倒序返回本单预支列表，分页参数 `page`/`pageSize`。

---

## ④ 入参

### 创建预支 请求体 CreateAdvanceReqVO

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| payeeStaffId | Long(字符串) | 是 | 借款对象，取候选下拉项的 id（#4465），须为本单人员，否则 585007 |
| advanceType | String | 是 | 借款类型，取字典 `advance_type` 的 dictValue（见 ⑥） |
| amount | BigDecimal | 是 | 预支金额，须 > 0（≥ 0.01） |
| purpose | String | 否 | 用途说明 |
| voucherUrl | String | 否 | 凭证文件 URL |

### 候选下拉 / 分页查询 path

候选下拉 `GET /v3/admin/order/{orderId}/advance/payee-candidates` 仅路径参数 `orderId`，无 query。

### 驳回 请求体 RejectAdvanceReqVO

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| reason | String | 是 | 驳回原因 |

### 分页查询 query

| 字段 | 类型 | 必填 | 默认 | 说明 |
|---|---|---|---|---|
| page | int | 否 | 1 | 页码 |
| pageSize | int | 否 | 20 | 每页条数 |

approve / reject / revoke 仅路径参数 `advanceId`（approve/revoke 无请求体）。

---

## ⑤ 出参

创建/审批/驳回 返回 `data` 为单个 OrderAdvanceRespVO；分页查询 `data` 为 `PageResult<OrderAdvanceRespVO>`；候选下拉 `data` 为 `PageResult<AdvancePayeeCandidateVO>`。

### AdvancePayeeCandidateVO（借款对象候选，#4465）

| 字段 | 类型 | 说明 |
|---|---|---|
| id | String | 候选 ID（=创建预支 payeeStaffId，order 人员分配 ID） |
| staffName | String | 员工姓名 |
| staffRole | String | 员工角色代码（DRIVER/LEADER/PHOTOGRAPHER/OTHER 等） |
| staffRoleText | String | 员工角色文案（司机/导游/摄影师/其他） |
| reporterRank | String | 报账人等级（PRIMARY/SECONDARY/NONE） |
| isDefault | boolean | 是否默认选中（主报账人为 true，前端默认选此项） |

### OrderAdvanceRespVO

| 字段 | 类型 | 说明 |
|---|---|---|
| id | String | 预支 ID（雪花，字符串） |
| orderId | String | 订单 ID（字符串） |
| payeeStaffId | String | 报账人 staff 分配 ID（快照） |
| payeeName | String | 报账人姓名 |
| payeeRole | String | 报账人角色代码（DRIVER/LEADER/PHOTOGRAPHER/OTHER 等） |
| payeeRoleText | String | 报账人角色文案（DRIVER→司机/LEADER→导游/PHOTOGRAPHER→摄影师/OTHER→其他） |
| advanceType | String | 借款类型代码（字典 advance_type dictValue） |
| amount | BigDecimal | 预支金额 |
| purpose | String | 用途说明 |
| voucherUrl | String | 凭证文件 URL |
| status | String | 状态代码（SUBMITTED/APPROVED/REJECTED） |
| statusText | String | 状态文案（待审批/已通过/已驳回） |
| rejectReason | String | 驳回原因（仅 REJECTED 有值） |
| createdByName | String | 创建人姓名 |
| createTime | DateTime | 创建时间 |
| submittedAt | DateTime | 提交审批时间（创建即填充） |
| approvedAt | DateTime | 审批时间（通过/驳回时填充） |
| approvedBy | String | 审批人姓名 |

---

## ⑥ 枚举 / 数据字典

### 状态 status（#4452 去 DRAFT，3 态）

| code | 文案 statusText | 说明 |
|---|---|---|
| SUBMITTED | 待审批 | 创建后初始态，财务未审批前可撤回 |
| APPROVED | 已通过 | 财务审批通过（终态，触发后置事件） |
| REJECTED | 已驳回 | 财务审批驳回，含 rejectReason（终态） |

> 旧 `DRAFT`（草稿）状态已删除，出参不再出现。

### 借款类型 advanceType（数据字典 `advance_type`，#4440）

| dictValue | dictLabel |
|---|---|
| ACCOMMODATION_DEPOSIT | 住宿押金 |
| TICKET | 门票 |
| CATERING | 餐饮 |
| TRANSPORT | 交通用车 |

> 字典项可后台维护，前端建议通过字典接口 `GET /admin/dict/data/advance_type` 动态取值，不要硬编码。

---

## ⑦ 错误码

| code | 含义 |
|---|---|
| 585000 | 预支记录不存在 |
| 585001 | 订单当前状态不可创建预支 |
| 585002 | ~~订单尚未配置主报账人~~（#4465 起借款对象可选任一人员，本码已废弃不再返回） |
| 585003 | 预支金额必须大于 0 |
| 585004 | 预支金额超过可用余额上限 |
| 585005 | 预支当前状态不允许此操作（如撤回/审批一条非 SUBMITTED 的记录） |
| 585006 | 借款类型非法（须为字典 advance_type 内的值） |
| 585007 | 借款对象不属于本订单人员（payeeStaffId 非本单人员，#4465） |
| 400 | 参数校验失败（如 payeeStaffId 未填「借款对象必填」、advanceType 未填「借款类型必填」、amount 为空/≤0、reason 未填） |

---

## ⑧ 示例

### 典型：借款对象候选下拉（#4465）

请求 `GET /v3/admin/order/2070038865875230722/advance/payee-candidates`
```json
{
  "code": 200, "success": true, "message": "成功",
  "data": {
    "total": 2, "page": 1, "pageSize": 2,
    "records": [
      { "id": "9000000000000000001", "staffName": "刘领队", "staffRole": "LEADER", "staffRoleText": "导游", "reporterRank": "PRIMARY", "isDefault": true },
      { "id": "9000000000000000002", "staffName": "孙师傅", "staffRole": "DRIVER", "staffRoleText": "司机", "reporterRank": "NONE", "isDefault": false }
    ]
  }
}
```

### 典型：创建预支（#4465 带 payeeStaffId）

请求 `POST /v3/admin/order/2070038865875230722/advance`
```json
{ "payeeStaffId": "9000000000000000002", "advanceType": "TICKET", "amount": 100, "purpose": "景区门票预付" }
```
响应
```json
{
  "code": 200, "success": true, "message": "成功",
  "data": {
    "id": "2070423136842539010", "orderId": "2070038865875230722",
    "payeeStaffId": "9000000000000000002", "payeeName": "孙师傅",
    "payeeRole": "DRIVER", "payeeRoleText": "司机",
    "advanceType": "TICKET", "amount": 100, "purpose": "景区门票预付",
    "status": "SUBMITTED", "statusText": "待审批",
    "createdByName": "定制师A", "submittedAt": "2026-06-26 16:25:17",
    "approvedAt": null, "approvedBy": null, "rejectReason": null
  }
}
```

### 异常：借款对象不属于本单（#4465）

请求体 `{ "payeeStaffId": "999999", "advanceType": "TICKET", "amount": 100 }`
```json
{ "code": 585007, "success": false, "message": "借款对象不属于本订单人员", "data": null }
```

### 边界：审批通过

请求 `PUT /v3/admin/order/advance/2070423411452010498/approve`
```json
{ "code": 200, "success": true, "data": { "status": "APPROVED", "statusText": "已通过", "approvedBy": "admin", "approvedAt": "2026-06-26 16:25:40" } }
```

### 异常：撤回一条已审批的预支

请求 `DELETE /v3/admin/order/advance/2070423411452010498`
```json
{ "code": 585005, "success": false, "message": "预支当前状态不允许此操作", "data": null }
```

### 异常：调用已删除的 submit 端点

请求 `PUT /v3/admin/order/advance/{advanceId}/submit`
```json
{ "code": 404, "success": false, "message": "接口不存在: PUT /v3/admin/order/advance/{advanceId}/submit", "data": null }
```

### 异常：借款类型非法

请求体 `{ "advanceType": "FOO", "amount": 100 }`
```json
{ "code": 585006, "success": false, "message": "借款类型非法（须为字典 advance_type 内的值）", "data": null }
```

---

## ⑨ 业务边界

- 仅订单状态为「待出发 / 出行中」时可创建预支（#4462）；已完成、已取消、定制中、待付款均返 585001。
- 一个订单可有多笔预支；在途（SUBMITTED + APPROVED）金额之和不得超过订单待收尾款。
- 借款对象（payee）由前端从本单人员候选下拉选定（#4465），创建时快照该人员姓名/角色，预支记录创建后不随人员变更而变化；候选默认选中主报账人。
- 仅 SUBMITTED 可被 approve / reject / 撤回；APPROVED / REJECTED 为终态。
- 撤回为软删，列表不再返回被撤回记录。

---

## ⑩ 修改前后对比（#4452 去草稿）

| 维度 | 旧（#4429 初版） | 新（#4452） |
|---|---|---|
| 流程 | 草稿 DRAFT → 提交 SUBMITTED → 审批 | 创建即 SUBMITTED → 审批 |
| 创建后状态 | DRAFT（草稿） | SUBMITTED（待审批） |
| 提交端点 | PUT `.../submit` | 已删除（404） |
| DELETE 语义 | 删除草稿（仅 DRAFT） | 撤回待审批（仅 SUBMITTED） |
| status 取值 | DRAFT/SUBMITTED/APPROVED/REJECTED | SUBMITTED/APPROVED/REJECTED |

---

## ⑪ 影响评估 / 回滚

- 前端：去掉「保存草稿 / 提交」两步交互，改为「创建即提交审批」；删除按钮语义改为「撤回」（仅待审批可点）；状态枚举去掉草稿。
- 后端：去草稿态随 PR #4453 上线，Flyway V20260626_005 已执行（status 默认值 SUBMITTED + 历史 DRAFT 行迁移）。

---

## ⑫ 注意事项

- 所有 ID 字段（id/orderId/payeeStaffId）为字符串，避免 JS 精度丢失。
- 借款类型务必从字典接口取值，字典可后台扩展。
- 金额上限由后端依据订单待收尾款实时计算，前端提交后以后端校验为准。

---

## ⑬ 关联 / 联系人

- Issue：https://git.1814.love:8443/wx/HL/issues/4429 ｜ /4440 ｜ /4452 ｜ /4462 ｜ /4465
- PR：https://git.1814.love:8443/wx/HL/pulls/4429 ｜ /4442 ｜ /4453 ｜ /4463 ｜ /4466
- 负责人：yst（腰苏图）
