Files
zkxw/docs/superpowers/specs/2026-10-03-cell-storage-protocol-sample-payment-analysis.md
T
zlwuandCursor e7677d1661 feat: finish P0 per-sample payment and presign order integration
Wire finance transfer list, protocol view v2 readonly payment, and mark design/plan as P0 implemented.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-03 11:25:47 +08:00

488 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 细胞存储三模块现状分析(改码参考)
**日期**:2026-10-03
**状态**:现状梳理(非改造设计)
**用途**:后续按新思路改代码前的权威对照基线;描述「当前代码实际如何工作」,而非「应当如何」。
**覆盖模块**:
| 菜单 | Hash 路由 | menuCode |
|------|-----------|----------|
| 采集协议单 | `/#/cell-storage/protocol-audit` | `biz:cellStorageProtocolAudit:list` |
| 样本信息管理 | `/#/cell-storage/sample-info` | `cell-storage:sample-info:list` |
| 缴退费财务确认及开票 | `/#/cell-storage/payment-confirm` | `biz:cellStoragePaymentConfirm:list` |
**需求权威**:`docs/中科兴皖需求V0.3.md` §2.2.6~2.2.9
**相关设计文档**(增量能力):
- `docs/superpowers/specs/2026-05-13-cell-storage-protocol-modules-design.md`(早期模块设计)
- `docs/superpowers/specs/2026-05-19-protocol-donation-convert-design.md`(转科研)
- `docs/superpowers/specs/2026-06-04-protocol-pay-mode-switch-design.md`(缴费模式切换,若存在)
- `docs/superpowers/specs/2026-06-17-protocol-invoice-request-design.md`(开票)
- `docs/superpowers/specs/2026-06-01-sample-qc-sms-notification-design.md`
- `docs/superpowers/specs/2025-05-23-storage-entry-sms-notification-design.md`
- OpenSpec:`openspec/changes/zkxw-engineering-standards/specs/api-conventions/spec.md`(细胞存储样本跨表关联键)
---
## 1. 总览:双并行业务架构
采集协议单是源头。创建后并行两条线:
```
┌──────────────────── 细胞存储 ────────────────────┐
│ 【采集协议单】 合同 + 客户 + 样本源头 + 缴费配置 │
│ │ │
│ ├──────────────┬─────────────────────────┤
│ ▼ ▼ │
│ 【样本信息管理】 【缴退费财务确认及开票】 │
│ 只读运营台+短信 财务终审+开票完成 │
│ │ ▲ │
│ ▼ │ 业务侧发起 │
│ 接收→质检→生产→入库 缴费信息 Tab / 满月待办 │
└──────────────────────────────────────────────────┘
```
| 模块 | 角色 | 本质 |
|------|------|------|
| 采集协议单 | 市场/采集专员 | 主单据 CRUD、状态变更、缴费发起、开票申请入口 |
| 样本信息管理 | 运营 | **只读查询台** + 线下短信提取/标记(不登记接收、不做质检入库操作) |
| 缴退费财务确认及开票 | 财务 | 收款/退费确认 + 开票完成 |
**菜单演进**:样本相关 Tab、缴费确认、开票已从早期「单一协议管理页」拆出;隐藏路由仍保留 `protocol`(录入表格版)、`payment-info` 等兼容入口。
---
## 2. 数据模型(以协议为中心)
```
biz_collection_protocol (1)
├── biz_protocol_party_customer (1) 当事人/客户
├── biz_protocol_collection_info (1) 采集信息
├── biz_protocol_sample (N)
│ └── biz_protocol_sample_transport
├── biz_protocol_payment (1)
│ └── biz_protocol_payment_detail (N)
├── biz_protocol_audit (N) 审核记录(不改协议状态)
├── biz_protocol_storage_cert
├── biz_protocol_fullmoon_todo
├── biz_protocol_invoice_request (N)
│ └── biz_protocol_invoice_request_item (N)
└── biz_protocol_donation_convert (0..1) 与科研捐赠互转
```
### 2.1 跨表关联硬规则(OpenSpec)
```
关联键 = tenant_no + sample_code
禁止用 collection_protocol_id / protocol_sample_id 做业务匹配
```
原因:协议编辑可能重建样本行 `id`;协议可转科研;样本编码不变。接收防重、详情回显、质检/入库 JOIN 均按此规则。
### 2.2 协议主表关键字段
| 字段 | 含义 |
|------|------|
| `protocol_no` / `protocol_no_id` | 协议号(可自动分配号池) |
| `protocol_status` | `dsx` 待生效 / `sxz` 生效中 / `ygq` 已过期 / `yzf` 已作废 / `sdfq` 属地废弃 / `zky` 转科研 |
| `protocol_attachments` / `consent_form_urls` / `health_survey_form_urls` | 附件 URL 逗号分隔 |
| `void_type` | 作废类型(字典);作废时必填 |
| `collector_user_id` | 采集专员 |
| `effective_*` / `expire_*` / `void_*` / `to_research_*` | 各状态变更时间与描述 |
无效态 `yzf` / `sdfq`:下游接收、生产列表会过滤。
---
## 3. 【采集协议单】模块
### 3.1 前端结构
```
web-admin/src/views/cell-storage/
├── protocol/ # 隐藏录入列表
│ ├── index.vue
│ ├── ProtocolFormDialog.vue # 新增/编辑大表单(权威录入)
│ ├── ProtocolViewDialog.vue
│ ├── ProtocolAuditDialog.vue
│ └── ProtocolStatusChangeDialog.vue
└── protocol-audit/ # 主管理页
├── index.vue # Pill Tab
├── ProtocolAuditTab.vue
├── PaymentInfoTab.vue # 业务侧缴费看板
├── StorageCertTab.vue
├── FullmoonTodoTab.vue # 满月保险 + 开票申请入口
└── Sample*InfoTab.vue # 被 sample-info 复用
```
API:`web-admin/src/api/collection-protocol.ts`
### 3.2 协议状态机
```
创建 → [dsx] 待生效
│ 协议生效
▼
[sxz] 生效中 ──┬── 过期 → [ygq]
├── 作废 → [yzf](须 voidType)
├── 属地废弃 → [sdfq]
└── 转科研 → [zky](convert API,建捐赠单)
```
- **审核独立**:只写 `biz_protocol_audit`(`shtg`/`shth`),不驱动 `protocol_status`。
- **协议号**:空则 `allocate_next_biz_no`;已存在不可改。
- **删除**:软删主单 + 样本 + 运输,释放样本编码占用。
### 3.3 表单保存顺序(API 硬约束)
`_persist_protocol_sub_resources`(`backend/app/api/collection_protocol.py`):
```
1. party_customer(可 upsert biz_customer)
2. collection_info
3. payment + payment_details ← 必须在样本之前
4. samples(含运输)
5. storage_cert
```
原因:缴费校验失败时不得已占用样本编码。创建失败补偿 `soft_delete_collection_protocol`。
### 3.4 ProtocolFormDialog 字段分区
| Tab | 主要字段 | payload 路径 |
|-----|----------|--------------|
| 基本信息 | 协议状态*、登记日、采集专员、备注、三类附件 | 主表字段 + attachments |
| 采集信息 | 地区、医院、医护、销售、样本/母血采集时间 | `collectionInfo` |
| 样本及运输 | 类型、编码、价格、年限、状态、特殊标记、运输 | `samples[]` + `transport` |
| 客户信息 | 关联客户、母亲/父亲/新生儿全套 | `partyCustomer` |
| 缴费信息 | 模式、合同额、分期参数、明细行 | `payment` + `paymentDetails` |
- `formRules` **仅强制** `protocolStatus`。
- 组件:`sample-transport-form-section`(样本+运输权威 UI)。
- 附件:卡片化立即上传(OpenSpec UI 附件约定以本弹窗为样例)。
**提交前前端校验顺序**:
1. el-form validate
2. 缴费模式 ↔ 有效样本数(`djfq` 仅 2 样本)
3. 新建时样本编码 `usedInProtocol` 占用拦截
4. 趸交应缴合计 ≠ 合同额 → **仅 warning,不阻断**
5. `syncFqPaymentDetailRowCount` → POST/PUT
### 3.5 缴费默认定价(样本数 × 模式)
`web-admin/src/utils/protocol-payment-presets.ts`:
| 有效样本数(≠ yzf) | 可选模式 | 合同金额示例 |
|---------------------|----------|-------------|
| 1 | `dj` / `fq` | 趸交 22880;分期 25880(6 期) |
| 2 | `dj` / `djfq` / `fq` | 趸交 37880;趸交+分期 41000;分期 44000 |
### 3.6 缴费模式切换矩阵
前端:`protocol-pay-mode-switch.ts`
后端:`protocol_pay_mode_switch.py`(`LOCKED_STATUSES` / `BLOCKING_STATUSES`)
| 从 → 到 | 条件 | 结果 |
|---------|------|------|
| 任意 | 存在进行中确认(前端还拦 `ytf`) | 阻断 |
| `dj` → `fq`/`djfq` | 已有锁定明细 | 阻断 |
| `dj` → `fq`/`djfq` | 全待缴 | 确认后按期数重建 |
| `fq`/`djfq` → `dj` | 已全部缴清 | 阻断 |
| `fq`/`djfq` → `dj` | 有已缴+未缴 | 保留 yjf,作废 dfj,新增剩余趸交 |
| `fq` ↔ `djfq` | 已有锁定 | 阻断;否则重建 |
后端保存 P0:`jfqrz`/`tfqrz` 禁止保存;分期条数=期数;比例合计 100%;期数不少于已缴最大期次。
**注意**:前端 `BLOCKING` 含 `ytf`,后端不含(已退费允许随协议保存只读明细)。
### 3.7 后端核心文件
| 路径 | 角色 |
|------|------|
| `backend/app/api/collection_protocol.py` | 协议/缴费/储存证/满月/开票 API |
| `backend/app/crud/collection_protocol.py` | 核心 CRUD(约 3900+ 行) |
| `backend/app/services/protocol_pay_mode_switch.py` | 缴费校验 |
| `backend/app/crud/collection_protocol_no.py` | 协议号池 |
| `backend/app/models/models.py` | ORM |
---
## 4. 【样本信息管理】模块
### 4.1 定位
**不是**样本录入页,**不是**接收/质检/入库操作台。
是协议样本的 **全链路查询 + 客户短信触达台**(组件复用自 `protocol-audit/`):
| Tab | 职责 | 短信 |
|-----|------|------|
| 样本采集信息 | 采集进度 | 无 |
| 样本接收信息 | 接收结论 | 有 |
| 样本质检信息 | 入库质检结论 | 有 |
| 样本入库信息 | 入库/拒入库 | 有 |
深链:`?tab=sample-collection|sample-reception|sample-qc|storage-entry`
旧路由 `sample-reception-info` / `storage-entry-info` 重定向到本页。
### 4.2 样本状态 `sample_status`
```
(空) 未采集 → ycj 已采集 → jcz 检测中 → yrk 已入库
任意 → yzf 已作废
```
- 协议表单录入默认常为 `ycj`。
- **接收登记不改** `sample_status`。
- **入库**:`store_sample` 按 `sample_code` 回写 `yrk` + `storage_in_date`。
- **取消入库**:若原为 `yrk` → 回退 `jcz`,清空 `storage_in_date`。
### 4.3 真实写库链路(本页只读展示)
接收结论 = `js` 时(`create_sample_reception`):
```
写 biz_sample_reception + 检查项
→ create_work_order_from_reception # 入库质检工单 dpz
→ create_production_work_order_from_reception
→ try_auto_dispatch_builtin_tasks… # 内置任务派发
→ create_review_from_reception # 入库放行审核 drk
```
结论 = `bj`:只写接收,不创建工单/审核。
防重:`tenant_no + reception_source + sample_code`。
实验室入库编码:结论为 `js` 时必填且租户唯一。
### 4.4 质检结论派生
`backend/app/services/qc_work_order_conclusion.py`:
```
任一任务 conclusion == btg → 工单 btg
全部任务 conclusion == tg → 工单 tg
否则 → ""(尚无结论)
```
工单状态:`dpz` → `ypz` →(全部任务 ywc)`ywc` 并落库 `wo.conclusion`。
`try_reopen_work_order`:回退 `ypz`,清空 conclusion。
质检 Tab 列表派生(`_qc_status_label`):
| 条件 | 码 |
|------|-----|
| 无工单等 | `dzj` 待质检 |
| `ypz` | `zjz` 质检中 |
| `ywc` + tg/btg | `tg` / `btg` |
| `ywc` 结论未齐 | `zjz` |
| 工单 `yzf` | `yzf` |
协议详情回显:`get_qc_info_map_for_samples` 细胞存储侧必须按 **sample_code** 关联(`trigger_source=reception`)。
### 4.5 入库双审
`submit_review`(production / qc 两侧):
- 任一方 `btg` → 聚合 `btg`
- 双方 `tg` → 聚合 `tg` → `storage_transition` = `store` → `store_sample`
- 已入库后结论变 `btg` → `unstore`
前置:生产与质检均通过且 `storage_status=drk` 才能入库。
### 4.6 短信规则
系统**不自动发送**。运营:提取文案 → 复制 → 线下发 → 标记已通知。
| Tab | 模板要点 |
|-----|----------|
| 接收 | 已于{日期}完成接收,结论:{接收/拒收} |
| 质检 | 合格 / 未通过;未完成返回「暂无短信内容…」 |
| 入库 | 已入库日期 / 审核未通过;待确认暂无内容 |
### 4.7 与缴退费关系
- **无直接 FK**;缴费挂协议不挂样本行。
- 间接:有效样本数 → 默认定价 / 可选缴费模式。
- 本页无缴费操作;查看协议弹窗内可见缴费。
### 4.8 代码锚点
| 路径 | 角色 |
|------|------|
| `web-admin/.../sample-info/index.vue` | 壳页 |
| `web-admin/.../protocol-audit/Sample*InfoTab.vue` | 四 Tab |
| `backend/app/api/protocol_sample_collection.py` | 采集列表 |
| `backend/app/api/sample_sms_notification.py` 等 | 三短信 API |
| `backend/app/crud/sample_reception.py` | 接收写库 |
| `backend/app/crud/qc_work_order.py` | 质检工单 |
| `backend/app/crud/storage_release_review.py` | 入库放行 |
---
## 5. 【缴退费财务确认及开票】模块
### 5.1 三角色协作
```
业务(缴费信息 / 满月待办) 财务(本模块)
提交缴费确认 dfj→jfqrz → 确认收款 → yjf(或退回→dfj)
提交退费确认 yjf→tfqrz → 确认退费 → ytf(或退回→yjf)
发起开票申请 → dkp → 完成开票 → ykp(上传发票)
```
前端:
- `payment-confirm/index.vue`(缴费确认)
- `RefundConfirmTab.vue`
- `InvoiceRequestTab.vue`
- 上游:`PaymentInfoTab.vue`、`FullmoonTodoTab.vue`
### 5.2 缴费明细状态机(`pay_status`)
```
dfj → jfqrz → yjf → tfqrz → ytf
↑退回 ↑退回
```
| 状态 | 业务可做 | 财务可做 | 关键写入 |
|------|----------|----------|----------|
| `dfj` | 编辑草稿、提交确认 | — | 转账方式/金额/时间/凭证 |
| `jfqrz` | — | 确认/退回 | finance_* → yjf 或 → dfj |
| `yjf` | 发起退费 | — | refund_* → tfqrz |
| `tfqrz` | — | 确认/退回 | refund_finance_* → ytf 或 → yjf |
| `ytf` | — | — | 终态 |
提交收款强制:转账方式非空 + `transfer_amount > 0`。
提交退费强制:仅 `yjf` + `refund_amount > 0`。
**注意**:批量提交收款不跑单条草稿完整性校验(假定已是合格 `dfj`)。
### 5.3 开票(独立状态机,不改 `pay_status`)
```
可开票 = pay_status=yjf AND 明细未在有效申请子表(dkp|ykp)
金额 = Σ transfer_amount(快照 line_amount / invoice_amount)
完成 = 仅 dkp + 发票附件必填 → ykp
不做 = 驳回、冲红、改缴费状态、结构化发票号/日期
```
- 多期可合并一张发票(1 主表 + N 子表)。
- 已占用期不可再选;剩余期可另起申请。
- 抬头六字段申请时 **选填**。
函数:`create_invoice_request` / `complete_invoice_request` / `list_invoice_eligible_payment_details`。
### 5.4 平行模块(勿混淆)
| 模块 | 差异 |
|------|------|
| 临床缴退费财务确认 | `biz_pilot_payment`;无开票 Tab |
| 渠道服务订单财务确认 | 另一套流水状态机 |
---
## 6. 协议转科研(`zky`)边界
入口:`research_donation.convert_from_storage`。
### 6.1 前置
- 源协议 `sxz`
- 无有效 `biz_protocol_donation_convert`
- 协议号非空
### 6.2 事务内拷贝
| 拷贝 | 不拷贝 |
|------|--------|
| 捐赠主单(同号 `donation_no`) | **缴费主表/明细** |
| party / collection | 审核 / 开票 / 储存证 / 满月 |
| samples(同 sample_code)+ transports | 接收/质检/入库审核记录 |
源协议 → `zky` + `to_research_*`;写关联表 `convert_type=storage_to_research`。
### 6.3 同码共存
配对后两边可暂共用 `sample_code`;保存唯一性对配对 ID **豁免**(`get_partner_sample_codes`)。
之后两边可各自改号,**不自动同步**;关联表 `biz_no` 只留转移快照。
产品含义:转科研是合同形态切换,**不是财务结算迁移**;退费/开票仍对原协议缴费明细。
---
## 7. 端到端写库总图
```
协议保存
客户 → 采集 →【缴费校验+明细】→ 样本编码占用 → 储存证
│ │
▼ ▼
缴费明细状态机 sample_code
dfj→jfqrz→yjf→… │
│ ▼
│ 接收(js) → QC + 生产 + 入库审核(drk)
│ │
▼ ▼ 双审 tg
财务确认 / 开票(dkp→ykp) store → sample_status=yrk
转科研(sxz→zky):新捐赠同号同码;缴费不跟走
```
---
## 8. 实现时注意点(改码易踩坑)
1. **跨表只认 `tenant_no + sample_code`**,勿用样本行 id 做业务匹配。
2. **缴费先于样本**保存;创建失败要补偿软删。
3. **审核不改协议状态**;财务确认不改协议状态;开票不改 `pay_status`。
4. **前端/后端缴费阻断集合不完全一致**(`ytf`)。
5. **批量提交收款**校验弱于单条。
6. **转科研不迁缴费**;样本下游历史仍按编码可查。
7. **管理端色值**用 Fresh Mint(`#10b981`),勿混小程序三墙绿。
8. 平行「缴退费财务确认」菜单(临床/渠道)表模型不同。
---
## 9. 关键代码地图(速查)
### 前端
| 模块 | 路径 |
|------|------|
| 协议主页 | `web-admin/src/views/cell-storage/protocol-audit/` |
| 协议表单 | `web-admin/src/views/cell-storage/protocol/ProtocolFormDialog.vue` |
| 样本信息 | `web-admin/src/views/cell-storage/sample-info/index.vue` |
| 财务确认 | `web-admin/src/views/cell-storage/payment-confirm/` |
| API | `web-admin/src/api/collection-protocol.ts` 等 |
| 缴费工具 | `web-admin/src/utils/protocol-payment-*.ts`、`protocol-pay-mode-switch.ts` |
| 路由 | `web-admin/src/router/modules/sqxp.ts` |
### 后端
| 模块 | 路径 |
|------|------|
| 协议 API/CRUD | `backend/app/api/collection_protocol.py`、`crud/collection_protocol.py` |
| 样本采集列表 | `api/protocol_sample_collection.py`、`crud/protocol_sample_collection.py` |
| 接收 | `crud/sample_reception.py` |
| 质检 | `crud/qc_work_order.py`、`services/qc_work_order_conclusion.py` |
| 入库 | `crud/storage_release_review.py` |
| 短信 | `crud/sample_sms_notification.py`、`qc_sms_notification.py`、`storage_entry_sms_notification.py` |
| 转科研 | `crud/research_donation.py`(`convert_from_storage`)、`crud/protocol_donation_convert.py` |
| 缴费模式 | `services/protocol_pay_mode_switch.py` |
---
## 10. 后续改码建议用法
1. 先在本文确认「现状行为」与「要改的边界」。
2. 若行为变更跨状态机/表结构,另开 OpenSpec change 或 `docs/superpowers/specs/YYYY-MM-DD-*-design.md`。
3. 改样本下游关联时,对照 §2.1 与 OpenSpec api-conventions。
4. 改缴费/开票时,对照 §5 状态矩阵与「明确不做」列表,避免无意引入驳回/冲红等本期未实现能力。
5. 改协议表单字段时,同步检查:Create/Update Body、`_row_to_dict`、前端 `formData`(见 `docs/dev-field-consistency.md`)。
---
**文档维护**:若实现偏离本文描述,请同步更新本节或标注「已失效段落」,避免后续改码基于过时基线。