"""采集协议样本采集信息列表 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