665 lines
23 KiB
Python
665 lines
23 KiB
Python
"""出库放行审核 CRUD"""
|
|
from __future__ import annotations
|
|
|
|
import math
|
|
from datetime import datetime
|
|
from typing import Any, Dict, List, Optional
|
|
|
|
from sqlalchemy import and_, func, or_
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.core.utils import generate_uuid
|
|
from app.models.models import (
|
|
BizOutboundReleaseReview,
|
|
BizPilotApplication,
|
|
BizPilotInfusionPlan,
|
|
BizPilotInfusionSchedule,
|
|
BizPilotPatient,
|
|
BizProductionTask,
|
|
BizProductionWorkOrder,
|
|
BizQcTask,
|
|
BizQcWorkOrder,
|
|
SysUser,
|
|
)
|
|
from app.models.qc_param import BizQcTaskResult
|
|
|
|
_REVIEW_CONCLUSION_LABELS = {"tg": "通过", "btg": "不通过"}
|
|
_SCHEDULE_STATUS_LABELS = {
|
|
"dsc": "待生产",
|
|
"ysc": "已生产",
|
|
"yjf": "已交付",
|
|
"yqx": "已取消",
|
|
}
|
|
_QC_STATUS_LABELS = {
|
|
"dzc": "待质检",
|
|
"yzj": "已质检",
|
|
"yqx": "已取消",
|
|
}
|
|
_BIZ_TYPE_LABELS = {
|
|
"pilot_service": "中试服务",
|
|
"clinical_application": "临床应用",
|
|
}
|
|
|
|
|
|
# ────────────────────────────────────────────
|
|
# Helpers
|
|
# ────────────────────────────────────────────
|
|
|
|
|
|
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 _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 _resolve_schedule_biz_type(
|
|
app: Optional[BizPilotApplication],
|
|
qwo: Optional[BizQcWorkOrder],
|
|
pwo: Optional[BizProductionWorkOrder],
|
|
) -> str:
|
|
"""输注计划业务类型:优先出库工单 biz_type,否则取申请单 biz_type。"""
|
|
if qwo and (qwo.biz_type or "").strip():
|
|
return (qwo.biz_type or "").strip()
|
|
if pwo and (pwo.biz_type or "").strip():
|
|
return (pwo.biz_type or "").strip()
|
|
if app and (app.biz_type or "").strip():
|
|
return (app.biz_type or "").strip()
|
|
if app:
|
|
return "pilot_service"
|
|
return ""
|
|
|
|
|
|
def _row_to_dict(row) -> Dict[str, Any]:
|
|
"""将 BizOutboundReleaseReview ORM 对象转为前端字典。"""
|
|
return {
|
|
"id": row.id,
|
|
"infusionScheduleId": row.infusion_schedule_id or "",
|
|
"productionWorkOrderId": row.production_work_order_id or "",
|
|
"qcWorkOrderId": row.qc_work_order_id or "",
|
|
"reviewConclusion": row.review_conclusion or "",
|
|
"reviewConclusionLabel": _REVIEW_CONCLUSION_LABELS.get(
|
|
row.review_conclusion or "", row.review_conclusion or ""
|
|
),
|
|
"reviewerUserId": row.reviewer_user_id or "",
|
|
"reviewByName": row.review_by_name or "",
|
|
"reviewAttachments": row.review_attachments or "",
|
|
"reviewRemark": row.review_remark or "",
|
|
"reviewTime": _fmt_dt(row.review_time),
|
|
"tenantNo": row.tenant_no or "",
|
|
"orderNum": row.order_num if row.order_num is not None else 0,
|
|
"remark": row.remark or "",
|
|
"createBy": row.create_by or "",
|
|
"createTime": _fmt_dt(row.create_time),
|
|
"updateBy": row.update_by or "",
|
|
"updateTime": _fmt_dt(row.update_time),
|
|
}
|
|
|
|
|
|
# ────────────────────────────────────────────
|
|
# Paginated list
|
|
# ────────────────────────────────────────────
|
|
|
|
|
|
def list_reviews_paginated(
|
|
db: Session,
|
|
*,
|
|
tenant_no: str,
|
|
keyword: Optional[str] = None,
|
|
review_conclusion: Optional[str] = None,
|
|
schedule_status: Optional[str] = None,
|
|
qc_status: Optional[str] = None,
|
|
biz_type: Optional[str] = None,
|
|
infusion_time_start: Optional[str] = None,
|
|
infusion_time_end: Optional[str] = None,
|
|
page: int = 1,
|
|
page_size: int = 10,
|
|
user_info=None,
|
|
) -> Dict[str, Any]:
|
|
"""分页列出出库放行审核数据,以输注计划为主表 LEFT JOIN 审核/生产/质检工单。"""
|
|
# ── 基础查询:输注计划 ──
|
|
q0 = db.query(BizPilotInfusionSchedule).filter(
|
|
BizPilotInfusionSchedule.tenant_no == tenant_no,
|
|
BizPilotInfusionSchedule.del_flag == 0,
|
|
)
|
|
|
|
bt_filter = (biz_type or "").strip()
|
|
if bt_filter:
|
|
app_ids_for_biz = (
|
|
db.query(BizPilotApplication.id)
|
|
.filter(
|
|
BizPilotApplication.tenant_no == tenant_no,
|
|
BizPilotApplication.del_flag == 0,
|
|
BizPilotApplication.biz_type == bt_filter,
|
|
)
|
|
.subquery()
|
|
)
|
|
plan_ids_for_biz = (
|
|
db.query(BizPilotInfusionPlan.id)
|
|
.filter(
|
|
BizPilotInfusionPlan.tenant_no == tenant_no,
|
|
BizPilotInfusionPlan.del_flag == 0,
|
|
BizPilotInfusionPlan.pilot_application_id.in_(app_ids_for_biz),
|
|
)
|
|
.subquery()
|
|
)
|
|
q0 = q0.filter(BizPilotInfusionSchedule.infusion_plan_id.in_(plan_ids_for_biz))
|
|
|
|
# ── 关键字过滤(application_no, patient_name)需要子查询 ──
|
|
kw = (keyword or "").strip()
|
|
if kw:
|
|
like = f"%{kw}%"
|
|
# 通过 infusion_plan_id 关联 plan → application → patient
|
|
plan_ids = (
|
|
db.query(BizPilotInfusionPlan.id)
|
|
.filter(
|
|
BizPilotInfusionPlan.tenant_no == tenant_no,
|
|
BizPilotInfusionPlan.del_flag == 0,
|
|
)
|
|
.subquery()
|
|
)
|
|
app_ids = (
|
|
db.query(BizPilotApplication.id)
|
|
.filter(
|
|
BizPilotApplication.tenant_no == tenant_no,
|
|
BizPilotApplication.del_flag == 0,
|
|
or_(
|
|
BizPilotApplication.application_no.like(like),
|
|
BizPilotApplication.application_name.like(like),
|
|
),
|
|
)
|
|
.subquery()
|
|
)
|
|
patient_app_ids = (
|
|
db.query(BizPilotPatient.pilot_application_id)
|
|
.filter(
|
|
BizPilotPatient.tenant_no == tenant_no,
|
|
BizPilotPatient.del_flag == 0,
|
|
BizPilotPatient.patient_name.like(like),
|
|
)
|
|
.subquery()
|
|
)
|
|
# 找出满足条件的 infusion_plan_id
|
|
matched_plan_ids = (
|
|
db.query(BizPilotInfusionPlan.id)
|
|
.filter(
|
|
BizPilotInfusionPlan.tenant_no == tenant_no,
|
|
BizPilotInfusionPlan.del_flag == 0,
|
|
or_(
|
|
BizPilotInfusionPlan.pilot_application_id.in_(app_ids),
|
|
BizPilotInfusionPlan.pilot_application_id.in_(patient_app_ids),
|
|
),
|
|
)
|
|
.subquery()
|
|
)
|
|
q0 = q0.filter(
|
|
BizPilotInfusionSchedule.infusion_plan_id.in_(matched_plan_ids)
|
|
)
|
|
|
|
# ── schedule_status 过滤 ──
|
|
ss = (schedule_status or "").strip()
|
|
if ss:
|
|
q0 = q0.filter(BizPilotInfusionSchedule.schedule_status == ss)
|
|
|
|
# ── qc_status 过滤 ──
|
|
qs = (qc_status or "").strip()
|
|
if qs:
|
|
q0 = q0.filter(BizPilotInfusionSchedule.qc_status == qs)
|
|
|
|
# ── 输注时间范围 ──
|
|
time_bounds = []
|
|
if (infusion_time_start or "").strip():
|
|
try:
|
|
start_dt = datetime.strptime(infusion_time_start.strip()[:10], "%Y-%m-%d")
|
|
time_bounds.append(BizPilotInfusionSchedule.infusion_time >= start_dt)
|
|
except ValueError:
|
|
pass
|
|
if (infusion_time_end or "").strip():
|
|
try:
|
|
end_dt = datetime.strptime(infusion_time_end.strip()[:10], "%Y-%m-%d")
|
|
end_dt = end_dt.replace(hour=23, minute=59, second=59)
|
|
time_bounds.append(BizPilotInfusionSchedule.infusion_time <= end_dt)
|
|
except ValueError:
|
|
pass
|
|
if time_bounds:
|
|
q0 = q0.filter(or_(and_(*time_bounds), BizPilotInfusionSchedule.infusion_time.is_(None)))
|
|
|
|
# ── review_conclusion 过滤 ──
|
|
rc = (review_conclusion or "").strip()
|
|
if rc == "pending":
|
|
# 待审核:不存在审核记录
|
|
reviewed_schedule_ids = (
|
|
db.query(BizOutboundReleaseReview.infusion_schedule_id)
|
|
.filter(
|
|
BizOutboundReleaseReview.tenant_no == tenant_no,
|
|
BizOutboundReleaseReview.del_flag == 0,
|
|
BizOutboundReleaseReview.review_conclusion != "",
|
|
)
|
|
.subquery()
|
|
)
|
|
q0 = q0.filter(
|
|
~BizPilotInfusionSchedule.id.in_(reviewed_schedule_ids)
|
|
)
|
|
elif rc:
|
|
# 有具体结论
|
|
reviewed_schedule_ids = (
|
|
db.query(BizOutboundReleaseReview.infusion_schedule_id)
|
|
.filter(
|
|
BizOutboundReleaseReview.tenant_no == tenant_no,
|
|
BizOutboundReleaseReview.del_flag == 0,
|
|
BizOutboundReleaseReview.review_conclusion == rc,
|
|
)
|
|
.subquery()
|
|
)
|
|
q0 = q0.filter(
|
|
BizPilotInfusionSchedule.id.in_(reviewed_schedule_ids)
|
|
)
|
|
|
|
# ── 数据权限 ──
|
|
if user_info:
|
|
from app.core.data_scope import apply_data_scope
|
|
|
|
q0 = apply_data_scope(q0, BizPilotInfusionSchedule, user_info, db, tenant_no)
|
|
|
|
# ── 分页 ──
|
|
total = (
|
|
q0.order_by(None)
|
|
.with_entities(func.count(BizPilotInfusionSchedule.id))
|
|
.scalar()
|
|
or 0
|
|
)
|
|
page = max(1, page)
|
|
page_size = min(max(1, page_size), 100)
|
|
schedules = (
|
|
q0.order_by(BizPilotInfusionSchedule.create_time.desc())
|
|
.offset((page - 1) * page_size)
|
|
.limit(page_size)
|
|
.all()
|
|
)
|
|
|
|
schedule_ids = [s.id for s in schedules]
|
|
|
|
# ── 批量加载关联数据 ──
|
|
# 审核记录
|
|
review_map: Dict[str, BizOutboundReleaseReview] = {}
|
|
if schedule_ids:
|
|
for rev in (
|
|
db.query(BizOutboundReleaseReview)
|
|
.filter(
|
|
BizOutboundReleaseReview.infusion_schedule_id.in_(schedule_ids),
|
|
BizOutboundReleaseReview.tenant_no == tenant_no,
|
|
BizOutboundReleaseReview.del_flag == 0,
|
|
)
|
|
.all()
|
|
):
|
|
review_map[rev.infusion_schedule_id] = rev
|
|
|
|
# 生产工单(出库类型)
|
|
production_map: Dict[str, BizProductionWorkOrder] = {}
|
|
if schedule_ids:
|
|
for pwo in (
|
|
db.query(BizProductionWorkOrder)
|
|
.filter(
|
|
BizProductionWorkOrder.infusion_schedule_id.in_(schedule_ids),
|
|
BizProductionWorkOrder.work_order_type == "outbound",
|
|
BizProductionWorkOrder.biz_type.in_(
|
|
list(_BIZ_TYPE_LABELS.keys())
|
|
),
|
|
BizProductionWorkOrder.tenant_no == tenant_no,
|
|
BizProductionWorkOrder.del_flag == 0,
|
|
)
|
|
.all()
|
|
):
|
|
production_map[pwo.infusion_schedule_id] = pwo
|
|
|
|
# 质检工单(出库类型)
|
|
qc_map: Dict[str, BizQcWorkOrder] = {}
|
|
if schedule_ids:
|
|
for qwo in (
|
|
db.query(BizQcWorkOrder)
|
|
.filter(
|
|
BizQcWorkOrder.infusion_schedule_id.in_(schedule_ids),
|
|
BizQcWorkOrder.qc_biz_type == "outbound",
|
|
BizQcWorkOrder.biz_type.in_(list(_BIZ_TYPE_LABELS.keys())),
|
|
BizQcWorkOrder.tenant_no == tenant_no,
|
|
BizQcWorkOrder.del_flag == 0,
|
|
)
|
|
.all()
|
|
):
|
|
qc_map[qwo.infusion_schedule_id] = qwo
|
|
|
|
# 输注计划 → 申请单 → 患者
|
|
plan_ids_for_schedules = {
|
|
s.id: s.infusion_plan_id for s in schedules if s.infusion_plan_id
|
|
}
|
|
unique_plan_ids = list(set(plan_ids_for_schedules.values()))
|
|
|
|
plan_map: Dict[str, BizPilotInfusionPlan] = {}
|
|
if unique_plan_ids:
|
|
for plan in (
|
|
db.query(BizPilotInfusionPlan)
|
|
.filter(
|
|
BizPilotInfusionPlan.id.in_(unique_plan_ids),
|
|
BizPilotInfusionPlan.tenant_no == tenant_no,
|
|
BizPilotInfusionPlan.del_flag == 0,
|
|
)
|
|
.all()
|
|
):
|
|
plan_map[plan.id] = plan
|
|
|
|
unique_app_ids = list(
|
|
{p.pilot_application_id for p in plan_map.values() if p.pilot_application_id}
|
|
)
|
|
app_map: Dict[str, BizPilotApplication] = {}
|
|
if unique_app_ids:
|
|
for app in (
|
|
db.query(BizPilotApplication)
|
|
.filter(
|
|
BizPilotApplication.id.in_(unique_app_ids),
|
|
BizPilotApplication.tenant_no == tenant_no,
|
|
BizPilotApplication.del_flag == 0,
|
|
)
|
|
.all()
|
|
):
|
|
app_map[app.id] = app
|
|
|
|
# 患者
|
|
patient_map: Dict[str, BizPilotPatient] = {}
|
|
if unique_app_ids:
|
|
for patient in (
|
|
db.query(BizPilotPatient)
|
|
.filter(
|
|
BizPilotPatient.pilot_application_id.in_(unique_app_ids),
|
|
BizPilotPatient.tenant_no == tenant_no,
|
|
BizPilotPatient.del_flag == 0,
|
|
)
|
|
.all()
|
|
):
|
|
patient_map[patient.pilot_application_id] = patient
|
|
|
|
# ── 组装列表项 ──
|
|
items: List[Dict[str, Any]] = []
|
|
for s in schedules:
|
|
plan = plan_map.get(s.infusion_plan_id)
|
|
app = app_map.get(plan.pilot_application_id) if plan else None
|
|
patient = patient_map.get(app.id) if app else None
|
|
rev = review_map.get(s.id)
|
|
pwo = production_map.get(s.id)
|
|
qwo = qc_map.get(s.id)
|
|
biz_type = _resolve_schedule_biz_type(app, qwo, pwo)
|
|
|
|
item: Dict[str, Any] = {
|
|
"scheduleId": s.id,
|
|
"bizType": biz_type,
|
|
"bizTypeLabel": _BIZ_TYPE_LABELS.get(biz_type, biz_type),
|
|
"applicationNo": (app.application_no or "") if app else "",
|
|
"applicationName": (app.application_name or "") if app else "",
|
|
"channelName": (app.channel_name or "") if app else "",
|
|
"patientName": (patient.patient_name or "") if patient else "",
|
|
"cellType": s.cell_type or (plan.cell_type or "") if plan else (s.cell_type or ""),
|
|
"specText": s.spec_text or (plan.spec_text or "") if plan else (s.spec_text or ""),
|
|
"infusionTime": _fmt_dt(s.infusion_time),
|
|
"scheduleStatus": s.schedule_status or "",
|
|
"scheduleStatusLabel": _SCHEDULE_STATUS_LABELS.get(
|
|
s.schedule_status or "", s.schedule_status or ""
|
|
),
|
|
"qcStatus": s.qc_status or "",
|
|
"qcStatusLabel": _QC_STATUS_LABELS.get(
|
|
s.qc_status or "", s.qc_status or ""
|
|
),
|
|
"reviewConclusion": "",
|
|
"reviewConclusionLabel": "",
|
|
"reviewByName": "",
|
|
"reviewTime": "",
|
|
"reviewRemark": "",
|
|
"reviewAttachments": "",
|
|
"reviewId": "",
|
|
"productionWorkOrderNo": "",
|
|
"productionWorkOrderStatus": "",
|
|
"qcWorkOrderNo": "",
|
|
"qcWorkOrderStatus": "",
|
|
}
|
|
if rev:
|
|
rc_val = rev.review_conclusion or ""
|
|
item["reviewConclusion"] = rc_val
|
|
item["reviewConclusionLabel"] = _REVIEW_CONCLUSION_LABELS.get(rc_val, rc_val)
|
|
item["reviewByName"] = rev.review_by_name or ""
|
|
item["reviewTime"] = _fmt_dt(rev.review_time)
|
|
item["reviewRemark"] = rev.review_remark or ""
|
|
item["reviewAttachments"] = rev.review_attachments or ""
|
|
item["reviewId"] = rev.id
|
|
else:
|
|
item["reviewConclusionLabel"] = "待审核"
|
|
|
|
if pwo:
|
|
item["productionWorkOrderNo"] = pwo.work_order_no or ""
|
|
item["productionWorkOrderStatus"] = pwo.work_order_status or ""
|
|
if qwo:
|
|
item["qcWorkOrderNo"] = qwo.work_order_no or ""
|
|
item["qcWorkOrderStatus"] = qwo.work_order_status or ""
|
|
|
|
items.append(item)
|
|
|
|
return {
|
|
"list": items,
|
|
"total": int(total),
|
|
"page": page,
|
|
"pageSize": page_size,
|
|
"totalPages": _total_pages(int(total), page_size),
|
|
}
|
|
|
|
|
|
# ────────────────────────────────────────────
|
|
# Detail aggregation
|
|
# ────────────────────────────────────────────
|
|
|
|
|
|
def get_schedule_detail(
|
|
db: Session, schedule_id: str, tenant_no: str
|
|
) -> Optional[Dict[str, Any]]:
|
|
"""按输注计划 ID 聚合出库放行审核详情(中试上下文 + 生产 + 质检 + 审核)。"""
|
|
from app.crud.qc_work_order import build_outbound_pilot_context_by_schedule
|
|
|
|
pilot_context = build_outbound_pilot_context_by_schedule(db, schedule_id, tenant_no)
|
|
|
|
# ── 生产工单 + 任务 ──
|
|
production_info: Dict[str, Any] = {}
|
|
pwo = (
|
|
db.query(BizProductionWorkOrder)
|
|
.filter(
|
|
BizProductionWorkOrder.infusion_schedule_id == schedule_id,
|
|
BizProductionWorkOrder.work_order_type == "outbound",
|
|
BizProductionWorkOrder.biz_type.in_(list(_BIZ_TYPE_LABELS.keys())),
|
|
BizProductionWorkOrder.tenant_no == tenant_no,
|
|
BizProductionWorkOrder.del_flag == 0,
|
|
)
|
|
.first()
|
|
)
|
|
if pwo:
|
|
from app.crud.production_work_order import (
|
|
_production_info_item,
|
|
)
|
|
|
|
production_info = _production_info_item(db, pwo)
|
|
|
|
# ── 质检工单 + 任务 + 结果 ──
|
|
qc_info: Dict[str, Any] = {}
|
|
qwo = (
|
|
db.query(BizQcWorkOrder)
|
|
.filter(
|
|
BizQcWorkOrder.infusion_schedule_id == schedule_id,
|
|
BizQcWorkOrder.qc_biz_type == "outbound",
|
|
BizQcWorkOrder.biz_type.in_(list(_BIZ_TYPE_LABELS.keys())),
|
|
BizQcWorkOrder.tenant_no == tenant_no,
|
|
BizQcWorkOrder.del_flag == 0,
|
|
)
|
|
.first()
|
|
)
|
|
if qwo:
|
|
from app.crud.qc_work_order import _qc_info_item
|
|
|
|
qc_info = _qc_info_item(db, qwo, tenant_no)
|
|
|
|
# ── 审核记录 ──
|
|
review_info: Dict[str, Any] = {}
|
|
rev = (
|
|
db.query(BizOutboundReleaseReview)
|
|
.filter(
|
|
BizOutboundReleaseReview.infusion_schedule_id == schedule_id,
|
|
BizOutboundReleaseReview.tenant_no == tenant_no,
|
|
BizOutboundReleaseReview.del_flag == 0,
|
|
)
|
|
.first()
|
|
)
|
|
if rev:
|
|
review_info = _row_to_dict(rev)
|
|
|
|
outbound_material: Dict[str, Any] = {}
|
|
try:
|
|
from app.crud import outbound_release_material as orm_svc
|
|
|
|
outbound_material = orm_svc.get_outbound_material(db, schedule_id, tenant_no)
|
|
except Exception:
|
|
outbound_material = {}
|
|
|
|
# 与 OutboundReleaseReviewDetail / 出库质检详情一致:中试字段展平到顶层,勿嵌套 pilotContext
|
|
return {
|
|
**(pilot_context or {}),
|
|
"productionInfo": production_info,
|
|
"qcInfo": qc_info,
|
|
"reviewInfo": review_info,
|
|
"outboundMaterial": outbound_material,
|
|
}
|
|
|
|
|
|
# ────────────────────────────────────────────
|
|
# Review operations
|
|
# ────────────────────────────────────────────
|
|
|
|
|
|
def review_schedule(
|
|
db: Session,
|
|
schedule_id: str,
|
|
*,
|
|
conclusion: str,
|
|
remark: Optional[str] = None,
|
|
attachments: Optional[str] = None,
|
|
tenant_no: str,
|
|
user_id: str,
|
|
) -> Dict[str, Any]:
|
|
"""对指定输注计划执行出库放行审核(upsert)。"""
|
|
now = datetime.now()
|
|
user_name = _get_user_name(db, user_id)
|
|
|
|
# 查找已有审核记录
|
|
rev = (
|
|
db.query(BizOutboundReleaseReview)
|
|
.filter(
|
|
BizOutboundReleaseReview.infusion_schedule_id == schedule_id,
|
|
BizOutboundReleaseReview.tenant_no == tenant_no,
|
|
BizOutboundReleaseReview.del_flag == 0,
|
|
)
|
|
.first()
|
|
)
|
|
|
|
# 自动填充生产工单 ID
|
|
pwo = (
|
|
db.query(BizProductionWorkOrder)
|
|
.filter(
|
|
BizProductionWorkOrder.infusion_schedule_id == schedule_id,
|
|
BizProductionWorkOrder.work_order_type == "outbound",
|
|
BizProductionWorkOrder.tenant_no == tenant_no,
|
|
BizProductionWorkOrder.del_flag == 0,
|
|
)
|
|
.first()
|
|
)
|
|
# 自动填充质检工单 ID
|
|
qwo = (
|
|
db.query(BizQcWorkOrder)
|
|
.filter(
|
|
BizQcWorkOrder.infusion_schedule_id == schedule_id,
|
|
BizQcWorkOrder.qc_biz_type == "outbound",
|
|
BizQcWorkOrder.tenant_no == tenant_no,
|
|
BizQcWorkOrder.del_flag == 0,
|
|
)
|
|
.first()
|
|
)
|
|
|
|
if rev:
|
|
# update
|
|
rev.review_conclusion = conclusion
|
|
rev.review_remark = (remark or "").strip() or None
|
|
rev.review_attachments = (attachments or "").strip() or None
|
|
rev.reviewer_user_id = user_id
|
|
rev.review_by_name = user_name
|
|
rev.review_time = now
|
|
rev.production_work_order_id = pwo.id if pwo else (rev.production_work_order_id or "")
|
|
rev.qc_work_order_id = qwo.id if qwo else (rev.qc_work_order_id or "")
|
|
rev.update_by = user_id
|
|
rev.update_time = now
|
|
else:
|
|
# create
|
|
rev = BizOutboundReleaseReview(
|
|
id=generate_uuid(),
|
|
infusion_schedule_id=schedule_id,
|
|
production_work_order_id=pwo.id if pwo else "",
|
|
qc_work_order_id=qwo.id if qwo else "",
|
|
review_conclusion=conclusion,
|
|
reviewer_user_id=user_id,
|
|
review_by_name=user_name,
|
|
review_time=now,
|
|
review_remark=(remark or "").strip() or None,
|
|
review_attachments=(attachments or "").strip() or None,
|
|
tenant_no=tenant_no,
|
|
order_num=0,
|
|
remark="",
|
|
create_by=user_id,
|
|
create_dept_id="",
|
|
create_time=now,
|
|
update_by=user_id,
|
|
update_time=now,
|
|
del_flag=0,
|
|
)
|
|
db.add(rev)
|
|
|
|
db.flush()
|
|
return _row_to_dict(rev)
|
|
|
|
|
|
def batch_review(
|
|
db: Session,
|
|
schedule_ids: List[str],
|
|
*,
|
|
conclusion: str,
|
|
remark: Optional[str] = None,
|
|
attachments: Optional[str] = None,
|
|
tenant_no: str,
|
|
user_id: str,
|
|
) -> Dict[str, Any]:
|
|
"""批量审核多个输注计划。"""
|
|
success = 0
|
|
fail = 0
|
|
for sid in schedule_ids:
|
|
try:
|
|
review_schedule(
|
|
db,
|
|
sid,
|
|
conclusion=conclusion,
|
|
remark=remark,
|
|
attachments=attachments,
|
|
tenant_no=tenant_no,
|
|
user_id=user_id,
|
|
)
|
|
success += 1
|
|
except Exception:
|
|
fail += 1
|
|
return {"success": success, "fail": fail}
|