|
|
@@ -0,0 +1,185 @@
|
|
|
+"""生产任务步骤废弃物产出 — 只读汇总列表。"""
|
|
|
+from __future__ import annotations
|
|
|
+
|
|
|
+from typing import Any, Dict, Iterable, List, Optional, Set, Tuple
|
|
|
+
|
|
|
+from sqlalchemy import and_, or_
|
|
|
+from sqlalchemy.orm import Session
|
|
|
+
|
|
|
+from app.models.models import (
|
|
|
+ BizOutputType,
|
|
|
+ BizProductionTask,
|
|
|
+ BizProductionTaskStep,
|
|
|
+ BizProductionTaskStepOutput,
|
|
|
+ BizProductionWorkOrder,
|
|
|
+)
|
|
|
+
|
|
|
+WASTE_OUTPUT_TYPE_CODE = "FQW"
|
|
|
+WASTE_OUTPUT_TYPE_NAME = "废弃物"
|
|
|
+
|
|
|
+
|
|
|
+def pick_waste_output_type_ids(rows: Iterable[Dict[str, Any]]) -> Set[str]:
|
|
|
+ """从产出类型行中选出废弃物类型 id:优先 type_code=FQW,否则兜底 type_name=废弃物。"""
|
|
|
+ rows = list(rows or [])
|
|
|
+ by_code = {
|
|
|
+ str(r.get("id") or "").strip()
|
|
|
+ for r in rows
|
|
|
+ if str(r.get("type_code") or "").strip() == WASTE_OUTPUT_TYPE_CODE
|
|
|
+ and str(r.get("id") or "").strip()
|
|
|
+ }
|
|
|
+ if by_code:
|
|
|
+ return by_code
|
|
|
+ return {
|
|
|
+ str(r.get("id") or "").strip()
|
|
|
+ for r in rows
|
|
|
+ if str(r.get("type_name") or "").strip() == WASTE_OUTPUT_TYPE_NAME
|
|
|
+ and str(r.get("id") or "").strip()
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+def match_waste_output_keyword(
|
|
|
+ keyword: Optional[str], output_name: Optional[str], output_barcode: Optional[str]
|
|
|
+) -> bool:
|
|
|
+ kw = (keyword or "").strip()
|
|
|
+ if not kw:
|
|
|
+ return True
|
|
|
+ name = output_name or ""
|
|
|
+ barcode = output_barcode or ""
|
|
|
+ return kw in name or kw in barcode
|
|
|
+
|
|
|
+
|
|
|
+def resolve_waste_output_type_ids(db: Session, tenant_no: str) -> Set[str]:
|
|
|
+ rows = (
|
|
|
+ db.query(BizOutputType.id, BizOutputType.type_code, BizOutputType.type_name)
|
|
|
+ .filter(BizOutputType.tenant_no == tenant_no, BizOutputType.del_flag == 0)
|
|
|
+ .all()
|
|
|
+ )
|
|
|
+ return pick_waste_output_type_ids(
|
|
|
+ [{"id": r.id, "type_code": r.type_code, "type_name": r.type_name} for r in rows]
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+def _fmt_dt(t) -> str:
|
|
|
+ if t is None:
|
|
|
+ return ""
|
|
|
+ if hasattr(t, "strftime"):
|
|
|
+ return t.strftime("%Y-%m-%d %H:%M:%S")
|
|
|
+ return str(t)
|
|
|
+
|
|
|
+
|
|
|
+def _normalize_date_bound(value: str, *, end_of_day: bool = False) -> str:
|
|
|
+ v = (value or "").strip()
|
|
|
+ if not v:
|
|
|
+ return v
|
|
|
+ if len(v) <= 10:
|
|
|
+ return f"{v} 23:59:59" if end_of_day else f"{v} 00:00:00"
|
|
|
+ return v
|
|
|
+
|
|
|
+
|
|
|
+def list_production_waste_outputs(
|
|
|
+ db: Session,
|
|
|
+ *,
|
|
|
+ tenant_no: str,
|
|
|
+ page: int = 1,
|
|
|
+ page_size: int = 10,
|
|
|
+ keyword: Optional[str] = None,
|
|
|
+ create_time_start: Optional[str] = None,
|
|
|
+ create_time_end: Optional[str] = None,
|
|
|
+ user_info=None,
|
|
|
+) -> Tuple[List[Dict[str, Any]], int]:
|
|
|
+ type_ids = resolve_waste_output_type_ids(db, tenant_no)
|
|
|
+ if not type_ids:
|
|
|
+ return [], 0
|
|
|
+
|
|
|
+ q = (
|
|
|
+ db.query(
|
|
|
+ BizProductionTaskStepOutput,
|
|
|
+ BizProductionTaskStep,
|
|
|
+ BizProductionTask,
|
|
|
+ BizProductionWorkOrder,
|
|
|
+ )
|
|
|
+ .join(
|
|
|
+ BizProductionTaskStep,
|
|
|
+ and_(
|
|
|
+ BizProductionTaskStep.id == BizProductionTaskStepOutput.step_id,
|
|
|
+ BizProductionTaskStep.del_flag == 0,
|
|
|
+ ),
|
|
|
+ )
|
|
|
+ .join(
|
|
|
+ BizProductionTask,
|
|
|
+ and_(
|
|
|
+ BizProductionTask.id == BizProductionTaskStep.task_id,
|
|
|
+ BizProductionTask.del_flag == 0,
|
|
|
+ BizProductionTask.tenant_no == tenant_no,
|
|
|
+ ),
|
|
|
+ )
|
|
|
+ .outerjoin(
|
|
|
+ BizProductionWorkOrder,
|
|
|
+ and_(
|
|
|
+ BizProductionWorkOrder.id == BizProductionTask.work_order_id,
|
|
|
+ BizProductionWorkOrder.del_flag == 0,
|
|
|
+ ),
|
|
|
+ )
|
|
|
+ .filter(
|
|
|
+ BizProductionTaskStepOutput.tenant_no == tenant_no,
|
|
|
+ BizProductionTaskStepOutput.del_flag == 0,
|
|
|
+ BizProductionTaskStepOutput.output_type_id.in_(list(type_ids)),
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+ kw = (keyword or "").strip()
|
|
|
+ if kw:
|
|
|
+ like = f"%{kw}%"
|
|
|
+ q = q.filter(
|
|
|
+ or_(
|
|
|
+ BizProductionTaskStepOutput.output_name.like(like),
|
|
|
+ BizProductionTaskStepOutput.output_barcode.like(like),
|
|
|
+ )
|
|
|
+ )
|
|
|
+ if create_time_start and create_time_start.strip():
|
|
|
+ q = q.filter(
|
|
|
+ BizProductionTaskStepOutput.create_time
|
|
|
+ >= _normalize_date_bound(create_time_start.strip())
|
|
|
+ )
|
|
|
+ if create_time_end and create_time_end.strip():
|
|
|
+ q = q.filter(
|
|
|
+ BizProductionTaskStepOutput.create_time
|
|
|
+ <= _normalize_date_bound(create_time_end.strip(), end_of_day=True)
|
|
|
+ )
|
|
|
+
|
|
|
+ if user_info is not None:
|
|
|
+ from app.core.data_scope import apply_data_scope
|
|
|
+
|
|
|
+ q = apply_data_scope(q, BizProductionTask, user_info, db, tenant_no)
|
|
|
+
|
|
|
+ q = q.order_by(BizProductionTaskStepOutput.create_time.desc())
|
|
|
+ total = q.count()
|
|
|
+ page = max(1, int(page or 1))
|
|
|
+ page_size = max(1, min(int(page_size or 10), 100))
|
|
|
+ rows = q.offset((page - 1) * page_size).limit(page_size).all()
|
|
|
+
|
|
|
+ items: List[Dict[str, Any]] = []
|
|
|
+ for out, step, task, wo in rows:
|
|
|
+ items.append(
|
|
|
+ {
|
|
|
+ "id": out.id,
|
|
|
+ "outputName": out.output_name or "",
|
|
|
+ "outputBarcode": out.output_barcode or "",
|
|
|
+ "remark": out.remark or "",
|
|
|
+ "createTime": _fmt_dt(out.create_time),
|
|
|
+ "stepId": out.step_id or "",
|
|
|
+ "processName": (step.process_name if step else None) or "",
|
|
|
+ "taskId": (task.id if task else None) or "",
|
|
|
+ "taskNo": (task.task_no if task else None) or "",
|
|
|
+ "workOrderId": (wo.id if wo else None) or (task.work_order_id if task else "") or "",
|
|
|
+ "workOrderNo": (wo.work_order_no if wo else None) or "",
|
|
|
+ "sampleCode": (wo.sample_code if wo else None) or "",
|
|
|
+ }
|
|
|
+ )
|
|
|
+ return items, total
|
|
|
+
|
|
|
+
|
|
|
+def total_pages(total: int, page_size: int) -> int:
|
|
|
+ if page_size <= 0:
|
|
|
+ return 0
|
|
|
+ return (total + page_size - 1) // page_size
|