样本接收 UC… 此前无内置匹配规则导致工单空任务;新增 5 步原代培养模版与回补脚本,且不影响 BC 六步模版。 Co-authored-by: Cursor <cursoragent@cursor.com>
473 lines
14 KiB
Python
473 lines
14 KiB
Python
"""确保 UC 入库编码对应的 5 步内置工艺路线 / 任务组合存在。
|
|
|
|
步骤顺序(业务确认):
|
|
1. 脐带MSC原代细胞培养
|
|
2. 脐带MSC原代细胞培养半换液
|
|
3. 脐带MSC原代二次贴壁
|
|
4. 脐带MSC原代重铺操作
|
|
5. 干细胞冻存
|
|
|
|
内容从既有「干细胞培养(内置模版)」按工序代码拷贝,排除传代。
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
from typing import Any, Dict, List, Optional
|
|
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.models.models import (
|
|
BizProcessRoute,
|
|
BizProcessRouteStep,
|
|
BizProcessRouteStepConsumable,
|
|
BizProcessRouteStepDataCollection,
|
|
BizProcessRouteStepOutput,
|
|
BizProcessRouteStepReagent,
|
|
BizProductionTaskSet,
|
|
BizProductionTaskSetItem,
|
|
)
|
|
from app.core.utils import generate_uuid
|
|
from app.utils.lab_storage_code_task_set import (
|
|
BC_MSC_BUILTIN_SET_NAME,
|
|
UC_MSC_BUILTIN_SET_NAME,
|
|
)
|
|
|
|
UC_MSC_ROUTE_CODE = "MSC原代-UC-BUILTIN"
|
|
UC_MSC_ROUTE_NAME = UC_MSC_BUILTIN_SET_NAME
|
|
SOURCE_MSC_ROUTE_CODE = "MSC培养-BUILTIN"
|
|
SOURCE_MSC_ROUTE_NAME = BC_MSC_BUILTIN_SET_NAME
|
|
|
|
# process_code → 目标 step_order
|
|
UC_MSC_STEP_ORDER: List[str] = [
|
|
"SOP-SC-003-E01-01", # 原代细胞培养
|
|
"SOP-SC-004-E01-01", # 半换液
|
|
"SOP-SC-006-E01-01", # 二次贴壁
|
|
"SOP-SC-005-E01-01", # 重铺
|
|
"SOP-SC-008-E01-01", # 冻存
|
|
]
|
|
|
|
|
|
def _now() -> datetime:
|
|
return datetime.now()
|
|
|
|
|
|
def get_uc_msc_builtin_set(
|
|
db: Session, *, tenant_no: str
|
|
) -> Optional[BizProductionTaskSet]:
|
|
return (
|
|
db.query(BizProductionTaskSet)
|
|
.filter(
|
|
BizProductionTaskSet.tenant_no == tenant_no,
|
|
BizProductionTaskSet.set_name == UC_MSC_BUILTIN_SET_NAME,
|
|
BizProductionTaskSet.is_builtin == "1",
|
|
BizProductionTaskSet.del_flag == 0,
|
|
)
|
|
.first()
|
|
)
|
|
|
|
|
|
def _find_source_route(db: Session, *, tenant_no: str) -> BizProcessRoute:
|
|
route = (
|
|
db.query(BizProcessRoute)
|
|
.filter(
|
|
BizProcessRoute.tenant_no == tenant_no,
|
|
BizProcessRoute.route_code == SOURCE_MSC_ROUTE_CODE,
|
|
BizProcessRoute.del_flag == 0,
|
|
)
|
|
.first()
|
|
)
|
|
if route:
|
|
return route
|
|
route = (
|
|
db.query(BizProcessRoute)
|
|
.filter(
|
|
BizProcessRoute.tenant_no == tenant_no,
|
|
BizProcessRoute.route_name == SOURCE_MSC_ROUTE_NAME,
|
|
BizProcessRoute.is_builtin == "1",
|
|
BizProcessRoute.del_flag == 0,
|
|
)
|
|
.first()
|
|
)
|
|
if not route:
|
|
raise ValueError(
|
|
f"缺少源工艺路线 {SOURCE_MSC_ROUTE_CODE}/{SOURCE_MSC_ROUTE_NAME},无法创建 UC 模版"
|
|
)
|
|
return route
|
|
|
|
|
|
def _clone_step_children(
|
|
db: Session,
|
|
*,
|
|
src_step_id: str,
|
|
dst_step_id: str,
|
|
tenant_no: str,
|
|
user_id: Optional[str],
|
|
create_dept_id: str,
|
|
now: datetime,
|
|
) -> None:
|
|
reagent_rows = (
|
|
db.query(BizProcessRouteStepReagent)
|
|
.filter(
|
|
BizProcessRouteStepReagent.step_id == src_step_id,
|
|
BizProcessRouteStepReagent.tenant_no == tenant_no,
|
|
BizProcessRouteStepReagent.del_flag == 0,
|
|
)
|
|
.all()
|
|
)
|
|
for r in reagent_rows:
|
|
db.add(
|
|
BizProcessRouteStepReagent(
|
|
id=generate_uuid(),
|
|
step_id=dst_step_id,
|
|
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,
|
|
tenant_no=tenant_no,
|
|
order_num=r.order_num,
|
|
remark=r.remark,
|
|
create_by=user_id,
|
|
create_dept_id=create_dept_id or "",
|
|
create_time=now,
|
|
update_by=user_id,
|
|
update_time=now,
|
|
del_flag=0,
|
|
)
|
|
)
|
|
|
|
consumable_rows = (
|
|
db.query(BizProcessRouteStepConsumable)
|
|
.filter(
|
|
BizProcessRouteStepConsumable.step_id == src_step_id,
|
|
BizProcessRouteStepConsumable.tenant_no == tenant_no,
|
|
BizProcessRouteStepConsumable.del_flag == 0,
|
|
)
|
|
.all()
|
|
)
|
|
for c in consumable_rows:
|
|
db.add(
|
|
BizProcessRouteStepConsumable(
|
|
id=generate_uuid(),
|
|
step_id=dst_step_id,
|
|
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,
|
|
tenant_no=tenant_no,
|
|
order_num=c.order_num,
|
|
create_by=user_id,
|
|
create_dept_id=create_dept_id or "",
|
|
create_time=now,
|
|
update_by=user_id,
|
|
update_time=now,
|
|
del_flag=0,
|
|
)
|
|
)
|
|
|
|
output_rows = (
|
|
db.query(BizProcessRouteStepOutput)
|
|
.filter(
|
|
BizProcessRouteStepOutput.step_id == src_step_id,
|
|
BizProcessRouteStepOutput.tenant_no == tenant_no,
|
|
BizProcessRouteStepOutput.del_flag == 0,
|
|
)
|
|
.all()
|
|
)
|
|
for o in output_rows:
|
|
db.add(
|
|
BizProcessRouteStepOutput(
|
|
id=generate_uuid(),
|
|
step_id=dst_step_id,
|
|
output_type_id=o.output_type_id,
|
|
output_name=o.output_name,
|
|
output_barcode=o.output_barcode,
|
|
tenant_no=tenant_no,
|
|
order_num=o.order_num,
|
|
remark=o.remark,
|
|
create_by=user_id,
|
|
create_dept_id=create_dept_id or "",
|
|
create_time=now,
|
|
update_by=user_id,
|
|
update_time=now,
|
|
del_flag=0,
|
|
)
|
|
)
|
|
|
|
dc_rows = (
|
|
db.query(BizProcessRouteStepDataCollection)
|
|
.filter(
|
|
BizProcessRouteStepDataCollection.step_id == src_step_id,
|
|
BizProcessRouteStepDataCollection.tenant_no == tenant_no,
|
|
BizProcessRouteStepDataCollection.del_flag == 0,
|
|
)
|
|
.all()
|
|
)
|
|
for d in dc_rows:
|
|
db.add(
|
|
BizProcessRouteStepDataCollection(
|
|
id=generate_uuid(),
|
|
step_id=dst_step_id,
|
|
collection_name=d.collection_name,
|
|
unit=d.unit,
|
|
tenant_no=tenant_no,
|
|
order_num=d.order_num,
|
|
remark=d.remark,
|
|
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 _source_steps_by_code(
|
|
db: Session, *, route_id: str, tenant_no: str
|
|
) -> Dict[str, BizProcessRouteStep]:
|
|
rows = (
|
|
db.query(BizProcessRouteStep)
|
|
.filter(
|
|
BizProcessRouteStep.route_id == route_id,
|
|
BizProcessRouteStep.tenant_no == tenant_no,
|
|
BizProcessRouteStep.del_flag == 0,
|
|
)
|
|
.all()
|
|
)
|
|
out: Dict[str, BizProcessRouteStep] = {}
|
|
for row in rows:
|
|
code = (row.process_code or "").strip()
|
|
if code and code not in out:
|
|
out[code] = row
|
|
return out
|
|
|
|
|
|
def ensure_uc_msc_builtin_template(
|
|
db: Session,
|
|
*,
|
|
tenant_no: str,
|
|
user_id: Optional[str] = None,
|
|
create_dept_id: str = "",
|
|
) -> Dict[str, Any]:
|
|
"""幂等创建 UC 5 步内置路线与任务组合;已存在则直接返回。"""
|
|
existing = get_uc_msc_builtin_set(db, tenant_no=tenant_no)
|
|
if existing and (existing.status or "1") == "1":
|
|
item = (
|
|
db.query(BizProductionTaskSetItem)
|
|
.filter(
|
|
BizProductionTaskSetItem.set_id == existing.id,
|
|
BizProductionTaskSetItem.tenant_no == tenant_no,
|
|
BizProductionTaskSetItem.del_flag == 0,
|
|
)
|
|
.order_by(
|
|
BizProductionTaskSetItem.order_num.asc(),
|
|
BizProductionTaskSetItem.create_time.asc(),
|
|
)
|
|
.first()
|
|
)
|
|
if item and item.route_id:
|
|
route = (
|
|
db.query(BizProcessRoute)
|
|
.filter(
|
|
BizProcessRoute.id == item.route_id,
|
|
BizProcessRoute.tenant_no == tenant_no,
|
|
BizProcessRoute.del_flag == 0,
|
|
)
|
|
.first()
|
|
)
|
|
step_count = (
|
|
db.query(BizProcessRouteStep.id)
|
|
.filter(
|
|
BizProcessRouteStep.route_id == item.route_id,
|
|
BizProcessRouteStep.tenant_no == tenant_no,
|
|
BizProcessRouteStep.del_flag == 0,
|
|
)
|
|
.count()
|
|
)
|
|
if route and step_count >= len(UC_MSC_STEP_ORDER):
|
|
return {
|
|
"created": False,
|
|
"setId": existing.id,
|
|
"setName": existing.set_name,
|
|
"routeId": route.id,
|
|
"routeCode": route.route_code,
|
|
"stepCount": step_count,
|
|
}
|
|
|
|
source = _find_source_route(db, tenant_no=tenant_no)
|
|
by_code = _source_steps_by_code(db, route_id=source.id, tenant_no=tenant_no)
|
|
missing = [c for c in UC_MSC_STEP_ORDER if c not in by_code]
|
|
if missing:
|
|
raise ValueError(
|
|
"源工艺路线缺少工序,无法创建 UC 模版:" + "、".join(missing)
|
|
)
|
|
|
|
now = _now()
|
|
route = (
|
|
db.query(BizProcessRoute)
|
|
.filter(
|
|
BizProcessRoute.tenant_no == tenant_no,
|
|
BizProcessRoute.route_code == UC_MSC_ROUTE_CODE,
|
|
BizProcessRoute.del_flag == 0,
|
|
)
|
|
.first()
|
|
)
|
|
if not route:
|
|
route = BizProcessRoute(
|
|
id=generate_uuid(),
|
|
route_code=UC_MSC_ROUTE_CODE,
|
|
route_name=UC_MSC_ROUTE_NAME,
|
|
product_type_id=source.product_type_id,
|
|
version=source.version,
|
|
is_current="0",
|
|
equipment_ids=source.equipment_ids,
|
|
equipment_name=source.equipment_name,
|
|
tenant_no=tenant_no,
|
|
order_num=0,
|
|
remark="内置模版:UC 入库编码自动派发(5 步,不含传代)",
|
|
is_builtin="1",
|
|
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(route)
|
|
db.flush()
|
|
else:
|
|
route.route_name = UC_MSC_ROUTE_NAME
|
|
route.is_builtin = "1"
|
|
route.update_by = user_id
|
|
route.update_time = now
|
|
db.add(route)
|
|
|
|
# 重建步骤(幂等:软删旧步骤后按目标顺序插入)
|
|
old_steps = (
|
|
db.query(BizProcessRouteStep)
|
|
.filter(
|
|
BizProcessRouteStep.route_id == route.id,
|
|
BizProcessRouteStep.tenant_no == tenant_no,
|
|
BizProcessRouteStep.del_flag == 0,
|
|
)
|
|
.all()
|
|
)
|
|
for s in old_steps:
|
|
s.del_flag = 1
|
|
s.update_by = user_id
|
|
s.update_time = now
|
|
db.add(s)
|
|
db.flush()
|
|
|
|
for order, code in enumerate(UC_MSC_STEP_ORDER, start=1):
|
|
src = by_code[code]
|
|
new_step = BizProcessRouteStep(
|
|
id=generate_uuid(),
|
|
route_id=route.id,
|
|
process_id=src.process_id,
|
|
step_order=order,
|
|
process_code=src.process_code,
|
|
category_id=src.category_id,
|
|
process_name=src.process_name,
|
|
description=src.description,
|
|
equipment_ids=src.equipment_ids,
|
|
equipment_name=src.equipment_name,
|
|
estimated_duration=src.estimated_duration,
|
|
sop_ids=src.sop_ids,
|
|
sop_content=src.sop_content,
|
|
tenant_no=tenant_no,
|
|
order_num=order,
|
|
remark=src.remark,
|
|
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_children(
|
|
db,
|
|
src_step_id=src.id,
|
|
dst_step_id=new_step.id,
|
|
tenant_no=tenant_no,
|
|
user_id=user_id,
|
|
create_dept_id=create_dept_id or "",
|
|
now=now,
|
|
)
|
|
|
|
header = existing or BizProductionTaskSet(
|
|
id=generate_uuid(),
|
|
set_name=UC_MSC_BUILTIN_SET_NAME,
|
|
status="1",
|
|
tenant_no=tenant_no,
|
|
order_num=0,
|
|
remark="内置模版:UC 入库编码自动派发",
|
|
is_builtin="1",
|
|
create_by=user_id,
|
|
create_dept_id=create_dept_id or "",
|
|
create_time=now,
|
|
update_by=user_id,
|
|
update_time=now,
|
|
del_flag=0,
|
|
)
|
|
if existing:
|
|
header.status = "1"
|
|
header.is_builtin = "1"
|
|
header.update_by = user_id
|
|
header.update_time = now
|
|
db.add(header)
|
|
db.flush()
|
|
|
|
items = (
|
|
db.query(BizProductionTaskSetItem)
|
|
.filter(
|
|
BizProductionTaskSetItem.set_id == header.id,
|
|
BizProductionTaskSetItem.tenant_no == tenant_no,
|
|
BizProductionTaskSetItem.del_flag == 0,
|
|
)
|
|
.all()
|
|
)
|
|
for it in items:
|
|
it.del_flag = 1
|
|
it.update_by = user_id
|
|
it.update_time = now
|
|
db.add(it)
|
|
db.flush()
|
|
|
|
item = BizProductionTaskSetItem(
|
|
id=generate_uuid(),
|
|
set_id=header.id,
|
|
task_name="{lab_storage_code}样本处理",
|
|
route_id=route.id,
|
|
route_name=route.route_name,
|
|
description=UC_MSC_BUILTIN_SET_NAME,
|
|
remark=None,
|
|
tenant_no=tenant_no,
|
|
order_num=1,
|
|
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(item)
|
|
db.flush()
|
|
|
|
return {
|
|
"created": True,
|
|
"setId": header.id,
|
|
"setName": header.set_name,
|
|
"routeId": route.id,
|
|
"routeCode": route.route_code,
|
|
"stepCount": len(UC_MSC_STEP_ORDER),
|
|
}
|