提交
This commit is contained in:
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`
|
||||
Reference in new issue
Block a user