366 lines
12 KiB
Python
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
|