2026-05-19-protocol-donation-convert-design.md 13 KB

细胞存储 ⇄ 科研捐赠 转移改造方案

日期:2026-05-19
状态:已实施(2026-05-19)
关联模块:采集协议单管理、捐赠协议单管理


1. 背景与问题

1.1 业务场景

  • 协议转科研:细胞存储采集协议(sxz)→ 新建科研捐赠单,源协议状态 zky。
  • 转细胞存储:科研捐赠单(sxz)→ 新建采集协议单,源捐赠状态 zckc。

产品约定:

  1. 转移时 协议号与样本编码与源单一致(同一业务在不同模块各一张单)。
  2. 转移后用户 可能单独调整 采集协议号或捐赠协议号(两边不必长期同号)。
  3. 二次编辑保存时不应误报「协议号已存在」「样本编码已被占用」。

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-storage
  • POST /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

文档结束