From 16ed452d005482ee451daa01745c4eff86d8c32d Mon Sep 17 00:00:00 2001 From: yaosutu <770858045@qq.com> Date: Thu, 17 Sep 2026 16:35:52 +0800 Subject: [PATCH] =?UTF-8?q?docs(changelog-v2):=20#7857=20=E6=94=B6?= =?UTF-8?q?=E7=A5=A8(=E8=BF=9B=E9=A1=B9=E5=8F=91=E7=A5=A8)=E5=9F=9F?= =?UTF-8?q?=E4=B8=8A=E7=BA=BF=E2=80=94=E2=80=94=E7=99=BB=E8=AE=B0/?= =?UTF-8?q?=E7=BC=96=E8=BE=91/=E6=A0=B8=E5=AF=B9/=E4=BD=9C=E5=BA=9F=20+=20?= =?UTF-8?q?=E5=85=B3=E8=81=94=E5=8B=BE=E9=80=89=20+=20=E4=BE=9B=E5=BA=94?= =?UTF-8?q?=E5=95=86=E5=AF=B9=E8=B4=A6=208=20=E7=AB=AF=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 changelogs-v2/2026-09/17_7857 收票(进项发票)域上线-新增接口-管理后台.md - 覆盖 Epic #7857(PR #7860/#7874/#7880 + 文档 #7891,已合并 dev-v3 并部署测试服验证通过) - 8 端点:page/detail/create/update/verify/void/biz-candidates/supplier-recon - 错误码 599400-599411 + 字典 fin_invoice_in_type + 菜单「收票管理」全内联自包含 --- ..._收票(进项发票)域上线-新增接口-管理后台.md | 627 ++++++++++++++++++ 1 file changed, 627 insertions(+) create mode 100644 changelogs-v2/2026-09/17_7857_收票(进项发票)域上线-新增接口-管理后台.md diff --git a/changelogs-v2/2026-09/17_7857_收票(进项发票)域上线-新增接口-管理后台.md b/changelogs-v2/2026-09/17_7857_收票(进项发票)域上线-新增接口-管理后台.md new file mode 100644 index 00000000..ed5c6994 --- /dev/null +++ b/changelogs-v2/2026-09/17_7857_收票(进项发票)域上线-新增接口-管理后台.md @@ -0,0 +1,627 @@ +--- +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: "pending" +frontend_owner: "" +frontend_ref: "" +target_release: "" +verified_at: "2026-09-17" +status_note: "财务域收票(进项发票)首次交付:登记收票 → 核对 → 作废主链 + 按供应商捞可勾选应付/预付 + 供应商收票对账共 8 个新端点。一张票可挂多笔应付/预付/费用报销(ΣmatchAmount ≤ invoiceAmount,允许部分匹配)也可零关联纯挂供应商;不管进项税抵扣、不接税务查验。后端已合并 dev-v3 并部署测试服,行为级验证通过。菜单「收票管理」(path=/finance/invoice/in)已挂「发票管理」目录下并授权 SUPER_ADMIN/ADMIN/FINANCE。前端此前无收票页面,按本文一次对接即可。" +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`:`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`) + +### 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) |