2026-06-03-sample-reception-lab-storage-code-design.md 7.9 KB

样本接收 — 实验室入库编码

日期: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 新增:

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 时租户内唯一;历史软删记录保留原编码,不参与业务冲突。

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

{ "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 并保存编码