--- schema: "hl-changelog/v2" ticket: "8809" title: "出纳付款联动司导收款账户(选已建档账户 / 手填自动建档,报账款线)" consumer: "admin" author: "yst" change_type: "修改接口" backend_status: "merged" gateway_status: "not_required" frontend_status: "implemented" frontend_owner: "hl-admin(claude-opus-4-8)" frontend_ref: "1f96d87b5df0c4e56e9db65f43856b96fb849269" target_release: "v2.1" verified_at: "2026-10-09" status_note: "后端 PR #8810 已合并 dev-v3(squash e50a43b72a),含 Flyway 迁移 V20261009_136(fin_reimburse 加收款账户两列 + fin_payee_account 唯一索引),需部署 order-v3 后生效;存量报账单新字段为 null。前端已交付:付款弹窗 REIMBURSE 门控加收款账户「选已建档(list-by-payee 下拉+默认预选)/手填建档」二选一,CASH 隐藏清空,账户类型按付款方式过滤;台账加收款账户留痕列、报账详情加快照展示;checkpoint 全量通过" 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)