Files
zkxw/docs/superpowers/specs/2026-07-01-special-sample-mark-design.md
T
2026-07-01 16:37:33 +08:00

294 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 样本特殊标记与特殊说明
> 日期: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 全链路展示;不含筛选/导出/小程序 |