文件
hl-api-changelog/changelogs-v2/2026-09/22_8164_收票核销硬钩稽钱侧防御钩子-修改接口-管理后台.md

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 配置变更,前端无需配合回滚。存量数据不受影响(钩子只拦写操作,不做数据订正)。

十二、注意事项

  1. 两个新错误码的 msg 已含引导动作(「须先作废或改票」),前端可直接展示 msg 作为错误提示
  2. 599413 的比较口径:应付取冲抵后实付额 actualPayAmount(amount − ΣprepayOffsets 冲抵金额),不是表单里的 amount 原值;预付取 amount。编辑应付时若同时改了冲抵明细,触发 599413 的门槛以重算后的实付额为准
  3. 匹配额统计剔 VOIDED 作废票:先把挂着的票作废,匹配额立即释放,单据即可正常删改——这是解除拦截的标准动作
  4. EXPENSE 费用报销单不在本守卫范围,报销单操作不会出现这两个错误码
  5. 本组钩子是 fail-fast 硬拦,不存在 WARN 放行路径;业务上确需解除拦截时,正确做法是作废/改票释放匹配额

十三、关联 / 联系人