Files
shanxia2.0/backend/app/crud/sample_sms_notification.py
T
2026-05-25 15:14:06 +08:00

457 lines
14 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"""
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 generate_uuid
from app.models.models import (
BizCellSampleType,
BizCollectionProtocol,
BizProtocolPartyCustomer,
BizProtocolSample,
BizSampleReception,
BizSampleSmsNotification,
)
# biz_sample_sms_notification may use general_ci; align with unicode_ci tables on JOIN
_UNICODE_CI = "utf8mb4_unicode_ci"
def _unicode_ci(col):
return col.collate(_UNICODE_CI)
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 _normalize_date_bound(value: str, *, end_of_day: bool = False) -> str:
val = (value or "").strip()
if len(val) == 10:
return f"{val} 23:59:59" if end_of_day else f"{val} 00:00:00"
return val
def _accept_conclusion_label(v: str) -> str:
return {"js": "接收", "bj": "拒收"}.get(v, "待接收确认")
def _row_to_dict(
*,
notification_id: str = "",
sample_reception_id: str = "",
protocol_id: str = "",
sms_notified: int = 0,
sms_content: str = "",
protocol_no: str = "",
mother_name: str = "",
newborn_name: str = "",
sample_code: str = "",
sample_type_name: str = "",
reception_time: str = "",
accept_conclusion: str = "",
collection_protocol_id: str = "",
protocol_status: str = "",
) -> Dict[str, Any]:
return {
"id": notification_id,
"sampleReceptionId": sample_reception_id,
"protocolId": protocol_id,
"protocolNo": protocol_no,
"protocolStatus": protocol_status,
"motherName": mother_name,
"newbornName": newborn_name,
"sampleCode": sample_code,
"sampleTypeName": sample_type_name,
"receptionTime": reception_time,
"acceptConclusion": accept_conclusion,
"acceptConclusionLabel": _accept_conclusion_label(accept_conclusion),
"smsNotified": sms_notified,
"smsContent": sms_content,
"collectionProtocolId": collection_protocol_id,
}
def _base_query(db: Session, tenant_no: str):
return (
db.query(
BizProtocolSample,
BizSampleReception,
BizSampleSmsNotification,
BizCollectionProtocol,
BizProtocolPartyCustomer,
)
.outerjoin(
BizSampleReception,
and_(
_unicode_ci(BizSampleReception.sample_code)
== _unicode_ci(BizProtocolSample.sample_code),
BizSampleReception.tenant_no == tenant_no,
BizSampleReception.reception_source == "cell_storage",
BizSampleReception.del_flag == 0,
),
)
.outerjoin(
BizSampleSmsNotification,
and_(
BizSampleSmsNotification.sample_reception_id == BizSampleReception.id,
BizSampleSmsNotification.tenant_no == tenant_no,
BizSampleSmsNotification.del_flag == "0",
),
)
.outerjoin(
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,
),
)
.filter(
BizProtocolSample.tenant_no == tenant_no,
BizProtocolSample.del_flag == 0,
)
)
def _ensure_notification(
db: Session,
reception: Optional[BizSampleReception],
tenant_no: str,
user_id: Optional[str] = None,
*,
psample: Optional[BizProtocolSample] = None,
) -> BizSampleSmsNotification:
lookup_id = reception.id if reception else (psample.id if psample else None)
if not lookup_id:
raise ValueError("reception or psample required")
existing = (
db.query(BizSampleSmsNotification)
.filter(
BizSampleSmsNotification.sample_reception_id == lookup_id,
BizSampleSmsNotification.tenant_no == tenant_no,
BizSampleSmsNotification.del_flag == "0",
)
.first()
)
if existing:
return existing
obj = BizSampleSmsNotification(
id=generate_uuid(),
sample_reception_id=lookup_id,
protocol_id=(reception.collection_protocol_id if reception else psample.collection_protocol_id) or "",
sms_notified=0,
tenant_no=tenant_no,
create_by=user_id,
update_by=user_id,
)
db.add(obj)
db.flush()
db.commit()
db.refresh(obj)
return obj
def resolve_notification(
db: Session,
*,
notification_id: str,
tenant_no: str,
sample_reception_id: Optional[str] = None,
user_id: Optional[str] = None,
) -> Optional[BizSampleSmsNotification]:
notif = (
db.query(BizSampleSmsNotification)
.filter(
BizSampleSmsNotification.id == notification_id,
BizSampleSmsNotification.tenant_no == tenant_no,
BizSampleSmsNotification.del_flag == "0",
)
.first()
)
if notif:
return notif
if not sample_reception_id:
return None
rec = (
db.query(BizSampleReception)
.filter(
BizSampleReception.id == sample_reception_id,
BizSampleReception.tenant_no == tenant_no,
BizSampleReception.del_flag == 0,
)
.first()
)
if rec:
return _ensure_notification(db, rec, tenant_no=tenant_no, user_id=user_id)
psample = (
db.query(BizProtocolSample)
.filter(
BizProtocolSample.id == sample_reception_id,
BizProtocolSample.tenant_no == tenant_no,
BizProtocolSample.del_flag == 0,
)
.first()
)
if psample:
return _ensure_notification(db, None, tenant_no=tenant_no, user_id=user_id, psample=psample)
return None
def _fetch_sample_type_name(db: Session, sample_type_id: Optional[str], tenant_no: str) -> str:
if not sample_type_id:
return ""
t = (
db.query(BizCellSampleType)
.filter(
BizCellSampleType.id == sample_type_id,
BizCellSampleType.del_flag == 0,
)
.first()
)
return (t.type_name or "") if t else ""
def list_notifications(
db: Session,
*,
page: int = 1,
page_size: int = 10,
tenant_no: str,
keyword: Optional[str] = None,
accept_conclusion: Optional[str] = None,
sms_notified: Optional[int] = None,
reception_time_start: Optional[str] = None,
reception_time_end: Optional[str] = None,
user_info=None,
) -> Tuple[List[Dict[str, Any]], int]:
if not tenant_no:
return [], 0
q = _base_query(db, tenant_no)
if keyword and keyword.strip():
kw = f"%{keyword.strip()}%"
q = q.filter(
or_(
BizCollectionProtocol.protocol_no.like(kw),
BizProtocolPartyCustomer.mother_name.like(kw),
BizProtocolSample.sample_code.like(kw),
)
)
if accept_conclusion:
if accept_conclusion == "dqr":
q = q.filter(BizSampleReception.id.is_(None))
else:
q = q.filter(BizSampleReception.accept_conclusion == accept_conclusion)
if sms_notified is not None:
if sms_notified == 0:
q = q.filter(
or_(
BizSampleSmsNotification.sms_notified == 0,
BizSampleSmsNotification.id.is_(None),
)
)
else:
q = q.filter(BizSampleSmsNotification.sms_notified == sms_notified)
if reception_time_start:
q = q.filter(
BizSampleReception.reception_time >= _normalize_date_bound(reception_time_start)
)
if reception_time_end:
q = q.filter(
BizSampleReception.reception_time
<= _normalize_date_bound(reception_time_end, end_of_day=True)
)
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)
# MySQL 5.7 不支持 NULLS LAST;用 IS NULL 升序把空接收时间排到最后
# 末尾加 BizProtocolSample.id 作为确定性排序,避免相同时间的记录顺序不确定
rows = (
q.order_by(
BizSampleReception.reception_time.is_(None),
BizSampleReception.reception_time.desc(),
BizProtocolSample.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 ""
items: List[Dict[str, Any]] = []
for psample, rec, notif, proto, party in rows:
if notif is None:
notif = _ensure_notification(db, rec, tenant_no=tenant_no, psample=psample)
sample_type_name = ""
if psample and psample.sample_type_id:
sample_type_name = type_map.get(psample.sample_type_id, "")
items.append(
_row_to_dict(
notification_id=notif.id,
sample_reception_id=rec.id if rec else psample.id,
protocol_id=notif.protocol_id or "",
sms_notified=notif.sms_notified or 0,
sms_content=notif.sms_content or "",
protocol_no=(proto.protocol_no or "") if proto 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 (rec.sample_code if rec else "") or "",
sample_type_name=sample_type_name,
reception_time=_fmt_dt(rec.reception_time) if rec else "",
accept_conclusion=rec.accept_conclusion if rec else "",
collection_protocol_id=(proto.id or "") if proto else "",
protocol_status=(proto.protocol_status or "") if proto else "",
)
)
return items, int(total)
def toggle_notification_status(
db: Session,
*,
notification_id: str,
tenant_no: str,
sms_notified: int,
user_id: Optional[str] = None,
sample_reception_id: Optional[str] = None,
) -> Optional[BizSampleSmsNotification]:
notif = resolve_notification(
db,
notification_id=notification_id,
tenant_no=tenant_no,
sample_reception_id=sample_reception_id,
user_id=user_id,
)
if not notif:
return None
notif.sms_notified = sms_notified
notif.notified_by = user_id if sms_notified == 1 else None
notif.notified_time = datetime.now() if sms_notified == 1 else None
notif.update_by = user_id
notif.update_time = datetime.now()
db.commit()
db.refresh(notif)
return notif
def generate_sms_content(
db: Session,
*,
notification_id: str,
tenant_no: str,
sample_reception_id: Optional[str] = None,
user_id: Optional[str] = None,
) -> Optional[str]:
notif = resolve_notification(
db,
notification_id=notification_id,
tenant_no=tenant_no,
sample_reception_id=sample_reception_id,
user_id=user_id,
)
if not notif:
return None
rec = (
db.query(BizSampleReception)
.filter(
BizSampleReception.id == notif.sample_reception_id,
BizSampleReception.tenant_no == tenant_no,
BizSampleReception.del_flag == 0,
)
.first()
)
if not rec:
notif.sms_content = "暂无短信内容,样本尚未完成接收确认"
notif.update_time = datetime.now()
db.commit()
db.refresh(notif)
return notif.sms_content
mother_name = ""
proto = (
db.query(BizCollectionProtocol)
.filter(
BizCollectionProtocol.id == rec.collection_protocol_id,
BizCollectionProtocol.del_flag == 0,
)
.first()
)
if proto:
party = (
db.query(BizProtocolPartyCustomer)
.filter(
BizProtocolPartyCustomer.collection_protocol_id == proto.id,
BizProtocolPartyCustomer.del_flag == 0,
)
.first()
)
if party:
mother_name = party.mother_name or ""
sample_code = rec.sample_code or ""
reception_date = _fmt_dt(rec.reception_time)[:10] if rec.reception_time else ""
conclusion_label = _accept_conclusion_label(rec.accept_conclusion or "")
content = (
f"尊敬的{mother_name}女士,您的样本(编码:{sample_code})"
f"已于{reception_date}完成接收处理,结论:{conclusion_label}。"
f"如有疑问请联系客服。"
)
notif.sms_content = content
notif.update_time = datetime.now()
db.commit()
db.refresh(notif)
return content