| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369 |
- """生产部门样本接收 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": row.inoculation_amount or "",
- "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 = (
- (str(data.get("inoculationAmount") or "").strip() or None)
- 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
|