diff --git a/changelogs-v2/2026-10/09_8809_出纳付款联动司导收款账户-修改接口-管理后台.md b/changelogs-v2/2026-10/09_8809_出纳付款联动司导收款账户-修改接口-管理后台.md new file mode 100644 index 00000000..51f0dbfd --- /dev/null +++ b/changelogs-v2/2026-10/09_8809_出纳付款联动司导收款账户-修改接口-管理后台.md @@ -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)