"""生产部门样本接收 CRUD""" from __future__ import annotations from datetime import datetime from typing import Any, Dict, List, Optional, Set, Tuple from sqlalchemy.orm import Session from app.core.utils import generate_uuid from app.models.models import BizSampleProductionReception, BizSampleReception, SysUser from app.crud import production_task_step_assignee as step_assignee_svc PRODUCTION_RECEPTION_HANDLER_ROLE = step_assignee_svc.PRODUCTION_STEP_ASSIGNEE_ROLE SAMPLE_TYPE_PERINATAL_HSC = "围产期造血干细胞" SAMPLE_TYPE_UC_MSC = "脐带间充质干细胞" INFECTIOUS_FOUR_LABELS = {"zc": "正常", "bzc": "不正常"} # (source, sample_type_name) -> required writable field keys (camelCase API) FIELD_PROFILES: Dict[Tuple[str, str], List[str]] = { ("cell_storage", SAMPLE_TYPE_PERINATAL_HSC): [ "originalBloodWeightG", "hesVolumeMl", "bloodType", "infectiousFourTestResult", "enrichedBloodWeightG", "cryopreservationVolumeMl", ], ("cell_storage", SAMPLE_TYPE_UC_MSC): [ "inoculationAmount", "cryopreservationTime", "storageTotalTubes", "availableCellCount1e7", ], ("research_donation", SAMPLE_TYPE_UC_MSC): [ "inoculationAmount", "cryopreservationTime", ], ("research_donation", SAMPLE_TYPE_PERINATAL_HSC): [ "originalBloodVolumeMl", "nucleatedCellCount1e8", ], } ALL_TYPE_FIELDS = { "originalBloodWeightG", "hesVolumeMl", "bloodType", "infectiousFourTestResult", "enrichedBloodWeightG", "cryopreservationVolumeMl", "inoculationAmount", "cryopreservationTime", "storageTotalTubes", "availableCellCount1e7", "originalBloodVolumeMl", "nucleatedCellCount1e8", } def _fmt_dt(t: Optional[datetime]) -> str: return t.strftime("%Y-%m-%d %H:%M:%S") if t else "" def _dec(v: Any) -> Optional[float]: if v is None: return None return float(v) def _int_val(v: Any) -> Optional[int]: if v is None or v == "": return None return int(v) def resolve_field_profile(source: str, sample_type_name: str) -> List[str]: src = (source or "cell_storage").strip() name = (sample_type_name or "").strip() return list(FIELD_PROFILES.get((src, name), [])) def _row_to_dict(row: BizSampleProductionReception) -> Dict[str, Any]: result = row.infectious_four_test_result or "" return { "id": row.id, "receptionId": row.reception_id, "sampleCode": row.sample_code or "", "receptionSource": row.reception_source or "cell_storage", "sampleTypeName": row.sample_type_name or "", "handlerUserId": row.handler_user_id or "", "handlerName": row.handler_name or "", "processTime": _fmt_dt(row.process_time), "originalBloodWeightG": _dec(row.original_blood_weight_g), "hesVolumeMl": _dec(row.hes_volume_ml), "bloodType": row.blood_type or "", "infectiousFourTestResult": result, "infectiousFourTestResultLabel": INFECTIOUS_FOUR_LABELS.get(result, result), "enrichedBloodWeightG": _dec(row.enriched_blood_weight_g), "cryopreservationVolumeMl": _dec(row.cryopreservation_volume_ml), "inoculationAmount": _dec(row.inoculation_amount), "cryopreservationTime": _fmt_dt(row.cryopreservation_time), "storageTotalTubes": row.storage_total_tubes, "availableCellCount1e7": _dec(row.available_cell_count_1e7), "originalBloodVolumeMl": _dec(row.original_blood_volume_ml), "nucleatedCellCount1e8": _dec(row.nucleated_cell_count_1e8), "remark": row.remark or "", "createTime": _fmt_dt(row.create_time), "updateTime": _fmt_dt(row.update_time), } def _validate_handler_user_id( db: Session, handler_user_id: str, tenant_no: str ) -> str: uid = (handler_user_id or "").strip() if not uid: raise ValueError("请选择处理人") step_assignee_svc.validate_assignee_user_ids( db, [uid], tenant_no, required_role_name=PRODUCTION_RECEPTION_HANDLER_ROLE ) return uid def _get_qc_reception( db: Session, *, reception_id: str, tenant_no: str ) -> BizSampleReception: rec = ( db.query(BizSampleReception) .filter( BizSampleReception.id == reception_id, BizSampleReception.tenant_no == tenant_no, BizSampleReception.del_flag == 0, ) .first() ) if not rec: raise ValueError("关联的质检接收记录不存在") if (rec.accept_conclusion or "").strip() == "bj": raise ValueError("样本已拒收,无法登记生产接收信息") return rec def _validate_payload( data: Dict[str, Any], *, source: str, sample_type_name: str, ) -> None: required = {"handlerUserId", "processTime"} | set( resolve_field_profile(source, sample_type_name) ) for key in required: val = data.get(key) if val is None or (isinstance(val, str) and not val.strip()): raise ValueError(f"请填写必填字段:{key}") infectious = data.get("infectiousFourTestResult") if infectious is not None and str(infectious).strip(): if str(infectious).strip() not in INFECTIOUS_FOUR_LABELS: raise ValueError("传染四项检测结果无效") def _apply_payload_to_row( row: BizSampleProductionReception, data: Dict[str, Any], *, source: str, sample_type_name: str, ) -> None: profile_keys = set(resolve_field_profile(source, sample_type_name)) allowed = profile_keys | {"handlerUserId", "handlerName", "processTime", "remark"} row.handler_user_id = str(data.get("handlerUserId") or "").strip() or None row.handler_name = str(data.get("handlerName") or "").strip() or None pt = data.get("processTime") if isinstance(pt, datetime): row.process_time = pt row.remark = str(data.get("remark") or "").strip() or None def _set_decimal(attr: str, key: str) -> None: if key not in allowed: setattr(row, attr, None) return setattr(row, attr, data.get(key)) row.original_blood_weight_g = data.get("originalBloodWeightG") if "originalBloodWeightG" in allowed else None row.hes_volume_ml = data.get("hesVolumeMl") if "hesVolumeMl" in allowed else None row.blood_type = ( str(data.get("bloodType") or "").strip() or None if "bloodType" in allowed else None ) row.infectious_four_test_result = ( str(data.get("infectiousFourTestResult") or "").strip() or None if "infectiousFourTestResult" in allowed else None ) row.enriched_blood_weight_g = ( data.get("enrichedBloodWeightG") if "enrichedBloodWeightG" in allowed else None ) row.cryopreservation_volume_ml = ( data.get("cryopreservationVolumeMl") if "cryopreservationVolumeMl" in allowed else None ) row.inoculation_amount = data.get("inoculationAmount") if "inoculationAmount" in allowed else None ct = data.get("cryopreservationTime") row.cryopreservation_time = ct if "cryopreservationTime" in allowed and ct else None row.storage_total_tubes = ( _int_val(data.get("storageTotalTubes")) if "storageTotalTubes" in allowed else None ) row.available_cell_count_1e7 = ( data.get("availableCellCount1e7") if "availableCellCount1e7" in allowed else None ) row.original_blood_volume_ml = ( data.get("originalBloodVolumeMl") if "originalBloodVolumeMl" in allowed else None ) row.nucleated_cell_count_1e8 = ( data.get("nucleatedCellCount1e8") if "nucleatedCellCount1e8" in allowed else None ) def get_by_reception_id( db: Session, reception_id: str, tenant_no: str ) -> Optional[Dict[str, Any]]: row = ( db.query(BizSampleProductionReception) .filter( BizSampleProductionReception.reception_id == reception_id, BizSampleProductionReception.tenant_no == tenant_no, BizSampleProductionReception.del_flag == 0, ) .first() ) return _row_to_dict(row) if row else None def get_map_by_reception_ids( db: Session, reception_ids: List[str], tenant_no: str ) -> Dict[str, Dict[str, Any]]: if not reception_ids: return {} rows = ( db.query(BizSampleProductionReception) .filter( BizSampleProductionReception.reception_id.in_(reception_ids), BizSampleProductionReception.tenant_no == tenant_no, BizSampleProductionReception.del_flag == 0, ) .all() ) return {r.reception_id: _row_to_dict(r) for r in rows} def get_by_id(db: Session, row_id: str, tenant_no: str) -> Optional[Dict[str, Any]]: row = ( db.query(BizSampleProductionReception) .filter( BizSampleProductionReception.id == row_id, BizSampleProductionReception.tenant_no == tenant_no, BizSampleProductionReception.del_flag == 0, ) .first() ) return _row_to_dict(row) if row else None def create_production_reception( db: Session, *, reception_id: str, tenant_no: str, user_id: Optional[str], create_dept_id: str, data: Dict[str, Any], sample_type_name: str = "", ) -> BizSampleProductionReception: qc = _get_qc_reception(db, reception_id=reception_id, tenant_no=tenant_no) exists = ( db.query(BizSampleProductionReception) .filter( BizSampleProductionReception.reception_id == reception_id, BizSampleProductionReception.tenant_no == tenant_no, BizSampleProductionReception.del_flag == 0, ) .first() ) if exists: raise ValueError("该样本已登记生产接收信息,请使用编辑") source = (qc.reception_source or "cell_storage").strip() type_name = (sample_type_name or data.get("sampleTypeName") or "").strip() _validate_payload(data, source=source, sample_type_name=type_name) handler_name = str(data.get("handlerName") or "").strip() handler_user_id = _validate_handler_user_id( db, str(data.get("handlerUserId") or ""), tenant_no ) if handler_user_id and not handler_name: u = db.query(SysUser).filter(SysUser.id == handler_user_id).first() if u: handler_name = u.nick_name or "" row = BizSampleProductionReception( id=generate_uuid(), reception_id=reception_id, sample_code=(qc.sample_code or "").strip() or None, reception_source=source, sample_type_name=type_name or None, tenant_no=tenant_no, create_by=user_id, create_dept_id=create_dept_id or "", update_by=user_id, ) data = dict(data) data["handlerUserId"] = handler_user_id data["handlerName"] = handler_name _apply_payload_to_row(row, data, source=source, sample_type_name=type_name) db.add(row) db.commit() db.refresh(row) return row def update_production_reception( db: Session, *, row_id: str, tenant_no: str, user_id: Optional[str], data: Dict[str, Any], ) -> BizSampleProductionReception: row = ( db.query(BizSampleProductionReception) .filter( BizSampleProductionReception.id == row_id, BizSampleProductionReception.tenant_no == tenant_no, BizSampleProductionReception.del_flag == 0, ) .first() ) if not row: raise ValueError("生产接收记录不存在") _get_qc_reception(db, reception_id=row.reception_id, tenant_no=tenant_no) source = (row.reception_source or "cell_storage").strip() type_name = (row.sample_type_name or "").strip() _validate_payload(data, source=source, sample_type_name=type_name) handler_name = str(data.get("handlerName") or "").strip() handler_user_id = _validate_handler_user_id( db, str(data.get("handlerUserId") or ""), tenant_no ) if handler_user_id and not handler_name: u = db.query(SysUser).filter(SysUser.id == handler_user_id).first() if u: handler_name = u.nick_name or "" data = dict(data) data["handlerUserId"] = handler_user_id data["handlerName"] = handler_name _apply_payload_to_row(row, data, source=source, sample_type_name=type_name) row.update_by = user_id row.update_time = datetime.now() db.add(row) db.commit() db.refresh(row) return row