文件
hl-api-changelog/changelogs-v2/2026-09/17_7857_收票(进项发票)域上线-新增接口-管理后台.md
T
2026-09-17 19:51:40 +08:00

628 行
28 KiB
Markdown
原始文件 Blame 文件历史

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
---
schema: "hl-changelog/v2"
ticket: "invoice-in-domain-launch"
title: "收票(进项发票)域上线——登记/编辑/核对/作废 + 关联勾选 + 供应商对账(8 个端点)"
consumer: "admin"
author: "yst(GIT)"
change_type: "新增接口"
backend_status: "deployed"
gateway_status: "verified"
frontend_status: "verified"
frontend_owner: "mmg"
frontend_ref: "f5751df4cc81568229bc1ae9fa770b6bf28a2120"
target_release: ""
verified_at: "2026-09-17"
status_note: "财务域收票(进项发票)首次交付:登记收票 → 核对 → 作废主链 + 按供应商捞可勾选应付/预付 + 供应商收票对账共 8 个新端点。一张票可挂多笔应付/预付/费用报销(ΣmatchAmount ≤ invoiceAmount,允许部分匹配)也可零关联纯挂供应商;不管进项税抵扣、不接税务查验。后端已合并 dev-v3 并部署测试服,行为级验证通过。菜单「收票管理」(path=/finance/invoice/in)已挂「发票管理」目录下并授权 SUPER_ADMIN/ADMIN/FINANCE。前端此前无收票页面,按本文一次对接即可。[mmg 2026-09-17 交付] ①api/finance/invoice-in.js 8 端点+RECEIVED/VERIFIED/VOIDED 三态字典(写四函数关去重;599400-599411 拦截器透 message 不建字典;docstring 标注全删重插/Σ≤票面 599406/599408 非 400/biz-candidates 不捞 EXPENSE);②finance/invoice/in 页内三视图:台账列表(发票号模糊+供应商弹窗精确+状态+收票日期区间)|供应商收票对账(应收=应付+预付生效合计/已收只认 VERIFIED/未收>0 警示)|详情只读(留痕快照+关联明细空态「纯挂供应商」);③行操作按 status 原码:RECEIVED=[查看,编辑|核对,作废] renderActionsWithMore,VERIFIED=[查看,作废],VOIDED=[查看];作废原因 NForm 必填 trim≤500;④登记/编辑弹窗:选定供应商即拉 biz-candidates 换供应商重拉;编辑拉详情三路合并建行——候选行/详情 PAYMENT·PREPAY 行(上限=remainingAmount+本票原 matchAmount)/EXPENSE 只读保留行(仅可移除,整包回传防全删重插静默清掉);ΣmatchAmount≤invoiceAmount NForm 预校验;create 无勾选=纯挂供应商不带 relations,update 恒带整包;invoiceType 走字典 fin_invoice_in_type,xxxName 后端回填直渲;⑤原型状态选项旧版以契约三态为准,原型无对账/详情布局按 PayableDetailPanel 模式自建。api spec 8 例+页面 spec 7 例+弹窗 spec 6 例,定向 21/21,scoped checkpoint 13 项全绿。"
updated_at: "2026-09-17"
base: "dev-v3"
---
# 收票(进项发票)域上线(管理后台)
> **服务**: hl-order-service-v3(hl-finance 模块,端口 8086);字典 / 菜单在 hl-user-service
> **类型**: 🆕 新增(本期首次交付,前端首次对接)
> **日期**: 2026-09-17
> **影响范围**: 管理后台财务域「发票管理 - 收票管理」页面(新页面)
> **覆盖 Epic**: #7857(代码 PR-1 #7860 地基两表 / PR-2 #7874 核心接口+字典种子 / PR-3 #7880 核对/作废+对账+菜单 / 文档 PR #7891)
---
## 一、接口背景
收票(进项发票) = **供应商开给我方的发票台账**。业务主链:
```
登记收票(RECEIVED 已收票) → 核对(VERIFIED 已核对)
↘ 作废(VOIDED 已作废,终态,RECEIVED/VERIFIED 均可作废)
```
核心口径:
- **供应商是对账锚点**:每张票必挂一个开票供应商(反查资源域,无效 → 599410 fail-fast)
- **一张票可挂多笔业务**:应付 / 预付 / 费用报销均可关联(N:N 关系表),`ΣmatchAmount ≤ invoiceAmount`(应用层校验,**允许部分匹配**);也可**零关联**纯挂供应商(票先到、业务后补挂的场景走编辑补关联)
- **不管进项税抵扣**:`taxRate` / `taxAmount` 仅记录可空,不做税额勾稽
- **不接税务查验**:纯内部台账,无外部税局接口
- **核对 / 作废全留痕**:操作人 ID + 姓名快照 + 时间落库,详情接口可见
配套查询:**供应商收票对账**(每行一供应商:应收票额 = 应付+预付已生效合计,已收 = VERIFIED 票合计,未收 = 差额),用于追供应商欠票。
---
## 二、变更清单
| # | 变更 | 类型 | 说明 |
|---|------|------|------|
| 1 | `GET /admin/finance/invoice-in/page` | ✨ 新增 | 进项发票分页(供应商 / 状态 / 发票号码模糊 / 收票日期区间筛选) |
| 2 | `GET /admin/finance/invoice-in/{id}` | ✨ 新增 | 进项发票详情(主表全字段 + relations 关联列表 + 核对/作废留痕快照) |
| 3 | `POST /admin/finance/invoice-in/create` | ✨ 新增 | 登记进项发票(落 RECEIVED;可挂多笔应付/预付/费用报销,也可纯挂供应商) |
| 4 | `PUT /admin/finance/invoice-in/update` | ✨ 新增 | 编辑进项发票(仅 RECEIVED 态;关联全删重插) |
| 5 | `POST /admin/finance/invoice-in/verify` | ✨ 新增 | 核对进项发票(RECEIVED→VERIFIED;CAS 守卫 + 核对人快照) |
| 6 | `POST /admin/finance/invoice-in/void` | ✨ 新增 | 作废进项发票(RECEIVED/VERIFIED→VOIDED 终态;作废原因必填 599408) |
| 7 | `GET /admin/finance/invoice-in/biz-candidates` | ✨ 新增 | 按供应商捞可勾选业务单据(已生效且未收齐票的应付/预付,供登记关联勾选) |
| 8 | `GET /admin/finance/invoice-in/supplier-recon/page` | ✨ 新增 | 供应商收票对账分页(应收 / 已收 / 未收三列) |
| 9 | 错误码段位 599400-599499 | ✨ 新增 | 5994 段首次落码(hl-finance),启用 599400-599411 |
| 10 | 字典 `fin_invoice_in_type` | ✨ 新增 | 发票类型字典(dict_type_id=10163):SPECIAL 专票 / NORMAL 普票 / ELECTRONIC 电子票 |
| 11 | 菜单「收票管理」 | ✨ 新增 | menu_id=2026090900000000901,path=`/finance/invoice/in`,挂「发票管理」(...0900) 目录下,已授权 SUPER_ADMIN / ADMIN / FINANCE |
---
## 三、接口详情
| 项 | 说明 |
|---|---|
| 使用场景 | 管理后台财务域「发票管理 - 收票管理」:收票登记 / 核对 / 作废 + 供应商欠票对账 |
| 认证 | 管理后台 JWT(网关统一鉴权),需「收票管理」菜单权限(SUPER_ADMIN / ADMIN / FINANCE 已授权) |
| 幂等性 | 查询类(page / detail / biz-candidates / supplier-recon)只读幂等;写类:登记靠票号唯一约束防重(599401),核对走 CAS 状态守卫(重复核对 599403),作废走状态机守卫(已作废 599404),重复提交安全 |
| 限流 | 走网关统一限流,无模块特殊限流 |
| ID 序列化 | 所有 Long 型 ID(`id` / `supplierId` / `bizId` / `verifyBy` / `voidBy` / 关联行 `id`)序列化为 **String**,防 JS 精度丢失 |
| 状态机 | `RECEIVED` 已收票 → `VERIFIED` 已核对;`RECEIVED` / `VERIFIED` → `VOIDED` 已作废(**终态**,任何写操作拦截 599404) |
---
## 四、接口入参
分页参数(page / supplier-recon 两个分页端点通用,继承统一分页基类):
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `page` | Integer | 否 | 页码,默认 1,最小 1(兼容别名 `pageNo`) |
| `pageSize` | Integer | 否 | 每页条数,默认 20,范围 1-100 |
### 4.1 `GET /page` 进项发票分页 Query 入参
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `invoiceNo` | String | 否 | 发票号码(**模糊**);空 = 不限 |
| `supplierId` | Long | 否 | 开票供应商ID 精确;空 = 不限 |
| `status` | String | 否 | 单据状态:`RECEIVED` / `VERIFIED` / `VOIDED`;空 = 全部 |
| `receiveDateStart` | Date | 否 | 收票日期起(闭区间),格式 `yyyy-MM-dd`;空 = 不限 |
| `receiveDateEnd` | Date | 否 | 收票日期止(闭区间),格式 `yyyy-MM-dd`;空 = 不限 |
### 4.2 `GET /{id}` 详情 路径入参
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `id` | Long | ✅ | 进项发票ID(路径变量;不存在或已软删 → 599400) |
### 4.3 `POST /create` 登记 请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `invoiceNo` | String | ✅ | 发票号码,≤ 40 字符;**全局唯一**,重复 → 599401 |
| `invoiceCode` | String | 否 | 发票代码(纸质票有,电子票可空),≤ 20 字符 |
| `invoiceType` | String | ✅ | 发票类型,字典 `fin_invoice_in_type` 取值:`SPECIAL` / `NORMAL` / `ELECTRONIC`;域外值 → 599409 |
| `supplierId` | Long | ✅ | 开票供应商ID;反查资源域无效 / 不可付款 / 服务降级 → 599410(涉钱 fail-fast) |
| `invoiceAmount` | BigDecimal | ✅ | 价税合计,**须 > 0**,否则 → 599405 |
| `taxRate` | BigDecimal | 否 | 税率%(仅记录,不管进项税抵扣) |
| `taxAmount` | BigDecimal | 否 | 税额(仅记录,不管进项税抵扣) |
| `invoiceDate` | Date | 否 | 开票日期,格式 `yyyy-MM-dd` |
| `receiveDate` | Date | 否 | 收票日期,格式 `yyyy-MM-dd` |
| `voucherUrl` | String | 否 | 发票影像URL,≤ 500 字符 |
| `remark` | String | 否 | 备注,≤ 500 字符 |
| `relations` | Array | 否 | 关联业务源列表;**可空 = 纯挂供应商**;一张票可挂多笔。元素结构见下 |
`relations[]` 元素(InvoiceInRelReqVO):
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `bizType` | String | ✅ | 业务类型:`PAYMENT` 应付款 / `PREPAY` 预付款 / `EXPENSE` 费用报销 |
| `bizId` | Long | ✅ | 业务单据ID;查无此单 / 归属不符 → 599407 |
| `matchAmount` | BigDecimal | ✅ | 本票对该笔的匹配金额,**须 > 0**(否则 599405);同票 ΣmatchAmount ≤ invoiceAmount(否则 599406) |
> 同一张票**重复关联同一笔**业务单据(同 bizType+bizId)→ 599411。
### 4.4 `PUT /update` 编辑 请求体
字段同 4.3 登记请求体,**额外加**:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `invoiceInId` | Long | ✅ | 进项发票ID;仅 `RECEIVED` 态可编辑(VERIFIED → 599402/599403,VOIDED → 599404) |
> **关联走全删重插**:`relations` 传什么就是什么;传空数组 / 不传 = 清空全部关联(变纯挂供应商票)。校验规则同登记。
### 4.5 `POST /verify` 核对 请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `invoiceInId` | Long | ✅ | 进项发票ID;仅 `RECEIVED` → `VERIFIED`(CAS 守卫;已核对 → 599403,已作废 → 599404) |
### 4.6 `POST /void` 作废 请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `invoiceInId` | Long | ✅ | 进项发票ID;`RECEIVED` / `VERIFIED` → `VOIDED` 终态(已作废 → 599404) |
| `voidReason` | String | ✅ | 作废原因,≤ 500 字符;**空(含纯空白)→ 599408**(不加 @NotBlank,空原因落业务错误码而非 400) |
### 4.7 `GET /biz-candidates` 可勾选业务单据 Query 入参
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `supplierId` | Long | ✅ | 供应商ID;捞该供应商名下**已生效且未收齐票**的应付/预付单据 |
### 4.8 `GET /supplier-recon/page` 供应商收票对账 Query 入参
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `supplierName` | String | 否 | 供应商名称(**模糊**);空 = 不限 |
---
## 五、出参字段
统一外层 `Result<T>`:`code`(0=成功,否则业务错误码)/ `message` / `data`。分页端点 `data` 为 `PageResult`:`records` / `total` / `page` / `pageSize`。
- `POST /create` 返回 `data = { "id": "String" }`(新票 ID,状态 RECEIVED)
- `PUT /update`、`POST /verify`、`POST /void` 返回 `data = null`(`Result<Void>`)
### 5.1 列表行 `InvoiceInRespVO`(`/page` 的 records 元素)
| 字段 | 类型 | 说明 |
|---|---|---|
| `id` | String | 进项发票ID(Long → String) |
| `invoiceNo` | String | 发票号码 |
| `invoiceCode` | String | 发票代码(可空) |
| `invoiceType` | String | 发票类型:`SPECIAL` / `NORMAL` / `ELECTRONIC` |
| `invoiceTypeName` | String | 发票类型中文名(字典 label 回填:专票 / 普票 / 电子票) |
| `supplierId` | String | 开票供应商ID(Long → String) |
| `supplierName` | String | 供应商名称快照 |
| `invoiceAmount` | BigDecimal | 价税合计 |
| `taxRate` | BigDecimal | 税率%(可空) |
| `taxAmount` | BigDecimal | 税额(可空) |
| `invoiceDate` | Date | 开票日期(可空) |
| `receiveDate` | Date | 收票日期(可空) |
| `voucherUrl` | String | 发票影像URL(可空) |
| `remark` | String | 备注(可空) |
| `status` | String | 单据状态:`RECEIVED` / `VERIFIED` / `VOIDED` |
| `statusName` | String | 状态中文名(已收票 / 已核对 / 已作废) |
| `voidReason` | String | 作废原因(仅 `status=VOIDED` 时有值,其余为 null) |
| `createTime` | DateTime | 创建时间 |
### 5.2 详情 `InvoiceInDetailRespVO`(`GET /{id}`)
= 5.1 全部字段 + 以下追加:
| 字段 | 类型 | 说明 |
|---|---|---|
| `verifyBy` | String | 核对人ID(`status=VERIFIED` 时有值,Long → String) |
| `verifyByName` | String | 核对人姓名快照 |
| `verifyTime` | DateTime | 核对时间 |
| `voidBy` | String | 作废人ID(`status=VOIDED` 时有值,Long → String) |
| `voidByName` | String | 作废人姓名快照 |
| `voidTime` | DateTime | 作废时间 |
| `relations` | Array | 关联业务源列表(按登记顺序升序;**纯挂供应商票 = 空列表 []**,不为 null) |
`relations[]` 元素(InvoiceInRelRespVO):
| 字段 | 类型 | 说明 |
|---|---|---|
| `id` | String | 关联行ID(Long → String) |
| `bizType` | String | 业务类型:`PAYMENT` / `PREPAY` / `EXPENSE` |
| `bizTypeName` | String | 业务类型中文名(应付款 / 预付款 / 费用报销) |
| `bizId` | String | 业务单据ID(Long → String) |
| `bizNo` | String | 业务单号快照 |
| `matchAmount` | BigDecimal | 本票对该笔的匹配金额 |
### 5.3 可勾选业务单据 `InvoiceInBizCandidateRespVO`(`/biz-candidates`,返回 `List`,非分页)
| 字段 | 类型 | 说明 |
|---|---|---|
| `bizType` | String | 业务类型:`PAYMENT` 应付款 / `PREPAY` 预付款(本端点只捞这两类,不含 EXPENSE) |
| `bizTypeName` | String | 业务类型中文名 |
| `bizId` | String | 业务单据ID(Long → String) |
| `bizNo` | String | 业务单号 |
| `amount` | BigDecimal | 单据金额 |
| `matchedAmount` | BigDecimal | 已被有效发票(非作废)匹配金额合计 |
| `remainingAmount` | BigDecimal | 剩余可收票金额 = amount − matchedAmount(**> 0 才返回**,收齐票的单据不出现) |
### 5.4 供应商收票对账行 `InvoiceInSupplierReconRespVO`(`/supplier-recon/page` 的 records 元素)
| 字段 | 类型 | 说明 |
|---|---|---|
| `supplierId` | String | 供应商ID(Long → String) |
| `supplierName` | String | 供应商名称(快照) |
| `payableAmount` | BigDecimal | 应收票额 = Σ该供应商应付款(APPROVED/PAID).实付金额 + Σ预付款(APPROVED/PAID).金额 |
| `receivedAmount` | BigDecimal | 已收票额 = Σ该供应商 **VERIFIED** 已核对票的 invoice_amount(RECEIVED 未核对不计入) |
| `unreceivedAmount` | BigDecimal | 未收票额 = payableAmount − receivedAmount |
---
## 六、枚举 / 数据字典
### status 单据状态(状态机枚举)
| 值 | 中文名 | 含义 | 可执行动作 |
|---|---|---|---|
| `RECEIVED` | 已收票 | 登记落库初始态 | 编辑 / 核对 / 作废 |
| `VERIFIED` | 已核对 | 核对完成 | 作废(不可编辑) |
| `VOIDED` | 已作废 | 终态 | 无(任何写操作 → 599404) |
### bizType 业务类型(FinInvoiceInBizTypeEnum)
| 值 | 中文名 | 说明 |
|---|---|---|
| `PAYMENT` | 应付款 | 关联 fin_payment 单据 |
| `PREPAY` | 预付款 | 关联 fin_prepay 单据 |
| `EXPENSE` | 费用报销 | 关联费用报销单(登记/编辑 relations 可用;biz-candidates 端点不捞此类) |
### invoiceType 发票类型(数据字典 `fin_invoice_in_type`,dict_type_id=10163)
| 值 | label | 说明 |
|---|---|---|
| `SPECIAL` | 专票 | 增值税专用发票 |
| `NORMAL` | 普票 | 增值税普通发票 |
| `ELECTRONIC` | 电子票 | 电子发票 |
> 取值域外的 `invoiceType` → 599409。`invoiceTypeName` 由后端按字典 label 回填,前端直接渲染即可;状态 `statusName` / 业务类型 `bizTypeName` 同理后端回填。
---
## 七、错误码
5994 段首次落码(owner = hl-finance,段位 599400-599499):
| 错误码 | 含义 | 触发场景 |
|---|---|---|
| 599400 | 进项发票不存在 | 详情 / 编辑 / 核对 / 作废传了不存在(或已软删)的 ID |
| 599401 | 发票号码已存在 | 登记 / 编辑时 invoiceNo 撞号(应用侧查重 + 并发撞号兜底同码) |
| 599402 | 仅已收票状态可编辑/核对 | 对 VERIFIED 票执行编辑或核对 |
| 599403 | 发票已核对 | 重复核对(CAS 守卫拦截) |
| 599404 | 发票已作废 | 对 VOIDED 终态票执行任何写操作 |
| 599405 | 发票金额非法 | invoiceAmount ≤ 0,或 matchAmount ≤ 0(同码) |
| 599406 | 匹配金额合计超过票面金额 | 同票 ΣmatchAmount > invoiceAmount |
| 599407 | 关联业务单据不存在 | relations 里 bizType+bizId 回链查无此单 / 归属不符 |
| 599408 | 作废原因必填 | 作废时 voidReason 为空或纯空白 |
| 599409 | 发票类型非法 | invoiceType 不在字典取值域(SPECIAL/NORMAL/ELECTRONIC 之外) |
| 599410 | 开票供应商无效 | 反查资源域:查无此供应商 / 不可付款 / 服务降级(涉钱 fail-fast) |
| 599411 | 同一张票重复关联同一业务单据 | 同请求内 relations 出现重复 bizType+bizId |
> 参数非空 / 长度 / 格式类校验失败走 Spring 400(如 invoiceNo 为空、receiveDate 格式错),不占 5994 段。
---
## 八、示例
### 8.1 典型成功
**8.1.1 登记一张挂两笔业务的专票**:
```
POST /admin/finance/invoice-in/create
Content-Type: application/json
```
```json
{
"invoiceNo": "24317000000123456789",
"invoiceType": "SPECIAL",
"supplierId": 2096854417461403650,
"invoiceAmount": 12600.00,
"taxRate": 6,
"taxAmount": 713.21,
"invoiceDate": "2026-09-10",
"receiveDate": "2026-09-15",
"voucherUrl": "https://oss.example.com/invoice/24317000000123456789.pdf",
"remark": "9月团住宿费票",
"relations": [
{ "bizType": "PAYMENT", "bizId": 2100159999888777666, "matchAmount": 10000.00 },
{ "bizType": "PREPAY", "bizId": 2100178000111222333, "matchAmount": 2600.00 }
]
}
```
响应(`code=0`):
```json
{ "code": 0, "message": "success", "data": { "id": "2101200000000000001" } }
```
**8.1.2 分页查询**:
```
GET /admin/finance/invoice-in/page?supplierId=2096854417461403650&status=RECEIVED&page=1&pageSize=20
```
```json
{
"code": 0,
"data": {
"records": [
{
"id": "2101200000000000001",
"invoiceNo": "24317000000123456789",
"invoiceCode": null,
"invoiceType": "SPECIAL",
"invoiceTypeName": "专票",
"supplierId": "2096854417461403650",
"supplierName": "示例酒店A",
"invoiceAmount": 12600.00,
"taxRate": 6,
"taxAmount": 713.21,
"invoiceDate": "2026-09-10",
"receiveDate": "2026-09-15",
"voucherUrl": "https://oss.example.com/invoice/24317000000123456789.pdf",
"remark": "9月团住宿费票",
"status": "RECEIVED",
"statusName": "已收票",
"voidReason": null,
"createTime": "2026-09-15 10:23:45"
}
],
"total": 1,
"page": 1,
"pageSize": 20
}
}
```
**8.1.3 核对**:
```
POST /admin/finance/invoice-in/verify
```
```json
{ "invoiceInId": 2101200000000000001 }
```
```json
{ "code": 0, "message": "success", "data": null }
```
核对后详情(`GET /admin/finance/invoice-in/2101200000000000001`)节选——留痕快照 + relations:
```json
{
"code": 0,
"data": {
"id": "2101200000000000001",
"invoiceNo": "24317000000123456789",
"status": "VERIFIED",
"statusName": "已核对",
"verifyBy": "1",
"verifyByName": "腰苏图",
"verifyTime": "2026-09-15 11:05:12",
"voidBy": null,
"voidByName": null,
"voidTime": null,
"relations": [
{
"id": "2101200000000000011",
"bizType": "PAYMENT",
"bizTypeName": "应付款",
"bizId": "2100159999888777666",
"bizNo": "FK20260916003",
"matchAmount": 10000.00
},
{
"id": "2101200000000000012",
"bizType": "PREPAY",
"bizTypeName": "预付款",
"bizId": "2100178000111222333",
"bizNo": "YF20260916001",
"matchAmount": 2600.00
}
]
}
}
```
**8.1.4 供应商收票对账**:
```
GET /admin/finance/invoice-in/supplier-recon/page?supplierName=示例&page=1&pageSize=20
```
```json
{
"code": 0,
"data": {
"records": [
{
"supplierId": "2096854417461403650",
"supplierName": "示例酒店A",
"payableAmount": 20000.00,
"receivedAmount": 12600.00,
"unreceivedAmount": 7400.00
}
],
"total": 1,
"page": 1,
"pageSize": 20
}
}
```
### 8.2 边界情况
**8.2.1 零关联纯挂供应商票**(票先到、业务后补挂)——`relations` 不传:
```json
{
"invoiceNo": "04300160011199887766",
"invoiceType": "ELECTRONIC",
"supplierId": 2096854417461403650,
"invoiceAmount": 500.00,
"receiveDate": "2026-09-16"
}
```
登记成功;详情 `relations` 返回空列表 `[]`(不为 null)。后续通过 `PUT /update` 补挂关联(全删重插口径,传新数组即可)。
**8.2.2 部分匹配**——票面 12600 只匹配 10000(ΣmatchAmount < invoiceAmount,允许):
```json
{
"invoiceNo": "24317000000199998888",
"invoiceType": "NORMAL",
"supplierId": 2096854417461403650,
"invoiceAmount": 12600.00,
"relations": [
{ "bizType": "PAYMENT", "bizId": 2100159999888777666, "matchAmount": 10000.00 }
]
}
```
登记成功(差额 2600 可留在后续票继续匹配该笔应付,直至 biz-candidates 里 remainingAmount 归 0 不再出现)。
**8.2.3 biz-candidates 已收齐票单据不出现**:
```
GET /admin/finance/invoice-in/biz-candidates?supplierId=2096854417461403650
```
```json
{
"code": 0,
"data": [
{
"bizType": "PAYMENT",
"bizTypeName": "应付款",
"bizId": "2100159999888777666",
"bizNo": "FK20260916003",
"amount": 20000.00,
"matchedAmount": 10000.00,
"remainingAmount": 10000.00
}
]
}
```
`remainingAmount > 0` 才返回;若某笔已被有效票(RECEIVED/VERIFIED)匹配满额,该行不出现。作废票释放匹配额度后会重新出现。
### 8.3 业务失败
**8.3.1 匹配金额合计超票面 → 599406**:
```json
{
"invoiceNo": "24317000000177776666",
"invoiceType": "SPECIAL",
"supplierId": 2096854417461403650,
"invoiceAmount": 1000.00,
"relations": [
{ "bizType": "PAYMENT", "bizId": 2100159999888777666, "matchAmount": 1500.00 }
]
}
```
```json
{ "code": 599406, "message": "匹配金额合计超过票面金额", "data": null }
```
**8.3.2 对已核对票再核对 → 599403**:
```
POST /admin/finance/invoice-in/verify
{ "invoiceInId": 2101200000000000001 }
```
```json
{ "code": 599403, "message": "发票已核对", "data": null }
```
**8.3.3 作废不传原因 → 599408**:
```
POST /admin/finance/invoice-in/void
{ "invoiceInId": 2101200000000000001, "voidReason": " " }
```
```json
{ "code": 599408, "message": "作废原因必填", "data": null }
```
**8.3.4 票号重复 → 599401**:
```json
{ "code": 599401, "message": "发票号码已存在", "data": null }
```
---
## 九、业务边界
**适用**:
- 供应商发票的登记 / 编辑 / 核对 / 作废全生命周期管理
- 一张票分摊到多笔应付 / 预付 / 费用报销(含部分匹配)
- 票先到业务后补挂(零关联登记 → 编辑补挂)
- 按供应商追欠票(supplier-recon 对账页)
**不适用**:
- 进项税抵扣核算(taxRate/taxAmount 仅记录,不做税额勾稽,不对接财务记账)
- 税务查验 / 真伪核验(不接税局接口)
- 销项发票(开给客户的发票走发票管理既有功能,不在本域)
- 历史票批量导入(无导入端点)
**特殊边界**:
- **编辑全删重插**:`PUT /update` 的 relations 是"最终态"语义,不是增量——要保留旧关联必须在请求里带上
- **VOIDED 是终态**:作废后不可恢复、不可再编辑;作废票不占 biz-candidates 的 matchedAmount(额度释放)
- **已收票额只认 VERIFIED**:supplier-recon 的 receivedAmount 只合计已核对票;RECEIVED 未核对票不算"已收",这是催核对的设计意图
- **biz-candidates 只捞 PAYMENT/PREPAY**:EXPENSE 费用报销不出现在勾选列表,但 relations 里手工传 EXPENSE+bizId 合法
- **发票号码全局唯一**:不区分供应商——同一 invoiceNo 即使不同供应商也撞 599401
- **核对是 CAS 守卫**:并发两人同时点核对,一人成功一人 599403,无脏写
---
## 十、修改前后对比
本期为**新增接口**,无修改前后对比(前端此前无收票管理任何页面,按本文一次对接即可)。
---
## 十一、影响评估 / 回滚
| 项 | 说明 |
|---|---|
| 破坏兼容 | 无——纯新增 8 个端点,不改任何既有接口 |
| 前端同步上线 | 无强依赖——前端未对接不影响任何既有功能;「收票管理」菜单已上架并授权 SUPER_ADMIN / ADMIN / FINANCE,页面就绪前菜单点进去为空属预期 |
| 回滚方案 | 后端回滚 = 下线 8 个端点 + 隐藏菜单(前端不调用即无感);DDL 回滚 = DROP fin_invoice_in / fin_invoice_in_rel 两表(本期新建,无历史数据包袱) |
---
## 十二、注意事项
1. **Long ID 全 String**:`id` / `supplierId` / `bizId` / `verifyBy` / `voidBy` / 关联行 `id` 均为 String 输出,直接当字符串用,不要 `Number()` 转换(雪花 ID 超 JS 安全整数)。
2. **三个 xxxName 后端已回填**:`invoiceTypeName`(字典)/ `statusName`(状态机枚举)/ `bizTypeName`(业务类型枚举)随响应直接给,前端不用再查字典渲染。
3. **编辑即覆盖**:`PUT /update` 关联全删重插,前端编辑页打开时先把详情 relations 回填到勾选态,提交时整包回传,否则旧关联会被清空。
4. **作废原因前端必填校验也要做**:空原因后端落 599408(不是 400),但建议前端表单层就拦掉,少一次往返。
5. **金额校验顺序**:invoiceAmount ≤ 0 → 599405;matchAmount ≤ 0 → 599405(同码);ΣmatchAmount > invoiceAmount → 599406。前端可按 `0 < ΣmatchAmount ≤ invoiceAmount` 预校验。
6. **日期格式严格 `yyyy-MM-dd`**:invoiceDate / receiveDate / receiveDateStart / receiveDateEnd 传时间戳或 `2026/09/01` 会 400。
7. **supplier-recon 口径别算反**:payableAmount 只看 APPROVED/PAID 的应付+预付(草稿/已撤销不计);receivedAmount 只认 VERIFIED。两列差额 = 供应商欠票,是本页的核心 actionable 列。
8. **biz-candidates 在登记/编辑弹窗里联动调用**:选定供应商后立即调该端点拉勾选列表;换供应商要重拉(候选按供应商隔离)。
9. **幂等重试安全**:登记撞票号 599401、核对重复 599403、作废重复 599404,均为明确业务码,前端可据码提示而非笼统报错。
---
## 十三、关联 / 联系人
| 项 | 链接 |
|---|---|
| Epic Issue | https://git.1814.love:8443/wx/HL/issues/7857 |
| 代码 PR-1(地基:两表 + DO/Mapper/枚举/错误码) | https://git.1814.love:8443/wx/HL/pulls/7860 |
| 代码 PR-2(核心接口 + 字典种子) | https://git.1814.love:8443/wx/HL/pulls/7874 |
| 代码 PR-3(核对/作废/供应商对账 + 菜单) | https://git.1814.love:8443/wx/HL/pulls/7880 |
| 文档 PR(SRS / API / DM / 详设同步) | https://git.1814.love:8443/wx/HL/pulls/7891 |
| 代码基线(dev-v3 HEAD) | https://git.1814.love:8443/wx/HL/commit/1d0cd8d965e123a1650d921646648c247f3ad669 |
| 后端负责人 | 腰苏图(yst) |