294 lines
10 KiB
Markdown
294 lines
10 KiB
Markdown
# 样本特殊标记与特殊说明
|
||
|
||
> 日期:2026-07-01
|
||
> 状态:待审核
|
||
> 关联:`2026-05-19-protocol-donation-convert-design.md`(捐赠转细胞存储样本复制)
|
||
> 关联模块:采集协议单、捐赠协议单、样本信息、质检、生产、入库审核
|
||
|
||
---
|
||
|
||
## 1. 背景与目标
|
||
|
||
### 1.1 业务诉求
|
||
|
||
在**采集协议单**与**捐赠协议单**的样本信息中,增加「特殊样本」标记及「特殊说明」文本字段。例如样本为小三阳时:勾选特殊样本,特殊说明填写「小三阳」。
|
||
|
||
凡展示样本数据的界面,若该样本为特殊样本,须**醒目展示**:
|
||
|
||
- 红色加粗 **【特殊样本】**
|
||
- 红色加粗 **特殊说明** 内容
|
||
|
||
### 1.2 目标
|
||
|
||
1. 两种协议单的样本录入弹窗支持维护 `isSpecial` + `specialRemark`。
|
||
2. 勾选特殊样本时,特殊说明**必填**(已确认:选项 A)。
|
||
3. 所有 web-admin 展示样本数据的页面/弹窗/卡片,特殊样本信息只读展示,样式统一。
|
||
4. 捐赠转细胞存储时,特殊标记随样本复制到新 `biz_protocol_sample` 行。
|
||
5. 历史数据默认非特殊样本,兼容现有流程。
|
||
|
||
### 1.3 非目标(本期不做)
|
||
|
||
- 小程序端展示
|
||
- 按特殊样本筛选列表
|
||
- 导出 Excel 增加特殊说明列
|
||
- 下游业务表(接收、质检工单等)冗余存储特殊字段
|
||
|
||
---
|
||
|
||
## 2. 业务规则
|
||
|
||
### 2.1 字段语义
|
||
|
||
| 字段 | API 名 | 类型 | 说明 |
|
||
|------|--------|------|------|
|
||
| 特殊样本标记 | `isSpecial` | `boolean` | `true` 表示特殊样本 |
|
||
| 特殊说明 | `specialRemark` | `string` | 自由文本,如「小三阳」 |
|
||
|
||
与现有 `remark`(普通备注)**语义分离**:`remark` 为通用备注;`specialRemark` 为结构化警示说明,仅在 `isSpecial=true` 时有效。
|
||
|
||
### 2.2 校验规则(已确认)
|
||
|
||
| 条件 | 规则 |
|
||
|------|------|
|
||
| `isSpecial = true` | `specialRemark` **必填**,trim 后非空,最大 256 字符 |
|
||
| `isSpecial = false` | `specialRemark` 保存时**清空**为 `null` / 空字符串 |
|
||
| 未勾选却填写了说明 | 前端保存前清空;后端以 `isSpecial` 为准,忽略 `specialRemark` |
|
||
|
||
前后端均须校验;后端为最终门禁。
|
||
|
||
### 2.3 编辑与展示
|
||
|
||
- **可编辑**:采集/捐赠协议单新增、编辑样本(`SampleTransportFormSection` 子弹窗)。
|
||
- **只读展示**:协议查看弹窗、下游质检/生产/接收等所有样本展示位。
|
||
- **编辑时机**:协议单保存样本时一并持久化;下游模块通过 JOIN 源样本表读取最新值。
|
||
|
||
### 2.4 捐赠转细胞存储
|
||
|
||
`research_donation.py` 复制 `BizResearchDonationSample` → `BizProtocolSample` 时,须同步复制:
|
||
|
||
- `is_special` ← 源样本 `is_special`
|
||
- `special_remark` ← 源样本 `special_remark`
|
||
|
||
---
|
||
|
||
## 3. 数据模型
|
||
|
||
### 3.1 表结构变更
|
||
|
||
两张样本表**对称**增加字段(位于业务字段末尾、`tenant_no` 之前):
|
||
|
||
**`biz_protocol_sample`**
|
||
|
||
```sql
|
||
ALTER TABLE `biz_protocol_sample`
|
||
ADD COLUMN `is_special` tinyint(4) NOT NULL DEFAULT 0 COMMENT '是否特殊样本:0否 1是' AFTER `storage_expire_date`,
|
||
ADD COLUMN `special_remark` varchar(256) DEFAULT NULL COMMENT '特殊说明' AFTER `is_special`;
|
||
```
|
||
|
||
**`biz_research_donation_sample`**
|
||
|
||
```sql
|
||
ALTER TABLE `biz_research_donation_sample`
|
||
ADD COLUMN `is_special` tinyint(4) NOT NULL DEFAULT 0 COMMENT '是否特殊样本:0否 1是' AFTER `storage_expire_date`,
|
||
ADD COLUMN `special_remark` varchar(256) DEFAULT NULL COMMENT '特殊说明' AFTER `is_special`;
|
||
```
|
||
|
||
同步更新 `docs/sql/zkxw.sql` 基线。
|
||
|
||
### 3.2 ORM
|
||
|
||
- `backend/app/models/models.py` → `BizProtocolSample`
|
||
- `backend/app/models/research_donation.py` → `BizResearchDonationSample`
|
||
|
||
### 3.3 API 字段(camelCase)
|
||
|
||
`_sample_to_dict` 统一输出:
|
||
|
||
```python
|
||
"isSpecial": bool(s.is_special),
|
||
"specialRemark": (s.special_remark or "").strip(),
|
||
```
|
||
|
||
`save_samples` 写入时:
|
||
|
||
- `is_special = 1 if raw.get("isSpecial") else 0`
|
||
- `special_remark` 仅在 `is_special=1` 时写入 trim 后文本,否则 `None`
|
||
|
||
---
|
||
|
||
## 4. 架构方案
|
||
|
||
### 4.1 选定方案:样本表唯一数据源 + 读取 JOIN
|
||
|
||
特殊标记仅存于 `biz_protocol_sample` / `biz_research_donation_sample`,下游模块在 list/detail API 中 JOIN 或复用已有样本查询补出 `isSpecial`、`specialRemark`。
|
||
|
||
**不**在 `biz_sample_reception`、`biz_qc_work_order` 等子表冗余。
|
||
|
||
### 4.2 后端改造模式
|
||
|
||
凡构建样本相关 dict 的位置,按 `_sample_to_dict` 模式补字段。重点文件:
|
||
|
||
| 文件 | 改动 |
|
||
|------|------|
|
||
| `crud/collection_protocol.py` | `_sample_to_dict`、`save_samples` |
|
||
| `crud/research_donation.py` | `_sample_to_dict`、`save_samples`、转存储复制 |
|
||
| `crud/protocol_sample_collection.py` | list item 构建 |
|
||
| `crud/research_donation_sample_collection.py` | list item 构建 |
|
||
| `crud/sample_reception.py` | 待接收/已接收/批量 list |
|
||
| `crud/qc_work_order.py` | 工单 list/detail 中样本信息 |
|
||
| `crud/production_work_order.py` | 同上 |
|
||
| `crud/storage_release_review.py` | 入库审核 list/detail |
|
||
| 转运前样本等相关 CRUD | 协议卡片内样本 list |
|
||
|
||
Pydantic Create/Update body 中 samples 数组项增加可选字段 `isSpecial`、`specialRemark`;后端 save 时校验。
|
||
|
||
运行 `python scripts/check_field_consistency.py` 验证 CRUD ↔ Schema ↔ 前端字段一致。
|
||
|
||
---
|
||
|
||
## 5. 前端设计
|
||
|
||
### 5.1 录入 UI — `SampleTransportFormSection`
|
||
|
||
**位置**:添加/编辑样本子弹窗,「样本状态」表单项之后。
|
||
|
||
**控件**:
|
||
|
||
1. `el-checkbox` — 标签「特殊样本」,绑定 `sampleDraft.isSpecial`
|
||
2. `el-input` type="textarea" — 标签「特殊说明」,`:disabled="!sampleDraft.isSpecial"`,`maxlength=256`,`show-word-limit`
|
||
|
||
**交互**:
|
||
|
||
- 取消勾选 → 清空 `specialRemark`
|
||
- 确定时:`isSpecial=true` 且 `specialRemark` 为空 → `ElMessage.warning` + 表单校验失败
|
||
|
||
**列表表格**:新增列「特殊样本」(`min-width` ≥ 120):
|
||
|
||
- 非特殊:显示 `—`
|
||
- 特殊:inline 展示 `SpecialSampleDisplay` 组件内容
|
||
|
||
`createEmptySample()` / `sampleDraft` 初始化增加 `isSpecial: false`、`specialRemark: ''`。
|
||
|
||
采集协议单、捐赠协议单共用此组件,**改一处覆盖两种协议**。
|
||
|
||
### 5.2 展示 UI — 公共组件
|
||
|
||
新建 `web-admin/src/components/business/special-sample-display/index.vue`:
|
||
|
||
```vue
|
||
<!-- props: isSpecial, specialRemark, layout?: 'inline' | 'block' -->
|
||
<template v-if="isSpecial">
|
||
<span class="special-sample-mark">【特殊样本】</span>
|
||
<span v-if="specialRemark" class="special-sample-remark">{{ specialRemark }}</span>
|
||
</template>
|
||
```
|
||
|
||
样式(scoped + 可全局复用 class):
|
||
|
||
```scss
|
||
.special-sample-mark,
|
||
.special-sample-remark {
|
||
color: var(--el-color-danger);
|
||
font-weight: 700;
|
||
}
|
||
.special-sample-remark {
|
||
margin-left: 4px;
|
||
}
|
||
```
|
||
|
||
- **非特殊样本**:组件不渲染任何 DOM(不占位、不显示「—」)。
|
||
- **不使用 `el-tag`**:此为警示文本,非状态枚举。
|
||
|
||
### 5.3 查看弹窗
|
||
|
||
**`ProtocolViewDialog` / `DonationViewDialog`** 样本卡片:
|
||
|
||
- 卡片 header 标题行:样本类型名旁 inline 展示 `SpecialSampleDisplay`
|
||
- 样本信息 data-grid:独立一行「特殊说明」(仅 `isSpecial` 时显示,block 布局)
|
||
|
||
### 5.4 下游展示接入点(P1 全量)
|
||
|
||
| 模块 | 文件 |
|
||
|------|------|
|
||
| 协议管理 | `ProtocolAuditTab.vue`、`PreTransitProtocolCardList.vue` |
|
||
| 捐赠审核 | `research-donation/audit/index.vue` |
|
||
| 样本采集信息 | `SampleCollectionInfoTab.vue`、`DonationSampleCollectionInfoTab.vue` |
|
||
| 样本接收 | `sample-reception/index.vue`、`SampleReceptionCardList.vue` |
|
||
| 质检工单 | `QcWorkOrderProtocolGroup.vue`、`QcWorkOrderSampleGroup.vue`、`QcWorkOrderTaskSubList.vue` |
|
||
| 入库审核 | `storage-release-review/index.vue`、`StorageReleaseReviewDialog.vue`、`StorageReleaseViewDialog.vue` |
|
||
| 生产工单 | `ProductionWorkOrderCardList.vue`、`SampleReceptionDialog.vue`、`DispatchTaskDialog.vue` 等 |
|
||
| 溯源 | `SampleCustomerViewDialog.vue` |
|
||
|
||
**展示策略**:
|
||
|
||
1. 列表/卡片:**样本编码列或标题行**旁 inline 展示(最醒目)
|
||
2. 详情弹窗:样本编码下方或信息区独立一行
|
||
3. 统一使用 `SpecialSampleDisplay`,禁止各页复制样式
|
||
|
||
### 5.5 TypeScript 类型
|
||
|
||
`web-admin/src/types/api.d.ts` 中协议/捐赠样本相关类型补充 `isSpecial?: boolean`、`specialRemark?: string`。
|
||
|
||
---
|
||
|
||
## 6. 实施阶段
|
||
|
||
| 阶段 | 范围 | 交付标准 |
|
||
|------|------|----------|
|
||
| **P0** | DB + Model + 两协议 CRUD + 录入组件 + 两协议查看弹窗 | 可在协议单录入/查看特殊样本 |
|
||
| **P1** | 公共展示组件 + 下游全部展示点 + 转存储复制 | 质检/生产/接收等链路可见红色警示 |
|
||
| **P2**(可选) | 筛选、导出、小程序 | 产品另行排期 |
|
||
|
||
建议 P0、P1 **同一 PR 或连续 PR** 交付,避免下游看不到标记造成业务风险。
|
||
|
||
---
|
||
|
||
## 7. 测试要点
|
||
|
||
### 7.1 录入校验
|
||
|
||
- [ ] 勾选特殊样本、说明为空 → 前端拦截
|
||
- [ ] 勾选特殊样本、说明为空 → 后端 API 返回 422/400
|
||
- [ ] 未勾选、说明有值 → 保存后 `specialRemark` 为空
|
||
- [ ] 说明超 256 字 → 校验失败
|
||
|
||
### 7.2 持久化
|
||
|
||
- [ ] 采集协议单编辑保存后 detail 回显正确
|
||
- [ ] 捐赠协议单同上
|
||
- [ ] 历史样本(无新字段)→ `isSpecial=false`
|
||
|
||
### 7.3 展示
|
||
|
||
- [ ] 协议查看弹窗:红色加粗【特殊样本】+ 说明
|
||
- [ ] 非特殊样本:各页无多余占位
|
||
- [ ] 样本接收、质检工单、生产工单卡片可见特殊标记
|
||
|
||
### 7.4 转存储
|
||
|
||
- [ ] 捐赠单特殊样本转细胞存储后,新采集协议单样本保留 `isSpecial` + `specialRemark`
|
||
|
||
### 7.5 字段一致性
|
||
|
||
- [ ] `python scripts/check_field_consistency.py` 通过
|
||
|
||
---
|
||
|
||
## 8. 风险与约束
|
||
|
||
1. **双表对称**:采集与捐赠样本表、CRUD、UI 须同步改动,避免只改一侧。
|
||
2. **改动面大**:下游展示点 15+,须用公共组件收敛样式。
|
||
3. **与普通 remark 区分**:文档与 UI 文案明确,避免用户把特殊说明写入备注。
|
||
4. **警示色**:使用 Element Plus danger 色 + `font-weight: 700`,符合 Fresh Mint 规范中对强提醒的使用场景。
|
||
|
||
---
|
||
|
||
## 9. 决策记录
|
||
|
||
| 议题 | 决策 |
|
||
|------|------|
|
||
| 勾选特殊样本时特殊说明是否必填 | **A. 必填**(前后端校验) |
|
||
| 数据存储位置 | 样本源表唯一数据源,下游 JOIN |
|
||
| 展示组件 | 新建 `SpecialSampleDisplay`,红色加粗文本,非 Tag |
|
||
| 本期范围 | web-admin 全链路展示;不含筛选/导出/小程序 |
|