# 样本接收 — 实验室入库编码 **日期**: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 提示:`RK+接收日期+序号,保存后不可修改` ### 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 并保存编码