docs(changelog): 出纳付款联动司导收款账户(报账款线选/手填建档,#8809)
changelog-filename-gate / validate (push) Failing after 2s

- 修改 POST /admin/finance/cashier/reimburse/pay:入参加 payeeAccountId/newPayeeAccount,出参加收款账户留痕
- 新增 GET /admin/finance/payee-accounts/list-by-payee 选账户下拉数据源
- 队列/台账/报账详情出参加报账人档案与收款账户字段
- 新增错误码 598612-598615
这个提交包含在:
yaosutu
2026-10-09 14:07:38 +08:00
父节点 3ce7ef580a
当前提交 0b6467b2c3
@@ -0,0 +1,415 @@
---
schema: "hl-changelog/v2"
ticket: "8809"
title: "出纳付款联动司导收款账户(选已建档账户 / 手填自动建档,报账款线)"
consumer: "admin"
author: "yst"
change_type: "修改接口"
backend_status: "merged"
gateway_status: "not_required"
frontend_status: "pending"
frontend_owner: ""
frontend_ref: ""
target_release: ""
verified_at: ""
status_note: "后端 PR #8810 已合并 dev-v3(squash e50a43b72a),含 Flyway 迁移 V20261009_136(fin_reimburse 加收款账户两列 + fin_payee_account 唯一索引),需部署 order-v3 后生效;存量报账单新字段为 null"
updated_at: "2026-10-09"
base: "dev-v3"
---
# 出纳付款联动司导收款账户(报账款线)
> 面向:管理后台前端(hl-admin)
> 日期:2026-10-09 | 后端:hl-finance(编译进 order-v3 同进程,端口 8086)
> 范围:只动 **REIMBURSE 报账款付款线**;其余 7 条出纳付款线(advance / company-loan / expense / payment / nonbiz 等)入参壳共用但对新字段**静默忽略**,行为零变化。
## 1. 接口背景
报账款是出纳付款给司导/报账人的钱。此前出纳付款表单只有「公司出账账户」(payAccountId),**没有「司导收款账户」**——出纳把钱打到哪张卡/哪个收款码全靠线下沟通,系统无留痕。
本次让出纳在付款弹窗里:
1. **选账户**:下拉列出该报账人已建档的收款账户(新接口 list-by-payee 提供数据源,队列行已自动带出默认账户);
2. **手填建档**:现场登记新收款账户,付款成功同时自动落入收款账户主数据(fin_payee_account),账户名强制取服务人员档案真名,不允许手填名称。
付款成功后台账、报账详情均可回查「这单钱打给了哪个账户」。
## 2. 变更清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|------|------|------|----------|------|
| 1 | 报账款登记付款 | POST | /admin/finance/cashier/reimburse/pay | 修改接口 | 入参新增 payeeAccountId 与嵌套 newPayeeAccount(二选一);出参加 payeeAccountId / payeeAccountLabel |
| 2 | 按收款方查账户全集 | GET | /admin/finance/payee-accounts/list-by-payee | **新增接口** | 付款弹窗「选账户」下拉数据源,轻量不分页 |
| 3 | 报账款待付款队列 | GET | /admin/finance/cashier/reimburse/queue | 修改接口 | 出参加 bizType / reporterAssignmentId / reporterRefId / reporterPayeeType / defaultPayeeAccountId / defaultPayeeAccountLabel |
| 4 | 出纳台账 | GET | /admin/finance/cashier/payments/page | 修改接口 | 出参行(仅 REIMBURSE 行)回填 payeeAccountId / payeeAccountLabel 留痕 |
| 5 | 报账详情 | GET | /admin/finance/reimburses/{id} | 修改接口 | 出参加 payeeAccountId / payeeAccountSnapshot 留痕 |
其余出纳付款线(advance / company-loan / expense / payment / nonbiz 等 7 条)的 /pay 接口:入参壳上能看到 payeeAccountId / newPayeeAccount 两个字段,但**传了会被静默忽略,不报错**,行为与之前完全一致——前端不要在这些页签渲染收款账户控件。
## 3. 接口详情
### 3.1 报账款登记付款 POST /admin/finance/cashier/reimburse/pay
- **使用场景**:出纳在「财务出纳-报账款」页签对待付款报账单(status=APPROVED 且 direction=PAYABLE)执行付款,记资金流水 OUT + 回写 PAID。
- **认证**:需管理后台 JWT。
- **幂等性**:有。重复付款由 CAS 条件更新兜底(重复提交报 598602 状态冲突),重复手填建档由收款账户唯一索引兜底(不重复建档)。
- **限流**:无。
### 3.2 按收款方查账户全集 GET /admin/finance/payee-accounts/list-by-payee
- **使用场景**:付款弹窗打开时,用队列行的 reporterPayeeType + reporterRefId 拉该报账人全部收款账户,渲染「选账户」下拉。
- **认证**:需管理后台 JWT。
- **幂等性**:只读。
- **限流**:无。
### 3.3 报账款待付款队列 GET /admin/finance/cashier/reimburse/queue
- **使用场景**:报账款页签列表;行数据现在直接带主报账人档案信息与默认收款账户,前端打开付款弹窗时**无需二次查询**即可预选默认账户。
- **认证 / 幂等 / 限流**:同上,只读。
## 4. 接口入参
### 4.1 付款入参 CashierPayFormReqVO(变化后全量,仅报账款线语义)
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| bizId | String(Long) | 是 | 业务单据ID(报账单ID) |
| payAccountId | String(Long) | 是 | 出账公司账户ID(fin_fund_account) |
| payMethod | String | 否 | 付款方式(字典 fin_pay_way:CASH 现金 / BANK 银行转账 / THIRD_PARTY 三方支付) |
| payChannel | String | 条件 | 付款渠道(WXPAY / ALIPAY;仅 payMethod=THIRD_PARTY 时传,不超过 20 字符) |
| amount | Number | 是 | 付款金额(大于 0,须等于单据结算金额,不一致 598610 硬拦) |
| fee | Number | 否 | 手续费(不小于 0) |
| voucherNo | String | 否 | 付款凭证号 |
| voucherUrl | String | 否 | 付款凭证影像 URL |
| payDate | Date | 是 | 付款日期(yyyy-MM-dd,可回溯补录) |
| **payeeAccountId** | **String(Long)** | **条件必填** | **新增**:已建档收款账户ID。payMethod=BANK/THIRD_PARTY 时与 newPayeeAccount **二选一必填**;CASH 时**禁传**(598612) |
| **newPayeeAccount** | **Object** | **条件必填** | **新增**:手填建档对象,与 payeeAccountId **互斥**(同传 598612),子字段见 4.2 |
#### 4.2 newPayeeAccount 子字段(手填建档)
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| payeeRefId | String(Long) | 是 | 服务人员档案ID(司机=driverId,其余角色=staffId)。**强制挂档案**,不允许纯手填名称 |
| accountType | String | 是 | 账户类型:WECHAT_QR / ALIPAY_QR / BANK_CARD(对公 CORP_ACCOUNT 在报账款线被拦,见 §6) |
| qrUrl | String | 条件 | 收款码图 URL(QR 类账户必填) |
| bankAccount | String | 条件 | 银行卡号(BANK_CARD 必填) |
| bankName | String | 否 | 开户行 |
**不传 payeeName**:账户名一律以服务人员档案真名覆盖,出纳手填的名称不生效(防止同名串账)。
### 4.3 list-by-payee 查询参数
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| payeeType | String | 是 | 收款方类型:DRIVER / GUIDE / PHOTOGRAPHER / LEADER(直接取队列行 reporterPayeeType) |
| payeeRefId | Long | 是 | 关联ID(司机=driverId,其余=staffId;直接取队列行 reporterRefId) |
### 4.4 队列入参
无变化:仅分页参数 page / pageSize。
## 5. 出参字段
统一 Result 包装;分页为 PageResult(records / total / page / pageSize)。Long 主键一律以 **JSON 字符串**返回。
### 5.1 付款响应 CashierPayRespVO(变化后)
| 字段 | 类型 | 说明 |
|------|------|------|
| flowId | String(Long) | 资金流水ID |
| flowNo | String | 资金流水单号 |
| balanceAfter | Number | 出账账户付后余额 |
| bizId | String(Long) | 业务单据ID(已回写 PAID) |
| **payeeAccountId** | **String(Long),可空** | **新增**:实际落账的收款账户ID(手填建档场景返回新建ID);现金付款为 null |
| **payeeAccountLabel** | **String,可空** | **新增**:收款账户快照(类型+脱敏尾号+收款人名,如「银行卡 ****0123 巴特尔」);现金付款为 null |
### 5.2 list-by-payee 响应(PayeeAccountRowRespVO 数组)
| 字段 | 类型 | 说明 |
|------|------|------|
| id | String(Long) | 收款账户ID |
| payeeType | String | 收款方类型 |
| payeeRefId | String(Long) | 关联ID |
| payeeName | String | 收款方名称(档案真名快照) |
| accountType | String | 账户类型:WECHAT_QR / ALIPAY_QR / BANK_CARD / CORP_ACCOUNT |
| qrUrl | String | 收款码图 URL |
| bankAccount | String | 银行卡号/对公账号(展示层脱敏) |
| bankName | String | 开户行 |
| isDefault | Integer | 是否默认账户:0 否 / 1 是 |
### 5.3 队列行 ReimburseQueueRowRespVO(新增字段)
| 字段 | 类型 | 说明 |
|------|------|------|
| **bizType** | **String** | **新增**:业务对象类型(ORDER 订单 / GROUP_BATCH 团期批次) |
| **reporterAssignmentId** | **String(Long),可空** | **新增**:主报账人人员分配ID(多态口径,无主报账人为空)。**注意:这是分配行ID,不能直接当 payeeRefId 用** |
| **reporterRefId** | **String(Long),可空** | **新增**:主报账人档案ID(司机=driverId,其余=staffId),后端已按 bizType 反查好,前端直接用 |
| **reporterPayeeType** | **String,可空** | **新增**:主报账人收款方类型(DRIVER/GUIDE/PHOTOGRAPHER/LEADER;不可映射角色为空) |
| **defaultPayeeAccountId** | **String(Long),可空** | **新增**:该报账人默认收款账户ID(无默认账户为空) |
| **defaultPayeeAccountLabel** | **String,可空** | **新增**:默认账户标签(类型+脱敏尾号,如「银行卡 ****0123」;无默认账户为空) |
其余既有字段(reimburseId / bizNo / reporterName / settleAmount / status 等)无变化。
### 5.4 台账行 / 报账详情(留痕字段)
- 台账 GET /admin/finance/cashier/payments/page 行(CashierPaymentRowRespVO)新增 payeeAccountId(String 可空)+ payeeAccountLabel(String 可空),**仅 REIMBURSE 报账款行有值**,其余付款线恒 null。
- 报账详情 GET /admin/finance/reimburses/{id} 新增 payeeAccountId(String 可空)+ payeeAccountSnapshot(String 可空,类型+脱敏尾号+收款人名)。现金付款两字段为 null。
## 6. 枚举 / 数据字典
### 6.1 账户类型 accountType(PayeeAccountTypeEnum)
| 码值 | 中文 | 报账款线可用 |
|------|------|------|
| WECHAT_QR | 微信收款码 | 可用 |
| ALIPAY_QR | 支付宝收款码 | 可用 |
| BANK_CARD | 银行卡 | 可用 |
| CORP_ACCOUNT | 对公账户 | 不可用(报账款对私,手填传对公被拦 595202) |
### 6.2 收款方类型 payeeType / reporterPayeeType
DRIVER 司机 / GUIDE 导游 / PHOTOGRAPHER 摄影师 / LEADER 领队。角色映射:GUIDE_ASSISTANT(副导游)归并为 GUIDE;其余不可映射角色 reporterPayeeType 为空,付款时选账户会被归属校验拦(598614)。
### 6.3 付款方式 payMethod(字典 fin_pay_way,无变化)
CASH 现金 / BANK 银行转账 / THIRD_PARTY 三方支付。
### 6.4 账户类型与付款方式强校验(已拍板硬拦)
| payMethod | 允许的账户类型 | 错位报错 |
|---|---|---|
| BANK | BANK_CARD | 598615 |
| THIRD_PARTY | WECHAT_QR / ALIPAY_QR | 598615 |
| CASH | 不允许传任何收款账户 | 598612 |
## 7. 错误码
### 7.1 新增(出纳域 5986 段顺延)
| 码 | 常量 | 文案 | 触发场景 |
|---|---|---|---|
| 598612 | CASHIER_PAYEE_ACCOUNT_CONFLICT | 收款账户参数非法(现金付款无需收款账户;选账户与手填建档二选一) | CASH 传了 payeeAccountId/newPayeeAccount;或两者同传 |
| 598613 | CASHIER_PAYEE_ACCOUNT_REQUIRED | 该付款方式须选择或登记报账人收款账户 | BANK/THIRD_PARTY 但两者皆空 |
| 598614 | CASHIER_PAYEE_ACCOUNT_NOT_MATCH | 收款账户不属于本报账人 | 所选账户的 payeeRefId 不等于报账单主报账人档案ID |
| 598615 | CASHIER_PAYEE_ACCOUNT_TYPE_MISMATCH | 收款账户类型与付款方式不匹配(银行转账须选银行卡账户,三方支付须选收款码账户) | 如 payMethod=BANK 却选了微信收款码账户 |
### 7.2 复用(收款账户域 5952xx,手填建档链抛出)
| 码 | 说明 |
|---|---|
| 595201 | 收款账户不存在或已停用(选了已被删除/停用的账户) |
| 595202 | 账户要素组合校验失败(QR 类缺 qrUrl / BANK_CARD 缺 bankAccount / 报账款线传 CORP_ACCOUNT 等) |
| 595204 | 服务人员档案不存在或不在册(手填建档 payeeRefId 校验 fail-fast) |
### 7.3 既有(本次不变)
598602 状态冲突(重复付款)/ 598610 金额与单据应付不一致 / 598604 账户余额不足 等沿用。
## 8. 示例
### 8.1 典型成功(银行转账 + 手填建档银行卡)
请求:
```
POST /admin/finance/cashier/reimburse/pay
```
```json
{
"bizId": "88010001",
"payAccountId": "3101",
"payMethod": "BANK",
"amount": 1500.00,
"payDate": "2026-10-09",
"newPayeeAccount": {
"payeeRefId": "6601",
"accountType": "BANK_CARD",
"bankAccount": "6222021234567890123",
"bankName": "工商银行呼和浩特支行"
}
}
```
响应:
```json
{
"code": 0,
"data": {
"flowId": "990001",
"flowNo": "FL2026100912300001",
"balanceAfter": 48500.00,
"bizId": "88010001",
"payeeAccountId": "7701",
"payeeAccountLabel": "银行卡 ****0123 巴特尔"
},
"msg": ""
}
```
队列行带默认账户(付款弹窗直接预选):
```json
{
"reimburseId": "88010001",
"bizNo": "BX202610090001",
"reporterName": "巴特尔",
"bizType": "ORDER",
"reporterAssignmentId": "5501",
"reporterRefId": "6601",
"reporterPayeeType": "DRIVER",
"defaultPayeeAccountId": "7701",
"defaultPayeeAccountLabel": "银行卡 ****0123",
"settleAmount": 1500.00
}
```
list-by-payee 下拉数据源:
```
GET /admin/finance/payee-accounts/list-by-payee?payeeType=DRIVER&payeeRefId=6601
```
```json
{
"code": 0,
"data": [
{
"id": "7701",
"payeeType": "DRIVER",
"payeeRefId": "6601",
"payeeName": "巴特尔",
"accountType": "BANK_CARD",
"qrUrl": null,
"bankAccount": "****0123",
"bankName": "工商银行呼和浩特支行",
"isDefault": 1
}
]
}
```
### 8.2 边界情况
**8.2a 现金付款(不带收款账户,两新字段回 null)**:
```json
{
"bizId": "88010002",
"payAccountId": "3101",
"payMethod": "CASH",
"amount": 300.00,
"payDate": "2026-10-09"
}
```
```json
{
"code": 0,
"data": {
"flowId": "990002",
"flowNo": "FL2026100912300002",
"balanceAfter": 48200.00,
"bizId": "88010002",
"payeeAccountId": null,
"payeeAccountLabel": null
}
}
```
**8.2b 报账人无默认账户 / 角色不可映射**:队列行 defaultPayeeAccountId / defaultPayeeAccountLabel / reporterPayeeType 为 null,下拉为空数组,出纳须手填建档。
### 8.3 业务失败
**8.3a 银行转账不传收款账户(598613)**:
```json
{
"bizId": "88010001",
"payAccountId": "3101",
"payMethod": "BANK",
"amount": 1500.00,
"payDate": "2026-10-09"
}
```
```json
{ "code": 598613, "msg": "该付款方式须选择或登记报账人收款账户", "data": null }
```
**8.3b 银行转账选了微信收款码账户(598615)**:
请求体同上但改为传 `"payeeAccountId": "7702"`(7702 是该报账人的 WECHAT_QR 账户)。
```json
{ "code": 598615, "msg": "收款账户类型与付款方式不匹配(银行转账须选银行卡账户,三方支付须选收款码账户)", "data": null }
```
**8.3c 选了别的报账人的账户(598614)**:
```json
{ "code": 598614, "msg": "收款账户不属于本报账人", "data": null }
```
**8.3d 现金付款传了收款账户 / 选账户与手填同传(598612)**:
```json
{ "code": 598612, "msg": "收款账户参数非法(现金付款无需收款账户;选账户与手填建档二选一)", "data": null }
```
## 9. 业务边界
- **适用**:报账款(REIMBURSE)页签、direction=PAYABLE(付给司导)的付款单。
- **不适用**:
- 反向收款(confirm-in,司导向公司上交余款)**零改动**,不涉及收款账户。
- 其余 7 条出纳付款线不接联动,壳入参上的两个新字段传了**静默忽略**。
- **强制挂档案**:手填建档必须选服务人员档案(payeeRefId 必填),账户名=档案真名,出纳不能手填收款人姓名。
- **例外通道**:收款人不是司导本人(如家属代收)时,**不**走付款弹窗手填,先去「收款账户管理」用组织类/手填入口建档,再回到付款弹窗选择(归属校验口径:账户须挂到该报账人档案下,否则 598614)。
- **首个账户自动置默认**:手填建档的账户若是该收款方名下首个账户,自动成为默认;已有账户时不抢既有默认。
- **重复提交安全**:双击/重试不会产生重复账户(唯一索引兜底),也不会重复扣款(598602)。
## 10. 修改前后对比
### 10.1 字段级
| 位置 | 原来 | 现在 |
|------|------|------|
| 付款入参 | 只有出账账户 payAccountId | 新增 payeeAccountId / newPayeeAccount(BANK/THIRD_PARTY 二选一必填,CASH 禁传) |
| 付款响应 | flowId/flowNo/balanceAfter/bizId | 新增 payeeAccountId / payeeAccountLabel(CASH 为 null) |
| 队列行 | 无报账人档案/账户信息 | 新增 bizType / reporterAssignmentId / reporterRefId / reporterPayeeType / defaultPayeeAccountId / defaultPayeeAccountLabel |
| 台账行 | 无收款账户留痕 | REIMBURSE 行回填 payeeAccountId / payeeAccountLabel,其余线 null |
| 报账详情 | 无收款账户留痕 | 新增 payeeAccountId / payeeAccountSnapshot |
| 选账户下拉数据源 | 不存在 | 新增 GET /admin/finance/payee-accounts/list-by-payee |
### 10.2 行为级
| 项 | 原来 | 现在 |
|----|------|------|
| BANK/THIRD_PARTY 付款 | 只填出账账户即可 | **必须**选收款账户或手填建档,否则 598613 |
| CASH 付款 | 不变 | 传收款账户参数会被 598612 拦(前端应禁用控件) |
| 账户与付款方式 | 无校验 | 错位强拦 598615(银行转账须银行卡,三方须收款码) |
| 账户归属 | 无校验 | 只能选/建本报账人名下账户,否则 598614 |
## 11. 影响评估 / 回滚
- **破坏性**:**有(行为级)**。报账款 BANK/THIRD_PARTY 付款从「不填收款账户也能付」变为「必填」,前端付款弹窗**必须同步改造**(加选账户下拉 + 手填建档表单 + CASH 时禁用),否则出纳付款会被 598613 全量拦截。
- **前端同步上线**:**要求**。后端部署后旧版前端的报账款银行/三方付款会全部报 598613,前端改版须与后端部署同步或紧随。
- **回滚方案**:后端回退到 #8810 之前版本即可恢复旧行为;前端新版代码对旧后端兼容(新字段不传即走旧链路)。DB 新增两列可空,保留无害;fin_payee_account 唯一索引不影响既有数据。
## 12. 注意事项
1. **金额/ID 一律字符串**:所有 Long ID(bizId / payeeAccountId / payeeRefId / defaultPayeeAccountId 等)JSON 序列化为字符串,前端按 string 处理。
2. **CASH 禁用收款账户控件**:payMethod 切到 CASH 时隐藏/禁用选账户与手填入口,并清空已填值,避免 598612。
3. **下拉数据源用队列行字段**:打开付款弹窗时直接拿 reporterPayeeType + reporterRefId 调 list-by-payee,**不要**用 reporterAssignmentId(那是分配行ID,口径不同)。
4. **默认账户预选**:队列行 defaultPayeeAccountId 非空时直接预选,出纳可改选或改手填。
5. **新字段全部可空**:存量报账单、现金付款、其余付款线场景下 payeeAccountId/Label 均为 null,渲染一律判空。
6. **部署时序**:依赖 Flyway 迁移 V20261009_136__fin_reimburse_payee_account_link.sql(fin_reimburse 加两列 + fin_payee_account 加唯一索引),order-v3 部署时自动执行;**未部署前新字段不生效(598613 不会触发,行为同旧版)**。
7. **手填建档幂等**:同一报账人同一账户要素重复提交不会重复建档,返回已有账户ID,前端无需防重逻辑(但仍建议提交后禁用按钮)。
## 13. 关联 / 联系人
- Issue:https://git.1814.love/wx/HL/issues/8809
- PR:https://git.1814.love/wx/HL/pulls/8810
- Commit:https://git.1814.love/wx/HL/commit/e50a43b72a68e93b993154d1bb45ad4aa185d92b
- 关联:#8679(收款账户关联服务人员档案基建)
- 后端负责人:腰苏图(yst)