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