Release storico UDC e picking list
This commit is contained in:
121
main.py
121
main.py
@@ -11,6 +11,10 @@ import sys
|
||||
import tkinter as tk
|
||||
import time
|
||||
|
||||
from runtime_support import ensure_stdio, run_with_fatal_log
|
||||
|
||||
ensure_stdio("warehouse_main")
|
||||
|
||||
import customtkinter as ctk
|
||||
from tkinter import messagebox
|
||||
|
||||
@@ -24,6 +28,8 @@ from login_window import prompt_login
|
||||
from locale_text import load_locale_catalog, text as loc_text
|
||||
from reset_corsie import open_reset_corsie_window
|
||||
from search_pallets import open_search_window
|
||||
from storico_pickinglist import open_storico_pickinglist_window
|
||||
from storico_udc import open_storico_udc_window
|
||||
from tooltips import WidgetToolTip, load_tooltip_catalog, tooltip_text
|
||||
from ui_theme import theme_font, theme_section, theme_value
|
||||
from user_session import UserSession, create_user_session
|
||||
@@ -45,9 +51,9 @@ BYPASS_LOGIN_USER = {
|
||||
"codice_unita": "U1",
|
||||
}
|
||||
|
||||
# Create one global loop and make it the default everywhere.
|
||||
# Create one global loop for database work. Tk must keep the main thread clean;
|
||||
# callers schedule async jobs on this loop explicitly.
|
||||
_loop = get_global_loop()
|
||||
asyncio.set_event_loop(_loop)
|
||||
|
||||
|
||||
def _noop(*args, **kwargs):
|
||||
@@ -148,7 +154,9 @@ class Launcher(ctk.CTk):
|
||||
"layout",
|
||||
"multi_udc",
|
||||
"search",
|
||||
"storico_udc",
|
||||
"pickinglist",
|
||||
"storico_pickinglist",
|
||||
]
|
||||
|
||||
def __init__(self, session: UserSession, db_client: AsyncMSSQLClient):
|
||||
@@ -164,6 +172,9 @@ class Launcher(ctk.CTk):
|
||||
self._is_cascading = False
|
||||
self._focus_restore_pending: set[str] = set()
|
||||
self._restore_suppressed_until = 0.0
|
||||
self._exit_icon = self._make_exit_icon(
|
||||
color=str(theme_value(self._theme, "exit_icon_color", "#ca3d3d"))
|
||||
)
|
||||
self.title(
|
||||
f"{loc_text('launcher.window_title', catalog=self._locale_catalog, default='Warehouse 1.0.0')} - {self.session.display_name}"
|
||||
)
|
||||
@@ -185,45 +196,63 @@ class Launcher(ctk.CTk):
|
||||
"reset_corsie",
|
||||
loc_text("launcher.reset_corsie", catalog=self._locale_catalog, default="Gestione Corsie"),
|
||||
"launcher.open_reset_corsie",
|
||||
lambda: self._open_child_window(
|
||||
lambda: self._open_or_focus_child_window(
|
||||
"reset_corsie",
|
||||
open_reset_corsie_window(self, self.db_client, session=self.session),
|
||||
lambda: open_reset_corsie_window(self, self.db_client, session=self.session),
|
||||
),
|
||||
),
|
||||
(
|
||||
"layout",
|
||||
loc_text("launcher.layout", catalog=self._locale_catalog, default="Gestione Layout"),
|
||||
"launcher.open_layout",
|
||||
lambda: self._open_child_window(
|
||||
lambda: self._open_or_focus_child_window(
|
||||
"layout",
|
||||
open_layout_window(self, self.db_client, session=self.session),
|
||||
lambda: open_layout_window(self, self.db_client, session=self.session),
|
||||
),
|
||||
),
|
||||
(
|
||||
"multi_udc",
|
||||
loc_text("launcher.multi_udc", catalog=self._locale_catalog, default="UDC Fantasma"),
|
||||
"launcher.open_multi_udc",
|
||||
lambda: self._open_child_window(
|
||||
lambda: self._open_or_focus_child_window(
|
||||
"multi_udc",
|
||||
open_celle_multiple_window(self, self.db_client, session=self.session),
|
||||
lambda: open_celle_multiple_window(self, self.db_client, session=self.session),
|
||||
),
|
||||
),
|
||||
(
|
||||
"search",
|
||||
loc_text("launcher.search", catalog=self._locale_catalog, default="Ricerca UDC"),
|
||||
"launcher.open_search",
|
||||
lambda: self._open_child_window(
|
||||
lambda: self._open_or_focus_child_window(
|
||||
"search",
|
||||
open_search_window(self, self.db_client, session=self.session),
|
||||
lambda: open_search_window(self, self.db_client, session=self.session),
|
||||
),
|
||||
),
|
||||
(
|
||||
"storico_udc",
|
||||
loc_text("launcher.history_udc", catalog=self._locale_catalog, default="Storico movimenti UDC"),
|
||||
"launcher.open_history_udc",
|
||||
lambda: self._open_or_focus_child_window(
|
||||
"storico_udc",
|
||||
lambda: open_storico_udc_window(self, self.db_client, session=self.session),
|
||||
),
|
||||
),
|
||||
(
|
||||
"pickinglist",
|
||||
loc_text("launcher.pickinglist", catalog=self._locale_catalog, default="Gestione Picking List"),
|
||||
"launcher.open_pickinglist",
|
||||
lambda: self._open_child_window(
|
||||
lambda: self._open_or_focus_child_window(
|
||||
"pickinglist",
|
||||
open_pickinglist_window(self, self.db_client, session=self.session),
|
||||
lambda: open_pickinglist_window(self, self.db_client, session=self.session),
|
||||
),
|
||||
),
|
||||
(
|
||||
"storico_pickinglist",
|
||||
loc_text("launcher.history_pickinglist", catalog=self._locale_catalog, default="Storico Picking List"),
|
||||
"launcher.open_history_pickinglist",
|
||||
lambda: self._open_or_focus_child_window(
|
||||
"storico_pickinglist",
|
||||
lambda: open_storico_pickinglist_window(self, self.db_client, session=self.session),
|
||||
),
|
||||
),
|
||||
(
|
||||
@@ -257,12 +286,23 @@ class Launcher(ctk.CTk):
|
||||
for idx, (_key, label, permission, callback) in enumerate(actions):
|
||||
row = 1 + (idx // max_buttons_per_row)
|
||||
column = idx % max_buttons_per_row
|
||||
text = label
|
||||
button_options = {}
|
||||
if _key == "exit":
|
||||
row = 2
|
||||
column = max_buttons_per_row - 1
|
||||
text = label
|
||||
button_options = {
|
||||
"image": self._exit_icon,
|
||||
"compound": "left",
|
||||
}
|
||||
button = ctk.CTkButton(
|
||||
wrap,
|
||||
text=label,
|
||||
text=text,
|
||||
font=theme_font(self._theme, "button_font", default=("Segoe UI", 11, "bold")),
|
||||
state="normal" if self.session.can(permission) else "disabled",
|
||||
command=callback,
|
||||
**button_options,
|
||||
)
|
||||
button.grid(row=row, column=column, padx=button_padx, pady=button_pady, sticky="ew")
|
||||
tip = tooltip_text(permission, catalog=self._tooltip_catalog)
|
||||
@@ -282,6 +322,18 @@ class Launcher(ctk.CTk):
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _make_exit_icon(self, *, color: str) -> tk.PhotoImage:
|
||||
"""Create a small red X icon without adding image assets to the project."""
|
||||
|
||||
size = 14
|
||||
image = tk.PhotoImage(width=size, height=size)
|
||||
for offset in range(3, 11):
|
||||
image.put(color, (offset, offset))
|
||||
image.put(color, (offset + 1, offset))
|
||||
image.put(color, (offset, size - 1 - offset))
|
||||
image.put(color, (offset + 1, size - 1 - offset))
|
||||
return image
|
||||
|
||||
def _apply_dynamic_geometry(self) -> None:
|
||||
"""Size the launcher around its current content and keep it docked at the top."""
|
||||
|
||||
@@ -334,6 +386,34 @@ class Launcher(ctk.CTk):
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _open_or_focus_child_window(self, key: str, factory) -> None:
|
||||
"""Open one child per launcher key, or focus the existing window."""
|
||||
|
||||
self._child_windows_by_key = {
|
||||
child_key: child
|
||||
for child_key, child in self._child_windows_by_key.items()
|
||||
if getattr(child, "winfo_exists", lambda: False)()
|
||||
}
|
||||
existing = self._child_windows_by_key.get(key)
|
||||
if existing is not None and getattr(existing, "winfo_exists", lambda: False)():
|
||||
try:
|
||||
if hasattr(existing, "state") and existing.state() == "iconic":
|
||||
existing.deiconify()
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
existing.lift()
|
||||
existing.focus_force()
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
place_window_below_parent_later(self, existing)
|
||||
except Exception:
|
||||
pass
|
||||
return
|
||||
|
||||
self._open_child_window(key, factory())
|
||||
|
||||
def _forget_child_window(self, key: str, window: tk.Misc, event_widget: tk.Misc | None = None) -> None:
|
||||
"""Remove stale references to child windows that have been closed."""
|
||||
|
||||
@@ -460,7 +540,9 @@ class Launcher(ctk.CTk):
|
||||
self.destroy()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
def run_app() -> int:
|
||||
"""Run the backoffice application entry point."""
|
||||
|
||||
ctk.set_appearance_mode("light")
|
||||
ctk.set_default_color_theme("green")
|
||||
if not _acquire_single_instance_mutex():
|
||||
@@ -479,7 +561,7 @@ if __name__ == "__main__":
|
||||
stop_global_loop()
|
||||
except Exception:
|
||||
pass
|
||||
raise SystemExit(0)
|
||||
return 0
|
||||
|
||||
db_cfg = ensure_db_config(_loop)
|
||||
if db_cfg is None:
|
||||
@@ -487,7 +569,7 @@ if __name__ == "__main__":
|
||||
stop_global_loop()
|
||||
except Exception:
|
||||
pass
|
||||
raise SystemExit(0)
|
||||
return 0
|
||||
|
||||
db_app = AsyncMSSQLClient(build_dsn_from_config(db_cfg))
|
||||
|
||||
@@ -511,7 +593,7 @@ if __name__ == "__main__":
|
||||
|
||||
if session is None:
|
||||
_shutdown_runtime(bootstrap=bootstrap, dispose_db=True)
|
||||
raise SystemExit(0)
|
||||
return 0
|
||||
|
||||
_destroy_tk_root(bootstrap)
|
||||
|
||||
@@ -519,3 +601,8 @@ if __name__ == "__main__":
|
||||
Launcher(session, db_app).mainloop()
|
||||
finally:
|
||||
_shutdown_runtime(bootstrap=None, dispose_db=True)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(run_with_fatal_log("Warehouse Backoffice", run_app))
|
||||
|
||||
Reference in New Issue
Block a user