2025-12-11 14:32:31 +08:00
|
|
|
|
#!/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', # 密码
|
2026-01-16 14:47:53 +08:00
|
|
|
|
'charset': 'utf8mb4',
|
|
|
|
|
|
'target_process_name': '泵空跑合' # 目标工序名称,用于精确查询工单
|
2025-12-11 14:32:31 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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']),
|
2026-01-16 14:47:53 +08:00
|
|
|
|
'charset': config.get('charset', DEFAULT_CONFIG['charset']),
|
|
|
|
|
|
'target_process_name': config.get('target_process_name', DEFAULT_CONFIG['target_process_name']) or DEFAULT_CONFIG['target_process_name']
|
2025-12-11 14:32:31 +08:00
|
|
|
|
}
|
|
|
|
|
|
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)
|
2026-01-16 14:47:53 +08:00
|
|
|
|
- process_name: 工序名称 (CNEXTPROCNAME)
|
2025-12-11 14:32:31 +08:00
|
|
|
|
- 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',
|
2026-01-16 14:47:53 +08:00
|
|
|
|
'process_name': WORK_ORDER_DB_CONFIG.get('target_process_name', '泵空跑合'),
|
2025-12-11 14:32:31 +08:00
|
|
|
|
'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()
|
|
|
|
|
|
|
2026-01-16 14:47:53 +08:00
|
|
|
|
# 获取目标工序名称并去除前后空格
|
|
|
|
|
|
target_process_name = WORK_ORDER_DB_CONFIG.get('target_process_name', '泵空跑合').strip()
|
|
|
|
|
|
|
2025-12-11 14:32:31 +08:00
|
|
|
|
# 构建SQL查询语句(SQL Server使用?作为参数占位符)
|
2026-01-16 14:47:53 +08:00
|
|
|
|
# 使用LIKE模糊匹配,更容错
|
2025-12-11 14:32:31 +08:00
|
|
|
|
sql_query = """
|
|
|
|
|
|
SELECT
|
|
|
|
|
|
mte.CSYMBOL,
|
2026-01-16 14:47:53 +08:00
|
|
|
|
mte.CNEXTPROCNAME,
|
2025-12-11 14:32:31 +08:00
|
|
|
|
mpe.CMATERIALSYMBOL,
|
|
|
|
|
|
mte.CEXCUTOR
|
|
|
|
|
|
FROM MES_TASK_EXTEND mte
|
|
|
|
|
|
INNER JOIN MBP_PROJECT_EXTEND mpe ON mte.CPGDID = mpe.CID
|
2026-01-16 14:47:53 +08:00
|
|
|
|
WHERE mte.CSYMBOL LIKE ?
|
|
|
|
|
|
AND RTRIM(LTRIM(ISNULL(mte.CNEXTPROCNAME, ''))) LIKE ?
|
2025-12-11 14:32:31 +08:00
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
# 添加通配符进行模糊匹配
|
2026-01-16 14:47:53 +08:00
|
|
|
|
work_order_pattern = f"{work_order_no}%"
|
|
|
|
|
|
process_name_pattern = f"%{target_process_name}%"
|
|
|
|
|
|
logger.debug(f"执行查询: {sql_query} with parameters: '{work_order_pattern}', '{process_name_pattern}'")
|
|
|
|
|
|
cursor.execute(sql_query, work_order_pattern, process_name_pattern)
|
2025-12-11 14:32:31 +08:00
|
|
|
|
row = cursor.fetchone()
|
|
|
|
|
|
|
|
|
|
|
|
if not row:
|
2026-01-16 14:47:53 +08:00
|
|
|
|
logger.warning(f"未找到工单号 '{work_order_no}' 且工序名称为 '{target_process_name}' 的相关信息")
|
2025-12-11 14:32:31 +08:00
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
# 提取查询结果 - pyodbc返回Row对象,可以通过索引或属性访问
|
|
|
|
|
|
work_order_info = {
|
|
|
|
|
|
'work_order_no': work_order_no,
|
|
|
|
|
|
'process_no': row.CSYMBOL if row.CSYMBOL else '', # 工序号
|
2026-01-16 14:47:53 +08:00
|
|
|
|
'process_name': row.CNEXTPROCNAME if row.CNEXTPROCNAME else '', # 工序名称
|
2025-12-11 14:32:31 +08:00
|
|
|
|
'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)
|
|
|
|
|
|
|
2026-01-16 14:47:53 +08:00
|
|
|
|
# 获取目标工序名称并去除前后空格
|
|
|
|
|
|
target_process_name = WORK_ORDER_DB_CONFIG.get('target_process_name', '泵空跑合').strip()
|
|
|
|
|
|
|
|
|
|
|
|
# 使用LIKE模糊匹配,更容错
|
2025-12-11 14:32:31 +08:00
|
|
|
|
sql_query = """
|
|
|
|
|
|
SELECT
|
|
|
|
|
|
mte.CSYMBOL,
|
2026-01-16 14:47:53 +08:00
|
|
|
|
mte.CNEXTPROCNAME,
|
2025-12-11 14:32:31 +08:00
|
|
|
|
mpe.CMATERIALSYMBOL,
|
|
|
|
|
|
mte.CEXCUTOR
|
|
|
|
|
|
FROM MES_TASK_EXTEND mte
|
|
|
|
|
|
INNER JOIN MBP_PROJECT_EXTEND mpe ON mte.CPGDID = mpe.CID
|
2026-01-16 14:47:53 +08:00
|
|
|
|
WHERE mte.CSYMBOL LIKE %s
|
|
|
|
|
|
AND RTRIM(LTRIM(ISNULL(mte.CNEXTPROCNAME, ''))) LIKE %s
|
2025-12-11 14:32:31 +08:00
|
|
|
|
"""
|
|
|
|
|
|
|
2026-01-16 14:47:53 +08:00
|
|
|
|
# 添加通配符进行模糊匹配
|
|
|
|
|
|
work_order_pattern = f"{work_order_no}%"
|
|
|
|
|
|
process_name_pattern = f"%{target_process_name}%"
|
|
|
|
|
|
logger.debug(f"执行查询(pymssql): {sql_query} with parameters: '{work_order_pattern}', '{process_name_pattern}'")
|
|
|
|
|
|
cursor.execute(sql_query, (work_order_pattern, process_name_pattern))
|
2025-12-11 14:32:31 +08:00
|
|
|
|
result = cursor.fetchone()
|
|
|
|
|
|
|
|
|
|
|
|
if not result:
|
2026-01-16 14:47:53 +08:00
|
|
|
|
logger.warning(f"未找到工单号 '{work_order_no}' 且工序名称为 '{target_process_name}' 的相关信息")
|
2025-12-11 14:32:31 +08:00
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
work_order_info = {
|
|
|
|
|
|
'work_order_no': work_order_no,
|
|
|
|
|
|
'process_no': result.get('CSYMBOL', ''),
|
2026-01-16 14:47:53 +08:00
|
|
|
|
'process_name': result.get('CNEXTPROCNAME', ''),
|
2025-12-11 14:32:31 +08:00
|
|
|
|
'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}")
|
|
|
|
|
|
|