424 lines
13 KiB
Python
424 lines
13 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.models.models import (
|
|
BizCollectionProtocol,
|
|
BizProtocolSample,
|
|
BizQcLabTestItem,
|
|
BizQcTask,
|
|
BizQcThirdPartyItem,
|
|
BizQcWorkOrder,
|
|
BizQcWorkOrderLabItem,
|
|
BizQcWorkOrderThirdItem,
|
|
BizSampleReception,
|
|
)
|
|
|
|
|
|
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 _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]:
|
|
return {
|
|
"id": wo.id,
|
|
"workOrderNo": wo.work_order_no,
|
|
"receptionId": wo.reception_id,
|
|
"protocolSampleId": wo.protocol_sample_id,
|
|
"collectionProtocolId": wo.collection_protocol_id,
|
|
"protocolNo": wo.protocol_no or "",
|
|
"sampleCode": wo.sample_code or "",
|
|
"workOrderStatus": wo.work_order_status,
|
|
"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: BizSampleReception,
|
|
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
|
|
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,
|
|
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,
|
|
tenant_no: Optional[str] = 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)
|
|
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)
|
|
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()
|
|
)
|
|
return [_wo_to_dict(x) for x in rows], 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 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)
|
|
rec = (
|
|
db.query(BizSampleReception)
|
|
.filter(BizSampleReception.id == wo.reception_id, BizSampleReception.del_flag == 0)
|
|
.first()
|
|
)
|
|
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 "",
|
|
}
|
|
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 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 != "dpz":
|
|
raise ValueError("仅待配置工单可配置检测项目")
|
|
task_cnt = (
|
|
db.query(BizQcTask)
|
|
.filter(BizQcTask.work_order_id == wo.id, BizQcTask.del_flag == 0)
|
|
.count()
|
|
)
|
|
if task_cnt:
|
|
raise ValueError("工单已存在任务,无法重新配置")
|
|
|
|
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,
|
|
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,
|
|
)
|
|
|
|
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,
|
|
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,
|
|
)
|
|
|
|
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()
|