Files
shanxia2.0/backend/app/crud/storage_release_review.py
T
2026-05-23 21:27:10 +08:00

815 lines
28 KiB
Python

from __future__ import annotations
import math
from datetime import datetime
from typing import Any, Dict, List, Optional
from sqlalchemy import func, or_
from sqlalchemy.orm import Session
from app.core.utils import generate_uuid
from app.models.models import (
BizCollectionProtocol,
BizProductionTask,
BizProductionWorkOrder,
BizProtocolPartyCustomer,
BizProtocolSample,
BizProtocolSampleTransport,
BizQcTask,
BizQcWorkOrder,
BizSampleReception,
BizStorageReleaseReview,
SysUser,
)
from app.models.research_donation import (
BizResearchDonation,
BizResearchDonationParty,
BizResearchDonationSample,
BizResearchDonationSampleTransport,
)
_RECEPTION_SOURCE_LABELS = {
"cell_storage": "细胞存储",
"research_donation": "科研捐赠",
}
_REVIEW_CONCLUSION_LABELS = {"tg": "通过", "btg": "不通过"}
_STORAGE_STATUS_LABELS = {"drk": "待入库", "yrk": "已入库"}
_PROTOCOL_STATUS_LABELS = {
"dsx": "待生效",
"sxz": "生效中",
"ygq": "已过期",
"yzf": "已作废",
"zky": "转科研",
}
_DONATION_STATUS_LABELS = {
"dsx": "待生效",
"sxz": "生效中",
"ygq": "已过期",
"yzf": "已作废",
"zckc": "转细胞存储",
}
_QC_WORK_ORDER_STATUS_LABELS = {"dpz": "待配置", "ypz": "已配置", "ywc": "已完成"}
_PRODUCTION_WORK_ORDER_STATUS_LABELS = {"dpz": "待配置", "jxz": "进行中", "ywc": "已完成"}
def _fmt_dt(t: Optional[datetime]) -> str:
return t.strftime("%Y-%m-%d %H:%M:%S") if t else ""
def total_pages(total: int, page_size: int) -> int:
if total <= 0:
return 0
return int(math.ceil(total / float(page_size)))
def _row_to_dict(r: BizStorageReleaseReview) -> Dict[str, Any]:
return {
"id": r.id,
"sampleCode": r.sample_code or "",
"receptionSource": r.reception_source or "",
"receptionSourceLabel": _RECEPTION_SOURCE_LABELS.get(r.reception_source or "", r.reception_source or ""),
"protocolId": r.protocol_id or "",
"receptionId": r.reception_id or "",
"protocolNo": r.protocol_no or "",
"sampleName": r.sample_name or "",
"reviewConclusion": r.review_conclusion or "",
"reviewConclusionLabel": _REVIEW_CONCLUSION_LABELS.get(r.review_conclusion or "", r.review_conclusion or ""),
"reviewRemark": r.review_remark or "",
"reviewAttachments": r.review_attachments or "",
"reviewBy": r.review_by or "",
"reviewByName": r.review_by_name or "",
"reviewTime": _fmt_dt(r.review_time),
"storageStatus": r.storage_status or "drk",
"storageStatusLabel": _STORAGE_STATUS_LABELS.get(r.storage_status or "drk", r.storage_status or "drk"),
"storageBy": r.storage_by or "",
"storageByName": r.storage_by_name or "",
"storageTime": _fmt_dt(r.storage_time),
"remark": r.remark or "",
"createTime": _fmt_dt(r.create_time),
"updateTime": _fmt_dt(r.update_time),
}
def create_review_from_reception(
db: Session,
*,
reception: BizSampleReception,
protocol_no: str,
sample_name: str,
tenant_no: str,
user_id: Optional[str] = None,
create_dept_id: str = "",
) -> BizStorageReleaseReview:
source = getattr(reception, "reception_source", "cell_storage") or "cell_storage"
if source == "research_donation":
protocol_id = reception.research_donation_id or ""
else:
protocol_id = reception.collection_protocol_id or ""
obj = BizStorageReleaseReview(
id=generate_uuid(),
sample_code=reception.sample_code or "",
reception_source=source,
protocol_id=protocol_id,
reception_id=reception.id,
protocol_no=protocol_no or None,
sample_name=sample_name or None,
storage_status="drk",
tenant_no=tenant_no,
create_by=user_id,
create_dept_id=create_dept_id or "",
update_by=user_id,
create_time=datetime.now(),
update_time=datetime.now(),
del_flag=0,
)
db.add(obj)
db.flush()
return obj
def list_reviews(
db: Session,
*,
tenant_no: str,
page: int = 1,
page_size: int = 10,
reception_source: Optional[str] = None,
review_conclusion: Optional[str] = None,
storage_status: Optional[str] = None,
keyword: Optional[str] = None,
user_info=None,
) -> Dict[str, Any]:
q0 = db.query(BizStorageReleaseReview).filter(
BizStorageReleaseReview.tenant_no == tenant_no,
BizStorageReleaseReview.del_flag == 0,
)
rs = (reception_source or "").strip()
if rs:
q0 = q0.filter(BizStorageReleaseReview.reception_source == rs)
rc = (review_conclusion or "").strip()
if rc:
q0 = q0.filter(BizStorageReleaseReview.review_conclusion == rc)
ss = (storage_status or "").strip()
if ss:
q0 = q0.filter(BizStorageReleaseReview.storage_status == ss)
kw = (keyword or "").strip()
if kw:
like = f"%{kw}%"
q0 = q0.filter(
or_(
BizStorageReleaseReview.protocol_no.like(like),
BizStorageReleaseReview.sample_code.like(like),
BizStorageReleaseReview.sample_name.like(like),
)
)
if user_info:
from app.core.data_scope import apply_data_scope
q0 = apply_data_scope(q0, BizStorageReleaseReview, user_info, db, tenant_no)
total = q0.order_by(None).with_entities(func.count(BizStorageReleaseReview.id)).scalar() or 0
page = max(1, page)
page_size = min(max(1, page_size), 100)
rows = (
q0.order_by(BizStorageReleaseReview.create_time.desc())
.offset((page - 1) * page_size)
.limit(page_size)
.all()
)
items = _enrich_review_list_items(db, [_row_to_dict(r) for r in rows])
return {
"list": items,
"total": int(total),
"page": page,
"pageSize": page_size,
"totalPages": total_pages(int(total), page_size),
}
def _enrich_review_list_items(db: Session, items: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
if not items:
return items
from app.crud.qc_work_order import _enrich_work_order_list_items
items = _enrich_work_order_list_items(db, items)
cell_pids = [
x["protocolId"]
for x in items
if (x.get("receptionSource") or "cell_storage") != "research_donation" and x.get("protocolId")
]
donation_pids = [
x["protocolId"]
for x in items
if (x.get("receptionSource") or "") == "research_donation" and x.get("protocolId")
]
proto_map: Dict[str, BizCollectionProtocol] = {}
if cell_pids:
proto_map = {
p.id: p
for p in db.query(BizCollectionProtocol)
.filter(BizCollectionProtocol.id.in_(cell_pids), BizCollectionProtocol.del_flag == 0)
.all()
}
party_map: Dict[str, str] = {}
if cell_pids:
for party in (
db.query(BizProtocolPartyCustomer)
.filter(
BizProtocolPartyCustomer.collection_protocol_id.in_(cell_pids),
BizProtocolPartyCustomer.del_flag == 0,
)
.all()
):
party_map[party.collection_protocol_id] = party.mother_name or ""
donation_map: Dict[str, BizResearchDonation] = {}
if donation_pids:
donation_map = {
d.id: d
for d in db.query(BizResearchDonation)
.filter(BizResearchDonation.id.in_(donation_pids), BizResearchDonation.del_flag == 0)
.all()
}
dparty_map: Dict[str, str] = {}
if donation_pids:
for party in (
db.query(BizResearchDonationParty)
.filter(
BizResearchDonationParty.research_donation_id.in_(donation_pids),
BizResearchDonationParty.del_flag == 0,
)
.all()
):
dparty_map[party.research_donation_id] = party.mother_name or ""
for item in items:
source = item.get("receptionSource") or "cell_storage"
pid = item.get("protocolId") or ""
item.setdefault("motherName", "")
item.setdefault("protocolStatus", "")
item.setdefault("protocolStatusLabel", "")
if source == "research_donation":
donation = donation_map.get(pid)
if donation:
st = donation.donation_status or ""
item["protocolStatus"] = st
item["protocolStatusLabel"] = _DONATION_STATUS_LABELS.get(st, st)
item["motherName"] = dparty_map.get(pid, "")
else:
proto = proto_map.get(pid)
if proto:
st = getattr(proto, "protocol_status", "") or ""
item["protocolStatus"] = st
item["protocolStatusLabel"] = _PROTOCOL_STATUS_LABELS.get(st, st)
item["motherName"] = party_map.get(pid, "")
return items
def _get_user_name(db: Session, user_id: str) -> str:
u = db.query(SysUser).filter(SysUser.id == user_id).first()
return (u.nick_name or "") if u else ""
def get_review_detail(db: Session, review_id: str, tenant_no: str) -> Optional[Dict[str, Any]]:
rev = (
db.query(BizStorageReleaseReview)
.filter(
BizStorageReleaseReview.id == review_id,
BizStorageReleaseReview.tenant_no == tenant_no,
BizStorageReleaseReview.del_flag == 0,
)
.first()
)
if not rev:
return None
result: Dict[str, Any] = {"review": _row_to_dict(rev)}
source = rev.reception_source or "cell_storage"
if source == "cell_storage":
protocol_info = _build_cell_storage_protocol_info(db, rev)
result["protocolInfo"] = protocol_info
transport_info = _build_cell_storage_transport_info(db, rev)
result["transportInfo"] = transport_info
else:
protocol_info = _build_research_donation_protocol_info(db, rev)
result["protocolInfo"] = protocol_info
transport_info = _build_research_donation_transport_info(db, rev)
result["transportInfo"] = transport_info
from app.crud import sample_reception as sample_reception_svc
reception_detail = sample_reception_svc.get_reception_by_id(
db, rev.reception_id or "", tenant_no
)
if reception_detail:
result["reception"] = reception_detail
result["receptionInfo"] = {
"receptionTime": reception_detail.get("receptionTime") or "",
"handlerName": reception_detail.get("handlerName") or "",
"receiveTemperature": reception_detail.get("receiveTemperature") or "",
"volumeMl": reception_detail.get("volumeMl"),
"netWeightG": reception_detail.get("netWeightG"),
"acceptConclusion": reception_detail.get("acceptConclusion") or "",
"acceptConclusionLabel": reception_detail.get("acceptConclusionLabel") or "",
"remark": reception_detail.get("remark") or "",
"checkResults": reception_detail.get("checkResults") or [],
"receiveAttachments": reception_detail.get("receiveAttachments") or "",
"receiveAttachmentFiles": reception_detail.get("receiveAttachmentFiles") or [],
"transportType": reception_detail.get("transportType") or "",
"expressCompanyName": reception_detail.get("expressCompanyName") or "",
"waybillNo": reception_detail.get("waybillNo") or "",
"carrierName": reception_detail.get("carrierName") or "",
"carrierPhone": reception_detail.get("carrierPhone") or "",
"etaTime": reception_detail.get("etaTime") or "",
}
else:
result["receptionInfo"] = _build_reception_info(db, rev)
qc_info = _build_qc_info(db, rev, tenant_no)
result["qcInfo"] = qc_info
production_info = _build_production_info(db, rev, tenant_no)
result["productionInfo"] = production_info
return result
def _build_cell_storage_protocol_info(db: Session, rev: BizStorageReleaseReview) -> Dict[str, Any]:
proto = (
db.query(BizCollectionProtocol)
.filter(
BizCollectionProtocol.id == (rev.protocol_id or ""),
BizCollectionProtocol.del_flag == 0,
)
.first()
)
if not proto:
return {}
sample = (
db.query(BizProtocolSample)
.filter(
BizProtocolSample.collection_protocol_id == proto.id,
BizProtocolSample.sample_code == (rev.sample_code or ""),
BizProtocolSample.tenant_no == rev.tenant_no,
BizProtocolSample.del_flag == 0,
)
.first()
)
party = (
db.query(BizProtocolPartyCustomer)
.filter(
BizProtocolPartyCustomer.collection_protocol_id == proto.id,
BizProtocolPartyCustomer.del_flag == 0,
)
.first()
)
info: Dict[str, Any] = {
"protocolNo": proto.protocol_no or "",
"protocolStatus": getattr(proto, "protocol_status", "") or "",
"motherName": "",
"motherPhone": "",
"fatherName": "",
"fatherPhone": "",
"newbornName": "",
"sampleCode": rev.sample_code or "",
"sampleTypeName": "",
"collectAmount": None,
"storageYears": None,
}
if sample:
from app.crud.qc_work_order import _load_sample_type_name_map
type_map = _load_sample_type_name_map(db, [sample.sample_type_id]) if sample.sample_type_id else {}
info["sampleTypeName"] = type_map.get(sample.sample_type_id or "", "")
info["collectAmount"] = float(sample.collect_amount) if sample.collect_amount else None
info["storageYears"] = sample.storage_years
if party:
info["motherName"] = party.mother_name or ""
info["motherPhone"] = party.mother_phone or ""
info["fatherName"] = party.father_name or ""
info["fatherPhone"] = party.father_phone or ""
info["newbornName"] = party.newborn_name or ""
return info
def _build_research_donation_protocol_info(db: Session, rev: BizStorageReleaseReview) -> Dict[str, Any]:
donation = (
db.query(BizResearchDonation)
.filter(
BizResearchDonation.id == (rev.protocol_id or ""),
BizResearchDonation.del_flag == 0,
)
.first()
)
if not donation:
return {}
dsample = (
db.query(BizResearchDonationSample)
.filter(
BizResearchDonationSample.research_donation_id == donation.id,
BizResearchDonationSample.sample_code == (rev.sample_code or ""),
BizResearchDonationSample.tenant_no == rev.tenant_no,
BizResearchDonationSample.del_flag == 0,
)
.first()
)
party = (
db.query(BizResearchDonationParty)
.filter(
BizResearchDonationParty.research_donation_id == donation.id,
BizResearchDonationParty.del_flag == 0,
)
.first()
)
info: Dict[str, Any] = {
"protocolNo": donation.donation_no or "",
"protocolStatus": donation.donation_status or "",
"motherName": "",
"motherPhone": "",
"fatherName": "",
"fatherPhone": "",
"newbornName": "",
"sampleCode": rev.sample_code or "",
"sampleTypeName": "",
"collectAmount": None,
"storageYears": None,
}
if dsample:
from app.crud.qc_work_order import _load_sample_type_name_map
type_map = _load_sample_type_name_map(db, [dsample.sample_type_id]) if dsample.sample_type_id else {}
info["sampleTypeName"] = type_map.get(dsample.sample_type_id or "", "")
info["collectAmount"] = float(dsample.collect_amount) if dsample.collect_amount else None
info["storageYears"] = dsample.storage_years
if party:
info["motherName"] = party.mother_name or ""
info["motherPhone"] = party.mother_phone or ""
info["fatherName"] = party.father_name or ""
info["fatherPhone"] = party.father_phone or ""
info["newbornName"] = party.newborn_name or ""
return info
def _build_cell_storage_transport_info(db: Session, rev: BizStorageReleaseReview) -> Dict[str, Any]:
sample = (
db.query(BizProtocolSample)
.filter(
BizProtocolSample.sample_code == (rev.sample_code or ""),
BizProtocolSample.collection_protocol_id == (rev.protocol_id or ""),
BizProtocolSample.tenant_no == rev.tenant_no,
BizProtocolSample.del_flag == 0,
)
.first()
)
if not sample:
return {}
transport = (
db.query(BizProtocolSampleTransport)
.filter(
BizProtocolSampleTransport.sample_id == sample.id,
BizProtocolSampleTransport.collection_protocol_id == (rev.protocol_id or ""),
BizProtocolSampleTransport.del_flag == 0,
)
.first()
)
if not transport:
return {}
return {
"transportType": transport.transport_type or "",
"waybillNo": transport.waybill_no or "",
"expressFee": float(transport.express_fee) if transport.express_fee else None,
"etaTime": _fmt_dt(transport.eta_time),
"carrierName": transport.carrier_name or "",
"carrierPhone": transport.carrier_phone or "",
}
def _build_research_donation_transport_info(db: Session, rev: BizStorageReleaseReview) -> Dict[str, Any]:
dsample = (
db.query(BizResearchDonationSample)
.filter(
BizResearchDonationSample.sample_code == (rev.sample_code or ""),
BizResearchDonationSample.research_donation_id == (rev.protocol_id or ""),
BizResearchDonationSample.tenant_no == rev.tenant_no,
BizResearchDonationSample.del_flag == 0,
)
.first()
)
if not dsample:
return {}
transport = (
db.query(BizResearchDonationSampleTransport)
.filter(
BizResearchDonationSampleTransport.sample_id == dsample.id,
BizResearchDonationSampleTransport.research_donation_id == (rev.protocol_id or ""),
BizResearchDonationSampleTransport.del_flag == 0,
)
.first()
)
if not transport:
return {}
return {
"transportType": transport.transport_type or "",
"waybillNo": transport.waybill_no or "",
"expressFee": float(transport.express_fee) if transport.express_fee else None,
"etaTime": _fmt_dt(transport.eta_time),
"carrierName": transport.carrier_name or "",
"carrierPhone": transport.carrier_phone or "",
}
def _build_reception_info(db: Session, rev: BizStorageReleaseReview) -> Dict[str, Any]:
rec = (
db.query(BizSampleReception)
.filter(
BizSampleReception.id == (rev.reception_id or ""),
BizSampleReception.tenant_no == rev.tenant_no,
BizSampleReception.del_flag == 0,
)
.first()
)
if not rec:
return {}
return {
"receptionTime": _fmt_dt(rec.reception_time),
"handlerName": rec.handler_name or "",
"receiveTemperature": rec.receive_temperature or "",
"volumeMl": float(rec.volume_ml) if rec.volume_ml else None,
"netWeightG": float(rec.net_weight_g) if rec.net_weight_g else None,
"acceptConclusion": rec.accept_conclusion or "",
}
def _build_qc_info(db: Session, rev: BizStorageReleaseReview, tenant_no: str) -> Dict[str, Any]:
wo = (
db.query(BizQcWorkOrder)
.filter(
BizQcWorkOrder.sample_code == (rev.sample_code or ""),
BizQcWorkOrder.tenant_no == tenant_no,
BizQcWorkOrder.del_flag == 0,
)
.first()
)
if not wo:
return {}
tasks = (
db.query(BizQcTask)
.filter(
BizQcTask.work_order_id == wo.id,
BizQcTask.tenant_no == tenant_no,
BizQcTask.del_flag == 0,
)
.all()
)
task_list = []
_task_type_labels = {"sysz": "实验室自检", "dsfjc": "第三方检测"}
_task_status_labels = {"dzx": "待执行", "jxz": "进行中", "ywc": "已完成"}
_conclusion_labels = {"tg": "通过", "btg": "不通过"}
for t in tasks:
task_list.append({
"id": t.id,
"taskType": t.task_type or "",
"taskTypeLabel": _task_type_labels.get(t.task_type or "", t.task_type or ""),
"taskStatus": t.task_status or "",
"taskStatusLabel": _task_status_labels.get(t.task_status or "", t.task_status or ""),
"conclusion": getattr(t, "conclusion", "") or "",
"conclusionLabel": _conclusion_labels.get(getattr(t, "conclusion", "") or "", getattr(t, "conclusion", "") or ""),
})
wo_status = wo.work_order_status or ""
return {
"workOrderNo": wo.work_order_no or "",
"workOrderStatus": wo_status,
"workOrderStatusLabel": _QC_WORK_ORDER_STATUS_LABELS.get(wo_status, wo_status),
"tasks": task_list,
}
def _build_production_info(db: Session, rev: BizStorageReleaseReview, tenant_no: str) -> Dict[str, Any]:
pwo = (
db.query(BizProductionWorkOrder)
.filter(
BizProductionWorkOrder.reception_id == (rev.reception_id or ""),
BizProductionWorkOrder.tenant_no == tenant_no,
BizProductionWorkOrder.del_flag == 0,
)
.first()
)
if not pwo:
return {}
tasks = (
db.query(BizProductionTask)
.filter(
BizProductionTask.work_order_id == pwo.id,
BizProductionTask.tenant_no == tenant_no,
BizProductionTask.del_flag == 0,
)
.all()
)
task_list = []
_task_status_labels = {"dpz": "待配置", "dzx": "待执行", "jxz": "执行中", "ywc": "已完成"}
for t in tasks:
task_list.append({
"id": t.id,
"taskNo": t.task_no or "",
"taskName": t.task_name or "",
"taskStatus": t.task_status or "",
"taskStatusLabel": _task_status_labels.get(t.task_status or "", t.task_status or ""),
"startedTime": _fmt_dt(t.started_time),
"completedTime": _fmt_dt(t.completed_time),
})
pwo_status = pwo.work_order_status or ""
return {
"workOrderNo": pwo.work_order_no or "",
"workOrderStatus": pwo_status,
"workOrderStatusLabel": _PRODUCTION_WORK_ORDER_STATUS_LABELS.get(pwo_status, pwo_status),
"tasks": task_list,
}
def submit_review(
db: Session,
review_id: str,
*,
review_conclusion: str,
review_remark: Optional[str] = None,
review_attachments: Optional[str] = None,
tenant_no: str,
user_id: str,
) -> BizStorageReleaseReview:
rev = (
db.query(BizStorageReleaseReview)
.filter(
BizStorageReleaseReview.id == review_id,
BizStorageReleaseReview.tenant_no == tenant_no,
BizStorageReleaseReview.del_flag == 0,
)
.first()
)
if not rev:
raise ValueError("审核记录不存在")
user_name = _get_user_name(db, user_id)
rev.review_conclusion = review_conclusion
rev.review_remark = (review_remark or "").strip() or None
rev.review_attachments = (review_attachments or "").strip() or None
rev.review_by = user_id
rev.review_by_name = user_name
rev.review_time = datetime.now()
rev.update_by = user_id
rev.update_time = datetime.now()
db.add(rev)
db.flush()
return rev
def batch_submit_review(
db: Session,
ids: List[str],
*,
review_conclusion: str,
review_remark: Optional[str] = None,
review_attachments: Optional[str] = None,
tenant_no: str,
user_id: str,
) -> Dict[str, Any]:
success = 0
fail = 0
for rid in ids:
try:
submit_review(
db,
rid,
review_conclusion=review_conclusion,
review_remark=review_remark,
review_attachments=review_attachments,
tenant_no=tenant_no,
user_id=user_id,
)
success += 1
except Exception:
fail += 1
return {"success": success, "fail": fail}
def store_sample(
db: Session,
review_id: str,
*,
tenant_no: str,
user_id: str,
) -> BizStorageReleaseReview:
rev = (
db.query(BizStorageReleaseReview)
.filter(
BizStorageReleaseReview.id == review_id,
BizStorageReleaseReview.tenant_no == tenant_no,
BizStorageReleaseReview.del_flag == 0,
)
.first()
)
if not rev:
raise ValueError("审核记录不存在")
if rev.review_conclusion != "tg":
raise ValueError("只有审核通过的记录才能执行入库操作")
if rev.storage_status != "drk":
raise ValueError("当前状态不允许入库操作")
user_name = _get_user_name(db, user_id)
rev.storage_status = "yrk"
rev.storage_by = user_id
rev.storage_by_name = user_name
rev.storage_time = datetime.now()
rev.update_by = user_id
rev.update_time = datetime.now()
db.add(rev)
if (rev.reception_source or "cell_storage") == "cell_storage":
sample_code = (rev.sample_code or "").strip()
if sample_code:
ps = (
db.query(BizProtocolSample)
.filter(
BizProtocolSample.sample_code == sample_code,
BizProtocolSample.tenant_no == tenant_no,
BizProtocolSample.del_flag == 0,
)
.first()
)
if ps:
ps.sample_status = "yrk"
ps.update_time = datetime.now()
db.add(ps)
db.flush()
return rev
def batch_store_samples(
db: Session,
ids: List[str],
*,
tenant_no: str,
user_id: str,
) -> Dict[str, Any]:
success = 0
fail = 0
for rid in ids:
try:
store_sample(db, rid, tenant_no=tenant_no, user_id=user_id)
success += 1
except Exception:
fail += 1
return {"success": success, "fail": fail}
def get_storage_release_map_for_samples(
db: Session,
sample_ids: List[str],
tenant_no: str,
*,
sample_code_by_id: Optional[Dict[str, str]] = None,
) -> Dict[str, Dict[str, Any]]:
"""按样本批量返回存储放行审核摘要。
当传入 sample_code_by_id 时,按租户 + 样本编码关联存储放行审核记录,
返回以 sample_id 为 key 的审核信息映射。
"""
if not tenant_no or not sample_code_by_id:
return {}
codes = list({(c or "").strip() for c in sample_code_by_id.values() if (c or "").strip()})
if not codes:
return {}
rows = (
db.query(BizStorageReleaseReview)
.filter(
BizStorageReleaseReview.tenant_no == tenant_no,
BizStorageReleaseReview.del_flag == 0,
BizStorageReleaseReview.sample_code.in_(codes),
)
.all()
)
code_to_review: Dict[str, BizStorageReleaseReview] = {}
for r in rows:
code = (r.sample_code or "").strip()
if code and code not in code_to_review:
code_to_review[code] = r
out: Dict[str, Dict[str, Any]] = {}
for sid, raw_code in sample_code_by_id.items():
code = (raw_code or "").strip()
if not code:
continue
rev = code_to_review.get(code)
if not rev or not (rev.review_conclusion or "").strip():
continue
rc = rev.review_conclusion or ""
out[sid] = {
"reviewConclusion": rc,
"reviewConclusionLabel": _REVIEW_CONCLUSION_LABELS.get(rc, rc),
"reviewByName": rev.review_by_name or "",
"reviewTime": _fmt_dt(rev.review_time),
"reviewRemark": rev.review_remark or "",
"reviewAttachments": rev.review_attachments or "",
}
return out