Files
zkxw/docs/superpowers/specs/2026-06-03-sample-reception-lab-storage-code-design.md
T
2026-06-03 17:53:48 +08:00

218 lines
7.9 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-06-03
**状态**:已实现
**关联**:`web-admin/src/views/quality-inspection/sample-reception/index.vue`、`backend/app/crud/sample_reception.py`、`backend/app/crud/biz_no_generator.py`、`docs/superpowers/specs/2026-05-19-unified-sample-reception-design.md`
---
## 1. 已确认业务规则
| 项 | 规则 |
|----|------|
| 字段名称 | **实验室入库编码** |
| 编码格式 | `RK` + `YYYYMMDD` + **3 位序号**(左补零),例:`RK20260603001` |
| 日期段 | **`YYYYMMDD` = 样本接收时间的日期部分**(非服务器当天、非打开弹窗当天) |
| 序号规则 | 同一租户、同一接收日期下递增;默认自动生成;用户可在提交前手动修改 |
| 并发 | 自动分配须原子占号,避免多人同时拿到相同默认序号 |
| 锁定 | 保存成功后编码**只读**,编辑已接收记录时不可修改 |
| 结论条件 | **仅接收(`js`)** 生成并保存入库编码;**拒收(`bj`)** 不生成、不保存、不展示该字段 |
| 适用范围 | 单条接收、批量接收、查看详情、已接收列表(仅 `js` 有值) |
---
## 2. 方案(已选 A)
| 方案 | 说明 | 结论 |
|------|------|------|
| **A. 预占 + 提交校验** | 打开弹窗/改接收日期时调 allocate;提交校验格式、日期一致性与唯一性 | **采用** |
| B. 仅提交时分配 | 预览码不锁号 | 不满足并发要求 |
| C. 前端生成 | 无服务端原子序号 | 不采用 |
---
## 3. 数据模型
### 3.1 表变更
`biz_sample_reception` 新增:
```sql
ALTER TABLE biz_sample_reception
ADD COLUMN lab_storage_code VARCHAR(32) NULL
COMMENT '实验室入库编码 RK+接收日期YYYYMMDD+3位序号'
AFTER sample_code;
```
索引(软删除场景):
```sql
CREATE UNIQUE INDEX uk_sr_tenant_lab_code
ON biz_sample_reception (tenant_no, lab_storage_code, del_flag);
```
> 说明:`del_flag=0` 时租户内唯一;历史软删记录保留原编码,不参与业务冲突。
### 3.2 序号表复用
复用现有 `biz_biz_no_sequence`,`prefix = 'RK'`,`biz_date = YYYYMMDD`(**来自接收时间日期**)。
- 序号宽度:**3 位**,上限 999/日/租户
- 与 `allocate_next_biz_no()` 相同:`INSERT ... ON DUPLICATE KEY UPDATE last_seq = LAST_INSERT_ID(last_seq + 1)`
### 3.3 手动编码与序号同步
用户手动输入 `RK20260603088` 且提交成功时,在同一事务内:
```
last_seq = MAX(last_seq, 88)
```
避免后续自动分配仍从较小序号开始导致撞号。
分配前亦取:
```
next_seq = MAX(last_seq, MAX(已存在编码末三位)) + 1
```
(限定同一 `tenant_no + prefix + biz_date`)
---
## 4. 后端 API
### 4.1 预分配编码
```
POST /web/admin/sample-receptions/allocate-lab-storage-codes
```
**Body**
| 字段 | 类型 | 说明 |
|------|------|------|
| `receptionDate` | string | 必填,`YYYY-MM-DD`,由前端从「样本接收时间」截取 |
| `count` | int | 默认 1,批量接收为 N,上限 50 |
**Response**
```json
{ "codes": ["RK20260603001", "RK20260603002"] }
```
**逻辑**
1. 校验 `receptionDate` 格式
2. 对 `biz_date = receptionDate.replace('-','')` 原子递增 `count` 次
3. 返回连续编码列表
权限:与样本接收提交一致(`require_admin_or_super_admin` 或现有接收写权限)。
### 4.2 创建 / 更新接收
`SampleReceptionCreateBody` / `SampleReceptionUpdateBody` 增加:
- `labStorageCode`(**仅 `acceptConclusion=js` 时必填**;`bj` 时忽略且存 `NULL`;更新不可改)
**创建校验(`acceptConclusion=js`)**
1. `labStorageCode` 非空
2. 正则:`^RK\d{8}\d{3}$`
3. 编码中 `YYYYMMDD` **必须等于** `receptionTime` 的日期部分
4. 租户内 `del_flag=0` 唯一
5. 成功后同步 `biz_biz_no_sequence.last_seq`(见 §3.3)
**创建校验(`acceptConclusion=bj`)**
- 不得传入有效 `labStorageCode`(传入非空则 400,或一律忽略并置 `NULL`)
- 不调用 allocate、不占用 RK 序号
**更新**
- 忽略客户端传入的 `labStorageCode` 变更(或 API 不接受该字段),保持原值
### 4.3 列表与详情
`_reception_to_dict` / 列表项增加 `labStorageCode`;已接收列表关键词搜索纳入该字段。
---
## 5. 前端交互
### 5.1 单条接收弹窗
1. 默认「是否接收」= **接收(`js`)**;此时:
- 打开弹窗 → 默认接收时间 = 当前时间 → 调 `allocate(receptionDate, count=1)` → 填入「实验室入库编码」
2. 用户切换为 **拒收(`bj`)**:
- **隐藏**「实验室入库编码」表单项
- 清空本地编码值;**不再**占用/提交编码
3. 用户从 `bj` 切回 `js`:按当前接收时间日期重新 `allocate(1)`(除非编辑已保存的 `js` 记录)
4. 用户修改**样本接收时间**且**日期变化**(且当前为 `js`):
- 若编码未被手动编辑 → 自动按新日期重新 `allocate(1)` 并替换
- 若已手动编辑 → 不自动改码;提交时若日期与编码不一致,提示错误
5. 编码输入框:新建 `js` 时可编辑;编辑已保存 `js` 记录时 `disabled`;`bj` 记录不展示
6. 字段位置:「接收登记」区,与接收温度/体积同级(`form-grid-3`)
7. label 提示:`<span class="form-label-tip">RK+接收日期+序号,保存后不可修改</span>`
### 5.2 批量接收
1. 打开弹窗 → 每条 entry 默认 `receptionTime = now`、`acceptConclusion = js`
2. **仅 `acceptConclusion=js` 的 entry** 参与 allocate:按接收日期分组,`allocate(count=组内 js 条数)`
3. 每条 `js` entry 增加 `labStorageCode`;改接收时间日期时,同 §5.1 规则
4. entry 切为 `bj` → 隐藏该条编码字段并清空;切回 `js` → 为该条单独 re-allocate
5. 提交:`js` 携带 `labStorageCode`;`bj` 不传或传空
### 5.3 查看详情 / 已接收列表
- 详情:`js` 展示「实验室入库编码」;`bj` 不展示该字段(或整块接收信息不含此项)
- 已接收 Tab:`js` 显示编码;`bj` 显示 `-`
- 筛选关键词 placeholder 增加「入库编码」(仅匹配有编码的 `js` 记录)
---
## 6. 并发与异常
| 场景 | 处理 |
|------|------|
| 两人同时打开弹窗 | allocate 返回不同序号 |
| 用户改码为已存在编码 | 提交 400:「实验室入库编码已存在」 |
| 当日序号 > 999 | allocate / 提交返回明确错误 |
| 批量部分失败 | 已成功记录保留编码;失败项可重试(新 allocate) |
| 取消弹窗未提交 | 已占序号允许空号,不回滚 sequence |
| `js` 改 `bj` 再提交 | 已 allocate 的序号允许空号;落库 `lab_storage_code=NULL` |
---
## 7. 影响文件(实现清单)
| 层级 | 文件 |
|------|------|
| SQL | `docs/sql/sample_reception_add_lab_storage_code.sql`、`docs/sql/zkxw.sql` |
| 后端 | `models.py`、`crud/biz_no_generator.py`(或 `sample_reception_lab_code.py`)、`crud/sample_reception.py`、`api/sample_reception.py` |
| 前端 | `api/sample-reception.ts`、`views/quality-inspection/sample-reception/index.vue` |
| 关联只读 | 协议查看弹窗中样本接收区块(若展示接收信息) |
| 校验 | `scripts/check_field_consistency.py` |
---
## 8. 非目标
- 事后修改已保存的入库编码
- 跨租户共享序号
- 拒收(`bj`)生成或保存入库编码
---
## 9. 测试要点
1. 同日连续接收 3 条 → `001/002/003`
2. 接收时间日期为昨天 → 编码日期段为昨天,序号独立计数
3. 两人并发打开单条接收 → 默认编码不同
4. 手动改为已存在编码 → 提交失败
5. 手动改为 `...088` 后,下一条自动分配 > 088
6. 保存后编辑弹窗 → 编码 disabled
7. 批量 5 条同日期且均为 `js` → 连续 5 码;混合两个日期 → 各日期独立序号
8. 单条先选 `js` 占号后改 `bj` 提交 → 无入库编码;库中 `lab_storage_code` 为 NULL
9. 批量中 3 条 `js` + 2 条 `bj` → 仅 3 条 allocate 并保存编码