# 采集协议单开票申请 — 设计说明 **日期**:2026-06-17 **状态**:已实现 **需求来源**:客户在「满月待办管理」中新增开票申请;财务在「缴退费财务确认」中处理并回传发票。 **关联页面**: - `web-admin/src/views/cell-storage/protocol-audit/FullmoonTodoTab.vue`(业务发起 + 查看) - `web-admin/src/views/cell-storage/payment-confirm/index.vue`(新增 Tab) - 新建:`web-admin/src/views/cell-storage/payment-confirm/InvoiceRequestTab.vue` **参考实现**: - 满月待办办理保险:`FullmoonTodoTab.vue` - 财务退费确认:`RefundConfirmTab.vue` - 财务附件上传:`ProtocolFinanceConfirmFields` + `finance-attachments.ts` - 渠道/体检机构开票信息字段:`biz_channel_invoice` / `biz_checkup_institution_invoice` --- ## 1. 背景与目标 细胞存储「采集协议单管理」→「满月待办管理」目前已有保险办理能力。客户希望对**已财务确认的缴费**发起开票申请,由财务集中开票并上传发票附件,业务侧可查看反馈。 **目标**: 1. 业务人员在满月待办中对当前协议单下**已缴费**明细发起开票申请。 2. 开票金额 = 所选缴费明细**实缴金额之和**(`Σ transfer_amount`),明细行金额只读,合计只读。 3. **多期可合并为一张发票**:一次申请可勾选多期,生成 **1 条开票申请**;财务上传 **1 份**发票附件覆盖该申请下所有期数。 4. 已纳入有效申请(待开票/已开票)的缴费明细不可再次勾选。 4. 财务人员在「缴退费财务确认」新增「开票申请」Tab,处理待开票申请并上传发票附件。 5. 业务人员可查看申请状态及财务回传的发票附件。 **已确认的产品决策**: | 议题 | 决策 | |------|------| | 申请时开票信息(抬头、税号等) | 提供完整字段区块,**全部选填** | | 财务完成开票 | **仅上传发票附件 + 财务备注**,不填发票号/开票日期等结构化字段 | | 申请与缴费关系 | **主表 + 明细子表**:一次申请可含 1~N 条缴费明细,**合并开一张发票**;子表保证每条明细不重复纳入 | | 财务驳回流程 | **本期不做**;仅 `待开票` → `已开票` 两态 | --- ## 2. 现有系统锚点 ### 2.1 数据模型 ``` biz_collection_protocol(采集协议单) └── biz_protocol_payment(缴费主表) └── biz_protocol_payment_detail(缴费明细) └── biz_protocol_invoice_request_item(开票申请明细,N:1) └── biz_protocol_invoice_request(开票申请主表,一张发票) ``` ### 2.2 页面入口 | 角色 | 菜单路径 | 现状 | |------|----------|------| | 业务 | 细胞存储 → 采集协议单管理 → 满月待办管理 | 列表 + 办理保险 | | 财务 | 细胞存储 → 缴退费财务确认 | Tab:缴费确认 \| 退费确认 | ### 2.3 与需求文档关系 `docs/中科兴皖需求V0.3.md` §2.2 已描述缴费确认、退费确认、满月保险,**未描述开票申请**。本设计为新增能力,不改变既有缴退费状态机。 --- ## 3. 状态与流转 ### 3.1 开票申请状态(新) | 状态码 | 含义 | 触发 | |--------|------|------| | `dkp` | 待开票 | 业务提交开票申请 | | `ykp` | 已开票 | 财务上传发票附件并确认 | ### 3.2 与缴费状态关系 - **可申请**:`biz_protocol_payment_detail.pay_status = yjf`(已缴费) - **不可申请**:`dfj`、`jfqrz`、`tfqrz`、`ytf`,或该明细已出现在任一有效申请的**子表**中(主表 `del_flag=0` 且 `status IN (dkp, ykp)`) - 缴费状态流转**不因开票申请而改变**;开票是独立子流程 ### 3.3 流程图 ``` 业务(满月待办 · 同一弹窗) → 多选 1~N 条可开票缴费明细 + 选填开票信息 → 一次提交 → 生成 1 条 dkp 申请(含 N 条明细子表) → 主表 invoice_amount = 各期实缴之和 财务(缴退费财务确认 · 开票申请 Tab) → 待开票列表(一条申请对应一张待开发票) → 查看申请内各期明细 + 合计金额 → 上传 1 份发票附件 + 财务备注 → status=ykp ``` --- ## 4. 业务规则 ### 4.1 可开票缴费明细条件(后端强校验) 同时满足: 1. `payment_detail.collection_protocol_id` = 当前协议单 2. `pay_status = yjf` 3. 该 `payment_detail_id` 未出现在任何有效申请的子表 `biz_protocol_invoice_request_item` 中 4. `tenant_no` 与当前用户租户一致 ### 4.2 开票金额 - **明细行**:各期 `transfer_amount`,申请时只读 - **申请合计**:`invoice_amount` = 所选明细 `transfer_amount` 之和,主表只读展示 - 子表每行写入 `line_amount` 快照;主表 `invoice_amount` = Σ `line_amount` - 后端校验:主表金额必须等于子表行金额之和,且每行等于对应明细当前 `transfer_amount` ### 4.3 分期、趸交与合并开票 | 场景 | 行为 | |------|------| | 趸交(dj) | 1 条 yjf 明细 → 1 条申请、1 行子表 | | 分期(fq) | 可只开第 1 期;也可**多选多期一次提交**,合并为 **1 条申请、1 张发票** | | 一次多选 N 期 | 1 条主表申请 + N 行子表;财务待办 **1 条**;发票附件 **1 份** | | 已开过票的期数 | 不可再勾选(已在其他有效申请的子表中) | | 剩余期数 | 可另起一次新申请(例如第 1~2 期已开,第 3 期单独再申请) | 弹窗展示:**已选 N 期 · 合计开票金额 ¥xxx**。 ### 4.4 申请侧表单字段 **必选**: - `payment_detail_ids`(多选,至少 1 条;`el-table` 勾选列) **选填**(写入主表申请): | 展示标签 | 字段 | 说明 | |----------|------|------| | 发票抬头 | `invoice_title` | 对齐渠道开票信息 | | 税号 | `tax_no` | | | 单位地址 | `company_address` | | | 电话 | `company_phone` | | | 开户银行 | `bank_name` | | | 银行账号 | `bank_account` | | | 申请备注 | `apply_remark` | | 弹窗中同步只读展示:客户姓名、协议号、期数、应缴/实缴金额、转账时间、转账方式。 ### 4.5 财务处理字段 **必填**: - `invoice_attachments`(至少 1 个附件,逗号分隔 MinIO URL) **选填**: - `finance_remark` **不提供**:发票号码、开票日期、发票类型等输入项。 ### 4.6 退费交叉(本期约定) - `ytf`(已退费)明细**不可**出现在可开票列表 - 若某期 `ykp` 后发生退费:本期**不实现**发票冲红/作废;由线下处理(范围外) --- ## 5. 数据库设计 ### 5.1 主表 `biz_protocol_invoice_request` 一张物理发票对应一条申请主记录。 ```sql CREATE TABLE `biz_protocol_invoice_request` ( `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', `collection_protocol_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '采集协议单ID', `invoice_amount` decimal(18,2) NOT NULL COMMENT '开票合计金额(子表实缴之和)', `invoice_title` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '发票抬头', `tax_no` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '税号', `company_address` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '单位地址', `company_phone` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '电话', `bank_name` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '开户银行', `bank_account` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '银行账号', `apply_remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '申请备注', `invoice_attachments` text COLLATE utf8mb4_unicode_ci COMMENT '财务上传发票附件逗号分隔URL', `finance_remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '财务备注', `status` varchar(8) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'dkp' COMMENT 'dkp待开票 ykp已开票', `finance_handle_time` datetime DEFAULT NULL COMMENT '财务处理时间', `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', `order_num` int(11) DEFAULT NULL COMMENT '排序号', `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', PRIMARY KEY (`id`), KEY `idx_inv_req_protocol` (`collection_protocol_id`,`del_flag`), KEY `idx_inv_req_status` (`tenant_no`,`status`,`del_flag`,`create_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='采集协议单开票申请主表'; ``` ### 5.2 子表 `biz_protocol_invoice_request_item` 申请与缴费明细的多对一关联;**每条缴费明细在有效申请中最多出现一次**。 ```sql CREATE TABLE `biz_protocol_invoice_request_item` ( `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID', `invoice_request_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '开票申请主表ID', `payment_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '缴费主表ID', `payment_detail_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '缴费明细ID', `installment_no` int(11) DEFAULT NULL COMMENT '期数快照,趸交为空', `line_amount` decimal(18,2) NOT NULL COMMENT '该行开票金额(实缴快照)', `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号', `order_num` int(11) DEFAULT NULL COMMENT '排序号', `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id', `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id', `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除', PRIMARY KEY (`id`), KEY `idx_inv_item_req` (`invoice_request_id`,`del_flag`), KEY `idx_inv_item_detail` (`payment_detail_id`,`del_flag`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='采集协议单开票申请明细'; ``` **唯一性**:创建时校验 `payment_detail_id` 不得存在于任何有效主表(`status IN (dkp,ykp)`, `del_flag=0`)下的有效子表行中。 ### 5.3 查找与对账 - 按协议单查历史:主表 `collection_protocol_id` - 判断某期是否已开票:子表 `payment_detail_id` + 关联主表状态 - 列表展示 `installmentSummary`(如「第1、2、3期」)及 `itemCount`,详情展开子表明细 ### 5.4 字段顺序 遵循 `database-conventions`:业务字段 → 公共字段。 --- ## 6. 后端 API 路由注册:`app/api/collection_protocol.py`(或独立 `protocol_invoice.py`,与现有缴退费接口同模块)。 | 方法 | 路径 | 说明 | |------|------|------| | GET | `/web/admin/collection-protocols/{protocolId}/invoice-eligible-payments` | 可开票缴费明细列表 | | GET | `/web/admin/collection-protocols/{protocolId}/invoice-requests` | 某协议单开票申请历史 | | POST | `/web/admin/collection-protocols/{protocolId}/invoice-requests` | 提交开票申请(`paymentDetailIds[]` → 1 主表 + N 子表) | | GET | `/web/admin/protocol-invoice-requests` | 财务列表(`status=dkp\|ykp`,分页筛选) | | GET | `/web/admin/protocol-invoice-requests/pending-count` | 待开票数量(Tab 角标) | | PUT | `/web/admin/protocol-invoice-requests/{id}/complete` | 财务确认已开票 | ### 6.1 列表筛选(财务) - `status`:`dkp` / `ykp` - `keyword`:母亲姓名、身份证号、新生儿姓名、新生儿身份证号(与缴退费财务确认一致) - `create_time` 范围(可选) ### 6.2 列表返回扩展字段 关联协议单与客户信息,供列表/卡片展示: - `motherName`、`motherIdCard`、`newbornName`、`protocolNo` - `invoiceAmount`(合计)、`itemCount`、`installmentSummary`(如「第1、2、3期」) - `items[]`:`installmentNo`、`lineAmount`、`transferTime`、`transferType` 等 - `status`、`createTime`、`financeHandleTime` - 申请侧开票信息、发票附件列表(已开票时) ### 6.3 校验清单 - 租户隔离:`tenant_no` 来自当前用户 - 数据权限:`apply_data_scope` 对齐 `list_receipt_confirmed_details` - 创建:`paymentDetailIds` 非空、无重复;每条明细满足 §4.1;同事务插入 1 主表 + N 子表;金额校验见 §4.2 - 完成:仅 `dkp` 可完成;`invoice_attachments` 非空 ### 6.4 `_row_to_dict` 与 Schema - CRUD `_invoice_request_to_dict` + `_invoice_request_item_to_dict`;详情/列表返回嵌套 `items` - CreateBody / CompleteBody Pydantic 与前端 `formData` 对齐 - 完成后运行 `python scripts/check_field_consistency.py` --- ## 7. 前端设计 ### 7.1 界面模式 | 区域 | 模式 | 参考 | |------|------|------| | 满月待办开票 | P06 复杂列表 + 弹窗 | `FullmoonTodoTab.vue` | | 财务开票 Tab | P21 财务确认双区(待办 + 已办) | `RefundConfirmTab.vue` | ### 7.2 满月待办 `FullmoonTodoTab.vue` **操作列**(`fixed="right"`,`min-width` 增至约 320px): - 查看(已有) - 办理保险(已有) - **开票申请**(单一入口,**始终可点**;申请与查看均在同一弹窗完成) > **交互决策(2026-06-17)**:不单独提供「查看开票」按钮。用户只点「开票申请」,弹窗内上半区查看历史、下半区发起新申请。 **统一弹窗「开票申请」**(`dialog-form-layout`,建议 `width` 约 800~900px): 打开时并行请求: - `GET .../invoice-requests`(历史) - `GET .../invoice-eligible-payments`(可新开票的缴费明细) 弹窗自上而下分三块: | 区块 | 内容 | 说明 | |------|------|------| | **摘要** | 只读:客户姓名、协议号、新生儿等 | 固定顶部 | | **申请记录** | `el-table` | 申请时间、**包含期数**(`installmentSummary`)、**合计金额**、状态 Tag、财务处理时间;`ykp` 展示发票附件;可展开查看各行期数/金额 | | **新增申请** | 表单区 | 有可开票明细时展示;否则展示提示「暂无可开票缴费记录」并隐藏提交 | **新增申请表单**(`label-position="top"`、`form-grid-2`): 1. 可开票缴费明细(`el-table` **多选**勾选列;已开过票 / 非 `yjf` 的行不可选) 2. 已选汇总(`已选 N 条 · 合计 ¥xxx`,只读) 3. 开票信息 6 字段(均选填) 4. 申请备注 **底栏**:取消 / 提交申请(生成 **1 条**合并申请) **列表列(可选增强)**: - 「开票进度」如 `1/3 已开票`,非必须首期实现 ### 7.3 缴退费财务确认 **`payment-confirm/index.vue`**: - Tab 顺序:缴费确认 | 退费确认 | **开票申请** - 新增 `invoicePendingBadge`,挂载时拉取 `pending-count` - `` **`InvoiceRequestTab.vue`**(结构对齐 `RefundConfirmTab.vue`): | 区块 | 内容 | |------|------| | 待开票 | 卡片网格 + 关键词筛选 + 分页;操作「处理开票」 | | 已开票 | 表格 + 筛选 + 分页;操作「查看」 | **处理弹窗**: - 只读:客户信息、协议号、**子表明细表格**(期数、各行金额)、**合计金额**、申请时间、开票信息、申请备注 - 可编辑:发票附件(1 份,覆盖该申请下所有期数)、财务备注 - 底栏:取消 / 确认已开票 **查看弹窗**(已开票):只读展示全部信息 + 附件。 ### 7.4 UI 规范 - 状态 Tag:`dkp` → `warning` 待开票,`ykp` → `success` 已开票 - 按钮须带 `@element-plus/icons-vue` 图标 - 遮罩无模糊:`modal-class="protocol-dialog-mask"` - 金额展示:`formatMoneyDisplay` + `¥` 前缀 ### 7.5 API 模块 `web-admin/src/api/collection-protocol.ts` 新增对应 `fetch*` 函数。 --- ## 8. 权限与菜单 - 业务:复用满月待办列表权限 `cell-storage:fullmoon-todo:list`;若需细粒度可增加 `cell-storage:fullmoon-todo:invoice` - 财务:缴退费财务确认页面权限下增加开票处理;建议 `cell-storage:payment-confirm:invoice` - 菜单:不新增一级菜单,仅在现有页面扩展 Tab / 操作 --- ## 9. 测试要点 | 场景 | 预期 | |------|------| | 趸交已缴费 | 可发起 1 次申请,金额=实缴 | | 分期 3 期均已缴费 | 可一次多选 3 期 → **1 条**申请、合计金额=三期实缴之和、财务 **1 份**附件 | | 分期部分已开 | 第 1~2 期已在申请 A 中,则仅第 3 期可勾选并另起申请 B | | 多期合并 | 子表 3 行、主表 1 行;`installmentSummary`=「第1、2、3期」 | | 同一明细重复申请 | 第二次提交失败 | | 未缴费 / 确认中明细 | 不出现在可开票列表 | | 已退费明细 | 不可申请 | | 财务无附件提交 | 校验失败 | | 财务完成后业务查看 | 可见附件与备注 | | 租户 / 数据权限 | 跨租户不可见 | --- ## 10. 实施文件清单(概要) | 层级 | 文件 | |------|------| | SQL | `docs/sql/protocol_invoice_request.sql`(主表 + 子表) | | Backend Model | `backend/app/models/models.py` | | Backend CRUD | `backend/app/crud/collection_protocol.py` 或新文件 | | Backend API | `backend/app/api/collection_protocol.py` | | Backend Schema | `backend/app/schemas/collection_protocol.py` | | Frontend API | `web-admin/src/api/collection-protocol.ts` | | Frontend 业务 | `FullmoonTodoTab.vue` | | Frontend 财务 | `InvoiceRequestTab.vue`、`payment-confirm/index.vue` | 完成后执行 `./scripts/update-graph.sh frontend` 与 `backend`(如有新模块)。 --- ## 11. 范围外(YAGNI) - 财务驳回 / 重新申请 - 发票号码、开票日期结构化字段 - 已开票后退费的发票冲红 - 小程序端开票申请 - 消息/短信通知 --- ## 12. 审批记录 | 日期 | 决策 | |------|------| | 2026-06-17 | 开票信息字段提供但全部选填 | | 2026-06-17 | 财务仅上传附件 + 备注 | | 2026-06-17 | 两态流转 dkp/ykp;满月待办单一「开票申请」弹窗(申请+查看) | | 2026-06-17 | 缴费明细支持多选;一次提交生成 **1 条申请** | | 2026-06-17 | **多期合并一张发票**:主表+子表;财务 1 份附件;子表防重复纳入 |