feat: 如果开启 api 接口,那么就推送数据

This commit is contained in:
zhu-mengmeng 2025-06-18 15:48:13 +08:00
parent c091a55cb0
commit 0906f188fb
12 changed files with 709 additions and 125 deletions

83
apis/gc_api.py Normal file
View File

@ -0,0 +1,83 @@
from utils.api_utils import ApiUtils
import logging
import json
class GcApi:
def __init__(self):
"""初始化托盘API工具类"""
self.api_utils = ApiUtils()
def get_gc_info(self, gc_code):
"""
获取GC信息
Args:
gc_code: GC编号
Returns:
dict: GC信息
"""
try:
# API 配置中的键名
api_key = "get_gc_info"
# 构建 form-data 格式的数据
data = {
"sc_gch": gc_code,
"data_corp":"JT"
}
# 将工程号作为参数传递,使用 data 参数传递 form-data 格式数据
response = self.api_utils.post(api_key, data=data)
# 请求失败时返回空数据
if not response.get("status", False):
return {
"success": False,
"message": "获取GC信息失败",
"data": None
}
return response
except Exception as e:
logging.error(f"获取GC信息失败: {str(e)}")
return None
def get_order_info(self, order_code):
"""
获取订单信息
"""
try:
# API 配置中的键名
api_key = "get_order_info"
# 构建 form-data 格式的数据
order_dict = {"srch_mo":order_code,"data_corp":"JT"}
data = {
"parms": json.dumps(order_dict), # 必须将数据序列化为JSON字符串
"pageIndex": 0,
"pageSize": 10,
"sortField": "",
"sortOrder": ""
}
# 将工程号作为参数传递,使用 data 参数传递 form-data 格式数据
response = self.api_utils.post(api_key, data=data)
return response
except Exception as e:
logging.error(f"获取订单信息失败: {str(e)}")
return None
def add_order_info(self, info):
"""
添加订单信息
"""
try:
# API 配置中的键名
api_key = "add_order_info"
# 构建 form-data 格式的数据
data = {
"parms": json.dumps(info), # 必须将数据序列化为JSON字符串
"pageIndex": 0,
"pageSize": 10,
"sortField": "",
"sortOrder": ""
}
# 将工程号作为参数传递,使用 data 参数传递 form-data 格式数据
response = self.api_utils.post(api_key, data=data)
return response
except Exception as e:
logging.error(f"添加订单信息失败: {str(e)}")
return None

View File

@ -8,10 +8,13 @@
"enable_camera": false "enable_camera": false
}, },
"base_url": "http://localhost:8084", "base_url": "http://localhost:8084",
"mode": "standalone" "mode": "api"
}, },
"apis": { "apis": {
"get_tray_info": "/apjt/xcsc/tpda/getByTp_note/" "get_tray_info": "/apjt/xcsc/tpda/getByTp_note/",
"get_gc_info": "/jsjt/xcsc/tprk/getBZGCInfoToWsbz.do",
"get_order_info": "/jsjt/xcsc/tprk/getXsddBzrkGridListToWsbz.do",
"add_order_info": "/jsjt/xcsc/tprk/bzrkAdd01.do"
}, },
"database": { "database": {
"default": "sqlite", "default": "sqlite",

29
dao/gc_dao.py Normal file
View File

@ -0,0 +1,29 @@
import json
import logging
from datetime import datetime
from utils.sql_utils import SQLUtils
class GcDao:
"""检验项目配置和数据访问对象"""
def __init__(self):
"""初始化数据访问对象"""
self.db = SQLUtils('sqlite', database='db/jtDB.db')
def __del__(self):
"""析构函数,确保数据库连接关闭"""
if hasattr(self, 'db'):
self.db.close()
def get_gc_info_by_id(self, id):
"""根据id获取GC信息"""
try:
sql = """
SELECT * FROM wsbz_gc_info WHERE id = ?
"""
self.db.cursor.execute(sql, (id,))
return self.db.cursor.fetchone()
except Exception as e:
logging.error(f"获取GC信息失败: {str(e)}")
return None

View File

@ -214,7 +214,72 @@ class InspectionDAO:
except Exception as e: except Exception as e:
logging.error(f"更新检验项目启用状态失败: {str(e)}") logging.error(f"更新检验项目启用状态失败: {str(e)}")
return False return False
def save_order_info(self, order_id,data):
"""保存订单信息到 wsbz_order_info 表
Args:
data: 订单信息字典
Returns:
bool: 操作是否成功
"""
try:
if not data:
return False
sql = """
INSERT INTO wsbz_order_info (
data_corp, user_id, user_name, gzl_zl, ddmo, xpack,
sc_gch, qd, spack_type, mxzs, jt, ddnote, code, type,
lable, lib, gzl, maxsl, cz, size, cd, luno, qfqd,
pono, xj, ysl, dycz, zh, edit_id, remarks
) VALUES (
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
)
"""
params = (
data.get("data_corp", "JT"),
data.get("user_id", ""),
data.get("user_name", ""),
data.get("zx_zl", ""),
data.get("note", ""),
data.get("xpack", ""),
order_id if order_id else "",
data.get("qd", ""),
data.get("spack_type", ""),
data.get("mxzs", ""),
data.get("jt", ""),
data.get("note", ""),
data.get("code", ""),
data.get("type", ""),
data.get("template_name", ""),
data.get("lib", ""),
data.get("zx_code", ""),
data.get("maxsl", ""),
data.get("cz", ""),
data.get("size", ""),
data.get("cd", ""),
data.get("luno", ""),
data.get("qfqd", ""),
data.get("khno", ""),
data.get("size", ""),
data.get("ysl", ""),
data.get("dycz", ""),
data.get("zh", ""),
data.get("edit_id", ""),
data.get("remarks", ""),
)
self.db.cursor.execute(sql, params)
self.db.conn.commit()
return True
except Exception as e:
logging.error(f"保存订单信息失败: {str(e)}")
self.db.conn.rollback()
return False
def save_inspection_data(self, order_id, data, username='system'): def save_inspection_data(self, order_id, data, username='system'):
"""保存检验数据 """保存检验数据
@ -314,7 +379,7 @@ class InspectionDAO:
LEFT JOIN wsbz_inspection_config c ON d.config_id = c.id LEFT JOIN wsbz_inspection_config c ON d.config_id = c.id
WHERE d.is_deleted = FALSE AND d.tray_id = ? WHERE d.is_deleted = FALSE AND d.tray_id = ?
AND d.order_id IN ({placeholders}) AND d.order_id IN ({placeholders})
ORDER BY d.create_time, d.order_id, d.position ORDER BY d.create_time
""" """
params = [tray_id] + order_ids params = [tray_id] + order_ids
@ -384,8 +449,7 @@ class InspectionDAO:
return data_list return data_list
except Exception as e: except Exception as e:
logging.error(f"获取检验数据失败: {str(e)}") logging.error(f"获取检验数据失败: {str(e)}")
return [] return []
def get_package_record(self, tray_id): def get_package_record(self, tray_id):
"""根据托盘号获取包装记录 """根据托盘号获取包装记录
@ -458,4 +522,182 @@ class InspectionDAO:
self.db.conn.commit() self.db.conn.commit()
except Exception as e: except Exception as e:
logging.error(f"删除检验数据失败: {str(e)}") logging.error(f"删除检验数据失败: {str(e)}")
self.db.conn.rollback() self.db.conn.rollback()
def get_axios_num_by_order_id(self, order_id):
"""获取托盘号对应的轴号"""
try:
sql = """
SELECT max(axis_package_id) as axios_num FROM wsbz_inspection_pack_data WHERE order_id = ?
AND is_deleted = FALSE
"""
params = (order_id,)
self.db.cursor.execute(sql, params)
result = self.db.cursor.fetchone()
return int(result[0]) if result else 0
except Exception as e:
logging.error(f"获取轴号失败: {str(e)}")
return 0
def get_axios_num(self, tray_id):
"""获取托盘号对应的轴号"""
try:
sql = """
SELECT max(axis_package_id) as axios_num FROM wsbz_inspection_pack_data WHERE tray_id = ?
AND is_deleted = FALSE
"""
params = (tray_id,)
self.db.cursor.execute(sql, params)
result = self.db.cursor.fetchone()
return int(result[0]) if result else 0
except Exception as e:
logging.error(f"获取轴号失败: {str(e)}")
return 0
def get_gzl_zl(self,order_id):
"""获取工字轮重量"""
try:
sql = """
SELECT gzl_zl FROM wsbz_order_info WHERE sc_gch = ?
"""
params = (order_id,)
self.db.cursor.execute(sql, params)
result = self.db.cursor.fetchone()
return result[0] if result else 0
except Exception as e:
logging.error(f"获取工字轮重量失败: {str(e)}")
return 0
def get_order_create_time(self, order_id):
"""获取工程号的最早创建时间
Args:
order_id: 工程号
Returns:
str: 创建时间格式为'YYYY-MM-DD HH:MM:SS'如果未找到则返回None
"""
try:
sql = """
SELECT MIN(create_time) FROM wsbz_inspection_data
WHERE order_id = ? AND is_deleted = FALSE
"""
self.db.cursor.execute(sql, (order_id,))
result = self.db.cursor.fetchone()
return result[0] if result and result[0] else None
except Exception as e:
logging.error(f"获取工程号创建时间失败: {str(e)}")
return None
def get_orders_by_create_time(self, order_ids):
"""按创建时间排序工程号
Args:
order_ids: 工程号列表
Returns:
list: 按创建时间排序的工程号列表
"""
try:
if not order_ids:
return []
# 构建IN子句
placeholders = ','.join(['?' for _ in order_ids])
# 查询每个工程号的最早创建时间并排序
sql = f"""
SELECT order_id, MIN(create_time) as first_create_time
FROM wsbz_inspection_data
WHERE order_id IN ({placeholders}) AND is_deleted = FALSE
GROUP BY order_id
ORDER BY first_create_time
"""
self.db.cursor.execute(sql, order_ids)
results = self.db.cursor.fetchall()
# 提取排序后的工程号
sorted_order_ids = [row[0] for row in results]
# 确保所有传入的工程号都在结果中
for order_id in order_ids:
if order_id not in sorted_order_ids:
sorted_order_ids.append(order_id)
return sorted_order_ids
except Exception as e:
logging.error(f"按创建时间排序工程号失败: {str(e)}")
return order_ids # 出错时返回原始顺序
def get_order_info(self, order_id):
"""获取订单信息
Args:
order_id: 工程号
Returns:
dict: 订单信息字典
"""
try:
sql = """
SELECT data_corp,user_id,user_name,gzl_zl,mzl,ddmo,xpack,sc_gch,qd,spack_type,mxzs,jt,ddnote,code,
type,lable,lib,gzl,maxsl,cz,size,cd,luno,qfqd,pono,xj,ysl,dycz,zh,edit_id,remarks
FROM wsbz_order_info WHERE sc_gch = ?
"""
params = (order_id,)
self.db.cursor.execute(sql, params)
result = self.db.cursor.fetchone()
if not result:
return {}
# 获取列名
column_names = [desc[0] for desc in self.db.cursor.description]
# 转换为字典
result_dict = {}
for i, value in enumerate(result):
if i < len(column_names):
result_dict[column_names[i]] = value
return result_dict
except Exception as e:
logging.error(f"获取订单信息失败: {str(e)}")
return {}
def get_order_others_info(self, order_id, tray_id):
"""获取订单其他信息
Args:
order_id: 工程号
tray_id: 托盘号
Returns:
dict: 订单其他信息字典以name为keyvalue为值
"""
try:
sql = """
SELECT t1.order_id, CASE WHEN t1.position = 12 THEN 'mzl' ELSE name END AS name, value
FROM wsbz_inspection_data t1
LEFT JOIN main.wsbz_inspection_config wic ON t1.config_id = wic.id
WHERE order_id = ?
AND tray_id = ?
AND CASE WHEN t1.position = 12 THEN 'mzl' ELSE name END IS NOT NULL
AND COALESCE(value, '') != ''
"""
params = (order_id, tray_id)
self.db.cursor.execute(sql, params)
results = self.db.cursor.fetchall()
if not results:
return {}
# 将结果转换为字典以name为keyvalue为值
result_dict = {}
for row in results:
if len(row) >= 3: # 确保行至少有3个元素
name = row[1] # name在第二列
value = row[2] # value在第三列
result_dict[name] = value
return result_dict
except Exception as e:
logging.error(f"获取订单其他信息失败: {str(e)}")
return {}

Binary file not shown.

View File

@ -2,14 +2,16 @@ from pymodbus.client import ModbusTcpClient
client = ModbusTcpClient('localhost', port=5020) client = ModbusTcpClient('localhost', port=5020)
client.connect() client.connect()
# client.write_registers(address=11, values=[114]) # client.write_registers(address=11, values=[110])
client.write_registers(address=13, values=[1])
# client.write_registers(address=6, values=[1]) # client.write_registers(address=6, values=[1])
# client.write_registers(address=5, values=[16]) # client.write_registers(address=5, values=[16])
# 贴标完成 # 贴标完成
# client.write_registers(address=13, values=[1])
# client.write_registers(address=24, values=[1]) # client.write_registers(address=24, values=[1])
# client.write_registers(address=13, values=[0])
result = client.read_holding_registers(address=4, count=1) result = client.read_holding_registers(address=13, count=1)
print(result.registers[0],"123===") print(result.registers[0],"123===")
client.close() client.close()

48
tests/test_gc_api.py Normal file
View File

@ -0,0 +1,48 @@
import sys
import os
import logging
from pathlib import Path
# 添加项目根目录到系统路径,以便导入模块
project_root = str(Path(__file__).parent.parent)
sys.path.insert(0, project_root)
# 配置日志
logging.basicConfig(level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(name)s - [%(funcName)s:%(lineno)d] - %(message)s')
# 导入需要测试的模块
from apis.gc_api import GcApi
def test_gc_api():
"""测试 GcApi 的 get_gc_info 方法是否能正确处理 form-data 格式的请求"""
print("开始测试 GcApi.get_gc_info 方法...")
# 创建 GcApi 实例
gc_api = GcApi()
# 测试工程号
test_gc_code = "JTPD25060003"
# 调用方法
print(f"使用工程号 {test_gc_code} 调用 get_gc_info...")
response = gc_api.get_gc_info(test_gc_code)
# 打印结果
print(f"API 响应: {response}")
if response:
print("测试成功: API 返回了有效响应")
else:
print("测试失败: API 返回了空响应")
# 检查响应格式
if isinstance(response, dict) and "status" in response:
print(f"响应状态: {response.get('status', False)}")
print(f"响应消息: {response.get('message', '')}")
print(f"响应数据: {response.get('data', None)}")
else:
print(f"响应格式不符合预期: {response}")
if __name__ == "__main__":
test_gc_api()

View File

@ -1,6 +1,7 @@
import requests import requests
import json import json
from .config_loader import ConfigLoader from .config_loader import ConfigLoader
import logging
class ApiUtils: class ApiUtils:
def __init__(self): def __init__(self):
@ -54,10 +55,22 @@ class ApiUtils:
"Content-Type": "application/json" "Content-Type": "application/json"
} }
# 如果是 POST 请求且有表单数据,则使用 form-data 格式
if method.upper() == "POST" and data and not json_data:
# 对于 form-data 格式,不设置 Content-Type让 requests 自动处理
default_headers = {}
if headers: if headers:
default_headers.update(headers) default_headers.update(headers)
try: try:
# 记录请求信息,便于调试
logging.info(f"发送 {method} 请求到 {full_url}")
logging.info(f"参数: {params}")
logging.info(f"数据: {data}")
logging.info(f"JSON数据: {json_data}")
logging.info(f"请求头: {default_headers}")
response = requests.request( response = requests.request(
method=method, method=method,
url=full_url, url=full_url,
@ -67,11 +80,17 @@ class ApiUtils:
headers=default_headers headers=default_headers
) )
# 记录响应状态
logging.info(f"响应状态码: {response.status_code}")
logging.info(f"响应内容: {response.text[:500]}...") # 只记录前500个字符
response.raise_for_status() response.raise_for_status()
return response.json() return response.json()
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
logging.error(f"请求异常: {str(e)}")
return {"success": False, "message": f"请求异常: {str(e)}"} return {"success": False, "message": f"请求异常: {str(e)}"}
except json.JSONDecodeError: except json.JSONDecodeError as e:
logging.error(f"响应数据不是有效的JSON格式: {str(e)}")
return {"success": False, "message": "响应数据不是有效的 JSON 格式"} return {"success": False, "message": "响应数据不是有效的 JSON 格式"}
def get(self, url, params=None, headers=None): def get(self, url, params=None, headers=None):

View File

@ -76,17 +76,32 @@ class LoadingDialog(LoadingDialogUI):
# 如果返回的是元组(数据库查询结果),将其转换为字典 # 如果返回的是元组(数据库查询结果),将其转换为字典
# 根据dao/pallet_type_dao.py中get_pallet_info_by_pallet_id方法的SQL查询 # 根据dao/pallet_type_dao.py中get_pallet_info_by_pallet_id方法的SQL查询
# 返回的字段顺序为pallet_code, pallet_name, description, axios_name, axios_type, tier, size, amount, weight # 返回的字段顺序为pallet_code, pallet_name, description, axios_name, axios_type, tier, size, amount, weight
response = { try:
"success": True, response = {
"data": { "success": True,
"tp_note": pallet_info[0] if len(pallet_info) > 0 else "", "data": {
"product_name": pallet_info[1] if len(pallet_info) > 1 else "", "tp_note": pallet_info[0] if isinstance(pallet_info, tuple) and len(pallet_info) > 0 else "",
"axis_type": pallet_info[4] if len(pallet_info) > 4 else "", "product_name": pallet_info[1] if isinstance(pallet_info, tuple) and len(pallet_info) > 1 else "",
"tier": str(pallet_info[5]) if len(pallet_info) > 5 else "", "axis_type": pallet_info[4] if isinstance(pallet_info, tuple) and len(pallet_info) > 4 else "",
"weight": str(pallet_info[8]) if len(pallet_info) > 8 else "", "tier": str(pallet_info[5]) if isinstance(pallet_info, tuple) and len(pallet_info) > 5 else "",
"quantity": "" "weight": str(pallet_info[8]) if isinstance(pallet_info, tuple) and len(pallet_info) > 8 else "",
"quantity": ""
}
}
except (IndexError, TypeError) as e:
logging.warning(f"处理托盘信息时出错: {str(e)}, pallet_info类型: {type(pallet_info)}")
# 如果pallet_info是字符串或其他非元组类型创建一个基本响应
response = {
"success": True,
"data": {
"tp_note": tray_code,
"product_name": "",
"axis_type": "",
"tier": "",
"weight": "",
"quantity": ""
}
} }
}
logging.info(f"托盘信息响应: {response}") logging.info(f"托盘信息响应: {response}")

View File

@ -25,7 +25,7 @@ def get_user_info(user_id):
try: try:
# 始终使用SQLite数据源获取用户信息 # 始终使用SQLite数据源获取用户信息
db = SQLUtils(source_name='sqlite') db = SQLUtils(source_name='sqlite')
db.execute_query("SELECT username, 'Default Corp', 1, 1 FROM user WHERE username = ?", (user_id,)) db.execute_query("SELECT username, corp_id as corp_name, corp_id FROM wsbz_user WHERE username = ?", (user_id,))
result = db.fetchone() result = db.fetchone()
db.close() db.close()
if result: if result:
@ -70,7 +70,7 @@ class LoginWidget(LoginUI):
return return
if check_user_login(user_id, password): if check_user_login(user_id, password):
try: try:
user_name, corp_name, corp_id, position_id = get_user_info(user_id) user_name, corp_name, corp_id = get_user_info(user_id)
if not corp_name: if not corp_name:
corp_name = "未知公司" corp_name = "未知公司"
# 移除登录成功的弹框 # 移除登录成功的弹框
@ -81,7 +81,7 @@ class LoginWidget(LoginUI):
try: try:
import logging import logging
logging.info(f"正在创建主窗口用户ID: {user_id}, 姓名: {user_name}, 公司: {corp_name}") logging.info(f"正在创建主窗口用户ID: {user_id}, 姓名: {user_name}, 公司: {corp_name}")
self.main_window = MainWindow(user_id, user_name, corp_name, corp_id, position_id) self.main_window = MainWindow(user_id, user_name, corp_name, corp_id)
self.main_window.showMaximized() # 窗口最大化显示 self.main_window.showMaximized() # 窗口最大化显示
logging.info("主窗口已显示(最大化)") logging.info("主窗口已显示(最大化)")
except Exception as e: except Exception as e:

View File

@ -6,6 +6,8 @@ from datetime import datetime
from pathlib import Path from pathlib import Path
from utils.modbus_utils import ModbusUtils from utils.modbus_utils import ModbusUtils
from utils.modbus_monitor import get_instance as get_modbus_monitor from utils.modbus_monitor import get_instance as get_modbus_monitor
from utils.app_mode import AppMode
from apis.gc_api import GcApi
from utils.register_handlers import ( from utils.register_handlers import (
NGHandler, NGHandler,
WeightDataHandler, WeightDataHandler,
@ -44,14 +46,13 @@ from utils.serial_manager import SerialManager
class MainWindow(MainWindowUI): class MainWindow(MainWindowUI):
"""主窗口""" """主窗口"""
def __init__(self, user_id=None, user_name=None, corp_name=None, corp_id=None, position_id=None): def __init__(self, user_id=None, user_name=None, corp_name=None, corp_id=None):
super().__init__() super().__init__()
self.user_id = user_id self.user_id = user_id
self.user_name = user_name self.user_name = user_name
self.corp_name = corp_name self.corp_name = corp_name
self.corp_id = corp_id self.corp_id = corp_id
self.position_id = position_id
self.init_seq = {} # 初始化轴包装的序号 self.init_seq = {} # 初始化轴包装的序号
self._loading_data_in_progress = False # 数据加载状态标志,防止循环调用 self._loading_data_in_progress = False # 数据加载状态标志,防止循环调用
@ -172,51 +173,18 @@ class MainWindow(MainWindowUI):
# 加载托盘号列表 # 加载托盘号列表
self.load_pallet_codes() self.load_pallet_codes()
def get_axios_num(self,tray_id):
# def add_pallet_type_selectors(self): """获取托盘号对应的轴号"""
# """添加托盘类型选择下拉框""" from dao.inspection_dao import InspectionDAO
# # 创建上料托盘类型选择下拉框 inspection_dao = InspectionDAO()
# self.input_pallet_type_label = QLabel("上料托盘类型:") axios_num = inspection_dao.get_axios_num(tray_id)
# self.input_pallet_type_label.setFont(self.normal_font) return axios_num
# self.input_pallet_type_label.setVisible(False) def get_axios_num_by_order_id(self, order_id):
# self.material_form_layout.addRow(self.input_pallet_type_label) """获取订单号对应的轴号"""
from dao.inspection_dao import InspectionDAO
# self.input_pallet_type_combo = QComboBox() inspection_dao = InspectionDAO()
# self.input_pallet_type_combo.setFont(self.normal_font) axios_num = inspection_dao.get_axios_num_by_order_id(order_id)
# self.input_pallet_type_combo.setVisible(False) return axios_num
# self.material_form_layout.addRow("", self.input_pallet_type_combo)
# # 创建下料托盘类型选择下拉框
# self.output_pallet_type_label = QLabel("下料托盘类型:")
# self.output_pallet_type_label.setFont(self.normal_font)
# self.output_pallet_type_label.setVisible(False)
# self.output_form_layout.addRow(self.output_pallet_type_label)
# self.output_pallet_type_combo = QComboBox()
# self.output_pallet_type_combo.setFont(self.normal_font)
# self.output_pallet_type_combo.setVisible(False)
# self.output_form_layout.addRow("", self.output_pallet_type_combo)
# # 加载托盘类型数据
# self.update_pallet_types()
# def update_pallet_types(self):
# """更新托盘类型下拉框"""
# # 重新加载托盘类型数据
# self.pallet_type_manager.reload_pallet_types()
# # 更新上料托盘类型
# input_types = self.pallet_type_manager.get_pallet_types_by_operation("input")
# self.input_pallet_type_combo.clear()
# for pallet_type in input_types:
# self.input_pallet_type_combo.addItem(pallet_type['type_name'], pallet_type['id'])
# # 更新下料托盘类型
# output_types = self.pallet_type_manager.get_pallet_types_by_operation("output")
# self.output_pallet_type_combo.clear()
# for pallet_type in output_types:
# self.output_pallet_type_combo.addItem(pallet_type['type_name'], pallet_type['id'])
def load_config(self): def load_config(self):
"""加载配置文件""" """加载配置文件"""
config_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "config", "app_config.json") config_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "config", "app_config.json")
@ -308,7 +276,7 @@ class MainWindow(MainWindowUI):
logging.info("显示主页面") logging.info("显示主页面")
def load_pallet_codes(self): def load_pallet_codes(self):
"""从托盘类型管理器加载托盘号并更新到tray_edit""" """从托盘类型管理器加载托盘号并更新到tray_edit TODO要实现动态数据源切换"""
try: try:
# 获取当前文本,以便保留用户选择 # 获取当前文本,以便保留用户选择
current_text = self.tray_edit.currentText() current_text = self.tray_edit.currentText()
@ -696,12 +664,40 @@ class MainWindow(MainWindowUI):
logging.info("工程号输入框按下回车事件") logging.info("工程号输入框按下回车事件")
# 获取当前输入的工程号 # 获取当前输入的工程号
order_text = self.order_edit.text().strip() order_text = self.order_edit.text().strip()
tray_id = self.tray_edit.currentText()
if order_text: if order_text:
logging.info(f"输入的工程号: {order_text}") logging.info(f"输入的工程号: {order_text}")
#判断是否是接口,如果不是接口直接添加如果是则走接口
# 在微丝产线表格中添加一条新记录 # 如果开启接口模式,则需要调用接口同步到业务库
self.add_new_inspection_row(order_text) order_info = None
if AppMode.is_api():
# 调用接口
gc_api = GcApi()
gc_response = gc_api.get_gc_info(order_text)
axios_num = self.get_axios_num(tray_id)
# 防止response为None导致异常
if bool(gc_response.get("status", False)):
# 获取工程号信息,并且初始化数据
data = gc_response.get("data", {})
order_response = gc_api.get_order_info(data.get("ddnote",{}))
if(order_response.get("status",False)):
# 将接口数据保存到数据库,用于后续的关联使用
from dao.inspection_dao import InspectionDAO
inspection_dao = InspectionDAO()
order_info = order_response.get("data", {})[0]
# 设置轴数
order_info["axios_num"] = axios_num
order_info["sc_gch"] = order_text
order_info['user_id'] = self.user_id
order_info['user_name'] = self.user_name
order_info['data_corp'] = self.corp_id
inspection_dao.save_order_info(order_text,order_info)
# 在微丝产线表格中添加一条新记录
self.add_new_inspection_row(order_text,order_info)
else: else:
logging.warning("工程号为空") logging.warning("工程号为空")
@ -710,11 +706,12 @@ class MainWindow(MainWindowUI):
# 处理完后可以清除焦点,让输入框失去焦点 # 处理完后可以清除焦点,让输入框失去焦点
self.central_widget.setFocus() self.central_widget.setFocus()
def add_new_inspection_row(self, order_id): def add_new_inspection_row(self, order_id, order_info):
"""在微丝产线表格中添加一条新记录,添加到表格末尾 """在微丝产线表格中添加一条新记录,添加到表格末尾
Args: Args:
order_id: 工程号 order_id: 工程号
order_info: 从接口获取的工程号信息
""" """
try: try:
# 获取启用的检验配置 # 获取启用的检验配置
@ -757,9 +754,36 @@ class MainWindow(MainWindowUI):
for i, config in enumerate(enabled_configs): for i, config in enumerate(enabled_configs):
col_index = 2 + i # 检验列从第3列开始 col_index = 2 + i # 检验列从第3列开始
# 创建空的可编辑单元格 # 创建单元格
item = QTableWidgetItem("") item = QTableWidgetItem("")
item.setTextAlignment(Qt.AlignCenter) item.setTextAlignment(Qt.AlignCenter)
# 如果有order_info数据尝试匹配字段并设置值
if order_info:
config_name = config.get('name')
# 检查order_info中是否有与config_name匹配的键
if config_name in order_info:
value = str(order_info[config_name])
item = QTableWidgetItem(value)
item.setTextAlignment(Qt.AlignCenter)
# 设置单元格背景为浅绿色,表示自动填充
item.setBackground(QBrush(QColor("#c8e6c9")))
# 保存到数据库
from dao.inspection_dao import InspectionDAO
inspection_dao = InspectionDAO()
tray_id = self.tray_edit.currentText()
data = [{
'position': config.get('position'),
'config_id': config.get('id'),
'value': value,
'status': 'pass', # 默认设置为通过状态
'remark': '',
'tray_id': tray_id
}]
inspection_dao.save_inspection_data(order_id, data)
logging.info(f"自动填充字段 {config_name} 值为 {value}")
# 设置单元格属性以标识其关联的检验项 # 设置单元格属性以标识其关联的检验项
item.setData(Qt.UserRole, config.get('id')) item.setData(Qt.UserRole, config.get('id'))
self.process_table.setItem(data_start_row, col_index, item) self.process_table.setItem(data_start_row, col_index, item)
@ -790,16 +814,20 @@ class MainWindow(MainWindowUI):
tray_id = self.tray_edit.currentText() tray_id = self.tray_edit.currentText()
# 为每个检验位置创建一个空记录,确保工程号在数据库中存在 # 为每个检验位置创建一个空记录,确保工程号在数据库中存在
# 只为没有自动填充值的配置创建空记录
for config in enabled_configs: for config in enabled_configs:
data = [{ config_name = config.get('name')
'position': config.get('position'), # 如果order_info中没有对应的键或者order_info为None
'config_id': config.get('id'), if not order_info or config_name not in order_info:
'value': '', data = [{
'status': '', # 默认设置为通过状态 'position': config.get('position'),
'remark': '', 'config_id': config.get('id'),
'tray_id': tray_id 'value': '',
}] 'status': '', # 默认设置为通过状态
inspection_dao.save_inspection_data(order_id, data) 'remark': '',
'tray_id': tray_id
}]
inspection_dao.save_inspection_data(order_id, data)
# 为贴标和称重也创建空记录 # 为贴标和称重也创建空记录
for position in [11, 12, 13]: # 11是贴标12是毛重13是净重 for position in [11, 12, 13]: # 11是贴标12是毛重13是净重
@ -1045,9 +1073,6 @@ class MainWindow(MainWindowUI):
# 保存到数据库 # 保存到数据库
inspection_dao.save_inspection_data(order_id, data) inspection_dao.save_inspection_data(order_id, data)
# 注意不要在这里调用数据加载方法而是依靠信号和槽机制或QTimer安全地触发加载
except Exception as e: except Exception as e:
logging.error(f"保存检验数据失败: {str(e)}") logging.error(f"保存检验数据失败: {str(e)}")
# 显示错误消息 # 显示错误消息
@ -1055,14 +1080,26 @@ class MainWindow(MainWindowUI):
def _safe_load_data(self): def _safe_load_data(self):
"""安全地加载数据,避免循环调用""" """安全地加载数据,避免循环调用"""
# 获取当前托盘号,用于日志记录
tray_id = self.tray_edit.currentText()
if self._loading_data_in_progress: if self._loading_data_in_progress:
# 如果已经在加载数据,不要再次触发 # 如果已经在加载数据,不要再次触发
logging.debug("已有数据加载正在进行,忽略此次请求") logging.debug(f"已有数据加载正在进行,忽略此次请求 (托盘号: {tray_id})")
return return
try: try:
self._loading_data_in_progress = True self._loading_data_in_progress = True
self.load_finished_inspection_data() self.load_finished_inspection_data()
logging.info(f"数据加载完成,托盘号: {tray_id}")
except Exception as e:
logging.error(f"安全加载数据失败: {str(e)}, 托盘号: {tray_id}")
# 即使加载失败,也尝试显示包装记录
try:
self.show_pack_item()
logging.info(f"加载失败后尝试显示包装记录, 托盘号: {tray_id}")
except Exception as ex:
logging.error(f"加载失败后显示包装记录失败: {str(ex)}, 托盘号: {tray_id}")
finally: finally:
self._loading_data_in_progress = False self._loading_data_in_progress = False
@ -1121,8 +1158,10 @@ class MainWindow(MainWindowUI):
# 添加数据到表格 - 从第3行开始添加数据 # 添加数据到表格 - 从第3行开始添加数据
row_idx = 2 row_idx = 2
# 确保按工程号倒序排列,最新的工程号在最前面 # 使用DAO方法按创建时间排序工程号确保FIFO顺序最早创建的在最前面
sorted_order_ids = sorted(orders_data.keys(), reverse=False) from dao.inspection_dao import InspectionDAO
inspection_dao = InspectionDAO()
sorted_order_ids = inspection_dao.get_orders_by_create_time(list(orders_data.keys()))
for order_id in sorted_order_ids: for order_id in sorted_order_ids:
items = orders_data[order_id] items = orders_data[order_id]
@ -1192,10 +1231,24 @@ class MainWindow(MainWindowUI):
finally: finally:
# 加载包装记录,但要避免循环调用 # 加载包装记录,但要避免循环调用
# 设置一个标志,防止 show_pack_item 触发更多的数据加载 # 设置一个标志,防止 show_pack_item 触发更多的数据加载
if not hasattr(self, '_loading_data_in_progress'): # 只有在_safe_load_data调用此方法且没有明确设置加载状态的情况下才调用
has_loading_flag = hasattr(self, '_loading_data_in_progress')
is_loading = getattr(self, '_loading_data_in_progress', False)
# 如果是被_safe_load_data调用即已经设置了_loading_data_in_progress则无需额外设置
if has_loading_flag and is_loading:
# 直接调用show_pack_item不改变加载状态
try:
self.show_pack_item()
logging.info("在load_finished_inspection_data中调用show_pack_item")
except Exception as e:
logging.error(f"在load_finished_inspection_data中调用show_pack_item失败: {str(e)}")
# 否则这是直接调用此方法非_safe_load_data需要设置加载状态
elif not is_loading:
self._loading_data_in_progress = True self._loading_data_in_progress = True
try: try:
self.show_pack_item() self.show_pack_item()
logging.info("在load_finished_inspection_data中直接调用show_pack_item")
finally: finally:
self._loading_data_in_progress = False self._loading_data_in_progress = False
@ -1248,7 +1301,7 @@ class MainWindow(MainWindowUI):
inspection_dao.save_package_record(order_id, tray_id, label_value, weight_value,net_weight_value, finish_time) inspection_dao.save_package_record(order_id, tray_id, label_value, weight_value,net_weight_value, finish_time)
# 回显数据,但避免循环调用 # 回显数据,但避免循环调用
if not hasattr(self, '_loading_data_in_progress'): if not getattr(self, '_loading_data_in_progress'):
self._loading_data_in_progress = True self._loading_data_in_progress = True
try: try:
self.show_pack_item() self.show_pack_item()
@ -1268,17 +1321,37 @@ class MainWindow(MainWindowUI):
# 获取托盘号 # 获取托盘号
tray_id = self.tray_edit.currentText() tray_id = self.tray_edit.currentText()
logging.info(f"显示包装记录,当前托盘号: {tray_id}")
if not tray_id:
logging.warning("托盘号为空,无法显示包装记录")
# 清空表格
self.record_table.setRowCount(0)
self.update_package_statistics()
return
# 读取已包装的记录信息,然后回显到 UI # 读取已包装的记录信息,然后回显到 UI
package_record = inspection_dao.get_package_record(tray_id) package_record = inspection_dao.get_package_record(tray_id)
# 记录获取的数据情况
if package_record:
logging.info(f"成功获取包装记录,托盘号={tray_id},记录数量={len(package_record)}")
else:
logging.info(f"包装记录为空,托盘号={tray_id}")
# 完全清空包装记录表格(包括所有行) # 完全清空包装记录表格(包括所有行)
self.record_table.setRowCount(0) self.record_table.setRowCount(0)
# 断开包装记录表的信号连接(如果有) # 断开包装记录表的信号连接(如果有)
try: try:
self.record_table.cellChanged.disconnect() # 检查是否已经连接了cellChanged信号
except: if self.record_table.receivers(self.record_table.cellChanged) > 0:
self.record_table.cellChanged.disconnect()
except TypeError:
# 忽略"Failed to disconnect"类型错误
pass pass
except Exception as e:
logging.warning(f"断开record_table.cellChanged信号失败: {str(e)}")
# 设置表头固定不动 # 设置表头固定不动
self.record_table.horizontalHeader().setSectionResizeMode(QHeaderView.Fixed) self.record_table.horizontalHeader().setSectionResizeMode(QHeaderView.Fixed)
@ -1373,6 +1446,8 @@ class MainWindow(MainWindowUI):
# 更新包装记录统计数据 # 更新包装记录统计数据
self.update_package_statistics() self.update_package_statistics()
logging.info(f"包装记录显示完成,托盘号={tray_id},总记录数={self.record_table.rowCount()}")
except Exception as e: except Exception as e:
logging.error(f"显示包装记录失败: {str(e)}") logging.error(f"显示包装记录失败: {str(e)}")
QMessageBox.warning(self, "显示失败", f"显示包装记录失败: {str(e)}") QMessageBox.warning(self, "显示失败", f"显示包装记录失败: {str(e)}")
@ -1681,7 +1756,10 @@ class MainWindow(MainWindowUI):
self.save_inspection_data(order_id, tray_id, 12, 12, str(weight), "pass") self.save_inspection_data(order_id, tray_id, 12, 12, str(weight), "pass")
# 保存净重到数据库(毛重-工字轮重量TODO 先默认工字轮重量为10g后续从接口获取 # 保存净重到数据库(毛重-工字轮重量TODO 先默认工字轮重量为10g后续从接口获取
net_weight = weight - 10 from dao.inspection_dao import InspectionDAO
inspection_dao = InspectionDAO()
gzl_zl = inspection_dao.get_gzl_zl(order_id)
net_weight = float(weight) - float(gzl_zl)
self.save_inspection_data(order_id, tray_id, 13, 13, str(net_weight), "pass") self.save_inspection_data(order_id, tray_id, 13, 13, str(net_weight), "pass")
# 设置净重单元格 # 设置净重单元格
@ -1748,8 +1826,8 @@ class MainWindow(MainWindowUI):
# 计算贴标列索引 - 贴标位置在检验列之后的第一列 # 计算贴标列索引 - 贴标位置在检验列之后的第一列
label_col = 2 + len(enabled_configs) label_col = 2 + len(enabled_configs)
# 生成贴标号(托盘号+号) # 生成贴标号(托盘号+号)
label_value = f"{self.init_seq[tray_id]}" axios_num = self.get_axios_num(tray_id)+1
# 断开单元格变更信号,避免程序自动写入时触发 # 断开单元格变更信号,避免程序自动写入时触发
try: try:
@ -1758,15 +1836,15 @@ class MainWindow(MainWindowUI):
pass pass
# 创建并设置贴标单元格 # 创建并设置贴标单元格
label_item = QTableWidgetItem(label_value) label_item = QTableWidgetItem(axios_num)
label_item.setTextAlignment(Qt.AlignCenter) label_item.setTextAlignment(Qt.AlignCenter)
# 写入单元格 # 写入单元格
self.process_table.setItem(data_row, label_col, label_item) self.process_table.setItem(data_row, label_col, label_item)
logging.info(f"已将贴标数据 {label_value} 写入表格单元格 [{data_row}, {label_col}]") logging.info(f"已将贴标数据 {axios_num} 写入表格单元格 [{data_row}, {label_col}]")
# 保存贴标数据到数据库 # 保存贴标数据到数据库
self.save_inspection_data(order_id, tray_id, 11, 11, label_value, "pass") self.save_inspection_data(order_id, tray_id, 11, 11, axios_num, "pass")
# 调用加载到包装记录的方法 # 调用加载到包装记录的方法
self.load_finished_record_to_package_record(order_id, tray_id) self.load_finished_record_to_package_record(order_id, tray_id)
@ -1775,12 +1853,44 @@ class MainWindow(MainWindowUI):
# 删除当前处理的行 # 删除当前处理的行
self.process_table.removeRow(data_row) self.process_table.removeRow(data_row)
logging.info(f"已删除处理完成的行 {data_row}") logging.info(f"已删除处理完成的行 {data_row}")
#如果开启 api 模式,则调用接口添加到包装记录
if AppMode.is_api():
from dao.inspection_dao import InspectionDAO
inspection_dao = InspectionDAO()
# 调用接口
gc_api = GcApi()
axios_num = self.get_axios_num_by_order_id(order_id)
# 获取订单信息和其他信息,两者都已经是字典格式
info = {}
order_info = inspection_dao.get_order_info(order_id)
info.update(order_info)
order_others_info = inspection_dao.get_order_others_info(order_id, tray_id)
info.update(order_others_info)
info['data_corp'] = 'T'
info['zh'] = axios_num
# 获取本机IP地址
import socket
try:
# 通过连接外部服务器获取本机IP不实际建立连接
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(("8.8.8.8", 80))
local_ip = s.getsockname()[0]
s.close()
info['nw_ip'] = local_ip.replace('.', '')
except Exception as e:
logging.error(f"获取本机IP失败: {str(e)}")
# 如果获取失败,使用本地回环地址
info['nw_ip'] = '127.0.0.1'.replace('.', '')
# 调用接口添加到包装记录
gc_api.add_order_info(info)
# 重新连接单元格变更信号 # 重新连接单元格变更信号
self.process_table.cellChanged.connect(self.handle_inspection_cell_changed) self.process_table.cellChanged.connect(self.handle_inspection_cell_changed)
# 更新托盘号对应的序号
self.init_seq[tray_id] += 1
except Exception as e: except Exception as e:
logging.error(f"处理贴标完成信号失败: {str(e)}") logging.error(f"处理贴标完成信号失败: {str(e)}")
# 确保信号重新连接 # 确保信号重新连接
@ -2257,31 +2367,27 @@ class MainWindow(MainWindowUI):
tray_id = self.tray_edit.currentText() tray_id = self.tray_edit.currentText()
if tray_id: if tray_id:
logging.info(f"托盘号变更为 {tray_id},启动监听") logging.info(f"托盘号变更为 {tray_id},启动监听")
# 确保启动Modbus监控
if not hasattr(self, 'modbus_monitor') or not self.modbus_monitor.is_running():
try:
self.setup_modbus_monitor()
logging.info("已在托盘号变更时启动Modbus监控")
except Exception as e:
logging.error(f"托盘号变更时启动Modbus监控失败: {str(e)}")
# 确保启动串口监听
try:
self.serial_manager.auto_open_configured_ports()
# 启动键盘监听器
self.serial_manager.start_keyboard_listener()
logging.info("已在托盘号变更时启动串口和键盘监听器")
except Exception as e:
logging.error(f"托盘号变更时启动串口监听失败: {str(e)}")
# 初始化托盘号对应的序号(如果不存在) # 初始化托盘号对应的序号(如果不存在)
if tray_id not in self.init_seq: if tray_id not in self.init_seq:
self.init_seq[tray_id] = 1 self.init_seq[tray_id] = 1
logging.info(f"初始化托盘号 {tray_id} 的序号为 1") logging.info(f"初始化托盘号 {tray_id} 的序号为 1")
# 加载数据 # 加载检验数据
self._safe_load_data() self._safe_load_data()
# 无论_safe_load_data是否成功都确保显示包装记录
# 临时保存当前加载状态
prev_loading_state = getattr(self, '_loading_data_in_progress', False)
try:
# 设置加载状态为True避免无限循环调用
self._loading_data_in_progress = True
# 强制显示包装记录
self.show_pack_item()
logging.info(f"托盘号变更:直接调用显示包装记录, 托盘号={tray_id}")
finally:
# 恢复之前的加载状态
self._loading_data_in_progress = prev_loading_state
except Exception as e: except Exception as e:
logging.error(f"处理托盘号变更失败: {str(e)}") logging.error(f"处理托盘号变更失败: {str(e)}")

View File

@ -0,0 +1,37 @@
{'note': 'JT2504019', 'type_name': '不锈钢丝', 'code': 'CP02013', 'corp': '江苏佳腾', 'spack': 'JT2504019-0005', 'tqd': '780', 'zx_name': 'DIN-355/ABS无字白轴', 'cdname': '待定', 'bzfs': '托盘上大纸箱', 'bccd': '0.494', 'type': 'ZL00', 'bz_bqd': '700', 'maxsl': 35.45, 'ysl': None, 'khjq': '2025-05-31', 'price': 19.2, 'sl': 4600.0, 'spack_type': '木托', 'ddzl': '外销合同', 'qfqd': None, 'customerexp': 'JW', 'zzyq': '42-44', 'cd': '待定', 'mo': 'JT2504019-1', 'dycz': None, 'tccd': '0.506', 'zx_code': 'ZX0023', 'bqlb': '14', 'khno': 'SCJW25-010Add', 'template_name': '日本-白签', 'size': '0.5', 'bqd': None, 'remarks_hb': 'G01|生产注意产品表面及排线装箱时做好固定外箱相连两侧各贴一张标签每箱27轴|42-44公斤/轴27轴/箱', 'mxid': 17321, 'ddyq': '42-44公斤/轴27轴/箱', 'cz': '304', 'luno': 'J2410115', 'yzgg': None, 'je': 88320.0, 'zx_zl': '1.95', 'remarks': '42-44公斤/轴27轴/箱', 'rq': '2025-04-08', 'bz_tqd': '800', 'customer': 'JTDW00070'}
size 线
zx_zl
{
"data_corp":,
"user_id":,
"user_name":,
"gzl_zl":,
"mzl":,
"ddmo":,
"xpack":,
"sc_gch":,
"qd":,
"spack_type":,
"mxzs":,
"jt":,
"ddnote":,
"code": ,
"type":,
"lable":,
"lib":,
"gzl":,
"maxsl":,
"cz":,
"size":,
"cd":,
"luno":,
"qfqd":,
"pono":,
"xj":,
"ysl":,
"dycz":,
"zh":,
"edit_id":,
"remarks":,
}