--- 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`:`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) |