Checkpoint before more window sizing work
This commit is contained in:
531
reset_corsie.py
531
reset_corsie.py
@@ -1,16 +1,166 @@
|
||||
"""Window used to inspect and empty an entire warehouse aisle.
|
||||
"""Window used to inspect and logically empty an entire warehouse aisle.
|
||||
|
||||
The module exposes a destructive maintenance tool: it summarizes the occupancy
|
||||
state of a selected aisle and, after explicit confirmation, deletes matching
|
||||
rows from ``MagazziniPallet``.
|
||||
The tool summarizes the current occupancy of one aisle and, after explicit
|
||||
confirmation, unloads every active UDC through the same logical movement
|
||||
semantics used by the rest of the WMS.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
import sys
|
||||
import tkinter as tk
|
||||
from functools import wraps
|
||||
from pathlib import Path
|
||||
from tkinter import messagebox, simpledialog, ttk
|
||||
from typing import Any
|
||||
|
||||
import customtkinter as ctk
|
||||
|
||||
from gestione_aree import AsyncRunner, BusyOverlay
|
||||
from busy_overlay import InlineBusyOverlay
|
||||
from gestione_aree import AsyncRunner
|
||||
from gestione_scarico import move_pallet_async
|
||||
from tooltips import WidgetToolTip, load_tooltip_catalog, tooltip_text
|
||||
from ui_theme import theme_color, theme_font, theme_padding, theme_section, theme_value
|
||||
from window_placement import place_window_fullsize_below_parent_later
|
||||
|
||||
try:
|
||||
from loguru import logger
|
||||
except Exception: # pragma: no cover - fallback used only when Loguru is not available
|
||||
class _FallbackLogger:
|
||||
"""Minimal adapter used only when Loguru is not installed yet."""
|
||||
|
||||
def __init__(self):
|
||||
self._logger = logging.getLogger(MODULE_LOG_NAME if "MODULE_LOG_NAME" in globals() else __name__)
|
||||
self._logger.setLevel(logging.DEBUG)
|
||||
self._logger.propagate = False
|
||||
|
||||
def bind(self, **_kwargs):
|
||||
return self
|
||||
|
||||
def add(self, sink, level="INFO", format=None, encoding="utf-8", **_kwargs):
|
||||
handler: logging.Handler
|
||||
if hasattr(sink, "write"):
|
||||
handler = logging.StreamHandler(sink)
|
||||
else:
|
||||
handler = logging.FileHandler(str(sink), encoding=encoding)
|
||||
handler.setLevel(getattr(logging, str(level).upper(), logging.INFO))
|
||||
handler.setFormatter(
|
||||
logging.Formatter("%(asctime)s | %(levelname)-8s | %(name)s | %(message)s")
|
||||
)
|
||||
self._logger.addHandler(handler)
|
||||
return 0
|
||||
|
||||
def log(self, level, message):
|
||||
getattr(self._logger, str(level).lower(), self._logger.info)(message)
|
||||
|
||||
def debug(self, message):
|
||||
self._logger.debug(message)
|
||||
|
||||
def info(self, message):
|
||||
self._logger.info(message)
|
||||
|
||||
def exception(self, message):
|
||||
self._logger.exception(message)
|
||||
|
||||
logger = _FallbackLogger()
|
||||
|
||||
|
||||
RESET_CORSIE_LOG_MODE = "INFO" # "OFF" | "INFO" | "DEBUG"
|
||||
MODULE_LOG_NAME = Path(__file__).stem
|
||||
MODULE_LOG_PATH = Path(__file__).with_suffix(".log")
|
||||
_MODULE_LOG_ENABLED = RESET_CORSIE_LOG_MODE.upper() != "OFF"
|
||||
_MODULE_LOG_LEVEL = "DEBUG" if RESET_CORSIE_LOG_MODE.upper() == "DEBUG" else "INFO"
|
||||
_MODULE_LOGGER = logger.bind(warehouse_module=MODULE_LOG_NAME)
|
||||
_MODULE_LOGGING_CONFIGURED = False
|
||||
|
||||
|
||||
def _configure_module_logger():
|
||||
"""Configure console and file logging for this module."""
|
||||
global _MODULE_LOGGING_CONFIGURED
|
||||
if _MODULE_LOGGING_CONFIGURED:
|
||||
return
|
||||
if not _MODULE_LOG_ENABLED:
|
||||
_MODULE_LOGGING_CONFIGURED = True
|
||||
return
|
||||
|
||||
record_filter = lambda record: record["extra"].get("warehouse_module") == MODULE_LOG_NAME
|
||||
|
||||
logger.add(
|
||||
sys.stderr,
|
||||
level=_MODULE_LOG_LEVEL,
|
||||
colorize=True,
|
||||
filter=record_filter,
|
||||
format=(
|
||||
"<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | "
|
||||
"<level>{level: <8}</level> | "
|
||||
"<cyan>" + MODULE_LOG_NAME + "</cyan> | "
|
||||
"<level>{message}</level>"
|
||||
),
|
||||
)
|
||||
logger.add(
|
||||
MODULE_LOG_PATH,
|
||||
level=_MODULE_LOG_LEVEL,
|
||||
colorize=False,
|
||||
encoding="utf-8",
|
||||
filter=record_filter,
|
||||
format="{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | " + MODULE_LOG_NAME + " | {message}",
|
||||
)
|
||||
_MODULE_LOGGING_CONFIGURED = True
|
||||
|
||||
|
||||
def _format_payload(payload: Any) -> str:
|
||||
"""Serialize payloads for human-readable logging."""
|
||||
try:
|
||||
return json.dumps(payload, ensure_ascii=False, indent=2, default=str)
|
||||
except Exception:
|
||||
return repr(payload)
|
||||
|
||||
|
||||
def _log_call(level: str | None = None):
|
||||
"""Trace entry, exit and failure of selected high-level functions."""
|
||||
|
||||
def decorator(func):
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
effective_level = level or _MODULE_LOG_LEVEL
|
||||
_MODULE_LOGGER.log(
|
||||
effective_level,
|
||||
f"CALL {func.__qualname__} args={_format_payload(args[1:] if args else ())} kwargs={_format_payload(kwargs)}",
|
||||
)
|
||||
try:
|
||||
result = func(*args, **kwargs)
|
||||
except Exception:
|
||||
_MODULE_LOGGER.exception(f"FAIL {func.__qualname__}")
|
||||
raise
|
||||
_MODULE_LOGGER.log(effective_level, f"RETURN {func.__qualname__}")
|
||||
return result
|
||||
|
||||
return wrapper
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
def _log_sql(query_name: str, sql: str, params: dict[str, Any] | None = None):
|
||||
"""Log one SQL statement and its parameters."""
|
||||
_MODULE_LOGGER.log(_MODULE_LOG_LEVEL, f"SQL {query_name} params={_format_payload(params or {})}")
|
||||
_MODULE_LOGGER.debug(f"SQL {query_name} text:\n{sql.strip()}")
|
||||
|
||||
|
||||
def _log_dataset(query_name: str, rows: list[Any]):
|
||||
"""Log query results at summary or full-debug level depending on the flag."""
|
||||
_MODULE_LOGGER.log(_MODULE_LOG_LEVEL, f"SQL {query_name} returned {len(rows)} rows")
|
||||
if RESET_CORSIE_LOG_MODE.upper() == "DEBUG":
|
||||
_MODULE_LOGGER.debug(f"SQL {query_name} dataset:\n{_format_payload(rows)}")
|
||||
|
||||
|
||||
_configure_module_logger()
|
||||
if _MODULE_LOG_ENABLED:
|
||||
_MODULE_LOGGER.info(
|
||||
f"Logging inizializzato su {MODULE_LOG_PATH.name} livello={_MODULE_LOG_LEVEL} mode={RESET_CORSIE_LOG_MODE.upper()}"
|
||||
)
|
||||
|
||||
|
||||
SQL_CORSIE = """
|
||||
WITH C AS (
|
||||
@@ -76,60 +226,191 @@ WHERE COALESCE(s.n,0) > 0
|
||||
ORDER BY TRY_CONVERT(int,c.Colonna), c.Colonna, TRY_CONVERT(int,c.Fila), c.Fila;
|
||||
"""
|
||||
|
||||
SQL_COUNT_DELETE = """
|
||||
SELECT COUNT(*) AS RowsToDelete
|
||||
FROM dbo.MagazziniPallet mp
|
||||
JOIN dbo.Celle c ON c.ID = mp.IDCella
|
||||
WHERE c.ID <> 9999 AND LTRIM(RTRIM(c.Corsia)) = :corsia;
|
||||
SQL_COUNT_RESET = """
|
||||
SELECT
|
||||
COUNT(DISTINCT g.BarcodePallet) AS TotUDC,
|
||||
COUNT(DISTINCT g.IDCella) AS TotCelle
|
||||
FROM dbo.XMag_GiacenzaPallet g
|
||||
JOIN dbo.Celle c ON c.ID = g.IDCella
|
||||
WHERE c.ID <> 9999
|
||||
AND LTRIM(RTRIM(c.Corsia)) = :corsia;
|
||||
"""
|
||||
|
||||
SQL_DELETE = """
|
||||
DELETE mp
|
||||
FROM dbo.MagazziniPallet mp
|
||||
JOIN dbo.Celle c ON c.ID = mp.IDCella
|
||||
WHERE c.ID <> 9999 AND LTRIM(RTRIM(c.Corsia)) = :corsia;
|
||||
SQL_UDC_RESET = """
|
||||
WITH U AS (
|
||||
SELECT DISTINCT
|
||||
g.BarcodePallet AS BarcodePallet,
|
||||
g.IDCella AS IDCella,
|
||||
CONCAT(LTRIM(RTRIM(c.Corsia)), '.', LTRIM(RTRIM(c.Colonna)), '.', LTRIM(RTRIM(c.Fila))) AS Ubicazione,
|
||||
TRY_CONVERT(int, c.Colonna) AS SortColNum,
|
||||
LTRIM(RTRIM(c.Colonna)) AS SortColTxt,
|
||||
TRY_CONVERT(int, c.Fila) AS SortFilaNum,
|
||||
LTRIM(RTRIM(c.Fila)) AS SortFilaTxt
|
||||
FROM dbo.XMag_GiacenzaPallet g
|
||||
JOIN dbo.Celle c ON c.ID = g.IDCella
|
||||
WHERE c.ID <> 9999
|
||||
AND LTRIM(RTRIM(c.Corsia)) = :corsia
|
||||
)
|
||||
SELECT
|
||||
BarcodePallet,
|
||||
IDCella,
|
||||
Ubicazione
|
||||
FROM U
|
||||
ORDER BY
|
||||
SortColNum,
|
||||
SortColTxt,
|
||||
SortFilaNum,
|
||||
SortFilaTxt,
|
||||
BarcodePallet;
|
||||
"""
|
||||
|
||||
|
||||
class ResetCorsieWindow(ctk.CTkToplevel):
|
||||
"""Toplevel used to inspect and clear the pallets assigned to an aisle."""
|
||||
|
||||
@_log_call()
|
||||
def __init__(self, parent, db_client, session=None):
|
||||
"""Create the window and immediately load the list of aisles."""
|
||||
super().__init__(parent)
|
||||
self._theme = theme_section("reset_corsie", {})
|
||||
self.title("Reset Corsie - svuotamento celle per corsia")
|
||||
self.geometry("1000x680")
|
||||
self.minsize(880, 560)
|
||||
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]))
|
||||
self.resizable(True, True)
|
||||
try:
|
||||
self.configure(fg_color=theme_color(self._theme, "window_fg_color", ("#efefef", "#2f2f2f")))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
self.db = db_client
|
||||
self.session = session
|
||||
self._busy = BusyOverlay(self)
|
||||
self._busy = InlineBusyOverlay(self, self._theme)
|
||||
self._async = AsyncRunner(self)
|
||||
self._refresh_token = 0
|
||||
self._tooltip_catalog = load_tooltip_catalog()
|
||||
|
||||
self._build_ui()
|
||||
self._load_corsie()
|
||||
|
||||
def _setup_tree_style(self):
|
||||
"""Apply a denser, spreadsheet-like style to the main result grid."""
|
||||
|
||||
style = ttk.Style(self)
|
||||
style.configure(
|
||||
"ResetCorsie.Treeview.Heading",
|
||||
font=theme_font(self._theme, "tree_heading_font", ("Segoe UI", 10, "bold")),
|
||||
background=theme_value(self._theme, "tree_heading_bg", "#b8c7db"),
|
||||
foreground=theme_value(self._theme, "tree_heading_fg", "#10243e"),
|
||||
relief="flat",
|
||||
padding=theme_padding(self._theme, "tree_heading_padding", (8, 6)),
|
||||
)
|
||||
style.map(
|
||||
"ResetCorsie.Treeview.Heading",
|
||||
background=[("active", theme_value(self._theme, "tree_heading_bg_active", "#aebfd6"))],
|
||||
relief=[("pressed", "groove"), ("!pressed", "flat")],
|
||||
)
|
||||
style.configure(
|
||||
"ResetCorsie.Treeview",
|
||||
font=theme_font(self._theme, "tree_body_font", ("Segoe UI", 10)),
|
||||
rowheight=int(theme_value(self._theme, "tree_row_height", 30)),
|
||||
background=theme_value(self._theme, "tree_body_bg", "#ffffff"),
|
||||
fieldbackground=theme_value(self._theme, "tree_body_bg", "#ffffff"),
|
||||
foreground=theme_value(self._theme, "tree_body_fg", "#1f1f1f"),
|
||||
borderwidth=0,
|
||||
)
|
||||
style.map(
|
||||
"ResetCorsie.Treeview",
|
||||
background=[("selected", theme_value(self._theme, "tree_selected_bg", "#cfe4ff"))],
|
||||
foreground=[("selected", theme_value(self._theme, "tree_selected_fg", "#10243e"))],
|
||||
)
|
||||
|
||||
@_log_call()
|
||||
def _build_ui(self):
|
||||
"""Create selectors, summary widgets and the occupied-cell grid."""
|
||||
self._setup_tree_style()
|
||||
top = ctk.CTkFrame(self)
|
||||
top.pack(fill="x", padx=8, pady=8)
|
||||
ctk.CTkLabel(top, text="Corsia:").pack(side="left")
|
||||
self.cmb = ctk.CTkComboBox(top, width=140, values=[])
|
||||
top.pack(
|
||||
fill="x",
|
||||
padx=int(theme_value(self._theme, "frame_padx", 8)),
|
||||
pady=int(theme_value(self._theme, "frame_pady", 8)),
|
||||
)
|
||||
try:
|
||||
top.configure(fg_color=theme_color(self._theme, "top_frame_fg_color", ("#d7d7d7", "#3b3b3b")))
|
||||
except Exception:
|
||||
pass
|
||||
ctk.CTkLabel(
|
||||
top,
|
||||
text="Corsia:",
|
||||
font=theme_font(self._theme, "toolbar_label_font", ("Segoe UI", 10)),
|
||||
).pack(side="left")
|
||||
self.cmb = ctk.CTkComboBox(
|
||||
top,
|
||||
width=int(theme_value(self._theme, "combobox_width", 140)),
|
||||
height=int(theme_value(self._theme, "combobox_height", 28)),
|
||||
values=[],
|
||||
font=theme_font(self._theme, "combobox_font", ("Segoe UI", 10)),
|
||||
dropdown_font=theme_font(self._theme, "combobox_font", ("Segoe UI", 10)),
|
||||
)
|
||||
self.cmb.pack(side="left", padx=(6, 10))
|
||||
ctk.CTkButton(top, text="Carica", command=self.refresh).pack(side="left")
|
||||
ctk.CTkButton(top, text="Svuota corsia...", command=self._ask_reset).pack(side="right")
|
||||
btn_refresh = ctk.CTkButton(
|
||||
top,
|
||||
text="Carica",
|
||||
command=self.refresh,
|
||||
width=int(theme_value(self._theme, "toolbar_button_width", 140)),
|
||||
height=int(theme_value(self._theme, "toolbar_button_height", 28)),
|
||||
corner_radius=int(theme_value(self._theme, "toolbar_button_corner_radius", 6)),
|
||||
font=theme_font(self._theme, "toolbar_button_font", ("Segoe UI", 10, "bold")),
|
||||
)
|
||||
btn_refresh.pack(side="left")
|
||||
btn_reset = ctk.CTkButton(
|
||||
top,
|
||||
text="Svuota corsia...",
|
||||
command=self._ask_reset,
|
||||
width=int(theme_value(self._theme, "toolbar_button_width", 140)),
|
||||
height=int(theme_value(self._theme, "toolbar_button_height", 28)),
|
||||
corner_radius=int(theme_value(self._theme, "toolbar_button_corner_radius", 6)),
|
||||
font=theme_font(self._theme, "toolbar_button_font", ("Segoe UI", 10, "bold")),
|
||||
)
|
||||
btn_reset.pack(side="right")
|
||||
WidgetToolTip(btn_refresh, tooltip_text("reset_corsie.refresh", catalog=self._tooltip_catalog))
|
||||
WidgetToolTip(btn_reset, tooltip_text("reset_corsie.empty_aisle", catalog=self._tooltip_catalog))
|
||||
|
||||
mid = ctk.CTkFrame(self)
|
||||
mid.pack(fill="both", expand=True, padx=8, pady=(0, 8))
|
||||
mid.pack(
|
||||
fill="both",
|
||||
expand=True,
|
||||
padx=int(theme_value(self._theme, "frame_padx", 8)),
|
||||
pady=(0, int(theme_value(self._theme, "frame_pady", 8))),
|
||||
)
|
||||
try:
|
||||
mid.configure(fg_color=theme_color(self._theme, "mid_frame_fg_color", ("#e5e5e5", "#383838")))
|
||||
except Exception:
|
||||
pass
|
||||
mid.grid_columnconfigure(0, weight=1)
|
||||
mid.grid_rowconfigure(0, weight=1)
|
||||
|
||||
self.tree = ttk.Treeview(mid, columns=("Ubicazione", "NumUDC"), show="headings", selectmode="browse")
|
||||
self.tree = ttk.Treeview(
|
||||
mid,
|
||||
columns=("Ubicazione", "NumUDC"),
|
||||
show="headings",
|
||||
selectmode="browse",
|
||||
style="ResetCorsie.Treeview",
|
||||
)
|
||||
self.tree.heading("Ubicazione", text="Ubicazione")
|
||||
self.tree.heading("NumUDC", text="UDC in cella")
|
||||
self.tree.column("Ubicazione", width=240, anchor="w")
|
||||
self.tree.column("NumUDC", width=120, anchor="e")
|
||||
self.tree.column(
|
||||
"Ubicazione",
|
||||
width=int(theme_value(self._theme, "tree_col_ubicazione_width", 340)),
|
||||
anchor=str(theme_value(self._theme, "tree_col_ubicazione_anchor", "center")),
|
||||
)
|
||||
self.tree.column(
|
||||
"NumUDC",
|
||||
width=int(theme_value(self._theme, "tree_col_num_udc_width", 160)),
|
||||
anchor=str(theme_value(self._theme, "tree_col_num_udc_anchor", "center")),
|
||||
)
|
||||
self.tree.tag_configure("odd", background=theme_value(self._theme, "tree_row_odd_bg", "#ffffff"))
|
||||
self.tree.tag_configure("even", background=theme_value(self._theme, "tree_row_even_bg", "#f3f6fb"))
|
||||
|
||||
sy = ttk.Scrollbar(mid, orient="vertical", command=self.tree.yview)
|
||||
sx = ttk.Scrollbar(mid, orient="horizontal", command=self.tree.xview)
|
||||
@@ -139,11 +420,27 @@ class ResetCorsieWindow(ctk.CTkToplevel):
|
||||
sx.grid(row=1, column=0, sticky="ew")
|
||||
|
||||
bottom = ctk.CTkFrame(self)
|
||||
bottom.pack(fill="x", padx=8, pady=(0, 8))
|
||||
ctk.CTkLabel(bottom, text="Riepilogo", font=("Segoe UI", 12, "bold")).pack(anchor="w", padx=8, pady=(8, 0))
|
||||
bottom.pack(
|
||||
fill="x",
|
||||
padx=int(theme_value(self._theme, "frame_padx", 8)),
|
||||
pady=(0, int(theme_value(self._theme, "frame_pady", 8))),
|
||||
)
|
||||
try:
|
||||
bottom.configure(fg_color=theme_color(self._theme, "bottom_frame_fg_color", ("#dcdcdc", "#363636")))
|
||||
except Exception:
|
||||
pass
|
||||
ctk.CTkLabel(
|
||||
bottom,
|
||||
text="Riepilogo",
|
||||
font=theme_font(self._theme, "summary_title_font", ("Segoe UI", 12, "bold")),
|
||||
).pack(anchor="w", padx=8, pady=(8, 0))
|
||||
|
||||
g = ctk.CTkFrame(bottom)
|
||||
g.pack(fill="x", padx=8, pady=8)
|
||||
try:
|
||||
g.configure(fg_color=theme_color(self._theme, "inner_summary_frame_fg_color", ("#d4d4d4", "#404040")))
|
||||
except Exception:
|
||||
pass
|
||||
self.var_tot_celle = tk.StringVar(value="0")
|
||||
self.var_occ = tk.StringVar(value="0")
|
||||
self.var_dbl = tk.StringVar(value="0")
|
||||
@@ -151,8 +448,16 @@ class ResetCorsieWindow(ctk.CTkToplevel):
|
||||
|
||||
def _kv(parent_widget, label, var, col):
|
||||
"""Build a compact summary label/value pair."""
|
||||
ctk.CTkLabel(parent_widget, text=label, font=("Segoe UI", 9, "bold")).grid(row=0, column=col * 2, sticky="w", padx=(0, 6))
|
||||
ctk.CTkLabel(parent_widget, textvariable=var).grid(row=0, column=col * 2 + 1, sticky="w", padx=(0, 18))
|
||||
ctk.CTkLabel(
|
||||
parent_widget,
|
||||
text=label,
|
||||
font=theme_font(self._theme, "summary_label_font", ("Segoe UI", 9, "bold")),
|
||||
).grid(row=0, column=col * 2, sticky="w", padx=(0, 6))
|
||||
ctk.CTkLabel(
|
||||
parent_widget,
|
||||
textvariable=var,
|
||||
font=theme_font(self._theme, "summary_value_font", ("Segoe UI", 9)),
|
||||
).grid(row=0, column=col * 2 + 1, sticky="w", padx=(0, 18))
|
||||
|
||||
g.grid_columnconfigure(7, weight=1)
|
||||
_kv(g, "Tot. celle:", self.var_tot_celle, 0)
|
||||
@@ -160,10 +465,14 @@ class ResetCorsieWindow(ctk.CTkToplevel):
|
||||
_kv(g, "Celle doppie:", self.var_dbl, 2)
|
||||
_kv(g, "Tot. pallet:", self.var_pallet, 3)
|
||||
|
||||
@_log_call()
|
||||
def _load_corsie(self):
|
||||
"""Load available aisles and preselect ``1A`` when present."""
|
||||
_log_sql("reset_corsie_corsie", SQL_CORSIE, {})
|
||||
|
||||
def _ok(res):
|
||||
rows = res.get("rows", []) if isinstance(res, dict) else []
|
||||
_log_dataset("reset_corsie_corsie", rows)
|
||||
items = [r[0] for r in rows]
|
||||
self.cmb.configure(values=items)
|
||||
if items:
|
||||
@@ -174,87 +483,168 @@ class ResetCorsieWindow(ctk.CTkToplevel):
|
||||
messagebox.showinfo("Info", "Nessuna corsia trovata.", parent=self)
|
||||
|
||||
def _err(ex):
|
||||
_MODULE_LOGGER.exception(f"Errore caricamento corsie reset corsie: {ex}")
|
||||
messagebox.showerror("Errore", f"Caricamento corsie fallito:\n{ex}", parent=self)
|
||||
|
||||
self._async.run(self.db.query_json(SQL_CORSIE, {}), _ok, _err, busy=self._busy, message="Carico corsie...")
|
||||
|
||||
@_log_call()
|
||||
def refresh(self):
|
||||
"""Refresh both the summary counters and the occupied-cell list."""
|
||||
corsia = self.cmb.get().strip()
|
||||
if not corsia:
|
||||
return
|
||||
_log_sql("reset_corsie_riepilogo", SQL_RIEPILOGO, {"corsia": corsia})
|
||||
_log_sql("reset_corsie_dettaglio", SQL_DETTAGLIO, {"corsia": corsia})
|
||||
|
||||
def _ok_sum(res):
|
||||
rows = res.get("rows", []) if isinstance(res, dict) else []
|
||||
if rows:
|
||||
tot, occ, dbl, pallet = rows[0]
|
||||
self.var_tot_celle.set(str(tot or 0))
|
||||
self.var_occ.set(str(occ or 0))
|
||||
self.var_dbl.set(str(dbl or 0))
|
||||
self.var_pallet.set(str(pallet or 0))
|
||||
else:
|
||||
self.var_tot_celle.set("0")
|
||||
self.var_occ.set("0")
|
||||
self.var_dbl.set("0")
|
||||
self.var_pallet.set("0")
|
||||
async def _q():
|
||||
riepilogo = await self.db.query_json(SQL_RIEPILOGO, {"corsia": corsia})
|
||||
dettaglio = await self.db.query_json(SQL_DETTAGLIO, {"corsia": corsia})
|
||||
return {"riepilogo": riepilogo, "dettaglio": dettaglio}
|
||||
|
||||
def _err_sum(ex):
|
||||
messagebox.showerror("Errore", f"Riepilogo fallito:\n{ex}", parent=self)
|
||||
def _ok(payload):
|
||||
try:
|
||||
riepilogo = payload.get("riepilogo", {}) if isinstance(payload, dict) else {}
|
||||
dettaglio = payload.get("dettaglio", {}) if isinstance(payload, dict) else {}
|
||||
|
||||
self._async.run(self.db.query_json(SQL_RIEPILOGO, {"corsia": corsia}), _ok_sum, _err_sum, busy=self._busy, message=f"Riepilogo {corsia}...")
|
||||
sum_rows = riepilogo.get("rows", []) if isinstance(riepilogo, dict) else []
|
||||
det_rows = dettaglio.get("rows", []) if isinstance(dettaglio, dict) else []
|
||||
_log_dataset("reset_corsie_riepilogo", sum_rows)
|
||||
_log_dataset("reset_corsie_dettaglio", det_rows)
|
||||
|
||||
def _ok_det(res):
|
||||
rows = res.get("rows", []) if isinstance(res, dict) else []
|
||||
for item in self.tree.get_children():
|
||||
self.tree.delete(item)
|
||||
for _idc, ubi, n in rows:
|
||||
self.tree.insert("", "end", values=(ubi, n))
|
||||
if sum_rows:
|
||||
tot, occ, dbl, pallet = sum_rows[0]
|
||||
self.var_tot_celle.set(str(tot or 0))
|
||||
self.var_occ.set(str(occ or 0))
|
||||
self.var_dbl.set(str(dbl or 0))
|
||||
self.var_pallet.set(str(pallet or 0))
|
||||
else:
|
||||
self.var_tot_celle.set("0")
|
||||
self.var_occ.set("0")
|
||||
self.var_dbl.set("0")
|
||||
self.var_pallet.set("0")
|
||||
|
||||
def _err_det(ex):
|
||||
messagebox.showerror("Errore", f"Dettaglio fallito:\n{ex}", parent=self)
|
||||
for item in self.tree.get_children():
|
||||
self.tree.delete(item)
|
||||
for idx, (_idc, ubi, n) in enumerate(det_rows):
|
||||
tag = "even" if idx % 2 else "odd"
|
||||
self.tree.insert("", "end", values=(ubi, n), tags=(tag,))
|
||||
except Exception as ex:
|
||||
_MODULE_LOGGER.exception(f"Errore UI refresh reset corsie corsia={corsia}: {ex}")
|
||||
messagebox.showerror("Errore", f"Aggiornamento interfaccia fallito:\n{ex}", parent=self)
|
||||
|
||||
self._async.run(self.db.query_json(SQL_DETTAGLIO, {"corsia": corsia}), _ok_det, _err_det, busy=None, message=None)
|
||||
def _err(ex):
|
||||
_MODULE_LOGGER.exception(f"Errore refresh reset corsie corsia={corsia}: {ex}")
|
||||
messagebox.showerror("Errore", f"Refresh fallito:\n{ex}", parent=self)
|
||||
|
||||
self._async.run(_q(), _ok, _err, busy=self._busy, message=f"Riepilogo {corsia}...")
|
||||
|
||||
@_log_call()
|
||||
def _ask_reset(self):
|
||||
"""Ask for confirmation and start the delete flow for the selected aisle."""
|
||||
"""Ask for confirmation and start the logical unload flow for the selected aisle."""
|
||||
corsia = self.cmb.get().strip()
|
||||
if not corsia:
|
||||
return
|
||||
_log_sql("reset_corsie_count_reset", SQL_COUNT_RESET, {"corsia": corsia})
|
||||
|
||||
def _ok_count(res):
|
||||
rows = res.get("rows", []) if isinstance(res, dict) else []
|
||||
n = int(rows[0][0]) if rows else 0
|
||||
if n <= 0:
|
||||
messagebox.showinfo("Svuota corsia", f"Nessun pallet da rimuovere per la corsia {corsia}.", parent=self)
|
||||
_log_dataset("reset_corsie_count_reset", rows)
|
||||
tot_udc = int(rows[0][0] or 0) if rows else 0
|
||||
tot_celle = int(rows[0][1] or 0) if rows else 0
|
||||
if tot_udc <= 0:
|
||||
messagebox.showinfo("Svuota corsia", f"Nessuna UDC attiva da scaricare per la corsia {corsia}.", parent=self)
|
||||
return
|
||||
msg = (
|
||||
f"Verranno cancellati {n} record da MagazziniPallet per la corsia {corsia}.",
|
||||
"Questa operazione e' irreversibile.",
|
||||
f"Verranno scaricate logicamente {tot_udc} UDC attive distribuite su {tot_celle} celle della corsia {corsia}.",
|
||||
"L'operazione verra' eseguita come scarico verso 9000000 / 9999, senza cancellazioni fisiche dirette.",
|
||||
"Digitare il nome della corsia per confermare:",
|
||||
)
|
||||
confirm = simpledialog.askstring("Conferma", "\n".join(msg), parent=self)
|
||||
if confirm is None:
|
||||
_MODULE_LOGGER.info(f"Reset corsia {corsia}: conferma annullata dall'utente")
|
||||
return
|
||||
if confirm.strip().upper() != corsia.upper():
|
||||
_MODULE_LOGGER.info(f"Reset corsia {corsia}: testo conferma non corrispondente ({confirm!r})")
|
||||
messagebox.showwarning("Annullato", "Testo di conferma non corrispondente.", parent=self)
|
||||
return
|
||||
self._do_reset(corsia)
|
||||
|
||||
def _err_count(ex):
|
||||
messagebox.showerror("Errore", f"Conteggio righe da cancellare fallito:\n{ex}", parent=self)
|
||||
_MODULE_LOGGER.exception(f"Errore conteggio reset corsie corsia={corsia}: {ex}")
|
||||
messagebox.showerror("Errore", f"Conteggio UDC da scaricare fallito:\n{ex}", parent=self)
|
||||
|
||||
self._async.run(self.db.query_json(SQL_COUNT_DELETE, {"corsia": corsia}), _ok_count, _err_count, busy=self._busy, message="Verifico...")
|
||||
self._async.run(self.db.query_json(SQL_COUNT_RESET, {"corsia": corsia}), _ok_count, _err_count, busy=self._busy, message="Verifico...")
|
||||
|
||||
@_log_call()
|
||||
def _do_reset(self, corsia: str):
|
||||
"""Execute the actual delete and refresh the window afterwards."""
|
||||
def _ok_del(_):
|
||||
messagebox.showinfo("Completato", f"Corsia {corsia}: svuotamento completato.", parent=self)
|
||||
"""Execute the logical unload of every active UDC in the selected aisle."""
|
||||
_log_sql("reset_corsie_udc_reset", SQL_UDC_RESET, {"corsia": corsia})
|
||||
|
||||
async def _q():
|
||||
payload = await self.db.query_json(SQL_UDC_RESET, {"corsia": corsia})
|
||||
rows = payload.get("rows", []) if isinstance(payload, dict) else []
|
||||
_log_dataset("reset_corsie_udc_reset", rows)
|
||||
|
||||
success = 0
|
||||
failed: list[dict[str, Any]] = []
|
||||
utente = str(getattr(self.session, "login", "") or "warehouse_ui").strip()
|
||||
|
||||
for barcode_pallet, idcella, ubicazione in rows:
|
||||
try:
|
||||
await move_pallet_async(
|
||||
self.db,
|
||||
barcode_pallet=str(barcode_pallet or "").strip(),
|
||||
target_idcella=9999,
|
||||
target_barcode_cella="9000000",
|
||||
utente=utente,
|
||||
)
|
||||
success += 1
|
||||
except Exception as ex:
|
||||
failed.append(
|
||||
{
|
||||
"barcode_pallet": str(barcode_pallet or ""),
|
||||
"idcella": int(idcella or 0),
|
||||
"ubicazione": str(ubicazione or ""),
|
||||
"error": str(ex),
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
"total": len(rows),
|
||||
"success": success,
|
||||
"failed": failed,
|
||||
}
|
||||
|
||||
def _ok_del(result):
|
||||
total = int((result or {}).get("total", 0))
|
||||
success = int((result or {}).get("success", 0))
|
||||
failed = list((result or {}).get("failed", []))
|
||||
_MODULE_LOGGER.info(
|
||||
f"Reset corsia {corsia}: scarico logico completato success={success} total={total} failed={len(failed)}"
|
||||
)
|
||||
if failed:
|
||||
messagebox.showwarning(
|
||||
"Completato con errori",
|
||||
(
|
||||
f"Corsia {corsia}: scaricate {success} UDC su {total}.\n"
|
||||
f"Errori su {len(failed)} UDC. Controllare {MODULE_LOG_PATH.name}."
|
||||
),
|
||||
parent=self,
|
||||
)
|
||||
else:
|
||||
messagebox.showinfo(
|
||||
"Completato",
|
||||
f"Corsia {corsia}: svuotamento logico completato su {success} UDC.",
|
||||
parent=self,
|
||||
)
|
||||
self.refresh()
|
||||
|
||||
def _err_del(ex):
|
||||
_MODULE_LOGGER.exception(f"Errore reset logico corsie corsia={corsia}: {ex}")
|
||||
messagebox.showerror("Errore", f"Svuotamento fallito:\n{ex}", parent=self)
|
||||
|
||||
self._async.run(self.db.query_json(SQL_DELETE, {"corsia": corsia}), _ok_del, _err_del, busy=self._busy, message=f"Svuoto {corsia}...")
|
||||
self._async.run(_q(), _ok_del, _err_del, busy=self._busy, message=f"Svuoto {corsia}...")
|
||||
|
||||
|
||||
def open_reset_corsie_window(parent, db_app, session=None):
|
||||
@@ -262,6 +652,10 @@ def open_reset_corsie_window(parent, db_app, session=None):
|
||||
key = "_reset_corsie_window_singleton"
|
||||
ex = getattr(parent, key, None)
|
||||
if ex and ex.winfo_exists():
|
||||
try:
|
||||
ex.deiconify()
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
ex.lift()
|
||||
ex.focus_force()
|
||||
@@ -270,6 +664,7 @@ def open_reset_corsie_window(parent, db_app, session=None):
|
||||
pass
|
||||
win = ResetCorsieWindow(parent, db_app, session=session)
|
||||
setattr(parent, key, win)
|
||||
place_window_fullsize_below_parent_later(parent, win)
|
||||
try:
|
||||
win.lift()
|
||||
win.focus_force()
|
||||
|
||||
Reference in New Issue
Block a user