Files
zkxw/backend/app/crud/protocol_sample_collection.py
T

457 lines
17 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""采集协议样本采集信息列表 CRUD(样本信息管理 · 样本采集信息 Tab)"""
from __future__ import annotations
import math
from datetime import datetime
from typing import Any, Dict, List, Optional, Tuple
from sqlalchemy import and_, or_
from sqlalchemy.orm import Session
from app.core.utils import format_date
from app.utils.sample_special import sample_special_to_dict
from app.models.models import (
BizCellSampleType,
BizCollectionHospital,
BizCollectionProtocol,
BizCollectionRegion,
BizProtocolCollectionInfo,
BizProtocolPartyCustomer,
BizProtocolSample,
)
def total_pages(total: int, page_size: int) -> int:
if total <= 0:
return 0
return int(math.ceil(total / float(page_size)))
def _fmt_dt(t: Optional[datetime]) -> str:
return t.strftime("%Y-%m-%d %H:%M:%S") if t else ""
def _sample_status_label(status: str) -> str:
return {
"ycj": "已采集",
"jcz": "检测中",
"yrk": "已入库",
"yzf": "已作废",
}.get(status, "未采集" if not status else status)
def _row_to_dict(
*,
protocol_sample_id: str,
collection_protocol_id: str,
protocol_no: str,
protocol_status: str,
expected_delivery_date: str,
mother_name: str,
newborn_name: str,
sample_code: str,
sample_type_name: str,
sample_status: str,
sample_status_label: str,
region_name: str,
hospital_name: str,
medical_staff: str,
sample_collect_time: str,
maternal_blood_collect_time: str,
) -> Dict[str, Any]:
return {
"protocolSampleId": protocol_sample_id,
"collectionProtocolId": collection_protocol_id,
"protocolNo": protocol_no,
"protocolStatus": protocol_status,
"expectedDeliveryDate": expected_delivery_date,
"motherName": mother_name,
"newbornName": newborn_name,
"sampleCode": sample_code,
"sampleTypeName": sample_type_name,
"sampleStatus": sample_status,
"sampleStatusLabel": sample_status_label,
"regionName": region_name,
"hospitalName": hospital_name,
"medicalStaff": medical_staff,
"sampleCollectTime": sample_collect_time,
"maternalBloodCollectTime": maternal_blood_collect_time,
}
def _base_query(db: Session, tenant_no: str):
return (
db.query(
BizProtocolSample,
BizCollectionProtocol,
BizProtocolPartyCustomer,
BizProtocolCollectionInfo,
)
.join(
BizCollectionProtocol,
and_(
BizCollectionProtocol.id == BizProtocolSample.collection_protocol_id,
BizCollectionProtocol.del_flag == 0,
),
)
.outerjoin(
BizProtocolPartyCustomer,
and_(
BizProtocolPartyCustomer.collection_protocol_id == BizCollectionProtocol.id,
BizProtocolPartyCustomer.del_flag == 0,
),
)
.outerjoin(
BizProtocolCollectionInfo,
and_(
BizProtocolCollectionInfo.collection_protocol_id == BizCollectionProtocol.id,
BizProtocolCollectionInfo.del_flag == 0,
),
)
.filter(
BizProtocolSample.tenant_no == tenant_no,
BizProtocolSample.del_flag == 0,
)
)
def _apply_sample_status_filter(q, sample_status: Optional[str]):
st = (sample_status or "").strip()
if not st or st == "all":
return q
if st == "uncollected":
return q.filter(
or_(
BizProtocolSample.sample_status.is_(None),
BizProtocolSample.sample_status == "",
)
)
if st == "collected":
return q.filter(BizProtocolSample.sample_status.in_(["ycj", "jcz", "yrk"]))
if st == "voided":
return q.filter(BizProtocolSample.sample_status == "yzf")
return q.filter(BizProtocolSample.sample_status == st)
def _apply_reception_flag_filter(q, flag: Optional[str], *, sample_code_col, tenant_no: str, source: str):
from sqlalchemy import exists
from app.models.models import BizSampleReception
f = (flag or "").strip()
if not f:
return q
base = and_(
BizSampleReception.tenant_no == tenant_no,
BizSampleReception.del_flag == 0,
BizSampleReception.reception_source == source,
BizSampleReception.sample_code == sample_code_col,
)
if f == "js":
return q.filter(exists().where(and_(base, BizSampleReception.accept_conclusion == "js")))
if f == "bj":
return q.filter(exists().where(and_(base, BizSampleReception.accept_conclusion == "bj")))
if f == "none":
return q.filter(
~exists().where(and_(base, BizSampleReception.accept_conclusion.in_(["js", "bj"])))
)
return q
def _apply_qc_flag_filter(q, flag: Optional[str], *, sample_code_col, tenant_no: str, source: str):
from sqlalchemy import exists
from app.models.models import BizQcWorkOrder
f = (flag or "").strip()
if not f:
return q
base = and_(
BizQcWorkOrder.tenant_no == tenant_no,
BizQcWorkOrder.del_flag == 0,
BizQcWorkOrder.reception_source == source,
BizQcWorkOrder.trigger_source == "reception",
BizQcWorkOrder.sample_code == sample_code_col,
)
if f == "complete":
return q.filter(exists().where(and_(base, BizQcWorkOrder.conclusion.in_(["tg", "btg"]))))
if f == "voided":
return q.filter(exists().where(and_(base, BizQcWorkOrder.work_order_status == "yzf")))
if f == "incomplete":
return q.filter(
~exists().where(
and_(
base,
or_(
BizQcWorkOrder.conclusion.in_(["tg", "btg"]),
BizQcWorkOrder.work_order_status == "yzf",
),
)
)
)
return q
def _apply_storage_flag_filter(q, flag: Optional[str], *, sample_code_col, tenant_no: str, source: str):
from sqlalchemy import exists
from app.models.models import BizStorageReleaseReview
f = (flag or "").strip()
if not f:
return q
base = and_(
BizStorageReleaseReview.tenant_no == tenant_no,
BizStorageReleaseReview.del_flag == 0,
BizStorageReleaseReview.reception_source == source,
BizStorageReleaseReview.sample_code == sample_code_col,
)
if f == "yrk":
return q.filter(exists().where(and_(base, BizStorageReleaseReview.storage_status == "yrk")))
if f == "rejected":
return q.filter(
exists().where(
and_(
base,
BizStorageReleaseReview.storage_status != "yrk",
BizStorageReleaseReview.review_conclusion == "btg",
)
)
)
if f == "none":
return q.filter(
~exists().where(
and_(
base,
or_(
BizStorageReleaseReview.storage_status == "yrk",
BizStorageReleaseReview.review_conclusion == "btg",
),
)
)
)
return q
def list_protocol_sample_collections(
db: Session,
*,
page: int = 1,
page_size: int = 10,
tenant_no: str,
keyword: Optional[str] = None,
sample_status: Optional[str] = None,
collection_flag: Optional[str] = None,
reception_flag: Optional[str] = None,
qc_flag: Optional[str] = None,
storage_flag: Optional[str] = None,
storage_cert_handled: Optional[str] = None,
insurance_handled: Optional[str] = None,
user_info=None,
) -> Tuple[List[Dict[str, Any]], int]:
if not tenant_no:
return [], 0
q = _base_query(db, tenant_no)
q = _apply_sample_status_filter(q, collection_flag or sample_status)
q = _apply_reception_flag_filter(
q, reception_flag, sample_code_col=BizProtocolSample.sample_code, tenant_no=tenant_no, source="cell_storage"
)
q = _apply_qc_flag_filter(
q, qc_flag, sample_code_col=BizProtocolSample.sample_code, tenant_no=tenant_no, source="cell_storage"
)
q = _apply_storage_flag_filter(
q, storage_flag, sample_code_col=BizProtocolSample.sample_code, tenant_no=tenant_no, source="cell_storage"
)
from app.models.models import BizProtocolFullmoonTodo, BizProtocolStorageCert
sch = (storage_cert_handled or "").strip()
if sch in ("1", "0", "true", "false"):
want = sch in ("1", "true")
handled_pred = or_(
BizProtocolStorageCert.handle_time.isnot(None),
BizProtocolStorageCert.make_status == "1",
and_(BizProtocolStorageCert.make_date.isnot(None), BizProtocolStorageCert.make_date != ""),
)
q = q.outerjoin(
BizProtocolStorageCert,
and_(
BizProtocolStorageCert.collection_protocol_id == BizCollectionProtocol.id,
BizProtocolStorageCert.tenant_no == tenant_no,
BizProtocolStorageCert.del_flag == 0,
),
)
q = q.filter(handled_pred) if want else q.filter(~handled_pred)
ih = (insurance_handled or "").strip()
if ih in ("1", "0", "true", "false"):
want = ih in ("1", "true")
handled_pred = or_(
BizProtocolFullmoonTodo.handle_time.isnot(None),
BizProtocolFullmoonTodo.todo_status == "1",
and_(BizProtocolFullmoonTodo.handle_date.isnot(None), BizProtocolFullmoonTodo.handle_date != ""),
)
q = q.outerjoin(
BizProtocolFullmoonTodo,
and_(
BizProtocolFullmoonTodo.collection_protocol_id == BizCollectionProtocol.id,
BizProtocolFullmoonTodo.tenant_no == tenant_no,
BizProtocolFullmoonTodo.del_flag == 0,
),
)
q = q.filter(handled_pred) if want else q.filter(~handled_pred)
if keyword and keyword.strip():
kw = f"%{keyword.strip()}%"
kw_conds = [
BizProtocolPartyCustomer.mother_name.like(kw),
BizProtocolSample.sample_code.like(kw),
]
from app.crud import sample_reception as reception_svc
lab_codes = reception_svc.sample_codes_matching_lab_storage_keyword(
db, tenant_no, keyword, reception_source="cell_storage"
)
if lab_codes:
kw_conds.append(BizProtocolSample.sample_code.in_(lab_codes))
q = q.filter(or_(*kw_conds))
if user_info:
from app.core.data_scope import apply_data_scope
q = apply_data_scope(q, BizProtocolSample, user_info, db, tenant_no)
total = q.count()
page = max(1, page)
page_size = min(max(1, page_size), 100)
rows = (
q.order_by(
BizProtocolPartyCustomer.expected_delivery_date.is_(None),
BizProtocolPartyCustomer.expected_delivery_date.desc(),
BizCollectionProtocol.create_time.desc(),
BizProtocolSample.id.desc(),
)
.offset((page - 1) * page_size)
.limit(page_size)
.all()
)
sample_type_ids = list({
ps.sample_type_id for ps, _, _, _ in rows if ps and ps.sample_type_id
})
type_map: Dict[str, str] = {}
if sample_type_ids:
for t in (
db.query(BizCellSampleType)
.filter(BizCellSampleType.id.in_(sample_type_ids), BizCellSampleType.del_flag == 0)
.all()
):
type_map[t.id] = t.type_name or ""
region_ids = list({
(ci.region_id or "").strip()
for _, _, _, ci in rows
if ci and (ci.region_id or "").strip()
})
hospital_ids = list({
(ci.hospital_id or "").strip()
for _, _, _, ci in rows
if ci and (ci.hospital_id or "").strip()
})
region_name_map: Dict[str, str] = {}
hospital_name_map: Dict[str, str] = {}
if region_ids:
for r in (
db.query(BizCollectionRegion.id, BizCollectionRegion.region_name)
.filter(BizCollectionRegion.id.in_(region_ids), BizCollectionRegion.del_flag == 0)
.all()
):
region_name_map[r.id] = r.region_name or ""
if hospital_ids:
for h in (
db.query(BizCollectionHospital.id, BizCollectionHospital.hospital_name)
.filter(BizCollectionHospital.id.in_(hospital_ids), BizCollectionHospital.del_flag == 0)
.all()
):
hospital_name_map[h.id] = h.hospital_name or ""
items: List[Dict[str, Any]] = []
for psample, proto, party, ci in rows:
status = (psample.sample_status or "").strip() if psample else ""
region_id = (ci.region_id or "").strip() if ci else ""
hospital_id = (ci.hospital_id or "").strip() if ci else ""
item = _row_to_dict(
protocol_sample_id=psample.id,
collection_protocol_id=proto.id if proto else "",
protocol_no=(proto.protocol_no or "") if proto else "",
protocol_status=(proto.protocol_status or "") if proto else "",
expected_delivery_date=format_date(party.expected_delivery_date) if party else "",
mother_name=(party.mother_name or "") if party else "",
newborn_name=(party.newborn_name or "") if party else "",
sample_code=psample.sample_code or "",
sample_type_name=type_map.get(psample.sample_type_id or "", ""),
sample_status=status,
sample_status_label=_sample_status_label(status),
region_name=region_name_map.get(region_id, ""),
hospital_name=hospital_name_map.get(hospital_id, ""),
medical_staff=(ci.medical_staff or "") if ci else "",
sample_collect_time=_fmt_dt(ci.sample_collect_time) if ci else "",
maternal_blood_collect_time=_fmt_dt(ci.maternal_blood_collect_time) if ci else "",
)
item.update(sample_special_to_dict(psample))
# 客户服务宽表:当事人 / 协议附件 / 备注 / 市场专员 id
item["motherPhone"] = (party.mother_phone or "") if party else ""
item["motherIdCard"] = (party.mother_id_card or "") if party else ""
item["fatherName"] = (party.father_name or "") if party else ""
item["fatherPhone"] = (party.father_phone or "") if party else ""
item["fatherIdCard"] = (party.father_id_card or "") if party else ""
item["newbornIdCard"] = (party.newborn_id_card or "") if party else ""
item["newbornSex"] = (party.newborn_sex or "") if party else ""
item["newbornDeliveryTime"] = _fmt_dt(party.newborn_delivery_time) if party else ""
item["protocolRemark"] = (proto.remark or "") if proto else ""
item["protocolAttachments"] = (proto.protocol_attachments or "") if proto else ""
item["consentFormUrls"] = (proto.consent_form_urls or "") if proto else ""
item["healthSurveyFormUrls"] = (proto.health_survey_form_urls or "") if proto else ""
item["salesUserId"] = (ci.sales_user_id or "") if ci else ""
items.append(item)
if items and tenant_no:
from app.crud import sample_reception as reception_svc
from app.crud.storage_release_review import _get_user_name
from app.models.models import BizProtocolSamplePayment
codes = [i["sampleCode"] for i in items if i.get("sampleCode")]
code_map = reception_svc.get_lab_storage_code_map_by_sample_codes(
db, tenant_no, codes, reception_source="cell_storage"
)
pay_mode_map: Dict[str, str] = {}
if codes:
for sp in (
db.query(BizProtocolSamplePayment)
.filter(
BizProtocolSamplePayment.tenant_no == tenant_no,
BizProtocolSamplePayment.del_flag == 0,
BizProtocolSamplePayment.sample_code.in_(codes),
)
.all()
):
sc = (sp.sample_code or "").strip()
if sc and sc not in pay_mode_map:
pay_mode_map[sc] = (sp.pay_mode or "").strip()
sales_ids = list({(i.get("salesUserId") or "").strip() for i in items if i.get("salesUserId")})
sales_name_map = {uid: _get_user_name(db, uid) for uid in sales_ids if uid}
pay_mode_label = {"dj": "趸交", "fq": "分期"}
for item in items:
sc = (item.get("sampleCode") or "").strip()
item["labStorageCode"] = code_map.get(sc, "") if sc else ""
mode = pay_mode_map.get(sc, "")
item["payMode"] = mode
item["payModeLabel"] = pay_mode_label.get(mode, mode or "")
sid = (item.get("salesUserId") or "").strip()
item["marketSpecialistName"] = sales_name_map.get(sid, "") if sid else ""
from app.services.sample_info_list_enrich import enrich_sample_info_items
enrich_sample_info_items(
db, tenant_no, items, reception_source="cell_storage", include_handle_flags=True
)
return items, total