docs(changelog): #8164 收票核销硬钩稽PR-2——钱侧防御钩子599413/599414(应付/预付编辑改价超额+删除/驳回有票关联硬拦,修改接口-管理后台)
changelog-filename-gate / validate (push) Failing after 2s
changelog-filename-gate / validate (push) Failing after 2s
这个提交包含在:
@@ -0,0 +1,261 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8164"
|
||||
title: "收票核销硬钩稽 PR-2——钱侧防御钩子:应付/预付编辑改价超额(599413)+ 删除/驳回有票关联(599414)硬拦"
|
||||
consumer: "admin"
|
||||
author: "yst(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "pending"
|
||||
frontend_status: "pending"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "收票(进项发票)域钱侧防御钩子上线:应付/预付的编辑草稿、删除草稿、驳回六个既有接口新增服务端硬校验——编辑改价后金额低于该单已被有效票匹配的金额报 599413;删除/驳回存在非作废收票关联的单据报 599414。无新接口、入参/出参字段零变化,仅行为收紧 + 新增两个错误码。已合并 dev-v3(PR #8165,merge commit c4a1f1fb69)并部署测试服、行为级验证通过。匹配额口径 = Σ fin_invoice_in_rel.match_amount(剔 VOIDED 作废票),作废票后匹配额自动释放、单据恢复可删改;无票或票已作废的单据不受影响。"
|
||||
updated_at: "2026-09-22"
|
||||
base: "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
|
||||
|
||||
响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"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,放行):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": null,
|
||||
"msg": ""
|
||||
}
|
||||
```
|
||||
|
||||
等额放行是有意设计:matched > newAmount 才拦,matched = newAmount 视为票-款刚好持平。预付编辑(PUT /admin/finance/prepays/{id})同理,比较口径为 amount。
|
||||
|
||||
### 8.3 业务失败 —— 编辑草稿把金额改到已匹配票额之下,触发 599413
|
||||
|
||||
同 8.2 的单据,若改为 5000.00(低于已匹配 8000.00):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 599413,
|
||||
"data": null,
|
||||
"msg": "收票匹配额超过单据金额(须先作废或改票)"
|
||||
}
|
||||
```
|
||||
|
||||
整单不落库、事务整体回滚。再给一个 599414 示例——删除一张已被发票匹配的应付草稿:
|
||||
|
||||
DELETE /admin/finance/payments/1982736450000055
|
||||
|
||||
```json
|
||||
{
|
||||
"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 放行**路径;业务上确需解除拦截时,正确做法是作废/改票释放匹配额
|
||||
|
||||
## 十三、关联 / 联系人
|
||||
|
||||
- **Issue**: [#8164 收票核销硬钩稽 PR-2——钱侧防御钩子](https://git.1814.love:8443/wx/HL/issues/8164)
|
||||
- **PR**: [#8165 feat(finance): 收票核销硬钩稽 PR-2——InvoiceInRelGuard 钱侧防御钩子 + 应付/预付六处接线](https://git.1814.love:8443/wx/HL/pulls/8165)
|
||||
- **Commit**: [c4a1f1fb69](https://git.1814.love:8443/wx/HL/commit/c4a1f1fb691277bfa7590b1db24e6ef7654777e6)
|
||||
- **关联前置**: PR-1 [#8163 挂票生效态门槛(599412)+ biz-recon 下钻](https://git.1814.love:8443/wx/HL/pulls/8163)(changelog 已推)
|
||||
- **后端负责人**: 腰苏图(yst)
|
||||
在新工单中引用
屏蔽一个用户