2026-05-19-unified-sample-reception-design.md 10 KB

统一样本接收 — 支持科研捐赠来源

日期:2026-05-19 状态:待审核 关联:AGENTS.md(database-conventions、api-conventions、ui-conventions)

背景与目标

当前【样本接收】模块仅支持来自细胞存储(采集协议单)的样本。业务需要科研捐赠(捐赠协议单)的样本也走统一的样本接收流程,接收后自动生成质检工单。

核心诉求:

  1. 样本接收页面统一展示两种来源的待接收/已接收样本,用类型标签区分
  2. 完整展示运输信息(运输方式、快递公司、快递单号、预计到达时间、携带人等),方便接收人员核对
  3. 科研捐赠样本接收后,同样触发质检工单创建

方案:现有表加字段 + 双路径查询

1. 数据库变更

在 biz_sample_reception 表新增 3 个字段:

-- docs/sql/sample_reception_add_donation_source.sql

ALTER TABLE biz_sample_reception
  ADD COLUMN reception_source VARCHAR(20) NOT NULL DEFAULT 'cell_storage'
    COMMENT '来源类型 cell_storage细胞存储 research_donation科研捐赠'
    AFTER receive_attachments,
  ADD COLUMN donation_sample_id VARCHAR(64) NULL
    COMMENT '捐赠样本ID(biz_research_donation_sample.id, 仅科研捐赠来源)'
    AFTER reception_source,
  ADD COLUMN research_donation_id VARCHAR(64) NULL
    COMMENT '科研捐赠单ID(biz_research_donation.id, 仅科研捐赠来源)'
    AFTER donation_sample_id;

约束规则:

  • reception_source = 'cell_storage':protocol_sample_id + collection_protocol_id 必填,donation_sample_id = NULL
  • reception_source = 'research_donation':donation_sample_id + research_donation_id 必填,protocol_sample_id = NULL
  • 现有数据 reception_source 默认 'cell_storage',无迁移风险,无需数据回填

2. 后端 Model 变更

文件:backend/app/models/models.py — BizSampleReception 类

新增字段:

reception_source = Column(String(20), nullable=False, default="cell_storage", comment="来源类型 cell_storage/research_donation")
donation_sample_id = Column(String(64), nullable=True, comment="捐赠样本ID(仅科研捐赠)")
research_donation_id = Column(String(64), nullable=True, comment="科研捐赠单ID(仅科研捐赠)")

3. 后端 CRUD 变更

文件:backend/app/crud/sample_reception.py

3.1 新增捐赠样本查询函数

新增 _donation_sample_detail_rows_query(db) — 对应捐赠样本的四表 JOIN:

  • BizResearchDonationSample + BizResearchDonation + BizResearchDonationParty + BizResearchDonationSampleTransport

新增 _donation_sample_pending_query(db, tenant_no, keyword) — 查询未被接收的捐赠样本:

  • 排除条件:BizSampleReception.donation_sample_id IN (已接收的捐赠样本ID)
  • 关键词搜索:donation_no、sample_code、mother_name、newborn_name

新增 _donation_rows_to_sample_list_items(db, rows, tenant_no, ...) — 将捐赠样本行转为统一列表项字典,字段映射:

protocolSampleId → donation_sample.id(注意:字段名保持 protocolSampleId 不变,前端统一使用)
protocolNo → donation.donation_no(协议号/捐赠编号)
sourceType → 'research_donation'
sourceTypeLabel → '科研捐赠'

3.2 修改待接收列表函数

修改 list_pending_protocol_samples():

  • 分别查询细胞存储待接收样本和捐赠待接收样本
  • 合并结果列表
  • 按 eta_time 排序(统一排序)
  • 手动分页(先合并再截取)

修改 list_received_protocol_samples():

  • 从 BizSampleReception 查询时,按 reception_source 分两组
  • cell_storage 组走现有 _sample_detail_rows_query 逻辑(不变)
  • research_donation 组走新增的捐赠查询逻辑
  • 合并后按 reception_time DESC 排序

3.3 修改创建接收记录函数

修改 create_sample_reception():

  • 新增参数:reception_source: str = "cell_storage"、donation_sample_id: Optional[str] = None、research_donation_id: Optional[str] = None
  • 当 reception_source = 'research_donation' 时:
    • 校验 donation_sample_id 对应的 BizResearchDonationSample 存在
    • 校验该捐赠样本未被接收(查 BizSampleReception.donation_sample_id)
    • protocol_sample_id 置为空字符串或 NULL
    • collection_protocol_id 置为空字符串或 NULL
  • 接收结论为 js 时,仍触发 qc_wo_svc.create_work_order_from_reception()

3.4 统一列表项输出格式

两种来源的列表项保持统一字段名,前端无需区分:

字段名 细胞存储来源 科研捐赠来源
protocolSampleId biz_protocol_sample.id biz_research_donation_sample.id
protocolNo 采集协议号 捐赠编号
sampleCode 样本编码 样本编码
sampleTypeName 样本类型名 样本类型名
motherName 母亲姓名 母亲姓名
newbornName 新生儿姓名 新生儿姓名
sourceType cell_storage research_donation
sourceTypeLabel 细胞存储 科研捐赠
etaTime 预计到达时间 预计到达时间
transportType 运输方式 运输方式
waybillNo 快递单号 快递单号
expressCompanyName 快递公司名 快递公司名
carrierName 携带人 携带人
carrierPhone 携带人电话 携带人电话

4. 后端 API 变更

文件:backend/app/api/sample_reception.py

4.1 待接收列表增加来源筛选

GET /web/admin/sample-receptions/pending 新增可选参数:

  • sourceType:cell_storage / research_donation / 不传(全部)

4.2 创建接收记录支持捐赠来源

SampleReceptionCreateBody 新增字段:

source_type: Optional[str] = Field("cell_storage", alias="sourceType")  # cell_storage / research_donation
donation_sample_id: Optional[str] = Field(None, alias="donationSampleId")  # 科研捐赠时必传

当 source_type = 'research_donation' 时,protocolSampleId 可不传,改为传 donationSampleId。

5. 前端变更

文件:web-admin/src/views/quality-inspection/sample-reception/index.vue

5.1 搜索区增加来源类型筛选

在 filter-grid 中增加:

<el-form-item label="来源类型">
  <el-select v-model="searchForm.sourceType" placeholder="全部" clearable>
    <el-option label="细胞存储" value="cell_storage" />
    <el-option label="科研捐赠" value="research_donation" />
  </el-select>
</el-form-item>

5.2 表格新增"来源类型"列

在序号列后增加:

<el-table-column prop="sourceTypeLabel" label="来源类型" width="100" align="center" header-align="center">
  <template #default="{ row }">
    <el-tag :type="row.sourceType === 'research_donation' ? 'warning' : 'success'" size="small">
      {{ row.sourceTypeLabel }}
    </el-tag>
  </template>
</el-table-column>

5.3 表格增加运输信息展示列

新增列(在快递单号之后):

  • transportType:运输方式(快递运输 / 人员携带)
  • expressCompanyName:快递公司
  • carrierName:携带人
  • carrierPhone:携带人电话

5.4 接收弹窗展示完整运输信息

单个接收弹窗(receiveDialogVisible)和查看详情弹窗(viewDialogVisible)中,在"样本信息"区块后增加"运输信息"区块:

运输信息
  运输方式:快递运输
  快递公司:顺丰速运
  快递单号:SF1234567890
  预计到达:2026-05-20 14:00:00
  携带人:张三 / 13800138000

使用 readonly-grid form-grid-2 布局,运输方式为"快递运输"时显示快递公司和快递单号,为"人员携带"时显示携带人和电话。

5.5 批量接收弹窗同步调整

batch-receive-card__meta 区域增加来源类型标签和运输信息展示。

5.6 提交参数调整

fetchSampleReceptionSubmit 调用时,增加 sourceType 和 donationSampleId 参数:

  • 细胞存储样本:sourceType: 'cell_storage',传 protocolSampleId
  • 科研捐赠样本:sourceType: 'research_donation',传 donationSampleId

文件:web-admin/src/api/sample-reception.ts

fetchSampleReceptionSubmit 参数类型增加:

sourceType?: 'cell_storage' | 'research_donation'
donationSampleId?: string  // sourceType=research_donation 时必传

fetchSampleReceptionPendingList 参数增加 sourceType?: string。

5.7 row-key 兼容

当前 row-key="protocolSampleId",捐赠样本也复用此字段名(后端统一输出),无需修改。

6. 变更范围汇总

层 文件 变更类型
SQL docs/sql/sample_reception_add_donation_source.sql(新建) ALTER TABLE
Model backend/app/models/models.py(BizSampleReception) 新增 3 字段
CRUD backend/app/crud/sample_reception.py 新增捐赠查询函数 + 修改 pending/received/create
API backend/app/api/sample_reception.py 修改 schema + 路由参数
前端 API web-admin/src/api/sample-reception.ts 参数扩展
前端页面 web-admin/src/views/quality-inspection/sample-reception/index.vue UI 变更

不涉及的文件(零影响):

  • 采集协议单 CRUD/API/前端
  • 捐赠协议单 CRUD/API/前端
  • 质检工单 CRUD(create_work_order_from_reception 已基于 reception 对象创建,无需改动)

7. 质检工单衔接

create_work_order_from_reception() 接收 BizSampleReception 对象。新增的捐赠接收记录走同一函数,内部通过 reception.collection_protocol_id 查询协议信息。对于捐赠来源,需确认该函数是否依赖 collection_protocol_id 非空。

如依赖,则需在质检工单创建逻辑中增加 reception_source 判断,对捐赠来源改为通过 research_donation_id 查询捐赠单信息作为工单的关联协议。实施时需读取 qc_work_order.py 的 create_work_order_from_reception 函数确认并适配。

设计决策记录

决策 选择 理由
统一表 vs 独立表 统一表加字段 用户要求统一入口,且现有细胞存储流程零改动
新增字段 vs 改用通用FK 新增字段 不破坏现有数据引用,风险最低
质检工单触发 捐赠也触发 用户确认需求一致
前端字段名 保持 protocolSampleId 不变 后端统一输出,前端改动最小化