From 8f9957a2dbf58bc500a5502e7f4eb83dd2e273d6 Mon Sep 17 00:00:00 2001 From: allebonvi Date: Fri, 5 Jun 2026 11:33:47 +0200 Subject: [PATCH] Introdotto versioning visibile ver 1.0 --- async_loop_singleton.py | 4 +++ async_msssql_query.py | 3 ++ audit_log.py | 2 ++ barcode.pyw | 6 ++++ barcode_client.py | 19 ++++++++++- barcode_repository.py | 4 +++ barcode_service.py | 2 ++ busy_overlay.py | 3 ++ db_config.py | 5 ++- gestione_aree.py | 3 +- gestione_layout.py | 5 ++- gestione_pickinglist.py | 5 ++- gestione_scarico.py | 5 ++- locale_text.py | 3 ++ login_window.py | 5 ++- main.py | 4 ++- prenota_sprenota_sql.py | 4 +++ reset_corsie.py | 5 ++- runtime_support.py | 46 +++++++++++++++++++++++--- search_pallets.py | 5 ++- storico_pickinglist.py | 5 ++- storico_udc.py | 5 ++- tooltips.py | 3 ++ ui_theme.py | 4 +++ user_session.py | 3 ++ version_info.py | 71 +++++++++++++++++++++++++++++++++++++++++ view_celle_multi_udc.py | 5 ++- warehouse.pyw | 6 ++++ window_placement.py | 3 ++ 29 files changed, 225 insertions(+), 18 deletions(-) create mode 100644 version_info.py diff --git a/async_loop_singleton.py b/async_loop_singleton.py index b9033f5..8527a9a 100644 --- a/async_loop_singleton.py +++ b/async_loop_singleton.py @@ -9,6 +9,10 @@ import asyncio import threading import contextlib +from version_info import module_version + +__version__ = module_version(__name__) + class _LoopHolder: """Store the global loop instance and its worker thread.""" diff --git a/async_msssql_query.py b/async_msssql_query.py index ece9039..626d3da 100644 --- a/async_msssql_query.py +++ b/async_msssql_query.py @@ -18,6 +18,9 @@ from typing import Any, Dict, Optional from sqlalchemy import text from sqlalchemy.ext.asyncio import create_async_engine from sqlalchemy.pool import NullPool +from version_info import module_version + +__version__ = module_version(__name__) try: import pyodbc diff --git a/audit_log.py b/audit_log.py index ab6232d..f6f3dde 100644 --- a/audit_log.py +++ b/audit_log.py @@ -8,7 +8,9 @@ from pathlib import Path from typing import Any from user_session import UserSession +from version_info import module_version +__version__ = module_version(__name__) AUDIT_LOG_PATH = Path(__file__).with_name("warehouse_audit.log") _LOGGER = logging.getLogger("warehouse_audit") diff --git a/barcode.pyw b/barcode.pyw index 443cf77..e6db1f2 100644 --- a/barcode.pyw +++ b/barcode.pyw @@ -1,3 +1,9 @@ +from pathlib import Path +import os + + +os.chdir(Path(__file__).resolve().parent) + from runtime_support import ensure_stdio, run_with_fatal_log ensure_stdio("warehouse_barcode") diff --git a/barcode_client.py b/barcode_client.py index 3b1be4b..7c2bbe8 100644 --- a/barcode_client.py +++ b/barcode_client.py @@ -19,6 +19,9 @@ from barcode_repository import BarcodeRepository from barcode_service import BarcodeActionResult, BarcodeService, BarcodeViewState from db_config import build_dsn_from_config, ensure_db_config from login_window import prompt_login_compact +from version_info import module_version, versioned_title + +__version__ = module_version(__name__) class BarcodeClientApp: @@ -59,9 +62,11 @@ class BarcodeClientApp: self._apply_state(self.service.state) self._bind_keys() self.root.protocol("WM_DELETE_WINDOW", self._shutdown) + self.root.after(80, self._finalize_window_placement) + self.root.after(250, self._finalize_window_placement) def _build_ui(self) -> None: - self.root.title("WMS") + self.root.title(versioned_title("WMS", __name__)) self.root.configure(bg="#f1f1f1") self._apply_responsive_geometry() @@ -254,6 +259,18 @@ class BarcodeClientApp: self.root.geometry(f"{width}x{height}+{x}+{y}") + def _finalize_window_placement(self) -> None: + """Let Windows finish frame sizing, then snap the barcode window in place.""" + + try: + if self._is_barcode_desktop: + self.root.state("zoomed") + else: + self.root.geometry("+0+0") + self.root.update_idletasks() + except Exception: + pass + def _bind_keys(self) -> None: self.root.bind("", lambda _e: self._start_queue(1)) self.root.bind("", lambda _e: self._start_queue(0)) diff --git a/barcode_repository.py b/barcode_repository.py index 050ac81..0436782 100644 --- a/barcode_repository.py +++ b/barcode_repository.py @@ -9,6 +9,10 @@ from __future__ import annotations from dataclasses import dataclass from typing import Any +from version_info import module_version + +__version__ = module_version(__name__) + SQL_NEXT_PICKING = """ SELECT TOP (1) diff --git a/barcode_service.py b/barcode_service.py index efffd26..ab694a9 100644 --- a/barcode_service.py +++ b/barcode_service.py @@ -6,7 +6,9 @@ from dataclasses import dataclass from typing import Literal from barcode_repository import BarcodeRepository, LegacyMoveResult +from version_info import module_version +__version__ = module_version(__name__) ModeName = Literal["idle", "priority_high", "priority_low", "manual_load", "manual_unload", "confirm"] diff --git a/busy_overlay.py b/busy_overlay.py index 6d49479..6ac3f0c 100644 --- a/busy_overlay.py +++ b/busy_overlay.py @@ -6,6 +6,9 @@ import tkinter as tk import customtkinter as ctk from ui_theme import theme_color, theme_font, theme_padding, theme_value +from version_info import module_version + +__version__ = module_version(__name__) class InlineBusyOverlay: diff --git a/db_config.py b/db_config.py index fd60156..a4f41de 100644 --- a/db_config.py +++ b/db_config.py @@ -13,6 +13,9 @@ from async_msssql_query import AsyncMSSQLClient, make_mssql_dsn from locale_text import load_locale_catalog, text as loc_text from tooltips import WidgetToolTip, load_tooltip_catalog, tooltip_text from ui_theme import theme_section, theme_value +from version_info import module_version, versioned_title + +__version__ = module_version(__name__) CONFIG_PATH = Path(__file__).with_name("db_connection.json") DEFAULT_DB_CONFIG: dict[str, Any] = { @@ -101,7 +104,7 @@ class DatabaseConfigWindow(tk.Toplevel): self.result_config: dict[str, Any] | None = None merged = {**DEFAULT_DB_CONFIG, **(initial or {})} - self.title(loc_text("dbconfig.title", catalog=self._locale_catalog, default="Configurazione Database")) + self.title(versioned_title(loc_text("dbconfig.title", catalog=self._locale_catalog, default="Configurazione Database"), __name__)) self.geometry(str(theme_value(self._theme, "window_geometry", "520x360"))) self.resizable(False, False) try: diff --git a/gestione_aree.py b/gestione_aree.py index 52eb131..18f3589 100644 --- a/gestione_aree.py +++ b/gestione_aree.py @@ -19,8 +19,9 @@ from typing import Any, Callable, Optional import customtkinter as ctk from async_loop_singleton import get_global_loop +from version_info import module_version -__VERSION__ = "GestioneAreeFrame v3.3.0-singleloop" +__version__ = module_version(__name__) try: from async_msssql_query import AsyncMSSQLClient # noqa: F401 diff --git a/gestione_layout.py b/gestione_layout.py index 2d020ac..688fcb6 100644 --- a/gestione_layout.py +++ b/gestione_layout.py @@ -26,8 +26,11 @@ from tksheet import Sheet, natural_sort_key from tooltips import WidgetToolTip, load_tooltip_catalog, tooltip_text from ui_theme import theme_color, theme_font, theme_section, theme_value from user_session import UserSession +from version_info import module_version, versioned_title from window_placement import place_window_fullsize_below_parent_later +__version__ = module_version(__name__) + try: from loguru import logger except Exception: # pragma: no cover - fallback used only when Loguru is not available @@ -212,7 +215,7 @@ class LayoutWindow(ctk.CTkToplevel): self._theme = theme_section("layout_window", {}) self._locale_catalog = load_locale_catalog() self._tooltip_catalog = load_tooltip_catalog() - self.title(loc_text("layout.title", catalog=self._locale_catalog, default="Layout corsie")) + self.title(versioned_title(loc_text("layout.title", catalog=self._locale_catalog, default="Layout corsie"), __name__)) self.geometry(str(theme_value(self._theme, "window_geometry", "1200x740"))) minsize = theme_value(self._theme, "window_minsize", [980, 560]) self.minsize(int(minsize[0]), int(minsize[1])) diff --git a/gestione_pickinglist.py b/gestione_pickinglist.py index 3bcff6f..503dca4 100644 --- a/gestione_pickinglist.py +++ b/gestione_pickinglist.py @@ -71,8 +71,11 @@ 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 version_info import module_version, versioned_title from window_placement import place_window_fullsize_below_parent_later +__version__ = module_version(__name__) + # === IMPORT procedura async prenota/s-prenota (no pyodbc qui) === import asyncio try: @@ -1240,7 +1243,7 @@ def open_pickinglist_window(parent: tk.Misc, db_client, session: UserSession | N win = ctk.CTkToplevel(parent) locale_catalog = load_locale_catalog() - win.title(loc_text("picking.title", catalog=locale_catalog, default="Gestione Picking List")) + win.title(versioned_title(loc_text("picking.title", catalog=locale_catalog, default="Gestione Picking List"), __name__)) theme = theme_section("pickinglist_window", {}) win.geometry(str(theme_value(theme, "window_geometry", "1200x700"))) minsize = theme_value(theme, "window_minsize", [1000, 560]) diff --git a/gestione_scarico.py b/gestione_scarico.py index d2d7c1f..1d3371d 100644 --- a/gestione_scarico.py +++ b/gestione_scarico.py @@ -21,6 +21,9 @@ from busy_overlay import InlineBusyOverlay 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 version_info import module_version, versioned_title + +__version__ = module_version(__name__) try: from loguru import logger @@ -457,7 +460,7 @@ class ScaricoDialog(ctk.CTkToplevel): self._async = AsyncRunner(self) self.rows_tree: ttk.Treeview | None = None - self.title(loc_text("scarico.title", catalog=self._locale_catalog, default="Scarica {ubicazione}").format(ubicazione=ubicazione)) + self.title(versioned_title(loc_text("scarico.title", catalog=self._locale_catalog, default="Scarica {ubicazione}").format(ubicazione=ubicazione), __name__)) self.resizable(False, False) self.transient(parent) self.protocol("WM_DELETE_WINDOW", self._close) diff --git a/locale_text.py b/locale_text.py index d68bac9..6914728 100644 --- a/locale_text.py +++ b/locale_text.py @@ -6,6 +6,9 @@ import json from functools import lru_cache from pathlib import Path +from version_info import module_version + +__version__ = module_version(__name__) _LOCALE_FILE = Path(__file__).with_name("locale.json") diff --git a/login_window.py b/login_window.py index 60e1195..cad9045 100644 --- a/login_window.py +++ b/login_window.py @@ -11,6 +11,9 @@ from gestione_aree import AsyncRunner from locale_text import load_locale_catalog, text as loc_text from ui_theme import theme_section, theme_value from user_session import UserSession, create_user_session +from version_info import module_version, versioned_title + +__version__ = module_version(__name__) SQL_LOGIN = """ @@ -64,7 +67,7 @@ class LoginWindow(tk.Toplevel): self._show_ready_after_id: str | None = None self._clear_topmost_after_id: str | None = None - self.title(loc_text("login.msg.title", catalog=self._locale_catalog, default="Login")) + self.title(versioned_title(loc_text("login.msg.title", catalog=self._locale_catalog, default="Login"), __name__)) self.geometry("170x145+0+0" if self.compact else str(theme_value(self._theme, "window_geometry", "165x155+0+0"))) self.resizable(False, False) try: diff --git a/main.py b/main.py index e880044..496f0f6 100644 --- a/main.py +++ b/main.py @@ -33,6 +33,7 @@ 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 +from version_info import module_version, versioned_title from view_celle_multi_udc import open_celle_multiple_window from window_placement import ( cascade_children_below_parent, @@ -43,6 +44,7 @@ from window_placement import ( # Development shortcut: skip the login dialog and boot directly as MAG1. # Set to False when you want to restore normal authentication. BYPASS_LOGIN = False +__version__ = module_version(__name__) BYPASS_LOGIN_USER = { "operator_id": 4, "login": "MAG1", @@ -176,7 +178,7 @@ class Launcher(ctk.CTk): 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}" + f"{versioned_title(loc_text('launcher.window_title', catalog=self._locale_catalog, default='Warehouse'), __name__)} - {self.session.display_name}" ) self._apply_dynamic_geometry() diff --git a/prenota_sprenota_sql.py b/prenota_sprenota_sql.py index ba68ad0..538e2f2 100644 --- a/prenota_sprenota_sql.py +++ b/prenota_sprenota_sql.py @@ -10,6 +10,10 @@ from functools import wraps from pathlib import Path from typing import Any, Dict, List, Optional +from version_info import module_version + +__version__ = module_version(__name__) + try: from loguru import logger except Exception: # pragma: no cover - safety fallback if dependency is missing locally diff --git a/reset_corsie.py b/reset_corsie.py index 7e3dbca..c442d9c 100644 --- a/reset_corsie.py +++ b/reset_corsie.py @@ -24,8 +24,11 @@ from gestione_scarico import move_pallet_async from locale_text import load_locale_catalog, text as loc_text from tooltips import WidgetToolTip, load_tooltip_catalog, tooltip_text from ui_theme import theme_color, theme_font, theme_padding, theme_section, theme_value +from version_info import module_version, versioned_title from window_placement import place_window_fullsize_below_parent_later +__version__ = module_version(__name__) + try: from loguru import logger except Exception: # pragma: no cover - fallback used only when Loguru is not available @@ -275,7 +278,7 @@ class ResetCorsieWindow(ctk.CTkToplevel): super().__init__(parent) self._theme = theme_section("reset_corsie", {}) self._locale_catalog = load_locale_catalog() - self.title(loc_text("reset.title", catalog=self._locale_catalog, default="Gestione Corsie - svuotamento celle per corsia")) + self.title(versioned_title(loc_text("reset.title", catalog=self._locale_catalog, default="Gestione Corsie - svuotamento celle per corsia"), __name__)) self.geometry(str(theme_value(self._theme, "window_geometry", "1000x680"))) minsize = theme_value(self._theme, "window_minsize", [880, 560]) self.minsize(int(minsize[0]), int(minsize[1])) diff --git a/runtime_support.py b/runtime_support.py index 79c079d..724aada 100644 --- a/runtime_support.py +++ b/runtime_support.py @@ -6,25 +6,42 @@ import sys import traceback from datetime import datetime from pathlib import Path +import tempfile from typing import Callable, TypeVar +from version_info import module_version + +__version__ = module_version(__name__) BASE_DIR = Path(__file__).resolve().parent FATAL_LOG = BASE_DIR / "warehouse_fatal.log" +TEMP_DIR = Path(tempfile.gettempdir()) _STDIO_HANDLES = [] T = TypeVar("T") +def _open_log_file(path: Path): + """Open a log path, falling back to the user temp directory when needed.""" + + try: + path.parent.mkdir(parents=True, exist_ok=True) + return path.open("a", encoding="utf-8", buffering=1) + except Exception: + fallback = TEMP_DIR / path.name + fallback.parent.mkdir(parents=True, exist_ok=True) + return fallback.open("a", encoding="utf-8", buffering=1) + + def ensure_stdio(app_name: str) -> None: """Give ``pythonw`` a real stdout/stderr target before loggers are imported.""" stamp = datetime.now().strftime("%Y%m%d") if sys.stdout is None: - handle = (BASE_DIR / f"{app_name}_stdout_{stamp}.log").open("a", encoding="utf-8", buffering=1) + handle = _open_log_file(BASE_DIR / f"{app_name}_stdout_{stamp}.log") _STDIO_HANDLES.append(handle) sys.stdout = handle if sys.stderr is None: - handle = (BASE_DIR / f"{app_name}_stderr_{stamp}.log").open("a", encoding="utf-8", buffering=1) + handle = _open_log_file(BASE_DIR / f"{app_name}_stderr_{stamp}.log") _STDIO_HANDLES.append(handle) sys.stderr = handle @@ -32,12 +49,21 @@ def ensure_stdio(app_name: str) -> None: def log_fatal(app_name: str, exc: BaseException) -> None: """Write one startup/runtime crash to a persistent log file.""" - with FATAL_LOG.open("a", encoding="utf-8") as handle: + with _open_log_file(FATAL_LOG) as handle: handle.write("\n" + "=" * 80 + "\n") handle.write(f"{datetime.now():%Y-%m-%d %H:%M:%S} | {app_name}\n") handle.write("".join(traceback.format_exception(type(exc), exc, exc.__traceback__))) +def log_runtime_event(app_name: str, message: str) -> None: + """Write a lightweight launch/shutdown trace for console-less starts.""" + + safe_name = "".join(ch if ch.isalnum() or ch in ("_", "-") else "_" for ch in app_name.lower()) + path = BASE_DIR / f"{safe_name}_launch.log" + with _open_log_file(path) as handle: + handle.write(f"{datetime.now():%Y-%m-%d %H:%M:%S} | {message}\n") + + def show_fatal_message(app_name: str, exc: BaseException) -> None: """Show a best-effort message box for console-less launches.""" @@ -49,7 +75,10 @@ def show_fatal_message(app_name: str, exc: BaseException) -> None: root.withdraw() messagebox.showerror( app_name, - f"Avvio non riuscito.\n\nDettaglio salvato in:\n{FATAL_LOG}\n\n{exc}", + "Avvio non riuscito.\n\n" + f"Controlla i log in:\n{BASE_DIR}\n\n" + f"Se non ci sono, controlla anche:\n{TEMP_DIR}\n\n" + f"{exc}", parent=root, ) root.destroy() @@ -61,8 +90,15 @@ def run_with_fatal_log(app_name: str, func: Callable[[], T]) -> T: """Run an app entry point and persist otherwise invisible ``pythonw`` crashes.""" try: - return func() + log_runtime_event( + app_name, + f"START exe={sys.executable!r} version={sys.version.split()[0]} cwd={Path.cwd()} base={BASE_DIR} argv={sys.argv!r}", + ) + result = func() + log_runtime_event(app_name, f"RETURN result={result!r}") + return result except BaseException as exc: + log_runtime_event(app_name, f"EXCEPTION type={type(exc).__name__} value={exc!r}") log_fatal(app_name, exc) show_fatal_message(app_name, exc) raise diff --git a/search_pallets.py b/search_pallets.py index cf04d46..493790a 100644 --- a/search_pallets.py +++ b/search_pallets.py @@ -11,9 +11,12 @@ 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 version_info import module_version, versioned_title from window_placement import place_window_fullsize_below_parent_later from tooltips import WidgetToolTip, load_tooltip_catalog, tooltip_text +__version__ = module_version(__name__) + try: from openpyxl import Workbook from openpyxl.styles import Alignment, Font @@ -85,7 +88,7 @@ class SearchWindow(ctk.CTkToplevel): 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.title(versioned_title(loc_text("search.title", catalog=self._locale_catalog, default="Ricerca UDC/Lotto/Codice"), __name__)) 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])) diff --git a/storico_pickinglist.py b/storico_pickinglist.py index 3b46e67..a5e96cf 100644 --- a/storico_pickinglist.py +++ b/storico_pickinglist.py @@ -13,8 +13,11 @@ 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 version_info import module_version, versioned_title from window_placement import place_window_fullsize_below_parent_later +__version__ = module_version(__name__) + SQL_STORICO_PL = """ WITH base AS ( @@ -136,7 +139,7 @@ class StoricoPickingListWindow(ctk.CTkToplevel): self._busy = InlineBusyOverlay(self, self._theme) self.var_documento = tk.StringVar() - self.title(loc_text("history.picking.title", catalog=self._locale_catalog, default="Storico Picking List")) + self.title(versioned_title(loc_text("history.picking.title", catalog=self._locale_catalog, default="Storico Picking List"), __name__)) self.geometry(str(theme_value(self._theme, "window_geometry", "1200x720"))) minsize = theme_value(self._theme, "window_minsize", [980, 560]) self.minsize(int(minsize[0]), int(minsize[1])) diff --git a/storico_udc.py b/storico_udc.py index b341f9e..7eb877e 100644 --- a/storico_udc.py +++ b/storico_udc.py @@ -12,8 +12,11 @@ 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 version_info import module_version, versioned_title from window_placement import place_window_fullsize_below_parent_later +__version__ = module_version(__name__) + SQL_STORICO_UDC = """ WITH direct AS ( @@ -155,7 +158,7 @@ class StoricoUDCWindow(ctk.CTkToplevel): self._busy = InlineBusyOverlay(self, self._theme) self.var_udc = tk.StringVar(value=str(initial_udc or "")) - self.title(loc_text("history.udc.title", catalog=self._locale_catalog, default="Storico movimenti UDC")) + self.title(versioned_title(loc_text("history.udc.title", catalog=self._locale_catalog, default="Storico movimenti UDC"), __name__)) 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])) diff --git a/tooltips.py b/tooltips.py index c1052fa..a963277 100644 --- a/tooltips.py +++ b/tooltips.py @@ -6,6 +6,9 @@ import json from pathlib import Path import tkinter as tk +from version_info import module_version + +__version__ = module_version(__name__) _TOOLTIP_FILE = Path(__file__).with_name("tooltip.json") diff --git a/ui_theme.py b/ui_theme.py index 622e927..08afe59 100644 --- a/ui_theme.py +++ b/ui_theme.py @@ -12,6 +12,10 @@ from functools import lru_cache from pathlib import Path from typing import Any +from version_info import module_version + +__version__ = module_version(__name__) + THEME_PATH = Path(__file__).with_name("ui_theme.json") diff --git a/user_session.py b/user_session.py index 3a9bc8c..b8dd258 100644 --- a/user_session.py +++ b/user_session.py @@ -6,6 +6,9 @@ from dataclasses import dataclass, field from datetime import datetime from typing import FrozenSet +from version_info import module_version + +__version__ = module_version(__name__) ALL_ACTIONS: FrozenSet[str] = frozenset( { diff --git a/version_info.py b/version_info.py new file mode 100644 index 0000000..6b8858e --- /dev/null +++ b/version_info.py @@ -0,0 +1,71 @@ +"""Application and module version registry for Warehouse/FlyWMS bridge.""" + +from __future__ import annotations + +from pathlib import Path + + +APP_VERSION = "1.0.0" + +MODULE_VERSIONS: dict[str, str] = { + "app": APP_VERSION, + "async_loop_singleton": "1.0.0", + "async_msssql_query": "1.0.0", + "audit_log": "1.0.0", + "main": "1.0.0", + "barcode_client": "1.0.0", + "barcode_repository": "1.0.0", + "barcode_service": "1.0.0", + "busy_overlay": "1.0.0", + "db_config": "1.0.0", + "gestione_aree": "1.0.0", + "gestione_layout": "1.0.0", + "gestione_pickinglist": "1.0.0", + "gestione_scarico": "1.0.0", + "locale_text": "1.0.0", + "login_window": "1.0.0", + "prenota_sprenota_sql": "1.0.0", + "reset_corsie": "1.0.0", + "search_pallets": "1.0.0", + "storico_pickinglist": "1.0.0", + "storico_udc": "1.0.0", + "tooltips": "1.0.0", + "ui_theme": "1.0.0", + "user_session": "1.0.0", + "view_celle_multi_udc": "1.0.0", + "window_placement": "1.0.0", +} + + +def module_key(module_name: str) -> str: + """Return the stable registry key for a module name or path.""" + + name = str(module_name or "").replace("\\", "/") + stem = Path(name).stem if "/" in name or "." not in name else name.rsplit(".", 1)[-1] + return stem or "app" + + +def module_version(module_name: str | None = None) -> str: + """Return the version for a module, falling back to the app version.""" + + if not module_name: + return APP_VERSION + return MODULE_VERSIONS.get(module_key(module_name), APP_VERSION) + + +def version_label(module_name: str | None = None) -> str: + """Return the standard visual label used in window titles.""" + + return f"ver. {module_version(module_name)}" + + +def versioned_title(title: str, module_name: str | None = None) -> str: + """Append the standard version label to a window title.""" + + clean_title = str(title or "").strip() + label = version_label(module_name) + if not clean_title: + return label + if label.lower() in clean_title.lower(): + return clean_title + return f"{clean_title} - {label}" diff --git a/view_celle_multi_udc.py b/view_celle_multi_udc.py index c79e731..84d642e 100644 --- a/view_celle_multi_udc.py +++ b/view_celle_multi_udc.py @@ -22,8 +22,11 @@ from gestione_scarico import move_pallet_async from locale_text import load_locale_catalog, text as loc_text from tooltips import WidgetToolTip, load_tooltip_catalog, tooltip_text from ui_theme import theme_color, theme_font, theme_section, theme_value +from version_info import module_version, versioned_title from window_placement import place_window_fullsize_below_parent_later +__version__ = module_version(__name__) + try: from loguru import logger except Exception: # pragma: no cover - fallback used only when Loguru is not available @@ -436,7 +439,7 @@ class CelleMultipleWindow(ctk.CTkToplevel): self._theme = theme_section("multi_udc", {}) self._locale_catalog = load_locale_catalog() self._tooltip_catalog = load_tooltip_catalog() - self.title(loc_text("multi.title", catalog=self._locale_catalog, default="Celle con piu' pallet")) + self.title(versioned_title(loc_text("multi.title", catalog=self._locale_catalog, default="Celle con piu' pallet"), __name__)) self.session = session self.geometry(str(theme_value(self._theme, "window_geometry", "1100x700"))) minsize = theme_value(self._theme, "window_minsize", [900, 550]) diff --git a/warehouse.pyw b/warehouse.pyw index 8acde11..58a64bf 100644 --- a/warehouse.pyw +++ b/warehouse.pyw @@ -1,3 +1,9 @@ +from pathlib import Path +import os + + +os.chdir(Path(__file__).resolve().parent) + from runtime_support import ensure_stdio, run_with_fatal_log ensure_stdio("warehouse_main") diff --git a/window_placement.py b/window_placement.py index 75df5a5..6caa53c 100644 --- a/window_placement.py +++ b/window_placement.py @@ -8,6 +8,9 @@ import math import tkinter as tk from pathlib import Path +from version_info import module_version + +__version__ = module_version(__name__) MODULE_LOG_NAME = "window_placement" MODULE_LOG_PATH = Path(__file__).with_name("window_placement.log")