From 7f3ce9d3afbb09ccc851a3330f59d081a3dc3b76 Mon Sep 17 00:00:00 2001 From: allebonvi Date: Mon, 6 Jul 2026 18:14:13 +0200 Subject: [PATCH] Aggiungi export XLSX diagnostica --- diagnostica.py | 107 +++++++++++++++++++++++++++++++++++++++++++++++- locale.json | 12 ++++++ version_info.py | 2 +- 3 files changed, 119 insertions(+), 2 deletions(-) diff --git a/diagnostica.py b/diagnostica.py index cca6627..f8d6b84 100644 --- a/diagnostica.py +++ b/diagnostica.py @@ -3,7 +3,8 @@ from __future__ import annotations import tkinter as tk -from tkinter import messagebox, ttk +from datetime import datetime +from tkinter import filedialog, messagebox, ttk from typing import Any import customtkinter as ctk @@ -72,6 +73,7 @@ class DiagnosticaWindow(ctk.CTkToplevel): self._load_in_progress = False self._current_check = "" self.var_status = tk.StringVar(value="") + self._last_export_slug = "diagnostica" self.title( versioned_title( @@ -124,6 +126,15 @@ class DiagnosticaWindow(ctk.CTkToplevel): font=button_font, ).grid(row=0, column=0, sticky="w", padx=(0, 8)) + self.btn_export = ctk.CTkButton( + top, + text=loc_text("diagnostics.button.export", catalog=self._locale_catalog, default="Esporta XLSX"), + command=self._export_xlsx, + font=button_font, + state="disabled", + ) + self.btn_export.grid(row=0, column=2, sticky="e") + ctk.CTkLabel( self, textvariable=self.var_status, @@ -205,6 +216,11 @@ class DiagnosticaWindow(ctk.CTkToplevel): self._load_in_progress = True self._current_check = key + self._last_export_slug = key + try: + self.btn_export.configure(state="disabled") + except Exception: + pass try: self._busy.show(busy_text) self._async.run(job(), self._on_loaded, self._on_error) @@ -237,6 +253,10 @@ class DiagnosticaWindow(ctk.CTkToplevel): ), tags=(zebra_tag(index),), ) + try: + self.btn_export.configure(state="normal" if rows else "disabled") + except Exception: + pass self._set_status( loc_text( "diagnostics.status.rows", @@ -264,6 +284,91 @@ class DiagnosticaWindow(ctk.CTkToplevel): parent=self, ) + def _export_xlsx(self) -> None: + """Export the currently visible diagnostic grid to an Excel file.""" + + rows = [self.tree.item(iid, "values") for iid in self.tree.get_children("")] + if not rows: + messagebox.showinfo( + loc_text("diagnostics.export.title", catalog=self._locale_catalog, default="Esporta"), + loc_text( + "diagnostics.export.empty", + catalog=self._locale_catalog, + default="Non ci sono righe da esportare.", + ), + parent=self, + ) + return + + try: + from openpyxl import Workbook + from openpyxl.styles import Alignment, Font + from openpyxl.utils import get_column_letter + except Exception as exc: + messagebox.showerror( + loc_text("diagnostics.export.title", catalog=self._locale_catalog, default="Esporta"), + loc_text( + "diagnostics.export.dep", + catalog=self._locale_catalog, + default="Per l'esportazione serve 'openpyxl' (pip install openpyxl).", + ).format(error=exc), + parent=self, + ) + return + + ts = datetime.now().strftime("%Y%m%d_%H%M") + default_name = f"{self._last_export_slug}_{ts}.xlsx" + path = filedialog.asksaveasfilename( + parent=self, + title=loc_text("diagnostics.export.title", catalog=self._locale_catalog, default="Esporta"), + defaultextension=".xlsx", + filetypes=[("Excel Workbook", "*.xlsx")], + initialfile=default_name, + ) + if not path: + return + + try: + wb = Workbook() + ws = wb.active + ws.title = "Diagnostica" + columns = list(self.tree["columns"]) + headers = [str(self.tree.heading(col, "text") or col) for col in columns] + + for col_index, header in enumerate(headers, start=1): + cell = ws.cell(row=1, column=col_index, value=header) + cell.font = Font(bold=True) + cell.alignment = Alignment(horizontal="center", vertical="center") + + for row_index, row in enumerate(rows, start=2): + for col_index, value in enumerate(row, start=1): + ws.cell(row=row_index, column=col_index, value=value) + + widths: dict[int, int] = {} + for row in ws.iter_rows(values_only=True): + for col_index, value in enumerate(row, start=1): + widths[col_index] = max(widths.get(col_index, 0), len("" if value is None else str(value))) + for col_index, width in widths.items(): + ws.column_dimensions[get_column_letter(col_index)].width = min(max(width + 2, 12), 70) + + wb.save(path) + messagebox.showinfo( + loc_text("diagnostics.export.title", catalog=self._locale_catalog, default="Esporta"), + loc_text("diagnostics.export.ok", catalog=self._locale_catalog, default="File creato:\n{path}").format(path=path), + parent=self, + ) + except Exception as exc: + log_exception(__name__, exc, context="export diagnostica xlsx") + messagebox.showerror( + loc_text("diagnostics.export.title", catalog=self._locale_catalog, default="Esporta"), + loc_text( + "diagnostics.export.error", + catalog=self._locale_catalog, + default="Errore durante l'esportazione:\n{error}", + ).format(error=exc), + parent=self, + ) + def open_diagnostica_window(parent: tk.Misc, db_client, session=None) -> tk.Misc: """Open the diagnostics window.""" diff --git a/locale.json b/locale.json index 379d042..346f830 100644 --- a/locale.json +++ b/locale.json @@ -78,11 +78,17 @@ "history.picking.msg.detail_error": "Errore dettaglio:\n{error}", "diagnostics.title": "Diagnostica", "diagnostics.button.shipped_not_disassociated": "UDC spedite non disassociate", + "diagnostics.button.export": "Esporta XLSX", "diagnostics.busy.shipped_not_disassociated": "Cerco UDC spedite ma ancora in cella...", "diagnostics.status.rows": "Righe trovate: {count}", "diagnostics.col.udc": "UDC", "diagnostics.col.shipped_not_disassociated": "Spedita ma non disassociata", "diagnostics.col.symbolic_cell": "Nome simbolico cella", + "diagnostics.export.title": "Esporta", + "diagnostics.export.empty": "Non ci sono righe da esportare.", + "diagnostics.export.dep": "Per l'esportazione serve 'openpyxl' (pip install openpyxl).", + "diagnostics.export.ok": "File creato:\n{path}", + "diagnostics.export.error": "Errore durante l'esportazione:\n{error}", "reset.title": "Gestione Corsie - svuotamento celle per corsia", "reset.label.aisle": "Corsia:", "reset.button.refresh": "Carica", @@ -201,11 +207,17 @@ "history.picking.msg.detail_error": "Detail load failed:\n{error}", "diagnostics.title": "Diagnostics", "diagnostics.button.shipped_not_disassociated": "Shipped UDC not disassociated", + "diagnostics.button.export": "Export XLSX", "diagnostics.busy.shipped_not_disassociated": "Searching shipped UDC still assigned to cells...", "diagnostics.status.rows": "Rows found: {count}", "diagnostics.col.udc": "UDC", "diagnostics.col.shipped_not_disassociated": "Shipped but not disassociated", "diagnostics.col.symbolic_cell": "Symbolic cell name", + "diagnostics.export.title": "Export", + "diagnostics.export.empty": "There are no rows to export.", + "diagnostics.export.dep": "Export requires 'openpyxl' (pip install openpyxl).", + "diagnostics.export.ok": "File created:\n{path}", + "diagnostics.export.error": "Export failed:\n{error}", "reset.title": "Aisle Management - empty cells by aisle", "reset.label.aisle": "Aisle:", "reset.button.refresh": "Load", diff --git a/version_info.py b/version_info.py index 7eea97a..7b1987d 100644 --- a/version_info.py +++ b/version_info.py @@ -18,7 +18,7 @@ MODULE_VERSIONS: dict[str, str] = { "barcode_service": "1.0.24", "busy_overlay": "1.0.0", "db_config": "1.0.0", - "diagnostica": "1.0.0", + "diagnostica": "1.0.1", "gestione_aree": "1.0.1", "gestione_layout": "1.0.2", "gestione_pickinglist": "1.0.4",