2026-05-23-storage-release-review.md 31 KB

入库放行审核 实施计划

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 和菜单插入语句:

-- 入库放行审核表
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: 检查表是否创建成功

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)添加:

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,包含:

"""入库放行审核 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,实现以下函数:

"""入库放行审核 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

    • 批量入库,逐条检查前置条件
    • 返回成功/失败数量

辅助函数:

_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 的模式:

"""入库放行审核 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=["入库放行审核"])

端点清单:

@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:

from app.api.storage_release_review import router as storage_release_review_router
# 添加到 __all__ 列表

修改 backend/app/main.py:

# 导入行添加
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(...) 之后)添加:

        # 自动创建入库放行审核记录
        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:

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 中新增:

// 入库放行审核结论
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 }>,

同时添加协议类别标签:

// 协议类别
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 之后)添加:

{
  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:

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:

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: 提交
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 (入库放行审核)"