certflow.controllers.bom_material_controller 源代码

"""BOM 零件牌号控制器

桥接 BomMaterial 查询视图(View)与 BOM 零件服务(Service):封装按生产令号
查询、来源统计与材料牌号订正(手动/自学习)等业务逻辑,视图不直接操作 ORM。
模型类与来源常量经服务层间接引用,控制器不直接依赖 ``certflow.models.*``。

架构层次:View (BomMaterialQueryView) → Controller (BomMaterialController)
         → Service (bom_material_service) → Model (BomMaterial)
"""

from __future__ import annotations

from datetime import datetime
from typing import Any

from sqlalchemy import func

from certflow.controllers.base_controller import BaseController
from certflow.services.bom_material_service import (
    MATERIAL_SOURCE_AUTO_LEARN,
    MATERIAL_SOURCE_CHOICES,
    MATERIAL_SOURCE_MANUAL,
    BomMaterial,
    ensure_bom_material_columns,
    scan_and_import,
)


[文档] class BomMaterialController(BaseController): """BOM 零件牌号控制器 — 查询视图的业务调度层""" def __init__(self, db_session=None, db_manager=None, use_shared: bool = True): """初始化控制器;同时补齐 bom_materials 存量库新增列。""" super().__init__(db_session=db_session, db_manager=db_manager, use_shared=use_shared) # 存量库可能缺 material_source/material_note/updated_at 列,幂等补齐 ensure_bom_material_columns(self.db_manager.engine) # ============================================================ # 查询 # ============================================================
[文档] def search(self, production_order_no: str, *, fuzzy: bool = False) -> list[BomMaterial]: """按生产令号查询其全部零件。 Args: production_order_no: 生产令号(精确匹配或模糊包含) fuzzy: True 时使用 LIKE %pon% 模糊匹配 Returns: list[BomMaterial]:按 (生产令号, 序号) 排序的零件记录 """ q = self.session.query(BomMaterial) if production_order_no: if fuzzy: q = q.filter(BomMaterial.production_order_no.like(f"%{production_order_no}%")) else: q = q.filter(BomMaterial.production_order_no == production_order_no) return q.order_by(BomMaterial.production_order_no, BomMaterial.seq).all()
[文档] def list_all(self) -> list[BomMaterial]: """返回全部 BOM 零件记录(按 生产令号, 序号 排序)。""" return ( self.session.query(BomMaterial) .order_by(BomMaterial.production_order_no, BomMaterial.seq) .all() )
[文档] def get_by_id(self, record_id: int) -> BomMaterial | None: """按主键获取记录。""" return self.session.get(BomMaterial, record_id)
[文档] def distinct_production_order_nos(self) -> list[str]: """返回全部不重复的生产令号(升序),供搜索框自动补全。""" return [ r[0] for r in self.session.query(BomMaterial.production_order_no) .distinct() .order_by(BomMaterial.production_order_no) .all() ]
[文档] def source_summary(self) -> dict[str, int]: """统计各材料牌号来源的记录数(用于状态栏展示)。""" rows = ( self.session.query(BomMaterial.material_source, func.count(BomMaterial.id)) .group_by(BomMaterial.material_source) .all() ) return {src or "import": cnt for src, cnt in rows}
# ============================================================ # 订正(手动 / 自学习) # ============================================================
[文档] def update_material_grade( self, record_id: int, material_grade: str | None, material_source: str = MATERIAL_SOURCE_MANUAL, material_note: str | None = None, ) -> bool: """订正某零件的材料牌号,并记录来源与更新时间。 Args: record_id: 记录主键 material_grade: 新的材料牌号(空串/None 视为清空) material_source: 来源(manual/auto_learn),缺省 manual material_note: 补全备注 Returns: bool: 是否更新成功(id 不存在返回 False) """ obj = self.session.get(BomMaterial, record_id) if obj is None: return False obj.material_grade = (material_grade or None) if material_grade else None obj.material_source = material_source obj.material_note = (material_note or None) if material_note else None obj.updated_at = datetime.now() self.commit() return True
[文档] def auto_fill( self, record_id: int, material_grade: str, material_note: str | None = None, ) -> bool: """自学习补全某零件的材料牌号(来源固定 auto_learn)。""" return self.update_material_grade( record_id, material_grade, material_source=MATERIAL_SOURCE_AUTO_LEARN, material_note=material_note, )
# ============================================================ # 静态门面:收口视图层对 service / model 的散落直连 # (即使 BomMaterialController 未注入实例,视图仍可经类名调用,向后兼容) # ============================================================
[文档] @staticmethod def material_source_choices() -> list[tuple[str, str]]: """材料来源下拉选项(委托 model ``MATERIAL_SOURCE_CHOICES``)。""" return list(MATERIAL_SOURCE_CHOICES)
[文档] @staticmethod def scan_and_import(folder: str, session: Any, *, mode: str = "skip") -> dict[str, Any]: """递归扫描目录并入库(委托 ``bom_material_service.scan_and_import``)。 Args: folder: 待扫描目录 session: SQLAlchemy 会话 mode: 导入模式(clear/append/refresh/skip) Returns: 导入统计字典(files/rows/orders/...) """ return scan_and_import(folder, session, mode=mode)