#!/usr/bin/env python # -*- coding: utf-8 -*- """ 工单查询模块 - 独立的SQL Server查询功能 """ import json import os from typing import Dict, Optional from pathlib import Path from logger import get_logger logger = get_logger() # 配置文件路径 CONFIG_FILE = Path(__file__).parent / 'work_order_db_config.json' # 默认配置 DEFAULT_CONFIG = { 'debug_mode': False, # Debug模式:True时使用假数据,不连接数据库 'host': 'localhost', # 数据库服务器地址 'port': 1433, # SQL Server端口(通常是1433) 'database': 'MES_DB', # 数据库名 'username': 'sa', # 用户名 'password': 'password', # 密码 'charset': 'utf8mb4' } def load_db_config() -> Dict[str, any]: """ 从配置文件加载数据库配置 如果配置文件不存在,则创建默认配置文件 """ if CONFIG_FILE.exists(): try: with open(CONFIG_FILE, 'r', encoding='utf-8') as f: config = json.load(f) # 只提取需要的配置项 return { 'debug_mode': config.get('debug_mode', DEFAULT_CONFIG['debug_mode']), 'host': config.get('host', DEFAULT_CONFIG['host']), 'port': config.get('port', DEFAULT_CONFIG['port']), 'database': config.get('database', DEFAULT_CONFIG['database']), 'username': config.get('username', DEFAULT_CONFIG['username']), 'password': config.get('password', DEFAULT_CONFIG['password']), 'charset': config.get('charset', DEFAULT_CONFIG['charset']) } except Exception as e: logger.error(f"读取配置文件失败: {e}") return DEFAULT_CONFIG.copy() else: # 创建默认配置文件 try: with open(CONFIG_FILE, 'w', encoding='utf-8') as f: config_with_desc = DEFAULT_CONFIG.copy() config_with_desc['description'] = '工单数据库连接配置' config_with_desc['comment'] = '请根据实际环境修改以上配置' json.dump(config_with_desc, f, ensure_ascii=False, indent=2) logger.info(f"已创建默认配置文件: {CONFIG_FILE}") except Exception as e: logger.error(f"创建配置文件失败: {e}") return DEFAULT_CONFIG.copy() # 工单数据库配置(从文件加载) WORK_ORDER_DB_CONFIG = load_db_config() def reload_config(): """ 重新加载配置文件 """ global WORK_ORDER_DB_CONFIG WORK_ORDER_DB_CONFIG = load_db_config() logger.info("工单数据库配置已重新加载") def query_work_order(work_order_no: str) -> Optional[Dict[str, str]]: """ 查询工单信息 - SQL Server版本 Args: work_order_no: 工单号 Returns: 包含工单信息的字典,如果查询失败则返回None 字典包含: - work_order_no: 工单号 - process_no: 工序号 (CSYMBOL) - part_no: 零件号 (CMATERIALSYMBOL) - executor: 执行人 (CEXCUTOR) """ if not work_order_no: logger.warning("工单号为空") return None # Debug模式:返回假数据 if WORK_ORDER_DB_CONFIG.get('debug_mode', False): logger.info(f"[DEBUG模式] 返回工单号 {work_order_no} 的假数据") return { 'work_order_no': work_order_no, 'process_no': 'DEBUG-001', 'part_no': 'PART-DEBUG-12345', 'executor': '测试人员' } conn = None cursor = None try: import pyodbc # 尝试不同的ODBC驱动 drivers = [ "ODBC Driver 17 for SQL Server", "ODBC Driver 18 for SQL Server", "ODBC Driver 13 for SQL Server", "ODBC Driver 11 for SQL Server", "SQL Server Native Client 11.0", "SQL Server Native Client 10.0", "SQL Server" ] # 查找可用的驱动 available_drivers = pyodbc.drivers() driver_to_use = None for driver in drivers: if driver in available_drivers: driver_to_use = driver logger.info(f"使用ODBC驱动: {driver}") break if not driver_to_use: logger.warning(f"未找到合适的SQL Server ODBC驱动,可用驱动: {available_drivers}") # 尝试使用第一个包含SQL的驱动 for available in available_drivers: if 'SQL' in available.upper(): driver_to_use = available logger.info(f"尝试使用驱动: {driver_to_use}") break if not driver_to_use: raise Exception("未找到SQL Server ODBC驱动,将尝试pymssql") # 构建SQL Server连接字符串 conn_str = ( f"DRIVER={{{driver_to_use}}};" f"SERVER={WORK_ORDER_DB_CONFIG['host']},{WORK_ORDER_DB_CONFIG['port']};" f"DATABASE={WORK_ORDER_DB_CONFIG['database']};" f"UID={WORK_ORDER_DB_CONFIG['username']};" f"PWD={WORK_ORDER_DB_CONFIG['password']}" ) logger.info(f"连接到SQL Server: {WORK_ORDER_DB_CONFIG['host']}:{WORK_ORDER_DB_CONFIG['port']}/{WORK_ORDER_DB_CONFIG['database']}") # 连接数据库 conn = pyodbc.connect(conn_str) cursor = conn.cursor() # 构建SQL查询语句(SQL Server使用?作为参数占位符) # 使用LIKE模糊匹配,支持工单号前缀查询(如 W2001150.001 可以匹配 W2001150.001-01:10) sql_query = """ SELECT mte.CSYMBOL, mpe.CMATERIALSYMBOL, mte.CEXCUTOR FROM MES_TASK_EXTEND mte INNER JOIN MBP_PROJECT_EXTEND mpe ON mte.CPGDID = mpe.CID WHERE mte.CSYMBOL LIKE ? """ # 添加通配符进行模糊匹配 search_pattern = f"{work_order_no}%" logger.debug(f"执行查询: {sql_query} with parameter: {search_pattern}") cursor.execute(sql_query, search_pattern) row = cursor.fetchone() if not row: logger.warning(f"未找到工单号 '{work_order_no}' 的相关信息") return None # 提取查询结果 - pyodbc返回Row对象,可以通过索引或属性访问 work_order_info = { 'work_order_no': work_order_no, 'process_no': row.CSYMBOL if row.CSYMBOL else '', # 工序号 'part_no': row.CMATERIALSYMBOL if row.CMATERIALSYMBOL else '', # 零件号 'executor': row.CEXCUTOR if row.CEXCUTOR else '' # 执行人 } logger.info(f"工单查询成功: {work_order_info}") return work_order_info except ImportError: logger.error("pyodbc未安装,请运行: pip install pyodbc") # 尝试使用pymssql作为备选 try: import pymssql logger.info("尝试使用pymssql连接SQL Server") conn = pymssql.connect( server=WORK_ORDER_DB_CONFIG['host'], port=WORK_ORDER_DB_CONFIG['port'], user=WORK_ORDER_DB_CONFIG['username'], password=WORK_ORDER_DB_CONFIG['password'], database=WORK_ORDER_DB_CONFIG['database'] ) cursor = conn.cursor(as_dict=True) # 使用LIKE模糊匹配,支持工单号前缀查询 sql_query = """ SELECT mte.CSYMBOL, mpe.CMATERIALSYMBOL, mte.CEXCUTOR FROM MES_TASK_EXTEND mte INNER JOIN MBP_PROJECT_EXTEND mpe ON mte.CPGDID = mpe.CID WHERE mte.CSYMBOL LIKE %s """ search_pattern = f"{work_order_no}%" cursor.execute(sql_query, search_pattern) result = cursor.fetchone() if not result: logger.warning(f"未找到工单号 '{work_order_no}' 的相关信息") return None work_order_info = { 'work_order_no': work_order_no, 'process_no': result.get('CSYMBOL', ''), 'part_no': result.get('CMATERIALSYMBOL', ''), 'executor': result.get('CEXCUTOR', '') } logger.info(f"工单查询成功(pymssql): {work_order_info}") return work_order_info except ImportError: error_msg = "SQL Server驱动未安装,请安装以下任一库:\n1. pip install pyodbc\n2. pip install pymssql" logger.error(error_msg) raise Exception(error_msg) except Exception as e: logger.error(f"pymssql查询错误: {e}") raise Exception(f"数据库查询错误: {e}") except Exception as e: logger.error(f"SQL Server查询错误: {e}") raise Exception(f"数据库查询错误: {e}") finally: if cursor: cursor.close() if conn: conn.close() logger.debug("数据库连接已关闭") def update_work_order_db_config(host: str = None, port: int = None, database: str = None, username: str = None, password: str = None, save_to_file: bool = True): """ 更新工单数据库连接配置 Args: host: 数据库服务器地址 port: 数据库端口 database: 数据库名 username: 用户名 password: 密码 save_to_file: 是否保存到配置文件 """ global WORK_ORDER_DB_CONFIG if host is not None: WORK_ORDER_DB_CONFIG['host'] = host if port is not None: WORK_ORDER_DB_CONFIG['port'] = port if database is not None: WORK_ORDER_DB_CONFIG['database'] = database if username is not None: WORK_ORDER_DB_CONFIG['username'] = username if password is not None: WORK_ORDER_DB_CONFIG['password'] = password logger.info(f"工单数据库配置已更新: host={WORK_ORDER_DB_CONFIG['host']}, " f"port={WORK_ORDER_DB_CONFIG['port']}, database={WORK_ORDER_DB_CONFIG['database']}") # 保存到配置文件 if save_to_file: try: config_to_save = WORK_ORDER_DB_CONFIG.copy() config_to_save['description'] = '工单数据库连接配置' config_to_save['comment'] = '请根据实际环境修改以上配置' with open(CONFIG_FILE, 'w', encoding='utf-8') as f: json.dump(config_to_save, f, ensure_ascii=False, indent=2) logger.info(f"配置已保存到文件: {CONFIG_FILE}") except Exception as e: logger.error(f"保存配置文件失败: {e}")