13 KiB
细胞存储 ⇄ 科研捐赠 转移改造方案
日期:2026-05-19
状态:已实施(2026-05-19)
关联模块:采集协议单管理、捐赠协议单管理
1. 背景与问题
1.1 业务场景
- 协议转科研:细胞存储采集协议(
sxz)→ 新建科研捐赠单,源协议状态zky。 - 转细胞存储:科研捐赠单(
sxz)→ 新建采集协议单,源捐赠状态zckc。
产品约定:
- 转移时 协议号与样本编码与源单一致(同一业务在不同模块各一张单)。
- 转移后用户 可能单独调整 采集协议号或捐赠协议号(两边不必长期同号)。
- 二次编辑保存时不应误报「协议号已存在」「样本编码已被占用」。
1.2 现状问题
| 问题 | 原因 |
|---|---|
转移后协议号为 KY… / XC… 临时串 |
convert_from_storage / convert_to_storage 临时生成编号 |
| 二次保存报样本编码已占用 | 全局唯一校验未识别「转移配对单」 |
| 同号需求与跨表唯一校验冲突 | protocol_no 与 donation_no 互斥,未对配对单豁免 |
2. 改造目标
| # | 目标 |
|---|---|
| G1 | 转移瞬间:新单 protocol_no / donation_no = 源单展示号;样本 sample_code / sample_code_id 原样拷贝 |
| G2 | 建立 转移关联 持久化记录(中间表),供保存/改号校验查询 |
| G3 | 样本编码、协议号唯一性:对 配对单据 ID 豁免,不依赖编号字符串是否相同 |
| G4 | 转移后允许用户 独立修改 本单协议号/捐赠号,不自动同步 对端 |
| G5 | 非配对单据仍严格执行全局唯一,不影响手工新建/审核等流程 |
3. 方案概述
flowchart TB
subgraph transfer [转移事务]
A[源单状态变更 zky/zckc]
B[新建目标单 同号同码]
C[写入关联表 biz_protocol_donation_convert]
end
subgraph save [编辑保存]
D[根据当前单 ID 查配对 protocol_id / donation_id]
E[协议号校验 排除配对单]
F[样本编码校验 排除配对单样本行]
end
transfer --> save
核心原则:配对关系由 关联表 + 双方主键 ID 锁定;biz_no 仅记录转移完成时的初始编号(审计),不参与 豁免判断。
4. 数据库设计
4.1 新建表 biz_protocol_donation_convert
脚本路径:docs/sql/biz_protocol_donation_convert.sql
| 字段 | 类型 | 说明 |
|---|---|---|
id |
VARCHAR(64) | 主键 UUID |
convert_type |
VARCHAR(32) | storage_to_research / research_to_storage |
protocol_id |
VARCHAR(64) | biz_collection_protocol.id |
research_donation_id |
VARCHAR(64) | biz_research_donation.id |
biz_no |
VARCHAR(64) | 转移完成时 的共用编号快照(不随改号自动更新) |
tenant_no |
VARCHAR(64) | 租户 |
| 公共字段 | — | order_num, remark, create_by, create_dept_id, create_time, update_by, update_time, del_flag |
索引与约束(建议)
UNIQUE (protocol_id):同一采集协议仅允许一条有效转移关联(应用层保证del_flag=0唯一)。UNIQUE (research_donation_id):同一科研捐赠仅允许一条有效转移关联。KEY (tenant_no, biz_no, del_flag):按初始编号排查。
不新建 主表字段为一期必选项;科研侧已有 source_type、source_protocol_id 可继续用于列表筛选与展示。
4.2 与现有表关系
biz_collection_protocol (1) ──< biz_protocol_donation_convert >── (1) biz_research_donation
│ │
├── biz_protocol_sample ├── biz_research_donation_sample
一对一:一次转移产生一对主单 + 一条关联记录。
5. 转移业务规则
5.1 协议转科研(convert_from_storage)
前置
- 源协议存在、
tenant_no匹配、del_flag=0。 - 源协议状态 =
sxz(生效中)。 - 该
protocol_id尚未存在有效关联记录。
事务内操作
| 步骤 | 操作 |
|---|---|
| 1 | 新建 biz_research_donation:donation_no = proto.protocol_no,donation_status=dsx,source_type=from_storage,source_protocol_id=protocol_id |
| 2 | 复制 party / collection / samples(+transport),样本编码与源一致 |
| 3 | 源协议 protocol_status=zky,写 to_research_time / to_research_desc |
| 4 | 插入关联表:convert_type=storage_to_research,biz_no=proto.protocol_no |
删除:KY + 时间戳 + UUID 临时号逻辑。
5.2 转细胞存储(convert_to_storage)
前置
- 源捐赠存在、
sxz、无有效关联。
事务内操作
| 步骤 | 操作 |
|---|---|
| 1 | 新建 biz_collection_protocol:protocol_no = donation.donation_no,protocol_status=dsx;protocol_no_id 可按 biz_no 反查号池(可选) |
| 2 | 复制子表,样本同码;价格字段置空 |
| 3 | 源捐赠 donation_status=zckc,写 to_storage_time / to_storage_desc |
| 4 | 插入关联表:convert_type=research_to_storage,biz_no=donation.donation_no |
删除:XC + 时间戳临时号逻辑。
5.3 转移后改号(用户可能调整)
| 规则 | 说明 |
|---|---|
| 允许改号 | 采集协议可改 protocol_no;科研捐赠可改 donation_no(需确认前端是否已暴露字段,后端须支持校验) |
| 不同步对端 | 改 A 单号 不自动修改 B 单号(方案 A) |
关联表 biz_no |
保留转移快照,不随改号更新 |
| 可选 UI 提示 | 改号成功后提示「配对单据当前编号为 xxx」(二期) |
6. 校验规则改造
6.1 公共能力(新建)
位置建议:backend/app/crud/protocol_donation_convert.py(或合入 research_donation.py)
| 函数 | 说明 |
|---|---|
create_convert_link(db, ...) |
转移事务内写入关联 |
get_convert_partner(db, tenant_no, *, protocol_id=None, donation_id=None) |
返回 { protocol_id, research_donation_id, biz_no, convert_type } 或 None |
get_partner_sample_codes(db, tenant_no, protocol_id, donation_id) |
配对单上所有 del_flag=0 的 sample_code 集合 |
6.2 样本编码唯一性
改造:collection_protocol.validate_sample_codes_not_used
占用集合 = 全租户 采集样本表 ∪ 科研捐赠样本表(del_flag=0)
占用集合 -= 配对 protocol_id 下样本编码
占用集合 -= 配对 research_donation_id 下样本编码
若 待保存编码 ∩ 占用集合 非空 → 报错
调用点
collection_protocol.save_samples:传入当前collection_protocol_id,查 partner 后排除。research_donation._create_samples:传入当前research_donation_id,查 partner 后排除。
本单 upsert 会先软删旧样本行,排除逻辑必须基于 配对单 ID,与双方编号是否一致无关。
6.3 协议号 / 捐赠号跨表唯一性
改造:create_collection_protocol、update_collection_protocol、create_donation、update_donation(若支持改 donation_no)。
判断「编号是否冲突」时:
冲突对象 = 其他采集协议.protocol_no(排除当前 protocol_id)
∪ 其他科研捐赠.donation_no(排除当前 donation_id)
若存在 convert 配对:
- 更新采集协议时:再排除 配对 research_donation_id 对应行的 donation_no
- 更新科研捐赠时:再排除 配对 protocol_id 对应行的 protocol_no
仅当 新编号 落在「冲突对象」中时报错
说明:配对单占用 整行单据 豁免,不要求新号与对端当前号相同。例如协议改为 A001、捐赠仍为 A002,只要无第三方占用,均应通过。
6.4 非转移场景
- 手工新建、无关联记录:保持现有严格唯一。
- 批量转移失败策略:维持现有「逐条调用、部分成功」行为。
7. 后端改动清单
| 序号 | 文件 | 改动要点 |
|---|---|---|
| 1 | docs/sql/biz_protocol_donation_convert.sql |
建表 |
| 2 | backend/app/models/research_donation.py |
BizProtocolDonationConvert |
| 3 | backend/app/models/__init__.py |
导出 Model |
| 4 | backend/app/crud/protocol_donation_convert.py |
关联 CRUD + 查询(新建) |
| 5 | backend/app/crud/research_donation.py |
convert_* 同号同码 + 写关联;_create_samples 传排除参数;update_donation 支持 donation_no 改号校验 |
| 6 | backend/app/crud/collection_protocol.py |
validate_sample_codes_not_used、save_samples、create/update 协议号校验 |
| 7 | backend/app/api/research_donation.py |
无路由变更(可选详情返回 convertPartner) |
API:不新增对外接口;现有:
POST /web/admin/research-donations/convert-from-storagePOST /web/admin/research-donations/{id}/convert-to-storage
8. 前端改动清单
8.1 一期(随然后端联调)
| 项 | 改动 |
|---|---|
| 转移确认文案 | 说明「将创建新单,初始协议号与样本编码与源单一致」 |
| 编辑表单 | 转移生成的新单:协议号/捐赠号保持可编辑(勿做只读) |
| 保存逻辑 | 无改动,依赖后端豁免 |
8.2 二期(体验增强)
| 项 | 改动 |
|---|---|
| 查看弹窗 | 展示「转移关联」:对端单据类型、编号、跳转 |
| 改号提示 | 保存后若与对端编号不一致,轻提示对端当前号 |
9. 历史数据
| 类型 | 处理 |
|---|---|
已用 KY…/XC… 生成的转移单 |
脚本按 source_protocol_id、to_research_desc、remark 尝试配对并补关联表;编号是否改为源单同号需 业务确认后 再批量更新 |
| 无法配对 | 保持现状,仅新转移走新逻辑 |
脚本建议:backend/scripts/backfill_protocol_donation_convert.py(可选)。
10. 实施步骤
| 阶段 | 内容 | 产出 |
|---|---|---|
| P1 | SQL + Model + 关联 CRUD | 表可建、可查询配对 |
| P2 | 改造 convert_from_storage / convert_to_storage |
转移同号同码 + 写关联 |
| P3 | 样本编码校验 + save_samples / _create_samples |
二次编辑样本保存通过 |
| P4 | 协议号/捐赠号 create & update 豁免 | 改号不误报、可独立改号 |
| P5 | 联调 + 回归 + 确认文案 | 测试清单通过 |
| P6 | 历史数据脚本(可选) | 补关联记录 |
11. 测试用例
11.1 转移
| # | 步骤 | 预期 |
|---|---|---|
| T1 | sxz 协议转科研 |
捐赠号=协议号;样本码一致;关联表 1 条;源单 zky |
| T2 | sxz 捐赠转存储 |
协议号=捐赠号;样本码一致;关联表 1 条;源单 zckc |
| T3 | 非 sxz 转移 |
400 业务错误 |
| T4 | 已转移协议再次转科研 | 失败 |
11.2 二次保存(样本)
| # | 步骤 | 预期 |
|---|---|---|
| T5 | 转移后不改编码,直接保存 | 成功 |
| T6 | 修改非编码字段后保存 | 成功 |
11.3 改号
| # | 步骤 | 预期 |
|---|---|---|
| T7 | 只改采集协议号 | 成功;捐赠号可不变 |
| T8 | 只改捐赠号 | 成功 |
| T9 | 改为第三方已占用号 | 失败 |
| T10 | 改为配对单当前持有的号 | 成功(豁免) |
11.4 回归
| # | 步骤 | 预期 |
|---|---|---|
| T11 | 无关联手工新建协议+样本 | 重复编码仍失败 |
| T12 | 审核、状态变更、删除 | 行为不变 |
12. 风险与待定项
| 项 | 说明 | 建议 |
|---|---|---|
| 号池下发状态 | 转移同号后 biz_collection_protocol_no 是否标记已下发 |
二期与基础数据模块对齐 |
protocol_no_id |
转存储时是否回填号池 ID | 可选:按 biz_no 查池 |
| 链式转移 | 同一业务多次互转 | 当前按 一对一 设计;若需支持需单独立项 |
| 软删一方 | 另一方编辑是否仍豁免 | 建议:仍豁免(关联保留,占用仅计 del_flag=0) |
13. 已确认产品决策
| 决策 | 结论 |
|---|---|
| 转移初始编号 | 与源单相同 |
| 转移初始样本编码 | 与源单相同 |
| 转移后改号 | 允许,两边可不一致 |
| 改号是否同步对端 | 否 |
关联表 biz_no |
转移快照,不随改号更新 |
| 豁免依据 | 关联表 单据 ID,非编号字符串 |
14. 参考代码位置(改造前)
| 逻辑 | 文件 |
|---|---|
| 转移(临时号) | backend/app/crud/research_donation.py → convert_from_storage / convert_to_storage |
| 样本编码校验 | backend/app/crud/collection_protocol.py → validate_sample_codes_not_used |
| 协议号互斥 | create_collection_protocol / update_collection_protocol / create_donation |
| 前端转移调用 | web-admin/src/api/research-donation.ts |
文档结束