This commit is contained in:
zlwu committed 2026-05-23 11:41:57 +08:00
1 parent 19de2040d0
commit 3779de12ce
78 files changed
+23068 -13824

No files matched your search

@@ -0,0 +1,843 @@
# 入库放行审核 实施计划
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 在质检任务菜单下新增"入库放行审核"功能,实现样本级别的放行审核和入库操作。
**Architecture:** 新建独立审核表 `biz_storage_release_review`,在样本接收完成时自动创建审核记录。后端新建完整的 Model → Schema → CRUD → API 路由链路。前端参照 `protocol-audit` 模式构建列表页 + 查看弹窗 + 审核弹窗。
**Tech Stack:** FastAPI + SQLAlchemy + Pydantic v2(后端),Vue 3 + Element Plus + TypeScript(前端)
**Design Spec:** `docs/superpowers/specs/2026-05-23-storage-release-review-design.md`
---
## File Structure
| 操作 | 文件路径 | 职责 |
|------|----------|------|
| 新增 | `docs/sql/biz_storage_release_review.sql` | DDL + 菜单 SQL |
| 修改 | `backend/app/models/models.py` | 新增 BizStorageReleaseReview 模型 |
| 新增 | `backend/app/schemas/storage_release_review.py` | Pydantic schemas |
| 新增 | `backend/app/crud/storage_release_review.py` | CRUD + 聚合查询 |
| 新增 | `backend/app/api/storage_release_review.py` | API 路由 |
| 修改 | `backend/app/api/__init__.py` | 注册新路由 |
| 修改 | `backend/app/main.py` | 注册新路由 |
| 修改 | `backend/app/crud/sample_reception.py` | 接收完成时自动创建审核记录 |
| 新增 | `web-admin/src/api/storage-release-review.ts` | 前端 API 模块 |
| 新增 | `web-admin/src/views/quality-inspection/storage-release-review/index.vue` | 列表页 |
| 新增 | `web-admin/src/views/quality-inspection/storage-release-review/StorageReleaseViewDialog.vue` | 查看弹窗 |
| 新增 | `web-admin/src/views/quality-inspection/storage-release-review/StorageReleaseReviewDialog.vue` | 审核弹窗 |
| 修改 | `web-admin/src/router/modules/sqxp.ts` | 添加路由 |
| 修改 | `web-admin/src/utils/status-tag-registry.ts` | 注册状态标签 |
---
## Task 1: SQL DDL + 菜单 SQL
**Files:**
- Create: `docs/sql/biz_storage_release_review.sql`
- [ ] **Step 1: 创建 SQL 文件**
创建 `docs/sql/biz_storage_release_review.sql`,包含建表 DDL 和菜单插入语句:
```sql
-- 入库放行审核表
CREATE TABLE `biz_storage_release_review` (
`id` VARCHAR(64) NOT NULL COMMENT '主键ID',
`sample_code` VARCHAR(64) NOT NULL COMMENT '样本编码(关联键)',
`reception_source` VARCHAR(32) NOT NULL COMMENT '来源:cell_storage=采集/research_donation=捐赠',
`protocol_id` VARCHAR(64) DEFAULT NULL COMMENT '协议单ID',
`reception_id` VARCHAR(64) DEFAULT NULL COMMENT '样本接收记录ID',
`protocol_no` VARCHAR(64) DEFAULT NULL COMMENT '协议编号',
`sample_name` VARCHAR(128) DEFAULT NULL COMMENT '样本名称',
`review_conclusion` VARCHAR(16) DEFAULT NULL COMMENT '审核结论:tg=通过/btg=不通过/NULL=待审核',
`review_remark` VARCHAR(512) DEFAULT NULL COMMENT '审核备注',
`review_attachments` VARCHAR(4000) DEFAULT NULL COMMENT '审核附件(逗号分隔URL)',
`review_by` VARCHAR(64) DEFAULT NULL COMMENT '审核人ID',
`review_by_name` VARCHAR(64) DEFAULT NULL COMMENT '审核人姓名',
`review_time` DATETIME DEFAULT NULL COMMENT '审核时间',
`storage_status` VARCHAR(16) NOT NULL DEFAULT 'drk' COMMENT '入库状态:drk=待入库/yrk=已入库',
`storage_by` VARCHAR(64) DEFAULT NULL COMMENT '入库操作人ID',
`storage_by_name` VARCHAR(64) DEFAULT NULL COMMENT '入库操作人姓名',
`storage_time` DATETIME DEFAULT NULL COMMENT '入库时间',
`tenant_no` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '租户编号',
`order_num` INT(11) DEFAULT NULL COMMENT '排序号',
`remark` VARCHAR(512) DEFAULT NULL COMMENT '备注',
`create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人ID',
`create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人ID',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`del_flag` TINYINT(4) NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除',
PRIMARY KEY (`id`),
KEY `idx_srr_sample` (`tenant_no`, `sample_code`, `del_flag`),
KEY `idx_srr_protocol` (`tenant_no`, `protocol_id`, `del_flag`),
KEY `idx_srr_status` (`tenant_no`, `review_conclusion`, `storage_status`, `del_flag`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='入库放行审核';
```
菜单 SQL 需要在 `sys_menu` 表中插入一条记录,参照现有质检任务子菜单的 ID 规则(如 `70000000000000000000000000000008` 为质检任务)。新菜单 ID 使用 `70000000000000000000000000000011`(递增),parent_id 为质量检测节点 `70000000000000000000000000000001`。
- [ ] **Step 2: 在数据库中执行建表语句**
在开发环境的 MySQL 中执行 DDL,确认表创建成功。
- [ ] **Step 3: 验证**
Run: 检查表是否创建成功
```sql
SHOW CREATE TABLE biz_storage_release_review;
```
Expected: 表结构正确,索引存在
---
## Task 2: 后端 Model(SQLAlchemy)
**Files:**
- Modify: `backend/app/models/models.py`
**模板参照:** 同文件中 `BizProtocolAudit` 类(line 743-761)
- [ ] **Step 1: 在 models.py 中新增 BizStorageReleaseReview 模型**
在 `BizProtocolAudit` 类定义之后(约 line 762)添加:
```python
class BizStorageReleaseReview(Base):
"""入库放行审核 biz_storage_release_review"""
__tablename__ = "biz_storage_release_review"
id = Column(String(64), primary_key=True, comment="主键ID")
sample_code = Column(String(64), nullable=False, comment="样本编码")
reception_source = Column(String(32), nullable=False, comment="来源:cell_storage/research_donation")
protocol_id = Column(String(64), nullable=True, comment="协议单ID")
reception_id = Column(String(64), nullable=True, comment="样本接收记录ID")
protocol_no = Column(String(64), nullable=True, comment="协议编号")
sample_name = Column(String(128), nullable=True, comment="样本名称")
review_conclusion = Column(String(16), nullable=True, comment="审核结论:tg/btg/NULL")
review_remark = Column(String(512), nullable=True, comment="审核备注")
review_attachments = Column(String(4000), nullable=True, comment="审核附件(逗号分隔URL)")
review_by = Column(String(64), nullable=True, comment="审核人ID")
review_by_name = Column(String(64), nullable=True, comment="审核人姓名")
review_time = Column(DateTime, nullable=True, comment="审核时间")
storage_status = Column(String(16), nullable=False, default="drk", comment="入库状态:drk/yrk")
storage_by = Column(String(64), nullable=True, comment="入库操作人ID")
storage_by_name = Column(String(64), nullable=True, comment="入库操作人姓名")
storage_time = Column(DateTime, nullable=True, comment="入库时间")
tenant_no = Column(String(64), nullable=False, default="", comment="租户编号")
order_num = Column(Integer, nullable=True, comment="排序号")
remark = Column(String(512), nullable=True, comment="备注")
create_by = Column(String(64), nullable=True, comment="创建人ID")
create_dept_id = Column(String(64), nullable=False, default="", comment="创建人部门ID")
create_time = Column(DateTime, nullable=False, default=datetime.now, comment="创建时间")
update_by = Column(String(64), nullable=True, comment="更新人ID")
update_time = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now, comment="更新时间")
del_flag = Column(SmallInteger, nullable=False, default=0, comment="删除标记")
```
- [ ] **Step 2: 验证无语法错误**
Run: `cd backend && python -c "from app.models.models import BizStorageReleaseReview; print('OK')"`
Expected: `OK`
---
## Task 3: 后端 Schema(Pydantic)
**Files:**
- Create: `backend/app/schemas/storage_release_review.py`
**模板参照:** 同目录下的其他 schema 文件,如 `order.py`(含 Base/Create/Update/Response 模式)
- [ ] **Step 1: 创建 Schema 文件**
创建 `backend/app/schemas/storage_release_review.py`,包含:
```python
"""入库放行审核 Pydantic schemas"""
from datetime import datetime
from typing import Any, Dict, List, Optional
from pydantic import BaseModel, Field
class StorageReleaseReviewBase(BaseModel):
sample_code: str = Field(..., description="样本编码")
reception_source: str = Field(..., description="来源:cell_storage/research_donation")
protocol_id: Optional[str] = Field(None, description="协议单ID")
reception_id: Optional[str] = Field(None, description="样本接收记录ID")
protocol_no: Optional[str] = Field(None, description="协议编号")
sample_name: Optional[str] = Field(None, description="样本名称")
class StorageReleaseReviewListQuery(BaseModel):
"""列表查询参数"""
page: int = Field(1, ge=1)
pageSize: int = Field(20, ge=1, le=200)
reception_source: Optional[str] = Field(None, description="来源筛选")
review_conclusion: Optional[str] = Field(None, description="审核结论筛选")
storage_status: Optional[str] = Field(None, description="入库状态筛选")
keyword: Optional[str] = Field(None, description="关键词搜索")
class ReviewSubmitBody(BaseModel):
"""审核提交请求体"""
review_conclusion: str = Field(..., description="审核结论:tg/btg")
review_remark: Optional[str] = Field(None, description="审核备注")
review_attachments: Optional[str] = Field(None, description="审核附件(逗号分隔URL)")
class BatchReviewBody(BaseModel):
"""批量审核请求体"""
ids: List[str] = Field(..., description="审核记录ID列表")
review_conclusion: str = Field(..., description="审核结论:tg/btg")
review_remark: Optional[str] = Field(None, description="审核备注")
review_attachments: Optional[str] = Field(None, description="审核附件(逗号分隔URL)")
class BatchStoreBody(BaseModel):
"""批量入库请求体"""
ids: List[str] = Field(..., description="入库记录ID列表")
class StorageReleaseReviewListItem(BaseModel):
"""列表项"""
id: str
sample_code: str
reception_source: str
protocol_no: Optional[str] = None
sample_name: Optional[str] = None
review_conclusion: Optional[str] = None
review_remark: Optional[str] = None
review_attachments: Optional[str] = None
review_by_name: Optional[str] = None
review_time: Optional[datetime] = None
storage_status: str = "drk"
storage_by_name: Optional[str] = None
storage_time: Optional[datetime] = None
create_time: Optional[datetime] = None
class StorageReleaseReviewDetail(BaseModel):
"""详情(含聚合信息)"""
review: Dict[str, Any] = {}
protocol_info: Optional[Dict[str, Any]] = None
transport_info: Optional[Dict[str, Any]] = None
reception_info: Optional[Dict[str, Any]] = None
qc_info: Optional[Dict[str, Any]] = None
production_info: Optional[Dict[str, Any]] = None
```
- [ ] **Step 2: 验证导入无错误**
Run: `cd backend && python -c "from app.schemas.storage_release_review import StorageReleaseReviewBase; print('OK')"`
Expected: `OK`
---
## Task 4: 后端 CRUD
**Files:**
- Create: `backend/app/crud/storage_release_review.py`
**模板参照:**
- `backend/app/crud/qc_work_order.py` — 跨表聚合查询模式(含 `_RECEPTION_SOURCE_LABELS`、`urls_to_attachment_list`)
- `backend/app/crud/sample_reception.py` — 租户隔离 + 分页模式(`_page_payload`)
- [ ] **Step 1: 创建 CRUD 文件**
创建 `backend/app/crud/storage_release_review.py`,实现以下函数:
```python
"""入库放行审核 CRUD"""
from __future__ import annotations
from datetime import datetime
from typing import Any, Dict, List, Optional
from sqlalchemy import and_, func, or_
from sqlalchemy.orm import Session
from app.core.utils import generate_uuid
from app.models.models import (
BizStorageReleaseReview,
BizSampleReception,
BizCollectionProtocol,
BizProtocolSample,
BizProtocolSampleTransport,
BizProtocolPartyCustomer,
BizQcWorkOrder,
BizQcTask,
BizProductionWorkOrder,
BizProductionTask,
SysUser,
)
from app.models.research_donation import (
BizResearchDonation,
BizResearchDonationSample,
BizResearchDonationSampleTransport,
BizResearchDonationParty,
)
```
**核心函数清单:**
1. `create_review_from_reception(db, *, reception, protocol_no, sample_name, tenant_no, user_id, create_dept_id) -> BizStorageReleaseReview`
- 在样本接收完成时调用
- INSERT 一条新记录,review_conclusion=NULL, storage_status='drk'
- 冗余写入 sample_code, reception_source, protocol_id, reception_id, protocol_no, sample_name
2. `list_reviews(db, *, tenant_no, page, page_size, reception_source=None, review_conclusion=None, storage_status=None, keyword=None) -> dict`
- 分页查询 `biz_storage_release_review`
- 支持筛选和关键词搜索(protocol_no / sample_code / sample_name)
- 默认排序 create_time DESC
- 返回 `{list, total, page, pageSize, totalPages}` 格式
3. `get_review_detail(db, review_id, tenant_no) -> dict`
- 聚合查询全链路信息
- 根据 reception_source JOIN 不同的协议表和运输表
- JOIN sample_reception 获取接收信息
- JOIN qc_work_order + qc_task 获取质检信息
- JOIN production_work_order + production_task 获取生产信息
- 返回结构: `{review, protocol_info, transport_info, reception_info, qc_info, production_info}`
4. `submit_review(db, review_id, *, review_conclusion, review_remark, review_attachments, tenant_no, user_id) -> dict`
- 验证记录存在 + 租户隔离
- UPDATE review_conclusion, review_remark, review_attachments, review_by, review_by_name, review_time
- 支持反复修改
5. `batch_submit_review(db, ids, *, review_conclusion, review_remark, review_attachments, tenant_no, user_id) -> dict`
- 批量 UPDATE,逐条处理
- 返回成功数量
6. `store_sample(db, review_id, *, tenant_no, user_id) -> dict`
- 前置条件:review_conclusion='tg' AND storage_status='drk'
- UPDATE storage_status='yrk', storage_by, storage_by_name, storage_time
- 同步 UPDATE biz_protocol_sample.sample_status='yrk'(仅采集协议,通过 sample_code + tenant_no 关联)
7. `batch_store_samples(db, ids, *, tenant_no, user_id) -> dict`
- 批量入库,逐条检查前置条件
- 返回成功/失败数量
**辅助函数:**
```python
_RECEPTION_SOURCE_LABELS = {"cell_storage": "采集协议", "research_donation": "捐赠协议"}
_CONCLUSION_LABELS = {"tg": "通过", "btg": "不通过"}
_STORAGE_STATUS_LABELS = {"drk": "待入库", "yrk": "已入库"}
```
- [ ] **Step 2: 验证导入无错误**
Run: `cd backend && python -c "from app.crud.storage_release_review import list_reviews; print('OK')"`
Expected: `OK`
---
## Task 5: 后端 API 路由
**Files:**
- Create: `backend/app/api/storage_release_review.py`
- Modify: `backend/app/api/__init__.py`
- Modify: `backend/app/main.py`
**模板参照:** `backend/app/api/sample_reception.py` — 路由定义模式、`_get_tenant_no`、`_get_current_user_id`、`success_response`、`error_response`、`_page_payload`
- [ ] **Step 1: 创建 API 路由文件**
创建 `backend/app/api/storage_release_review.py`,参照 `sample_reception.py` 的模式:
```python
"""入库放行审核 API"""
from fastapi import APIRouter, Depends, Query
from sqlalchemy.orm import Session
from app.core.database import get_db
from app.core.auth import require_admin_or_super_admin
from app.schemas.storage_release_review import (
ReviewSubmitBody,
BatchReviewBody,
BatchStoreBody,
)
from app.crud import storage_release_review as svc
# ... 导入辅助函数
router = APIRouter(prefix="/storage-release-reviews", tags=["入库放行审核"])
```
**端点清单:**
```python
@router.get("")
async def list_storage_release_reviews(
page: int = Query(1), pageSize: int = Query(20),
reception_source: str = Query(None),
review_conclusion: str = Query(None),
storage_status: str = Query(None),
keyword: str = Query(None),
db: Session = Depends(get_db),
user: dict = Depends(require_admin_or_super_admin),
):
"""分页列表"""
...
@router.get("/{review_id}/detail")
async def get_storage_release_review_detail(
review_id: str,
db: Session = Depends(get_db),
user: dict = Depends(require_admin_or_super_admin),
):
"""详情(聚合全链路)"""
...
@router.post("/{review_id}/review")
async def submit_review(
review_id: str, body: ReviewSubmitBody,
db: Session = Depends(get_db),
user: dict = Depends(require_admin_or_super_admin),
):
"""提交/修改审核"""
...
@router.post("/batch-review")
async def batch_review(
body: BatchReviewBody,
db: Session = Depends(get_db),
user: dict = Depends(require_admin_or_super_admin),
):
"""批量审核"""
...
@router.post("/{review_id}/store")
async def store_sample(
review_id: str,
db: Session = Depends(get_db),
user: dict = Depends(require_admin_or_super_admin),
):
"""样本入库"""
...
@router.post("/batch-store")
async def batch_store(
body: BatchStoreBody,
db: Session = Depends(get_db),
user: dict = Depends(require_admin_or_super_admin),
):
"""批量入库"""
...
```
- [ ] **Step 2: 注册路由**
修改 `backend/app/api/__init__.py`:
```python
from app.api.storage_release_review import router as storage_release_review_router
# 添加到 __all__ 列表
```
修改 `backend/app/main.py`:
```python
# 导入行添加
from app.api import ..., storage_release_review_router
# 注册行添加
app.include_router(storage_release_review_router, prefix=settings.API_V1_PREFIX)
```
- [ ] **Step 3: 验证路由注册成功**
Run: `cd backend && python -c "from app.main import app; routes = [r.path for r in app.routes if hasattr(r, 'path')]; print([r for r in routes if 'storage' in r])"`
Expected: 包含 `/api/storage-release-reviews` 相关路径
- [ ] **Step 4: 验证 Swagger 文档**
启动后端服务,访问 `/docs`,确认"入库放行审核"分组下有 6 个端点。
---
## Task 6: 后端触发器 — 样本接收时自动创建审核记录
**Files:**
- Modify: `backend/app/crud/sample_reception.py`
**修改位置:** `create_sample_reception` 函数中,`conclusion == "js"` 分支内(约 line 784-802),在创建 QC 工单和生产工单之后添加审核记录创建逻辑。
- [ ] **Step 1: 在 sample_reception.py 的 create_sample_reception 函数中添加触发逻辑**
在 line 802(`prod_wo_svc.create_production_work_order_from_reception(...)` 之后)添加:
```python
# 自动创建入库放行审核记录
from app.crud import storage_release_review as srr_svc
# 获取协议编号和样本名称
_protocol_no = ""
_sample_name = ""
if source == "cell_storage":
cp = (
db.query(BizCollectionProtocol)
.filter(BizCollectionProtocol.id == collection_protocol_id)
.first()
)
if cp:
_protocol_no = cp.protocol_no or ""
if sample:
_sample_name = sample.sample_name or sample.sample_code or ""
else:
rd = (
db.query(BizResearchDonation)
.filter(BizResearchDonation.id == _research_donation_id)
.first()
)
if rd:
_protocol_no = rd.protocol_no or ""
if dsample:
_sample_name = dsample.sample_name or dsample.sample_code or ""
srr_svc.create_review_from_reception(
db,
reception=obj,
protocol_no=_protocol_no,
sample_name=_sample_name,
tenant_no=tenant_no,
user_id=user_id,
create_dept_id=create_dept_id or "",
)
```
- [ ] **Step 2: 验证**
Run: `cd backend && python -c "from app.crud.sample_reception import create_sample_reception; print('OK')"`
Expected: `OK`
---
## Task 7: 前端 API 模块
**Files:**
- Create: `web-admin/src/api/storage-release-review.ts`
**模板参照:** `web-admin/src/api/qc-task.ts` 或 `web-admin/src/api/collection-protocol.ts`
- [ ] **Step 1: 创建前端 API 文件**
创建 `web-admin/src/api/storage-release-review.ts`:
```typescript
import request from '@/utils/request'
const BASE = '/api/admin/storage-release-reviews'
/** 分页列表 */
export function fetchStorageReleaseReviewList(params: {
page: number
pageSize: number
receptionSource?: string
reviewConclusion?: string
storageStatus?: string
keyword?: string
}) {
return request({ url: BASE, method: 'get', params })
}
/** 详情(聚合全链路) */
export function fetchStorageReleaseReviewDetail(id: string) {
return request({ url: `${BASE}/${id}/detail`, method: 'get' })
}
/** 提交/修改审核 */
export function submitStorageReleaseReview(
id: string,
data: {
reviewConclusion: string
reviewRemark?: string
reviewAttachments?: string
},
) {
return request({ url: `${BASE}/${id}/review`, method: 'post', data })
}
/** 批量审核 */
export function batchReviewStorageRelease(data: {
ids: string[]
reviewConclusion: string
reviewRemark?: string
reviewAttachments?: string
}) {
return request({ url: `${BASE}/batch-review`, method: 'post', data })
}
/** 样本入库 */
export function storeSample(id: string) {
return request({ url: `${BASE}/${id}/store`, method: 'post' })
}
/** 批量入库 */
export function batchStoreSample(data: { ids: string[] }) {
return request({ url: `${BASE}/batch-store`, method: 'post', data })
}
```
---
## Task 8: 前端状态标签注册
**Files:**
- Modify: `web-admin/src/utils/status-tag-registry.ts`
- [ ] **Step 1: 添加入库放行审核的状态标签**
在 `status-tag-registry.ts` 中新增:
```typescript
// 入库放行审核结论
storageReleaseConclusion: {
tg: { label: '通过', type: 'success' },
btg: { label: '不通过', type: 'danger' },
} as Record<string, { label: string; type: string }>,
// 入库状态
storageStatus: {
drk: { label: '待入库', type: 'warning' },
yrk: { label: '已入库', type: 'success' },
} as Record<string, { label: string; type: string }>,
```
同时添加协议类别标签:
```typescript
// 协议类别
receptionSource: {
cell_storage: { label: '采集协议', type: 'primary' },
research_donation: { label: '捐赠协议', type: 'success' },
} as Record<string, { label: string; type: string }>,
```
---
## Task 9: 前端列表页
**Files:**
- Create: `web-admin/src/views/quality-inspection/storage-release-review/index.vue`
- Modify: `web-admin/src/router/modules/sqxp.ts`
**模板参照(严格遵循):**
- `web-admin/src/views/cell-storage/protocol-audit/index.vue` — 列表页结构(筛选 + 多选 + 批量审核工具栏)
- `web-admin/src/views/quality-inspection/qc-task/index.vue` — 质检任务列表(状态标签、操作按钮条件显示)
- `web-admin/src/views/system/unit/index.vue` — 标准列表页模板
**UI 规范约束(来自 AGENTS.md):**
- 列表面板标题用 `panel-top` + `h2.title-bar-heading`,显示绿色竖线
- 工具栏:"已选 N 项"在前,然后主操作按钮(只有一个 `type="primary"`),不用 `space-between`
- 表格操作列 `fixed="right"`,`min-width >= 260px`
- 状态字段用 `<el-tag>` + 语义颜色
- 排序字段默认不展示
- [ ] **Step 1: 在 sqxp.ts 中添加路由**
在质量检测(QualityInspection)路由组的 children 数组末尾(`qc-material-usage` 之后)添加:
```typescript
{
id: '70000000000000000000000000000011',
name: 'QualityInspectionStorageReleaseReview',
path: 'storage-release-review',
component: '/quality-inspection/storage-release-review/index',
meta: {
title: '入库放行审核',
keepAlive: true,
menuCode: 'biz:storageReleaseReview:list',
}
},
```
- [ ] **Step 2: 创建列表页组件**
创建 `web-admin/src/views/quality-inspection/storage-release-review/index.vue`。
核心结构:
**筛选区域:**
- 协议类别(el-select):全部 / 采集协议(cell_storage) / 捐赠协议(research_donation)
- 审核状态(el-select):全部 / 待审核(null) / 通过(tg) / 不通过(btg)
- 入库状态(el-select):全部 / 待入库(drk) / 已入库(yrk)
- 搜索关键词(el-input)
**工具栏:**
- 已选 N 项
- [批量审核](type="primary")
- [批量入库](plain)
**表格列(el-table):**
- type=selection
- 协议编号 (protocol_no, width=160)
- 样本编码 (sample_code, width=140)
- 样本名称 (sample_name, width=140)
- 协议类别 (reception_source → el-tag: primary/success, width=100)
- 审核结论 (review_conclusion → el-tag: null→info"待审核", tg→success, btg→danger, width=100)
- 入库状态 (storage_status → el-tag: drk→warning"待入库", yrk→success"已入库", width=100)
- 审核人 (review_by_name, width=100)
- 审核时间 (review_time, width=170)
- 操作 (fixed=right, min-width=260)
- [查看] — 始终
- [审核] — 始终
- [样本入库] — 仅 review_conclusion==='tg' && storage_status==='drk'
**组件交互:**
- 点击[查看] → 打开 `StorageReleaseViewDialog`
- 点击[审核] → 打开 `StorageReleaseReviewDialog`
- 点击[样本入库] → 确认弹窗 → 调用 `storeSample` API → 刷新列表
- 批量审核 → 打开 `StorageReleaseReviewDialog`(批量模式)
- 批量入库 → 确认弹窗 → 调用 `batchStoreSample` API → 刷新列表
- [ ] **Step 3: 验证页面加载**
启动前端 dev server,访问 `/#/quality-inspection/storage-release-review`,确认页面渲染正常,表格显示。
---
## Task 10: 前端查看弹窗
**Files:**
- Create: `web-admin/src/views/quality-inspection/storage-release-review/StorageReleaseViewDialog.vue`
**模板参照:**
- `web-admin/src/components/business/qc-task-view-dialog/index.vue` — 多分区信息展示弹窗
- `web-admin/src/components/business/sample-qc-info-cards/index.vue` — QC 信息卡片组件
- `web-admin/src/components/business/system-view-dialog/index.vue` — 通用查看弹窗
**UI 规范约束:**
- Dialog forms: `label-position="top"`
- 无 backdrop blur (`backdrop-filter: none`)
- 使用 `.dialog-form-layout` 布局
- [ ] **Step 1: 创建查看弹窗组件**
创建 `StorageReleaseViewDialog.vue`,props:
```typescript
const props = defineProps<{
visible: boolean
reviewId: string
}>()
const emit = defineEmits(['update:visible'])
```
加载详情数据:调用 `fetchStorageReleaseReviewDetail(reviewId)`
**内部分区(每个分区用 `▎` 标题栏样式):**
1. **▎基础信息** — 协议编号、协议类别(el-tag)、样本编码、样本名称、客户姓名、联系电话
2. **▎运输信息** — 运输方式、运输温度、运输时长、运输公司
3. **▎接收信息** — 接收时间、接收人、接收结论(el-tag: js→success/bj→danger)、检查项表格
4. **▎质检信息** — 使用 `SampleQcInfoCards` 或自定义卡片展示工单+任务列表
5. **▎生产信息** — 生产工单编号、工单状态(el-tag)、任务列表(名称+状态el-tag)
6. **▎放行审核** — 审核结论(el-tag)、审核人、审核时间、审核备注、审核附件(预览/下载)
所有区域只读展示。
底部按钮:[关闭]
- [ ] **Step 2: 在列表页中引入并测试**
在 `index.vue` 中引入 `StorageReleaseViewDialog`,点击[查看]时传入 reviewId 并打开弹窗。
---
## Task 11: 前端审核弹窗
**Files:**
- Create: `web-admin/src/views/quality-inspection/storage-release-review/StorageReleaseReviewDialog.vue`
**模板参照:**
- `web-admin/src/views/cell-storage/protocol/ProtocolAuditDialog.vue` — 审核弹窗模式(结论 radio + 备注 textarea + 批量支持)
- `web-admin/src/views/research-donation/list/DonationAuditDialog.vue` — 捐赠审核弹窗(同模式)
**UI 规范约束:**
- Dialog forms: `label-position="top"`
- 无 backdrop blur
- 附件上传使用卡片化布局
- [ ] **Step 1: 创建审核弹窗组件**
创建 `StorageReleaseReviewDialog.vue`,props:
```typescript
const props = defineProps<{
visible: boolean
reviewIds: string[] // 支持单条和批量
}>()
const emit = defineEmits(['update:visible', 'submitted'])
```
**模式切换:**
- `reviewIds.length === 1` → 单条模式:上方展示详情信息(同查看弹窗)+ 下方审核表单
- `reviewIds.length > 1` → 批量模式:仅显示审核表单
**审核表单:**
- 审核结论: `el-radio-group`
- `el-radio value="tg"` 通过
- `el-radio value="btg"` 不通过
- 备注: `el-input type="textarea" :rows="3"`
- 附件: 使用现有上传组件,参照 `ProtocolFinanceConfirmFields` 的模式
- 上传触发为卡片(140×96px, 虚线边框 `#a7f3d0`)
- inline 排列在已上传附件同一行
- 选择文件后立即上传
底部按钮:[取消] [确定]
**提交逻辑:**
- 单条:调用 `submitStorageReleaseReview(id, data)`
- 批量:调用 `batchReviewStorageReview({ ids, ...data })`
- 成功后 emit `submitted`,父组件刷新列表
- [ ] **Step 2: 在列表页中引入并测试**
在 `index.vue` 中引入 `StorageReleaseReviewDialog`,点击[审核]或[批量审核]时传入 reviewIds 并打开弹窗。
---
## Task 12: 端到端验证
- [ ] **Step 1: 后端 API 验证**
启动后端,通过 Swagger `/docs` 测试所有 6 个端点:
1. GET 列表 — 返回分页数据
2. GET 详情 — 返回聚合信息
3. POST 审核 — 更新结论
4. POST 批量审核 — 批量更新
5. POST 入库 — 更新状态
6. POST 批量入库 — 批量更新状态
- [ ] **Step 2: 前端功能验证**
1. 创建一个采集协议单 → 添加样本 → 样本接收(结论=接收)
2. 确认"入库放行审核"列表中出现该样本
3. 点击[查看] → 确认所有信息区域正确展示
4. 点击[审核] → 填写结论和备注 → 提交 → 确认列表更新
5. 点击[样本入库] → 确认状态变为"已入库"
6. 测试批量审核和批量入库
7. 测试修改已审核的记录
- [ ] **Step 3: 更新知识图谱**
Run: `./scripts/update-graph.sh all`
Expected: 图谱增量更新完成
- [ ] **Step 4: 提交**
```bash
git add docs/sql/biz_storage_release_review.sql
git add backend/app/models/models.py
git add backend/app/schemas/storage_release_review.py
git add backend/app/crud/storage_release_review.py
git add backend/app/api/storage_release_review.py
git add backend/app/api/__init__.py
git add backend/app/main.py
git add backend/app/crud/sample_reception.py
git add web-admin/src/api/storage-release-review.ts
git add web-admin/src/views/quality-inspection/storage-release-review/
git add web-admin/src/router/modules/sqxp.ts
git add web-admin/src/utils/status-tag-registry.ts
git commit -m "feat: add storage release review module (入库放行审核)"
```
@@ -0,0 +1,404 @@
# 入库放行审核 — 设计文档
> 日期:2026-05-23
> 状态:待审阅
> 模块:质检任务 → 入库放行审核
## 1. 背景与目标
### 现状
当前系统流程:采集协议单/捐赠协议单录入 → 样本接收 → 入库质检工单(含实验室自检和第三方检测)→ 入库生产工单 → 细胞产品入库。
### 需求
客户要求在上述流程中增加一个"入库放行审核"环节。所有采集协议单和捐赠协议单的样本,在经过接收、质检、生产后,需经过放行审核通过后才算真正通过并存入库中。
### 目标
在【质检任务】菜单下新增【入库放行审核】子菜单,提供:
1. 样本级别的放行审核列表(采集协议 + 捐赠协议混合展示)
2. 查看每个样本的全链路信息(基础、运输、接收、质检、生产)
3. 审核操作(通过/不通过 + 备注 + 附件),支持批量
4. 样本入库操作,支持批量
5. 审核后可继续修改结论、备注、附件
## 2. 需求确认记录
| 维度 | 决定 |
|------|------|
| 审核粒度 | 样本级别 — 每个样本一行,独立审核 |
| 显示时机 | 样本接收后(accept_conclusion=js)即显示在列表中 |
| 结论选项 | 通过 / 不通过(tg / btg) |
| 审核后动作 | 仅记录结论;另设"样本入库"按钮单独触发入库 |
| 入库动作 | 仅更新样本状态,不涉及库位分配 |
| 批量操作 | 支持批量审核 + 批量入库 |
| 审核可修改 | 审核完成后可继续修改结论、备注、附件 |
## 3. 数据模型
### 3.1 新增表 `biz_storage_release_review`
```sql
CREATE TABLE `biz_storage_release_review` (
-- 主键
`id` VARCHAR(64) NOT NULL COMMENT '主键ID',
-- 业务字段
`sample_code` VARCHAR(64) NOT NULL COMMENT '样本编码(关联键)',
`reception_source` VARCHAR(32) NOT NULL COMMENT '来源:cell_storage=采集/research_donation=捐赠',
`protocol_id` VARCHAR(64) DEFAULT NULL COMMENT '协议单ID(冗余加速查询)',
`reception_id` VARCHAR(64) DEFAULT NULL COMMENT '样本接收记录ID',
`protocol_no` VARCHAR(64) DEFAULT NULL COMMENT '协议编号(冗余展示用)',
`sample_name` VARCHAR(128) DEFAULT NULL COMMENT '样本名称(冗余展示用)',
-- 审核字段
`review_conclusion` VARCHAR(16) DEFAULT NULL COMMENT '审核结论:tg=通过/btg=不通过/NULL=待审核',
`review_remark` VARCHAR(512) DEFAULT NULL COMMENT '审核备注',
`review_attachments` VARCHAR(4000) DEFAULT NULL COMMENT '审核附件(逗号分隔URL)',
`review_by` VARCHAR(64) DEFAULT NULL COMMENT '审核人ID',
`review_by_name` VARCHAR(64) DEFAULT NULL COMMENT '审核人姓名',
`review_time` DATETIME DEFAULT NULL COMMENT '审核时间',
-- 入库字段
`storage_status` VARCHAR(16) NOT NULL DEFAULT 'drk' COMMENT '入库状态:drk=待入库/yrk=已入库',
`storage_by` VARCHAR(64) DEFAULT NULL COMMENT '入库操作人ID',
`storage_by_name` VARCHAR(64) DEFAULT NULL COMMENT '入库操作人姓名',
`storage_time` DATETIME DEFAULT NULL COMMENT '入库时间',
-- 公共字段
`tenant_no` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '租户编号',
`order_num` INT(11) DEFAULT NULL COMMENT '排序号',
`remark` VARCHAR(512) DEFAULT NULL COMMENT '备注',
`create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人ID',
`create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人ID',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`del_flag` TINYINT(4) NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除',
PRIMARY KEY (`id`),
KEY `idx_srr_sample` (`tenant_no`, `sample_code`, `del_flag`),
KEY `idx_srr_protocol` (`tenant_no`, `protocol_id`, `del_flag`),
KEY `idx_srr_status` (`tenant_no`, `review_conclusion`, `storage_status`, `del_flag`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='入库放行审核';
```
### 3.2 跨表关联
```
biz_storage_release_review
├─ sample_code + tenant_no → biz_protocol_sample / biz_research_donation_sample
├─ protocol_id → biz_collection_protocol / biz_research_donation(基础信息)
├─ sample_code → biz_protocol_sample_transport / biz_research_donation_sample_transport(运输信息)
├─ reception_id → biz_sample_reception + biz_sample_reception_check_result(接收信息)
├─ sample_code → biz_qc_work_order + biz_qc_task(质检信息)
└─ sample_code → biz_production_work_order + biz_production_task(生产信息)
```
### 3.3 数据流转
```
样本接收完成 (accept_conclusion=js)
→ 自动 INSERT biz_storage_release_review (review_conclusion=NULL, storage_status=drk)
→ 冗余写入 protocol_no, sample_name, protocol_id, reception_id, reception_source
审核人提交审核
→ UPDATE review_conclusion/review_remark/review_attachments/review_by/review_by_name/review_time
→ 可反复修改(每次 UPDATE 覆盖)
点击"样本入库"
→ UPDATE storage_status=yrk, storage_by, storage_by_name, storage_time
→ 同步 UPDATE biz_protocol_sample.sample_status = 'yrk'(仅采集协议样本)
```
### 3.4 状态组合
| review_conclusion | storage_status | 列表标签 | 可用操作 |
|---|---|---|---|
| NULL | drk | 待审核 | 查看、审核 |
| tg | drk | 审核通过 | 查看、审核(修改)、样本入库 |
| btg | drk | 审核不通过 | 查看、审核(修改) |
| tg | yrk | 已入库 | 查看、审核(修改) |
| btg | yrk | 不通过-已入库 | 查看、审核(修改)— 异常状态,理论上不出现 |
## 4. 后端设计
### 4.1 新增/修改文件清单
| 操作 | 文件 | 职责 |
|------|------|------|
| 新增 | `backend/app/schemas/storage_release_review.py` | Pydantic schemas |
| 新增 | `backend/app/crud/storage_release_review.py` | CRUD + 聚合查询 |
| 新增 | `backend/app/api/storage_release_review.py` | API 路由 |
| 修改 | `backend/app/models/models.py` | 新增 BizStorageReleaseReview 模型 |
| 修改 | `backend/app/api/__init__.py` | 注册新路由 |
| 修改 | `backend/app/main.py` | 注册新路由 |
| 修改 | `backend/app/crud/sample_reception.py` | 样本接收完成时自动创建审核记录 |
### 4.2 API 端点
| 方法 | 路径 | 功能 |
|------|------|------|
| GET | `/api/admin/storage-release-reviews` | 分页列表 |
| GET | `/api/admin/storage-release-reviews/{id}/detail` | 详情(聚合全链路) |
| POST | `/api/admin/storage-release-reviews/{id}/review` | 提交/修改审核 |
| POST | `/api/admin/storage-release-reviews/batch-review` | 批量审核 |
| POST | `/api/admin/storage-release-reviews/{id}/store` | 样本入库 |
| POST | `/api/admin/storage-release-reviews/batch-store` | 批量入库 |
### 4.3 列表接口
`GET /api/admin/storage-release-reviews`
请求参数:
- `page` / `pageSize` — 分页
- `reception_source` — 筛选协议类别(cell_storage / research_donation)
- `review_conclusion` — 筛选审核结论(tg / btg / null)
- `storage_status` — 筛选入库状态(drk / yrk)
- `keyword` — 模糊搜索(协议编号、样本编码、样本名称)
- `tenant_no` — 租户隔离(从当前用户提取)
返回:分页列表,默认排序 `create_time DESC`。
### 4.4 详情接口
`GET /api/admin/storage-release-reviews/{id}/detail`
聚合查询逻辑:
1. 读取 `biz_storage_release_review` 记录本身
2. 根据 `reception_source` 决定 JOIN 哪张协议主表:
- `cell_storage` → `biz_collection_protocol` + `biz_protocol_sample` + `biz_protocol_sample_transport`
- `research_donation` → `biz_research_donation` + `biz_research_donation_sample` + `biz_research_donation_sample_transport`
3. JOIN `biz_sample_reception` + `biz_sample_reception_check_result`(接收信息)
4. JOIN `biz_qc_work_order` + `biz_qc_task` + `biz_qc_task_result`(质检信息)
5. JOIN `biz_production_work_order` + `biz_production_task`(生产信息)
返回结构:
```json
{
"review": { "...审核记录本身" },
"protocolInfo": { "...协议基础信息" },
"transportInfo": { "...运输信息" },
"receptionInfo": { "...接收信息+检查项" },
"qcInfo": {
"workOrder": { "...工单信息" },
"tasks": [{ "...任务列表含结论" }]
},
"productionInfo": {
"workOrder": { "...生产工单" },
"tasks": [{ "...生产任务" }]
}
}
```
### 4.5 审核接口
`POST /api/admin/storage-release-reviews/{id}/review`
请求体:
```json
{
"review_conclusion": "tg",
"review_remark": "样本质量合格",
"review_attachments": "url1,url2"
}
```
逻辑:
- 验证记录存在 + 租户隔离
- UPDATE review_conclusion, review_remark, review_attachments, review_by, review_by_name, review_time
- 若 storage_status=yrk 且结论改为 btg,保持 storage_status 不变(入库动作不可通过改结论回退)
`POST /api/admin/storage-release-reviews/batch-review`
请求体:
```json
{
"ids": ["id1", "id2"],
"review_conclusion": "tg",
"review_remark": "批量审核通过",
"review_attachments": ""
}
```
### 4.6 入库接口
`POST /api/admin/storage-release-reviews/{id}/store`
前置条件:`review_conclusion = tg` 且 `storage_status = drk`
逻辑:
- UPDATE storage_status=yrk, storage_by, storage_by_name, storage_time
- 同步更新 `biz_protocol_sample.sample_status = 'yrk'`(采集协议样本,通过 sample_code + tenant_no 关联)
`POST /api/admin/storage-release-reviews/batch-store`
请求体:
```json
{ "ids": ["id1", "id2"] }
```
前置条件:所有记录 review_conclusion=tg 且 storage_status=drk。
### 4.7 自动创建触发点
在 `backend/app/crud/sample_reception.py` 中,样本接收完成逻辑(accept_conclusion 设为 js 时):
- INSERT `biz_storage_release_review`
- 冗余写入 sample_code, reception_source, protocol_id, reception_id, protocol_no, sample_name
- 初始状态:review_conclusion=NULL, storage_status=drk
## 5. 前端设计
### 5.1 新增/修改文件清单
| 操作 | 文件 | 职责 |
|------|------|------|
| 新增 | `web-admin/src/api/storage-release-review.ts` | API 调用模块 |
| 新增 | `web-admin/src/views/quality-inspection/storage-release-review/index.vue` | 列表页 |
| 新增 | `web-admin/src/views/quality-inspection/storage-release-review/StorageReleaseViewDialog.vue` | 查看弹窗 |
| 新增 | `web-admin/src/views/quality-inspection/storage-release-review/StorageReleaseReviewDialog.vue` | 审核弹窗 |
| 修改 | `web-admin/src/router/modules/sqxp.ts` | 在质检任务路由组下添加子路由 |
### 5.2 路由配置
在 `sqxp.ts` 的质检任务(QualityInspection)路由组下添加:
```typescript
{
path: '/quality-inspection/storage-release-review',
name: 'StorageReleaseReview',
component: () => import('@views/quality-inspection/storage-release-review/index.vue'),
meta: {
title: '入库放行审核',
// 菜单图标仅一级显示
}
}
```
### 5.3 列表页 (`index.vue`)
参照模板:`protocol-audit/index.vue`(含批量审核的列表页)
**筛选栏:**
- 协议类别(下拉:全部 / 采集协议 / 捐赠协议)
- 审核状态(下拉:全部 / 待审核 / 通过 / 不通过)
- 入库状态(下拉:全部 / 待入库 / 已入库)
- 搜索关键词(输入框:协议编号/样本编码/样本名称)
**工具栏:**
- 左侧:已选 N 项
- 右侧:[批量审核] [批量入库] — 仅一条按钮为 type="primary"
**表格列:**
| 列 | 字段 | 渲染 | 宽度 |
|---|---|---|---|
| 选择框 | — | el-table-column type=selection | 50 |
| 协议编号 | protocol_no | 文本 | 160 |
| 样本编码 | sample_code | 文本 | 140 |
| 样本名称 | sample_name | 文本 | 140 |
| 协议类别 | reception_source | el-tag: cell_storage→primary, research_donation→success | 100 |
| 审核结论 | review_conclusion | el-tag: tg→success, btg→danger, null→info("待审核") | 100 |
| 入库状态 | storage_status | el-tag: drk→warning("待入库"), yrk→success("已入库") | 100 |
| 审核人 | review_by_name | 文本 | 100 |
| 审核时间 | review_time | 日期时间 | 170 |
| 操作 | — | fixed=right, min-width=260 | 260+ |
**操作按钮:**
- 查看 — 始终显示
- 审核 — 始终显示
- 样本入库 — 仅 review_conclusion=tg 且 storage_status=drk 时显示
### 5.4 查看弹窗 (`StorageReleaseViewDialog.vue`)
参照模板:`qc-task-view-dialog/index.vue`(多分区信息展示弹窗)
布局:使用 `SystemViewDialog` 或自定义 el-dialog,内部分为 5 个信息区域:
1. **▎基础信息** — 协议编号、协议类别、样本编码、样本名称、客户姓名、联系电话
2. **▎运输信息** — 运输方式、运输温度、运输时长、运输公司
3. **▎接收信息** — 接收时间、接收人、接收结论(el-tag: js→success, bj→danger)、检查项表格
4. **▎质检信息** — 工单编号、工单状态(el-tag)、任务列表(各任务名称 + 结论 el-tag)
5. **▎生产信息** — 生产工单编号、工单状态(el-tag)、任务列表(各任务名称 + 状态 el-tag)
6. **▎放行审核** — 审核结论(el-tag)、审核人、审核时间、审核备注、审核附件(文件列表,支持预览/下载)
所有区域均为只读展示。
### 5.5 审核弹窗 (`StorageReleaseReviewDialog.vue`)
参照模板:`ProtocolAuditDialog.vue`(审核弹窗)
上半部分:与查看弹窗完全相同(只读展示 5 个信息区域)
下半部分(表单区域):
- 审核结论:el-radio-group(通过 / 不通过)
- 备注:el-input type=textarea
- 附件:使用 `ProtocolFinanceConfirmFields` 或类似上传组件
底部按钮:[取消] [确定]
**批量审核**:不展示上半部分信息,仅显示审核表单(结论 + 备注 + 附件)。参照 `ProtocolAuditDialog` 批量模式。
### 5.6 可复用组件
考虑将信息展示区域(基础信息 + 运输信息 + 接收信息 + 质检信息 + 生产信息)抽取为共享子组件,供查看弹窗和审核弹窗复用。
候选组件:
- `SampleQcInfoCards` — 已有,可复用质检信息展示
- 新建 `StorageReleaseInfoSection.vue` — 统一封装 5 个信息区域的只读展示
### 5.7 状态标签注册
在 `status-tag-registry.ts` 中新增:
```typescript
storageReleaseConclusion: {
tg: { label: '通过', type: 'success' },
btg: { label: '不通过', type: 'danger' },
null: { label: '待审核', type: 'info' },
},
storageStatus: {
drk: { label: '待入库', type: 'warning' },
yrk: { label: '已入库', type: 'success' },
}
```
## 6. 菜单配置
在系统管理的静态菜单树中,质检任务节点下添加:
| 字段 | 值 |
|------|-----|
| 菜单名称 | 入库放行审核 |
| 路由路径 | /quality-inspection/storage-release-review |
| 组件路径 | quality-inspection/storage-release-review/index |
| 菜单类型 | 菜单(C) |
| 上级菜单 | 质检任务 |
| 排序 | 在现有质检子菜单之后 |
| 图标 | 无(二级菜单不显示图标) |
## 7. 权限设计
使用现有角色-菜单权限体系:
- 新增菜单节点后,由管理员在角色管理中分配权限
- 数据权限遵循现有数据范围规则(全部/本部门/本部门及以下/仅本人/自定义)
## 8. 边界与约束
### 不做的事
- 不涉及库位分配逻辑
- 不涉及工作流引擎(使用简单直审模式)
- 不改变现有样本接收、质检、生产工单的逻辑
- 不新增独立的权限角色(复用现有菜单权限体系)
### 异常处理
- 批量审核时,部分记录可能已被审核 → 覆盖更新(以最新提交为准)
- 批量入库时,跳过不满足条件的记录(结论非 tg 或已入库),返回成功/失败数量
- 修改审核结论时,已入库的记录不回退入库状态
### 附件处理
- 遵循现有模式:`review_attachments` 字段存储逗号分隔的 MinIO URL
- 上传使用现有 `fetchUploadAttachment` 接口
- 附件业务类型:`storage-release-review`