487 lines
16 KiB
Python
487 lines
16 KiB
Python
"""样本接收 CRUD(质量检测)"""
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
from typing import Any, Dict, List, Optional, Tuple
|
|
|
|
from sqlalchemy import and_, func, or_
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.core.utils import generate_uuid
|
|
from app.crud.collection_protocol import urls_to_attachment_list
|
|
from app.crud import sample_reception_check_result as check_result_svc
|
|
from app.models.models import (
|
|
BizCellSampleType,
|
|
BizCollectionProtocol,
|
|
BizExpressCompany,
|
|
BizProtocolPartyCustomer,
|
|
BizProtocolSample,
|
|
BizProtocolSampleTransport,
|
|
BizSampleReception,
|
|
SysUser,
|
|
)
|
|
|
|
|
|
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 _reception_to_dict(
|
|
r: BizSampleReception,
|
|
*,
|
|
check_results: Optional[List[Dict[str, Any]]] = None,
|
|
) -> Dict[str, Any]:
|
|
return {
|
|
"id": r.id,
|
|
"protocolSampleId": r.protocol_sample_id,
|
|
"collectionProtocolId": r.collection_protocol_id,
|
|
"receptionTime": _fmt_dt(r.reception_time),
|
|
"handlerUserId": r.handler_user_id or "",
|
|
"handlerName": r.handler_name or "",
|
|
"receiveTemperature": r.receive_temperature or "",
|
|
"volumeMl": _dec(r.volume_ml),
|
|
"netWeightG": _dec(r.net_weight_g),
|
|
"checkResults": check_results or [],
|
|
"acceptConclusion": r.accept_conclusion or "",
|
|
"receiveAttachments": r.receive_attachments or "",
|
|
"remark": r.remark or "",
|
|
"createTime": _fmt_dt(r.create_time),
|
|
"updateTime": _fmt_dt(r.update_time),
|
|
}
|
|
|
|
|
|
def _sample_detail_rows_query(db: Session):
|
|
"""协议样本 + 协议单 + 客户 + 运输(显式 select_from 避免多表 join 歧义)。"""
|
|
return (
|
|
db.query(
|
|
BizProtocolSample,
|
|
BizCollectionProtocol,
|
|
BizProtocolPartyCustomer,
|
|
BizProtocolSampleTransport,
|
|
)
|
|
.select_from(BizProtocolSample)
|
|
.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(
|
|
BizProtocolSampleTransport,
|
|
and_(
|
|
BizProtocolSampleTransport.sample_id == BizProtocolSample.id,
|
|
BizProtocolSampleTransport.del_flag == 0,
|
|
),
|
|
)
|
|
)
|
|
|
|
|
|
def _sample_pending_query(db: Session, tenant_no: str, keyword: Optional[str] = None):
|
|
"""待接收:协议样本且尚无有效接收记录。"""
|
|
received_ids = (
|
|
db.query(BizSampleReception.protocol_sample_id)
|
|
.filter(
|
|
BizSampleReception.tenant_no == tenant_no,
|
|
BizSampleReception.del_flag == 0,
|
|
)
|
|
.subquery()
|
|
)
|
|
q = _sample_detail_rows_query(db).filter(
|
|
BizProtocolSample.del_flag == 0,
|
|
BizProtocolSample.tenant_no == tenant_no,
|
|
BizCollectionProtocol.tenant_no == tenant_no,
|
|
~BizProtocolSample.id.in_(received_ids),
|
|
)
|
|
if keyword and keyword.strip():
|
|
kw = f"%{keyword.strip()}%"
|
|
q = q.filter(
|
|
or_(
|
|
BizCollectionProtocol.protocol_no.like(kw),
|
|
BizProtocolSample.sample_code.like(kw),
|
|
BizProtocolPartyCustomer.mother_name.like(kw),
|
|
BizProtocolPartyCustomer.newborn_name.like(kw),
|
|
)
|
|
)
|
|
return q
|
|
|
|
|
|
def _rows_to_sample_list_items(
|
|
db: Session,
|
|
rows: list,
|
|
tenant_no: str,
|
|
*,
|
|
reception_map: Optional[Dict[str, BizSampleReception]] = None,
|
|
) -> List[Dict[str, Any]]:
|
|
type_ids = list({s.sample_type_id for s, *_ in rows if s.sample_type_id})
|
|
type_map: Dict[str, str] = {}
|
|
if type_ids:
|
|
for t in (
|
|
db.query(BizCellSampleType)
|
|
.filter(
|
|
BizCellSampleType.id.in_(type_ids),
|
|
BizCellSampleType.del_flag == 0,
|
|
)
|
|
.all()
|
|
):
|
|
type_map[t.id] = t.type_name or ""
|
|
|
|
express_ids = list(
|
|
{tr.express_company_id for *_, tr in rows if tr and tr.express_company_id}
|
|
)
|
|
express_map: Dict[str, str] = {}
|
|
if express_ids:
|
|
for e in (
|
|
db.query(BizExpressCompany)
|
|
.filter(BizExpressCompany.id.in_(express_ids), BizExpressCompany.del_flag == 0)
|
|
.all()
|
|
):
|
|
express_map[e.id] = e.express_name or ""
|
|
|
|
reception_ids = (
|
|
[reception_map[s.id].id for s, *_ in rows if s.id in reception_map]
|
|
if reception_map
|
|
else []
|
|
)
|
|
results_map = (
|
|
check_result_svc.list_results_by_reception_ids(db, reception_ids, tenant_no)
|
|
if reception_ids
|
|
else {}
|
|
)
|
|
|
|
items: List[Dict[str, Any]] = []
|
|
for sample, protocol, party, transport in rows:
|
|
item: Dict[str, Any] = {
|
|
"protocolSampleId": sample.id,
|
|
"collectionProtocolId": protocol.id,
|
|
"protocolNo": protocol.protocol_no or "",
|
|
"protocolStatus": protocol.protocol_status or "",
|
|
"sampleCode": sample.sample_code or "",
|
|
"sampleTypeId": sample.sample_type_id or "",
|
|
"sampleTypeName": type_map.get(sample.sample_type_id or "", ""),
|
|
"collectAmount": _dec(sample.collect_amount),
|
|
"sampleStatus": sample.sample_status or "",
|
|
"motherName": (party.mother_name if party else "") or "",
|
|
"newbornName": (party.newborn_name if party else "") or "",
|
|
"etaTime": _fmt_dt(transport.eta_time) if transport and transport.eta_time else "",
|
|
"transportType": (transport.transport_type if transport else "") or "",
|
|
"waybillNo": (transport.waybill_no if transport else "") or "",
|
|
"expressCompanyId": (transport.express_company_id if transport else "") or "",
|
|
"expressCompanyName": express_map.get(
|
|
(transport.express_company_id if transport else "") or "", ""
|
|
),
|
|
"carrierName": (transport.carrier_name if transport else "") or "",
|
|
}
|
|
if reception_map and sample.id in reception_map:
|
|
rec = reception_map[sample.id]
|
|
cr = results_map.get(rec.id)
|
|
if not cr:
|
|
cr = check_result_svc.get_check_results_for_reception(db, rec, tenant_no)
|
|
item.update(_reception_to_dict(rec, check_results=cr))
|
|
item["receiveAttachmentFiles"] = urls_to_attachment_list(
|
|
rec.receive_attachments, db, tenant_no
|
|
)
|
|
items.append(item)
|
|
return items
|
|
|
|
|
|
def list_pending_protocol_samples(
|
|
db: Session,
|
|
*,
|
|
page: int = 1,
|
|
page_size: int = 10,
|
|
tenant_no: Optional[str] = None,
|
|
keyword: Optional[str] = None,
|
|
) -> Tuple[List[Dict[str, Any]], int]:
|
|
if not tenant_no:
|
|
return [], 0
|
|
q_base = _sample_pending_query(db, tenant_no, keyword)
|
|
total = q_base.order_by(None).with_entities(func.count(BizProtocolSample.id)).scalar() or 0
|
|
page = max(1, page)
|
|
page_size = min(max(1, page_size), 100)
|
|
rows = (
|
|
q_base.order_by(
|
|
BizProtocolSampleTransport.eta_time.is_(None),
|
|
BizProtocolSampleTransport.eta_time.asc(),
|
|
BizProtocolSample.create_time.asc(),
|
|
)
|
|
.offset((page - 1) * page_size)
|
|
.limit(page_size)
|
|
.all()
|
|
)
|
|
return _rows_to_sample_list_items(db, rows, tenant_no), int(total)
|
|
|
|
|
|
def list_received_protocol_samples(
|
|
db: Session,
|
|
*,
|
|
page: int = 1,
|
|
page_size: int = 10,
|
|
tenant_no: Optional[str] = None,
|
|
keyword: Optional[str] = None,
|
|
) -> Tuple[List[Dict[str, Any]], int]:
|
|
if not tenant_no:
|
|
return [], 0
|
|
q_rec = db.query(BizSampleReception).filter(
|
|
BizSampleReception.tenant_no == tenant_no,
|
|
BizSampleReception.del_flag == 0,
|
|
)
|
|
if keyword and keyword.strip():
|
|
kw = f"%{keyword.strip()}%"
|
|
sample_ids_kw = (
|
|
_sample_detail_rows_query(db)
|
|
.with_entities(BizProtocolSample.id)
|
|
.filter(
|
|
BizProtocolSample.del_flag == 0,
|
|
BizProtocolSample.tenant_no == tenant_no,
|
|
BizCollectionProtocol.tenant_no == tenant_no,
|
|
or_(
|
|
BizCollectionProtocol.protocol_no.like(kw),
|
|
BizProtocolSample.sample_code.like(kw),
|
|
BizProtocolPartyCustomer.mother_name.like(kw),
|
|
BizProtocolPartyCustomer.newborn_name.like(kw),
|
|
),
|
|
)
|
|
.subquery()
|
|
)
|
|
q_rec = q_rec.filter(
|
|
or_(
|
|
BizSampleReception.handler_name.like(kw),
|
|
BizSampleReception.protocol_sample_id.in_(sample_ids_kw),
|
|
)
|
|
)
|
|
|
|
total = q_rec.count()
|
|
page = max(1, page)
|
|
page_size = min(max(1, page_size), 100)
|
|
receptions = (
|
|
q_rec.order_by(BizSampleReception.reception_time.desc())
|
|
.offset((page - 1) * page_size)
|
|
.limit(page_size)
|
|
.all()
|
|
)
|
|
if not receptions:
|
|
return [], int(total)
|
|
|
|
sample_ids = [r.protocol_sample_id for r in receptions]
|
|
reception_map = {r.protocol_sample_id: r for r in receptions}
|
|
|
|
rows = (
|
|
_sample_detail_rows_query(db)
|
|
.filter(BizProtocolSample.id.in_(sample_ids))
|
|
.all()
|
|
)
|
|
row_by_sample = {row[0].id: row for row in rows}
|
|
ordered_rows = [row_by_sample[sid] for sid in sample_ids if sid in row_by_sample]
|
|
items = _rows_to_sample_list_items(
|
|
db, ordered_rows, tenant_no, reception_map=reception_map
|
|
)
|
|
return items, int(total)
|
|
|
|
|
|
def get_reception_by_id(
|
|
db: Session, reception_id: str, tenant_no: str
|
|
) -> Optional[Dict[str, Any]]:
|
|
rec = (
|
|
db.query(BizSampleReception)
|
|
.filter(
|
|
BizSampleReception.id == reception_id,
|
|
BizSampleReception.tenant_no == tenant_no,
|
|
BizSampleReception.del_flag == 0,
|
|
)
|
|
.first()
|
|
)
|
|
if not rec:
|
|
return None
|
|
sample = (
|
|
db.query(BizProtocolSample)
|
|
.filter(
|
|
BizProtocolSample.id == rec.protocol_sample_id,
|
|
BizProtocolSample.del_flag == 0,
|
|
)
|
|
.first()
|
|
)
|
|
if not sample:
|
|
cr = check_result_svc.get_check_results_for_reception(db, rec, tenant_no)
|
|
out = _reception_to_dict(rec, check_results=cr)
|
|
out["receiveAttachmentFiles"] = urls_to_attachment_list(
|
|
rec.receive_attachments, db, tenant_no
|
|
)
|
|
return out
|
|
rows = (
|
|
_sample_detail_rows_query(db)
|
|
.filter(BizProtocolSample.id == sample.id)
|
|
.all()
|
|
)
|
|
if not rows:
|
|
cr = check_result_svc.get_check_results_for_reception(db, rec, tenant_no)
|
|
out = _reception_to_dict(rec, check_results=cr)
|
|
out["receiveAttachmentFiles"] = urls_to_attachment_list(
|
|
rec.receive_attachments, db, tenant_no
|
|
)
|
|
return out
|
|
items = _rows_to_sample_list_items(
|
|
db, rows, tenant_no, reception_map={rec.protocol_sample_id: rec}
|
|
)
|
|
return items[0] if items else _reception_to_dict(rec)
|
|
|
|
|
|
def create_sample_reception(
|
|
db: Session,
|
|
*,
|
|
protocol_sample_id: str,
|
|
tenant_no: str,
|
|
user_id: Optional[str],
|
|
create_dept_id: str = "",
|
|
reception_time: datetime,
|
|
handler_user_id: Optional[str],
|
|
handler_name: Optional[str],
|
|
receive_temperature: Optional[str],
|
|
volume_ml: Optional[float] = None,
|
|
net_weight_g: Optional[float] = None,
|
|
checks: List[Dict[str, Any]],
|
|
accept_conclusion: str,
|
|
receive_attachments: Optional[str],
|
|
remark: Optional[str],
|
|
) -> BizSampleReception:
|
|
sample = (
|
|
db.query(BizProtocolSample)
|
|
.filter(
|
|
BizProtocolSample.id == protocol_sample_id,
|
|
BizProtocolSample.tenant_no == tenant_no,
|
|
BizProtocolSample.del_flag == 0,
|
|
)
|
|
.first()
|
|
)
|
|
if not sample:
|
|
raise ValueError("样本不存在")
|
|
exists = (
|
|
db.query(BizSampleReception)
|
|
.filter(
|
|
BizSampleReception.protocol_sample_id == protocol_sample_id,
|
|
BizSampleReception.tenant_no == tenant_no,
|
|
BizSampleReception.del_flag == 0,
|
|
)
|
|
.first()
|
|
)
|
|
if exists:
|
|
raise ValueError("该样本已登记接收,请勿重复操作")
|
|
|
|
conclusion = (accept_conclusion or "").strip()
|
|
if conclusion not in ("js", "bj"):
|
|
raise ValueError("接收结论无效")
|
|
|
|
handler_display = (handler_name or "").strip()
|
|
if handler_user_id and not handler_display:
|
|
u = db.query(SysUser).filter(SysUser.id == handler_user_id).first()
|
|
if u:
|
|
handler_display = u.nick_name or ""
|
|
|
|
obj = BizSampleReception(
|
|
id=generate_uuid(),
|
|
protocol_sample_id=protocol_sample_id,
|
|
collection_protocol_id=sample.collection_protocol_id,
|
|
reception_time=reception_time,
|
|
handler_user_id=handler_user_id,
|
|
handler_name=handler_display or None,
|
|
receive_temperature=(receive_temperature or "").strip() or None,
|
|
volume_ml=volume_ml,
|
|
net_weight_g=net_weight_g,
|
|
check_packaging=0,
|
|
check_sealing=0,
|
|
check_no_damage=0,
|
|
check_no_leakage=0,
|
|
check_color=0,
|
|
check_tying_rope=0,
|
|
check_no_stratification=0,
|
|
accept_conclusion=conclusion,
|
|
receive_attachments=(receive_attachments or "").strip() or None,
|
|
tenant_no=tenant_no,
|
|
remark=(remark or "").strip() or None,
|
|
create_by=user_id,
|
|
create_dept_id=create_dept_id or "",
|
|
update_by=user_id,
|
|
)
|
|
db.add(obj)
|
|
db.flush()
|
|
check_result_svc.save_reception_check_results(
|
|
db,
|
|
reception_id=obj.id,
|
|
tenant_no=tenant_no,
|
|
user_id=user_id,
|
|
create_dept_id=create_dept_id or "",
|
|
checks=checks,
|
|
)
|
|
from app.models.models import BizSampleReceptionCheckResult
|
|
|
|
saved_rows = (
|
|
db.query(BizSampleReceptionCheckResult)
|
|
.filter(
|
|
BizSampleReceptionCheckResult.reception_id == obj.id,
|
|
BizSampleReceptionCheckResult.del_flag == 0,
|
|
)
|
|
.all()
|
|
)
|
|
check_result_svc.sync_legacy_columns_from_results(db, obj, saved_rows)
|
|
db.add(obj)
|
|
if conclusion == "js":
|
|
from app.crud import qc_work_order as qc_wo_svc
|
|
|
|
qc_wo_svc.create_work_order_from_reception(
|
|
db,
|
|
reception=obj,
|
|
tenant_no=tenant_no,
|
|
user_id=user_id,
|
|
create_dept_id=create_dept_id or "",
|
|
)
|
|
db.commit()
|
|
db.refresh(obj)
|
|
return obj
|
|
|
|
|
|
def get_receptions_map_for_samples(
|
|
db: Session,
|
|
sample_ids: List[str],
|
|
tenant_no: str,
|
|
) -> Dict[str, Dict[str, Any]]:
|
|
"""按协议样本 ID 批量查询接收记录(含检查项明细、附件列表),供协议单查看弹窗使用。"""
|
|
if not sample_ids or not tenant_no:
|
|
return {}
|
|
rows = (
|
|
db.query(BizSampleReception)
|
|
.filter(
|
|
BizSampleReception.protocol_sample_id.in_(sample_ids),
|
|
BizSampleReception.tenant_no == tenant_no,
|
|
BizSampleReception.del_flag == 0,
|
|
)
|
|
.all()
|
|
)
|
|
if not rows:
|
|
return {}
|
|
reception_ids = [r.id for r in rows]
|
|
results_map = check_result_svc.list_results_by_reception_ids(db, reception_ids, tenant_no)
|
|
out: Dict[str, Dict[str, Any]] = {}
|
|
for rec in rows:
|
|
cr = results_map.get(rec.id)
|
|
if not cr:
|
|
cr = check_result_svc.get_check_results_for_reception(db, rec, tenant_no)
|
|
item = _reception_to_dict(rec, check_results=cr)
|
|
item["receiveAttachmentFiles"] = urls_to_attachment_list(
|
|
rec.receive_attachments, db, tenant_no
|
|
)
|
|
out[rec.protocol_sample_id] = item
|
|
return out
|