Files
zkxw/backend/app/services/protocol_sample_list_summary.py
T
zlwuandCursor db31990ebe feat: 细胞存储缴费催缴、统一样本信息与协议支付能力增强
汇总近期业务改动:催缴菜单与后端、样本信息快捷登记与进度、运费关联、客户档案扩展、协议优惠/期次与押金划转,并同步 SQL 迁移与管理端页面收敛。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-10 21:20:37 +08:00

256 lines
8.4 KiB
Python

"""协议/捐赠列表卡片:按样本编码批量取接收结论与门禁质检结论(轻量字段)。"""
from __future__ import annotations
from collections import defaultdict
from typing import Any, Dict, List, Optional
from sqlalchemy.orm import Session
from app.models.models import BizQcTask, BizQcWorkOrder, BizSampleReception
from app.services.qc_work_order_conclusion import (
build_qc_conclusion_fields,
work_order_qc_conclusion_label,
)
_ACCEPT_CONCLUSION_LABELS = {"js": "接收", "bj": "拒收"}
_VALID_QC_CONCLUSIONS = frozenset({"tg", "btg"})
_QC_WORK_ORDER_STATUS_LABELS = {
"dpz": "待配置",
"ypz": "已配置",
"ywc": "已完成",
"yzf": "已作废",
}
def empty_gate_qc_summary_fields() -> Dict[str, str]:
return {
"qcWorkOrderNo": "",
"qcWorkOrderStatus": "",
"qcWorkOrderStatusLabel": "",
"qcConclusion": "",
"qcConclusionLabel": "",
}
def _empty_gate_qc_summary_fields() -> Dict[str, str]:
return empty_gate_qc_summary_fields()
def _load_derived_qc_conclusions_by_work_order_ids(
db: Session,
work_order_ids: List[str],
) -> Dict[str, List[Optional[str]]]:
conclusions_by_wo: Dict[str, List[Optional[str]]] = defaultdict(list)
ids = list({(i or "").strip() for i in work_order_ids if (i or "").strip()})
if not ids:
return conclusions_by_wo
task_rows = (
db.query(BizQcTask.work_order_id, BizQcTask.conclusion)
.filter(
BizQcTask.del_flag == 0,
BizQcTask.work_order_id.in_(ids),
)
.all()
)
for wo_id, conc in task_rows:
wid = (wo_id or "").strip()
if wid:
conclusions_by_wo[wid].append(conc)
return conclusions_by_wo
def _build_gate_qc_summary_fields(
wo: BizQcWorkOrder,
conclusions_by_wo: Dict[str, List[Optional[str]]],
) -> Dict[str, str]:
stored = (getattr(wo, "conclusion", None) or "").strip()
if stored in _VALID_QC_CONCLUSIONS:
qc_conclusion = stored
qc_label = work_order_qc_conclusion_label(stored)
else:
wid = (wo.id or "").strip()
fields = build_qc_conclusion_fields(conclusions_by_wo.get(wid, []))
qc_conclusion = fields["qcConclusion"]
qc_label = fields["qcConclusionLabel"]
status = (wo.work_order_status or "").strip()
return {
"qcWorkOrderNo": (wo.work_order_no or "").strip(),
"qcWorkOrderStatus": status,
"qcWorkOrderStatusLabel": _QC_WORK_ORDER_STATUS_LABELS.get(status, status),
"qcConclusion": qc_conclusion,
"qcConclusionLabel": qc_label,
}
def _build_gate_qc_summaries_for_work_orders(
db: Session,
work_orders: List[BizQcWorkOrder],
) -> Dict[str, Dict[str, str]]:
if not work_orders:
return {}
need_derive_ids: List[str] = []
for wo in work_orders:
stored = (getattr(wo, "conclusion", None) or "").strip()
if stored not in _VALID_QC_CONCLUSIONS:
wid = (wo.id or "").strip()
if wid:
need_derive_ids.append(wid)
conclusions_by_wo = _load_derived_qc_conclusions_by_work_order_ids(db, need_derive_ids)
out: Dict[str, Dict[str, str]] = {}
for wo in work_orders:
wid = (wo.id or "").strip()
if wid:
out[wid] = _build_gate_qc_summary_fields(wo, conclusions_by_wo)
return out
def build_sample_item_reception_qc_fields(
reception: Optional[Dict[str, Any]],
qc: Optional[Dict[str, Any]],
) -> Dict[str, Any]:
"""合并接收/质检结论摘要为列表 sampleItems 字段。"""
conclusion = ""
remark = ""
if reception:
conclusion = (reception.get("acceptConclusion") or "").strip()
remark = (reception.get("remark") or "").strip()
qc_code = ""
qc_label = ""
if qc:
qc_code = (qc.get("qcConclusion") or "").strip()
qc_label = (qc.get("qcConclusionLabel") or "").strip()
if qc_code and not qc_label:
qc_label = work_order_qc_conclusion_label(qc_code)
return {
"receptionAcceptConclusion": conclusion,
"receptionAcceptConclusionLabel": _ACCEPT_CONCLUSION_LABELS.get(conclusion, ""),
"receptionRemark": remark,
"qcConclusion": qc_code,
"qcConclusionLabel": qc_label,
}
def get_list_reception_summary_map_by_sample_codes(
db: Session,
tenant_no: str,
sample_codes: List[str],
*,
reception_source: str,
) -> Dict[str, Dict[str, str]]:
"""按租户 + 样本编码 + 来源返回接收摘要(acceptConclusion / remark),同码取首条。"""
codes = list({(c or "").strip() for c in sample_codes if (c or "").strip()})
if not tenant_no or not codes or not (reception_source or "").strip():
return {}
rows = (
db.query(BizSampleReception)
.filter(
BizSampleReception.tenant_no == tenant_no,
BizSampleReception.del_flag == 0,
BizSampleReception.reception_source == reception_source,
BizSampleReception.sample_code.in_(codes),
)
.all()
)
out: Dict[str, Dict[str, str]] = {}
for rec in rows:
code = (rec.sample_code or "").strip()
if not code:
continue
prev = out.get(code)
prev_t = prev.get("_receptionTime") if prev else None
rec_t = rec.reception_time
if prev and rec_t and prev_t and rec_t <= prev_t:
continue
if prev and rec_t is None and prev_t is not None:
continue
out[code] = {
"acceptConclusion": (rec.accept_conclusion or "").strip(),
"remark": (rec.remark or "").strip(),
"receptionTime": rec_t.strftime("%Y-%m-%d %H:%M:%S") if rec_t else "",
"_receptionTime": rec_t,
}
for v in out.values():
v.pop("_receptionTime", None)
return out
def get_list_gate_qc_summary_map_by_reception_ids(
db: Session,
tenant_no: str,
reception_ids: List[str],
) -> Dict[str, Dict[str, str]]:
"""按 reception_id 批量返回样本接收触发的门禁质检摘要(工单号、状态、结论)。"""
ids = list({(i or "").strip() for i in reception_ids if (i or "").strip()})
if not tenant_no or not ids:
return {}
rows = (
db.query(BizQcWorkOrder)
.filter(
BizQcWorkOrder.tenant_no == tenant_no,
BizQcWorkOrder.del_flag == 0,
BizQcWorkOrder.trigger_source == "reception",
BizQcWorkOrder.reception_id.in_(ids),
)
.all()
)
summaries_by_wo_id = _build_gate_qc_summaries_for_work_orders(db, rows)
out: Dict[str, Dict[str, str]] = {}
for wo in rows:
rid = (wo.reception_id or "").strip()
wid = (wo.id or "").strip()
if not rid or rid in out or not wid:
continue
out[rid] = summaries_by_wo_id.get(wid) or _empty_gate_qc_summary_fields()
return out
def get_list_qc_conclusion_map_by_sample_codes(
db: Session,
tenant_no: str,
sample_codes: List[str],
*,
reception_source: str,
) -> Dict[str, Dict[str, str]]:
"""按租户 + 样本编码 + 来源返回门禁质检摘要(工单号、状态、结论)。"""
codes = list({(c or "").strip() for c in sample_codes if (c or "").strip()})
if not tenant_no or not codes or not (reception_source or "").strip():
return {}
rows = (
db.query(BizQcWorkOrder)
.filter(
BizQcWorkOrder.tenant_no == tenant_no,
BizQcWorkOrder.del_flag == 0,
BizQcWorkOrder.reception_source == reception_source,
BizQcWorkOrder.trigger_source == "reception",
BizQcWorkOrder.sample_code.in_(codes),
)
.all()
)
code_to_wo: Dict[str, BizQcWorkOrder] = {}
for wo in rows:
code = (wo.sample_code or "").strip()
if code and code not in code_to_wo:
code_to_wo[code] = wo
summaries_by_wo_id = _build_gate_qc_summaries_for_work_orders(db, list(code_to_wo.values()))
out: Dict[str, Dict[str, str]] = {}
for code, wo in code_to_wo.items():
wid = (wo.id or "").strip()
out[code] = summaries_by_wo_id.get(wid) or _empty_gate_qc_summary_fields()
return out
# 兼容旧调用名
def get_list_qc_status_map_by_sample_codes(
db: Session,
tenant_no: str,
sample_codes: List[str],
*,
reception_source: str,
) -> Dict[str, Dict[str, str]]:
return get_list_qc_conclusion_map_by_sample_codes(
db, tenant_no, sample_codes, reception_source=reception_source
)