process_route.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653
  1. """工艺路线 CRUD(biz_process_route + biz_process_route_step + 4张步骤子表)"""
  2. from __future__ import annotations
  3. import math
  4. from datetime import datetime
  5. from typing import Any, Dict, List, Optional, Tuple
  6. from sqlalchemy import and_, func, or_
  7. from sqlalchemy.orm import Session
  8. from app.core.utils import generate_uuid
  9. from app.crud.equipment_snapshot import parse_equipment_ids, resolve_equipment_list_snapshot
  10. from app.models.models import (
  11. BizProductionProductType,
  12. BizProcess,
  13. BizProcessReagent,
  14. BizProcessConsumable,
  15. BizProcessOutput,
  16. BizProcessDataCollection,
  17. BizProcessRoute,
  18. BizProcessRouteStep,
  19. BizProcessRouteStepReagent,
  20. BizProcessRouteStepConsumable,
  21. BizProcessRouteStepOutput,
  22. BizProcessRouteStepDataCollection,
  23. )
  24. # ---------------------------------------------------------------------------
  25. # 序列化函数
  26. # ---------------------------------------------------------------------------
  27. def _route_row_to_dict(r: BizProcessRoute) -> Dict[str, Any]:
  28. return {
  29. "id": r.id,
  30. "routeCode": r.route_code,
  31. "routeName": r.route_name,
  32. "productTypeId": r.product_type_id or "",
  33. "version": r.version or "",
  34. "isCurrent": r.is_current if r.is_current is not None else "0",
  35. "equipmentIds": parse_equipment_ids(getattr(r, "equipment_ids", None)),
  36. "equipmentName": getattr(r, "equipment_name", None) or "",
  37. "tenantNo": r.tenant_no,
  38. "orderNum": r.order_num if r.order_num is not None else 0,
  39. "remark": r.remark or "",
  40. "createTime": r.create_time.strftime("%Y-%m-%d %H:%M:%S") if r.create_time else "",
  41. "updateTime": r.update_time.strftime("%Y-%m-%d %H:%M:%S") if r.update_time else "",
  42. }
  43. def _step_row_to_dict(s: BizProcessRouteStep) -> Dict[str, Any]:
  44. return {
  45. "id": s.id,
  46. "routeId": s.route_id,
  47. "processId": s.process_id or "",
  48. "stepOrder": s.step_order if s.step_order is not None else 0,
  49. "processCode": s.process_code or "",
  50. "categoryId": s.category_id or "",
  51. "processName": s.process_name or "",
  52. "description": s.description or "",
  53. "estimatedDuration": s.estimated_duration or "",
  54. "sopIds": s.sop_ids or "",
  55. "remark": s.remark or "",
  56. }
  57. def _step_reagent_row_to_dict(r: BizProcessRouteStepReagent) -> Dict[str, Any]:
  58. return {
  59. "id": r.id,
  60. "stepId": r.step_id,
  61. "reagentCode": r.reagent_code or "",
  62. "reagentName": r.reagent_name or "",
  63. "spec": r.spec or "",
  64. "standardUsage": r.standard_usage or "",
  65. "unit": r.unit or "",
  66. "isKeyMaterial": r.is_key_material if r.is_key_material is not None else "0",
  67. "orderNum": r.order_num if r.order_num is not None else 0,
  68. "remark": r.remark or "",
  69. }
  70. def _step_consumable_row_to_dict(c: BizProcessRouteStepConsumable) -> Dict[str, Any]:
  71. return {
  72. "id": c.id,
  73. "stepId": c.step_id,
  74. "consumableCode": c.consumable_code or "",
  75. "consumableName": c.consumable_name or "",
  76. "specModel": c.spec_model or "",
  77. "standardUsage": c.standard_usage or "",
  78. "unit": c.unit or "",
  79. "isRequired": c.is_required if c.is_required is not None else "1",
  80. "orderNum": c.order_num if c.order_num is not None else 0,
  81. "remark": c.remark or "",
  82. }
  83. def _step_output_row_to_dict(o: BizProcessRouteStepOutput) -> Dict[str, Any]:
  84. return {
  85. "id": o.id,
  86. "stepId": o.step_id,
  87. "outputTypeId": o.output_type_id or "",
  88. "outputName": o.output_name or "",
  89. "outputBarcode": o.output_barcode or "",
  90. "orderNum": o.order_num if o.order_num is not None else 0,
  91. "remark": o.remark or "",
  92. }
  93. def _step_data_collection_row_to_dict(d: BizProcessRouteStepDataCollection) -> Dict[str, Any]:
  94. return {
  95. "id": d.id,
  96. "stepId": d.step_id,
  97. "collectionName": d.collection_name or "",
  98. "unit": d.unit or "",
  99. "orderNum": d.order_num if d.order_num is not None else 0,
  100. "remark": d.remark or "",
  101. }
  102. # ---------------------------------------------------------------------------
  103. # 路线主表 CRUD
  104. # ---------------------------------------------------------------------------
  105. def list_routes(db: Session, *, page: int = 1, page_size: int = 10,
  106. route_name: Optional[str] = None, route_code: Optional[str] = None,
  107. product_type_id: Optional[str] = None,
  108. tenant_no: Optional[str] = None,
  109. user_info=None) -> Tuple[List[Dict], int]:
  110. def _filtered():
  111. q = db.query(BizProcessRoute).filter(BizProcessRoute.del_flag == 0)
  112. if tenant_no:
  113. q = q.filter(BizProcessRoute.tenant_no == tenant_no)
  114. if route_name and route_name.strip():
  115. q = q.filter(BizProcessRoute.route_name.like(f"%{route_name.strip()}%"))
  116. if route_code and route_code.strip():
  117. q = q.filter(BizProcessRoute.route_code == route_code.strip())
  118. if product_type_id and product_type_id.strip():
  119. q = q.filter(BizProcessRoute.product_type_id == product_type_id.strip())
  120. if user_info:
  121. from app.core.data_scope import apply_data_scope
  122. q = apply_data_scope(q, BizProcessRoute, user_info, db, tenant_no)
  123. return q
  124. total = _filtered().order_by(None).with_entities(func.count(BizProcessRoute.id)).scalar() or 0
  125. page = max(1, page)
  126. page_size = min(max(1, page_size), 100)
  127. rows = (
  128. _filtered()
  129. .outerjoin(
  130. BizProductionProductType,
  131. and_(
  132. BizProcessRoute.product_type_id == BizProductionProductType.id,
  133. BizProductionProductType.del_flag == 0,
  134. ),
  135. )
  136. .order_by(
  137. BizProductionProductType.product_type_name.is_(None),
  138. BizProductionProductType.product_type_name.asc(),
  139. BizProcessRoute.create_time.desc(),
  140. )
  141. .offset((page - 1) * page_size)
  142. .limit(page_size)
  143. .all()
  144. )
  145. return [_route_row_to_dict(x) for x in rows], int(total)
  146. def get_route(db: Session, route_id: str) -> Optional[BizProcessRoute]:
  147. return db.query(BizProcessRoute).filter(
  148. and_(BizProcessRoute.id == route_id, BizProcessRoute.del_flag == 0)
  149. ).first()
  150. def get_route_detail(db: Session, route_id: str) -> Optional[Dict[str, Any]]:
  151. route = get_route(db, route_id)
  152. if not route:
  153. return None
  154. result = _route_row_to_dict(route)
  155. steps = (
  156. db.query(BizProcessRouteStep)
  157. .filter(and_(BizProcessRouteStep.route_id == route_id, BizProcessRouteStep.del_flag == 0))
  158. .order_by(BizProcessRouteStep.step_order.asc(), BizProcessRouteStep.create_time.asc())
  159. .all()
  160. )
  161. step_list = []
  162. for s in steps:
  163. sd = _step_row_to_dict(s)
  164. reagents = db.query(BizProcessRouteStepReagent).filter(
  165. and_(BizProcessRouteStepReagent.step_id == s.id, BizProcessRouteStepReagent.del_flag == 0)
  166. ).order_by(BizProcessRouteStepReagent.order_num.asc(), BizProcessRouteStepReagent.create_time.asc()).all()
  167. consumables = db.query(BizProcessRouteStepConsumable).filter(
  168. and_(BizProcessRouteStepConsumable.step_id == s.id, BizProcessRouteStepConsumable.del_flag == 0)
  169. ).order_by(BizProcessRouteStepConsumable.order_num.asc(), BizProcessRouteStepConsumable.create_time.asc()).all()
  170. outputs = db.query(BizProcessRouteStepOutput).filter(
  171. and_(BizProcessRouteStepOutput.step_id == s.id, BizProcessRouteStepOutput.del_flag == 0)
  172. ).order_by(BizProcessRouteStepOutput.order_num.asc(), BizProcessRouteStepOutput.create_time.asc()).all()
  173. data_collections = db.query(BizProcessRouteStepDataCollection).filter(
  174. and_(BizProcessRouteStepDataCollection.step_id == s.id, BizProcessRouteStepDataCollection.del_flag == 0)
  175. ).order_by(BizProcessRouteStepDataCollection.order_num.asc(), BizProcessRouteStepDataCollection.create_time.asc()).all()
  176. sd["reagents"] = [_step_reagent_row_to_dict(r) for r in reagents]
  177. sd["consumables"] = [_step_consumable_row_to_dict(c) for c in consumables]
  178. sd["outputs"] = [_step_output_row_to_dict(o) for o in outputs]
  179. sd["dataCollections"] = [_step_data_collection_row_to_dict(d) for d in data_collections]
  180. step_list.append(sd)
  181. result["steps"] = step_list
  182. return result
  183. def create_route(db: Session, *, route_code: str, route_name: str,
  184. product_type_id: Optional[str] = None, version: Optional[str] = None,
  185. remark: Optional[str] = None, tenant_no: str = "zkxw",
  186. user_id: Optional[str] = None) -> BizProcessRoute:
  187. rc = (route_code or "").strip()
  188. if not rc:
  189. raise ValueError("路线编码不能为空")
  190. now = datetime.now()
  191. obj = BizProcessRoute(
  192. id=generate_uuid(),
  193. route_code=rc,
  194. route_name=(route_name or "").strip() or rc,
  195. product_type_id=(product_type_id or "").strip() or None,
  196. version=(version or "").strip() or None,
  197. is_current="0",
  198. tenant_no=tenant_no,
  199. order_num=0,
  200. remark=(remark or "").strip() or None,
  201. create_by=user_id,
  202. create_dept_id="",
  203. create_time=now,
  204. update_by=user_id,
  205. update_time=now,
  206. del_flag=0,
  207. )
  208. db.add(obj)
  209. db.commit()
  210. db.refresh(obj)
  211. return obj
  212. def update_route(db: Session, route_id: str, *, user_id: Optional[str] = None, **kwargs) -> BizProcessRoute:
  213. obj = get_route(db, route_id)
  214. if not obj:
  215. raise ValueError("路线不存在")
  216. for field in ["route_code", "route_name", "product_type_id", "version", "remark"]:
  217. val = kwargs.get(field)
  218. if val is not None:
  219. setattr(obj, field, (val or "").strip() or None if field != "route_code" else (val or "").strip())
  220. if "equipment_ids" in kwargs:
  221. eids, enames = resolve_equipment_list_snapshot(
  222. db, kwargs.get("equipment_ids"), obj.tenant_no or "zkxw"
  223. )
  224. obj.equipment_ids = eids
  225. obj.equipment_name = enames
  226. obj.update_by = user_id
  227. obj.update_time = datetime.now()
  228. db.add(obj)
  229. db.commit()
  230. db.refresh(obj)
  231. return obj
  232. def set_current_route(db: Session, route_id: str, tenant_no: str) -> BizProcessRoute:
  233. obj = get_route(db, route_id)
  234. if not obj:
  235. raise ValueError("路线不存在")
  236. now = datetime.now()
  237. siblings = db.query(BizProcessRoute).filter(
  238. and_(BizProcessRoute.route_code == obj.route_code,
  239. BizProcessRoute.tenant_no == tenant_no,
  240. BizProcessRoute.del_flag == 0)
  241. ).all()
  242. for s in siblings:
  243. s.is_current = "0"
  244. s.update_time = now
  245. db.add(s)
  246. obj.is_current = "1"
  247. obj.update_time = now
  248. db.add(obj)
  249. db.commit()
  250. db.refresh(obj)
  251. return obj
  252. # ---------------------------------------------------------------------------
  253. # 步骤子表辅助
  254. # ---------------------------------------------------------------------------
  255. def _soft_delete_sub_records(db: Session, model_class, step_id: str, now: datetime) -> None:
  256. rows = db.query(model_class).filter(
  257. and_(model_class.step_id == step_id, model_class.del_flag == 0)
  258. ).all()
  259. for r in rows:
  260. r.del_flag = 1
  261. r.update_time = now
  262. db.add(r)
  263. def _create_step_reagent(db, step_id, tenant_no, user_id, now, data):
  264. db.add(BizProcessRouteStepReagent(
  265. id=generate_uuid(), step_id=step_id, tenant_no=tenant_no,
  266. reagent_code=(data.get("reagent_code") or "").strip() or None,
  267. reagent_name=(data.get("reagent_name") or "").strip() or None,
  268. spec=(data.get("spec") or "").strip() or None,
  269. standard_usage=(data.get("standard_usage") or "").strip() or None,
  270. unit=(data.get("unit") or "").strip() or None,
  271. is_key_material=data.get("is_key_material", "0"),
  272. remark=(data.get("remark") or "").strip() or None,
  273. order_num=data.get("order_num", 0) or 0,
  274. create_by=user_id, create_dept_id="",
  275. create_time=now, update_by=user_id, update_time=now, del_flag=0,
  276. ))
  277. def _create_step_consumable(db, step_id, tenant_no, user_id, now, data):
  278. db.add(BizProcessRouteStepConsumable(
  279. id=generate_uuid(), step_id=step_id, tenant_no=tenant_no,
  280. consumable_code=(data.get("consumable_code") or "").strip() or None,
  281. consumable_name=(data.get("consumable_name") or "").strip() or None,
  282. spec_model=(data.get("spec_model") or "").strip() or None,
  283. standard_usage=(data.get("standard_usage") or "").strip() or None,
  284. unit=(data.get("unit") or "").strip() or None,
  285. is_required=data.get("is_required", "1"),
  286. remark=(data.get("remark") or "").strip() or None,
  287. order_num=data.get("order_num", 0) or 0,
  288. create_by=user_id, create_dept_id="",
  289. create_time=now, update_by=user_id, update_time=now, del_flag=0,
  290. ))
  291. def _create_step_output(db, step_id, tenant_no, user_id, now, data):
  292. db.add(BizProcessRouteStepOutput(
  293. id=generate_uuid(), step_id=step_id, tenant_no=tenant_no,
  294. output_type_id=(data.get("output_type_id") or "").strip() or None,
  295. output_name=(data.get("output_name") or "").strip() or None,
  296. output_barcode=(data.get("output_barcode") or "").strip() or None,
  297. remark=(data.get("remark") or "").strip() or None,
  298. order_num=data.get("order_num", 0) or 0,
  299. create_by=user_id, create_dept_id="",
  300. create_time=now, update_by=user_id, update_time=now, del_flag=0,
  301. ))
  302. def _create_step_data_collection(db, step_id, tenant_no, user_id, now, data):
  303. db.add(BizProcessRouteStepDataCollection(
  304. id=generate_uuid(), step_id=step_id, tenant_no=tenant_no,
  305. collection_name=(data.get("collection_name") or "").strip() or None,
  306. unit=(data.get("unit") or "").strip() or None,
  307. order_num=data.get("order_num", 0) or 0,
  308. remark=(data.get("remark") or "").strip() or None,
  309. create_by=user_id, create_dept_id="",
  310. create_time=now, update_by=user_id, update_time=now, del_flag=0,
  311. ))
  312. # ---------------------------------------------------------------------------
  313. # 步骤 CRUD
  314. # ---------------------------------------------------------------------------
  315. def add_step(db: Session, *, route_id: str, process_id: str, step_order: Optional[int] = None,
  316. tenant_no: str = "zkxw", user_id: Optional[str] = None) -> Dict[str, Any]:
  317. route = get_route(db, route_id)
  318. if not route:
  319. raise ValueError("路线不存在")
  320. proc = db.query(BizProcess).filter(
  321. and_(BizProcess.id == process_id, BizProcess.del_flag == 0)
  322. ).first()
  323. if not proc:
  324. raise ValueError("工艺不存在")
  325. now = datetime.now()
  326. if step_order is None:
  327. max_order = db.query(func.max(BizProcessRouteStep.step_order)).filter(
  328. and_(BizProcessRouteStep.route_id == route_id, BizProcessRouteStep.del_flag == 0)
  329. ).scalar()
  330. step_order = (max_order or 0) + 1
  331. step = BizProcessRouteStep(
  332. id=generate_uuid(), route_id=route_id, process_id=process_id,
  333. step_order=step_order,
  334. process_code=proc.process_code, category_id=proc.category_id,
  335. process_name=proc.process_name, description=proc.description,
  336. estimated_duration=proc.estimated_duration,
  337. sop_ids=proc.sop_ids,
  338. tenant_no=tenant_no, order_num=0,
  339. remark=None, create_by=user_id, create_dept_id="",
  340. create_time=now, update_by=user_id, update_time=now, del_flag=0,
  341. )
  342. db.add(step)
  343. db.flush()
  344. src_reagents = db.query(BizProcessReagent).filter(
  345. and_(BizProcessReagent.process_id == process_id, BizProcessReagent.del_flag == 0)
  346. ).all()
  347. for r in src_reagents:
  348. _create_step_reagent(db, step.id, tenant_no, user_id, now, {
  349. "reagent_code": r.reagent_code, "reagent_name": r.reagent_name,
  350. "spec": r.spec, "standard_usage": r.standard_usage,
  351. "unit": r.unit, "is_key_material": r.is_key_material,
  352. })
  353. src_consumables = db.query(BizProcessConsumable).filter(
  354. and_(BizProcessConsumable.process_id == process_id, BizProcessConsumable.del_flag == 0)
  355. ).all()
  356. for c in src_consumables:
  357. _create_step_consumable(db, step.id, tenant_no, user_id, now, {
  358. "consumable_code": c.consumable_code, "consumable_name": c.consumable_name,
  359. "spec_model": c.spec_model, "standard_usage": c.standard_usage,
  360. "unit": c.unit, "is_required": c.is_required, "remark": c.remark,
  361. })
  362. src_outputs = db.query(BizProcessOutput).filter(
  363. and_(BizProcessOutput.process_id == process_id, BizProcessOutput.del_flag == 0)
  364. ).all()
  365. for o in src_outputs:
  366. _create_step_output(db, step.id, tenant_no, user_id, now, {
  367. "output_type_id": o.output_type_id, "output_name": o.output_name,
  368. "output_barcode": o.output_barcode,
  369. })
  370. src_collections = db.query(BizProcessDataCollection).filter(
  371. and_(BizProcessDataCollection.process_id == process_id, BizProcessDataCollection.del_flag == 0)
  372. ).all()
  373. for d in src_collections:
  374. _create_step_data_collection(db, step.id, tenant_no, user_id, now, {
  375. "collection_name": d.collection_name, "unit": d.unit, "order_num": d.order_num,
  376. })
  377. db.commit()
  378. detail = get_route_detail(db, route_id)
  379. return detail["steps"][-1] if detail and detail["steps"] else _step_row_to_dict(step)
  380. def update_step(db: Session, step_id: str, *, user_id: Optional[str] = None, **kwargs) -> BizProcessRouteStep:
  381. step = db.query(BizProcessRouteStep).filter(
  382. and_(BizProcessRouteStep.id == step_id, BizProcessRouteStep.del_flag == 0)
  383. ).first()
  384. if not step:
  385. raise ValueError("步骤不存在")
  386. for field in ["process_code", "category_id", "process_name", "description",
  387. "estimated_duration", "sop_ids"]:
  388. val = kwargs.get(field)
  389. if val is not None:
  390. setattr(step, field, (val or "").strip() or None)
  391. step.update_by = user_id
  392. step.update_time = datetime.now()
  393. db.add(step)
  394. db.commit()
  395. db.refresh(step)
  396. return step
  397. def reorder_steps(db: Session, route_id: str, step_ids: List[str]) -> None:
  398. now = datetime.now()
  399. for idx, sid in enumerate(step_ids):
  400. step = db.query(BizProcessRouteStep).filter(
  401. and_(BizProcessRouteStep.id == sid, BizProcessRouteStep.route_id == route_id,
  402. BizProcessRouteStep.del_flag == 0)
  403. ).first()
  404. if step:
  405. step.step_order = idx + 1
  406. step.update_time = now
  407. db.add(step)
  408. db.commit()
  409. def copy_step(db: Session, step_id: str, *, tenant_no: str = "zkxw",
  410. user_id: Optional[str] = None) -> Dict[str, Any]:
  411. """复制步骤,插入到原步骤之后,名称追加(复制)"""
  412. src = db.query(BizProcessRouteStep).filter(
  413. and_(BizProcessRouteStep.id == step_id, BizProcessRouteStep.del_flag == 0)
  414. ).first()
  415. if not src:
  416. raise ValueError("步骤不存在")
  417. now = datetime.now()
  418. new_order = src.step_order + 1
  419. # 后续步骤 step_order +1
  420. later_steps = db.query(BizProcessRouteStep).filter(
  421. and_(
  422. BizProcessRouteStep.route_id == src.route_id,
  423. BizProcessRouteStep.del_flag == 0,
  424. BizProcessRouteStep.step_order > src.step_order,
  425. )
  426. ).order_by(BizProcessRouteStep.step_order.asc()).all()
  427. for ls in later_steps:
  428. ls.step_order += 1
  429. ls.update_time = now
  430. db.add(ls)
  431. # 创建新步骤
  432. new_name = (src.process_name or "").strip()
  433. if not new_name.endswith("(复制)"):
  434. new_name += "(复制)"
  435. new_step = BizProcessRouteStep(
  436. id=generate_uuid(), route_id=src.route_id, process_id=src.process_id,
  437. step_order=new_order,
  438. process_code=src.process_code, category_id=src.category_id,
  439. process_name=new_name, description=src.description,
  440. estimated_duration=src.estimated_duration,
  441. sop_ids=src.sop_ids,
  442. tenant_no=tenant_no, order_num=0,
  443. remark=src.remark, create_by=user_id, create_dept_id="",
  444. create_time=now, update_by=user_id, update_time=now, del_flag=0,
  445. )
  446. db.add(new_step)
  447. db.flush()
  448. # 复制子表:试剂
  449. for r in db.query(BizProcessRouteStepReagent).filter(
  450. and_(BizProcessRouteStepReagent.step_id == step_id, BizProcessRouteStepReagent.del_flag == 0)
  451. ).order_by(
  452. BizProcessRouteStepReagent.order_num.asc(),
  453. BizProcessRouteStepReagent.create_time.asc(),
  454. ).all():
  455. _create_step_reagent(db, new_step.id, tenant_no, user_id, now, {
  456. "reagent_code": r.reagent_code, "reagent_name": r.reagent_name,
  457. "spec": r.spec, "standard_usage": r.standard_usage,
  458. "unit": r.unit, "is_key_material": r.is_key_material,
  459. "order_num": r.order_num,
  460. "remark": r.remark,
  461. })
  462. # 复制子表:耗材
  463. for c in db.query(BizProcessRouteStepConsumable).filter(
  464. and_(BizProcessRouteStepConsumable.step_id == step_id, BizProcessRouteStepConsumable.del_flag == 0)
  465. ).order_by(
  466. BizProcessRouteStepConsumable.order_num.asc(),
  467. BizProcessRouteStepConsumable.create_time.asc(),
  468. ).all():
  469. _create_step_consumable(db, new_step.id, tenant_no, user_id, now, {
  470. "consumable_code": c.consumable_code, "consumable_name": c.consumable_name,
  471. "spec_model": c.spec_model, "standard_usage": c.standard_usage,
  472. "unit": c.unit, "is_required": c.is_required,
  473. "order_num": c.order_num,
  474. "remark": c.remark,
  475. })
  476. # 复制子表:产出
  477. for o in db.query(BizProcessRouteStepOutput).filter(
  478. and_(BizProcessRouteStepOutput.step_id == step_id, BizProcessRouteStepOutput.del_flag == 0)
  479. ).order_by(
  480. BizProcessRouteStepOutput.order_num.asc(),
  481. BizProcessRouteStepOutput.create_time.asc(),
  482. ).all():
  483. _create_step_output(db, new_step.id, tenant_no, user_id, now, {
  484. "output_type_id": o.output_type_id, "output_name": o.output_name,
  485. "output_barcode": o.output_barcode,
  486. "order_num": o.order_num,
  487. "remark": o.remark,
  488. })
  489. # 复制子表:数据采集
  490. for d in db.query(BizProcessRouteStepDataCollection).filter(
  491. and_(BizProcessRouteStepDataCollection.step_id == step_id, BizProcessRouteStepDataCollection.del_flag == 0)
  492. ).order_by(
  493. BizProcessRouteStepDataCollection.order_num.asc(),
  494. BizProcessRouteStepDataCollection.create_time.asc(),
  495. ).all():
  496. _create_step_data_collection(db, new_step.id, tenant_no, user_id, now, {
  497. "collection_name": d.collection_name, "unit": d.unit,
  498. "order_num": d.order_num, "remark": d.remark,
  499. })
  500. db.commit()
  501. detail = get_route_detail(db, src.route_id)
  502. return detail["steps"][-1] if detail and detail["steps"] else _step_row_to_dict(new_step)
  503. def soft_delete_step(db: Session, step_id: str) -> None:
  504. step = db.query(BizProcessRouteStep).filter(
  505. and_(BizProcessRouteStep.id == step_id, BizProcessRouteStep.del_flag == 0)
  506. ).first()
  507. if not step:
  508. raise ValueError("步骤不存在")
  509. now = datetime.now()
  510. step.del_flag = 1
  511. step.update_time = now
  512. db.add(step)
  513. _soft_delete_sub_records(db, BizProcessRouteStepReagent, step_id, now)
  514. _soft_delete_sub_records(db, BizProcessRouteStepConsumable, step_id, now)
  515. _soft_delete_sub_records(db, BizProcessRouteStepOutput, step_id, now)
  516. _soft_delete_sub_records(db, BizProcessRouteStepDataCollection, step_id, now)
  517. db.commit()
  518. def soft_delete_route(db: Session, route_id: str) -> None:
  519. route = get_route(db, route_id)
  520. if not route:
  521. raise ValueError("路线不存在")
  522. now = datetime.now()
  523. route.del_flag = 1
  524. route.update_time = now
  525. db.add(route)
  526. steps = db.query(BizProcessRouteStep).filter(
  527. and_(BizProcessRouteStep.route_id == route_id, BizProcessRouteStep.del_flag == 0)
  528. ).all()
  529. for s in steps:
  530. s.del_flag = 1
  531. s.update_time = now
  532. db.add(s)
  533. _soft_delete_sub_records(db, BizProcessRouteStepReagent, s.id, now)
  534. _soft_delete_sub_records(db, BizProcessRouteStepConsumable, s.id, now)
  535. _soft_delete_sub_records(db, BizProcessRouteStepOutput, s.id, now)
  536. _soft_delete_sub_records(db, BizProcessRouteStepDataCollection, s.id, now)
  537. db.commit()
  538. # ---------------------------------------------------------------------------
  539. # 子表完整替换更新
  540. # ---------------------------------------------------------------------------
  541. def update_step_reagents(db: Session, step_id: str, items: List[Dict], tenant_no: str, user_id: Optional[str] = None):
  542. now = datetime.now()
  543. _soft_delete_sub_records(db, BizProcessRouteStepReagent, step_id, now)
  544. for it in items:
  545. _create_step_reagent(db, step_id, tenant_no, user_id, now, it)
  546. db.commit()
  547. def update_step_consumables(db: Session, step_id: str, items: List[Dict], tenant_no: str, user_id: Optional[str] = None):
  548. now = datetime.now()
  549. _soft_delete_sub_records(db, BizProcessRouteStepConsumable, step_id, now)
  550. for it in items:
  551. _create_step_consumable(db, step_id, tenant_no, user_id, now, it)
  552. db.commit()
  553. def update_step_outputs(db: Session, step_id: str, items: List[Dict], tenant_no: str, user_id: Optional[str] = None):
  554. now = datetime.now()
  555. _soft_delete_sub_records(db, BizProcessRouteStepOutput, step_id, now)
  556. for it in items:
  557. _create_step_output(db, step_id, tenant_no, user_id, now, it)
  558. db.commit()
  559. def update_step_data_collections(db: Session, step_id: str, items: List[Dict], tenant_no: str, user_id: Optional[str] = None):
  560. now = datetime.now()
  561. _soft_delete_sub_records(db, BizProcessRouteStepDataCollection, step_id, now)
  562. for it in items:
  563. _create_step_data_collection(db, step_id, tenant_no, user_id, now, it)
  564. db.commit()
  565. def total_pages(total: int, page_size: int) -> int:
  566. if total <= 0:
  567. return 0
  568. return int(math.ceil(total / float(page_size)))