Introdotto versioning visibile ver 1.0

This commit is contained in:
2026-06-05 11:33:47 +02:00
parent 742f6a9fe9
commit 8f9957a2db
29 changed files with 225 additions and 18 deletions

View File

@@ -9,6 +9,10 @@ import asyncio
import threading import threading
import contextlib import contextlib
from version_info import module_version
__version__ = module_version(__name__)
class _LoopHolder: class _LoopHolder:
"""Store the global loop instance and its worker thread.""" """Store the global loop instance and its worker thread."""

View File

@@ -18,6 +18,9 @@ from typing import Any, Dict, Optional
from sqlalchemy import text from sqlalchemy import text
from sqlalchemy.ext.asyncio import create_async_engine from sqlalchemy.ext.asyncio import create_async_engine
from sqlalchemy.pool import NullPool from sqlalchemy.pool import NullPool
from version_info import module_version
__version__ = module_version(__name__)
try: try:
import pyodbc import pyodbc

View File

@@ -8,7 +8,9 @@ from pathlib import Path
from typing import Any from typing import Any
from user_session import UserSession 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") AUDIT_LOG_PATH = Path(__file__).with_name("warehouse_audit.log")
_LOGGER = logging.getLogger("warehouse_audit") _LOGGER = logging.getLogger("warehouse_audit")

View File

@@ -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 from runtime_support import ensure_stdio, run_with_fatal_log
ensure_stdio("warehouse_barcode") ensure_stdio("warehouse_barcode")

View File

@@ -19,6 +19,9 @@ from barcode_repository import BarcodeRepository
from barcode_service import BarcodeActionResult, BarcodeService, BarcodeViewState from barcode_service import BarcodeActionResult, BarcodeService, BarcodeViewState
from db_config import build_dsn_from_config, ensure_db_config from db_config import build_dsn_from_config, ensure_db_config
from login_window import prompt_login_compact from login_window import prompt_login_compact
from version_info import module_version, versioned_title
__version__ = module_version(__name__)
class BarcodeClientApp: class BarcodeClientApp:
@@ -59,9 +62,11 @@ class BarcodeClientApp:
self._apply_state(self.service.state) self._apply_state(self.service.state)
self._bind_keys() self._bind_keys()
self.root.protocol("WM_DELETE_WINDOW", self._shutdown) 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: def _build_ui(self) -> None:
self.root.title("WMS") self.root.title(versioned_title("WMS", __name__))
self.root.configure(bg="#f1f1f1") self.root.configure(bg="#f1f1f1")
self._apply_responsive_geometry() self._apply_responsive_geometry()
@@ -254,6 +259,18 @@ class BarcodeClientApp:
self.root.geometry(f"{width}x{height}+{x}+{y}") 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: def _bind_keys(self) -> None:
self.root.bind("<F1>", lambda _e: self._start_queue(1)) self.root.bind("<F1>", lambda _e: self._start_queue(1))
self.root.bind("<F2>", lambda _e: self._start_queue(0)) self.root.bind("<F2>", lambda _e: self._start_queue(0))

View File

@@ -9,6 +9,10 @@ from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any from typing import Any
from version_info import module_version
__version__ = module_version(__name__)
SQL_NEXT_PICKING = """ SQL_NEXT_PICKING = """
SELECT TOP (1) SELECT TOP (1)

View File

@@ -6,7 +6,9 @@ from dataclasses import dataclass
from typing import Literal from typing import Literal
from barcode_repository import BarcodeRepository, LegacyMoveResult 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"] ModeName = Literal["idle", "priority_high", "priority_low", "manual_load", "manual_unload", "confirm"]

View File

@@ -6,6 +6,9 @@ import tkinter as tk
import customtkinter as ctk import customtkinter as ctk
from ui_theme import theme_color, theme_font, theme_padding, theme_value from ui_theme import theme_color, theme_font, theme_padding, theme_value
from version_info import module_version
__version__ = module_version(__name__)
class InlineBusyOverlay: class InlineBusyOverlay:

View File

@@ -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 locale_text import load_locale_catalog, text as loc_text
from tooltips import WidgetToolTip, load_tooltip_catalog, tooltip_text from tooltips import WidgetToolTip, load_tooltip_catalog, tooltip_text
from ui_theme import theme_section, theme_value 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") CONFIG_PATH = Path(__file__).with_name("db_connection.json")
DEFAULT_DB_CONFIG: dict[str, Any] = { DEFAULT_DB_CONFIG: dict[str, Any] = {
@@ -101,7 +104,7 @@ class DatabaseConfigWindow(tk.Toplevel):
self.result_config: dict[str, Any] | None = None self.result_config: dict[str, Any] | None = None
merged = {**DEFAULT_DB_CONFIG, **(initial or {})} 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.geometry(str(theme_value(self._theme, "window_geometry", "520x360")))
self.resizable(False, False) self.resizable(False, False)
try: try:

View File

@@ -19,8 +19,9 @@ from typing import Any, Callable, Optional
import customtkinter as ctk import customtkinter as ctk
from async_loop_singleton import get_global_loop 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: try:
from async_msssql_query import AsyncMSSQLClient # noqa: F401 from async_msssql_query import AsyncMSSQLClient # noqa: F401

View File

@@ -26,8 +26,11 @@ from tksheet import Sheet, natural_sort_key
from tooltips import WidgetToolTip, load_tooltip_catalog, tooltip_text from tooltips import WidgetToolTip, load_tooltip_catalog, tooltip_text
from ui_theme import theme_color, theme_font, theme_section, theme_value from ui_theme import theme_color, theme_font, theme_section, theme_value
from user_session import UserSession from user_session import UserSession
from version_info import module_version, versioned_title
from window_placement import place_window_fullsize_below_parent_later from window_placement import place_window_fullsize_below_parent_later
__version__ = module_version(__name__)
try: try:
from loguru import logger from loguru import logger
except Exception: # pragma: no cover - fallback used only when Loguru is not available 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._theme = theme_section("layout_window", {})
self._locale_catalog = load_locale_catalog() self._locale_catalog = load_locale_catalog()
self._tooltip_catalog = load_tooltip_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"))) self.geometry(str(theme_value(self._theme, "window_geometry", "1200x740")))
minsize = theme_value(self._theme, "window_minsize", [980, 560]) minsize = theme_value(self._theme, "window_minsize", [980, 560])
self.minsize(int(minsize[0]), int(minsize[1])) self.minsize(int(minsize[0]), int(minsize[1]))

View File

@@ -71,8 +71,11 @@ from gestione_aree import AsyncRunner
from locale_text import load_locale_catalog, text as loc_text from locale_text import load_locale_catalog, text as loc_text
from ui_theme import theme_color, theme_font, theme_section, theme_value from ui_theme import theme_color, theme_font, theme_section, theme_value
from user_session import UserSession from user_session import UserSession
from version_info import module_version, versioned_title
from window_placement import place_window_fullsize_below_parent_later from window_placement import place_window_fullsize_below_parent_later
__version__ = module_version(__name__)
# === IMPORT procedura async prenota/s-prenota (no pyodbc qui) === # === IMPORT procedura async prenota/s-prenota (no pyodbc qui) ===
import asyncio import asyncio
try: try:
@@ -1240,7 +1243,7 @@ def open_pickinglist_window(parent: tk.Misc, db_client, session: UserSession | N
win = ctk.CTkToplevel(parent) win = ctk.CTkToplevel(parent)
locale_catalog = load_locale_catalog() 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", {}) theme = theme_section("pickinglist_window", {})
win.geometry(str(theme_value(theme, "window_geometry", "1200x700"))) win.geometry(str(theme_value(theme, "window_geometry", "1200x700")))
minsize = theme_value(theme, "window_minsize", [1000, 560]) minsize = theme_value(theme, "window_minsize", [1000, 560])

View File

@@ -21,6 +21,9 @@ from busy_overlay import InlineBusyOverlay
from locale_text import load_locale_catalog, text as loc_text from locale_text import load_locale_catalog, text as loc_text
from ui_theme import theme_color, theme_font, theme_section, theme_value from ui_theme import theme_color, theme_font, theme_section, theme_value
from user_session import UserSession from user_session import UserSession
from version_info import module_version, versioned_title
__version__ = module_version(__name__)
try: try:
from loguru import logger from loguru import logger
@@ -457,7 +460,7 @@ class ScaricoDialog(ctk.CTkToplevel):
self._async = AsyncRunner(self) self._async = AsyncRunner(self)
self.rows_tree: ttk.Treeview | None = None 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.resizable(False, False)
self.transient(parent) self.transient(parent)
self.protocol("WM_DELETE_WINDOW", self._close) self.protocol("WM_DELETE_WINDOW", self._close)

View File

@@ -6,6 +6,9 @@ import json
from functools import lru_cache from functools import lru_cache
from pathlib import Path from pathlib import Path
from version_info import module_version
__version__ = module_version(__name__)
_LOCALE_FILE = Path(__file__).with_name("locale.json") _LOCALE_FILE = Path(__file__).with_name("locale.json")

View File

@@ -11,6 +11,9 @@ from gestione_aree import AsyncRunner
from locale_text import load_locale_catalog, text as loc_text from locale_text import load_locale_catalog, text as loc_text
from ui_theme import theme_section, theme_value from ui_theme import theme_section, theme_value
from user_session import UserSession, create_user_session from user_session import UserSession, create_user_session
from version_info import module_version, versioned_title
__version__ = module_version(__name__)
SQL_LOGIN = """ SQL_LOGIN = """
@@ -64,7 +67,7 @@ class LoginWindow(tk.Toplevel):
self._show_ready_after_id: str | None = None self._show_ready_after_id: str | None = None
self._clear_topmost_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.geometry("170x145+0+0" if self.compact else str(theme_value(self._theme, "window_geometry", "165x155+0+0")))
self.resizable(False, False) self.resizable(False, False)
try: try:

View File

@@ -33,6 +33,7 @@ from storico_udc import open_storico_udc_window
from tooltips import WidgetToolTip, load_tooltip_catalog, tooltip_text from tooltips import WidgetToolTip, load_tooltip_catalog, tooltip_text
from ui_theme import theme_font, theme_section, theme_value from ui_theme import theme_font, theme_section, theme_value
from user_session import UserSession, create_user_session 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 view_celle_multi_udc import open_celle_multiple_window
from window_placement import ( from window_placement import (
cascade_children_below_parent, cascade_children_below_parent,
@@ -43,6 +44,7 @@ from window_placement import (
# Development shortcut: skip the login dialog and boot directly as MAG1. # Development shortcut: skip the login dialog and boot directly as MAG1.
# Set to False when you want to restore normal authentication. # Set to False when you want to restore normal authentication.
BYPASS_LOGIN = False BYPASS_LOGIN = False
__version__ = module_version(__name__)
BYPASS_LOGIN_USER = { BYPASS_LOGIN_USER = {
"operator_id": 4, "operator_id": 4,
"login": "MAG1", "login": "MAG1",
@@ -176,7 +178,7 @@ class Launcher(ctk.CTk):
color=str(theme_value(self._theme, "exit_icon_color", "#ca3d3d")) color=str(theme_value(self._theme, "exit_icon_color", "#ca3d3d"))
) )
self.title( 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() self._apply_dynamic_geometry()

View File

@@ -10,6 +10,10 @@ from functools import wraps
from pathlib import Path from pathlib import Path
from typing import Any, Dict, List, Optional from typing import Any, Dict, List, Optional
from version_info import module_version
__version__ = module_version(__name__)
try: try:
from loguru import logger from loguru import logger
except Exception: # pragma: no cover - safety fallback if dependency is missing locally except Exception: # pragma: no cover - safety fallback if dependency is missing locally

View File

@@ -24,8 +24,11 @@ from gestione_scarico import move_pallet_async
from locale_text import load_locale_catalog, text as loc_text from locale_text import load_locale_catalog, text as loc_text
from tooltips import WidgetToolTip, load_tooltip_catalog, tooltip_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 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 from window_placement import place_window_fullsize_below_parent_later
__version__ = module_version(__name__)
try: try:
from loguru import logger from loguru import logger
except Exception: # pragma: no cover - fallback used only when Loguru is not available except Exception: # pragma: no cover - fallback used only when Loguru is not available
@@ -275,7 +278,7 @@ class ResetCorsieWindow(ctk.CTkToplevel):
super().__init__(parent) super().__init__(parent)
self._theme = theme_section("reset_corsie", {}) self._theme = theme_section("reset_corsie", {})
self._locale_catalog = load_locale_catalog() 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"))) self.geometry(str(theme_value(self._theme, "window_geometry", "1000x680")))
minsize = theme_value(self._theme, "window_minsize", [880, 560]) minsize = theme_value(self._theme, "window_minsize", [880, 560])
self.minsize(int(minsize[0]), int(minsize[1])) self.minsize(int(minsize[0]), int(minsize[1]))

View File

@@ -6,25 +6,42 @@ import sys
import traceback import traceback
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
import tempfile
from typing import Callable, TypeVar from typing import Callable, TypeVar
from version_info import module_version
__version__ = module_version(__name__)
BASE_DIR = Path(__file__).resolve().parent BASE_DIR = Path(__file__).resolve().parent
FATAL_LOG = BASE_DIR / "warehouse_fatal.log" FATAL_LOG = BASE_DIR / "warehouse_fatal.log"
TEMP_DIR = Path(tempfile.gettempdir())
_STDIO_HANDLES = [] _STDIO_HANDLES = []
T = TypeVar("T") 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: def ensure_stdio(app_name: str) -> None:
"""Give ``pythonw`` a real stdout/stderr target before loggers are imported.""" """Give ``pythonw`` a real stdout/stderr target before loggers are imported."""
stamp = datetime.now().strftime("%Y%m%d") stamp = datetime.now().strftime("%Y%m%d")
if sys.stdout is None: 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) _STDIO_HANDLES.append(handle)
sys.stdout = handle sys.stdout = handle
if sys.stderr is None: 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) _STDIO_HANDLES.append(handle)
sys.stderr = handle sys.stderr = handle
@@ -32,12 +49,21 @@ def ensure_stdio(app_name: str) -> None:
def log_fatal(app_name: str, exc: BaseException) -> None: def log_fatal(app_name: str, exc: BaseException) -> None:
"""Write one startup/runtime crash to a persistent log file.""" """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("\n" + "=" * 80 + "\n")
handle.write(f"{datetime.now():%Y-%m-%d %H:%M:%S} | {app_name}\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__))) 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: def show_fatal_message(app_name: str, exc: BaseException) -> None:
"""Show a best-effort message box for console-less launches.""" """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() root.withdraw()
messagebox.showerror( messagebox.showerror(
app_name, 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, parent=root,
) )
root.destroy() 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.""" """Run an app entry point and persist otherwise invisible ``pythonw`` crashes."""
try: 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: except BaseException as exc:
log_runtime_event(app_name, f"EXCEPTION type={type(exc).__name__} value={exc!r}")
log_fatal(app_name, exc) log_fatal(app_name, exc)
show_fatal_message(app_name, exc) show_fatal_message(app_name, exc)
raise raise

View File

@@ -11,9 +11,12 @@ from busy_overlay import InlineBusyOverlay
from gestione_aree import AsyncRunner from gestione_aree import AsyncRunner
from locale_text import load_locale_catalog, text as loc_text from locale_text import load_locale_catalog, text as loc_text
from ui_theme import theme_color, theme_font, theme_section, theme_value 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 window_placement import place_window_fullsize_below_parent_later
from tooltips import WidgetToolTip, load_tooltip_catalog, tooltip_text from tooltips import WidgetToolTip, load_tooltip_catalog, tooltip_text
__version__ = module_version(__name__)
try: try:
from openpyxl import Workbook from openpyxl import Workbook
from openpyxl.styles import Alignment, Font from openpyxl.styles import Alignment, Font
@@ -85,7 +88,7 @@ class SearchWindow(ctk.CTkToplevel):
self._theme = theme_section("search_window", {}) self._theme = theme_section("search_window", {})
self._locale_catalog = load_locale_catalog() self._locale_catalog = load_locale_catalog()
self._tooltip_catalog = load_tooltip_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"))) self.geometry(str(theme_value(self._theme, "window_geometry", "1100x720")))
minsize = theme_value(self._theme, "window_minsize", [900, 560]) minsize = theme_value(self._theme, "window_minsize", [900, 560])
self.minsize(int(minsize[0]), int(minsize[1])) self.minsize(int(minsize[0]), int(minsize[1]))

View File

@@ -13,8 +13,11 @@ from busy_overlay import InlineBusyOverlay
from gestione_aree import AsyncRunner from gestione_aree import AsyncRunner
from locale_text import load_locale_catalog, text as loc_text from locale_text import load_locale_catalog, text as loc_text
from ui_theme import theme_color, theme_font, theme_section, theme_value 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 window_placement import place_window_fullsize_below_parent_later
__version__ = module_version(__name__)
SQL_STORICO_PL = """ SQL_STORICO_PL = """
WITH base AS ( WITH base AS (
@@ -136,7 +139,7 @@ class StoricoPickingListWindow(ctk.CTkToplevel):
self._busy = InlineBusyOverlay(self, self._theme) self._busy = InlineBusyOverlay(self, self._theme)
self.var_documento = tk.StringVar() 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"))) self.geometry(str(theme_value(self._theme, "window_geometry", "1200x720")))
minsize = theme_value(self._theme, "window_minsize", [980, 560]) minsize = theme_value(self._theme, "window_minsize", [980, 560])
self.minsize(int(minsize[0]), int(minsize[1])) self.minsize(int(minsize[0]), int(minsize[1]))

View File

@@ -12,8 +12,11 @@ from busy_overlay import InlineBusyOverlay
from gestione_aree import AsyncRunner from gestione_aree import AsyncRunner
from locale_text import load_locale_catalog, text as loc_text from locale_text import load_locale_catalog, text as loc_text
from ui_theme import theme_color, theme_font, theme_section, theme_value 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 window_placement import place_window_fullsize_below_parent_later
__version__ = module_version(__name__)
SQL_STORICO_UDC = """ SQL_STORICO_UDC = """
WITH direct AS ( WITH direct AS (
@@ -155,7 +158,7 @@ class StoricoUDCWindow(ctk.CTkToplevel):
self._busy = InlineBusyOverlay(self, self._theme) self._busy = InlineBusyOverlay(self, self._theme)
self.var_udc = tk.StringVar(value=str(initial_udc or "")) 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"))) self.geometry(str(theme_value(self._theme, "window_geometry", "1100x720")))
minsize = theme_value(self._theme, "window_minsize", [900, 560]) minsize = theme_value(self._theme, "window_minsize", [900, 560])
self.minsize(int(minsize[0]), int(minsize[1])) self.minsize(int(minsize[0]), int(minsize[1]))

View File

@@ -6,6 +6,9 @@ import json
from pathlib import Path from pathlib import Path
import tkinter as tk import tkinter as tk
from version_info import module_version
__version__ = module_version(__name__)
_TOOLTIP_FILE = Path(__file__).with_name("tooltip.json") _TOOLTIP_FILE = Path(__file__).with_name("tooltip.json")

View File

@@ -12,6 +12,10 @@ from functools import lru_cache
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
from version_info import module_version
__version__ = module_version(__name__)
THEME_PATH = Path(__file__).with_name("ui_theme.json") THEME_PATH = Path(__file__).with_name("ui_theme.json")

View File

@@ -6,6 +6,9 @@ from dataclasses import dataclass, field
from datetime import datetime from datetime import datetime
from typing import FrozenSet from typing import FrozenSet
from version_info import module_version
__version__ = module_version(__name__)
ALL_ACTIONS: FrozenSet[str] = frozenset( ALL_ACTIONS: FrozenSet[str] = frozenset(
{ {

71
version_info.py Normal file
View File

@@ -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}"

View File

@@ -22,8 +22,11 @@ from gestione_scarico import move_pallet_async
from locale_text import load_locale_catalog, text as loc_text from locale_text import load_locale_catalog, text as loc_text
from tooltips import WidgetToolTip, load_tooltip_catalog, tooltip_text from tooltips import WidgetToolTip, load_tooltip_catalog, tooltip_text
from ui_theme import theme_color, theme_font, theme_section, theme_value 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 window_placement import place_window_fullsize_below_parent_later
__version__ = module_version(__name__)
try: try:
from loguru import logger from loguru import logger
except Exception: # pragma: no cover - fallback used only when Loguru is not available 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._theme = theme_section("multi_udc", {})
self._locale_catalog = load_locale_catalog() self._locale_catalog = load_locale_catalog()
self._tooltip_catalog = load_tooltip_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.session = session
self.geometry(str(theme_value(self._theme, "window_geometry", "1100x700"))) self.geometry(str(theme_value(self._theme, "window_geometry", "1100x700")))
minsize = theme_value(self._theme, "window_minsize", [900, 550]) minsize = theme_value(self._theme, "window_minsize", [900, 550])

View File

@@ -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 from runtime_support import ensure_stdio, run_with_fatal_log
ensure_stdio("warehouse_main") ensure_stdio("warehouse_main")

View File

@@ -8,6 +8,9 @@ import math
import tkinter as tk import tkinter as tk
from pathlib import Path from pathlib import Path
from version_info import module_version
__version__ = module_version(__name__)
MODULE_LOG_NAME = "window_placement" MODULE_LOG_NAME = "window_placement"
MODULE_LOG_PATH = Path(__file__).with_name("window_placement.log") MODULE_LOG_PATH = Path(__file__).with_name("window_placement.log")