Milestone ultima alpha

This commit is contained in:
2026-05-22 14:25:09 +02:00
parent 8489cd7459
commit a5e704c214
25 changed files with 3896 additions and 273 deletions

View File

@@ -7,8 +7,12 @@ from tkinter import filedialog, messagebox, ttk
import customtkinter as ctk
from gestione_aree import AsyncRunner, BusyOverlay
from busy_overlay import InlineBusyOverlay
from gestione_aree import AsyncRunner
from locale_text import load_locale_catalog, text as loc_text
from ui_theme import theme_color, theme_font, theme_section, theme_value
from window_placement import place_window_fullsize_below_parent_later
from tooltips import WidgetToolTip, load_tooltip_catalog, tooltip_text
try:
from openpyxl import Workbook
@@ -78,14 +82,22 @@ class SearchWindow(ctk.CTkToplevel):
def __init__(self, parent: tk.Widget, db_app, session=None):
"""Initialize widgets and keep a reference to the shared DB client."""
super().__init__(parent)
self.title("Warehouse - Ricerca UDC/Lotto/Codice")
self.geometry("1100x720")
self.minsize(900, 560)
self._theme = theme_section("search_window", {})
self._locale_catalog = load_locale_catalog()
self._tooltip_catalog = load_tooltip_catalog()
self.title(loc_text("search.title", catalog=self._locale_catalog, default="Ricerca UDC/Lotto/Codice"))
self.geometry(str(theme_value(self._theme, "window_geometry", "1100x720")))
minsize = theme_value(self._theme, "window_minsize", [900, 560])
self.minsize(int(minsize[0]), int(minsize[1]))
self.resizable(True, True)
self.db = db_app
self.session = session
self._busy = BusyOverlay(self)
try:
self.configure(fg_color=theme_color(self._theme, "window_fg_color", ("#efefef", "#2f2f2f")))
except Exception:
pass
self._busy = InlineBusyOverlay(self, self._theme)
self._async = AsyncRunner(self)
self._sort_state: dict[str, bool] = {}
@@ -97,26 +109,62 @@ class SearchWindow(ctk.CTkToplevel):
self.grid_rowconfigure(1, weight=1)
self.grid_columnconfigure(0, weight=1)
top = ctk.CTkFrame(self)
top = ctk.CTkFrame(
self,
fg_color=theme_color(self._theme, "toolbar_frame_fg_color", ("#d7d7d7", "#3b3b3b")),
)
top.grid(row=0, column=0, sticky="nsew", padx=8, pady=8)
for i in range(8):
top.grid_columnconfigure(i, weight=0)
top.grid_columnconfigure(7, weight=1)
ctk.CTkLabel(top, text="UDC:").grid(row=0, column=0, sticky="w")
label_font = theme_font(self._theme, "toolbar_label_font", ("Segoe UI", 10))
entry_font = theme_font(self._theme, "entry_font", ("Segoe UI", 10))
button_font = theme_font(self._theme, "toolbar_button_font", ("Segoe UI", 10, "bold"))
ctk.CTkLabel(
top, text=loc_text("search.label.udc", catalog=self._locale_catalog, default="UDC:"), font=label_font
).grid(row=0, column=0, sticky="w")
self.var_udc = tk.StringVar()
ctk.CTkEntry(top, textvariable=self.var_udc, width=160).grid(row=0, column=1, sticky="w", padx=(4, 12))
ctk.CTkEntry(top, textvariable=self.var_udc, width=160, font=entry_font).grid(
row=0, column=1, sticky="w", padx=(4, 12)
)
ctk.CTkLabel(top, text="Lotto:").grid(row=0, column=2, sticky="w")
ctk.CTkLabel(
top, text=loc_text("search.label.lot", catalog=self._locale_catalog, default="Lotto:"), font=label_font
).grid(row=0, column=2, sticky="w")
self.var_lotto = tk.StringVar()
ctk.CTkEntry(top, textvariable=self.var_lotto, width=140).grid(row=0, column=3, sticky="w", padx=(4, 12))
ctk.CTkEntry(top, textvariable=self.var_lotto, width=140, font=entry_font).grid(
row=0, column=3, sticky="w", padx=(4, 12)
)
ctk.CTkLabel(top, text="Codice prodotto:").grid(row=0, column=4, sticky="w")
ctk.CTkLabel(
top,
text=loc_text("search.label.code", catalog=self._locale_catalog, default="Codice prodotto:"),
font=label_font,
).grid(row=0, column=4, sticky="w")
self.var_codice = tk.StringVar()
ctk.CTkEntry(top, textvariable=self.var_codice, width=160).grid(row=0, column=5, sticky="w", padx=(4, 12))
ctk.CTkEntry(top, textvariable=self.var_codice, width=160, font=entry_font).grid(
row=0, column=5, sticky="w", padx=(4, 12)
)
ctk.CTkButton(top, text="Cerca", command=self._do_search).grid(row=0, column=6, sticky="w")
ctk.CTkButton(top, text="Esporta XLSX", command=self._export_xlsx).grid(row=0, column=7, sticky="e")
btn_search = ctk.CTkButton(
top,
text=loc_text("search.button.search", catalog=self._locale_catalog, default="Cerca"),
command=self._do_search,
font=button_font,
)
btn_search.grid(row=0, column=6, sticky="w")
btn_export = ctk.CTkButton(
top,
text=loc_text("search.button.export", catalog=self._locale_catalog, default="Esporta XLSX"),
command=self._export_xlsx,
font=button_font,
)
btn_export.grid(row=0, column=7, sticky="e")
tip = tooltip_text("launcher.open_search", catalog=self._tooltip_catalog)
if tip:
WidgetToolTip(btn_search, tip)
wrap = ctk.CTkFrame(self)
wrap.grid(row=1, column=0, sticky="nsew", padx=8, pady=(0, 8))
@@ -168,10 +216,22 @@ class SearchWindow(ctk.CTkToplevel):
"""Export the currently visible search results to an Excel file."""
rows = [self.tree.item(iid, "values") for iid in self.tree.get_children("")]
if not rows:
messagebox.showinfo("Esporta", "Non ci sono righe da esportare.", parent=self)
messagebox.showinfo(
loc_text("search.msg.export_title", catalog=self._locale_catalog, default="Esporta"),
loc_text("search.msg.export_empty", catalog=self._locale_catalog, default="Non ci sono righe da esportare."),
parent=self,
)
return
if not _HAS_XLSX:
messagebox.showerror("Esporta", "Per l'esportazione serve 'openpyxl' (pip install openpyxl).", parent=self)
messagebox.showerror(
loc_text("search.msg.export_title", catalog=self._locale_catalog, default="Esporta"),
loc_text(
"search.msg.export_dep",
catalog=self._locale_catalog,
default="Per l'esportazione serve 'openpyxl' (pip install openpyxl).",
),
parent=self,
)
return
from datetime import datetime
@@ -211,9 +271,17 @@ class SearchWindow(ctk.CTkToplevel):
for j, width in widths.items():
ws.column_dimensions[get_column_letter(j)].width = min(max(width + 2, 10), 60)
wb.save(fname)
messagebox.showinfo("Esporta", f"File creato:\n{fname}", parent=self)
messagebox.showinfo(
loc_text("search.msg.export_title", catalog=self._locale_catalog, default="Esporta"),
f"File creato:\n{fname}",
parent=self,
)
except Exception as ex:
messagebox.showerror("Esporta", f"Errore durante l'esportazione:{ex}", parent=self)
messagebox.showerror(
loc_text("search.msg.export_title", catalog=self._locale_catalog, default="Esporta"),
loc_text("search.msg.export_error", catalog=self._locale_catalog, default="Errore durante l'esportazione:{error}").format(error=ex),
parent=self,
)
def _on_dclick(self, evt):
"""Copy the selected pallet barcode when a result cell is double-clicked."""
@@ -357,8 +425,12 @@ class SearchWindow(ctk.CTkToplevel):
if not (udc or lotto or codice):
if not messagebox.askyesno(
"Conferma",
"Nessun filtro impostato. Vuoi cercare su TUTTO il magazzino?",
loc_text("search.msg.confirm_title", catalog=self._locale_catalog, default="Conferma"),
loc_text(
"search.msg.confirm_all",
catalog=self._locale_catalog,
default="Nessun filtro impostato. Vuoi cercare su TUTTO il magazzino?",
),
parent=self,
):
return
@@ -396,8 +468,12 @@ class SearchWindow(ctk.CTkToplevel):
if not rows:
messagebox.showinfo(
"Nessun risultato",
"Nessuna corrispondenza trovata con le chiavi di ricerca inserite.",
loc_text("search.msg.no_results_title", catalog=self._locale_catalog, default="Nessun risultato"),
loc_text(
"search.msg.no_results",
catalog=self._locale_catalog,
default="Nessuna corrispondenza trovata con le chiavi di ricerca inserite.",
),
parent=self,
)
else:
@@ -408,9 +484,19 @@ class SearchWindow(ctk.CTkToplevel):
def _err(ex):
self._busy.hide()
messagebox.showerror("Errore ricerca", str(ex), parent=self)
messagebox.showerror(
loc_text("search.msg.error_title", catalog=self._locale_catalog, default="Errore ricerca"),
str(ex),
parent=self,
)
self._async.run(self.db.query_json(SQL_SEARCH, params), _ok, _err, busy=self._busy, message="Cerco...")
self._async.run(
self.db.query_json(SQL_SEARCH, params),
_ok,
_err,
busy=self._busy,
message=loc_text("search.busy", catalog=self._locale_catalog, default="Cerco..."),
)
def open_search_window(parent, db_app, session=None):
@@ -427,4 +513,9 @@ def open_search_window(parent, db_app, session=None):
w = SearchWindow(parent, db_app, session=session)
setattr(parent, key, w)
place_window_fullsize_below_parent_later(parent, w)
try:
w.lift()
w.focus_force()
except Exception:
pass
return w