Files
zkxw/backend/app/crud/sample_production_reception.py
T
2026-06-30 09:19:06 +08:00

366 lines
12 KiB
Python

"""生产部门样本接收 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