日期: 2026-05-22 状态: 已确认(§1.3 一对多关系已被废弃,见
2026-06-13-slot-one-item-design.md:一格任何物品只能一个) 范围: 前后端全做(含细胞产品对接)
库位管理模块由两张表组成:
biz_storage_facility:树形结构(液氮罐 → 层 → 盒,is_leaf=1 为叶子存储节点)biz_storage_facility_slot:叶子节点下的储物格(如 A1-3),仅有 slot_code 和 slot_status("空"/"占用")细胞产品表 biz_cell_product 通过 storage_slot_id FK 指向 slot,但 slot 本身不知道存了什么。
slot_status 只有"空/占用",不记录物品类型、编码、来源| 维度 | 决策 |
|---|---|
| 储物格与物品关系 | 一对多 |
| 物品类型 | cell_product(细胞产品)+ other(其他,兜底) |
| 入库触发 | 细胞产品自动同步 + 手动入库(其他物品) |
| 出库触发 | 细胞产品状态变更自动出库 + 手动出库 |
| 历史记录 | 需要完整出入库历史 |
| 前端展示 | 点击储物格弹窗查看明细 + 手动出入库操作 |
| 实施范围 | 前后端全做 |
| 方案 | 描述 | 优点 | 缺点 | 结论 |
|---|---|---|---|---|
| A(采用) | 独立占用表 + Slot 缓存字段 | 统一查询入口、性能好、可扩展 | 双写需事务保证 | ✅ 推荐 |
| B | 纯占用表,无缓存 | 单一数据源 | 列表页性能差 | ❌ |
| C | 只加字段到 Slot 表 | 最简单 | 不支持一对多、无历史 | ❌ 不满足需求 |
biz_storage_slot_occupancyCREATE TABLE biz_storage_slot_occupancy (
id VARCHAR(64) NOT NULL COMMENT '主键ID',
slot_id VARCHAR(64) NOT NULL COMMENT '储物格ID (FK → biz_storage_facility_slot)',
item_type VARCHAR(32) NOT NULL COMMENT '物品类型: cell_product=细胞产品, other=其他',
item_id VARCHAR(64) NOT NULL COMMENT '物品ID (指向对应业务表主键)',
item_code VARCHAR(64) NULL COMMENT '物品编码 (冗余展示)',
item_name VARCHAR(200) NULL COMMENT '物品名称 (冗余展示)',
item_source VARCHAR(32) NULL COMMENT '来源: production=生产入库, manual=手动入库, transfer=转移',
stored_at DATETIME NULL COMMENT '入库时间',
removed_at DATETIME NULL COMMENT '出库时间 (NULL=当前在库)',
occupancy_status VARCHAR(16) NOT NULL DEFAULT 'stored' COMMENT 'stored=在库, removed=已出库',
-- 标准公共字段
tenant_no VARCHAR(64) NULL COMMENT '租户编号',
order_num INT DEFAULT 0 COMMENT '排序',
remark VARCHAR(500) NULL COMMENT '备注',
create_by VARCHAR(64) NULL COMMENT '创建者',
create_dept_id VARCHAR(64) NULL COMMENT '创建部门',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_by VARCHAR(64) NULL COMMENT '更新者',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
del_flag CHAR(1) DEFAULT '0' COMMENT '删除标志(0存在 1删除)',
PRIMARY KEY (id),
INDEX idx_sso_slot (slot_id, occupancy_status, del_flag),
INDEX idx_sso_item (item_type, item_id, del_flag),
INDEX idx_sso_tenant (tenant_no, del_flag)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='库位占用记录表';
biz_storage_facility_slotALTER TABLE biz_storage_facility_slot
ADD COLUMN occupied_count INT DEFAULT 0 COMMENT '当前占用数量',
ADD COLUMN last_item_type VARCHAR(32) NULL COMMENT '最近存储物品类型',
ADD COLUMN last_stored_at DATETIME NULL COMMENT '最近入库时间';
biz_storage_facility (树: 液氮罐→层→盒)
└── biz_storage_facility_slot (储物格, 新增: occupied_count, last_item_type, last_stored_at)
├── biz_storage_slot_occupancy (占用记录, 多态关联)
│ ├── item_type='cell_product' → item_id 指向 biz_cell_product.id
│ └── item_type='other' → item_id 为手动录入的标识
└── biz_cell_product.storage_slot_id (保留, 向后兼容)
occupied_count += 1)occupied_count -= 1)occupied_count 降到 0 时,slot_status 改回"空"| 文件 | 作用 |
|---|---|
backend/app/models/models.py |
新增 BizStorageSlotOccupancy 模型 |
backend/app/schemas/storage_occupancy.py |
新增,Pydantic schemas |
backend/app/crud/storage_occupancy.py |
新增,CRUD 层 |
backend/app/api/operations.py |
在现有 router 下新增路由 |
backend/app/crud/cell_product.py |
改造:入库/出库时同步写 occupancy |
backend/app/models/__init__.py |
新增导出 |
所有接口挂载在 /api/web/admin/ 下:
| 方法 | 路径 | 用途 |
|---|---|---|
| GET | /storage-slot-occupancies |
查询占用记录(按 slot_id / item_type / occupancy_status 筛选) |
| POST | /storage-slot-occupancies |
手动入库(item_type=other) |
| PUT | /storage-slot-occupancies/{id}/remove |
手动出库 |
| GET | /storage-slot-occupancies/history |
出入库历史(支持时间范围、物品类型筛选) |
GET /storage-slot-occupancies 查询参数:
slot_id(必传):储物格 IDoccupancy_status(可选,默认 stored):stored / removeditem_type(可选):cell_product / otherpage / page_size:分页GET /storage-slot-occupancies/history 查询参数:
slot_id(可选):储物格 IDfacility_node_id(可选):设施节点 ID(查该节点下所有格子的历史)item_type(可选)start_date / end_date(可选):时间范围page / page_size:分页| 函数 | 说明 |
|---|---|
list_occupancies(db, slot_id, status, item_type, page, page_size) |
分页查询占用记录 |
list_occupancy_history(db, slot_id, facility_node_id, item_type, start_date, end_date, page, page_size) |
出入库历史 |
create_occupancy(db, data, current_user) |
创建入库记录 + 更新 slot 缓存(同一事务)。item_type=other 时 item_id 自动生成 UUID |
remove_occupancy(db, occupancy_id, current_user) |
出库 + 更新 slot 缓存(同一事务) |
batch_create_occupancies(db, items, current_user) |
批量入库(细胞产品批量创建时调用) |
remove_by_item(db, item_type, item_id, current_user) |
按物品出库(细胞产品状态变更时调用) |
_refresh_slot_cache(db, slot_id) |
内部函数:刷新 slot 的 occupied_count / last_item_type / last_stored_at / slot_status |
| 触发点 | 改动 |
|---|---|
create_cell_product() |
创建后同步调用 create_occupancy() |
create_cell_products_batch() |
批量创建后同步调用 batch_create_occupancies() |
create_cell_products_by_quantity() |
同上 |
update_cell_product() |
状态变为 sold/used/voided 时调用 remove_by_item('cell_product', product_id) |
页面:web-admin/src/views/operations/storage-facility/index.vue
点击储物格 → 弹出占用明细弹窗,包含两个 Tab:
Tab 1:在库物品
| 列 | 说明 |
|---|---|
| 物品类型 | Tag 标签:cell_product=细胞产品, other=其他 |
| 物品编码 | item_code |
| 物品名称 | item_name |
| 来源 | production=生产入库, manual=手动入库, transfer=转移 |
| 入库时间 | stored_at |
| 操作 | [出库] 按钮 |
Tab 2:出入库历史
| 列 | 说明 |
|---|---|
| 物品类型 | 同上 |
| 物品编码 | item_code |
| 物品名称 | item_name |
| 来源 | item_source |
| 入库时间 | stored_at |
| 出库时间 | removed_at |
| 状态 | Tag:stored=在库, removed=已出库 |
弹窗底部「入库」按钮触发,表单字段:
| 字段 | 控件 | 必填 |
|---|---|---|
| 物品编码 | el-input | 是 |
| 物品名称 | el-input | 是 |
| 来源 | el-select(manual/transfer) | 是 |
| 备注 | el-input textarea | 否 |
提交后调用 POST /storage-slot-occupancies,刷新在库物品列表。
现有网格每个格子显示 occupied_count 数字角标。占用格子背景色保持橙色,角标显示数量。
页面:web-admin/src/views/production/cell-product/index.vue
改动极小:
文件:web-admin/src/api/admin.ts(追加)
| 函数 | 接口 |
|---|---|
fetchSlotOccupancies(params) |
GET /storage-slot-occupancies |
fetchSlotOccupancyHistory(params) |
GET /storage-slot-occupancies/history |
fetchCreateOccupancy(data) |
POST /storage-slot-occupancies |
fetchRemoveOccupancy(id) |
PUT /storage-slot-occupancies/{id}/remove |
文件:docs/sql/2026-05-22-storage-slot-occupancy.sql
包含:
CREATE TABLE biz_storage_slot_occupancyALTER TABLE biz_storage_facility_slot 增加缓存字段