779 lines
24 KiB
Python
779 lines
24 KiB
Python
"""生产任务集合 CRUD"""
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
from typing import Any, Dict, List, Optional, Set, Tuple
|
|
|
|
from sqlalchemy import func
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.core.utils import generate_uuid
|
|
from app.models.models import (
|
|
BizProcessRoute,
|
|
BizProductionTask,
|
|
BizProductionTaskSet,
|
|
BizProductionTaskSetItem,
|
|
BizProductionWorkOrder,
|
|
)
|
|
from app.utils.production_task_name_placeholders import (
|
|
apply_task_name_placeholders,
|
|
revert_task_name_placeholders,
|
|
)
|
|
|
|
|
|
def resolve_work_order_lab_storage_code(
|
|
db: Session,
|
|
*,
|
|
tenant_no: str,
|
|
wo: BizProductionWorkOrder,
|
|
) -> str:
|
|
"""解析工单对应实验室入库编码(样本接收优先,样本编码回退)。"""
|
|
from app.crud import sample_reception as sample_reception_svc
|
|
from app.models.models import BizSampleReception
|
|
|
|
reception_id = (getattr(wo, "reception_id", None) or "").strip()
|
|
if reception_id:
|
|
rec = (
|
|
db.query(BizSampleReception)
|
|
.filter(
|
|
BizSampleReception.id == reception_id,
|
|
BizSampleReception.tenant_no == tenant_no,
|
|
BizSampleReception.del_flag == 0,
|
|
)
|
|
.first()
|
|
)
|
|
if rec:
|
|
code = (rec.lab_storage_code or "").strip()
|
|
if code:
|
|
return code
|
|
sample_code = (getattr(wo, "sample_code", None) or "").strip()
|
|
if not sample_code:
|
|
return ""
|
|
source = (getattr(wo, "reception_source", None) or "").strip() or None
|
|
code_map = sample_reception_svc.get_lab_storage_code_map_by_sample_codes(
|
|
db, tenant_no, [sample_code], reception_source=source
|
|
)
|
|
return (code_map.get(sample_code) or "").strip()
|
|
|
|
|
|
def _strip_or_none(raw: Any) -> Optional[str]:
|
|
text = str(raw or "").strip()
|
|
return text or None
|
|
|
|
|
|
def normalize_item_payload(raw: Optional[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
|
|
data = raw or {}
|
|
task_name = str(data.get("taskName") or data.get("task_name") or "").strip()
|
|
if not task_name:
|
|
return None
|
|
route_raw = data.get("routeId") if "routeId" in data else data.get("route_id")
|
|
return {
|
|
"task_name": task_name,
|
|
"route_id": _strip_or_none(route_raw),
|
|
"description": _strip_or_none(data.get("description")),
|
|
"remark": _strip_or_none(data.get("remark")),
|
|
}
|
|
|
|
|
|
def skip_empty_named_tasks(tasks: List[Dict[str, Any]]) -> Tuple[List[Dict[str, Any]], int]:
|
|
kept: List[Dict[str, Any]] = []
|
|
skipped = 0
|
|
for row in tasks or []:
|
|
name = str(row.get("task_name") or row.get("taskName") or "").strip()
|
|
if not name:
|
|
skipped += 1
|
|
continue
|
|
route_raw = row.get("route_id") if "route_id" in row else row.get("routeId")
|
|
name_raw = row.get("route_name") if "route_name" in row else row.get("routeName")
|
|
kept.append(
|
|
{
|
|
"task_name": name,
|
|
"route_id": _strip_or_none(route_raw),
|
|
"route_name": _strip_or_none(name_raw),
|
|
"description": _strip_or_none(row.get("description")),
|
|
"remark": _strip_or_none(row.get("remark")),
|
|
}
|
|
)
|
|
return kept, skipped
|
|
|
|
|
|
def collect_missing_route_ids(items: List[Dict[str, Any]], existing_ids: Set[str]) -> List[str]:
|
|
missing: List[str] = []
|
|
seen: Set[str] = set()
|
|
alive = existing_ids or set()
|
|
for item in items or []:
|
|
rid = _strip_or_none(item.get("route_id"))
|
|
if not rid or rid in alive or rid in seen:
|
|
continue
|
|
seen.add(rid)
|
|
missing.append(rid)
|
|
return missing
|
|
|
|
|
|
def total_pages(total: int, page_size: int) -> int:
|
|
if page_size <= 0:
|
|
return 0
|
|
return (int(total) + int(page_size) - 1) // int(page_size)
|
|
|
|
|
|
def _item_to_dict(r: BizProductionTaskSetItem) -> Dict[str, Any]:
|
|
return {
|
|
"id": r.id,
|
|
"setId": r.set_id,
|
|
"taskName": r.task_name or "",
|
|
"routeId": r.route_id or "",
|
|
"routeName": r.route_name or "",
|
|
"description": r.description or "",
|
|
"remark": r.remark or "",
|
|
"orderNum": r.order_num if r.order_num is not None else 0,
|
|
}
|
|
|
|
|
|
def _set_to_dict(
|
|
r: BizProductionTaskSet,
|
|
item_count: int = 0,
|
|
items: Optional[List[Dict[str, Any]]] = None,
|
|
) -> Dict[str, Any]:
|
|
data: Dict[str, Any] = {
|
|
"id": r.id,
|
|
"setName": r.set_name or "",
|
|
"status": r.status or "1",
|
|
"itemCount": item_count,
|
|
"orderNum": r.order_num if r.order_num is not None else 0,
|
|
"remark": r.remark or "",
|
|
"isBuiltin": getattr(r, "is_builtin", None) or "0",
|
|
"createTime": r.create_time.strftime("%Y-%m-%d %H:%M:%S") if r.create_time else "",
|
|
"updateTime": r.update_time.strftime("%Y-%m-%d %H:%M:%S") if r.update_time else "",
|
|
}
|
|
if items is not None:
|
|
data["items"] = items
|
|
return data
|
|
|
|
|
|
def set_name_exists(
|
|
db: Session, name: str, tenant_no: str, exclude_id: Optional[str] = None
|
|
) -> bool:
|
|
n = (name or "").strip()
|
|
if not n:
|
|
return False
|
|
q = db.query(BizProductionTaskSet).filter(
|
|
BizProductionTaskSet.set_name == n,
|
|
BizProductionTaskSet.tenant_no == tenant_no,
|
|
BizProductionTaskSet.del_flag == 0,
|
|
)
|
|
if exclude_id:
|
|
q = q.filter(BizProductionTaskSet.id != exclude_id)
|
|
return q.first() is not None
|
|
|
|
|
|
def _require_status(status: Optional[str]) -> str:
|
|
st = (status or "1").strip()
|
|
if st not in ("0", "1"):
|
|
raise ValueError("状态无效")
|
|
return st
|
|
|
|
|
|
def _load_route_name_map(db: Session, tenant_no: str, route_ids: List[str]) -> Dict[str, str]:
|
|
ids = [rid for rid in route_ids if rid]
|
|
if not ids:
|
|
return {}
|
|
rows = (
|
|
db.query(BizProcessRoute)
|
|
.filter(
|
|
BizProcessRoute.tenant_no == tenant_no,
|
|
BizProcessRoute.del_flag == 0,
|
|
BizProcessRoute.id.in_(ids),
|
|
)
|
|
.all()
|
|
)
|
|
return {r.id: (r.route_name or "") for r in rows}
|
|
|
|
|
|
def _normalize_items_or_raise(raw_items: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
out: List[Dict[str, Any]] = []
|
|
for raw in raw_items or []:
|
|
item = normalize_item_payload(raw)
|
|
if item:
|
|
out.append(item)
|
|
if not out:
|
|
raise ValueError("请至少添加一条任务名称非空的条目")
|
|
return out
|
|
|
|
|
|
def _assert_routes_exist(
|
|
db: Session, tenant_no: str, items: List[Dict[str, Any]]
|
|
) -> Dict[str, str]:
|
|
wanted = [i["route_id"] for i in items if i.get("route_id")]
|
|
name_map = _load_route_name_map(db, tenant_no, wanted)
|
|
missing = collect_missing_route_ids(items, set(name_map.keys()))
|
|
if missing:
|
|
raise ValueError("存在无效或已删除的工艺路线")
|
|
return name_map
|
|
|
|
|
|
def _replace_items(
|
|
db: Session,
|
|
*,
|
|
set_id: str,
|
|
tenant_no: str,
|
|
items: List[Dict[str, Any]],
|
|
route_names: Dict[str, str],
|
|
user_id: Optional[str],
|
|
create_dept_id: str,
|
|
) -> None:
|
|
now = datetime.now()
|
|
existing = (
|
|
db.query(BizProductionTaskSetItem)
|
|
.filter(
|
|
BizProductionTaskSetItem.set_id == set_id,
|
|
BizProductionTaskSetItem.tenant_no == tenant_no,
|
|
BizProductionTaskSetItem.del_flag == 0,
|
|
)
|
|
.all()
|
|
)
|
|
for row in existing:
|
|
row.del_flag = 1
|
|
row.update_by = user_id
|
|
row.update_time = now
|
|
db.add(row)
|
|
for idx, item in enumerate(items, start=1):
|
|
rid = item.get("route_id")
|
|
db.add(
|
|
BizProductionTaskSetItem(
|
|
id=generate_uuid(),
|
|
set_id=set_id,
|
|
task_name=item["task_name"],
|
|
route_id=rid,
|
|
route_name=route_names.get(rid or "") if rid else None,
|
|
description=item.get("description"),
|
|
remark=item.get("remark"),
|
|
tenant_no=tenant_no,
|
|
order_num=idx,
|
|
create_by=user_id,
|
|
create_dept_id=create_dept_id or "",
|
|
create_time=now,
|
|
update_by=user_id,
|
|
update_time=now,
|
|
del_flag=0,
|
|
)
|
|
)
|
|
|
|
|
|
def _item_count_map(db: Session, tenant_no: str, set_ids: List[str]) -> Dict[str, int]:
|
|
if not set_ids:
|
|
return {}
|
|
rows = (
|
|
db.query(BizProductionTaskSetItem.set_id, func.count(BizProductionTaskSetItem.id))
|
|
.filter(
|
|
BizProductionTaskSetItem.tenant_no == tenant_no,
|
|
BizProductionTaskSetItem.del_flag == 0,
|
|
BizProductionTaskSetItem.set_id.in_(set_ids),
|
|
)
|
|
.group_by(BizProductionTaskSetItem.set_id)
|
|
.all()
|
|
)
|
|
return {sid: int(cnt) for sid, cnt in rows}
|
|
|
|
|
|
def list_sets(
|
|
db: Session,
|
|
*,
|
|
tenant_no: str,
|
|
page: int = 1,
|
|
page_size: int = 10,
|
|
keyword: Optional[str] = None,
|
|
status: Optional[str] = None,
|
|
) -> Tuple[List[Dict[str, Any]], int]:
|
|
q = db.query(BizProductionTaskSet).filter(
|
|
BizProductionTaskSet.tenant_no == tenant_no,
|
|
BizProductionTaskSet.del_flag == 0,
|
|
)
|
|
kw = (keyword or "").strip()
|
|
if kw:
|
|
q = q.filter(BizProductionTaskSet.set_name.like(f"%{kw}%"))
|
|
st = (status or "").strip()
|
|
if st in ("0", "1"):
|
|
q = q.filter(BizProductionTaskSet.status == st)
|
|
total = q.count()
|
|
rows = (
|
|
q.order_by(BizProductionTaskSet.create_time.desc())
|
|
.offset((page - 1) * page_size)
|
|
.limit(page_size)
|
|
.all()
|
|
)
|
|
counts = _item_count_map(db, tenant_no, [r.id for r in rows])
|
|
return [_set_to_dict(r, item_count=counts.get(r.id, 0)) for r in rows], int(total)
|
|
|
|
|
|
def list_active_sets(db: Session, *, tenant_no: str) -> List[Dict[str, Any]]:
|
|
rows = (
|
|
db.query(BizProductionTaskSet)
|
|
.filter(
|
|
BizProductionTaskSet.tenant_no == tenant_no,
|
|
BizProductionTaskSet.del_flag == 0,
|
|
BizProductionTaskSet.status == "1",
|
|
)
|
|
.order_by(BizProductionTaskSet.create_time.desc())
|
|
.all()
|
|
)
|
|
counts = _item_count_map(db, tenant_no, [r.id for r in rows])
|
|
return [_set_to_dict(r, item_count=counts.get(r.id, 0)) for r in rows]
|
|
|
|
|
|
def get_set(db: Session, set_id: str, tenant_no: str) -> Optional[Dict[str, Any]]:
|
|
row = (
|
|
db.query(BizProductionTaskSet)
|
|
.filter(
|
|
BizProductionTaskSet.id == set_id,
|
|
BizProductionTaskSet.tenant_no == tenant_no,
|
|
BizProductionTaskSet.del_flag == 0,
|
|
)
|
|
.first()
|
|
)
|
|
if not row:
|
|
return None
|
|
items = (
|
|
db.query(BizProductionTaskSetItem)
|
|
.filter(
|
|
BizProductionTaskSetItem.set_id == set_id,
|
|
BizProductionTaskSetItem.tenant_no == tenant_no,
|
|
BizProductionTaskSetItem.del_flag == 0,
|
|
)
|
|
.order_by(
|
|
BizProductionTaskSetItem.order_num.asc(),
|
|
BizProductionTaskSetItem.create_time.asc(),
|
|
)
|
|
.all()
|
|
)
|
|
return _set_to_dict(row, item_count=len(items), items=[_item_to_dict(i) for i in items])
|
|
|
|
|
|
def is_super_admin_user(user: Optional[Dict[str, Any]]) -> bool:
|
|
return bool(user) and (user.get("role") or "") == "super_admin"
|
|
|
|
|
|
def assert_set_writable(
|
|
db: Session,
|
|
set_id: str,
|
|
tenant_no: str,
|
|
user: Optional[Dict[str, Any]] = None,
|
|
) -> BizProductionTaskSet:
|
|
"""内置模版仅超级管理员可写。"""
|
|
row = (
|
|
db.query(BizProductionTaskSet)
|
|
.filter(
|
|
BizProductionTaskSet.id == set_id,
|
|
BizProductionTaskSet.tenant_no == tenant_no,
|
|
BizProductionTaskSet.del_flag == 0,
|
|
)
|
|
.first()
|
|
)
|
|
if not row:
|
|
raise ValueError("集合不存在")
|
|
if (getattr(row, "is_builtin", None) or "0") == "1" and not is_super_admin_user(user):
|
|
raise ValueError("内置模版仅超级管理员可修改")
|
|
return row
|
|
|
|
|
|
def create_set(
|
|
db: Session,
|
|
*,
|
|
tenant_no: str,
|
|
user_id: Optional[str] = None,
|
|
create_dept_id: str = "",
|
|
set_name: str,
|
|
status: str = "1",
|
|
remark: Optional[str] = None,
|
|
items: Optional[List[Dict[str, Any]]] = None,
|
|
) -> Dict[str, Any]:
|
|
name = (set_name or "").strip()
|
|
if not name:
|
|
raise ValueError("集合名称不能为空")
|
|
if set_name_exists(db, name, tenant_no):
|
|
raise ValueError("集合名称已存在")
|
|
normalized = _normalize_items_or_raise(items or [])
|
|
route_names = _assert_routes_exist(db, tenant_no, normalized)
|
|
now = datetime.now()
|
|
obj = BizProductionTaskSet(
|
|
id=generate_uuid(),
|
|
set_name=name,
|
|
status=_require_status(status),
|
|
tenant_no=tenant_no,
|
|
order_num=0,
|
|
remark=(remark or "").strip() or None,
|
|
is_builtin="0",
|
|
create_by=user_id,
|
|
create_dept_id=create_dept_id or "",
|
|
create_time=now,
|
|
update_by=user_id,
|
|
update_time=now,
|
|
del_flag=0,
|
|
)
|
|
db.add(obj)
|
|
db.flush()
|
|
_replace_items(
|
|
db,
|
|
set_id=obj.id,
|
|
tenant_no=tenant_no,
|
|
items=normalized,
|
|
route_names=route_names,
|
|
user_id=user_id,
|
|
create_dept_id=create_dept_id,
|
|
)
|
|
db.flush()
|
|
detail = get_set(db, obj.id, tenant_no)
|
|
if not detail:
|
|
raise ValueError("集合创建失败")
|
|
return detail
|
|
|
|
|
|
def update_set(
|
|
db: Session,
|
|
set_id: str,
|
|
*,
|
|
tenant_no: str,
|
|
user_id: Optional[str] = None,
|
|
set_name: Optional[str] = None,
|
|
status: Optional[str] = None,
|
|
remark: Optional[str] = None,
|
|
items: Optional[List[Dict[str, Any]]] = None,
|
|
) -> Dict[str, Any]:
|
|
obj = (
|
|
db.query(BizProductionTaskSet)
|
|
.filter(
|
|
BizProductionTaskSet.id == set_id,
|
|
BizProductionTaskSet.tenant_no == tenant_no,
|
|
BizProductionTaskSet.del_flag == 0,
|
|
)
|
|
.first()
|
|
)
|
|
if not obj:
|
|
raise ValueError("集合不存在")
|
|
if set_name is not None:
|
|
name = set_name.strip()
|
|
if not name:
|
|
raise ValueError("集合名称不能为空")
|
|
if set_name_exists(db, name, tenant_no, exclude_id=set_id):
|
|
raise ValueError("集合名称已存在")
|
|
obj.set_name = name
|
|
if status is not None:
|
|
obj.status = _require_status(status)
|
|
if remark is not None:
|
|
obj.remark = remark.strip() or None
|
|
if items is not None:
|
|
normalized = _normalize_items_or_raise(items)
|
|
route_names = _assert_routes_exist(db, tenant_no, normalized)
|
|
_replace_items(
|
|
db,
|
|
set_id=obj.id,
|
|
tenant_no=tenant_no,
|
|
items=normalized,
|
|
route_names=route_names,
|
|
user_id=user_id,
|
|
create_dept_id=obj.create_dept_id or "",
|
|
)
|
|
obj.update_by = user_id
|
|
obj.update_time = datetime.now()
|
|
db.add(obj)
|
|
db.flush()
|
|
detail = get_set(db, obj.id, tenant_no)
|
|
if not detail:
|
|
raise ValueError("集合不存在")
|
|
return detail
|
|
|
|
|
|
def delete_set(db: Session, set_id: str, *, tenant_no: str, user_id: Optional[str] = None) -> None:
|
|
obj = (
|
|
db.query(BizProductionTaskSet)
|
|
.filter(
|
|
BizProductionTaskSet.id == set_id,
|
|
BizProductionTaskSet.tenant_no == tenant_no,
|
|
BizProductionTaskSet.del_flag == 0,
|
|
)
|
|
.first()
|
|
)
|
|
if not obj:
|
|
raise ValueError("集合不存在")
|
|
now = datetime.now()
|
|
obj.del_flag = 1
|
|
obj.update_by = user_id
|
|
obj.update_time = now
|
|
db.add(obj)
|
|
items = (
|
|
db.query(BizProductionTaskSetItem)
|
|
.filter(
|
|
BizProductionTaskSetItem.set_id == set_id,
|
|
BizProductionTaskSetItem.del_flag == 0,
|
|
)
|
|
.all()
|
|
)
|
|
for row in items:
|
|
row.del_flag = 1
|
|
row.update_by = user_id
|
|
row.update_time = now
|
|
db.add(row)
|
|
db.flush()
|
|
|
|
|
|
def create_set_from_work_order(
|
|
db: Session,
|
|
*,
|
|
tenant_no: str,
|
|
user_id: Optional[str] = None,
|
|
create_dept_id: str = "",
|
|
work_order_id: str,
|
|
set_name: str,
|
|
remark: Optional[str] = None,
|
|
items: Optional[List[Dict[str, Any]]] = None,
|
|
) -> Dict[str, Any]:
|
|
wo_id = (work_order_id or "").strip()
|
|
wo = (
|
|
db.query(BizProductionWorkOrder)
|
|
.filter(
|
|
BizProductionWorkOrder.id == wo_id,
|
|
BizProductionWorkOrder.tenant_no == tenant_no,
|
|
BizProductionWorkOrder.del_flag == 0,
|
|
)
|
|
.first()
|
|
)
|
|
if not wo:
|
|
raise ValueError("工单不存在")
|
|
if items is not None:
|
|
raw = items
|
|
else:
|
|
tasks = (
|
|
db.query(BizProductionTask)
|
|
.filter(
|
|
BizProductionTask.work_order_id == wo_id,
|
|
BizProductionTask.tenant_no == tenant_no,
|
|
BizProductionTask.del_flag == 0,
|
|
)
|
|
.order_by(BizProductionTask.create_time.asc(), BizProductionTask.id.asc())
|
|
.all()
|
|
)
|
|
raw = [
|
|
{
|
|
"task_name": t.task_name,
|
|
"route_id": t.route_id,
|
|
"description": t.description,
|
|
"remark": t.remark,
|
|
}
|
|
for t in tasks
|
|
]
|
|
kept, skipped = skip_empty_named_tasks(raw)
|
|
if not kept:
|
|
raise ValueError("无可保存的生产任务(任务名称均为空)")
|
|
lab_storage_code = resolve_work_order_lab_storage_code(
|
|
db, tenant_no=tenant_no, wo=wo
|
|
)
|
|
route_ids = [i["route_id"] for i in kept if i.get("route_id")]
|
|
name_map = _load_route_name_map(db, tenant_no, route_ids)
|
|
payload_items = []
|
|
for i in kept:
|
|
rid = i.get("route_id")
|
|
if rid and rid not in name_map:
|
|
rid = None
|
|
payload_items.append(
|
|
{
|
|
"taskName": revert_task_name_placeholders(
|
|
i["task_name"] or "",
|
|
lab_storage_code=lab_storage_code,
|
|
),
|
|
"routeId": rid,
|
|
"description": i.get("description"),
|
|
"remark": i.get("remark"),
|
|
}
|
|
)
|
|
detail = create_set(
|
|
db,
|
|
tenant_no=tenant_no,
|
|
user_id=user_id,
|
|
create_dept_id=create_dept_id,
|
|
set_name=set_name,
|
|
status="1",
|
|
remark=remark,
|
|
items=payload_items,
|
|
)
|
|
return {**detail, "skippedEmptyNameCount": skipped}
|
|
|
|
|
|
def create_tasks_from_set(
|
|
db: Session,
|
|
*,
|
|
tenant_no: str,
|
|
user_id: Optional[str] = None,
|
|
create_dept_id: str = "",
|
|
work_order_id: str,
|
|
set_id: str,
|
|
items: Optional[List[Dict[str, Any]]] = None,
|
|
) -> Dict[str, Any]:
|
|
from app.crud import production_task as production_task_svc
|
|
|
|
wo_id = (work_order_id or "").strip()
|
|
wo = (
|
|
db.query(BizProductionWorkOrder)
|
|
.filter(
|
|
BizProductionWorkOrder.id == wo_id,
|
|
BizProductionWorkOrder.tenant_no == tenant_no,
|
|
BizProductionWorkOrder.del_flag == 0,
|
|
)
|
|
.first()
|
|
)
|
|
if not wo:
|
|
raise ValueError("工单不存在")
|
|
status = getattr(wo, "work_order_status", None) or ""
|
|
if status not in ("dpz", "jxz"):
|
|
raise ValueError("仅待配置或进行中的工单可派发任务")
|
|
header = (
|
|
db.query(BizProductionTaskSet)
|
|
.filter(
|
|
BizProductionTaskSet.id == (set_id or "").strip(),
|
|
BizProductionTaskSet.tenant_no == tenant_no,
|
|
BizProductionTaskSet.del_flag == 0,
|
|
)
|
|
.first()
|
|
)
|
|
if not header:
|
|
raise ValueError("任务组合模版不存在")
|
|
if (header.status or "1") != "1":
|
|
raise ValueError("任务组合模版已停用")
|
|
if items is not None:
|
|
payloads = _normalize_items_or_raise(items)
|
|
else:
|
|
rows = (
|
|
db.query(BizProductionTaskSetItem)
|
|
.filter(
|
|
BizProductionTaskSetItem.set_id == header.id,
|
|
BizProductionTaskSetItem.tenant_no == tenant_no,
|
|
BizProductionTaskSetItem.del_flag == 0,
|
|
)
|
|
.order_by(
|
|
BizProductionTaskSetItem.order_num.asc(),
|
|
BizProductionTaskSetItem.create_time.asc(),
|
|
)
|
|
.all()
|
|
)
|
|
if not rows:
|
|
raise ValueError("任务组合模版没有可派发的任务")
|
|
payloads = [
|
|
{
|
|
"route_id": row.route_id,
|
|
"task_name": row.task_name,
|
|
"description": row.description,
|
|
"remark": row.remark,
|
|
}
|
|
for row in rows
|
|
]
|
|
name_map = _load_route_name_map(
|
|
db, tenant_no, [p["route_id"] for p in payloads if p.get("route_id")]
|
|
)
|
|
missing = collect_missing_route_ids(payloads, set(name_map.keys()))
|
|
if missing:
|
|
raise ValueError("工艺路线已删除,无法派发:" + "、".join(missing))
|
|
lab_storage_code = resolve_work_order_lab_storage_code(
|
|
db, tenant_no=tenant_no, wo=wo
|
|
)
|
|
created = []
|
|
for row in payloads:
|
|
resolved_name = apply_task_name_placeholders(
|
|
row["task_name"] or "",
|
|
lab_storage_code=lab_storage_code,
|
|
)
|
|
task = production_task_svc.create_production_task(
|
|
db,
|
|
tenant_no=tenant_no,
|
|
user_id=user_id,
|
|
create_dept_id=create_dept_id,
|
|
data={
|
|
"work_order_id": wo_id,
|
|
"task_name": resolved_name,
|
|
"route_id": row.get("route_id"),
|
|
"description": row.get("description"),
|
|
"remark": row.get("remark"),
|
|
},
|
|
)
|
|
created.append(production_task_svc._task_to_dict(task))
|
|
return {"createdCount": len(created), "list": created}
|
|
|
|
|
|
def try_auto_dispatch_builtin_tasks_after_reception(
|
|
db: Session,
|
|
*,
|
|
tenant_no: str,
|
|
user_id: Optional[str] = None,
|
|
create_dept_id: str = "",
|
|
work_order: BizProductionWorkOrder,
|
|
lab_storage_code: Optional[str] = None,
|
|
) -> Optional[Dict[str, Any]]:
|
|
"""样本接收建工单后按入库编码尝试自动派发;失败不抛错。"""
|
|
import logging
|
|
|
|
logger = logging.getLogger(__name__)
|
|
wo = work_order
|
|
if not wo or not getattr(wo, "id", None):
|
|
return None
|
|
existing = (
|
|
db.query(BizProductionTask.id)
|
|
.filter(
|
|
BizProductionTask.work_order_id == wo.id,
|
|
BizProductionTask.tenant_no == tenant_no,
|
|
BizProductionTask.del_flag == 0,
|
|
)
|
|
.first()
|
|
)
|
|
if existing:
|
|
logger.info(
|
|
"skip auto-dispatch: work_order %s already has tasks",
|
|
wo.id,
|
|
)
|
|
return None
|
|
code = (lab_storage_code or "").strip()
|
|
if not code:
|
|
code = resolve_work_order_lab_storage_code(db, tenant_no=tenant_no, wo=wo)
|
|
from app.utils.lab_storage_code_task_set import (
|
|
match_builtin_task_set_name_by_lab_storage_code,
|
|
)
|
|
|
|
set_name = match_builtin_task_set_name_by_lab_storage_code(code)
|
|
if not set_name:
|
|
logger.info(
|
|
"skip auto-dispatch: lab_storage_code=%r no builtin set rule",
|
|
code,
|
|
)
|
|
return None
|
|
header = (
|
|
db.query(BizProductionTaskSet)
|
|
.filter(
|
|
BizProductionTaskSet.tenant_no == tenant_no,
|
|
BizProductionTaskSet.set_name == set_name,
|
|
BizProductionTaskSet.is_builtin == "1",
|
|
BizProductionTaskSet.status == "1",
|
|
BizProductionTaskSet.del_flag == 0,
|
|
)
|
|
.first()
|
|
)
|
|
if not header:
|
|
logger.warning(
|
|
"skip auto-dispatch: builtin set %r not found/enabled for tenant %s",
|
|
set_name,
|
|
tenant_no,
|
|
)
|
|
return None
|
|
try:
|
|
return create_tasks_from_set(
|
|
db,
|
|
tenant_no=tenant_no,
|
|
user_id=user_id,
|
|
create_dept_id=create_dept_id or "",
|
|
work_order_id=wo.id,
|
|
set_id=header.id,
|
|
)
|
|
except Exception:
|
|
logger.exception(
|
|
"auto-dispatch failed: work_order=%s set=%s lab_storage_code=%r",
|
|
wo.id,
|
|
set_name,
|
|
code,
|
|
)
|
|
return None
|