2026-06-17-protocol-invoice-request-design.md 19 KB

采集协议单开票申请 — 设计说明

日期: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. 已纳入有效申请(待开票/已开票)的缴费明细不可再次勾选。
  5. 财务人员在「缴退费财务确认」新增「开票申请」Tab,处理待开票申请并上传发票附件。
  6. 业务人员可查看申请状态及财务回传的发票附件。

已确认的产品决策:

议题 决策
申请时开票信息(抬头、税号等) 提供完整字段区块,全部选填
财务完成开票 仅上传发票附件 + 财务备注,不填发票号/开票日期等结构化字段
申请与缴费关系 主表 + 明细子表:一次申请可含 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

一张物理发票对应一条申请主记录。

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 / 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 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 份附件;子表防重复纳入