15 KiB
schema, ticket, title, consumer, author, change_type, backend_status, gateway_status, frontend_status, frontend_owner, frontend_ref, target_release, verified_at, status_note, updated_at, base
| schema | ticket | title | consumer | author | change_type | backend_status | gateway_status | frontend_status | frontend_owner | frontend_ref | target_release | verified_at | status_note | updated_at | base |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| hl-changelog/v2 | 8164 | 收票核销硬钩稽 PR-2——钱侧防御钩子:应付/预付编辑改价超额(599413)+ 删除/驳回有票关联(599414)硬拦 | admin | yst(GIT) | 修改接口 | deployed | pending | not_required | 收票(进项发票)域钱侧防御钩子上线:应付/预付的编辑草稿、删除草稿、驳回六个既有接口新增服务端硬校验——编辑改价后金额低于该单已被有效票匹配的金额报 599413;删除/驳回存在非作废收票关联的单据报 599414。无新接口、入参/出参字段零变化,仅行为收紧 + 新增两个错误码。已合并 dev-v3(PR #8165,merge commit c4a1f1fb69)并部署测试服、行为级验证通过。匹配额口径 = Σ fin_invoice_in_rel.match_amount(剔 VOIDED 作废票),作废票后匹配额自动释放、单据恢复可删改;无票或票已作废的单据不受影响。 前端核验(2026-09-22):应付 PaymentDetailPanel 驳回/编辑/删除与预付 advance/index 编辑/删除/驳回六处调用点 catch 全走 request.js 拦截器透 message,无按码分支、无文案重写,599413/599414 的 msg 引导动作自动覆盖,前端行为零改动判 not_required;payable.js/prepay.js 头注顺手补 599412-599414 口径(注释修正 590c1155)。 | 2026-09-22 | dev-v3 |
收票核销硬钩稽 PR-2:钱侧防御钩子(599413 / 599414)(管理后台)
服务: hl-order-service-v3(hl-finance 模块,端口 8086/8186) PR: #8165 Issue: #8164 日期: 2026-09-22 影响范围: 管理后台「财务管理 - 应付款管理 / 预付款管理」的编辑草稿、删除草稿、驳回操作(共 6 个既有接口)
一、接口背景
收票核销硬钩稽分两步走:
- PR-1(#8163,已推):票侧门槛——登记/编辑发票挂 PAYMENT/PREPAY 关联单时,单据必须是 APPROVED/PAID 生效态,否则 599412。
- PR-2(本次,#8165):钱侧防御钩子——反过来守「单据侧」:一张已经被发票匹配过的应付/预付单,不允许通过编辑把金额改到已匹配票额之下,也不允许直接删除/驳回,否则会出现「钱变了/钱没了,票还挂在上面」的票-款不符,对账口径失真。
PR-1 生效后正常路径下新票只能挂生效单,本组钩子主要拦存量脏数据(PR-1 之前挂上的草稿/审批中单据关联)与冲抵重算实付额(Epic #8127 改草稿冲抵明细会重算 actualPayAmount)两类接缝场景。涉票一律 fail-fast 硬拦,不放行。
无新接口、无字段增删改名、出参结构零变化,只有 2 个新错误码 + 6 个既有接口的行为收紧。
二、变更清单
| # | 接口 | 方法 | 路径 | 变更类型 |
|---|---|---|---|---|
| 1 | 编辑应付款草稿 | PUT | /admin/finance/payments/{id} | 行为收紧:改后实付额 < 已匹配票额 → 599413 |
| 2 | 删除应付款草稿 | DELETE | /admin/finance/payments/{id} | 行为收紧:存在非作废收票关联 → 599414 |
| 3 | 驳回应付款 | PUT | /admin/finance/payments/{id}/reject | 行为收紧:存在非作废收票关联 → 599414 |
| 4 | 编辑预付款草稿 | PUT | /admin/finance/prepays/{id} | 行为收紧:改后预付额 < 已匹配票额 → 599413 |
| 5 | 删除预付款草稿 | DELETE | /admin/finance/prepays/{id} | 行为收紧:存在非作废收票关联 → 599414 |
| 6 | 驳回预付款 | PUT | /admin/finance/prepays/{id}/reject | 行为收紧:存在非作废收票关联 → 599414 |
注意预付路径前缀是 /admin/finance/prepays(复数),与应付 /payments 一致。
三、接口详情
| 接口 | 使用场景 | 认证 | 幂等性 | 限流 |
|---|---|---|---|---|
| PUT /admin/finance/payments/{id} | 应付草稿(PENDING)编辑改价/改明细,含冲抵明细整体置换 | 管理后台 JWT | 非幂等(同内容重复提交幂等) | 网关默认 |
| DELETE /admin/finance/payments/{id} | 删除应付草稿(PENDING,软删) | 管理后台 JWT | 重复删除报单据不存在 | 网关默认 |
| PUT /admin/finance/payments/{id}/reject | 驳回审批中(SUBMITTED)应付单 | 管理后台 JWT | 非幂等(重复驳回报状态非法) | 网关默认 |
| PUT /admin/finance/prepays/{id} | 预付草稿(PENDING)编辑改价 | 管理后台 JWT | 非幂等 | 网关默认 |
| DELETE /admin/finance/prepays/{id} | 删除预付草稿(PENDING,软删) | 管理后台 JWT | 重复删除报单据不存在 | 网关默认 |
| PUT /admin/finance/prepays/{id}/reject | 驳回审批中(SUBMITTED)预付单 | 管理后台 JWT | 非幂等 | 网关默认 |
四、接口入参
入参结构零变化,仅服务端校验规则收紧。为自包含列关键字段:
4.1 PUT /admin/finance/payments/{id}(PaymentUpdateReqVO,不变)
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| supplierId | Long | 是 | 供应商 ID |
| payeeAccountId | Long | 是 | 收款账户 ID |
| amount | BigDecimal | 是 | 付款金额(>0)。新校验:冲抵重算后的实付额(actualPayAmount = amount − Σ冲抵金额)不得低于该单已匹配票额,否则 599413 |
| paymentType | String | 是 | 付款类型(fin_payment_type 字典标签,≤32) |
| reason | String | 是 | 付款事由(≤512) |
| teamNo | String | 否 | 团号(≤32) |
| orderId | Long | 否 | 关联订单 ID |
| resourceId | Long | 否 | 关联资源 ID |
| prepayOffsets | Array | 否 | 冲抵预付款明细(可空=不冲抵;整体置换语义) |
4.2 PUT /admin/finance/prepays/{id}(PrepayUpdateReqVO,不变)
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| supplierId | Long | 是 | 付款单位供应商 ID |
| offsetSupplierId | Long | 否 | 冲抵单位供应商 ID(默认=付款单位) |
| amount | BigDecimal | 是 | 预付金额(>0,整数≤13位、小数≤2位)。新校验:不得低于该单已匹配票额,否则 599413 |
| availableAmount | BigDecimal | 否 | 可冲抵金额(0 ≤ x ≤ amount) |
| payDate | LocalDate | 是 | 付款日期 |
| remark | String | 否 | 备注(≤512) |
4.3 DELETE / reject 四个接口
- DELETE /admin/finance/payments/{id}、DELETE /admin/finance/prepays/{id}:仅路径参数 id(Long),无请求体
- PUT /admin/finance/payments/{id}/reject、PUT /admin/finance/prepays/{id}/reject:路径参数 id + 请求体 { "reason": "驳回原因" }(不变)
五、出参字段
六个接口出参零变化:成功统一返回 Result<Void>({ "code": 0, "data": null, "msg": "" });失败走统一错误响应({ "code": 错误码, "data": null, "msg": "错误消息" })。
六、枚举 / 数据字典
6.1 单据状态(本次校验的前提,应付/预付一致)
| 值 | 中文名 | 可编辑/删除 | 可驳回 |
|---|---|---|---|
| PENDING | 草稿 | 是(受 599413/599414 约束) | 否 |
| SUBMITTED | 审批中 | 否 | 是(受 599414 约束) |
| APPROVED | 已批准 | 否 | 否 |
| REJECTED | 已驳回 | 否 | 否 |
| PAID | 已付讫 | 否 | 否 |
6.2 发票状态(匹配额口径依据,本次无新增值)
| 值 | 中文名 | 是否计入匹配额 |
|---|---|---|
| RECEIVED | 已收票 | 计入 |
| VERIFIED | 已核对 | 计入 |
| VOIDED | 已作废 | 不计入(作废自动释放匹配额,单据恢复可删改) |
七、错误码
| 错误码 | 常量 | 消息 | 触发场景 |
|---|---|---|---|
| 599413 | INVOICE_IN_MATCH_OVER_BIZ | 收票匹配额超过单据金额(须先作废或改票) | 本次新增。编辑应付/预付草稿时,改后金额(应付取冲抵后实付额 actualPayAmount,预付取 amount)低于该单已被有效票(剔 VOIDED)匹配的金额合计,整单回滚 |
| 599414 | INVOICE_IN_BIZ_HAS_REL | 单据已被收票关联(删除/驳回前须先作废或改票) | 本次新增。删除草稿 / 驳回单据时,该单存在任何非作废发票的匹配关联,不删单、状态不推进 |
| 598802 | PAYMENT_STATUS_ILLEGAL | 付款单状态非法 | 应付编辑/删除时非 PENDING、驳回时非 SUBMITTED(既有) |
| 599002 | PREPAY_STATUS_ILLEGAL / PREPAY_TRANSITION_ILLEGAL | 预付单状态非法 | 预付同上(既有) |
| 599412 | INVOICE_IN_REL_BIZ_NOT_EFFECTIVE | 关联业务单据未生效(应付/预付须已批准或已付讫才可收票) | 票侧(PR-1)挂票门槛,与本组钩子互为对偶(既有) |
599413 与 599414 的区别:413 = 编辑改价场景,「金额不能低于已匹配票额」;414 = 删除/驳回场景,「有票关联的单据不许消失/退出对账」。两个码的引导动作一致:先到收票管理作废相关发票或改票释放匹配额,再回来操作单据。
八、示例
8.1 典型成功 —— 删除一张无收票关联的应付草稿
请求:
DELETE /admin/finance/payments/1982736450000042
响应:
{
"code": 0,
"data": null,
"msg": ""
}
无收票关联(或关联票已全部作废)的单据删改驳回不受影响,行为与本次变更前完全一致。
8.2 边界情况 —— 编辑草稿,改后金额等于已匹配票额(等额放行)
场景:应付草稿 1982736450000055 已被一张 VERIFIED 发票匹配 8000.00。现将付款金额由 10000.00 改为 8000.00(无冲抵,实付额 = 8000.00)。
请求:
PUT /admin/finance/payments/1982736450000055
Content-Type: application/json
{
"supplierId": 1877665544332211,
"payeeAccountId": 1877665544000099,
"amount": 8000.00,
"paymentType": "GROUP_SETTLE",
"reason": "按实结调价",
"teamNo": "T20260918-01",
"orderId": null,
"resourceId": null,
"prepayOffsets": []
}
响应(已匹配 8000.00 ≤ 新实付 8000.00,放行):
{
"code": 0,
"data": null,
"msg": ""
}
等额放行是有意设计:matched > newAmount 才拦,matched = newAmount 视为票-款刚好持平。预付编辑(PUT /admin/finance/prepays/{id})同理,比较口径为 amount。
8.3 业务失败 —— 编辑草稿把金额改到已匹配票额之下,触发 599413
同 8.2 的单据,若改为 5000.00(低于已匹配 8000.00):
{
"code": 599413,
"data": null,
"msg": "收票匹配额超过单据金额(须先作废或改票)"
}
整单不落库、事务整体回滚。再给一个 599414 示例——删除一张已被发票匹配的应付草稿:
DELETE /admin/finance/payments/1982736450000055
{
"code": 599414,
"data": null,
"msg": "单据已被收票关联(删除/驳回前须先作废或改票)"
}
驳回链路(PUT /admin/finance/payments/{id}/reject、PUT /admin/finance/prepays/{id}/reject,请求体 { "reason": "..." })命中有效票关联时同样返回 599414,状态不推进、不落审核流水。
九、业务边界
适用场景
- 应付/预付草稿的正常编辑、删除;SUBMITTED 单的驳回——只要无有效收票关联,行为与之前完全一致
不适用场景(会被新钩子拦截)
- 把单据金额改到「已被有效票匹配的金额」之下(599413)
- 删除 / 驳回仍挂着有效票(RECEIVED/VERIFIED)匹配的单据(599414)
特殊边界
- 匹配额口径:Σ fin_invoice_in_rel.match_amount,只计有效票(发票非 VOIDED 且未软删);作废(VOIDED)发票后匹配额自动释放,单据即时恢复可删改,无需额外操作
- EXPENSE 费用报销单不挂本守卫(无供应商锚点、不进对账),其编辑/删除/驳回行为不变
- 正常路径下 PR-1 的 599412 门槛已要求 APPROVED/PAID 才可挂票,而生效单本就不可编辑/删除/驳回,故本组钩子主要拦存量脏数据(PR-1 之前挂到非生效单上的关联)与冲抵重算接缝
- 守卫与单据写操作同事务:守卫抛出即整单回滚,不会出现「单改了但校验没过」的中间态
十、修改前后对比
| 维度 | 修改前 | 修改后 |
|---|---|---|
| 编辑应付/预付草稿改价 | 只验状态(PENDING)+ 金额合法,可改到任意值 | 额外验:改后金额 ≥ 已匹配票额,否则 599413 整单回滚 |
| 删除应付/预付草稿 | 只验状态(PENDING),软删即走 | 额外验:无非作废收票关联,否则 599414 不删单 |
| 驳回应付/预付 | 只验状态(SUBMITTED) | 额外验:无非作废收票关联,否则 599414 状态不推进 |
| 入参/出参字段结构 | — | 零变化(无字段增删改名,仅新增 2 个错误码 + 6 接口行为收紧) |
| 无票 / 票已作废的单据 | 正常删改驳回 | 不变,照常放行 |
十一、影响评估 / 回滚
是否破坏兼容:行为级破坏(窄面)。原先「有票关联的草稿可删可改可驳回」的请求现在可能报 599413/599414;接口签名、字段、其余错误码均不变。
前端需要同步上线吗:不需要强同步,但建议在应付/预付的编辑草稿、删除草稿、驳回三处操作的错误提示里覆盖 599413 / 599414 两个新错误码(文案可直接用 msg)。正常业务流(PR-1 门槛 + 生效单不可删改)下极少触发,主要面向存量脏数据。
回滚方案:后端回滚本 PR 即恢复旧行为(不再拦截);无 DDL、无 Nacos 配置变更,前端无需配合回滚。存量数据不受影响(钩子只拦写操作,不做数据订正)。
十二、注意事项
- 两个新错误码的 msg 已含引导动作(「须先作废或改票」),前端可直接展示 msg 作为错误提示
- 599413 的比较口径:应付取冲抵后实付额 actualPayAmount(amount − ΣprepayOffsets 冲抵金额),不是表单里的 amount 原值;预付取 amount。编辑应付时若同时改了冲抵明细,触发 599413 的门槛以重算后的实付额为准
- 匹配额统计剔 VOIDED 作废票:先把挂着的票作废,匹配额立即释放,单据即可正常删改——这是解除拦截的标准动作
- EXPENSE 费用报销单不在本守卫范围,报销单操作不会出现这两个错误码
- 本组钩子是 fail-fast 硬拦,不存在 WARN 放行路径;业务上确需解除拦截时,正确做法是作废/改票释放匹配额
十三、关联 / 联系人
- Issue: #8164 收票核销硬钩稽 PR-2——钱侧防御钩子
- PR: #8165 feat(finance): 收票核销硬钩稽 PR-2——InvoiceInRelGuard 钱侧防御钩子 + 应付/预付六处接线
- Commit: c4a1f1fb69
- 关联前置: PR-1 #8163 挂票生效态门槛(599412)+ biz-recon 下钻(changelog 已推)
- 后端负责人: 腰苏图(yst)