From e3ab0502be27058ab5411f6a9b5b861c8ccb5cdf Mon Sep 17 00:00:00 2001 From: zhu-mengmeng <15588200382@163.com> Date: Fri, 25 Jul 2025 17:15:07 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=E6=A3=80=E9=AA=8C?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=92=8C=E5=8C=85=E8=A3=85=E8=AE=B0=E5=BD=95?= =?UTF-8?q?=E9=80=BB=E8=BE=91=EF=BC=8C=E6=96=B0=E5=A2=9E=E7=AE=B1=E5=8F=B7?= =?UTF-8?q?=E6=94=AF=E6=8C=81=EF=BC=8C=E4=BC=98=E5=8C=96=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=BA=93=E6=9F=A5=E8=AF=A2=E5=92=8C=E4=BF=9D=E5=AD=98=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dao/inspection_dao.py | 280 +++++++++++++++++++++++++++++------------ db/jtDB.db | Bin 1413120 -> 1413120 bytes from pymodbus.py | 2 +- widgets/main_window.py | 62 ++++++--- 4 files changed, 240 insertions(+), 104 deletions(-) diff --git a/dao/inspection_dao.py b/dao/inspection_dao.py index 4f5b6e6..8c39d6d 100644 --- a/dao/inspection_dao.py +++ b/dao/inspection_dao.py @@ -394,14 +394,14 @@ class InspectionDAO: status = item.get('status', '') remark = item.get('remark', '') tray_id = item.get('tray_id', '') - + package_id = item.get('package_id', '') # 获取新游标执行查询,避免递归使用 check_cursor = db.get_new_cursor() check_sql = """ SELECT id FROM wsbz_inspection_data - WHERE order_id = ? AND gc_note = ? AND position = ? AND tray_id = ? + WHERE order_id = ? AND gc_note = ? AND position = ? AND tray_id = ? AND package_id = ? """ - check_params = (order_id, gc_note, position, tray_id) + check_params = (order_id, gc_note, position, tray_id, package_id) check_cursor.execute(check_sql, check_params) existing_record = check_cursor.fetchone() @@ -413,12 +413,12 @@ class InspectionDAO: UPDATE wsbz_inspection_data SET config_id = ?, value = ?, status = ?, remark = ?, update_time = ?, update_by = ? - WHERE order_id = ? AND gc_note = ? AND position = ? AND tray_id = ? + WHERE order_id = ? AND gc_note = ? AND position = ? AND tray_id = ? AND package_id = ? """ update_params = ( config_id, value, status, remark, current_time, username, - order_id, gc_note, position, tray_id + order_id, gc_note, position, tray_id, package_id ) db.execute_update(update_sql, update_params) else: @@ -426,12 +426,12 @@ class InspectionDAO: insert_sql = """ INSERT INTO wsbz_inspection_data ( order_id, gc_note, position, config_id, value, status, remark, - create_time, create_by, update_time, update_by, tray_id - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + create_time, create_by, update_time, update_by, tray_id, package_id + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """ insert_params = ( order_id, gc_note, position, config_id, value, status, remark, - current_time, username, current_time, username, tray_id + current_time, username, current_time, username, tray_id, package_id ) db.execute_update(insert_sql, insert_params) @@ -441,21 +441,36 @@ class InspectionDAO: except Exception as e: logging.error(f"保存检验数据失败: {str(e)}") return False - def get_inspection_data_unfinished(self, tray_id): + def get_inspection_data_unfinished(self, tray_id, package_id=None): """获取未完成的检验数据,通过是否贴标来判断 + Args: + tray_id: 托盘号 + package_id: 箱号(spack),如果为None则只使用tray_id查询 + Returns: list: 未完成的检验数据列表 """ try: # 先获取所有没有贴标的工程号 - sql_orders = """ - SELECT DISTINCT d.gc_note - FROM wsbz_inspection_data d - WHERE d.is_deleted = FALSE AND d.tray_id = ? - AND status != 'labeled' - """ - params = (tray_id,) + if package_id: + # 如果提供了package_id,则同时使用tray_id和package_id查询 + sql_orders = """ + SELECT DISTINCT d.gc_note + FROM wsbz_inspection_data d + WHERE d.is_deleted = FALSE AND d.tray_id = ? AND d.package_id = ? + AND status != 'labeled' + """ + params = (tray_id, package_id) + else: + # 如果没有提供package_id,则只使用tray_id查询 + sql_orders = """ + SELECT DISTINCT d.gc_note + FROM wsbz_inspection_data d + WHERE d.is_deleted = FALSE AND d.tray_id = ? + AND status != 'labeled' + """ + params = (tray_id,) with SQLUtils('sqlite', database='db/jtDB.db') as db: db.cursor.execute(sql_orders, params) gc_notes = db.cursor.fetchall() @@ -468,17 +483,32 @@ class InspectionDAO: placeholders = ','.join(['?' for _ in gc_notes]) # 获取这些工程号的所有检验数据 - sql = f""" - SELECT d.id, d.gc_note, d.position, d.config_id, d.value, d.status, d.remark, - c.name, c.display_name, c.data_type, c.unit - FROM wsbz_inspection_data d - LEFT JOIN wsbz_inspection_config c ON d.config_id = c.id - WHERE d.is_deleted = FALSE AND d.tray_id = ? - AND d.gc_note IN ({placeholders}) - ORDER BY d.create_time - """ - - params = [tray_id] + gc_notes + if package_id: + # 如果提供了package_id,则同时使用tray_id和package_id查询 + sql = f""" + SELECT d.id, d.gc_note, d.position, d.config_id, d.value, d.status, d.remark, + c.name, c.display_name, c.data_type, c.unit, d.package_id + FROM wsbz_inspection_data d + LEFT JOIN wsbz_inspection_config c ON d.config_id = c.id + WHERE d.is_deleted = FALSE AND d.tray_id = ? AND d.package_id = ? + AND d.gc_note IN ({placeholders}) + ORDER BY d.create_time + """ + + params = [tray_id, package_id] + gc_notes + else: + # 如果没有提供package_id,则只使用tray_id查询 + sql = f""" + SELECT d.id, d.gc_note, d.position, d.config_id, d.value, d.status, d.remark, + c.name, c.display_name, c.data_type, c.unit, d.package_id + FROM wsbz_inspection_data d + LEFT JOIN wsbz_inspection_config c ON d.config_id = c.id + WHERE d.is_deleted = FALSE AND d.tray_id = ? + AND d.gc_note IN ({placeholders}) + ORDER BY d.create_time + """ + + params = [tray_id] + gc_notes with SQLUtils('sqlite', database='db/jtDB.db') as db: db.cursor.execute(sql, params) results = db.cursor.fetchall() @@ -496,7 +526,8 @@ class InspectionDAO: 'name': row[7], 'display_name': row[8], 'data_type': row[9], - 'unit': row[10] + 'unit': row[10], + 'package_id': row[11] if len(row) > 11 else '' } data_list.append(data) @@ -504,26 +535,40 @@ class InspectionDAO: except Exception as e: logging.error(f"获取未完成的检验数据失败: {str(e)}") return [] - def get_inspection_data_by_order(self, order_id,gc_note, tray_id): + def get_inspection_data_by_order(self, order_id, gc_note, tray_id, package_id=None): """根据工程号获取检验数据 Args: order_id: 订单号 gc_note: 工程号 tray_id: 托盘号 + package_id: 箱号(spack),如果为None则只使用tray_id查询 Returns: list: 检验数据列表 """ try: - sql = """ - SELECT d.id, d.position, d.config_id, d.value, d.status, d.remark, - c.name, c.display_name, c.data_type, c.unit - FROM wsbz_inspection_data d - LEFT JOIN wsbz_inspection_config c ON d.config_id = c.id - WHERE d.order_id = ? AND d.gc_note = ? AND d.is_deleted = FALSE AND d.tray_id = ? - ORDER BY d.create_time, d.order_id, d.position - """ - params = (order_id, gc_note, tray_id) + if package_id: + # 如果提供了package_id,则同时使用tray_id和package_id查询 + sql = """ + SELECT d.id, d.position, d.config_id, d.value, d.status, d.remark, + c.name, c.display_name, c.data_type, c.unit, d.package_id + FROM wsbz_inspection_data d + LEFT JOIN wsbz_inspection_config c ON d.config_id = c.id + WHERE d.order_id = ? AND d.gc_note = ? AND d.is_deleted = FALSE AND d.tray_id = ? AND d.package_id = ? + ORDER BY d.create_time, d.order_id, d.position + """ + params = (order_id, gc_note, tray_id, package_id) + else: + # 如果没有提供package_id,则只使用tray_id查询 + sql = """ + SELECT d.id, d.position, d.config_id, d.value, d.status, d.remark, + c.name, c.display_name, c.data_type, c.unit, d.package_id + FROM wsbz_inspection_data d + LEFT JOIN wsbz_inspection_config c ON d.config_id = c.id + WHERE d.order_id = ? AND d.gc_note = ? AND d.is_deleted = FALSE AND d.tray_id = ? + ORDER BY d.create_time, d.order_id, d.position + """ + params = (order_id, gc_note, tray_id) with SQLUtils('sqlite', database='db/jtDB.db') as db: db.cursor.execute(sql, params) @@ -541,7 +586,8 @@ class InspectionDAO: 'name': row[6], 'display_name': row[7], 'data_type': row[8], - 'unit': row[9] + 'unit': row[9], + 'package_id': row[10] if len(row) > 10 else '' } data_list.append(data) @@ -550,11 +596,12 @@ class InspectionDAO: logging.error(f"获取检验数据失败: {str(e)}") return [] - def get_package_record(self, tray_id): + def get_package_record(self, tray_id, package_id=None): """根据托盘号获取包装记录 Args: tray_id: 托盘号 + package_id: 箱号(spack),如果为None则只使用tray_id查询 Returns: list: 包装记录列表 """ @@ -583,7 +630,7 @@ class InspectionDAO: except Exception as e: logging.error(f"获取包装记录失败: {str(e)}") return [] - def save_package_record(self, order_id, tray_id, label_value, weight_value, net_weight_value, finish_time, gc_note=None): + def save_package_record(self, order_id, tray_id, label_value, weight_value, net_weight_value, finish_time, gc_note=None, package_id=None): """保存包装记录 Args: @@ -594,17 +641,22 @@ class InspectionDAO: net_weight_value: 净重值 finish_time: 完成时间 gc_note: 工程号 + package_id: 箱号(spack),如果为None则使用tray_id Returns: bool: 保存是否成功 """ # TODO:调用接口,获取到工程号对应的其他信息,比如材质,规格,后续完成 try: + # 如果没有提供package_id,则使用tray_id + if package_id is None: + package_id = tray_id + sql = """ - INSERT INTO wsbz_inspection_pack_data (order_id, tray_id, axis_package_id, weight, net_weight, pack_time, create_time, create_by, update_time, update_by, is_deleted,gc_note) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,?) + INSERT INTO wsbz_inspection_pack_data (order_id, tray_id, axis_package_id, weight, net_weight, pack_time, create_time, create_by, update_time, update_by, is_deleted, gc_note, package_id) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """ - params = (order_id, tray_id, label_value, weight_value, net_weight_value, finish_time, datetime.now(), 'system', datetime.now(), 'system', False,gc_note) + params = (order_id, tray_id, label_value, weight_value, net_weight_value, finish_time, datetime.now(), 'system', datetime.now(), 'system', False, gc_note, package_id) with SQLUtils('sqlite', database='db/jtDB.db') as db: db.begin_transaction() @@ -642,23 +694,33 @@ class InspectionDAO: logging.error(f"获取产品状态失败: {str(e)}") return 'init' # 出错时返回默认状态 - def check_package_record_exists(self, order_id, gc_note, tray_id): + def check_package_record_exists(self, order_id, gc_note, tray_id, package_id=None): """检查指定工程号和托盘号的包装记录是否已存在 Args: order_id: 订单号 gc_note: 工程号 tray_id: 托盘号 + package_id: 箱号(spack),如果为None则只使用tray_id查询 Returns: bool: 记录是否存在 """ try: - sql = """ - SELECT COUNT(*) FROM wsbz_inspection_pack_data - WHERE order_id = ? AND gc_note = ? AND tray_id = ? AND is_deleted = FALSE - """ - params = (order_id, gc_note, tray_id) + if package_id: + # 如果提供了package_id,则同时使用tray_id和package_id查询 + sql = """ + SELECT COUNT(*) FROM wsbz_inspection_pack_data + WHERE order_id = ? AND gc_note = ? AND tray_id = ? AND package_id = ? AND is_deleted = FALSE + """ + params = (order_id, gc_note, tray_id, package_id) + else: + # 如果没有提供package_id,则只使用tray_id查询 + sql = """ + SELECT COUNT(*) FROM wsbz_inspection_pack_data + WHERE order_id = ? AND gc_note = ? AND tray_id = ? AND is_deleted = FALSE + """ + params = (order_id, gc_note, tray_id) with SQLUtils('sqlite', database='db/jtDB.db') as db: db.cursor.execute(sql, params) @@ -670,7 +732,7 @@ class InspectionDAO: logging.error(f"检查包装记录是否存在失败: {str(e)}") return False - def update_product_status(self, order_id, gc_note, tray_id, new_status): + def update_product_status(self, order_id, gc_note, tray_id, new_status, package_id=None): """更新产品的状态 Args: @@ -678,6 +740,7 @@ class InspectionDAO: gc_note: 工程号 tray_id: 托盘号 new_status: 新状态 + package_id: 箱号(spack),如果为None则只使用tray_id查询 Returns: bool: 更新是否成功 @@ -685,32 +748,55 @@ class InspectionDAO: try: with SQLUtils('sqlite', database='db/jtDB.db') as db: # 更新该产品所有记录的状态字段 - update_sql = """ - UPDATE wsbz_inspection_data SET status = ?, update_time = ? - WHERE order_id = ? AND gc_note = ? AND tray_id = ? - """ - update_params = (new_status, datetime.now().strftime('%Y-%m-%d %H:%M:%S'), - order_id, gc_note, tray_id) + if package_id: + # 如果提供了package_id,则同时使用tray_id和package_id查询 + update_sql = """ + UPDATE wsbz_inspection_data SET status = ?, update_time = ? + WHERE order_id = ? AND gc_note = ? AND tray_id = ? AND package_id = ? + """ + update_params = (new_status, datetime.now().strftime('%Y-%m-%d %H:%M:%S'), + order_id, gc_note, tray_id, package_id) + else: + # 如果没有提供package_id,则只使用tray_id查询 + update_sql = """ + UPDATE wsbz_inspection_data SET status = ?, update_time = ? + WHERE order_id = ? AND gc_note = ? AND tray_id = ? + """ + update_params = (new_status, datetime.now().strftime('%Y-%m-%d %H:%M:%S'), + order_id, gc_note, tray_id) db.execute_update(update_sql, update_params) - logging.info(f"已更新产品状态: 订单号={order_id}, 工程号={gc_note}, 托盘号={tray_id}, 新状态={new_status}") + if package_id: + logging.info(f"已更新产品状态: 订单号={order_id}, 工程号={gc_note}, 托盘号={tray_id}, 箱号={package_id}, 新状态={new_status}") + else: + logging.info(f"已更新产品状态: 订单号={order_id}, 工程号={gc_note}, 托盘号={tray_id}, 新状态={new_status}") return True except Exception as e: logging.error(f"更新产品状态失败: {str(e)}") return False - def delete_inspection_data(self, order_id, gc_note, tray_id): + def delete_inspection_data(self, order_id, gc_note, tray_id, package_id=None): """删除检验数据 Args: order_id: 订单号 gc_note: 工程号 tray_id: 托盘号 + package_id: 箱号(spack),如果为None则只使用tray_id查询 """ try: - sql = """ - DELETE FROM wsbz_inspection_data - WHERE gc_note = ? AND tray_id = ? - """ - params = (gc_note, tray_id) + if package_id: + # 如果提供了package_id,则同时使用tray_id和package_id查询 + sql = """ + DELETE FROM wsbz_inspection_data + WHERE gc_note = ? AND tray_id = ? AND package_id = ? + """ + params = (gc_note, tray_id, package_id) + else: + # 如果没有提供package_id,则只使用tray_id查询 + sql = """ + DELETE FROM wsbz_inspection_data + WHERE gc_note = ? AND tray_id = ? + """ + params = (gc_note, tray_id) with SQLUtils('sqlite', database='db/jtDB.db') as db: db.begin_transaction() @@ -1139,35 +1225,48 @@ class InspectionDAO: except Exception as e: logging.error(f"获取炉号信息失败: {str(e)}") return None - def delete_package_record(self, order_id, gc_note, tray_id): + def delete_package_record(self, order_id, gc_note, tray_id, package_id=None): """删除包装记录 Args: order_id: 订单号 gc_note: 工程号 tray_id: 托盘号 + package_id: 箱号(spack),如果为None则只使用tray_id查询 Returns: bool: 删除是否成功 """ try: - sql = """ - DELETE FROM wsbz_inspection_pack_data - WHERE gc_note = ? AND tray_id = ? - """ - params = (gc_note, tray_id) + if package_id: + # 如果提供了package_id,则同时使用tray_id和package_id查询 + sql = """ + DELETE FROM wsbz_inspection_pack_data + WHERE gc_note = ? AND tray_id = ? AND package_id = ? + """ + params = (gc_note, tray_id, package_id) + else: + # 如果没有提供package_id,则只使用tray_id查询 + sql = """ + DELETE FROM wsbz_inspection_pack_data + WHERE gc_note = ? AND tray_id = ? + """ + params = (gc_note, tray_id) with SQLUtils('sqlite', database='db/jtDB.db') as db: db.begin_transaction() db.execute_update(sql, params) db.commit_transaction() - - logging.info(f"已删除包装记录: 订单号={order_id}, 工程号={gc_note}, 托盘号={tray_id}") + + if package_id: + logging.info(f"已删除包装记录: 订单号={order_id}, 工程号={gc_note}, 托盘号={tray_id}, 箱号={package_id}") + else: + logging.info(f"已删除包装记录: 订单号={order_id}, 工程号={gc_note}, 托盘号={tray_id}") return True except Exception as e: logging.error(f"删除包装记录失败: {str(e)}") return False - def get_inspection_data_by_config(self, order_id, gc_note, tray_id, position, config_id): + def get_inspection_data_by_config(self, order_id, gc_note, tray_id, position, config_id, package_id=None): """根据工程号、托盘号、位置和配置ID查询检验数据 Args: @@ -1176,23 +1275,37 @@ class InspectionDAO: tray_id: 托盘号 position: 位置序号 config_id: 配置ID + package_id: 箱号(spack),如果为None则只使用tray_id查询 Returns: dict: 检验数据记录,如果不存在则返回None """ try: # 使用SQLUtils获取数据库连接 - sql = """ - SELECT id, order_id, gc_note, position, config_id, value, status, remark, tray_id, create_time, update_time - FROM wsbz_inspection_data - WHERE order_id = ? AND gc_note = ? AND tray_id = ? AND position = ? AND config_id = ? - ORDER BY update_time DESC - LIMIT 1 - """ + if package_id: + # 如果提供了package_id,则同时使用tray_id和package_id查询 + sql = """ + SELECT id, order_id, gc_note, position, config_id, value, status, remark, tray_id, package_id, create_time, update_time + FROM wsbz_inspection_data + WHERE order_id = ? AND gc_note = ? AND tray_id = ? AND position = ? AND config_id = ? AND package_id = ? + ORDER BY update_time DESC + LIMIT 1 + """ + params = (order_id, gc_note, tray_id, position, config_id, package_id) + else: + # 如果没有提供package_id,则只使用tray_id查询 + sql = """ + SELECT id, order_id, gc_note, position, config_id, value, status, remark, tray_id, package_id, create_time, update_time + FROM wsbz_inspection_data + WHERE order_id = ? AND gc_note = ? AND tray_id = ? AND position = ? AND config_id = ? + ORDER BY update_time DESC + LIMIT 1 + """ + params = (order_id, gc_note, tray_id, position, config_id) with SQLUtils('sqlite', database='db/jtDB.db') as db: # 执行查询 - db.cursor.execute(sql, (order_id, gc_note, tray_id, position, config_id)) + db.cursor.execute(sql, params) # 获取结果 row = db.cursor.fetchone() @@ -1209,8 +1322,9 @@ class InspectionDAO: 'status': row[6], 'remark': row[7], 'tray_id': row[8], - 'create_time': row[9], - 'update_time': row[10] + 'package_id': row[9], + 'create_time': row[10], + 'update_time': row[11] } else: return None diff --git a/db/jtDB.db b/db/jtDB.db index b0e337c52fb20d75d0df678b534e4dd7604affd5..e53d788282c08cf21160a4adbb87c5c69ee68f84 100644 GIT binary patch delta 4098 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 r9F