# 核单子表扩列
- **变更日期**: 2026-07-07
- **端类型**: 管理后台
- **变更类型**: 修改接口
- **Issue**: https://git.1814.love:8443/wx/HL/issues/4764
- **PR**: https://git.1814.love:8443/wx/HL/pulls/4767

---
## 1. 接口背景

核单 epic PR2。核单三个子表（车席、馨票、人员费用）分别扩充字段，具体：
- 车席 Step 1: `HotelItemVO` 新增 `voucherUrls`（住宿凭证图片）
- 馨票 Step 2: `TicketItemVO` 新增 `paymentMethod` / `voucherUrls`；`scenicAssignmentId` 由可空改必填（❗ 破坏兆容）
- 人员费用 Step 3: `StaffFeeItemVO` 新增 `reimburse`；`SettlementStaffFeesSaveRespVO` 新增 `isPrimaryReporter` / `reimburse`

---
## 2. 变更清单

| 序号 | 方法 | 路径 | 说明 |
|------|------|------|------|
| 1 | GET | `/v3/admin/order/{orderId}/settlement/step1` | Step 1 查询，`HotelItemVO` +`voucherUrls` |
| 2 | PUT | `/v3/admin/order/{orderId}/settlement/step1` | Step 1 保存，入参`HotelItemVO` +`voucherUrls` |
| 3 | GET | `/v3/admin/order/{orderId}/settlement/step2` | Step 2 查询，`TicketItemVO` +`paymentMethod`/`voucherUrls`; `scenicAssignmentId` 由可空改必填 |
| 4 | PUT | `/v3/admin/order/{orderId}/settlement/step2` | Step 2 保存，同上 |
| 5 | GET | `/v3/admin/order/{orderId}/settlement/step3` | Step 3 查询，响应 +`isPrimaryReporter`/`reimburse` |
| 6 | PUT | `/v3/admin/order/{orderId}/settlement/step3` | Step 3 保存，入参 +`reimburse` |
---
## 3. 接口详情

| 项 | 说明 |
|-----|------|
| 认证 | JWT Bearer(管理端) |
| 幂等性 | PUT 非幂等（每次全量覆写子表） |
| 核单状态 | 仅允许在 settlement_status=IN_PROGRESS 时调用 |

---
## 4. 接口入参

### 4.1 路径参数（六个接口通用）
| orderId | Long | 是 | 订单 ID |

### 4.2 Step 1 PUT 请求体（`List<HotelItemVO>`）

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| id | Long | 否 | 空表新建行 |
| hotelAssignmentId | Long | 否 | 酒店配置 ID |
| stayDate | LocalDate | 是 | 入住日期 |
| roomType | String | 否 | 房间类型 |
| roomCount | Integer | 是 | 房间数量 |
| plannedCost | BigDecimal | 否 | 计划成本 |
| actualCost | BigDecimal | 否 | 实际成本 |
| paymentMethod | String | 否 | SIGNED/COMPANY_PAID/CASH_PAID |
| settleType | String | 否 | 派生行类型：cash/sign/company |
| remark | String | 否 | 备注 |
| **voucherUrls** | List<String> | 否 | **✨ 新增）: 住宿凭证图片 URL |

### 4.3 Step 2 PUT 请求体（`List<TicketItemVO>`）

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| sourceType | String | 是 | SCENIC_ASSIGNMENT/ACTIVITY_ASSIGNMENT |
| **scenicAssignmentId** | Long | **是** | **❗ 破坏兆容：旧可空，新必填** |
| dayDate | LocalDate | 是 | 游玩日期 |
| ticketCount | Integer | 是 | 馨票人数 |
| ticketUnitPrice | BigDecimal | 否 | 单价 |
| actualCost | BigDecimal | 否 | 实际成本 |
| **paymentMethod** | String | 否 | **✨ 新增**: SIGNED/COMPANY_PAID/CASH_PAID |
| **voucherUrls** | List<String> | 否 | **✨ 新增**: 馨票凭证图片 URL |
| remark | String | 否 | 备注 |

### 4.4 Step 3 PUT 请求体（`List<StaffFeeItemVO>`）

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| id | Long | 否 | 实体主键 |
| staffRole | String | 是 | LEADER/DRIVER/GUIDE/PHOTOGRAPHER/OTHER |
| staffId | Long | 否 | 人员 ID |
| detail | Object | 否 | 按角色不同, 见 §6 |
| **reimburse** | BigDecimal | 否 | **✨ 新增**: 小算补助金额，⊰ 0 |
| remark | String | 否 | 备注 |
---
## 5. 出参字段

### Step 1 GET 响应（`List<HotelItemVO>`）
> 新增 **`voucherUrls`** (List<String>)

### Step 2 GET 响应（`List<TicketItemVO>`）
> 新增 **`paymentMethod`** (String) / **`voucherUrls`** (List<String>)

### Step 3 GET 响应（`SettlementStaffFeesSaveRespVO`）

`StaffFeeRespItemVO` 内嵌子字段:
| 字段 | 类型 | 说明 |
|------|------|------|
| **isPrimaryReporter** | Boolean | **✨ 新增**: 是否主报账人，由 AssignmentService 实时派生 |
| **reimburse** | BigDecimal | **✨ 新增**: 小算补助金额，入参回显 |

---
## 6. 枚举/数据字典

**paymentMethod / settleType**
| 値 | 含义 |
|------|------|
| SIGNED | 签单结算（酒店收取相应金额） |
| COMPANY_PAID | 公司已付（公库打款） |
| CASH_PAID | 现付（司机墙付） |

**staffRole**
| 値 | 含义 |
|------|------|
| LEADER | 地接 |
| DRIVER | 司机 |
| GUIDE | 导游 |
| PHOTOGRAPHER | 摄影师 |
| OTHER | 其他 |

**detail 结构（按角色）**
- DRIVER: `{is_used, daily_fee, extra_cost, days[{date,is_used}]}`
- GUIDE/PHOTOGRAPHER: `{persons[{staffId,days[{date,is_used}]}], per_day}`
- LEADER: `{days, per_day}`
- OTHER: `[{name, amount}]`

---
## 7. 错误码

| 错误码 | 含义 | 触发场景 |
|--------|------|---------|
| 584028 | ❗ 破坏兆容 -- 馨票行必须关联资源配置 | Step 2 PUT 中 scenicAssignmentId 为空（曾可空，现必填） |
| 584001-584009 | Step 1 酒店校验错 | - |
| 584010-584018 | Step 2 馨票校验错 | - |
| 584020-584027 | Step 3 人员费用校验错 | - |

---
## 8. 示例

### 8.1 典型成功 -- Step 2 PUT （`scenicAssignmentId`必填）

```http
PUT /v3/admin/order/1234567890123456/settlement/step2
Content-Type: application/json

[{
  "sourceType": "SCENIC_ASSIGNMENT",
  "scenicAssignmentId": 98001,
  "dayDate": "2026-07-10",
  "ticketCount": 4,
  "actualCost": 1200.00,
  "paymentMethod": "CASH_PAID",
  "voucherUrls": ["https://oss.example.com/ticket.jpg"]
}]
```

响应: 200 OK

### 8.2 边界 -- Step 3 GET （`isPrimaryReporter` = true）
```json
{
  "code": 200,
  "data": {
    "items": [{
      "staffRole": "DRIVER",
      "isPrimaryReporter": true,
      "reimburse": 500.00
    }]
  }
}
```

### 8.3 业务失败 -- scenicAssignmentId 为空触发 584028
```json
[{
  "sourceType": "SCENIC_ASSIGNMENT",
  "scenicAssignmentId": null,
  "dayDate": "2026-07-10"
}]
```
```json
{
  "code": 584028,
  "msg": "馨票行必须关联资源配置（现必填，曾可空）"
}
```
---
## 9. 业务边界

适用: 核单流程中内 (settlement_status=IN_PROGRESS)
不适用: 已提交或已结算
特殊边界:
- Step 1 `voucherUrls`: 不传默空列表，传空删除已传的凭证
- Step 2 scenicAssignmentId: 必填，不再允许自由录入 (破坏兆容！)
- Step 3 isPrimaryReporter: 实时派生，前端只读

---
## 10. 修改前后对比

### Step 2 `TicketItemVO` 字段变化
| 字段 | 修改前 | 修改后 |
|------|------|------|
| scenicAssignmentId | 可空 | 必填 （❗破坏兆容） |
| paymentMethod | 无 | 新增 String |
| voucherUrls | 无 | 新增 List<String> |

### Step 3 `StaffFeeRespItemVO` 字段变化
| 字段 | 修改前 | 修改后 |
|------|------|------|
| isPrimaryReporter | 无 | 新增 Boolean |
| reimburse | 无 | 新增 BigDecimal |

---
## 11. 影响评估/回滚

破坏兆容: scenicAssignmentId 旧可空, 新必填。前端补充 scenicAssignmentId字段后提交。
回滚: 无需回滚，此为小版本内造数据制约。

---
## 12. 注意事项

- scenicAssignmentId 必填是 **破坏兆容变更**
- isPrimaryReporter 实时计算，前端只读

---
## 13. 关联/联系人

- Issue: https://git.1814.love:8443/wx/HL/issues/4764
- PR: https://git.1814.love:8443/wx/HL/pulls/4767
- Commit: https://git.1814.love:8443/wx/HL/commit/3fdb51f5aac7ee6bee58f8e9ab125d82859eaffd
- 后端负责人: 腰苏图