934 lines
38 KiB
Python
934 lines
38 KiB
Python
"""生产任务 CRUD(biz_production_task + 步骤 + 子表 + 投入/产出)"""
|
||
from __future__ import annotations
|
||
|
||
import math
|
||
from datetime import datetime
|
||
from typing import Any, Dict, List, Optional, Tuple
|
||
|
||
from sqlalchemy import and_, func
|
||
from sqlalchemy.orm import Session
|
||
|
||
from app.core.utils import generate_uuid
|
||
from app.models.models import (
|
||
BizProcess,
|
||
BizProcessConsumable,
|
||
BizProcessDataCollection,
|
||
BizProcessOutput,
|
||
BizProcessReagent,
|
||
BizProcessRoute,
|
||
BizProcessRouteStep,
|
||
BizProcessRouteStepConsumable,
|
||
BizProcessRouteStepDataCollection,
|
||
BizProcessRouteStepOutput,
|
||
BizProcessRouteStepReagent,
|
||
BizProductionTask,
|
||
BizProductionTaskInput,
|
||
BizProductionTaskOutput,
|
||
BizProductionTaskStep,
|
||
BizProductionTaskStepConsumable,
|
||
BizProductionTaskStepDataCollection,
|
||
BizProductionTaskStepOutput,
|
||
BizProductionTaskStepReagent,
|
||
)
|
||
|
||
|
||
def _fmt_dt(t: Optional[datetime]) -> str:
|
||
return t.strftime("%Y-%m-%d %H:%M:%S") if t else ""
|
||
|
||
|
||
def _generate_task_no(db: Session, tenant_no: str) -> str:
|
||
day = datetime.now().strftime("%Y%m%d")
|
||
like = f"RW{day}%"
|
||
cnt = (
|
||
db.query(func.count(BizProductionTask.id))
|
||
.filter(
|
||
BizProductionTask.tenant_no == tenant_no,
|
||
BizProductionTask.task_no.like(like),
|
||
)
|
||
.scalar()
|
||
)
|
||
return f"RW{day}{(cnt or 0) + 1:04d}"
|
||
|
||
|
||
def _task_to_dict(t: BizProductionTask) -> Dict[str, Any]:
|
||
return {
|
||
"id": t.id,
|
||
"taskNo": t.task_no,
|
||
"workOrderId": t.work_order_id,
|
||
"taskName": t.task_name or "",
|
||
"description": t.description or "",
|
||
"routeId": t.route_id or "",
|
||
"taskStatus": t.task_status,
|
||
"startedTime": _fmt_dt(t.started_time),
|
||
"completedTime": _fmt_dt(t.completed_time),
|
||
"remark": t.remark or "",
|
||
"attachments": t.attachments or "",
|
||
"tenantNo": t.tenant_no,
|
||
"orderNum": t.order_num if t.order_num is not None else 0,
|
||
"createTime": _fmt_dt(t.create_time),
|
||
"updateTime": _fmt_dt(t.update_time),
|
||
}
|
||
|
||
|
||
def _step_to_dict(s: BizProductionTaskStep) -> Dict[str, Any]:
|
||
return {
|
||
"id": s.id,
|
||
"taskId": s.task_id,
|
||
"stepOrder": s.step_order if s.step_order is not None else 0,
|
||
"processCode": s.process_code or "",
|
||
"categoryId": s.category_id or "",
|
||
"processName": s.process_name or "",
|
||
"description": s.description or "",
|
||
"equipmentName": s.equipment_name or "",
|
||
"estimatedDuration": s.estimated_duration or "",
|
||
"actualDuration": s.actual_duration or "",
|
||
"sopIds": s.sop_ids or "",
|
||
"stepStatus": s.step_status or "dzx",
|
||
"startedTime": _fmt_dt(s.started_time),
|
||
"completedTime": _fmt_dt(s.completed_time),
|
||
"remark": s.remark or "",
|
||
}
|
||
|
||
|
||
def _step_reagent_to_dict(r: BizProductionTaskStepReagent) -> Dict[str, Any]:
|
||
return {
|
||
"id": r.id,
|
||
"stepId": r.step_id,
|
||
"reagentCode": r.reagent_code or "",
|
||
"reagentName": r.reagent_name or "",
|
||
"spec": r.spec or "",
|
||
"standardUsage": r.standard_usage or "",
|
||
"actualUsage": r.actual_usage or "",
|
||
"unit": r.unit or "",
|
||
"isKeyMaterial": r.is_key_material if r.is_key_material is not None else "0",
|
||
"remark": r.remark or "",
|
||
}
|
||
|
||
|
||
def _step_consumable_to_dict(c: BizProductionTaskStepConsumable) -> Dict[str, Any]:
|
||
return {
|
||
"id": c.id,
|
||
"stepId": c.step_id,
|
||
"consumableCode": c.consumable_code or "",
|
||
"consumableName": c.consumable_name or "",
|
||
"specModel": c.spec_model or "",
|
||
"standardUsage": c.standard_usage or "",
|
||
"actualUsage": c.actual_usage or "",
|
||
"unit": c.unit or "",
|
||
"isRequired": c.is_required if c.is_required is not None else "1",
|
||
"remark": c.remark or "",
|
||
}
|
||
|
||
|
||
def _step_output_to_dict(o: BizProductionTaskStepOutput) -> Dict[str, Any]:
|
||
return {
|
||
"id": o.id,
|
||
"stepId": o.step_id,
|
||
"outputTypeId": o.output_type_id or "",
|
||
"outputName": o.output_name or "",
|
||
"outputBarcode": o.output_barcode or "",
|
||
"remark": o.remark or "",
|
||
}
|
||
|
||
|
||
def _step_data_collection_to_dict(d: BizProductionTaskStepDataCollection) -> Dict[str, Any]:
|
||
return {
|
||
"id": d.id,
|
||
"stepId": d.step_id,
|
||
"collectionName": d.collection_name or "",
|
||
"unit": d.unit or "",
|
||
"collectionValue": d.collection_value or "",
|
||
"remark": d.remark or "",
|
||
}
|
||
|
||
|
||
def _task_input_to_dict(i: BizProductionTaskInput) -> Dict[str, Any]:
|
||
return {
|
||
"id": i.id,
|
||
"taskId": i.task_id,
|
||
"inputType": i.input_type or "",
|
||
"inputId": i.input_id or "",
|
||
"inputCode": i.input_code or "",
|
||
"inputName": i.input_name or "",
|
||
"remark": i.remark or "",
|
||
}
|
||
|
||
|
||
def _task_output_to_dict(o: BizProductionTaskOutput) -> Dict[str, Any]:
|
||
return {
|
||
"id": o.id,
|
||
"taskId": o.task_id,
|
||
"cellProductId": o.cell_product_id or "",
|
||
"storageSlotId": o.storage_slot_id or "",
|
||
"outputName": o.output_name or "",
|
||
"outputBarcode": o.output_barcode or "",
|
||
"quantity": float(o.quantity) if o.quantity is not None else None,
|
||
"unit": o.unit or "",
|
||
"remark": o.remark or "",
|
||
"attachments": o.attachments or "",
|
||
}
|
||
|
||
|
||
def _soft_delete_sub(db: Session, model_class, parent_field: str, parent_id: str, now: datetime) -> None:
|
||
rows = db.query(model_class).filter(
|
||
and_(
|
||
getattr(model_class, parent_field) == parent_id,
|
||
model_class.del_flag == 0,
|
||
)
|
||
).all()
|
||
for r in rows:
|
||
r.del_flag = 1
|
||
r.update_time = now
|
||
db.add(r)
|
||
|
||
|
||
def _clone_step_reagents(
|
||
db: Session, src_step_id: str, new_step_id: str,
|
||
tenant_no: str, user_id: Optional[str], now: datetime,
|
||
) -> None:
|
||
for r in db.query(BizProcessRouteStepReagent).filter(
|
||
and_(BizProcessRouteStepReagent.step_id == src_step_id, BizProcessRouteStepReagent.del_flag == 0)
|
||
).all():
|
||
db.add(BizProductionTaskStepReagent(
|
||
id=generate_uuid(), step_id=new_step_id, tenant_no=tenant_no,
|
||
reagent_code=r.reagent_code, reagent_name=r.reagent_name,
|
||
spec=r.spec, standard_usage=r.standard_usage,
|
||
unit=r.unit, is_key_material=r.is_key_material,
|
||
remark=r.remark,
|
||
order_num=0, create_by=user_id, create_dept_id="",
|
||
create_time=now, update_by=user_id, update_time=now, del_flag=0,
|
||
))
|
||
|
||
|
||
def _clone_step_consumables(
|
||
db: Session, src_step_id: str, new_step_id: str,
|
||
tenant_no: str, user_id: Optional[str], now: datetime,
|
||
) -> None:
|
||
for c in db.query(BizProcessRouteStepConsumable).filter(
|
||
and_(BizProcessRouteStepConsumable.step_id == src_step_id, BizProcessRouteStepConsumable.del_flag == 0)
|
||
).all():
|
||
db.add(BizProductionTaskStepConsumable(
|
||
id=generate_uuid(), step_id=new_step_id, tenant_no=tenant_no,
|
||
consumable_code=c.consumable_code, consumable_name=c.consumable_name,
|
||
spec_model=c.spec_model, standard_usage=c.standard_usage,
|
||
unit=c.unit, is_required=c.is_required,
|
||
remark=c.remark,
|
||
order_num=0, create_by=user_id, create_dept_id="",
|
||
create_time=now, update_by=user_id, update_time=now, del_flag=0,
|
||
))
|
||
|
||
|
||
def _clone_step_outputs(
|
||
db: Session, src_step_id: str, new_step_id: str,
|
||
tenant_no: str, user_id: Optional[str], now: datetime,
|
||
) -> None:
|
||
for o in db.query(BizProcessRouteStepOutput).filter(
|
||
and_(BizProcessRouteStepOutput.step_id == src_step_id, BizProcessRouteStepOutput.del_flag == 0)
|
||
).all():
|
||
db.add(BizProductionTaskStepOutput(
|
||
id=generate_uuid(), step_id=new_step_id, tenant_no=tenant_no,
|
||
output_type_id=o.output_type_id, output_name=o.output_name,
|
||
output_barcode=o.output_barcode,
|
||
remark=o.remark,
|
||
order_num=0, create_by=user_id, create_dept_id="",
|
||
create_time=now, update_by=user_id, update_time=now, del_flag=0,
|
||
))
|
||
|
||
|
||
def _clone_step_data_collections(
|
||
db: Session, src_step_id: str, new_step_id: str,
|
||
tenant_no: str, user_id: Optional[str], now: datetime,
|
||
) -> None:
|
||
for d in db.query(BizProcessRouteStepDataCollection).filter(
|
||
and_(BizProcessRouteStepDataCollection.step_id == src_step_id, BizProcessRouteStepDataCollection.del_flag == 0)
|
||
).all():
|
||
db.add(BizProductionTaskStepDataCollection(
|
||
id=generate_uuid(), step_id=new_step_id, tenant_no=tenant_no,
|
||
collection_name=d.collection_name, unit=d.unit,
|
||
remark=d.remark,
|
||
order_num=0, create_by=user_id, create_dept_id="",
|
||
create_time=now, update_by=user_id, update_time=now, del_flag=0,
|
||
))
|
||
|
||
|
||
def _clone_route_to_task(
|
||
db: Session, task_id: str, route_id: str,
|
||
tenant_no: str, user_id: Optional[str], create_dept_id: str,
|
||
) -> None:
|
||
route_steps = (
|
||
db.query(BizProcessRouteStep)
|
||
.filter(and_(BizProcessRouteStep.route_id == route_id, BizProcessRouteStep.del_flag == 0))
|
||
.order_by(BizProcessRouteStep.step_order.asc(), BizProcessRouteStep.create_time.asc())
|
||
.all()
|
||
)
|
||
now = datetime.now()
|
||
for rs in route_steps:
|
||
new_step = BizProductionTaskStep(
|
||
id=generate_uuid(), task_id=task_id,
|
||
step_order=rs.step_order,
|
||
process_code=rs.process_code, category_id=rs.category_id,
|
||
process_name=rs.process_name, description=rs.description,
|
||
equipment_name=rs.equipment_name, estimated_duration=rs.estimated_duration,
|
||
sop_ids=rs.sop_ids,
|
||
step_status="dzx",
|
||
tenant_no=tenant_no, order_num=0,
|
||
remark=None, 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(new_step)
|
||
db.flush()
|
||
|
||
_clone_step_reagents(db, rs.id, new_step.id, tenant_no, user_id, now)
|
||
_clone_step_consumables(db, rs.id, new_step.id, tenant_no, user_id, now)
|
||
_clone_step_outputs(db, rs.id, new_step.id, tenant_no, user_id, now)
|
||
_clone_step_data_collections(db, rs.id, new_step.id, tenant_no, user_id, now)
|
||
|
||
|
||
def create_production_task(
|
||
db: Session, *, tenant_no: str, user_id: Optional[str] = None,
|
||
create_dept_id: str = "", data: Dict[str, Any],
|
||
) -> BizProductionTask:
|
||
task_no = _generate_task_no(db, tenant_no)
|
||
now = datetime.now()
|
||
task = BizProductionTask(
|
||
id=generate_uuid(),
|
||
task_no=task_no,
|
||
work_order_id=data.get("work_order_id", ""),
|
||
task_name=(data.get("task_name") or "").strip() or None,
|
||
description=(data.get("description") or "").strip() or None,
|
||
route_id=data.get("route_id") or None,
|
||
task_status="dpz",
|
||
remark=(data.get("remark") or "").strip() or None,
|
||
attachments=(data.get("attachments") or "").strip() or None,
|
||
tenant_no=tenant_no,
|
||
order_num=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(task)
|
||
db.flush()
|
||
|
||
route_id = data.get("route_id")
|
||
if route_id:
|
||
_clone_route_to_task(db, task.id, route_id, tenant_no, user_id, create_dept_id)
|
||
|
||
db.flush()
|
||
return task
|
||
|
||
|
||
def list_production_tasks(
|
||
db: Session, *, tenant_no: str,
|
||
page: int = 1, page_size: int = 10,
|
||
work_order_id: Optional[str] = None,
|
||
task_no: Optional[str] = None,
|
||
task_status: Optional[str] = None,
|
||
user_info=None,
|
||
) -> Tuple[List[Dict[str, Any]], int]:
|
||
def _filtered():
|
||
q = db.query(BizProductionTask).filter(
|
||
BizProductionTask.tenant_no == tenant_no,
|
||
BizProductionTask.del_flag == 0,
|
||
)
|
||
if work_order_id and work_order_id.strip():
|
||
q = q.filter(BizProductionTask.work_order_id == work_order_id.strip())
|
||
if task_no and task_no.strip():
|
||
q = q.filter(BizProductionTask.task_no.like(f"%{task_no.strip()}%"))
|
||
if task_status and task_status.strip():
|
||
q = q.filter(BizProductionTask.task_status == task_status.strip())
|
||
if user_info:
|
||
from app.core.data_scope import apply_data_scope
|
||
q = apply_data_scope(q, BizProductionTask, user_info, db, tenant_no)
|
||
return q
|
||
|
||
total = _filtered().order_by(None).with_entities(func.count(BizProductionTask.id)).scalar() or 0
|
||
page = max(1, page)
|
||
page_size = min(max(1, page_size), 100)
|
||
rows = (
|
||
_filtered()
|
||
.order_by(BizProductionTask.create_time.desc())
|
||
.offset((page - 1) * page_size)
|
||
.limit(page_size)
|
||
.all()
|
||
)
|
||
items = [_task_to_dict(x) for x in rows]
|
||
# 批量补充路线名称
|
||
route_ids = [x.route_id for x in rows if x.route_id]
|
||
if route_ids:
|
||
route_map = {
|
||
r.id: r.route_name
|
||
for r in db.query(BizProcessRoute.id, BizProcessRoute.route_name)
|
||
.filter(BizProcessRoute.id.in_(route_ids))
|
||
.all()
|
||
}
|
||
for item, row in zip(items, rows):
|
||
item["routeName"] = route_map.get(row.route_id, "")
|
||
return items, int(total)
|
||
|
||
|
||
def get_production_task_detail(db: Session, task_id: str, tenant_no: str) -> Optional[Dict[str, Any]]:
|
||
task = db.query(BizProductionTask).filter(
|
||
and_(
|
||
BizProductionTask.id == task_id,
|
||
BizProductionTask.tenant_no == tenant_no,
|
||
BizProductionTask.del_flag == 0,
|
||
)
|
||
).first()
|
||
if not task:
|
||
return None
|
||
|
||
result = _task_to_dict(task)
|
||
|
||
steps = (
|
||
db.query(BizProductionTaskStep)
|
||
.filter(and_(BizProductionTaskStep.task_id == task_id, BizProductionTaskStep.del_flag == 0))
|
||
.order_by(BizProductionTaskStep.step_order.asc(), BizProductionTaskStep.create_time.asc())
|
||
.all()
|
||
)
|
||
step_list = []
|
||
for s in steps:
|
||
sd = _step_to_dict(s)
|
||
reagents = db.query(BizProductionTaskStepReagent).filter(
|
||
and_(BizProductionTaskStepReagent.step_id == s.id, BizProductionTaskStepReagent.del_flag == 0)
|
||
).order_by(BizProductionTaskStepReagent.order_num.asc(), BizProductionTaskStepReagent.create_time.asc()).all()
|
||
consumables = db.query(BizProductionTaskStepConsumable).filter(
|
||
and_(BizProductionTaskStepConsumable.step_id == s.id, BizProductionTaskStepConsumable.del_flag == 0)
|
||
).order_by(BizProductionTaskStepConsumable.order_num.asc(), BizProductionTaskStepConsumable.create_time.asc()).all()
|
||
outputs = db.query(BizProductionTaskStepOutput).filter(
|
||
and_(BizProductionTaskStepOutput.step_id == s.id, BizProductionTaskStepOutput.del_flag == 0)
|
||
).order_by(BizProductionTaskStepOutput.order_num.asc(), BizProductionTaskStepOutput.create_time.asc()).all()
|
||
data_collections = db.query(BizProductionTaskStepDataCollection).filter(
|
||
and_(BizProductionTaskStepDataCollection.step_id == s.id, BizProductionTaskStepDataCollection.del_flag == 0)
|
||
).order_by(BizProductionTaskStepDataCollection.order_num.asc(), BizProductionTaskStepDataCollection.create_time.asc()).all()
|
||
|
||
sd["reagents"] = [_step_reagent_to_dict(r) for r in reagents]
|
||
sd["consumables"] = [_step_consumable_to_dict(c) for c in consumables]
|
||
sd["outputs"] = [_step_output_to_dict(o) for o in outputs]
|
||
sd["dataCollections"] = [_step_data_collection_to_dict(d) for d in data_collections]
|
||
step_list.append(sd)
|
||
|
||
inputs = db.query(BizProductionTaskInput).filter(
|
||
and_(BizProductionTaskInput.task_id == task_id, BizProductionTaskInput.del_flag == 0)
|
||
).order_by(BizProductionTaskInput.create_time.asc()).all()
|
||
|
||
outputs = db.query(BizProductionTaskOutput).filter(
|
||
and_(BizProductionTaskOutput.task_id == task_id, BizProductionTaskOutput.del_flag == 0)
|
||
).order_by(BizProductionTaskOutput.create_time.asc()).all()
|
||
|
||
result["steps"] = step_list
|
||
result["inputs"] = [_task_input_to_dict(i) for i in inputs]
|
||
result["outputs"] = [_task_output_to_dict(o) for o in outputs]
|
||
return result
|
||
|
||
|
||
def update_production_task(
|
||
db: Session, task_id: str, tenant_no: str,
|
||
user_id: Optional[str], data: Dict[str, Any],
|
||
) -> BizProductionTask:
|
||
task = db.query(BizProductionTask).filter(
|
||
and_(
|
||
BizProductionTask.id == task_id,
|
||
BizProductionTask.tenant_no == tenant_no,
|
||
BizProductionTask.del_flag == 0,
|
||
)
|
||
).first()
|
||
if not task:
|
||
raise ValueError("任务不存在")
|
||
for field in ["task_name", "description", "remark", "attachments"]:
|
||
val = data.get(field)
|
||
if val is not None:
|
||
setattr(task, field, (val or "").strip() or None)
|
||
task.update_by = user_id
|
||
task.update_time = datetime.now()
|
||
db.add(task)
|
||
db.commit()
|
||
db.refresh(task)
|
||
return task
|
||
|
||
|
||
def update_task_status(
|
||
db: Session, task_id: str, tenant_no: str,
|
||
user_id: Optional[str], status: str,
|
||
) -> BizProductionTask:
|
||
task = db.query(BizProductionTask).filter(
|
||
and_(
|
||
BizProductionTask.id == task_id,
|
||
BizProductionTask.tenant_no == tenant_no,
|
||
BizProductionTask.del_flag == 0,
|
||
)
|
||
).first()
|
||
if not task:
|
||
raise ValueError("任务不存在")
|
||
now = datetime.now()
|
||
task.task_status = status
|
||
if status == "jxz" and not task.started_time:
|
||
task.started_time = now
|
||
if status == "ywc" and not task.completed_time:
|
||
task.completed_time = now
|
||
task.update_by = user_id
|
||
task.update_time = now
|
||
db.add(task)
|
||
db.commit()
|
||
db.refresh(task)
|
||
return task
|
||
|
||
|
||
def add_task_step(
|
||
db: Session, task_id: str, tenant_no: str,
|
||
user_id: Optional[str], data: Dict[str, Any],
|
||
) -> Dict[str, Any]:
|
||
task = db.query(BizProductionTask).filter(
|
||
and_(
|
||
BizProductionTask.id == task_id,
|
||
BizProductionTask.tenant_no == tenant_no,
|
||
BizProductionTask.del_flag == 0,
|
||
)
|
||
).first()
|
||
if not task:
|
||
raise ValueError("任务不存在")
|
||
|
||
now = datetime.now()
|
||
step_order = data.get("step_order")
|
||
if step_order is None:
|
||
max_order = db.query(func.max(BizProductionTaskStep.step_order)).filter(
|
||
and_(BizProductionTaskStep.task_id == task_id, BizProductionTaskStep.del_flag == 0)
|
||
).scalar()
|
||
step_order = (max_order or 0) + 1
|
||
|
||
# 如果传入了 process_id,从工艺主表深拷贝基本信息和子表数据
|
||
process_id = data.get("process_id")
|
||
if process_id:
|
||
proc = db.query(BizProcess).filter(
|
||
and_(BizProcess.id == process_id, BizProcess.del_flag == 0)
|
||
).first()
|
||
if not proc:
|
||
raise ValueError("工艺不存在")
|
||
|
||
step = BizProductionTaskStep(
|
||
id=generate_uuid(), task_id=task_id,
|
||
step_order=step_order,
|
||
process_code=proc.process_code,
|
||
category_id=proc.category_id,
|
||
process_name=proc.process_name,
|
||
description=proc.description,
|
||
equipment_name=proc.equipment_name,
|
||
estimated_duration=proc.estimated_duration,
|
||
sop_ids=proc.sop_ids,
|
||
step_status="dzx",
|
||
tenant_no=tenant_no, order_num=0,
|
||
remark=None,
|
||
create_by=user_id, create_dept_id="",
|
||
create_time=now, update_by=user_id, update_time=now, del_flag=0,
|
||
)
|
||
db.add(step)
|
||
db.flush()
|
||
|
||
# 深拷贝工艺子表 → 任务步骤子表
|
||
for r in db.query(BizProcessReagent).filter(
|
||
and_(BizProcessReagent.process_id == process_id, BizProcessReagent.del_flag == 0)
|
||
).all():
|
||
db.add(BizProductionTaskStepReagent(
|
||
id=generate_uuid(), step_id=step.id, tenant_no=tenant_no,
|
||
reagent_code=r.reagent_code, reagent_name=r.reagent_name,
|
||
spec=r.spec, standard_usage=r.standard_usage,
|
||
unit=r.unit, is_key_material=r.is_key_material,
|
||
remark=r.remark,
|
||
order_num=0, create_by=user_id, create_dept_id="",
|
||
create_time=now, update_by=user_id, update_time=now, del_flag=0,
|
||
))
|
||
|
||
for c in db.query(BizProcessConsumable).filter(
|
||
and_(BizProcessConsumable.process_id == process_id, BizProcessConsumable.del_flag == 0)
|
||
).all():
|
||
db.add(BizProductionTaskStepConsumable(
|
||
id=generate_uuid(), step_id=step.id, tenant_no=tenant_no,
|
||
consumable_code=c.consumable_code, consumable_name=c.consumable_name,
|
||
spec_model=c.spec_model, standard_usage=c.standard_usage,
|
||
unit=c.unit, is_required=c.is_required,
|
||
remark=c.remark,
|
||
order_num=0, create_by=user_id, create_dept_id="",
|
||
create_time=now, update_by=user_id, update_time=now, del_flag=0,
|
||
))
|
||
|
||
for o in db.query(BizProcessOutput).filter(
|
||
and_(BizProcessOutput.process_id == process_id, BizProcessOutput.del_flag == 0)
|
||
).all():
|
||
db.add(BizProductionTaskStepOutput(
|
||
id=generate_uuid(), step_id=step.id, tenant_no=tenant_no,
|
||
output_type_id=o.output_type_id, output_name=o.output_name,
|
||
output_barcode=o.output_barcode,
|
||
remark=o.remark,
|
||
order_num=0, create_by=user_id, create_dept_id="",
|
||
create_time=now, update_by=user_id, update_time=now, del_flag=0,
|
||
))
|
||
|
||
for d in db.query(BizProcessDataCollection).filter(
|
||
and_(BizProcessDataCollection.process_id == process_id, BizProcessDataCollection.del_flag == 0)
|
||
).all():
|
||
db.add(BizProductionTaskStepDataCollection(
|
||
id=generate_uuid(), step_id=step.id, tenant_no=tenant_no,
|
||
collection_name=d.collection_name, unit=d.unit,
|
||
remark=d.remark,
|
||
order_num=0, create_by=user_id, create_dept_id="",
|
||
create_time=now, update_by=user_id, update_time=now, del_flag=0,
|
||
))
|
||
|
||
db.commit()
|
||
else:
|
||
# 无 process_id 时,使用前端传来的字段直接创建(兼容旧逻辑)
|
||
step = BizProductionTaskStep(
|
||
id=generate_uuid(), task_id=task_id,
|
||
step_order=step_order,
|
||
process_code=(data.get("process_code") or "").strip() or None,
|
||
category_id=(data.get("category_id") or "").strip() or None,
|
||
process_name=(data.get("process_name") or "").strip() or None,
|
||
description=(data.get("description") or "").strip() or None,
|
||
equipment_name=(data.get("equipment_name") or "").strip() or None,
|
||
estimated_duration=(data.get("estimated_duration") or "").strip() or None,
|
||
actual_duration=(data.get("actual_duration") or "").strip() or None,
|
||
sop_ids=(data.get("sop_ids") or "").strip() or None,
|
||
step_status=(data.get("step_status") or "dzx"),
|
||
tenant_no=tenant_no, order_num=0,
|
||
remark=(data.get("remark") or "").strip() or None,
|
||
create_by=user_id, create_dept_id="",
|
||
create_time=now, update_by=user_id, update_time=now, del_flag=0,
|
||
)
|
||
db.add(step)
|
||
db.commit()
|
||
|
||
# 返回带子表的完整步骤信息(与 get_production_task_detail 格式一致)
|
||
result = _step_to_dict(step)
|
||
reagents = db.query(BizProductionTaskStepReagent).filter(
|
||
and_(BizProductionTaskStepReagent.step_id == step.id, BizProductionTaskStepReagent.del_flag == 0)
|
||
).order_by(BizProductionTaskStepReagent.order_num.asc(), BizProductionTaskStepReagent.create_time.asc()).all()
|
||
consumables = db.query(BizProductionTaskStepConsumable).filter(
|
||
and_(BizProductionTaskStepConsumable.step_id == step.id, BizProductionTaskStepConsumable.del_flag == 0)
|
||
).order_by(BizProductionTaskStepConsumable.order_num.asc(), BizProductionTaskStepConsumable.create_time.asc()).all()
|
||
outputs = db.query(BizProductionTaskStepOutput).filter(
|
||
and_(BizProductionTaskStepOutput.step_id == step.id, BizProductionTaskStepOutput.del_flag == 0)
|
||
).order_by(BizProductionTaskStepOutput.order_num.asc(), BizProductionTaskStepOutput.create_time.asc()).all()
|
||
data_collections = db.query(BizProductionTaskStepDataCollection).filter(
|
||
and_(BizProductionTaskStepDataCollection.step_id == step.id, BizProductionTaskStepDataCollection.del_flag == 0)
|
||
).order_by(BizProductionTaskStepDataCollection.order_num.asc(), BizProductionTaskStepDataCollection.create_time.asc()).all()
|
||
|
||
result["reagents"] = [_step_reagent_to_dict(r) for r in reagents]
|
||
result["consumables"] = [_step_consumable_to_dict(c) for c in consumables]
|
||
result["outputs"] = [_step_output_to_dict(o) for o in outputs]
|
||
result["dataCollections"] = [_step_data_collection_to_dict(d) for d in data_collections]
|
||
return result
|
||
|
||
|
||
def update_task_step(
|
||
db: Session, step_id: str, tenant_no: str,
|
||
user_id: Optional[str], data: Dict[str, Any],
|
||
) -> BizProductionTaskStep:
|
||
step = db.query(BizProductionTaskStep).filter(
|
||
and_(
|
||
BizProductionTaskStep.id == step_id,
|
||
BizProductionTaskStep.tenant_no == tenant_no,
|
||
BizProductionTaskStep.del_flag == 0,
|
||
)
|
||
).first()
|
||
if not step:
|
||
raise ValueError("步骤不存在")
|
||
for field in [
|
||
"step_order", "process_code", "category_id", "process_name",
|
||
"description", "equipment_name", "estimated_duration", "actual_duration",
|
||
"sop_ids", "step_status", "remark",
|
||
]:
|
||
val = data.get(field)
|
||
if val is not None:
|
||
if field == "step_order":
|
||
setattr(step, field, int(val))
|
||
else:
|
||
setattr(step, field, (val or "").strip() or None)
|
||
if data.get("started_time"):
|
||
from dateutil.parser import parse as _parse_dt
|
||
step.started_time = _parse_dt(data["started_time"])
|
||
if data.get("completed_time"):
|
||
from dateutil.parser import parse as _parse_dt
|
||
step.completed_time = _parse_dt(data["completed_time"])
|
||
step.update_by = user_id
|
||
step.update_time = datetime.now()
|
||
db.add(step)
|
||
db.commit()
|
||
db.refresh(step)
|
||
return step
|
||
|
||
|
||
def delete_task_step(db: Session, step_id: str, tenant_no: str, user_id: Optional[str]) -> None:
|
||
step = db.query(BizProductionTaskStep).filter(
|
||
and_(
|
||
BizProductionTaskStep.id == step_id,
|
||
BizProductionTaskStep.tenant_no == tenant_no,
|
||
BizProductionTaskStep.del_flag == 0,
|
||
)
|
||
).first()
|
||
if not step:
|
||
raise ValueError("步骤不存在")
|
||
now = datetime.now()
|
||
step.del_flag = 1
|
||
step.update_by = user_id
|
||
step.update_time = now
|
||
db.add(step)
|
||
_soft_delete_sub(db, BizProductionTaskStepReagent, "step_id", step_id, now)
|
||
_soft_delete_sub(db, BizProductionTaskStepConsumable, "step_id", step_id, now)
|
||
_soft_delete_sub(db, BizProductionTaskStepOutput, "step_id", step_id, now)
|
||
_soft_delete_sub(db, BizProductionTaskStepDataCollection, "step_id", step_id, now)
|
||
db.commit()
|
||
|
||
|
||
def replace_step_reagents(
|
||
db: Session, step_id: str, tenant_no: str,
|
||
user_id: Optional[str], items: List[Dict[str, Any]],
|
||
) -> None:
|
||
now = datetime.now()
|
||
_soft_delete_sub(db, BizProductionTaskStepReagent, "step_id", step_id, now)
|
||
for idx, it in enumerate(items):
|
||
db.add(BizProductionTaskStepReagent(
|
||
id=generate_uuid(), step_id=step_id, tenant_no=tenant_no,
|
||
reagent_code=(it.get("reagent_code") or "").strip() or None,
|
||
reagent_name=(it.get("reagent_name") or "").strip() or None,
|
||
spec=(it.get("spec") or "").strip() or None,
|
||
standard_usage=(it.get("standard_usage") or "").strip() or None,
|
||
actual_usage=(it.get("actual_usage") or "").strip() or None,
|
||
unit=(it.get("unit") or "").strip() or None,
|
||
is_key_material=it.get("is_key_material", "0"),
|
||
remark=(it.get("remark") or "").strip() or None,
|
||
order_num=idx, create_by=user_id, create_dept_id="",
|
||
create_time=now, update_by=user_id, update_time=now, del_flag=0,
|
||
))
|
||
db.commit()
|
||
|
||
|
||
def replace_step_consumables(
|
||
db: Session, step_id: str, tenant_no: str,
|
||
user_id: Optional[str], items: List[Dict[str, Any]],
|
||
) -> None:
|
||
now = datetime.now()
|
||
_soft_delete_sub(db, BizProductionTaskStepConsumable, "step_id", step_id, now)
|
||
for idx, it in enumerate(items):
|
||
db.add(BizProductionTaskStepConsumable(
|
||
id=generate_uuid(), step_id=step_id, tenant_no=tenant_no,
|
||
consumable_code=(it.get("consumable_code") or "").strip() or None,
|
||
consumable_name=(it.get("consumable_name") or "").strip() or None,
|
||
spec_model=(it.get("spec_model") or "").strip() or None,
|
||
standard_usage=(it.get("standard_usage") or "").strip() or None,
|
||
actual_usage=(it.get("actual_usage") or "").strip() or None,
|
||
unit=(it.get("unit") or "").strip() or None,
|
||
is_required=it.get("is_required", "1"),
|
||
remark=(it.get("remark") or "").strip() or None,
|
||
order_num=idx, create_by=user_id, create_dept_id="",
|
||
create_time=now, update_by=user_id, update_time=now, del_flag=0,
|
||
))
|
||
db.commit()
|
||
|
||
|
||
def replace_step_outputs(
|
||
db: Session, step_id: str, tenant_no: str,
|
||
user_id: Optional[str], items: List[Dict[str, Any]],
|
||
) -> None:
|
||
now = datetime.now()
|
||
_soft_delete_sub(db, BizProductionTaskStepOutput, "step_id", step_id, now)
|
||
for idx, it in enumerate(items):
|
||
db.add(BizProductionTaskStepOutput(
|
||
id=generate_uuid(), step_id=step_id, tenant_no=tenant_no,
|
||
output_type_id=(it.get("output_type_id") or "").strip() or None,
|
||
output_name=(it.get("output_name") or "").strip() or None,
|
||
output_barcode=(it.get("output_barcode") or "").strip() or None,
|
||
remark=(it.get("remark") or "").strip() or None,
|
||
order_num=idx, create_by=user_id, create_dept_id="",
|
||
create_time=now, update_by=user_id, update_time=now, del_flag=0,
|
||
))
|
||
db.commit()
|
||
|
||
|
||
def replace_step_data_collections(
|
||
db: Session, step_id: str, tenant_no: str,
|
||
user_id: Optional[str], items: List[Dict[str, Any]],
|
||
) -> None:
|
||
now = datetime.now()
|
||
_soft_delete_sub(db, BizProductionTaskStepDataCollection, "step_id", step_id, now)
|
||
for idx, it in enumerate(items):
|
||
db.add(BizProductionTaskStepDataCollection(
|
||
id=generate_uuid(), step_id=step_id, tenant_no=tenant_no,
|
||
collection_name=(it.get("collection_name") or "").strip() or None,
|
||
unit=(it.get("unit") or "").strip() or None,
|
||
collection_value=(it.get("collection_value") or "").strip() or None,
|
||
remark=(it.get("remark") or "").strip() or None,
|
||
order_num=idx, create_by=user_id, create_dept_id="",
|
||
create_time=now, update_by=user_id, update_time=now, del_flag=0,
|
||
))
|
||
db.commit()
|
||
|
||
|
||
def set_task_inputs(
|
||
db: Session, task_id: str, tenant_no: str,
|
||
user_id: Optional[str], items: List[Dict[str, Any]],
|
||
) -> None:
|
||
now = datetime.now()
|
||
_soft_delete_sub(db, BizProductionTaskInput, "task_id", task_id, now)
|
||
for it in items:
|
||
db.add(BizProductionTaskInput(
|
||
id=generate_uuid(), task_id=task_id, tenant_no=tenant_no,
|
||
input_type=(it.get("input_type") or "").strip() or None,
|
||
input_id=(it.get("input_id") or "").strip() or None,
|
||
input_code=(it.get("input_code") or "").strip() or None,
|
||
input_name=(it.get("input_name") or "").strip() or None,
|
||
remark=(it.get("remark") or "").strip() or None,
|
||
order_num=0, create_by=user_id, create_dept_id="",
|
||
create_time=now, update_by=user_id, update_time=now, del_flag=0,
|
||
))
|
||
db.commit()
|
||
|
||
|
||
def set_task_outputs(
|
||
db: Session, task_id: str, tenant_no: str,
|
||
user_id: Optional[str], items: List[Dict[str, Any]],
|
||
) -> None:
|
||
now = datetime.now()
|
||
_soft_delete_sub(db, BizProductionTaskOutput, "task_id", task_id, now)
|
||
for it in items:
|
||
quantity = it.get("quantity")
|
||
db.add(BizProductionTaskOutput(
|
||
id=generate_uuid(), task_id=task_id, tenant_no=tenant_no,
|
||
cell_product_id=(it.get("cell_product_id") or "").strip() or None,
|
||
storage_slot_id=(it.get("storage_slot_id") or "").strip() or None,
|
||
output_name=(it.get("output_name") or "").strip() or None,
|
||
output_barcode=(it.get("output_barcode") or "").strip() or None,
|
||
quantity=quantity if quantity is not None else None,
|
||
unit=(it.get("unit") or "").strip() or None,
|
||
remark=(it.get("remark") or "").strip() or None,
|
||
attachments=(it.get("attachments") or "").strip() or None,
|
||
order_num=0, create_by=user_id, create_dept_id="",
|
||
create_time=now, update_by=user_id, update_time=now, del_flag=0,
|
||
))
|
||
db.commit()
|
||
|
||
|
||
def reorder_task_steps(db: Session, task_id: str, step_ids: List[str]) -> None:
|
||
now = datetime.now()
|
||
for idx, sid in enumerate(step_ids):
|
||
step = db.query(BizProductionTaskStep).filter(
|
||
and_(BizProductionTaskStep.id == sid, BizProductionTaskStep.task_id == task_id,
|
||
BizProductionTaskStep.del_flag == 0)
|
||
).first()
|
||
if step:
|
||
step.step_order = idx + 1
|
||
step.update_time = now
|
||
db.add(step)
|
||
db.commit()
|
||
|
||
|
||
def copy_task_step(db: Session, step_id: str, *, tenant_no: str = "zkxw",
|
||
user_id: Optional[str] = None) -> Dict[str, Any]:
|
||
"""复制生产任务步骤,插入到原步骤之后,名称追加(复制)"""
|
||
src = db.query(BizProductionTaskStep).filter(
|
||
and_(BizProductionTaskStep.id == step_id, BizProductionTaskStep.del_flag == 0)
|
||
).first()
|
||
if not src:
|
||
raise ValueError("步骤不存在")
|
||
|
||
now = datetime.now()
|
||
new_order = src.step_order + 1
|
||
|
||
# 后续步骤 step_order +1
|
||
later_steps = db.query(BizProductionTaskStep).filter(
|
||
and_(
|
||
BizProductionTaskStep.task_id == src.task_id,
|
||
BizProductionTaskStep.del_flag == 0,
|
||
BizProductionTaskStep.step_order > src.step_order,
|
||
)
|
||
).order_by(BizProductionTaskStep.step_order.asc()).all()
|
||
for ls in later_steps:
|
||
ls.step_order += 1
|
||
ls.update_time = now
|
||
db.add(ls)
|
||
|
||
# 创建新步骤
|
||
new_name = (src.process_name or "").strip()
|
||
if not new_name.endswith("(复制)"):
|
||
new_name += "(复制)"
|
||
new_step = BizProductionTaskStep(
|
||
id=generate_uuid(), task_id=src.task_id,
|
||
step_order=new_order,
|
||
process_code=src.process_code, category_id=src.category_id,
|
||
process_name=new_name, description=src.description,
|
||
equipment_name=src.equipment_name, estimated_duration=src.estimated_duration,
|
||
actual_duration=src.actual_duration, sop_ids=src.sop_ids,
|
||
step_status="dzx",
|
||
tenant_no=tenant_no, order_num=0,
|
||
remark=src.remark, create_by=user_id, create_dept_id="",
|
||
create_time=now, update_by=user_id, update_time=now, del_flag=0,
|
||
)
|
||
db.add(new_step)
|
||
db.flush()
|
||
|
||
# 复制子表:试剂(从生产任务步骤试剂表复制)
|
||
for r in db.query(BizProductionTaskStepReagent).filter(
|
||
and_(BizProductionTaskStepReagent.step_id == step_id, BizProductionTaskStepReagent.del_flag == 0)
|
||
).all():
|
||
db.add(BizProductionTaskStepReagent(
|
||
id=generate_uuid(), step_id=new_step.id, tenant_no=tenant_no,
|
||
reagent_code=r.reagent_code, reagent_name=r.reagent_name,
|
||
spec=r.spec, standard_usage=r.standard_usage,
|
||
actual_usage=r.actual_usage,
|
||
unit=r.unit, is_key_material=r.is_key_material,
|
||
remark=r.remark,
|
||
order_num=0, create_by=user_id, create_dept_id="",
|
||
create_time=now, update_by=user_id, update_time=now, del_flag=0,
|
||
))
|
||
|
||
# 复制子表:耗材
|
||
for c in db.query(BizProductionTaskStepConsumable).filter(
|
||
and_(BizProductionTaskStepConsumable.step_id == step_id, BizProductionTaskStepConsumable.del_flag == 0)
|
||
).all():
|
||
db.add(BizProductionTaskStepConsumable(
|
||
id=generate_uuid(), step_id=new_step.id, tenant_no=tenant_no,
|
||
consumable_code=c.consumable_code, consumable_name=c.consumable_name,
|
||
spec_model=c.spec_model, standard_usage=c.standard_usage,
|
||
actual_usage=c.actual_usage,
|
||
unit=c.unit, is_required=c.is_required,
|
||
remark=c.remark,
|
||
order_num=0, create_by=user_id, create_dept_id="",
|
||
create_time=now, update_by=user_id, update_time=now, del_flag=0,
|
||
))
|
||
|
||
# 复制子表:产出
|
||
for o in db.query(BizProductionTaskStepOutput).filter(
|
||
and_(BizProductionTaskStepOutput.step_id == step_id, BizProductionTaskStepOutput.del_flag == 0)
|
||
).all():
|
||
db.add(BizProductionTaskStepOutput(
|
||
id=generate_uuid(), step_id=new_step.id, tenant_no=tenant_no,
|
||
output_type_id=o.output_type_id, output_name=o.output_name,
|
||
output_barcode=o.output_barcode,
|
||
remark=o.remark,
|
||
order_num=0, create_by=user_id, create_dept_id="",
|
||
create_time=now, update_by=user_id, update_time=now, del_flag=0,
|
||
))
|
||
|
||
# 复制子表:数据采集
|
||
for d in db.query(BizProductionTaskStepDataCollection).filter(
|
||
and_(BizProductionTaskStepDataCollection.step_id == step_id, BizProductionTaskStepDataCollection.del_flag == 0)
|
||
).all():
|
||
db.add(BizProductionTaskStepDataCollection(
|
||
id=generate_uuid(), step_id=new_step.id, tenant_no=tenant_no,
|
||
collection_name=d.collection_name, unit=d.unit,
|
||
collection_value=d.collection_value,
|
||
remark=d.remark,
|
||
order_num=0, create_by=user_id, create_dept_id="",
|
||
create_time=now, update_by=user_id, update_time=now, del_flag=0,
|
||
))
|
||
|
||
db.commit()
|
||
return _step_to_dict(new_step)
|
||
|
||
|
||
def total_pages(total: int, page_size: int) -> int:
|
||
if total <= 0:
|
||
return 0
|
||
return int(math.ceil(total / float(page_size)))
|