production_material_formula.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. """配置试剂配方 BOM CRUD"""
  2. from __future__ import annotations
  3. from datetime import datetime
  4. from decimal import Decimal
  5. from typing import Any, Dict, List, Optional
  6. from sqlalchemy import and_
  7. from sqlalchemy.orm import Session
  8. from app.core.utils import generate_uuid
  9. from app.crud.production_material import _dec, _fmt_dt
  10. from app.models.production_material import BizProductionMaterial, BizProductionMaterialFormula
  11. COMPONENT_TYPES = ("reagent", "consumable")
  12. def _line_to_dict(r: BizProductionMaterialFormula) -> Dict[str, Any]:
  13. return {
  14. "id": r.id,
  15. "configuredMaterialId": r.configured_material_id,
  16. "componentMaterialId": r.component_material_id,
  17. "componentMaterialType": r.component_material_type or "",
  18. "componentMaterialCode": r.component_material_code or "",
  19. "componentMaterialName": r.component_material_name or "",
  20. "standardQty": _dec(r.standard_qty),
  21. "usageUnit": r.usage_unit or "",
  22. "orderNum": r.order_num if r.order_num is not None else 0,
  23. "remark": r.remark or "",
  24. }
  25. def _get_configured_material(
  26. db: Session, material_id: str, tenant_no: str
  27. ) -> BizProductionMaterial:
  28. row = (
  29. db.query(BizProductionMaterial)
  30. .filter(
  31. BizProductionMaterial.id == material_id,
  32. BizProductionMaterial.tenant_no == tenant_no,
  33. BizProductionMaterial.del_flag == 0,
  34. )
  35. .first()
  36. )
  37. if not row:
  38. raise ValueError("配置试剂不存在")
  39. if (row.material_type or "").strip() != "configured_reagent":
  40. raise ValueError("仅配置试剂目录可维护配方")
  41. return row
  42. def get_formula(
  43. db: Session, configured_material_id: str, tenant_no: str
  44. ) -> List[Dict[str, Any]]:
  45. _get_configured_material(db, configured_material_id, tenant_no)
  46. rows = (
  47. db.query(BizProductionMaterialFormula)
  48. .filter(
  49. BizProductionMaterialFormula.configured_material_id == configured_material_id,
  50. BizProductionMaterialFormula.tenant_no == tenant_no,
  51. BizProductionMaterialFormula.del_flag == 0,
  52. )
  53. .order_by(
  54. BizProductionMaterialFormula.order_num.asc(),
  55. BizProductionMaterialFormula.create_time.asc(),
  56. )
  57. .all()
  58. )
  59. return [_line_to_dict(r) for r in rows]
  60. def replace_formula(
  61. db: Session,
  62. configured_material_id: str,
  63. *,
  64. lines: List[Dict[str, Any]],
  65. tenant_no: str,
  66. user_id: Optional[str] = None,
  67. dept_id: str = "",
  68. ) -> List[Dict[str, Any]]:
  69. configured = _get_configured_material(db, configured_material_id, tenant_no)
  70. now = datetime.now()
  71. if not lines:
  72. raise ValueError("请至少添加一条配方明细")
  73. seen_components: set = set()
  74. new_rows: List[BizProductionMaterialFormula] = []
  75. for idx, it in enumerate(lines):
  76. comp_id = (
  77. it.get("componentMaterialId") or it.get("component_material_id") or ""
  78. ).strip()
  79. if not comp_id:
  80. raise ValueError("请选择子物料")
  81. if comp_id in seen_components:
  82. raise ValueError("配方中存在重复的子物料")
  83. seen_components.add(comp_id)
  84. comp_type = (
  85. it.get("componentMaterialType") or it.get("component_material_type") or ""
  86. ).strip().lower()
  87. if comp_type not in COMPONENT_TYPES:
  88. raise ValueError("子物料类型无效,应为试剂或耗材")
  89. comp = (
  90. db.query(BizProductionMaterial)
  91. .filter(
  92. BizProductionMaterial.id == comp_id,
  93. BizProductionMaterial.tenant_no == tenant_no,
  94. BizProductionMaterial.del_flag == 0,
  95. )
  96. .first()
  97. )
  98. if not comp:
  99. raise ValueError("子物料不存在")
  100. if (comp.material_type or "").strip() != comp_type:
  101. raise ValueError(f"子物料「{comp.material_name}」类型与所选不一致")
  102. qty_raw = it.get("standardQty") if it.get("standardQty") is not None else it.get("standard_qty")
  103. if qty_raw is None or Decimal(str(qty_raw)) <= 0:
  104. raise ValueError("标准用量必须大于0")
  105. standard_qty = Decimal(str(qty_raw))
  106. usage_unit = (
  107. it.get("usageUnit") or it.get("usage_unit") or comp.unit or ""
  108. ).strip()[:32]
  109. new_rows.append(
  110. BizProductionMaterialFormula(
  111. id=generate_uuid(),
  112. configured_material_id=configured_material_id,
  113. component_material_id=comp_id,
  114. component_material_type=comp_type,
  115. component_material_code=comp.material_code,
  116. component_material_name=comp.material_name,
  117. standard_qty=standard_qty,
  118. usage_unit=usage_unit or None,
  119. tenant_no=tenant_no,
  120. order_num=idx,
  121. remark=(it.get("remark") or "").strip()[:512] or None,
  122. create_by=user_id,
  123. create_dept_id=dept_id or configured.create_dept_id or "",
  124. create_time=now,
  125. update_by=user_id,
  126. update_time=now,
  127. del_flag=0,
  128. )
  129. )
  130. existing = (
  131. db.query(BizProductionMaterialFormula)
  132. .filter(
  133. BizProductionMaterialFormula.configured_material_id == configured_material_id,
  134. BizProductionMaterialFormula.tenant_no == tenant_no,
  135. BizProductionMaterialFormula.del_flag == 0,
  136. )
  137. .all()
  138. )
  139. for row in existing:
  140. row.del_flag = 1
  141. row.update_by = user_id
  142. row.update_time = now
  143. db.add(row)
  144. for row in new_rows:
  145. db.add(row)
  146. db.commit()
  147. return get_formula(db, configured_material_id, tenant_no)