2026-06-23-pre-transit-sample-design.md 15 KB

预产及在途样本 — 功能设计

日期: 2026-06-23
状态: 已实现
界面模式: P27 衍生列表(只读监控)+ 双分区单页;列表样式对齐采集协议单列表
参考页: web-admin/src/views/cell-storage/protocol/index.vue(列表壳层与列风格)、research-donation/list/index.vue(捐赠侧字段对齐)、collection_protocol.py 列表 _row_to_dict(样本概要字段)

1. 需求背景

客户在【质检管理】菜单下、【样本接收】上方新增 【预产及在途样本】 页面,用于质检人员集中查看接收前的两类待办:

分区 业务含义 列表粒度
上半区 · 在途样本 协议下存在 已采集、未接收 样本的协议单 协议单行(样本为行内展示字段)
下半区 · 预产样本 协议已建且 样本未录入 和/或 样本未采集 协议单行(样本为行内展示字段)

已确认展示维度(2026-06-23):上下半区均采用 协议单维度 展示;样本编码、类型、采集概况、运输/预计到达等作为 协议行内的概要信息,不拆成样本级多行。

已确认列表样式:参照 【采集协议单】 列表(cell-storage/protocol/index.vue)——panel-top + filter-block + list-table(stripe、列居中、el-tag 状态)+ list-pager-bar;无新增/编辑/删除工具栏与勾选列,操作列仅「查看」。

已确认交互:两个分区列表 操作列仅「查看」;按来源打开对应协议查看弹窗(采集 → ProtocolViewDialog,捐赠 → DonationViewDialog)。不提供接收、编辑、跳转样本接收等操作。

已确认数据规则(上半区):仅展示 sample_status = 'ycj'(已采集)且尚无有效接收记录 的样本。

2. 与现有模块关系

  • 【样本接收】:操作入口(登记接收、批量接收、触发质检工单);本页为 只读看板,与之互补。
  • 【采集协议单】列表:列布局、筛选区结构、表格视觉的直接模板(见 §5.2)。
  • 样本采集信息 Tab:仅作查看弹窗 handleView 参考,不作列表列模板。
  • 跨表关联键:判断是否已接收 SHALL 使用 tenant_no + sample_code(OpenSpec api-conventions § 细胞存储样本跨表关联键),与 sample_reception.py 现有逻辑一致。

3. 数据判定规则

列表粒度:先按 §3.1 / §3.2 判定 协议是否入选;再在行内挂载 与本分区相关的样本概要(sampleItems 为过滤后的子集,见 §3.4)。

3.1 上半区 · 在途(协议入选条件)

协议入选当且仅当:在其 del_flag=0 的样本子表中,存在至少一条同时满足:

sample_status = 'ycj'
AND NOT EXISTS 有效 biz_sample_reception(tenant_no + sample_code)

且协议本身:

del_flag = 0
AND tenant_no = 当前用户租户
AND protocol_status / donation_status NOT IN ('yzf', 'zky', 'zckc', 'ygq')

行内样本展示(sampleItems):仅包含满足上述「在途」条件的样本行;附带各样本运输字段(etaTime、waybillNo 等)。
行内汇总字段(复用采集协议单列表逻辑):

  • sampleCodes、sampleTypeNames、labStorageCodes — 由在途样本拼接
  • sampleCollectSummary — 仅统计在途样本,如「2个已采集(待接收)」
  • etaTimeSummary — 在途样本中最早的预计到达(用于排序与列展示,实施时命名可调整)

协议级排序:etaTimeSummary 升序(空值靠后)→ 协议 create_time 升序。

关键词:协议号、母亲姓名/电话/身份证、在途样本编码、实验室入库编码。

3.2 下半区 · 预产(协议入选条件)

协议入选当满足 公共协议条件 且符合 下列 A 或 B:

公共协议条件:

del_flag = 0
AND tenant_no = 当前用户租户
AND protocol_status / donation_status IN ('dsx', 'sxz')   -- 排除 ygq
AND NOT IN ('yzf', 'zky', 'zckc')
子场景 preDeliveryKind 入选条件
A · 样本未录入 no_sample 无任何 del_flag=0 样本子表行
B · 样本未采集 uncollected 存在 sample_status 为空的样本行

同一协议若同时无样本行与未采集样本不可能并存;若 仅有未采集样本 为 uncollected;仅有零样本 为 no_sample。若协议同时含未采集样本与在途样本,可同时出现在上下两区(各分区一行,样本概要不同)。

行内样本展示:

  • no_sample:sampleItems=[],sampleCollectSummary 展示「样本未录入」
  • uncollected:sampleItems 仅含未采集样本;sampleCollectSummary 如「2个未采集」

协议级排序:预产期 expected_delivery_date 降序 → 协议 create_time 降序。

关键词:协议号、母亲姓名/电话/身份证、未采集样本编码。

3.3 数据归属总览(协议维度)

场景 上半区 · 在途 下半区 · 预产
协议无样本行 — 展示(no_sample)
仅有未采集样本 — 展示(uncollected)
仅有已采集未接收样本 展示 —
兼有未采集 + 已采集未接收 展示(在途概要) 展示(未采集概要)
全部样本已接收 — —

3.4 行内样本概要结构(与采集协议单列表对齐)

后端复用 collection_protocol.list / research_donation.list 的组装方式,统一返回:

字段 说明
sampleItems[] { sampleTypeName, sampleCode, sampleStatus, labStorageCode, transport? },已按分区过滤
sampleTypeNames 逗号拼接
sampleCodes 逗号拼接
labStorageCodes 逗号拼接
sampleCollectSummary 文字概要(分区语义不同)
preDeliveryKind 仅下半区:no_sample / uncollected(协议同时仅一种预产态;若仅 uncollected 则标 uncollected)

上半区在途样本的 transport.etaTime 可汇总到列 预计到达(多样本取最早或展示「详见概要」,实施取最早一条)。

4. 后端设计

4.1 新增 API(推荐独立模块,方案 A)

接口 方法 路径 说明
在途样本列表 GET /web/admin/pre-transit-samples/in-transit 上半区
预产协议列表 GET /web/admin/pre-transit-samples/pre-delivery 下半区

公共查询参数:

参数 类型 说明
page int 页码
pageSize int 每页条数(≤100)
keyword string 关键词
sourceType string 可选:cell_storage / research_donation,空=全部

在途列表额外参数(可选一期):

参数 类型 说明
etaTimeStart string 预计到达开始 YYYY-MM-DD
etaTimeEnd string 预计到达结束 YYYY-MM-DD

权限:require_menu_code("biz:preTransitSample:list");apply_data_scope 挂在 协议主表(BizCollectionProtocol / BizResearchDonation),与采集协议单列表一致。

实现要点:

  1. 先查询符合分区条件的 协议 ID 集合(采集 + 捐赠 UNION,分页在协议级)。
  2. 批量加载当事人、采集信息、样本、运输、接收记录,组装与 list_collection_protocols 同构的协议行。
  3. 按分区规则 过滤 sampleItems 并生成 sampleCollectSummary 等概要字段。

实现文件:

  • backend/app/crud/pre_transit_sample.py — 协议级查询与行组装(可抽取与 collection_protocol 共用的样本概要 helper)
  • backend/app/api/pre_transit_sample.py — 路由
  • 注册:app/api/__init__.py、app/main.py

4.2 列表响应字段(上下半区均为协议行)

在 采集协议单列表行 基础上扩展,细胞存储与捐赠 字段名统一(捐赠的 donationStatus 映射为 protocolStatus 展示,donationNo 映射为 docNo 或保留双字段由前端列模板分支)。

协议主信息(对齐 protocol/index.vue 列):

字段 列名
sourceType, sourceTypeLabel 来源类型(新增,Tag 展示)
protocolStatus 状态
registerDate / donationDate 登记日期
motherName, motherPhone, motherIdCard 母亲信息
fatherName 父亲姓名
newbornSex, deliveryMode, newbornWeightGram 新生儿信息
regionName, hospitalName 采集地区/医院
collectorUserName, salesUserName 采集专员/销售
sampleCollectTime 样本采集时间
createTime 创建时间
expectedDeliveryDate 预产期(下半区排序用,上半区可展示)

样本概要(行内展示,非独立行):

字段 列名建议
sampleTypeNames 样本类型
sampleCodes 样本编码
labStorageCodes 实验室入库编码
sampleCollectSummary 样本采集概况
etaTimeSummary 预计到达(上半区)
preDeliveryKindLabel 预产类型(下半区:样本未录入 / 样本未采集)

查看用 ID:collectionProtocolId 或 researchDonationId(二选一有值)、sourceType。

4.3 预产列表响应字段

与 §4.2 相同;额外必有 preDeliveryKind、preDeliveryKindLabel。

5. 前端设计

5.1 菜单与路由

项 值
菜单名 预产及在途样本
父级 质检管理 QCM00000000000000000000000000001
排序 order_num = 0(位于样本接收 order_num = 1 之上)
路由 path pre-transit-sample
组件 /quality-inspection/pre-transit-sample/index
menuCode biz:preTransitSample:list

同步修改 web-admin/src/router/modules/sqxp.ts。

5.2 页面结构(对齐采集协议单列表)

page-container
└── section.panel
    ├── filter-block(结构同 protocol/index.vue:关键词、来源类型、创建时间等;下半区可不单独筛预产类型)
    ├── panel-top → h2.title-bar-heading「在途样本」
    ├── list-body.list-wrap
    │   ├── el-table.list-table(列布局参照 protocol/index.vue + 来源/样本概要列)
    │   └── pagination.list-pager-bar
    ├── panel-top → h2.title-bar-heading「预产样本」
    └── list-body.list-wrap(同上)

与采集协议单列表的差异:

采集协议单列表 本页
有勾选列、新增/批量删除工具栏 无工具栏、无勾选
操作:查看 + 更多(编辑/删除) 仅查看(btn-op-item + View,与协议列表查看按钮一致,不设 primary 亦可与协议页保持一致)
仅细胞存储 增加 来源类型 列;捐赠行状态 Tag 用 donationStatusTagType
无双分区 上下两个独立表格 + 分页

表格列(建议顺序):序号 → 来源类型 → 状态 → 登记日期 → 母亲姓名 → 母亲电话 → 母亲身份证 → 父亲姓名 → 新生儿性别 → 分娩方式 → 新生儿体重 → 采集地区 → 采集医院 → 采集专员 → 销售 → 样本类型 → 样本编码 → 样本采集概况 → 样本采集时间 → 预计到达(上半区)/ 预产类型(下半区)→ 创建时间 → 操作。

  • 列表:list-table + stripe;列 align="center";状态/来源用 el-tag。
  • 在途列表不对预计到达过期行标红。
  • 操作列:fixed="right",min-width ≥ 120。
  • 两个分区 独立分页。

实现建议:抽取 ProtocolListTableColumns 或在本页复制 protocol/index.vue 列定义并追加样本概要列,避免与录入页工具栏耦合。

5.3 查看交互

function handleView(row) {
  if (row.sourceType === 'research_donation') {
    // DonationViewDialog :donation-id="row.researchDonationId"
  } else {
    // ProtocolViewDialog :protocol-id="row.collectionProtocolId"
  }
}

挂载组件(默认配置,不展开审核/储存证等扩展 Tab):

  • @/views/cell-storage/protocol/ProtocolViewDialog.vue
  • @/views/research-donation/list/DonationViewDialog.vue

参照:SampleCollectionInfoTab.vue、DonationSampleCollectionInfoTab.vue 的 handleView 与 listRow 摘要传参。

5.4 API 模块

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

  • fetchPreTransitInTransitList(params)
  • fetchPreTransitPreDeliveryList(params)

6. 数据库与权限 SQL

文件:docs/sql/alter_sys_menu_add_pre_transit_sample.sql

  • 新增菜单 C 类型:biz:preTransitSample:list
  • 新增按钮 F 类型:biz:preTransitSample:list(查询,与菜单 perms 一致即可)
  • 将原「样本接收」order_num 调整为 2(若新菜单占 1)或新菜单 0、样本接收保持 1

菜单 id 使用新 UUID(实施时生成,避免与现有冲突)。

7. 非目标(一期不做)

  • 列表导出
  • 从本页直接接收样本
  • 双击行打开查看(可二期加,一期仅按钮)
  • 修改【样本接收】待接收 Tab 的过滤逻辑(保持现网行为)

8. 测试要点

  1. 上半区:仅含「存在 ycj 且未接收样本」的协议;行内 sampleItems 仅含在途样本;已接收、纯未采集协议不出现。
  2. 下半区:仅 dsx/sxz 且非 ygq;no_sample / uncollected 协议各一行;同一协议可同时出现在上下区时样本概要不同。
  3. 列表为协议维度,列风格与采集协议单列表一致;样本信息以概要列展示,非样本级多行。
  4. 细胞存储行点查看打开 ProtocolViewDialog;捐赠行打开 DonationViewDialog。
  5. 租户隔离、数据权限与关键词搜索正确。
  6. 菜单显示在样本接收上方;无权限用户不可访问 API。

9. 已确认项汇总

# 项 结论
1 上半区数据范围 仅 ycj + 未接收
2 操作列 仅查看
3 查看目标 采集 → 采集协议单;捐赠 → 捐赠协议单
4 布局 上下双分区、分开展示
5 来源 采集协议单 + 捐赠协议单均覆盖
6 下半区协议状态 仅 dsx + sxz,排除 ygq 已过期
7 在途过期样式 不标红、不特殊标记预计到达已过期行
8 下半区数据范围 样本未录入 + 样本未采集
9 列表粒度 上下半区均为协议单维度;样本为行内概要字段
10 列表样式 参照采集协议单列表(protocol/index.vue)

10. 待确认(可选,不阻塞开发)

  1. 筛选区是否在共用关键词之外,为在途区单独提供「预计到达日期」范围?