# Feature Specification: REV-005 发票业务流实现 **Feature Branch**: `002-rev005-invoice-flow` **Created**: 2026-03-16 **Status**: Verification Pending **Input**: 实现 REV-005 发票业务流核心功能:发票申请、开票校验、调用 SYS-008 发票服务、接收开票结果回写、更新发票与账单关联状态。 --- ## Document Scope & Sources - **Target documents**: - `docs/design/02_Detailed_Design/12_REV_Detailed.md`(REV-005 章节补充) - `docs/design/03_Technical_Design/03_Interface_Design.md`(IF-REV-008 发票申请接口、IF-REV-009 发票查询接口定义) - Backend: `backend/sw-business/sw-business-server` 新增 Service/Controller - **Primary source of truth**: - `docs/design/02_Detailed_Design/12_REV_Detailed.md` 中 REV-005 定义 - `docs/guides/BACKEND_CURRENT_STATUS.md` - `docs/guides/BACKEND_TABLE_MAPPING.md` - **Reference sources**: - Archive: `docs/design/04_Appendix/Archive/03_Design_Docs/营业收费管理系统-概要设计说明书20250912.md` - Existing: `InvoiceController.java`, `InvoiceServiceImpl.java` - **Scope decision**: 在已有基础数据层(开票配置、客户开票信息、税率配置)之上,补齐发票申请、开具、结果回写的完整业务流程。 --- ## User Scenarios & Testing ### User Story 1 - 发票申请与校验 (Priority: P1) 营业收费员或财务人员通过后台系统提交发票申请,系统校验账单状态、开票信息完整性和开票限额,生成发票申请记录。客户侧一期不直接发起开票申请,仅支持查询、下载、推送已开具的电子发票。 **Why this priority**: 发票申请是业务流程入口,必须优先实现。 **Independent Test**: 验证发票申请接口可接收申请,校验规则生效,申请记录正确落库。 **Acceptance Scenarios**: 1. **Given** 账单已缴费且未开票,客户开票信息完整,**When** 提交发票申请,**Then** 生成发票申请记录,返回申请单号 2. **Given** 账单未缴费,**When** 提交发票申请,**Then** 拒绝申请,提示"账单未缴费不可开票" 3. **Given** 账单已开票,**When** 提交发票申请,**Then** 拒绝申请,提示"账单已开票" 4. **Given** 开票金额超过限额,**When** 提交发票申请,**Then** 拒绝申请,提示"超过开票限额" 5. **Given** 同一原始账单被要求直接拆分为多次部分开票,**When** 提交发票申请,**Then** 拒绝直接部分开票,并提示需先按老系统口径完成拆账/分账后再分别开票 --- ### User Story 2 - 调用发票服务开票 (Priority: P1) 系统根据申请记录调用 SYS-008 发票服务完成开票,采用“异步申请 + 查询兜底”模式记录受理结果并主动查询最终状态。 **Why this priority**: 核心开票能力,必须实现与外部系统的对接。 **Independent Test**: 验证系统可正确组装开票参数,调用外部服务,处理返回结果。 **Acceptance Scenarios**: 1. **Given** 发票申请已校验通过,**When** 调用 SYS-008 开票接口,**Then** 正确传递账单、客户、税率信息 2. **Given** 外部服务返回开票成功,**When** 系统通过查询获取结果并回写状态,**Then** 更新发票状态为"已开票",记录发票代码号码 3. **Given** 外部服务返回开票失败,**When** 系统通过查询获取结果并回写状态,**Then** 更新发票状态为"开票失败",记录失败原因 4. **Given** 外部服务超时,**When** 发起查询,**Then** 可查询开票结果状态 --- ### User Story 3 - 发票结果回写与关联 (Priority: P2) 开票成功后更新账单发票状态,建立发票与账单关联,支持电子发票推送。 **Why this priority**: 保证账单与发票数据一致性,支持后续查询和下载。 **Independent Test**: 验证开票结果正确回写账单状态,关联关系可查。 **Acceptance Scenarios**: 1. **Given** 开票成功,**When** 回写结果,**Then** 账单发票状态更新,建立 invoice-charge 关联 2. **Given** 电子发票生成,**When** 客户请求下载,**Then** 返回电子发票文件或下载链接 3. **Given** 发票已关联账单,**When** 查询账单详情,**Then** 显示关联的发票信息 --- ### User Story 4 - 发票作废与红冲 (Priority: P3) 对已成功开具的发票提供后台作废与红冲入口,由 `SYS-008` 承接税控侧处理,`SYS-002` 负责发起申请、校验状态、查询补偿、结果回写与账单关联状态同步;客户侧仍仅消费有效电子发票结果,不直接发起作废或红冲。 **Why this priority**: 作废与红冲属于已开票后的后处理能力,依赖正常开票闭环,但必须在本轮二期中形成正式设计与 backend 实现入口。 **Independent Test**: 验证后台可对满足条件的成功发票发起作废或红冲;`SYS-008` 返回结果后,发票状态正确更新为 `INVALID` 或 `RED_INK`,并保留原票关联、失败原因与操作日志。 **Acceptance Scenarios**: 1. **Given** 发票状态为 `SUCCESS` 且未作废、未红冲,**When** 后台发起作废申请,**Then** 系统校验通过并调用 `SYS-008`,成功后将发票状态更新为 `INVALID` 2. **Given** 发票状态为 `SUCCESS` 且业务要求通过红字发票冲销,**When** 后台发起红冲申请,**Then** 系统校验通过并调用 `SYS-008`,成功后将发票状态更新为 `RED_INK` 3. **Given** 发票已处于 `INVALID` 或 `RED_INK`,**When** 再次发起作废或红冲,**Then** 系统拒绝重复处理并返回明确原因 4. **Given** `SYS-008` 未立即返回终态,**When** 系统执行主动查询或补偿查询,**Then** 保持处理中上下文并在取得终态后统一落账 --- ### Edge Cases - 外部发票服务不可用时的降级处理 - 重复提交同一账单的发票申请(幂等控制) - 开票过程中账单状态发生变化 - 拆账/分账后的多账单分别开票与原始单账单直接部分开票的边界判定 --- ## Requirements ### Functional Requirements - **FR-001**: 系统 MUST 提供后台发票申请接口,接收账单ID、开票类型(普票/专票)等参数,并支持单笔或批量选择已收费账单发起开票 - **FR-002**: 系统 MUST 校验账单状态(已缴费、未开票) - **FR-003**: 系统 MUST 校验客户开票信息完整性 - **FR-004**: 系统 MUST 校验开票限额 - **FR-005**: 系统 MUST 生成发票申请记录,包含申请单号、状态、时间戳 - **FR-006**: 系统 MUST 调用 SYS-008 发票服务完成开票 - **FR-007**: 系统 MUST 支持通过定时查询获取异步开票结果(轮询 SYS-008 查询接口) - **FR-008**: 系统 MUST 更新发票状态(申请中/已开票/开票失败) - **FR-009**: 系统 MUST 建立发票与账单的关联关系,并保留兼容老系统拆账/分账后分别开票及后续合并开票扩展的映射能力 - **FR-010**: 系统 MUST 支持客户侧查询、下载、推送已开具的电子发票,但一期不开放客户侧直接申请开票 - **FR-011**: 系统 MUST 记录关键操作日志(申请、查询、开票、结果回写、客户侧查询/下载/推送、作废、红冲),并保留触发来源、状态前后值与失败原因 - **FR-012**: 系统 MUST 提供后台发票作废入口,仅允许对已开票成功且未作废、未红冲的发票发起作废 - **FR-013**: 系统 MUST 提供后台发票红冲入口,仅允许对已开票成功且未红冲的发票发起红字冲销,并保留原票关联信息 - **FR-014**: 系统 MUST 通过查询补偿或结果回写统一落账作废/红冲终态,并同步更新发票状态、失败原因和账单发票关联状态 ### Key Entities - **发票申请单** (InvoiceApplication): 申请单号、账单ID、客户ID、申请金额、开票类型、状态 - **发票记录** (InvoiceRecord): 发票ID、申请单号、发票代码、发票号码、开票日期、金额、状态 - **账单发票关联** (ChargeInvoiceRelation): 账单ID、发票ID、关联类型(全额/拆账后分别开票/后续合并扩展) - **开票查询记录** (InvoiceQueryLog): 查询ID、申请单号、查询时间、查询结果、处理状态 --- ## Success Criteria ### Measurable Outcomes - **SC-001**: 发票申请接口响应时间 < 500ms(不包含外部服务调用) - **SC-002**: 发票申请校验通过率 > 95%(正常业务场景) - **SC-003**: 开票结果回写成功率 > 99% - **SC-004**: 电子发票下载成功率 > 99% - **SC-005**: 操作日志完整率 100%(所有关键操作均有日志) ### Business Outcomes - 营业收费员可通过系统完成发票申请到开票的全流程 - 客户可通过渠道查询和下载电子发票 - 财务人员可追溯发票开具全过程 --- ## Dependencies & Assumptions ### Dependencies - SYS-008 发票服务接口已定义(IF-REV-008) - 基础数据层已实现(biz_invoice, biz_cust_invoice, biz_invoice_taxrate) - 账单系统(REV-003)已实现 ### Assumptions - SYS-008 提供标准 REST 接口供调用 - 电子发票文件由 SYS-008 生成并提供下载链接 - 开票限额在配置中预定义 ### Scope Exclusions - 不实现发票的物理打印功能 - 不实现与税务局的直接对接(由 SYS-008 承接) - 不实现复杂的发票拆分合并逻辑(MVP 版本) --- ## Risks & Mitigation | 风险 | 影响 | 缓解措施 | |-----|-----|---------| | SYS-008 接口未就绪 | 无法联调 | 先实现 Mock 接口,定义好契约 | | 发票状态同步异常 | 数据不一致 | 实现补偿机制,支持对账查询 | | 并发开票冲突 | 重复开票 | 幂等控制,唯一申请单号 | --- ## Clarifications ### Session 2026-03-16 - **Q1**: SYS-008 发票服务结果获取方式 → **A**: 采用"异步+查询兜底"模式。提交开票申请后返回受理号,系统通过定时轮询查询结果接口获取开票状态(航信发票接口不支持回调,需主动查询)。 - **Q2**: 一期是否支持一张原始账单直接分多次部分开票 → **A**: 不支持对原始单账单直接任意部分金额开票;如需多张发票,沿用老系统口径,通过拆账/分账后的账单分别开票,并保留批量开票能力。 - **Q3**: 一期开放给哪些使用入口 → **A**: 采用“后台开票 + 客户侧查询下载”模式。后台营业收费员/财务人员负责申请和开具,客户侧仅支持查看、下载、推送已开票结果,不直接发起开票申请。 --- ## Next Steps 1. 在可用测试或联调环境中补充 SC-001 的响应时延采样,记录样本量、平均值、P95 与“排除外部调用”的统计口径。 2. 为 SC-002 ~ SC-004 准备样本集与统计表,将申请通过率、回写成功率、查询/下载/推送成功率沉淀到 `specs/002-rev005-invoice-flow/verification.md`。 3. 抽取 SC-005 运行态日志样本,核对操作人、客户、状态与日志内容是否与实现态矩阵一致。 4. 继续跟踪 `biz_invoice` 物理 DDL / migration 来源,并在提测前明确联调、落库与批量后处理风险。