文件
hl-api-changelog/changelogs-v2/2026-09/22_8161_收票核销硬钩稽挂票门槛与单据对账下钻-修改接口-管理后台.md
T
2026-09-22 16:25:47 +08:00

17 KiB
原始文件 Blame 文件历史

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 8161 收票核销硬钩稽 PR-1——登记/编辑挂票收紧为生效态门槛(新增错误码 599412)+ 新增单据维度收票对账下钻 biz-recon admin yst(GIT) 修改接口 merged pending verified mmg 5072da0b991cdcde4cc83cc5be94072eb049d224 v2.1 2026-09-22 收票(进项发票)域挂票规则收紧:登记(create)/编辑(update)挂 PAYMENT/PREPAY 关联单时新增生效态硬校验,非 APPROVED/PAID 抛新错误码 599412(此前可挂草稿/审批中/已驳回单);同时新增单据维度收票对账下钻 GET /admin/finance/invoice-in/biz-recon。EXPENSE 关联规则不变。已合并 dev-v3(PR #8163,merge commit 90739708e5),待部署测试服后验证。前端走 biz-candidates 勾选关联的链路天然不触发 599412(候选只出 APPROVED/PAID 单);若有手填 bizId 或复用存量草稿单关联的入口需处理 599412 提示。 前端已交付(5072da0b):invoice-in.js 新增 getInvoiceInBizRecon,详情「关联业务明细」行级「对账」下钻弹窗(五档金额后端字段直显、unmatchedAmount 负值红字不截断、EXPENSE 供应商空值兜底);599412 经核验前端全走 biz-candidates 勾选天然不触发,零改动。 2026-09-22 dev-v3

收票核销硬钩稽 PR-1:挂票生效态门槛(599412)+ 单据维度收票对账下钻 biz-recon(管理后台)

服务: hl-order-service-v3(hl-finance 模块,端口 8086/8186) PR: #8163 Issue: #8161 日期: 2026-09-22 影响范围: 管理后台「财务管理 - 收票管理(进项发票)」的登记/编辑弹窗(挂关联业务单据)、供应商收票对账页的「按单据下钻」能力


一、接口背景

收票(进项发票)域已上线(#7857),主链为「登记收票 → 核对 → 作废」,供应商为对账锚点。

本次 PR 解决两个缺口:

  1. 挂票门槛缺失:此前登记(create)/ 编辑(update)发票挂关联业务单据时,后端只验「单据存在 + 归属本供应商」,不验单据状态——发票可以挂到草稿(PENDING)/ 审批中(SUBMITTED)/ 已驳回(REJECTED)的应付款、预付款单上。而这些非生效态单据的金额还可能被修改、单据还可能被驳回,造成「钱变了票不知道」,对账口径失真。本次对 PAYMENT / PREPAY 增加生效态硬校验:只有 APPROVED(已批准)/ PAID(已付讫)(金额已冻结、无变更入口)的单据才可挂票,否则抛新错误码 599412。
  2. 对账无法下钻:供应商收票对账(supplier-recon)只有供应商维度合计,看不到单张业务单据的收票进度。本次新增单据维度下钻接口 biz-recon,按 bizType + bizId 查单张单据的应收票额 / 已匹配额(拆「已核对」「已收票未核对」两档)/ 未匹配额。

二、变更清单

# 接口 方法 路径 变更类型
1 单据维度收票对账下钻 GET /admin/finance/invoice-in/biz-recon 新增接口
2 登记进项发票 POST /admin/finance/invoice-in/create 行为收紧:PAYMENT/PREPAY 关联单须 APPROVED/PAID,否则 599412
3 编辑进项发票 PUT /admin/finance/invoice-in/update 行为收紧:同 #2(关联走全删重插,门槛自动覆盖存量数据)

入参结构、出参结构对 #2 #3 均零变化(字段名/类型/必填全不变),仅服务端校验规则收紧 + 新增一个错误码。

三、接口详情

接口 使用场景 认证 幂等性 限流
GET /admin/finance/invoice-in/biz-recon 收票对账页从供应商/单据行下钻,看单张应付/预付/费用报销单的收票进度与未匹配额 管理后台 JWT 只读,天然幂等 网关默认
POST /admin/finance/invoice-in/create 登记一张进项发票并可挂多笔业务单据 管理后台 JWT 非幂等(发票号码唯一,重复登记 599401) 网关默认
PUT /admin/finance/invoice-in/update 编辑已收票(RECEIVED)态发票,关联全删重插 管理后台 JWT 非幂等(同内容重复提交幂等) 网关默认

四、接口入参

4.1 biz-recon Query 参数

参数 类型 必填 说明
bizType String 是 业务类型:PAYMENT 应付款 / PREPAY 预付款 / EXPENSE 费用报销(大小写不敏感,取值域外按 599407 处理)
bizId Long 是 业务单据 ID(应付单/预付单/费用报销单主键;查无此单 → 599407)

4.2 create / update 请求体关联字段(结构不变,仅校验收紧)

请求体整体结构同既有版本(发票号码/发票类型/供应商/价税合计/日期/影像/备注等,见 #7857 changelog),本次只涉及 relations 数组元素的校验规则:

字段 类型 必填 说明
relations[].bizType String 是 PAYMENT / PREPAY / EXPENSE
relations[].bizId Long 是 业务单据 ID。新规则:PAYMENT/PREPAY 单据状态必须是 APPROVED 或 PAID,否则整单拒绝并报 599412;EXPENSE 维持只验存在
relations[].matchAmount BigDecimal 是 本票对该笔的匹配金额(>0;ΣmatchAmount > 票面金额 → 599406)

五、出参字段(biz-recon → InvoiceInBizReconRespVO)

统一响应包装 Result<InvoiceInBizReconRespVO>,data 字段如下:

字段 类型 说明
bizType String 业务类型英文码(PAYMENT/PREPAY/EXPENSE)
bizTypeName String 业务类型中文名(应付款/预付款/费用报销),直接展示,勿前端再硬编码 map
bizId String 业务单据 ID(Long 主键序列化为字符串,防 JS 精度丢失)
bizNo String 业务单据号(快照,如应付单号/预付单号/报销单号)
billStatus String 单据状态英文码:PENDING/SUBMITTED/APPROVED/REJECTED/PAID
billStatusName String 单据状态中文名(草稿/审批中/已批准/已驳回/已付讫),直接展示
supplierId String 或 null 供应商 ID(Long 序列化为字符串);EXPENSE 无供应商锚点,恒为 null
supplierName String 或 null 供应商名称(快照);EXPENSE 恒为 null
billAmount BigDecimal 应收票额:PAYMENT 取实付金额 actual_pay_amount / PREPAY 取金额 / EXPENSE 取金额
matchedVerifiedAmount BigDecimal 已核对票匹配额:该单关联的 VERIFIED 发票的 Σmatch_amount(剔作废票)
matchedReceivedAmount BigDecimal 已收票未核对匹配额:该单关联的 RECEIVED 发票的 Σmatch_amount(剔作废票)
matchedAmount BigDecimal 已匹配额合计 = matchedVerifiedAmount + matchedReceivedAmount
unmatchedAmount BigDecimal 未匹配额 = billAmount − matchedAmount,可为负数(= 多收票),前端展示不要 max(0,·) 截断
matchedInvoiceCount Long 匹配发票张数(剔作废票,JSON number 非字符串);无任何匹配时为 0,各匹配额为 0

六、枚举 / 数据字典

6.1 bizType(业务类型)

值 中文名 说明
PAYMENT 应付款 应收票额取实付金额(actual_pay_amount,含冲抵后口径)
PREPAY 预付款 应收票额取金额
EXPENSE 费用报销 无供应商锚点(supplierId/supplierName 为 null),不进供应商对账

6.2 billStatus(单据状态,应付/预付/费用报销三域状态名一致)

值 中文名 是否可挂票
PENDING 草稿 否(599412)
SUBMITTED 审批中 否(599412)
APPROVED 已批准 是
REJECTED 已驳回 否(599412)
PAID 已付讫 是

「是否可挂票」只对 PAYMENT/PREPAY 强制;EXPENSE 不验状态。

6.3 发票状态(匹配额拆分依据,本次无新增值)

值 中文名 是否计入匹配额
RECEIVED 已收票 计入 matchedReceivedAmount
VERIFIED 已核对 计入 matchedVerifiedAmount
VOIDED 已作废 不计入(作废自动释放匹配额)

七、错误码

错误码 常量 消息 触发场景
599412 INVOICE_IN_REL_BIZ_NOT_EFFECTIVE 关联业务单据未生效(应付/预付须已批准或已付讫才可收票) 本次新增。create/update 的 relations 挂 PAYMENT/PREPAY 且单据状态非 APPROVED/PAID 时抛出,整单拒绝
599407 INVOICE_IN_REL_BIZ_NOT_FOUND 关联业务单据不存在 biz-recon:bizType 取值域外或 bizId 查无此单;create/update:单据不存在、或不归属本发票的开票供应商
599405 INVOICE_IN_AMOUNT_INVALID 发票金额非法 relations[].matchAmount 为空或 ≤0(既有)
599406 INVOICE_IN_AMOUNT_EXCEEDED 匹配金额合计超过票面金额 ΣmatchAmount > invoiceAmount(既有)
599411 INVOICE_IN_REL_DUPLICATED 同一张票重复关联同一业务单据 同一请求内 bizType+bizId 重复(既有)

599412 与 599407 的区别:407 = 单据不存在 / 不归属本供应商;412 = 单据存在且归属正确,但状态未生效。前端错误提示文案可直接用 message,也可自行区分「选错单」与「单未生效」两种引导。

八、示例

8.1 典型成功 —— 应付单下钻,部分收票

请求:

GET /admin/finance/invoice-in/biz-recon?bizType=PAYMENT&bizId=1982736450011223

响应:

{
  "code": 0,
  "data": {
    "bizType": "PAYMENT",
    "bizTypeName": "应付款",
    "bizId": "1982736450011223",
    "bizNo": "PAY-202609-0042",
    "billStatus": "PAID",
    "billStatusName": "已付讫",
    "supplierId": "1877665544332211",
    "supplierName": "满洲里蓝天旅行社有限公司",
    "billAmount": 12000.00,
    "matchedVerifiedAmount": 8000.00,
    "matchedReceivedAmount": 2000.00,
    "matchedAmount": 10000.00,
    "unmatchedAmount": 2000.00,
    "matchedInvoiceCount": 2
  },
  "msg": ""
}

8.2 边界情况 —— EXPENSE 无匹配发票(各匹配额为 0、供应商字段为 null)

请求:

GET /admin/finance/invoice-in/biz-recon?bizType=EXPENSE&bizId=1966112233445566

响应:

{
  "code": 0,
  "data": {
    "bizType": "EXPENSE",
    "bizTypeName": "费用报销",
    "bizId": "1966112233445566",
    "bizNo": "EXP-202609-0118",
    "billStatus": "APPROVED",
    "billStatusName": "已批准",
    "supplierId": null,
    "supplierName": null,
    "billAmount": 3500.00,
    "matchedVerifiedAmount": 0,
    "matchedReceivedAmount": 0,
    "matchedAmount": 0,
    "unmatchedAmount": 3500.00,
    "matchedInvoiceCount": 0
  },
  "msg": ""
}

另一典型边界:多收票时 unmatchedAmount 为负数。例如 billAmount=10000、matchedAmount=12000,则 unmatchedAmount=-2000.00,属于有意返回,请原样展示(负值即「多收票」预警)。

8.3 业务失败 —— 登记发票挂「草稿态」应付单触发 599412

请求:

POST /admin/finance/invoice-in/create
Content-Type: application/json

{
  "invoiceNo": "INV-2026-092201",
  "invoiceType": "SPECIAL",
  "supplierId": 1877665544332211,
  "invoiceAmount": 5000.00,
  "invoiceDate": "2026-09-20",
  "receiveDate": "2026-09-22",
  "relations": [
    { "bizType": "PAYMENT", "bizId": 1982736450000001, "matchAmount": 5000.00 }
  ]
}

(该应付单状态为 PENDING 草稿)响应:

{
  "code": 599412,
  "data": null,
  "msg": "关联业务单据未生效(应付/预付须已批准或已付讫才可收票)"
}

整单拒绝,发票不落库。update 链路表现相同。

九、业务边界

适用场景

  • 收票对账页:从供应商对账行 / 单据行下钻,看单张应付/预付/费用报销单的收票进度(biz-recon)
  • 登记/编辑发票:挂已批准或已付讫的应付/预付单、任意状态的费用报销单

不适用场景

  • biz-recon 是单查接口(一次一单),不是分页列表,不要拿它批量刷数
  • EXPENSE 费用报销无供应商锚点,不进供应商维度对账(supplier-recon 只覆盖应付/预付);biz-recon 支持查 EXPENSE 仅用于展示
  • 非生效态应付/预付单不可作为挂票对象(本次收紧点),「先挂票后补批」的流程不再可行

特殊边界

  • 通过「可关联业务单据候选」接口(GET /admin/finance/invoice-in/biz-candidates)勾选的链路天然不触发 599412——候选接口本来就只返回 APPROVED/PAID 生效单,与本次门槛口径一致。只有手填 bizId、直调接口、或编辑「存量挂草稿单」的发票时才可能撞上 599412
  • 作废(VOIDED)发票自动释放其匹配额,释放后 biz-recon 的各匹配额即时下降,无需前端额外刷新逻辑(重新调接口即可)

十、修改前后对比

维度 修改前 修改后
create/update 挂 PAYMENT/PREPAY 只验「单据存在 + 归属本供应商」,草稿/审批中/已驳回单均可挂票成功 额外验状态:非 APPROVED/PAID → 整单拒绝,报 599412
create/update 挂 EXPENSE 只验存在 不变,仍只验存在
update 存量数据 编辑时关联全删重插,旧校验不拦非生效单 编辑一张「存量挂草稿单」的发票时,重插校验触发 599412,需先移除失效关联才能保存
单据维度对账 无接口(只有供应商维度 supplier-recon) 新增 GET /admin/finance/invoice-in/biz-recon
入参/出参字段结构 — 零变化(无字段增删改名;仅新增 biz-recon 接口与 599412 错误码)

十一、影响评估 / 回滚

是否破坏兼容:行为级破坏(窄面)。原先「挂非生效态应付/预付单」能成功的请求现在报 599412 失败;接口签名、字段、其余错误码均不变。

前端需要同步上线吗:不需要强同步,但建议排查:

  • 若登记/编辑弹窗的关联单来源全部走 biz-candidates 候选勾选 → 零改动,不会触发 599412
  • 若存在手填 bizId / 直调接口 / 复制历史发票等绕过候选的入口 → 需要把 599412 纳入错误提示(文案可直接用 msg),并引导用户先完成单据审批
  • 若有「多收票」展示位置 → 注意 unmatchedAmount 可为负,不要做 max(0, x) 截断

回滚方案:后端回滚本 PR 即恢复旧行为(不验状态、biz-recon 404);无 DDL、无 Nacos 配置变更,前端无需配合回滚。存量已挂非生效单的关联行不会被追溯清理,仍正常计入匹配额。

十二、注意事项

  1. 两个对账接口是「有意的不同口径」,不要互相对数:
    • biz-recon 的 matchedAmount = Σ match_amount(关联行口径),计 VERIFIED + RECEIVED 两档发票对该单的匹配金额之和
    • supplier-recon 的 receivedAmount = Σ invoice_amount(整票口径),仅计 VERIFIED 发票的整票价税合计
    • 一张 RECEIVED 未核对的票:在 biz-recon 里计入 matchedReceivedAmount,在 supplier-recon 里不计;一张票只部分匹配某单时:biz-recon 计 match_amount 部分,supplier-recon 核对后计整票 invoice_amount。两者天然不等,matchedVerifiedAmount / matchedReceivedAmount 拆两档正是为解释这个差异。前端不要把两个接口的金额互相核对相等,也不要混用字段名
  2. bizId / supplierId 是 JSON 字符串(Long 经 ToStringSerializer 序列化,防 JS 精度丢失);matchedInvoiceCount 是普通 JSON number
  3. bizTypeName / billStatusName 由后端返回中文,直接展示,不要再在前端维护硬编码 map
  4. EXPENSE 单的 supplierId / supplierName 恒为 null,展示时注意空值兜底
  5. biz-recon 对非法 bizType、不存在的 bizId 统一报 599407,不区分「类型错」与「单不存在」

十三、关联 / 联系人