| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458 |
- """质量检测 — 样本接收 API"""
- from typing import List, Optional
- from fastapi import APIRouter, Depends, Query
- from pydantic import BaseModel, ConfigDict, Field
- from sqlalchemy import or_
- from sqlalchemy.orm import Session
- from app.core.data_scope import apply_data_scope
- from app.core.database import get_db
- from app.core.menu_guard import require_admin_or_super_admin, require_any_menu_code, require_menu_code
- from app.core.response import error_response, success_response
- from app.core.utils import parse_datetime
- from app.crud import sample_reception as sr_svc
- from app.crud import sample_reception_check_item as check_item_svc
- from app.crud.collection_protocol import total_pages
- from app.models.models import BizSampleReception, BizSampleReceptionCheckItem, SysUser
- router = APIRouter(prefix="/web/admin", tags=["质量检测"])
- SAMPLE_RECEPTION_READ_MENU_CODES = (
- "biz:sampleReception:list",
- "production:sample-reception-completion:list",
- )
- def _get_tenant_no(db: Session, user: dict) -> str:
- user_id = user.get("user_id")
- if not user_id:
- raise PermissionError("用户身份信息缺失,无法确定租户")
- sys_user = db.query(SysUser).filter(SysUser.id == user_id).first()
- if not sys_user or not sys_user.tenant_no:
- raise PermissionError("用户租户信息缺失")
- return sys_user.tenant_no
- def _get_current_user_id(user: dict) -> str:
- user_id = user.get("user_id")
- if not user_id:
- raise PermissionError("用户身份信息缺失")
- return str(user_id)
- def _page_payload(items: list, total: int, page: int, page_size: int) -> dict:
- return {
- "list": items,
- "total": total,
- "page": page,
- "pageSize": page_size,
- "totalPages": total_pages(total, page_size),
- }
- # ---------- 检查项配置 ----------
- class CheckItemBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- item_code: str = Field(..., alias="itemCode")
- item_name: str = Field(..., alias="itemName")
- status: str = Field("1", alias="status")
- order_num: Optional[int] = Field(None, alias="orderNum")
- remark: Optional[str] = None
- class CheckItemUpdateBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- item_code: Optional[str] = Field(None, alias="itemCode")
- item_name: Optional[str] = Field(None, alias="itemName")
- status: Optional[str] = None
- order_num: Optional[int] = Field(None, alias="orderNum")
- remark: Optional[str] = None
- @router.get("/sample-reception-check-items")
- async def list_check_items(
- page: int = Query(1, ge=1),
- pageSize: int = Query(10, ge=1, le=100),
- keyword: Optional[str] = None,
- status: Optional[str] = None,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:sampleReception:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- query = db.query(BizSampleReceptionCheckItem).filter(
- BizSampleReceptionCheckItem.del_flag == 0,
- BizSampleReceptionCheckItem.tenant_no == tenant_no,
- )
- kw = (keyword or "").strip()
- if kw:
- like = f"%{kw}%"
- query = query.filter(
- or_(
- BizSampleReceptionCheckItem.item_code.like(like),
- BizSampleReceptionCheckItem.item_name.like(like),
- )
- )
- if status:
- query = query.filter(BizSampleReceptionCheckItem.status == status)
- query = apply_data_scope(query, BizSampleReceptionCheckItem, user, db, tenant_no)
- total = query.count()
- items = query.order_by(BizSampleReceptionCheckItem.create_time.desc()).offset((page - 1) * pageSize).limit(pageSize).all()
- result_list = [check_item_svc._row_to_dict(r) for r in items]
- return success_response(_page_payload(result_list, total, page, pageSize))
- except PermissionError as e:
- return error_response(403, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.get("/sample-reception-check-items/active")
- async def list_active_check_items(
- db: Session = Depends(get_db),
- user: dict = Depends(require_any_menu_code(*SAMPLE_RECEPTION_READ_MENU_CODES)),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- items = check_item_svc.list_active_check_items(db, tenant_no)
- return success_response(items)
- except PermissionError as e:
- return error_response(403, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.post("/sample-reception-check-items")
- async def create_check_item(
- body: CheckItemBody,
- db: Session = Depends(get_db),
- user: dict = Depends(require_admin_or_super_admin),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- user_id = _get_current_user_id(user)
- sys_user = db.query(SysUser).filter(SysUser.id == user_id).first()
- dept_id = (sys_user.dept_id or "") if sys_user else ""
- obj = check_item_svc.create_check_item(
- db,
- item_code=body.item_code,
- item_name=body.item_name,
- status=body.status,
- tenant_no=tenant_no,
- order_num=body.order_num,
- remark=body.remark,
- user_id=user_id,
- create_dept_id=dept_id,
- )
- return success_response(check_item_svc._row_to_dict(obj))
- except PermissionError as e:
- return error_response(403, str(e))
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.put("/sample-reception-check-items/{row_id}")
- async def update_check_item(
- row_id: str,
- body: CheckItemUpdateBody,
- db: Session = Depends(get_db),
- user: dict = Depends(require_admin_or_super_admin),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- user_id = _get_current_user_id(user)
- obj = check_item_svc.update_check_item(
- db,
- row_id,
- item_code=body.item_code,
- item_name=body.item_name,
- status=body.status,
- order_num=body.order_num,
- remark=body.remark,
- user_id=user_id,
- tenant_no=tenant_no,
- )
- return success_response(check_item_svc._row_to_dict(obj))
- except PermissionError as e:
- return error_response(403, str(e))
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.delete("/sample-reception-check-items/{row_id}")
- async def delete_check_item(
- row_id: str,
- db: Session = Depends(get_db),
- user: dict = Depends(require_admin_or_super_admin),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- check_item_svc.soft_delete_check_item(db, row_id, tenant_no=tenant_no)
- return success_response(None)
- except PermissionError as e:
- return error_response(403, str(e))
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- # ---------- 样本接收 ----------
- @router.get("/sample-receptions/pending")
- async def list_pending_samples(
- page: int = Query(1, ge=1),
- pageSize: int = Query(10, ge=1, le=100),
- keyword: Optional[str] = None,
- sourceType: Optional[str] = None,
- etaTimeStart: Optional[str] = Query(None, alias="etaTimeStart"),
- etaTimeEnd: Optional[str] = Query(None, alias="etaTimeEnd"),
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:sampleReception:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- items, total = sr_svc.list_pending_protocol_samples(
- db, page=page, page_size=pageSize, tenant_no=tenant_no, keyword=keyword,
- source_type=sourceType,
- eta_time_start=etaTimeStart,
- eta_time_end=etaTimeEnd,
- user_info=user,
- )
- return success_response(_page_payload(items, total, page, pageSize))
- except PermissionError as e:
- return error_response(403, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.get("/sample-receptions/received")
- async def list_received_samples(
- page: int = Query(1, ge=1),
- pageSize: int = Query(10, ge=1, le=100),
- keyword: Optional[str] = None,
- sourceType: Optional[str] = None,
- receptionTimeStart: Optional[str] = Query(None, alias="receptionTimeStart"),
- receptionTimeEnd: Optional[str] = Query(None, alias="receptionTimeEnd"),
- db: Session = Depends(get_db),
- user: dict = Depends(require_any_menu_code(*SAMPLE_RECEPTION_READ_MENU_CODES)),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- items, total = sr_svc.list_received_protocol_samples(
- db, page=page, page_size=pageSize, tenant_no=tenant_no, keyword=keyword,
- source_type=sourceType,
- reception_time_start=receptionTimeStart,
- reception_time_end=receptionTimeEnd,
- user_info=user,
- )
- return success_response(_page_payload(items, total, page, pageSize))
- except PermissionError as e:
- return error_response(403, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.get("/sample-receptions/by-sample-code")
- async def get_sample_reception_by_sample_code(
- sampleCode: str = Query(..., alias="sampleCode"),
- sourceType: Optional[str] = Query(None, alias="sourceType"),
- db: Session = Depends(get_db),
- user: dict = Depends(require_any_menu_code(*SAMPLE_RECEPTION_READ_MENU_CODES)),
- ):
- """根据样本编码查询样本接收记录:有则返回 receptionId + sampleInfo;无则仅返回 sampleInfo(用于新建)。"""
- try:
- tenant_no = _get_tenant_no(db, user)
- result = sr_svc.get_reception_by_sample_code(
- db, sample_code=sampleCode, source_type=sourceType, tenant_no=tenant_no
- )
- return success_response(result)
- except PermissionError as e:
- return error_response(403, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.get("/sample-receptions/{reception_id}")
- async def get_sample_reception_detail(
- reception_id: str,
- db: Session = Depends(get_db),
- user: dict = Depends(require_any_menu_code(*SAMPLE_RECEPTION_READ_MENU_CODES)),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- item = sr_svc.get_reception_by_id(db, reception_id, tenant_no)
- if not item:
- return error_response(404, "接收记录不存在")
- return success_response(item)
- except PermissionError as e:
- return error_response(403, str(e))
- except Exception as e:
- return error_response(400, str(e))
- class ReceptionCheckSubmit(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- check_item_id: str = Field(..., alias="checkItemId")
- checked: bool = False
- class SampleReceptionUpdateBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- reception_time: str = Field(..., alias="receptionTime")
- handler_user_id: Optional[str] = Field(None, alias="handlerUserId")
- handler_name: Optional[str] = Field(None, alias="handlerName")
- receive_temperature: Optional[str] = Field(None, alias="receiveTemperature")
- checks: List[ReceptionCheckSubmit] = Field(default_factory=list)
- accept_conclusion: str = Field(..., alias="acceptConclusion")
- receive_attachments: Optional[str] = Field(None, alias="receiveAttachments")
- remark: Optional[str] = None
- lab_storage_code: Optional[str] = Field(None, alias="labStorageCode")
- class SampleReceptionCreateBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- protocol_sample_id: Optional[str] = Field(None, alias="protocolSampleId")
- reception_time: str = Field(..., alias="receptionTime")
- handler_user_id: Optional[str] = Field(None, alias="handlerUserId")
- handler_name: Optional[str] = Field(None, alias="handlerName")
- receive_temperature: Optional[str] = Field(None, alias="receiveTemperature")
- checks: List[ReceptionCheckSubmit] = Field(default_factory=list)
- accept_conclusion: str = Field(..., alias="acceptConclusion")
- receive_attachments: Optional[str] = Field(None, alias="receiveAttachments")
- remark: Optional[str] = None
- source_type: Optional[str] = Field("cell_storage", alias="sourceType")
- donation_sample_id: Optional[str] = Field(None, alias="donationSampleId")
- lab_storage_code: Optional[str] = Field(None, alias="labStorageCode")
- class AllocateLabStorageCodesBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- reception_date: str = Field(..., alias="receptionDate")
- count: int = Field(1, ge=1, le=50)
- @router.post("/sample-receptions/allocate-lab-storage-codes")
- async def allocate_lab_storage_codes(
- body: AllocateLabStorageCodesBody,
- db: Session = Depends(get_db),
- user: dict = Depends(require_admin_or_super_admin),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- user_id = _get_current_user_id(user)
- from app.crud import sample_reception_lab_code as lab_code_svc
- codes = lab_code_svc.allocate_lab_storage_codes(
- db,
- tenant_no=tenant_no,
- reception_date=body.reception_date,
- count=body.count,
- user_id=user_id,
- )
- return success_response({"codes": codes})
- except PermissionError as e:
- return error_response(403, str(e))
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.post("/sample-receptions")
- async def post_sample_reception(
- body: SampleReceptionCreateBody,
- db: Session = Depends(get_db),
- user: dict = Depends(require_admin_or_super_admin),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- user_id = _get_current_user_id(user)
- sys_user = db.query(SysUser).filter(SysUser.id == user_id).first()
- dept_id = (sys_user.dept_id or "") if sys_user else ""
- rt = parse_datetime(body.reception_time)
- if not rt:
- return error_response(400, "样本接收时间格式无效")
- checks_payload = [
- {"checkItemId": c.check_item_id, "checked": c.checked} for c in body.checks
- ]
- obj = sr_svc.create_sample_reception(
- db,
- reception_source=body.source_type or "cell_storage",
- protocol_sample_id=body.protocol_sample_id if body.source_type != "research_donation" else None,
- donation_sample_id=body.donation_sample_id if body.source_type == "research_donation" else None,
- tenant_no=tenant_no,
- user_id=user_id,
- create_dept_id=dept_id,
- reception_time=rt,
- handler_user_id=body.handler_user_id or user_id,
- handler_name=body.handler_name,
- receive_temperature=body.receive_temperature,
- checks=checks_payload,
- accept_conclusion=body.accept_conclusion,
- receive_attachments=body.receive_attachments,
- remark=body.remark,
- lab_storage_code=body.lab_storage_code,
- )
- detail = sr_svc.get_reception_by_id(db, obj.id, tenant_no)
- return success_response(detail or {"id": obj.id})
- except PermissionError as e:
- return error_response(403, str(e))
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.put("/sample-receptions/{reception_id}")
- async def put_sample_reception(
- reception_id: str,
- body: SampleReceptionUpdateBody,
- db: Session = Depends(get_db),
- user: dict = Depends(require_admin_or_super_admin),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- user_id = _get_current_user_id(user)
- rt = parse_datetime(body.reception_time)
- if not rt:
- return error_response(400, "样本接收时间格式无效")
- checks_payload = [
- {"checkItemId": c.check_item_id, "checked": c.checked} for c in body.checks
- ]
- obj = sr_svc.update_sample_reception(
- db,
- reception_id=reception_id,
- tenant_no=tenant_no,
- user_id=user_id,
- reception_time=rt,
- handler_user_id=body.handler_user_id or user_id,
- handler_name=body.handler_name,
- receive_temperature=body.receive_temperature,
- checks=checks_payload,
- accept_conclusion=body.accept_conclusion,
- receive_attachments=body.receive_attachments,
- remark=body.remark,
- lab_storage_code=body.lab_storage_code,
- )
- detail = sr_svc.get_reception_by_id(db, obj.id, tenant_no)
- return success_response(detail or {"id": obj.id})
- except PermissionError as e:
- return error_response(403, str(e))
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
|