日期: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
| 项 | 规则 |
|---|---|
| 字段名称 | 实验室入库编码 |
| 编码格式 | RK + YYYYMMDD + 3 位序号(左补零),例:RK20260603001 |
| 日期段 | YYYYMMDD = 样本接收时间的日期部分(非服务器当天、非打开弹窗当天) |
| 序号规则 | 同一租户、同一接收日期下递增;默认自动生成;用户可在提交前手动修改 |
| 并发 | 自动分配须原子占号,避免多人同时拿到相同默认序号 |
| 锁定 | 保存成功后编码只读,编辑已接收记录时不可修改 |
| 结论条件 | 仅接收(js) 生成并保存入库编码;拒收(bj) 不生成、不保存、不展示该字段 |
| 适用范围 | 单条接收、批量接收、查看详情、已接收列表(仅 js 有值) |
| 方案 | 说明 | 结论 |
|---|---|---|
| A. 预占 + 提交校验 | 打开弹窗/改接收日期时调 allocate;提交校验格式、日期一致性与唯一性 | 采用 |
| B. 仅提交时分配 | 预览码不锁号 | 不满足并发要求 |
| C. 前端生成 | 无服务端原子序号 | 不采用 |
biz_sample_reception 新增:
ALTER TABLE biz_sample_reception
ADD COLUMN lab_storage_code VARCHAR(32) NULL
COMMENT '实验室入库编码 RK+接收日期YYYYMMDD+3位序号'
AFTER sample_code;
索引(软删除场景):
CREATE UNIQUE INDEX uk_sr_tenant_lab_code
ON biz_sample_reception (tenant_no, lab_storage_code, del_flag);
说明:
del_flag=0时租户内唯一;历史软删记录保留原编码,不参与业务冲突。
复用现有 biz_biz_no_sequence,prefix = 'RK',biz_date = YYYYMMDD(来自接收时间日期)。
allocate_next_biz_no() 相同:INSERT ... ON DUPLICATE KEY UPDATE last_seq = LAST_INSERT_ID(last_seq + 1)用户手动输入 RK20260603088 且提交成功时,在同一事务内:
last_seq = MAX(last_seq, 88)
避免后续自动分配仍从较小序号开始导致撞号。
分配前亦取:
next_seq = MAX(last_seq, MAX(已存在编码末三位)) + 1
(限定同一 tenant_no + prefix + biz_date)
POST /web/admin/sample-receptions/allocate-lab-storage-codes
Body
| 字段 | 类型 | 说明 |
|---|---|---|
receptionDate |
string | 必填,YYYY-MM-DD,由前端从「样本接收时间」截取 |
count |
int | 默认 1,批量接收为 N,上限 50 |
Response
{ "codes": ["RK20260603001", "RK20260603002"] }
逻辑
receptionDate 格式biz_date = receptionDate.replace('-','') 原子递增 count 次权限:与样本接收提交一致(require_admin_or_super_admin 或现有接收写权限)。
SampleReceptionCreateBody / SampleReceptionUpdateBody 增加:
labStorageCode(仅 acceptConclusion=js 时必填;bj 时忽略且存 NULL;更新不可改)创建校验(acceptConclusion=js)
labStorageCode 非空^RK\d{8}\d{3}$YYYYMMDD 必须等于 receptionTime 的日期部分del_flag=0 唯一biz_biz_no_sequence.last_seq(见 §3.3)创建校验(acceptConclusion=bj)
labStorageCode(传入非空则 400,或一律忽略并置 NULL)更新
labStorageCode 变更(或 API 不接受该字段),保持原值_reception_to_dict / 列表项增加 labStorageCode;已接收列表关键词搜索纳入该字段。
js);此时:
allocate(receptionDate, count=1) → 填入「实验室入库编码」bj):
bj 切回 js:按当前接收时间日期重新 allocate(1)(除非编辑已保存的 js 记录)js):
allocate(1) 并替换js 时可编辑;编辑已保存 js 记录时 disabled;bj 记录不展示form-grid-3)<span class="form-label-tip">RK+接收日期+序号,保存后不可修改</span>receptionTime = now、acceptConclusion = jsacceptConclusion=js 的 entry 参与 allocate:按接收日期分组,allocate(count=组内 js 条数)js entry 增加 labStorageCode;改接收时间日期时,同 §5.1 规则bj → 隐藏该条编码字段并清空;切回 js → 为该条单独 re-allocatejs 携带 labStorageCode;bj 不传或传空js 展示「实验室入库编码」;bj 不展示该字段(或整块接收信息不含此项)js 显示编码;bj 显示 -js 记录)| 场景 | 处理 |
|---|---|
| 两人同时打开弹窗 | allocate 返回不同序号 |
| 用户改码为已存在编码 | 提交 400:「实验室入库编码已存在」 |
| 当日序号 > 999 | allocate / 提交返回明确错误 |
| 批量部分失败 | 已成功记录保留编码;失败项可重试(新 allocate) |
| 取消弹窗未提交 | 已占序号允许空号,不回滚 sequence |
js 改 bj 再提交 |
已 allocate 的序号允许空号;落库 lab_storage_code=NULL |
| 层级 | 文件 |
|---|---|
| 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 |
bj)生成或保存入库编码001/002/003...088 后,下一条自动分配 > 088js → 连续 5 码;混合两个日期 → 各日期独立序号js 占号后改 bj 提交 → 无入库编码;库中 lab_storage_code 为 NULLjs + 2 条 bj → 仅 3 条 allocate 并保存编码