"""配置试剂配方 BOM CRUD""" from __future__ import annotations from datetime import datetime from decimal import Decimal from typing import Any, Dict, List, Optional from sqlalchemy import and_ from sqlalchemy.orm import Session from app.core.utils import generate_uuid from app.crud.production_material import _dec, _fmt_dt from app.models.production_material import BizProductionMaterial, BizProductionMaterialFormula COMPONENT_TYPES = ("reagent", "consumable") def _line_to_dict(r: BizProductionMaterialFormula) -> Dict[str, Any]: return { "id": r.id, "configuredMaterialId": r.configured_material_id, "componentMaterialId": r.component_material_id, "componentMaterialType": r.component_material_type or "", "componentMaterialCode": r.component_material_code or "", "componentMaterialName": r.component_material_name or "", "standardQty": _dec(r.standard_qty), "usageUnit": r.usage_unit or "", "orderNum": r.order_num if r.order_num is not None else 0, "remark": r.remark or "", } def _get_configured_material( db: Session, material_id: str, tenant_no: str ) -> BizProductionMaterial: row = ( db.query(BizProductionMaterial) .filter( BizProductionMaterial.id == material_id, BizProductionMaterial.tenant_no == tenant_no, BizProductionMaterial.del_flag == 0, ) .first() ) if not row: raise ValueError("配置试剂不存在") if (row.material_type or "").strip() != "configured_reagent": raise ValueError("仅配置试剂目录可维护配方") return row def get_formula( db: Session, configured_material_id: str, tenant_no: str ) -> List[Dict[str, Any]]: _get_configured_material(db, configured_material_id, tenant_no) rows = ( db.query(BizProductionMaterialFormula) .filter( BizProductionMaterialFormula.configured_material_id == configured_material_id, BizProductionMaterialFormula.tenant_no == tenant_no, BizProductionMaterialFormula.del_flag == 0, ) .order_by( BizProductionMaterialFormula.order_num.asc(), BizProductionMaterialFormula.create_time.asc(), ) .all() ) return [_line_to_dict(r) for r in rows] def replace_formula( db: Session, configured_material_id: str, *, lines: List[Dict[str, Any]], tenant_no: str, user_id: Optional[str] = None, dept_id: str = "", ) -> List[Dict[str, Any]]: configured = _get_configured_material(db, configured_material_id, tenant_no) now = datetime.now() if not lines: raise ValueError("请至少添加一条配方明细") seen_components: set = set() new_rows: List[BizProductionMaterialFormula] = [] for idx, it in enumerate(lines): comp_id = ( it.get("componentMaterialId") or it.get("component_material_id") or "" ).strip() if not comp_id: raise ValueError("请选择子物料") if comp_id in seen_components: raise ValueError("配方中存在重复的子物料") seen_components.add(comp_id) comp_type = ( it.get("componentMaterialType") or it.get("component_material_type") or "" ).strip().lower() if comp_type not in COMPONENT_TYPES: raise ValueError("子物料类型无效,应为试剂或耗材") comp = ( db.query(BizProductionMaterial) .filter( BizProductionMaterial.id == comp_id, BizProductionMaterial.tenant_no == tenant_no, BizProductionMaterial.del_flag == 0, ) .first() ) if not comp: raise ValueError("子物料不存在") if (comp.material_type or "").strip() != comp_type: raise ValueError(f"子物料「{comp.material_name}」类型与所选不一致") qty_raw = it.get("standardQty") if it.get("standardQty") is not None else it.get("standard_qty") if qty_raw is None or Decimal(str(qty_raw)) <= 0: raise ValueError("标准用量必须大于0") standard_qty = Decimal(str(qty_raw)) usage_unit = ( it.get("usageUnit") or it.get("usage_unit") or comp.unit or "" ).strip()[:32] new_rows.append( BizProductionMaterialFormula( id=generate_uuid(), configured_material_id=configured_material_id, component_material_id=comp_id, component_material_type=comp_type, component_material_code=comp.material_code, component_material_name=comp.material_name, standard_qty=standard_qty, usage_unit=usage_unit or None, tenant_no=tenant_no, order_num=idx, remark=(it.get("remark") or "").strip()[:512] or None, create_by=user_id, create_dept_id=dept_id or configured.create_dept_id or "", create_time=now, update_by=user_id, update_time=now, del_flag=0, ) ) existing = ( db.query(BizProductionMaterialFormula) .filter( BizProductionMaterialFormula.configured_material_id == configured_material_id, BizProductionMaterialFormula.tenant_no == tenant_no, BizProductionMaterialFormula.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 row in new_rows: db.add(row) db.commit() return get_formula(db, configured_material_id, tenant_no)