"""生产部门样本接收 API""" from typing import Optional from fastapi import APIRouter, Depends, Query from pydantic import BaseModel, ConfigDict, Field from sqlalchemy.orm import Session from app.core.database import get_db from app.core.menu_guard import 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_production_reception as prod_rec_svc from app.crud import sample_reception as sr_svc from app.models.models import SysUser router = APIRouter(prefix="/web/admin", tags=["生产管理"]) PRODUCTION_RECEPTION_READ_MENU_CODES = ( "production:work-order:list", "production:my-task:list", "biz:sampleReception:list", "production:sample-reception-completion:list", ) PRODUCTION_RECEPTION_WRITE_MENU_CODES = ( "production:work-order:list", "production:my-task: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) class ProductionReceptionBody(BaseModel): model_config = ConfigDict(populate_by_name=True) reception_id: Optional[str] = Field(None, alias="receptionId") sample_code: Optional[str] = Field(None, alias="sampleCode") source_type: Optional[str] = Field("cell_storage", alias="sourceType") sample_type_name: Optional[str] = Field(None, alias="sampleTypeName") handler_user_id: Optional[str] = Field(None, alias="handlerUserId") handler_name: Optional[str] = Field(None, alias="handlerName") process_time: str = Field(..., alias="processTime") original_blood_weight_g: Optional[float] = Field(None, alias="originalBloodWeightG") hes_volume_ml: Optional[float] = Field(None, alias="hesVolumeMl") blood_type: Optional[str] = Field(None, alias="bloodType") infectious_four_test_result: Optional[str] = Field(None, alias="infectiousFourTestResult") enriched_blood_weight_g: Optional[float] = Field(None, alias="enrichedBloodWeightG") cryopreservation_volume_ml: Optional[float] = Field(None, alias="cryopreservationVolumeMl") inoculation_amount: Optional[str] = Field(None, alias="inoculationAmount") cryopreservation_time: Optional[str] = Field(None, alias="cryopreservationTime") storage_total_tubes: Optional[int] = Field(None, alias="storageTotalTubes") available_cell_count_1e7: Optional[float] = Field(None, alias="availableCellCount1e7") original_blood_volume_ml: Optional[float] = Field(None, alias="originalBloodVolumeMl") nucleated_cell_count_1e8: Optional[float] = Field(None, alias="nucleatedCellCount1e8") remark: Optional[str] = None def _body_to_payload(body: ProductionReceptionBody) -> dict: cryo_time = parse_datetime(body.cryopreservation_time) if body.cryopreservation_time else None process_time = parse_datetime(body.process_time) if not process_time: raise ValueError("处理时间格式无效") return { "handlerUserId": body.handler_user_id, "handlerName": body.handler_name, "processTime": process_time, "sampleTypeName": body.sample_type_name, "originalBloodWeightG": body.original_blood_weight_g, "hesVolumeMl": body.hes_volume_ml, "bloodType": body.blood_type, "infectiousFourTestResult": body.infectious_four_test_result, "enrichedBloodWeightG": body.enriched_blood_weight_g, "cryopreservationVolumeMl": body.cryopreservation_volume_ml, "inoculationAmount": body.inoculation_amount, "cryopreservationTime": cryo_time, "storageTotalTubes": body.storage_total_tubes, "availableCellCount1e7": body.available_cell_count_1e7, "originalBloodVolumeMl": body.original_blood_volume_ml, "nucleatedCellCount1e8": body.nucleated_cell_count_1e8, "remark": body.remark, } def _resolve_reception_id( db: Session, *, tenant_no: str, reception_id: Optional[str], sample_code: Optional[str], source_type: Optional[str], ) -> str: rid = (reception_id or "").strip() if rid: return rid code = (sample_code or "").strip() if not code: raise ValueError("请提供 receptionId 或 sampleCode") result = sr_svc.get_reception_by_sample_code( db, sample_code=code, source_type=source_type, tenant_no=tenant_no ) found = (result.get("receptionId") or "").strip() if not found: raise ValueError("请先完成质检样本接收登记") return found @router.get("/production-sample-receptions/by-sample-code") async def get_production_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(*PRODUCTION_RECEPTION_READ_MENU_CODES)), ): try: tenant_no = _get_tenant_no(db, user) base = sr_svc.get_reception_by_sample_code( db, sample_code=sampleCode, source_type=sourceType, tenant_no=tenant_no ) reception_id = base.get("receptionId") qc_reception = None production_reception = None if reception_id: qc_reception = sr_svc.get_reception_by_id(db, reception_id, tenant_no) production_reception = prod_rec_svc.get_by_reception_id(db, reception_id, tenant_no) return success_response( { "receptionId": reception_id, "sampleInfo": base.get("sampleInfo"), "qcReception": qc_reception, "productionReception": production_reception, } ) except PermissionError as e: return error_response(403, str(e)) except Exception as e: return error_response(400, str(e)) @router.get("/production-sample-receptions/{row_id}") async def get_production_reception_detail( row_id: str, db: Session = Depends(get_db), user: dict = Depends(require_any_menu_code(*PRODUCTION_RECEPTION_READ_MENU_CODES)), ): try: tenant_no = _get_tenant_no(db, user) item = prod_rec_svc.get_by_id(db, row_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)) @router.post("/production-sample-receptions") async def post_production_reception( body: ProductionReceptionBody, db: Session = Depends(get_db), user: dict = Depends(require_any_menu_code(*PRODUCTION_RECEPTION_WRITE_MENU_CODES)), ): 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 "" reception_id = _resolve_reception_id( db, tenant_no=tenant_no, reception_id=body.reception_id, sample_code=body.sample_code, source_type=body.source_type, ) payload = _body_to_payload(body) type_name = (body.sample_type_name or "").strip() if not type_name: sample_info = sr_svc.get_reception_by_sample_code( db, sample_code=(body.sample_code or "").strip(), source_type=body.source_type, tenant_no=tenant_no, ).get("sampleInfo") if isinstance(sample_info, dict): type_name = str(sample_info.get("sampleTypeName") or "") obj = prod_rec_svc.create_production_reception( db, reception_id=reception_id, tenant_no=tenant_no, user_id=user_id, create_dept_id=dept_id, data=payload, sample_type_name=type_name, ) return success_response(prod_rec_svc.get_by_id(db, obj.id, tenant_no)) 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("/production-sample-receptions/{row_id}") async def put_production_reception( row_id: str, body: ProductionReceptionBody, db: Session = Depends(get_db), user: dict = Depends(require_any_menu_code(*PRODUCTION_RECEPTION_WRITE_MENU_CODES)), ): try: tenant_no = _get_tenant_no(db, user) user_id = _get_current_user_id(user) payload = _body_to_payload(body) obj = prod_rec_svc.update_production_reception( db, row_id=row_id, tenant_no=tenant_no, user_id=user_id, data=payload, ) return success_response(prod_rec_svc.get_by_id(db, obj.id, tenant_no)) 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))