1488 lines
49 KiB
Python
1488 lines
49 KiB
Python
"""质检工单 CRUD"""
|
||
from __future__ import annotations
|
||
|
||
from datetime import datetime
|
||
from decimal import Decimal
|
||
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 import qc_lab_test_item as lab_item_svc
|
||
from app.crud import qc_task as task_svc
|
||
from app.crud import qc_third_party_item as third_item_svc
|
||
from app.crud.qc_third_party_company import get_company
|
||
from app.crud.collection_protocol import urls_to_attachment_list
|
||
from app.crud import sample_reception as sample_reception_svc
|
||
from app.crud import sample_reception_check_result as check_result_svc
|
||
from app.crud.cell_product import _enrich_product_dicts as _cp_enrich, _row_to_dict as _cp_row_to_dict, get_cell_product
|
||
from app.models.models import (
|
||
BizCellProduct,
|
||
BizCellSampleType,
|
||
BizCollectionProtocol,
|
||
BizProtocolSample,
|
||
BizQcLabTestItem,
|
||
BizQcTask,
|
||
BizQcThirdPartyItem,
|
||
BizQcWorkOrder,
|
||
BizQcWorkOrderLabItem,
|
||
BizQcWorkOrderThirdItem,
|
||
BizSampleReception,
|
||
)
|
||
from app.models.qc_param import BizQcTaskResult
|
||
from app.models.research_donation import BizResearchDonation, BizResearchDonationSample
|
||
|
||
_RECEPTION_SOURCE_LABELS = {
|
||
"cell_storage": "细胞存储",
|
||
"research_donation": "科研捐赠",
|
||
}
|
||
_QC_BIZ_TYPE_LABELS = {
|
||
"inbound": "入库质检",
|
||
"outbound": "出库质检",
|
||
"temporary": "临时质检",
|
||
}
|
||
_ACCEPT_CONCLUSION_LABELS = {"js": "接收", "bj": "拒收"}
|
||
_WORK_ORDER_STATUS_LABELS = {"dpz": "待配置", "ypz": "已配置", "ywc": "已完成"}
|
||
_TASK_TYPE_LABELS = {"sysz": "实验室自检", "dsfjc": "第三方检测"}
|
||
_TASK_STATUS_LABELS = {"dzx": "待执行", "jxz": "进行中", "ywc": "已完成"}
|
||
_PROTOCOL_STATUS_LABELS = {
|
||
"dsx": "待生效", "sxz": "生效中", "ygq": "已过期", "yzf": "已作废", "zky": "转科研"
|
||
}
|
||
_DONATION_STATUS_LABELS = {
|
||
"dsx": "待生效", "sxz": "生效中", "ygq": "已过期", "yzf": "已作废", "zckc": "转细胞存储"
|
||
}
|
||
_CONCLUSION_LABELS = {"tg": "通过", "btg": "不通过"}
|
||
|
||
|
||
def _dec(v: Any) -> Optional[float]:
|
||
if v is None:
|
||
return None
|
||
return float(v)
|
||
|
||
|
||
def _fmt_dt(t: Optional[datetime]) -> str:
|
||
return t.strftime("%Y-%m-%d %H:%M:%S") if t else ""
|
||
|
||
|
||
def _reception_source_label(source: Optional[str]) -> str:
|
||
key = (source or "").strip() or "cell_storage"
|
||
return _RECEPTION_SOURCE_LABELS.get(key, key)
|
||
|
||
|
||
def _qc_biz_type_label(biz_type: Optional[str]) -> str:
|
||
key = (biz_type or "").strip() or "inbound"
|
||
return _QC_BIZ_TYPE_LABELS.get(key, key)
|
||
|
||
|
||
def _work_order_qc_biz_type(wo: BizQcWorkOrder) -> str:
|
||
wo_type = getattr(wo, "work_order_type", None) or "inbound"
|
||
if wo_type == "temporary":
|
||
return "temporary"
|
||
return getattr(wo, "qc_biz_type", None) or "inbound"
|
||
|
||
|
||
def _validate_cell_product_id(
|
||
db: Session, cell_product_id: Optional[str], tenant_no: str
|
||
) -> str:
|
||
cp_id = (cell_product_id or "").strip()
|
||
if not cp_id:
|
||
raise ValueError("请选择质检对象(细胞产品)")
|
||
cp = get_cell_product(db, cp_id, tenant_no)
|
||
if not cp:
|
||
raise ValueError("细胞产品不存在")
|
||
return cp_id
|
||
|
||
|
||
def _enrich_work_order_cell_products(
|
||
db: Session, tenant_no: str, items: List[Dict[str, Any]]
|
||
) -> List[Dict[str, Any]]:
|
||
if not items:
|
||
return items
|
||
cp_ids = list({x.get("cellProductId") for x in items if x.get("cellProductId")})
|
||
if not cp_ids:
|
||
return items
|
||
products = (
|
||
db.query(BizCellProduct)
|
||
.filter(
|
||
BizCellProduct.id.in_(cp_ids),
|
||
BizCellProduct.tenant_no == tenant_no,
|
||
BizCellProduct.del_flag == 0,
|
||
)
|
||
.all()
|
||
)
|
||
product_dicts = _cp_enrich(db, tenant_no, [_cp_row_to_dict(p) for p in products])
|
||
product_map = {d["id"]: d for d in product_dicts}
|
||
for item in items:
|
||
cp = product_map.get(item.get("cellProductId") or "")
|
||
item["cellProductCode"] = cp.get("productCode", "") if cp else ""
|
||
item["cellProductName"] = cp.get("productName", "") if cp else ""
|
||
item["cellProductTypeName"] = cp.get("productTypeName", "") if cp else ""
|
||
item["cellProductPassage"] = cp.get("passage", "") if cp else ""
|
||
item["cellProductSpecName"] = cp.get("specName", "") if cp else ""
|
||
return items
|
||
|
||
|
||
def _accept_conclusion_label(v: Optional[str]) -> str:
|
||
key = (v or "").strip()
|
||
return _ACCEPT_CONCLUSION_LABELS.get(key, key)
|
||
|
||
|
||
def _load_sample_type_name_map(db: Session, type_ids: List[str]) -> Dict[str, str]:
|
||
ids = [x for x in type_ids if x]
|
||
if not ids:
|
||
return {}
|
||
return {
|
||
t.id: t.type_name or ""
|
||
for t in db.query(BizCellSampleType)
|
||
.filter(BizCellSampleType.id.in_(ids), BizCellSampleType.del_flag == 0)
|
||
.all()
|
||
}
|
||
|
||
|
||
def _sample_type_name_for_reception(
|
||
db: Session,
|
||
rec: BizSampleReception,
|
||
type_map: Dict[str, str],
|
||
*,
|
||
protocol_samples: Dict[str, BizProtocolSample],
|
||
donation_samples: Dict[str, BizResearchDonationSample],
|
||
) -> str:
|
||
if (rec.reception_source or "cell_storage") == "research_donation":
|
||
sample = donation_samples.get(rec.donation_sample_id or "")
|
||
tid = sample.sample_type_id if sample else ""
|
||
else:
|
||
sample = protocol_samples.get(rec.protocol_sample_id or "")
|
||
tid = sample.sample_type_id if sample else ""
|
||
return type_map.get(tid or "", "")
|
||
|
||
|
||
def _enrich_work_order_list_items(
|
||
db: Session, items: List[Dict[str, Any]]
|
||
) -> List[Dict[str, Any]]:
|
||
if not items:
|
||
return items
|
||
rec_ids = [x["receptionId"] for x in items if x.get("receptionId")]
|
||
if not rec_ids:
|
||
return items
|
||
rec_map = {
|
||
r.id: r
|
||
for r in db.query(BizSampleReception)
|
||
.filter(BizSampleReception.id.in_(rec_ids), BizSampleReception.del_flag == 0)
|
||
.all()
|
||
}
|
||
cell_sample_ids = [
|
||
r.protocol_sample_id
|
||
for r in rec_map.values()
|
||
if (r.reception_source or "cell_storage") != "research_donation" and r.protocol_sample_id
|
||
]
|
||
donation_sample_ids = [
|
||
r.donation_sample_id
|
||
for r in rec_map.values()
|
||
if (r.reception_source or "") == "research_donation" and r.donation_sample_id
|
||
]
|
||
protocol_samples: Dict[str, BizProtocolSample] = {}
|
||
if cell_sample_ids:
|
||
protocol_samples = {
|
||
s.id: s
|
||
for s in db.query(BizProtocolSample)
|
||
.filter(BizProtocolSample.id.in_(cell_sample_ids), BizProtocolSample.del_flag == 0)
|
||
.all()
|
||
}
|
||
donation_samples: Dict[str, BizResearchDonationSample] = {}
|
||
if donation_sample_ids:
|
||
donation_samples = {
|
||
s.id: s
|
||
for s in db.query(BizResearchDonationSample)
|
||
.filter(
|
||
BizResearchDonationSample.id.in_(donation_sample_ids),
|
||
BizResearchDonationSample.del_flag == 0,
|
||
)
|
||
.all()
|
||
}
|
||
type_ids = list(
|
||
{
|
||
s.sample_type_id
|
||
for s in list(protocol_samples.values()) + list(donation_samples.values())
|
||
if s.sample_type_id
|
||
}
|
||
)
|
||
type_map = _load_sample_type_name_map(db, type_ids)
|
||
cell_proto_ids = [
|
||
rec.collection_protocol_id
|
||
for rec in rec_map.values()
|
||
if (rec.reception_source or "cell_storage") != "research_donation" and rec.collection_protocol_id
|
||
]
|
||
donation_ids = [
|
||
rec.research_donation_id
|
||
for rec in rec_map.values()
|
||
if (rec.reception_source or "") == "research_donation" and rec.research_donation_id
|
||
]
|
||
proto_status_map: Dict[str, str] = {}
|
||
if cell_proto_ids:
|
||
for p in (
|
||
db.query(BizCollectionProtocol.id, BizCollectionProtocol.protocol_status)
|
||
.filter(BizCollectionProtocol.id.in_(cell_proto_ids), BizCollectionProtocol.del_flag == 0)
|
||
.all()
|
||
):
|
||
proto_status_map[p.id] = p.protocol_status or ""
|
||
donation_status_map: Dict[str, str] = {}
|
||
if donation_ids:
|
||
for d in (
|
||
db.query(BizResearchDonation.id, BizResearchDonation.donation_status)
|
||
.filter(BizResearchDonation.id.in_(donation_ids), BizResearchDonation.del_flag == 0)
|
||
.all()
|
||
):
|
||
donation_status_map[d.id] = d.donation_status or ""
|
||
for item in items:
|
||
if item.get("workOrderType") == "temporary":
|
||
item["receptionSource"] = ""
|
||
item["receptionSourceLabel"] = ""
|
||
continue
|
||
rec = rec_map.get(item.get("receptionId") or "")
|
||
source = item.get("receptionSource") or (rec.reception_source if rec else "cell_storage")
|
||
item["receptionSourceLabel"] = _reception_source_label(source)
|
||
if not rec:
|
||
item.setdefault("receptionTime", "")
|
||
item.setdefault("handlerName", "")
|
||
item.setdefault("sampleTypeName", "")
|
||
continue
|
||
item["receptionTime"] = _fmt_dt(rec.reception_time)
|
||
item["handlerName"] = rec.handler_name or ""
|
||
item["sampleTypeName"] = _sample_type_name_for_reception(
|
||
db,
|
||
rec,
|
||
type_map,
|
||
protocol_samples=protocol_samples,
|
||
donation_samples=donation_samples,
|
||
)
|
||
if source == "research_donation":
|
||
_did = rec.research_donation_id if rec else ""
|
||
_st = donation_status_map.get(_did or "", "")
|
||
item["protocolStatus"] = _st
|
||
item["protocolStatusLabel"] = _DONATION_STATUS_LABELS.get(_st, _st)
|
||
else:
|
||
_pid = rec.collection_protocol_id if rec else ""
|
||
_st = proto_status_map.get(_pid or "", "")
|
||
item["protocolStatus"] = _st
|
||
item["protocolStatusLabel"] = _PROTOCOL_STATUS_LABELS.get(_st, _st)
|
||
return items
|
||
|
||
|
||
def _gen_daily_no(db: Session, tenant_no: str, prefix: str) -> str:
|
||
day = datetime.now().strftime("%Y%m%d")
|
||
like = f"{prefix}{day}%"
|
||
cnt = (
|
||
db.query(BizQcWorkOrder)
|
||
.filter(
|
||
BizQcWorkOrder.tenant_no == tenant_no,
|
||
BizQcWorkOrder.work_order_no.like(like),
|
||
)
|
||
.count()
|
||
)
|
||
return f"{prefix}{day}{cnt + 1:04d}"
|
||
|
||
|
||
def _wo_to_dict(wo: BizQcWorkOrder) -> Dict[str, Any]:
|
||
wo_type = getattr(wo, "work_order_type", None) or "inbound"
|
||
recv_source_raw = getattr(wo, "reception_source", None)
|
||
if wo_type == "temporary":
|
||
recv_source = (recv_source_raw or "").strip()
|
||
else:
|
||
recv_source = (recv_source_raw or "cell_storage").strip() or "cell_storage"
|
||
return {
|
||
"id": wo.id,
|
||
"workOrderNo": wo.work_order_no,
|
||
"workOrderType": wo_type,
|
||
"receptionId": wo.reception_id,
|
||
"protocolSampleId": wo.protocol_sample_id,
|
||
"collectionProtocolId": wo.collection_protocol_id,
|
||
"protocolNo": wo.protocol_no or "",
|
||
"sampleCode": wo.sample_code or "",
|
||
"receptionSource": recv_source,
|
||
"receptionSourceLabel": _reception_source_label(recv_source) if recv_source else "",
|
||
"qcBizType": _work_order_qc_biz_type(wo),
|
||
"qcBizTypeLabel": _qc_biz_type_label(_work_order_qc_biz_type(wo)),
|
||
"cellProductId": getattr(wo, "cell_product_id", None) or "",
|
||
"cellProductCode": "",
|
||
"cellProductName": "",
|
||
"cellProductTypeName": "",
|
||
"cellProductPassage": "",
|
||
"cellProductSpecName": "",
|
||
"receptionTime": "",
|
||
"handlerName": "",
|
||
"sampleTypeName": "",
|
||
"workOrderStatus": wo.work_order_status,
|
||
"workOrderStatusLabel": _WORK_ORDER_STATUS_LABELS.get(
|
||
wo.work_order_status or "", wo.work_order_status or ""
|
||
),
|
||
"hasLab": wo.has_lab == "1",
|
||
"hasThirdParty": wo.has_third_party == "1",
|
||
"configuredTime": _fmt_dt(wo.configured_time),
|
||
"configuredBy": wo.configured_by or "",
|
||
"thirdPartyFeeTotal": _dec(wo.third_party_fee_total),
|
||
"remark": wo.remark or "",
|
||
"createTime": _fmt_dt(wo.create_time),
|
||
"updateTime": _fmt_dt(wo.update_time),
|
||
}
|
||
|
||
|
||
def create_work_order_from_reception(
|
||
db: Session,
|
||
*,
|
||
reception,
|
||
tenant_no: str,
|
||
user_id: Optional[str] = None,
|
||
create_dept_id: str = "",
|
||
) -> Optional[BizQcWorkOrder]:
|
||
"""接收结论为 js 时创建待配置工单(flush,不 commit)。"""
|
||
if (reception.accept_conclusion or "").strip() != "js":
|
||
return None
|
||
exists = (
|
||
db.query(BizQcWorkOrder)
|
||
.filter(
|
||
BizQcWorkOrder.reception_id == reception.id,
|
||
BizQcWorkOrder.tenant_no == tenant_no,
|
||
BizQcWorkOrder.del_flag == 0,
|
||
)
|
||
.first()
|
||
)
|
||
if exists:
|
||
return exists
|
||
|
||
source = getattr(reception, "reception_source", "cell_storage") or "cell_storage"
|
||
|
||
if source == "research_donation":
|
||
protocol_no = ""
|
||
d = (
|
||
db.query(BizResearchDonation)
|
||
.filter(
|
||
BizResearchDonation.id == (reception.research_donation_id or ""),
|
||
BizResearchDonation.del_flag == 0,
|
||
)
|
||
.first()
|
||
)
|
||
if d:
|
||
protocol_no = d.donation_no or ""
|
||
sample_code = ""
|
||
ds = (
|
||
db.query(BizResearchDonationSample)
|
||
.filter(
|
||
BizResearchDonationSample.id == (reception.donation_sample_id or ""),
|
||
BizResearchDonationSample.del_flag == 0,
|
||
)
|
||
.first()
|
||
)
|
||
if ds:
|
||
sample_code = ds.sample_code or ""
|
||
wo = BizQcWorkOrder(
|
||
id=generate_uuid(),
|
||
work_order_no=_gen_daily_no(db, tenant_no, "ZJ"),
|
||
reception_id=reception.id,
|
||
protocol_sample_id=reception.donation_sample_id or "",
|
||
collection_protocol_id=reception.research_donation_id or "",
|
||
protocol_no=protocol_no or None,
|
||
sample_code=sample_code or None,
|
||
reception_source="research_donation",
|
||
qc_biz_type="inbound",
|
||
work_order_status="dpz",
|
||
has_lab="0",
|
||
has_third_party="0",
|
||
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,
|
||
)
|
||
else:
|
||
protocol_no = ""
|
||
p = (
|
||
db.query(BizCollectionProtocol)
|
||
.filter(
|
||
BizCollectionProtocol.id == reception.collection_protocol_id,
|
||
BizCollectionProtocol.del_flag == 0,
|
||
)
|
||
.first()
|
||
)
|
||
if p:
|
||
protocol_no = p.protocol_no or ""
|
||
sample_code = ""
|
||
s = (
|
||
db.query(BizProtocolSample)
|
||
.filter(BizProtocolSample.id == reception.protocol_sample_id, BizProtocolSample.del_flag == 0)
|
||
.first()
|
||
)
|
||
if s:
|
||
sample_code = s.sample_code or ""
|
||
wo = BizQcWorkOrder(
|
||
id=generate_uuid(),
|
||
work_order_no=_gen_daily_no(db, tenant_no, "ZJ"),
|
||
reception_id=reception.id,
|
||
protocol_sample_id=reception.protocol_sample_id,
|
||
collection_protocol_id=reception.collection_protocol_id,
|
||
protocol_no=protocol_no or None,
|
||
sample_code=sample_code or None,
|
||
reception_source="cell_storage",
|
||
qc_biz_type="inbound",
|
||
work_order_status="dpz",
|
||
has_lab="0",
|
||
has_third_party="0",
|
||
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(wo)
|
||
db.flush()
|
||
return wo
|
||
|
||
|
||
def list_work_orders(
|
||
db: Session,
|
||
*,
|
||
page: int = 1,
|
||
page_size: int = 10,
|
||
keyword: Optional[str] = None,
|
||
work_order_status: Optional[str] = None,
|
||
work_order_type: Optional[str] = None,
|
||
tenant_no: Optional[str] = None,
|
||
user_info=None,
|
||
) -> Tuple[List[Dict[str, Any]], int]:
|
||
def _filtered():
|
||
q0 = db.query(BizQcWorkOrder).filter(BizQcWorkOrder.del_flag == 0)
|
||
if tenant_no:
|
||
q0 = q0.filter(BizQcWorkOrder.tenant_no == tenant_no)
|
||
wt = (work_order_type or "").strip()
|
||
if wt:
|
||
q0 = q0.filter(BizQcWorkOrder.work_order_type == wt)
|
||
kw = (keyword or "").strip()
|
||
if kw:
|
||
like = f"%{kw}%"
|
||
q0 = q0.filter(
|
||
or_(
|
||
BizQcWorkOrder.work_order_no.like(like),
|
||
BizQcWorkOrder.protocol_no.like(like),
|
||
BizQcWorkOrder.sample_code.like(like),
|
||
)
|
||
)
|
||
st = (work_order_status or "").strip()
|
||
if st:
|
||
q0 = q0.filter(BizQcWorkOrder.work_order_status == st)
|
||
if user_info:
|
||
from app.core.data_scope import apply_data_scope
|
||
q0 = apply_data_scope(q0, BizQcWorkOrder, user_info, db, tenant_no)
|
||
return q0
|
||
|
||
total = _filtered().order_by(None).with_entities(func.count(BizQcWorkOrder.id)).scalar() or 0
|
||
page = max(1, page)
|
||
page_size = min(max(1, page_size), 100)
|
||
rows = (
|
||
_filtered()
|
||
.order_by(BizQcWorkOrder.create_time.desc())
|
||
.offset((page - 1) * page_size)
|
||
.limit(page_size)
|
||
.all()
|
||
)
|
||
items = [_wo_to_dict(x) for x in rows]
|
||
items = _enrich_work_order_list_items(db, items)
|
||
if tenant_no:
|
||
items = _enrich_work_order_cell_products(db, tenant_no, items)
|
||
return items, int(total)
|
||
|
||
|
||
def get_work_order(
|
||
db: Session, row_id: str, tenant_no: Optional[str] = None
|
||
) -> Optional[BizQcWorkOrder]:
|
||
q = db.query(BizQcWorkOrder).filter(BizQcWorkOrder.id == row_id, BizQcWorkOrder.del_flag == 0)
|
||
if tenant_no:
|
||
q = q.filter(BizQcWorkOrder.tenant_no == tenant_no)
|
||
return q.first()
|
||
|
||
|
||
def _lab_lines(db: Session, work_order_id: str) -> List[Dict[str, Any]]:
|
||
rows = (
|
||
db.query(BizQcWorkOrderLabItem)
|
||
.filter(
|
||
BizQcWorkOrderLabItem.work_order_id == work_order_id,
|
||
BizQcWorkOrderLabItem.del_flag == 0,
|
||
)
|
||
.order_by(BizQcWorkOrderLabItem.order_num.asc())
|
||
.all()
|
||
)
|
||
return [
|
||
{
|
||
"id": r.id,
|
||
"labTestItemId": r.lab_test_item_id,
|
||
"itemCode": r.item_code,
|
||
"itemName": r.item_name,
|
||
}
|
||
for r in rows
|
||
]
|
||
|
||
|
||
def _third_lines(db: Session, work_order_id: str) -> List[Dict[str, Any]]:
|
||
rows = (
|
||
db.query(BizQcWorkOrderThirdItem)
|
||
.filter(
|
||
BizQcWorkOrderThirdItem.work_order_id == work_order_id,
|
||
BizQcWorkOrderThirdItem.del_flag == 0,
|
||
)
|
||
.order_by(BizQcWorkOrderThirdItem.order_num.asc())
|
||
.all()
|
||
)
|
||
return [
|
||
{
|
||
"id": r.id,
|
||
"thirdPartyItemId": r.third_party_item_id,
|
||
"companyId": r.company_id,
|
||
"companyName": r.company_name,
|
||
"itemCode": r.item_code,
|
||
"itemName": r.item_name,
|
||
"feeAmount": _dec(r.fee_amount),
|
||
}
|
||
for r in rows
|
||
]
|
||
|
||
|
||
def _qc_info_item(db: Session, wo: BizQcWorkOrder, tenant_no: str) -> Dict[str, Any]:
|
||
tasks = task_svc.list_tasks_by_work_order(db, wo.id, tenant_no)
|
||
for t in tasks:
|
||
tt = (t.get("taskType") or "").strip()
|
||
ts = (t.get("taskStatus") or "").strip()
|
||
tc = (t.get("conclusion") or "").strip()
|
||
t["taskTypeLabel"] = _TASK_TYPE_LABELS.get(tt, tt)
|
||
t["taskStatusLabel"] = _TASK_STATUS_LABELS.get(ts, ts)
|
||
t["conclusionLabel"] = t.get("conclusionLabel") or _CONCLUSION_LABELS.get(tc, tc or "—")
|
||
wo_status = (wo.work_order_status or "").strip()
|
||
biz = _work_order_qc_biz_type(wo)
|
||
return {
|
||
**_wo_to_dict(wo),
|
||
"workOrderStatusLabel": _WORK_ORDER_STATUS_LABELS.get(wo_status, wo_status),
|
||
"qcBizTypeLabel": _qc_biz_type_label(biz),
|
||
"labItems": _lab_lines(db, wo.id),
|
||
"thirdPartyItems": _third_lines(db, wo.id),
|
||
"tasks": tasks,
|
||
}
|
||
|
||
|
||
def get_qc_info_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_ids 时,仍按 protocol_sample_id(存捐赠样本 id)关联。
|
||
"""
|
||
if not tenant_no:
|
||
return {}
|
||
|
||
if sample_code_by_id:
|
||
codes = list({(c or "").strip() for c in sample_code_by_id.values() if (c or "").strip()})
|
||
if not codes:
|
||
return {}
|
||
wos = (
|
||
db.query(BizQcWorkOrder)
|
||
.filter(
|
||
BizQcWorkOrder.tenant_no == tenant_no,
|
||
BizQcWorkOrder.del_flag == 0,
|
||
BizQcWorkOrder.reception_source == "cell_storage",
|
||
BizQcWorkOrder.sample_code.in_(codes),
|
||
)
|
||
.all()
|
||
)
|
||
code_to_wo: Dict[str, BizQcWorkOrder] = {}
|
||
for wo in wos:
|
||
code = (wo.sample_code or "").strip()
|
||
if code and code not in code_to_wo:
|
||
code_to_wo[code] = wo
|
||
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
|
||
wo = code_to_wo.get(code)
|
||
if wo:
|
||
out[sid] = _qc_info_item(db, wo, tenant_no)
|
||
return out
|
||
|
||
if not sample_ids:
|
||
return {}
|
||
wos = (
|
||
db.query(BizQcWorkOrder)
|
||
.filter(
|
||
BizQcWorkOrder.protocol_sample_id.in_(sample_ids),
|
||
BizQcWorkOrder.tenant_no == tenant_no,
|
||
BizQcWorkOrder.del_flag == 0,
|
||
)
|
||
.all()
|
||
)
|
||
out = {}
|
||
for wo in wos:
|
||
sid = (wo.protocol_sample_id or "").strip()
|
||
if sid:
|
||
out[sid] = _qc_info_item(db, wo, tenant_no)
|
||
return out
|
||
|
||
|
||
def get_work_order_detail(
|
||
db: Session, row_id: str, tenant_no: str
|
||
) -> Dict[str, Any]:
|
||
wo = get_work_order(db, row_id, tenant_no=tenant_no)
|
||
if not wo:
|
||
raise ValueError("工单不存在")
|
||
out = _wo_to_dict(wo)
|
||
enriched_list = _enrich_work_order_list_items(db, [out])
|
||
if tenant_no:
|
||
enriched_list = _enrich_work_order_cell_products(db, tenant_no, enriched_list)
|
||
enriched = enriched_list[0]
|
||
out.update(
|
||
{
|
||
"receptionTime": enriched.get("receptionTime", ""),
|
||
"handlerName": enriched.get("handlerName", ""),
|
||
"sampleTypeName": enriched.get("sampleTypeName", ""),
|
||
"receptionSourceLabel": enriched.get("receptionSourceLabel", ""),
|
||
"cellProductCode": enriched.get("cellProductCode", ""),
|
||
"cellProductName": enriched.get("cellProductName", ""),
|
||
"cellProductTypeName": enriched.get("cellProductTypeName", ""),
|
||
"cellProductPassage": enriched.get("cellProductPassage", ""),
|
||
"cellProductSpecName": enriched.get("cellProductSpecName", ""),
|
||
}
|
||
)
|
||
reception_detail = sample_reception_svc.get_reception_by_id(db, wo.reception_id, tenant_no)
|
||
if reception_detail:
|
||
out["reception"] = reception_detail
|
||
else:
|
||
rec = (
|
||
db.query(BizSampleReception)
|
||
.filter(BizSampleReception.id == wo.reception_id, BizSampleReception.del_flag == 0)
|
||
.first()
|
||
)
|
||
cr = (
|
||
check_result_svc.get_check_results_for_reception(db, rec, tenant_no) if rec else []
|
||
)
|
||
out["reception"] = {
|
||
"receptionTime": _fmt_dt(rec.reception_time) if rec else "",
|
||
"handlerName": (rec.handler_name if rec else "") or "",
|
||
"acceptConclusion": (rec.accept_conclusion if rec else "") or "",
|
||
"acceptConclusionLabel": _accept_conclusion_label(
|
||
rec.accept_conclusion if rec else ""
|
||
),
|
||
"sourceType": wo.reception_source or "cell_storage",
|
||
"sourceTypeLabel": _reception_source_label(wo.reception_source),
|
||
"sampleTypeName": enriched.get("sampleTypeName", ""),
|
||
"checkResults": cr,
|
||
"receiveAttachments": (rec.receive_attachments if rec else "") or "",
|
||
"receiveAttachmentFiles": urls_to_attachment_list(
|
||
rec.receive_attachments if rec else None, db, tenant_no
|
||
),
|
||
}
|
||
out["labItems"] = _lab_lines(db, wo.id)
|
||
out["thirdPartyItems"] = _third_lines(db, wo.id)
|
||
out["tasks"] = task_svc.list_tasks_by_work_order(db, wo.id, tenant_no)
|
||
return out
|
||
|
||
|
||
def _soft_delete_pending_work_order_tasks(
|
||
db: Session, work_order_id: str, tenant_no: str
|
||
) -> None:
|
||
"""调整检测前软删待执行任务;存在进行中/已完成任务时不允许调整。"""
|
||
tasks = (
|
||
db.query(BizQcTask)
|
||
.filter(
|
||
BizQcTask.work_order_id == work_order_id,
|
||
BizQcTask.tenant_no == tenant_no,
|
||
BizQcTask.del_flag == 0,
|
||
)
|
||
.all()
|
||
)
|
||
if any(t.task_status in ("jxz", "ywc") for t in tasks):
|
||
raise ValueError("存在进行中或已完成的质检任务,无法调整检测项目")
|
||
if not tasks:
|
||
return
|
||
now = datetime.now()
|
||
task_ids = [t.id for t in tasks]
|
||
if task_ids:
|
||
db.query(BizQcTaskResult).filter(
|
||
BizQcTaskResult.task_id.in_(task_ids),
|
||
BizQcTaskResult.tenant_no == tenant_no,
|
||
BizQcTaskResult.del_flag == 0,
|
||
).update(
|
||
{"del_flag": 1, "update_time": now},
|
||
synchronize_session="fetch",
|
||
)
|
||
for t in tasks:
|
||
t.del_flag = 1
|
||
t.update_time = now
|
||
db.add(t)
|
||
|
||
|
||
def configure_work_order(
|
||
db: Session,
|
||
work_order_id: str,
|
||
*,
|
||
has_lab: bool,
|
||
has_third_party: bool,
|
||
lab_item_ids: List[str],
|
||
third_party_item_ids: List[str],
|
||
tenant_no: str,
|
||
user_id: Optional[str] = None,
|
||
create_dept_id: str = "",
|
||
) -> BizQcWorkOrder:
|
||
wo = get_work_order(db, work_order_id, tenant_no=tenant_no)
|
||
if not wo:
|
||
raise ValueError("工单不存在")
|
||
if wo.work_order_status == "ywc":
|
||
raise ValueError("工单已完成,无法调整检测项目")
|
||
if wo.work_order_status not in ("dpz", "ypz"):
|
||
raise ValueError("当前工单状态不可配置检测项目")
|
||
order_source = "temporary" if (getattr(wo, "work_order_type", None) or "inbound") == "temporary" else "inbound"
|
||
if wo.work_order_status == "ypz":
|
||
_soft_delete_pending_work_order_tasks(db, wo.id, tenant_no)
|
||
|
||
if not has_lab and not has_third_party:
|
||
raise ValueError("请至少选择一种检测方式")
|
||
if has_lab and not lab_item_ids:
|
||
raise ValueError("请选择实验室自检项目")
|
||
if has_third_party and not third_party_item_ids:
|
||
raise ValueError("请选择第三方检测项目")
|
||
if not has_lab:
|
||
lab_item_ids = []
|
||
if not has_third_party:
|
||
third_party_item_ids = []
|
||
|
||
# 清除旧明细
|
||
for row in (
|
||
db.query(BizQcWorkOrderLabItem)
|
||
.filter(BizQcWorkOrderLabItem.work_order_id == wo.id, BizQcWorkOrderLabItem.del_flag == 0)
|
||
.all()
|
||
):
|
||
row.del_flag = 1
|
||
for row in (
|
||
db.query(BizQcWorkOrderThirdItem)
|
||
.filter(BizQcWorkOrderThirdItem.work_order_id == wo.id, BizQcWorkOrderThirdItem.del_flag == 0)
|
||
.all()
|
||
):
|
||
row.del_flag = 1
|
||
|
||
fee_total = Decimal("0")
|
||
seq = 0
|
||
|
||
for lid in lab_item_ids:
|
||
item = lab_item_svc.get_lab_item(db, lid, tenant_no=tenant_no)
|
||
if not item or item.status != "1":
|
||
raise ValueError("实验室项目无效或已停用")
|
||
seq += 1
|
||
line = BizQcWorkOrderLabItem(
|
||
id=generate_uuid(),
|
||
work_order_id=wo.id,
|
||
order_source=order_source,
|
||
lab_test_item_id=item.id,
|
||
item_code=item.item_code,
|
||
item_name=item.item_name,
|
||
tenant_no=tenant_no,
|
||
order_num=seq,
|
||
create_by=user_id,
|
||
create_dept_id=create_dept_id or "",
|
||
create_time=datetime.now(),
|
||
update_by=user_id,
|
||
update_time=datetime.now(),
|
||
del_flag=0,
|
||
)
|
||
db.add(line)
|
||
db.flush()
|
||
task_svc.create_task_for_line(
|
||
db,
|
||
work_order=wo,
|
||
task_type="sysz",
|
||
source_line_id=line.id,
|
||
item_code=line.item_code,
|
||
item_name=line.item_name,
|
||
tenant_no=tenant_no,
|
||
user_id=user_id,
|
||
create_dept_id=create_dept_id,
|
||
seq=seq,
|
||
order_source=order_source,
|
||
)
|
||
|
||
for tid in third_party_item_ids:
|
||
item = third_item_svc.get_item(db, tid, tenant_no=tenant_no)
|
||
if not item or item.status != "1":
|
||
raise ValueError("第三方项目无效或已停用")
|
||
comp = get_company(db, item.company_id, tenant_no=tenant_no)
|
||
if not comp:
|
||
raise ValueError("第三方公司不存在")
|
||
seq += 1
|
||
fee = item.fee_amount or Decimal("0")
|
||
fee_total += fee
|
||
line = BizQcWorkOrderThirdItem(
|
||
id=generate_uuid(),
|
||
work_order_id=wo.id,
|
||
order_source=order_source,
|
||
third_party_item_id=item.id,
|
||
company_id=comp.id,
|
||
company_name=comp.company_name,
|
||
item_code=item.item_code,
|
||
item_name=item.item_name,
|
||
fee_amount=fee,
|
||
tenant_no=tenant_no,
|
||
order_num=seq,
|
||
create_by=user_id,
|
||
create_dept_id=create_dept_id or "",
|
||
create_time=datetime.now(),
|
||
update_by=user_id,
|
||
update_time=datetime.now(),
|
||
del_flag=0,
|
||
)
|
||
db.add(line)
|
||
db.flush()
|
||
task_svc.create_task_for_line(
|
||
db,
|
||
work_order=wo,
|
||
task_type="dsfjc",
|
||
source_line_id=line.id,
|
||
item_code=line.item_code,
|
||
item_name=line.item_name,
|
||
tenant_no=tenant_no,
|
||
user_id=user_id,
|
||
create_dept_id=create_dept_id,
|
||
seq=seq,
|
||
company_id=comp.id,
|
||
company_name=comp.company_name,
|
||
fee_amount=fee,
|
||
order_source=order_source,
|
||
)
|
||
|
||
wo.has_lab = "1" if has_lab else "0"
|
||
wo.has_third_party = "1" if has_third_party else "0"
|
||
wo.work_order_status = "ypz"
|
||
wo.configured_time = datetime.now()
|
||
wo.configured_by = user_id
|
||
wo.third_party_fee_total = fee_total if has_third_party else None
|
||
wo.update_by = user_id
|
||
wo.update_time = datetime.now()
|
||
db.add(wo)
|
||
db.commit()
|
||
db.refresh(wo)
|
||
return wo
|
||
|
||
|
||
def try_complete_work_order(db: Session, work_order_id: str, tenant_no: str) -> None:
|
||
pending = (
|
||
db.query(BizQcTask)
|
||
.filter(
|
||
BizQcTask.work_order_id == work_order_id,
|
||
BizQcTask.tenant_no == tenant_no,
|
||
BizQcTask.del_flag == 0,
|
||
BizQcTask.task_status != "ywc",
|
||
)
|
||
.count()
|
||
)
|
||
if pending:
|
||
return
|
||
wo = get_work_order(db, work_order_id, tenant_no=tenant_no)
|
||
if wo and wo.work_order_status == "ypz":
|
||
wo.work_order_status = "ywc"
|
||
wo.update_time = datetime.now()
|
||
db.add(wo)
|
||
db.commit()
|
||
|
||
|
||
# ---------- 临时质检工单 ----------
|
||
|
||
|
||
def _temp_lab_lines(db: Session, work_order_id: str) -> List[Dict[str, Any]]:
|
||
rows = (
|
||
db.query(BizQcWorkOrderLabItem)
|
||
.filter(
|
||
BizQcWorkOrderLabItem.work_order_id == work_order_id,
|
||
BizQcWorkOrderLabItem.order_source == "temporary",
|
||
BizQcWorkOrderLabItem.del_flag == 0,
|
||
)
|
||
.order_by(BizQcWorkOrderLabItem.order_num.asc())
|
||
.all()
|
||
)
|
||
return [
|
||
{
|
||
"id": r.id,
|
||
"labTestItemId": r.lab_test_item_id,
|
||
"itemCode": r.item_code,
|
||
"itemName": r.item_name,
|
||
}
|
||
for r in rows
|
||
]
|
||
|
||
|
||
def _temp_third_lines(db: Session, work_order_id: str) -> List[Dict[str, Any]]:
|
||
rows = (
|
||
db.query(BizQcWorkOrderThirdItem)
|
||
.filter(
|
||
BizQcWorkOrderThirdItem.work_order_id == work_order_id,
|
||
BizQcWorkOrderThirdItem.order_source == "temporary",
|
||
BizQcWorkOrderThirdItem.del_flag == 0,
|
||
)
|
||
.order_by(BizQcWorkOrderThirdItem.order_num.asc())
|
||
.all()
|
||
)
|
||
return [
|
||
{
|
||
"id": r.id,
|
||
"thirdPartyItemId": r.third_party_item_id,
|
||
"companyId": r.company_id,
|
||
"companyName": r.company_name,
|
||
"itemCode": r.item_code,
|
||
"itemName": r.item_name,
|
||
"feeAmount": _dec(r.fee_amount),
|
||
}
|
||
for r in rows
|
||
]
|
||
|
||
|
||
def _temp_tasks(db: Session, work_order_id: str, tenant_no: str) -> List[Dict[str, Any]]:
|
||
rows = (
|
||
db.query(BizQcTask)
|
||
.filter(
|
||
BizQcTask.work_order_id == work_order_id,
|
||
BizQcTask.order_source == "temporary",
|
||
BizQcTask.tenant_no == tenant_no,
|
||
BizQcTask.del_flag == 0,
|
||
)
|
||
.order_by(BizQcTask.order_num.asc())
|
||
.all()
|
||
)
|
||
items = []
|
||
for t in rows:
|
||
d = task_svc._task_to_dict(t, "", db, tenant_no)
|
||
tt = (t.task_type or "").strip()
|
||
ts = (t.task_status or "").strip()
|
||
tc = (t.conclusion or "").strip()
|
||
d["taskTypeLabel"] = _TASK_TYPE_LABELS.get(tt, tt)
|
||
d["taskStatusLabel"] = _TASK_STATUS_LABELS.get(ts, ts)
|
||
d["conclusionLabel"] = _CONCLUSION_LABELS.get(tc, tc or "—")
|
||
items.append(d)
|
||
return items
|
||
|
||
|
||
def create_temp_qc_work_order(
|
||
db: Session,
|
||
*,
|
||
tenant_no: str,
|
||
user_id: Optional[str] = None,
|
||
create_dept_id: str = "",
|
||
cell_product_id: str = "",
|
||
remark: str = "",
|
||
) -> BizQcWorkOrder:
|
||
"""创建临时质检工单(work_order_type='temporary',LSZJ前缀编号)"""
|
||
cp_id = _validate_cell_product_id(db, cell_product_id, tenant_no)
|
||
wo = BizQcWorkOrder(
|
||
id=generate_uuid(),
|
||
work_order_no=_gen_daily_no(db, tenant_no, "LSZJ"),
|
||
work_order_type="temporary",
|
||
reception_id=None,
|
||
protocol_sample_id=None,
|
||
collection_protocol_id=None,
|
||
protocol_no=None,
|
||
sample_code=None,
|
||
reception_source=None,
|
||
qc_biz_type="temporary",
|
||
cell_product_id=cp_id,
|
||
work_order_status="dpz",
|
||
has_lab="0",
|
||
has_third_party="0",
|
||
tenant_no=tenant_no,
|
||
remark=remark,
|
||
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(wo)
|
||
db.flush()
|
||
return wo
|
||
|
||
|
||
def update_temp_qc_work_order(
|
||
db: Session,
|
||
work_order_id: str,
|
||
*,
|
||
tenant_no: str,
|
||
user_id: Optional[str] = None,
|
||
cell_product_id: Optional[str] = None,
|
||
remark: Optional[str] = None,
|
||
) -> BizQcWorkOrder:
|
||
"""编辑临时质检工单(备注、质检对象)"""
|
||
wo = get_work_order(db, work_order_id, tenant_no=tenant_no)
|
||
if not wo:
|
||
raise ValueError("工单不存在")
|
||
if (getattr(wo, "work_order_type", None) or "inbound") != "temporary":
|
||
raise ValueError("仅临时质检工单可编辑")
|
||
if cell_product_id is not None:
|
||
wo.cell_product_id = _validate_cell_product_id(db, cell_product_id, tenant_no)
|
||
if remark is not None:
|
||
wo.remark = (remark or "").strip() or None
|
||
wo.update_by = user_id
|
||
wo.update_time = datetime.now()
|
||
db.add(wo)
|
||
db.flush()
|
||
return wo
|
||
|
||
|
||
def list_temp_qc_work_orders(
|
||
db: Session,
|
||
*,
|
||
page: int = 1,
|
||
page_size: int = 10,
|
||
keyword: Optional[str] = None,
|
||
work_order_status: Optional[str] = None,
|
||
tenant_no: Optional[str] = None,
|
||
user_info=None,
|
||
) -> Tuple[List[Dict[str, Any]], int]:
|
||
def _filtered():
|
||
q0 = db.query(BizQcWorkOrder).filter(
|
||
BizQcWorkOrder.del_flag == 0,
|
||
BizQcWorkOrder.work_order_type == "temporary",
|
||
)
|
||
if tenant_no:
|
||
q0 = q0.filter(BizQcWorkOrder.tenant_no == tenant_no)
|
||
kw = (keyword or "").strip()
|
||
if kw:
|
||
like = f"%{kw}%"
|
||
q0 = q0.filter(BizQcWorkOrder.work_order_no.like(like))
|
||
st = (work_order_status or "").strip()
|
||
if st:
|
||
q0 = q0.filter(BizQcWorkOrder.work_order_status == st)
|
||
if user_info:
|
||
from app.core.data_scope import apply_data_scope
|
||
|
||
q0 = apply_data_scope(q0, BizQcWorkOrder, user_info, db, tenant_no)
|
||
return q0
|
||
|
||
total = _filtered().order_by(None).with_entities(func.count(BizQcWorkOrder.id)).scalar() or 0
|
||
page = max(1, page)
|
||
page_size = min(max(1, page_size), 100)
|
||
rows = (
|
||
_filtered()
|
||
.order_by(BizQcWorkOrder.create_time.desc())
|
||
.offset((page - 1) * page_size)
|
||
.limit(page_size)
|
||
.all()
|
||
)
|
||
items = [_wo_to_dict(x) for x in rows]
|
||
if tenant_no:
|
||
items = _enrich_work_order_cell_products(db, tenant_no, items)
|
||
return items, int(total)
|
||
|
||
|
||
def get_temp_qc_work_order(
|
||
db: Session, row_id: str, tenant_no: str
|
||
) -> Optional[BizQcWorkOrder]:
|
||
q = db.query(BizQcWorkOrder).filter(
|
||
BizQcWorkOrder.id == row_id,
|
||
BizQcWorkOrder.del_flag == 0,
|
||
BizQcWorkOrder.work_order_type == "temporary",
|
||
)
|
||
if tenant_no:
|
||
q = q.filter(BizQcWorkOrder.tenant_no == tenant_no)
|
||
return q.first()
|
||
|
||
|
||
def get_temp_qc_work_order_detail(
|
||
db: Session, row_id: str, tenant_no: str
|
||
) -> Optional[Dict[str, Any]]:
|
||
wo = get_temp_qc_work_order(db, row_id, tenant_no)
|
||
if not wo:
|
||
return None
|
||
out = _wo_to_dict(wo)
|
||
enriched_list = _enrich_work_order_cell_products(db, tenant_no, [out])
|
||
out = enriched_list[0]
|
||
out["labItems"] = _temp_lab_lines(db, wo.id)
|
||
out["thirdPartyItems"] = _temp_third_lines(db, wo.id)
|
||
out["tasks"] = _temp_tasks(db, wo.id, tenant_no)
|
||
return out
|
||
|
||
|
||
def configure_temp_qc_work_order(
|
||
db: Session,
|
||
work_order_id: str,
|
||
*,
|
||
has_lab: bool,
|
||
has_third_party: bool,
|
||
lab_item_ids: List[str],
|
||
third_party_item_ids: List[str],
|
||
tenant_no: str,
|
||
user_id: Optional[str] = None,
|
||
create_dept_id: str = "",
|
||
) -> BizQcWorkOrder:
|
||
wo = get_temp_qc_work_order(db, work_order_id, tenant_no)
|
||
if not wo:
|
||
raise ValueError("工单不存在")
|
||
if wo.work_order_status == "ywc":
|
||
raise ValueError("工单已完成,无法调整检测项目")
|
||
if wo.work_order_status not in ("dpz", "ypz"):
|
||
raise ValueError("当前工单状态不可配置检测项目")
|
||
|
||
if wo.work_order_status == "ypz":
|
||
tasks = (
|
||
db.query(BizQcTask)
|
||
.filter(
|
||
BizQcTask.work_order_id == wo.id,
|
||
BizQcTask.order_source == "temporary",
|
||
BizQcTask.tenant_no == tenant_no,
|
||
BizQcTask.del_flag == 0,
|
||
)
|
||
.all()
|
||
)
|
||
if any(t.task_status in ("jxz", "ywc") for t in tasks):
|
||
raise ValueError("存在进行中或已完成的质检任务,无法调整检测项目")
|
||
now = datetime.now()
|
||
task_ids = [t.id for t in tasks]
|
||
if task_ids:
|
||
db.query(BizQcTaskResult).filter(
|
||
BizQcTaskResult.task_id.in_(task_ids),
|
||
BizQcTaskResult.tenant_no == tenant_no,
|
||
BizQcTaskResult.del_flag == 0,
|
||
).update(
|
||
{"del_flag": 1, "update_time": now},
|
||
synchronize_session="fetch",
|
||
)
|
||
for t in tasks:
|
||
t.del_flag = 1
|
||
t.update_time = now
|
||
db.add(t)
|
||
|
||
if not has_lab and not has_third_party:
|
||
raise ValueError("请至少选择一种检测方式")
|
||
if has_lab and not lab_item_ids:
|
||
raise ValueError("请选择实验室自检项目")
|
||
if has_third_party and not third_party_item_ids:
|
||
raise ValueError("请选择第三方检测项目")
|
||
if not has_lab:
|
||
lab_item_ids = []
|
||
if not has_third_party:
|
||
third_party_item_ids = []
|
||
|
||
for row in (
|
||
db.query(BizQcWorkOrderLabItem)
|
||
.filter(
|
||
BizQcWorkOrderLabItem.work_order_id == wo.id,
|
||
BizQcWorkOrderLabItem.order_source == "temporary",
|
||
BizQcWorkOrderLabItem.del_flag == 0,
|
||
)
|
||
.all()
|
||
):
|
||
row.del_flag = 1
|
||
for row in (
|
||
db.query(BizQcWorkOrderThirdItem)
|
||
.filter(
|
||
BizQcWorkOrderThirdItem.work_order_id == wo.id,
|
||
BizQcWorkOrderThirdItem.order_source == "temporary",
|
||
BizQcWorkOrderThirdItem.del_flag == 0,
|
||
)
|
||
.all()
|
||
):
|
||
row.del_flag = 1
|
||
|
||
fee_total = Decimal("0")
|
||
seq = 0
|
||
|
||
for lid in lab_item_ids:
|
||
item = lab_item_svc.get_lab_item(db, lid, tenant_no=tenant_no)
|
||
if not item or item.status != "1":
|
||
raise ValueError("实验室项目无效或已停用")
|
||
seq += 1
|
||
line = BizQcWorkOrderLabItem(
|
||
id=generate_uuid(),
|
||
work_order_id=wo.id,
|
||
order_source="temporary",
|
||
lab_test_item_id=item.id,
|
||
item_code=item.item_code,
|
||
item_name=item.item_name,
|
||
tenant_no=tenant_no,
|
||
order_num=seq,
|
||
create_by=user_id,
|
||
create_dept_id=create_dept_id or "",
|
||
create_time=datetime.now(),
|
||
update_by=user_id,
|
||
update_time=datetime.now(),
|
||
del_flag=0,
|
||
)
|
||
db.add(line)
|
||
db.flush()
|
||
task_svc.create_task_for_line(
|
||
db,
|
||
work_order=wo,
|
||
task_type="sysz",
|
||
source_line_id=line.id,
|
||
item_code=line.item_code,
|
||
item_name=line.item_name,
|
||
tenant_no=tenant_no,
|
||
user_id=user_id,
|
||
create_dept_id=create_dept_id,
|
||
seq=seq,
|
||
order_source="temporary",
|
||
)
|
||
|
||
for tid in third_party_item_ids:
|
||
item = third_item_svc.get_item(db, tid, tenant_no=tenant_no)
|
||
if not item or item.status != "1":
|
||
raise ValueError("第三方项目无效或已停用")
|
||
comp = get_company(db, item.company_id, tenant_no=tenant_no)
|
||
if not comp:
|
||
raise ValueError("第三方公司不存在")
|
||
seq += 1
|
||
fee = item.fee_amount or Decimal("0")
|
||
fee_total += fee
|
||
line = BizQcWorkOrderThirdItem(
|
||
id=generate_uuid(),
|
||
work_order_id=wo.id,
|
||
order_source="temporary",
|
||
third_party_item_id=item.id,
|
||
company_id=comp.id,
|
||
company_name=comp.company_name,
|
||
item_code=item.item_code,
|
||
item_name=item.item_name,
|
||
fee_amount=fee,
|
||
tenant_no=tenant_no,
|
||
order_num=seq,
|
||
create_by=user_id,
|
||
create_dept_id=create_dept_id or "",
|
||
create_time=datetime.now(),
|
||
update_by=user_id,
|
||
update_time=datetime.now(),
|
||
del_flag=0,
|
||
)
|
||
db.add(line)
|
||
db.flush()
|
||
task_svc.create_task_for_line(
|
||
db,
|
||
work_order=wo,
|
||
task_type="dsfjc",
|
||
source_line_id=line.id,
|
||
item_code=line.item_code,
|
||
item_name=line.item_name,
|
||
tenant_no=tenant_no,
|
||
user_id=user_id,
|
||
create_dept_id=create_dept_id,
|
||
seq=seq,
|
||
company_id=comp.id,
|
||
company_name=comp.company_name,
|
||
fee_amount=fee,
|
||
order_source="temporary",
|
||
)
|
||
|
||
wo.has_lab = "1" if has_lab else "0"
|
||
wo.has_third_party = "1" if has_third_party else "0"
|
||
wo.work_order_status = "ypz"
|
||
wo.configured_time = datetime.now()
|
||
wo.configured_by = user_id
|
||
wo.third_party_fee_total = fee_total if has_third_party else None
|
||
wo.update_by = user_id
|
||
wo.update_time = datetime.now()
|
||
db.add(wo)
|
||
db.commit()
|
||
db.refresh(wo)
|
||
return wo
|
||
|
||
|
||
def try_complete_temp_qc_work_order(
|
||
db: Session, work_order_id: str, tenant_no: str
|
||
) -> None:
|
||
pending = (
|
||
db.query(BizQcTask)
|
||
.filter(
|
||
BizQcTask.work_order_id == work_order_id,
|
||
BizQcTask.order_source == "temporary",
|
||
BizQcTask.tenant_no == tenant_no,
|
||
BizQcTask.del_flag == 0,
|
||
BizQcTask.task_status != "ywc",
|
||
)
|
||
.count()
|
||
)
|
||
if pending:
|
||
return
|
||
wo = get_temp_qc_work_order(db, work_order_id, tenant_no)
|
||
if wo and wo.work_order_status == "ypz":
|
||
wo.work_order_status = "ywc"
|
||
wo.update_time = datetime.now()
|
||
db.add(wo)
|
||
db.commit()
|
||
|
||
|
||
def delete_temp_qc_work_order(
|
||
db: Session, work_order_id: str, tenant_no: str, user_id: Optional[str]
|
||
) -> None:
|
||
wo = get_temp_qc_work_order(db, work_order_id, tenant_no)
|
||
if not wo:
|
||
raise ValueError("工单不存在")
|
||
if wo.work_order_status == "ywc":
|
||
raise ValueError("已完成的工单不能删除")
|
||
|
||
now = datetime.now()
|
||
|
||
tasks = (
|
||
db.query(BizQcTask)
|
||
.filter(
|
||
BizQcTask.work_order_id == work_order_id,
|
||
BizQcTask.order_source == "temporary",
|
||
BizQcTask.del_flag == 0,
|
||
)
|
||
.all()
|
||
)
|
||
for task in tasks:
|
||
results = (
|
||
db.query(BizQcTaskResult)
|
||
.filter(
|
||
BizQcTaskResult.task_id == task.id,
|
||
BizQcTaskResult.del_flag == 0,
|
||
)
|
||
.all()
|
||
)
|
||
for r in results:
|
||
r.del_flag = 1
|
||
r.update_by = user_id
|
||
r.update_time = now
|
||
task.del_flag = 1
|
||
task.update_by = user_id
|
||
task.update_time = now
|
||
|
||
lab_items = (
|
||
db.query(BizQcWorkOrderLabItem)
|
||
.filter(
|
||
BizQcWorkOrderLabItem.work_order_id == work_order_id,
|
||
BizQcWorkOrderLabItem.order_source == "temporary",
|
||
BizQcWorkOrderLabItem.del_flag == 0,
|
||
)
|
||
.all()
|
||
)
|
||
for item in lab_items:
|
||
item.del_flag = 1
|
||
item.update_by = user_id
|
||
item.update_time = now
|
||
|
||
third_items = (
|
||
db.query(BizQcWorkOrderThirdItem)
|
||
.filter(
|
||
BizQcWorkOrderThirdItem.work_order_id == work_order_id,
|
||
BizQcWorkOrderThirdItem.order_source == "temporary",
|
||
BizQcWorkOrderThirdItem.del_flag == 0,
|
||
)
|
||
.all()
|
||
)
|
||
for item in third_items:
|
||
item.del_flag = 1
|
||
item.update_by = user_id
|
||
item.update_time = now
|
||
|
||
wo.del_flag = 1
|
||
wo.update_by = user_id
|
||
wo.update_time = now
|
||
|
||
db.commit()
|
||
|
||
|
||
def create_qc_work_order_manual(
|
||
db: Session,
|
||
*,
|
||
tenant_no: str,
|
||
user_id: Optional[str] = None,
|
||
create_dept_id: str = "",
|
||
protocol_no: str = "",
|
||
sample_code: str = "",
|
||
reception_source: str = "cell_storage",
|
||
qc_biz_type: str = "inbound",
|
||
remark: str = "",
|
||
) -> BizQcWorkOrder:
|
||
"""手动创建质检工单(不关联接收记录)"""
|
||
wo = BizQcWorkOrder(
|
||
id=generate_uuid(),
|
||
work_order_no=_gen_daily_no(db, tenant_no, "ZJ"),
|
||
work_order_type="inbound",
|
||
reception_id=None,
|
||
protocol_sample_id=None,
|
||
collection_protocol_id=None,
|
||
protocol_no=protocol_no or None,
|
||
sample_code=sample_code or None,
|
||
reception_source=reception_source,
|
||
qc_biz_type=qc_biz_type,
|
||
work_order_status="dpz",
|
||
has_lab="0",
|
||
has_third_party="0",
|
||
tenant_no=tenant_no,
|
||
remark=remark,
|
||
create_by=user_id,
|
||
create_dept_id=create_dept_id,
|
||
)
|
||
db.add(wo)
|
||
db.flush()
|
||
return wo
|
||
|
||
|
||
def delete_qc_work_order(db: Session, work_order_id: str, tenant_no: str, user_id: Optional[str]) -> None:
|
||
"""软删除质检工单及其关联的任务、任务结果、检测项目和第三方检测项目"""
|
||
wo = db.query(BizQcWorkOrder).filter(
|
||
and_(
|
||
BizQcWorkOrder.id == work_order_id,
|
||
BizQcWorkOrder.tenant_no == tenant_no,
|
||
BizQcWorkOrder.del_flag == 0,
|
||
)
|
||
).first()
|
||
if not wo:
|
||
raise ValueError("工单不存在")
|
||
if wo.work_order_status == "ywc":
|
||
raise ValueError("已完成的工单不能删除")
|
||
|
||
now = datetime.now()
|
||
|
||
# 1. 软删除所有关联任务及其结果
|
||
tasks = db.query(BizQcTask).filter(
|
||
and_(BizQcTask.work_order_id == work_order_id, BizQcTask.del_flag == 0)
|
||
).all()
|
||
from app.models.qc_param import BizQcTaskResult
|
||
|
||
for task in tasks:
|
||
results = db.query(BizQcTaskResult).filter(
|
||
and_(BizQcTaskResult.task_id == task.id, BizQcTaskResult.del_flag == 0)
|
||
).all()
|
||
for r in results:
|
||
r.del_flag = 1
|
||
r.update_by = user_id
|
||
r.update_time = now
|
||
task.del_flag = 1
|
||
task.update_by = user_id
|
||
task.update_time = now
|
||
|
||
# 2. 软删除检测项目
|
||
lab_items = db.query(BizQcWorkOrderLabItem).filter(
|
||
and_(BizQcWorkOrderLabItem.work_order_id == work_order_id, BizQcWorkOrderLabItem.del_flag == 0)
|
||
).all()
|
||
for item in lab_items:
|
||
item.del_flag = 1
|
||
item.update_by = user_id
|
||
item.update_time = now
|
||
|
||
# 3. 软删除第三方检测项目
|
||
third_items = db.query(BizQcWorkOrderThirdItem).filter(
|
||
and_(BizQcWorkOrderThirdItem.work_order_id == work_order_id, BizQcWorkOrderThirdItem.del_flag == 0)
|
||
).all()
|
||
for item in third_items:
|
||
item.del_flag = 1
|
||
item.update_by = user_id
|
||
item.update_time = now
|
||
|
||
# 4. 软删除工单本体
|
||
wo.del_flag = 1
|
||
wo.update_by = user_id
|
||
wo.update_time = now
|
||
|
||
db.commit()
|