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

@@ -66,7 +66,10 @@ except Exception:
natural_sort_key = None # type: ignore[assignment]
# Usa overlay e runner "collaudati"
from gestione_aree import BusyOverlay, AsyncRunner
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 user_session import UserSession
from window_placement import place_window_fullsize_below_parent_later
@@ -583,12 +586,18 @@ class GestionePickingListFrame(ctk.CTkFrame):
def __init__(self, master, *, db_client=None, conn_str=None, session: UserSession | None = None):
"""Create the master/detail picking list frame."""
super().__init__(master)
self._theme = theme_section("pickinglist_window", {})
self._locale_catalog = load_locale_catalog()
if db_client is None:
raise ValueError("GestionePickingListFrame richiede un db_client condiviso.")
self.db_client = db_client
self.session = session
self.runner = AsyncRunner(self) # runner condiviso (usa loop globale)
self.busy = BusyOverlay(self) # overlay collaudato
self.busy = InlineBusyOverlay(self, self._theme)
try:
self.configure(fg_color=theme_color(self._theme, "window_fg_color", ("#efefef", "#2f2f2f")))
except Exception:
pass
self.rows_models: list[PLRow] = []
self._detail_cache: Dict[Any, list] = {}
@@ -601,8 +610,7 @@ class GestionePickingListFrame(ctk.CTkFrame):
self._render_job = None # Tracking del job di rendering in corso
self._build_layout()
# 🔇 Niente reload immediato: carichiamo quando la finestra è idle (= già resa)
self.after_idle(self._first_show)
self._initial_load_started = False
def _can(self, action: str) -> bool:
"""Return whether the current user can execute one picking-list action."""
@@ -616,6 +624,9 @@ class GestionePickingListFrame(ctk.CTkFrame):
def _first_show(self):
"""Chiamato a finestra già resa → evitiamo sfarfallio del primo paint e mostriamo wait-cursor."""
if self._initial_load_started:
return
self._initial_load_started = True
self._first_loading = True
try:
self.winfo_toplevel().configure(cursor="watch")
@@ -633,13 +644,22 @@ class GestionePickingListFrame(ctk.CTkFrame):
top = ctk.CTkFrame(self)
top.grid(row=0, column=0, sticky="ew", padx=10, pady=(8,4))
try:
top.configure(fg_color=theme_color(self._theme, "toolbar_frame_fg_color", ("#d7d7d7", "#3b3b3b")))
except Exception:
pass
for i, (text, cmd) in enumerate([
("Ricarica", self.reload_from_db),
("Prenota", self.on_prenota),
("S-prenota", self.on_sprenota),
("Esporta XLSX", self.on_export)
(loc_text("picking.button.reload", catalog=self._locale_catalog, default="Ricarica"), self.reload_from_db),
(loc_text("picking.button.prenota", catalog=self._locale_catalog, default="Prenota"), self.on_prenota),
(loc_text("picking.button.sprenota", catalog=self._locale_catalog, default="S-prenota"), self.on_sprenota),
(loc_text("picking.button.export", catalog=self._locale_catalog, default="Esporta XLSX"), self.on_export)
]):
ctk.CTkButton(top, text=text, command=cmd).grid(row=0, column=i, padx=6)
ctk.CTkButton(
top,
text=text,
command=cmd,
font=theme_font(self._theme, "toolbar_button_font", ("Segoe UI", 10, "bold")),
).grid(row=0, column=i, padx=6)
# --- micro spinner a destra della toolbar ---
self.spinner = ToolbarSpinner(top)
@@ -942,7 +962,7 @@ class GestionePickingListFrame(ctk.CTkFrame):
# ----- load PL -----
@_log_call()
def reload_from_db(self, first: bool = False):
def reload_from_db(self, first: bool = False, reselect_documento: str | None = None):
"""Load or reload the picking list summary table from the database."""
self.spinner.start(" Carico…") # spinner ON
async def _job():
@@ -952,6 +972,8 @@ class GestionePickingListFrame(ctk.CTkFrame):
rows = _rows_to_dicts(res)
_log_dataset("SQL_PL", rows)
self._refresh_mid_rows(rows)
if reselect_documento:
self.after_idle(lambda doc=reselect_documento: self._reselect_documento_after_reload(doc))
self.spinner.stop() # spinner OFF
# se era il primo load, ripristina il cursore standard
if self._first_loading:
@@ -1057,7 +1079,7 @@ class GestionePickingListFrame(ctk.CTkFrame):
self.spinner.start(" Prenoto…")
async def _job():
return await sp_xExePackingListPallet_async(self.db_client, id_operatore, documento)
return await sp_xExePackingListPallet_async(self.db_client, id_operatore, documento, "P")
def _ok(res: SPResult):
_MODULE_LOGGER.log(_MODULE_LOG_LEVEL, f"Esito prenotazione documento={documento} rc={getattr(res, 'rc', None)} message={getattr(res, 'message', None)}")
@@ -1070,7 +1092,8 @@ class GestionePickingListFrame(ctk.CTkFrame):
outcome="ok",
target=documento,
)
self._recolor_row_by_documento(documento, desired)
self._detail_cache.pop(documento, None)
self.reload_from_db(reselect_documento=documento)
else:
msg = (res.message if res else "Errore sconosciuto")
log_user_action(
@@ -1130,7 +1153,7 @@ class GestionePickingListFrame(ctk.CTkFrame):
self.spinner.start(" S-prenoto…")
async def _job():
return await sp_xExePackingListPallet_async(self.db_client, id_operatore, documento)
return await sp_xExePackingListPallet_async(self.db_client, id_operatore, documento, "S")
def _ok(res: SPResult):
_MODULE_LOGGER.log(_MODULE_LOG_LEVEL, f"Esito s-prenotazione documento={documento} rc={getattr(res, 'rc', None)} message={getattr(res, 'message', None)}")
@@ -1143,7 +1166,8 @@ class GestionePickingListFrame(ctk.CTkFrame):
outcome="ok",
target=documento,
)
self._recolor_row_by_documento(documento, desired)
self._detail_cache.pop(documento, None)
self.reload_from_db(reselect_documento=documento)
else:
msg = (res.message if res else "Errore sconosciuto")
log_user_action(
@@ -1215,9 +1239,12 @@ def open_pickinglist_window(parent: tk.Misc, db_client, session: UserSession | N
pass
win = ctk.CTkToplevel(parent)
win.title("Gestione Picking List")
win.geometry("1200x700")
win.minsize(1000, 560)
locale_catalog = load_locale_catalog()
win.title(loc_text("picking.title", catalog=locale_catalog, default="Gestione Picking List"))
theme = theme_section("pickinglist_window", {})
win.geometry(str(theme_value(theme, "window_geometry", "1200x700")))
minsize = theme_value(theme, "window_minsize", [1000, 560])
win.minsize(int(minsize[0]), int(minsize[1]))
setattr(parent, key, win)
# Keep the toplevel hidden until the child frame has built its initial layout.
@@ -1238,19 +1265,9 @@ def open_pickinglist_window(parent: tk.Misc, db_client, session: UserSession | N
try:
win.update_idletasks()
place_window_fullsize_below_parent_later(parent, win)
try:
win.deiconify()
except Exception:
pass
win.lift()
try:
win.focus_force()
except Exception:
pass
try:
win.attributes("-alpha", 1.0)
except Exception:
pass
win.after(340, lambda: frame._first_show())
win.after(360, lambda: win.lift() if getattr(win, "winfo_exists", lambda: False)() else None)
win.after(380, lambda: win.focus_force() if getattr(win, "winfo_exists", lambda: False)() else None)
except Exception:
pass