Aggiungi export XLSX diagnostica

This commit is contained in:
2026-07-06 18:14:13 +02:00
parent 8e9294f80b
commit 7f3ce9d3af
3 changed files with 119 additions and 2 deletions

View File

@@ -3,7 +3,8 @@
from __future__ import annotations from __future__ import annotations
import tkinter as tk import tkinter as tk
from tkinter import messagebox, ttk from datetime import datetime
from tkinter import filedialog, messagebox, ttk
from typing import Any from typing import Any
import customtkinter as ctk import customtkinter as ctk
@@ -72,6 +73,7 @@ class DiagnosticaWindow(ctk.CTkToplevel):
self._load_in_progress = False self._load_in_progress = False
self._current_check = "" self._current_check = ""
self.var_status = tk.StringVar(value="") self.var_status = tk.StringVar(value="")
self._last_export_slug = "diagnostica"
self.title( self.title(
versioned_title( versioned_title(
@@ -124,6 +126,15 @@ class DiagnosticaWindow(ctk.CTkToplevel):
font=button_font, font=button_font,
).grid(row=0, column=0, sticky="w", padx=(0, 8)) ).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( ctk.CTkLabel(
self, self,
textvariable=self.var_status, textvariable=self.var_status,
@@ -205,6 +216,11 @@ class DiagnosticaWindow(ctk.CTkToplevel):
self._load_in_progress = True self._load_in_progress = True
self._current_check = key self._current_check = key
self._last_export_slug = key
try:
self.btn_export.configure(state="disabled")
except Exception:
pass
try: try:
self._busy.show(busy_text) self._busy.show(busy_text)
self._async.run(job(), self._on_loaded, self._on_error) self._async.run(job(), self._on_loaded, self._on_error)
@@ -237,6 +253,10 @@ class DiagnosticaWindow(ctk.CTkToplevel):
), ),
tags=(zebra_tag(index),), tags=(zebra_tag(index),),
) )
try:
self.btn_export.configure(state="normal" if rows else "disabled")
except Exception:
pass
self._set_status( self._set_status(
loc_text( loc_text(
"diagnostics.status.rows", "diagnostics.status.rows",
@@ -264,6 +284,91 @@ class DiagnosticaWindow(ctk.CTkToplevel):
parent=self, 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: def open_diagnostica_window(parent: tk.Misc, db_client, session=None) -> tk.Misc:
"""Open the diagnostics window.""" """Open the diagnostics window."""

View File

@@ -78,11 +78,17 @@
"history.picking.msg.detail_error": "Errore dettaglio:\n{error}", "history.picking.msg.detail_error": "Errore dettaglio:\n{error}",
"diagnostics.title": "Diagnostica", "diagnostics.title": "Diagnostica",
"diagnostics.button.shipped_not_disassociated": "UDC spedite non disassociate", "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.busy.shipped_not_disassociated": "Cerco UDC spedite ma ancora in cella...",
"diagnostics.status.rows": "Righe trovate: {count}", "diagnostics.status.rows": "Righe trovate: {count}",
"diagnostics.col.udc": "UDC", "diagnostics.col.udc": "UDC",
"diagnostics.col.shipped_not_disassociated": "Spedita ma non disassociata", "diagnostics.col.shipped_not_disassociated": "Spedita ma non disassociata",
"diagnostics.col.symbolic_cell": "Nome simbolico cella", "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.title": "Gestione Corsie - svuotamento celle per corsia",
"reset.label.aisle": "Corsia:", "reset.label.aisle": "Corsia:",
"reset.button.refresh": "Carica", "reset.button.refresh": "Carica",
@@ -201,11 +207,17 @@
"history.picking.msg.detail_error": "Detail load failed:\n{error}", "history.picking.msg.detail_error": "Detail load failed:\n{error}",
"diagnostics.title": "Diagnostics", "diagnostics.title": "Diagnostics",
"diagnostics.button.shipped_not_disassociated": "Shipped UDC not disassociated", "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.busy.shipped_not_disassociated": "Searching shipped UDC still assigned to cells...",
"diagnostics.status.rows": "Rows found: {count}", "diagnostics.status.rows": "Rows found: {count}",
"diagnostics.col.udc": "UDC", "diagnostics.col.udc": "UDC",
"diagnostics.col.shipped_not_disassociated": "Shipped but not disassociated", "diagnostics.col.shipped_not_disassociated": "Shipped but not disassociated",
"diagnostics.col.symbolic_cell": "Symbolic cell name", "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.title": "Aisle Management - empty cells by aisle",
"reset.label.aisle": "Aisle:", "reset.label.aisle": "Aisle:",
"reset.button.refresh": "Load", "reset.button.refresh": "Load",

View File

@@ -18,7 +18,7 @@ MODULE_VERSIONS: dict[str, str] = {
"barcode_service": "1.0.24", "barcode_service": "1.0.24",
"busy_overlay": "1.0.0", "busy_overlay": "1.0.0",
"db_config": "1.0.0", "db_config": "1.0.0",
"diagnostica": "1.0.0", "diagnostica": "1.0.1",
"gestione_aree": "1.0.1", "gestione_aree": "1.0.1",
"gestione_layout": "1.0.2", "gestione_layout": "1.0.2",
"gestione_pickinglist": "1.0.4", "gestione_pickinglist": "1.0.4",