19 KiB
采集协议单开票申请 — 设计说明
日期: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. 背景与目标
细胞存储「采集协议单管理」→「满月待办管理」目前已有保险办理能力。客户希望对已财务确认的缴费发起开票申请,由财务集中开票并上传发票附件,业务侧可查看反馈。
目标:
- 业务人员在满月待办中对当前协议单下已缴费明细发起开票申请。
- 开票金额 = 所选缴费明细实缴金额之和(
Σ transfer_amount),明细行金额只读,合计只读。 - 多期可合并为一张发票:一次申请可勾选多期,生成 1 条开票申请;财务上传 1 份发票附件覆盖该申请下所有期数。
- 已纳入有效申请(待开票/已开票)的缴费明细不可再次勾选。
- 财务人员在「缴退费财务确认」新增「开票申请」Tab,处理待开票申请并上传发票附件。
- 业务人员可查看申请状态及财务回传的发票附件。
已确认的产品决策:
| 议题 | 决策 |
|---|---|
| 申请时开票信息(抬头、税号等) | 提供完整字段区块,全部选填 |
| 财务完成开票 | 仅上传发票附件 + 财务备注,不填发票号/开票日期等结构化字段 |
| 申请与缴费关系 | 主表 + 明细子表:一次申请可含 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 可开票缴费明细条件(后端强校验)
同时满足:
payment_detail.collection_protocol_id= 当前协议单pay_status = yjf- 该
payment_detail_id未出现在任何有效申请的子表biz_protocol_invoice_request_item中 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
一张物理发票对应一条申请主记录。
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
申请与缴费明细的多对一关联;每条缴费明细在有效申请中最多出现一次。
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/ykpkeyword:母亲姓名、身份证号、新生儿姓名、新生儿身份证号(与缴退费财务确认一致)create_time范围(可选)
6.2 列表返回扩展字段
关联协议单与客户信息,供列表/卡片展示:
motherName、motherIdCard、newbornName、protocolNoinvoiceAmount(合计)、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):
- 可开票缴费明细(
el-table多选勾选列;已开过票 / 非yjf的行不可选) - 已选汇总(
已选 N 条 · 合计 ¥xxx,只读) - 开票信息 6 字段(均选填)
- 申请备注
底栏:取消 / 提交申请(生成 1 条合并申请)
列表列(可选增强):
- 「开票进度」如
1/3 已开票,非必须首期实现
7.3 缴退费财务确认
payment-confirm/index.vue:
- Tab 顺序:缴费确认 | 退费确认 | 开票申请
- 新增
invoicePendingBadge,挂载时拉取pending-count <InvoiceRequestTab v-if="mainTab === 'invoice'" @action-done="loadTabPendingCounts" />
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 份附件;子表防重复纳入 |