migrazione verso gitea
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
"""Graphical aisle layout viewer for warehouse cells and pallet occupancy."""
|
||||
|
||||
from __future__ import annotations
|
||||
import tkinter as tk
|
||||
from tkinter import Menu, messagebox, filedialog
|
||||
@@ -15,6 +17,7 @@ FG_LIGHT = "#FFFFFF"
|
||||
|
||||
|
||||
def pct_text(p_full: float, p_double: float | None = None) -> str:
|
||||
"""Format occupancy percentages for the progress-bar labels."""
|
||||
p_full = max(0.0, min(1.0, p_full))
|
||||
pf = round(p_full * 100, 1)
|
||||
pe = round(100 - pf, 1)
|
||||
@@ -36,6 +39,7 @@ class LayoutWindow(ctk.CTkToplevel):
|
||||
- Export XLSX
|
||||
"""
|
||||
def __init__(self, parent: tk.Widget, db_app):
|
||||
"""Create the window and initialize the state used by the matrix view."""
|
||||
super().__init__(parent)
|
||||
self.title("Warehouse · Layout corsie")
|
||||
self.geometry("1200x740")
|
||||
@@ -82,6 +86,7 @@ class LayoutWindow(ctk.CTkToplevel):
|
||||
|
||||
# ---------------- TOP BAR ----------------
|
||||
def _build_top(self):
|
||||
"""Create the top toolbar with aisle selection and search controls."""
|
||||
top = ctk.CTkFrame(self)
|
||||
top.grid(row=0, column=0, sticky="nsew", padx=8, pady=6)
|
||||
for i in range(4):
|
||||
@@ -114,6 +119,7 @@ class LayoutWindow(ctk.CTkToplevel):
|
||||
|
||||
# ---------------- MATRIX HOST ----------------
|
||||
def _build_matrix_host(self):
|
||||
"""Create the container that will host the dynamically rebuilt matrix."""
|
||||
center = ctk.CTkFrame(self)
|
||||
center.grid(row=1, column=0, sticky="nsew", padx=8, pady=(0, 6))
|
||||
center.grid_rowconfigure(0, weight=1)
|
||||
@@ -122,6 +128,7 @@ class LayoutWindow(ctk.CTkToplevel):
|
||||
self.host.grid(row=0, column=0, sticky="nsew", padx=4, pady=4)
|
||||
|
||||
def _apply_cell_style(self, btn: ctk.CTkButton, state: int):
|
||||
"""Apply the visual state associated with a cell occupancy level."""
|
||||
if state == 0:
|
||||
btn.configure(
|
||||
fg_color=COLOR_EMPTY, hover_color="#9A9A9A",
|
||||
@@ -139,6 +146,7 @@ class LayoutWindow(ctk.CTkToplevel):
|
||||
)
|
||||
|
||||
def _clear_highlight(self):
|
||||
"""Remove the temporary highlight from the previously focused cell."""
|
||||
if self._highlighted and self.buttons:
|
||||
r, c = self._highlighted
|
||||
try:
|
||||
@@ -162,6 +170,7 @@ class LayoutWindow(ctk.CTkToplevel):
|
||||
self._highlighted = None
|
||||
|
||||
def _rebuild_matrix(self, rows: int, cols: int, state, fila_txt, col_txt, desc, udc1, corsia):
|
||||
"""Recreate the visible cell matrix from the latest query result."""
|
||||
# prima rimuovi highlight su vecchi bottoni
|
||||
self._clear_highlight()
|
||||
# ripulisci host
|
||||
@@ -213,6 +222,7 @@ class LayoutWindow(ctk.CTkToplevel):
|
||||
|
||||
# ---------------- CONTEXT MENU ----------------
|
||||
def _open_menu(self, event, r, c):
|
||||
"""Open the context menu for a single matrix cell."""
|
||||
st = self.matrix_state[r][c]
|
||||
corsia = self.corsia_selezionata.get()
|
||||
label = f"{corsia}.{self.col_txt[r][c]}.{self.fila_txt[r][c]}"
|
||||
@@ -234,6 +244,7 @@ class LayoutWindow(ctk.CTkToplevel):
|
||||
m.tk_popup(x, y)
|
||||
|
||||
def _set_cell(self, r, c, val):
|
||||
"""Update a cell state in memory and refresh the local statistics."""
|
||||
self.matrix_state[r][c] = val
|
||||
btn = self.buttons[r][c]
|
||||
self._apply_cell_style(btn, val)
|
||||
@@ -241,6 +252,7 @@ class LayoutWindow(ctk.CTkToplevel):
|
||||
|
||||
# ---------------- STATS ----------------
|
||||
def _build_stats(self):
|
||||
"""Create progress bars, labels and legend for occupancy statistics."""
|
||||
bottom = ctk.CTkFrame(self)
|
||||
bottom.grid(row=2, column=0, sticky="nsew", padx=8, pady=6)
|
||||
bottom.grid_columnconfigure(0, weight=1)
|
||||
@@ -265,6 +277,7 @@ class LayoutWindow(ctk.CTkToplevel):
|
||||
self._legend(leg, 5, "Doppia UDC", COLOR_DOUBLE)
|
||||
|
||||
def _legend(self, parent, col, text, color):
|
||||
"""Add a legend entry describing one matrix color."""
|
||||
box = tk.Canvas(parent, width=18, height=12, highlightthickness=0)
|
||||
box.create_rectangle(0, 0, 18, 12, fill=color, width=1, outline="#444")
|
||||
box.grid(row=0, column=col)
|
||||
@@ -272,6 +285,7 @@ class LayoutWindow(ctk.CTkToplevel):
|
||||
|
||||
# ---------------- DATA LOADING ----------------
|
||||
def _load_corsie(self):
|
||||
"""Load the list of aisles available for visualization."""
|
||||
sql = """
|
||||
WITH C AS (
|
||||
SELECT DISTINCT LTRIM(RTRIM(Corsia)) AS Corsia
|
||||
@@ -316,6 +330,7 @@ class LayoutWindow(ctk.CTkToplevel):
|
||||
self._async.run(self.db.query_json(sql, {}), _ok, _err, busy=self._busy, message="Carico corsie…")
|
||||
|
||||
def _on_select(self, _):
|
||||
"""Load the selected aisle when the listbox selection changes."""
|
||||
sel = self.lb.curselection()
|
||||
if not sel:
|
||||
return
|
||||
@@ -324,6 +339,7 @@ class LayoutWindow(ctk.CTkToplevel):
|
||||
self._load_matrix(corsia)
|
||||
|
||||
def _select_corsia_in_listbox(self, corsia: str):
|
||||
"""Select a given aisle inside the listbox if it is present."""
|
||||
for i in range(self.lb.size()):
|
||||
if self.lb.get(i) == corsia:
|
||||
self.lb.selection_clear(0, tk.END)
|
||||
@@ -332,6 +348,7 @@ class LayoutWindow(ctk.CTkToplevel):
|
||||
break
|
||||
|
||||
def _load_matrix(self, corsia: str):
|
||||
"""Query and render the matrix for the selected aisle."""
|
||||
# nuovo token richiesta → evita che risposte vecchie spazzino la UI
|
||||
self._req_counter += 1
|
||||
req_id = self._req_counter
|
||||
@@ -440,6 +457,7 @@ class LayoutWindow(ctk.CTkToplevel):
|
||||
|
||||
# ---------------- SEARCH ----------------
|
||||
def _search_udc(self):
|
||||
"""Find a pallet barcode and navigate to the aisle and cell that contain it."""
|
||||
barcode = (self.search_var.get() or "").strip()
|
||||
if not barcode:
|
||||
self._toast("Inserisci un barcode UDC da cercare.")
|
||||
@@ -488,6 +506,7 @@ class LayoutWindow(ctk.CTkToplevel):
|
||||
self._async.run(self.db.query_json(sql, {"barcode": barcode}), _ok, _err, busy=self._busy, message="Cerco UDC…")
|
||||
|
||||
def _try_highlight(self, col_txt: str, fila_txt: str) -> bool:
|
||||
"""Highlight a cell by its textual row and column labels."""
|
||||
for r in range(len(self.col_txt)):
|
||||
for c in range(len(self.col_txt[r])):
|
||||
if self.col_txt[r][c] == col_txt and self.fila_txt[r][c] == fila_txt:
|
||||
@@ -504,15 +523,18 @@ class LayoutWindow(ctk.CTkToplevel):
|
||||
return False
|
||||
|
||||
def _highlight_cell_by_labels(self, col_txt: str, fila_txt: str):
|
||||
"""Show a toast when a searched cell cannot be highlighted."""
|
||||
if not self._try_highlight(col_txt, fila_txt):
|
||||
self._toast("Cella trovata ma non mappabile a pulsante.")
|
||||
|
||||
# ---------------- COMMANDS ----------------
|
||||
def _refresh_current(self):
|
||||
"""Reload the matrix of the currently selected aisle."""
|
||||
if self.corsia_selezionata.get():
|
||||
self._load_matrix(self.corsia_selezionata.get())
|
||||
|
||||
def _export_xlsx(self):
|
||||
"""Export both matrix metadata and the rendered grid to Excel."""
|
||||
if not self.matrix_state:
|
||||
messagebox.showinfo("Export", "Nessuna matrice da esportare.")
|
||||
return
|
||||
@@ -569,6 +591,7 @@ class LayoutWindow(ctk.CTkToplevel):
|
||||
|
||||
# ---------------- STATS ----------------
|
||||
def _refresh_stats(self):
|
||||
"""Refresh global and local occupancy statistics shown in the footer."""
|
||||
# globale dal DB
|
||||
sql_tot = """
|
||||
WITH C AS (
|
||||
@@ -612,6 +635,7 @@ class LayoutWindow(ctk.CTkToplevel):
|
||||
self.sel_text.configure(text=pct_text(p_full, p_dbl))
|
||||
|
||||
def _draw_bar(self, cv: tk.Canvas, p_full: float):
|
||||
"""Draw a horizontal occupancy bar on the given canvas."""
|
||||
cv.delete("all")
|
||||
w = max(300, cv.winfo_width() or 600)
|
||||
h = 18
|
||||
@@ -622,6 +646,7 @@ class LayoutWindow(ctk.CTkToplevel):
|
||||
|
||||
# ---------------- UTIL ----------------
|
||||
def _toast(self, msg, ms=1400):
|
||||
"""Show a transient status message at the bottom of the window."""
|
||||
if not hasattr(self, "_status"):
|
||||
self._status = ctk.CTkLabel(self, anchor="w")
|
||||
self._status.grid(row=3, column=0, sticky="ew")
|
||||
@@ -629,6 +654,7 @@ class LayoutWindow(ctk.CTkToplevel):
|
||||
self.after(ms, lambda: self._status.configure(text=""))
|
||||
|
||||
def _copy(self, txt: str):
|
||||
"""Copy a string to the clipboard and inform the user."""
|
||||
self.clipboard_clear()
|
||||
self.clipboard_append(txt)
|
||||
self._toast(f"Copiato: {txt}")
|
||||
@@ -636,6 +662,7 @@ class LayoutWindow(ctk.CTkToplevel):
|
||||
|
||||
|
||||
def destroy(self):
|
||||
"""Mark the window as closed and release dynamic widgets safely."""
|
||||
# evita nuovi refresh/async dopo destroy
|
||||
self._alive = False
|
||||
# cancella eventuali timer
|
||||
@@ -656,6 +683,7 @@ class LayoutWindow(ctk.CTkToplevel):
|
||||
pass
|
||||
|
||||
def open_layout_window(parent, db_app):
|
||||
"""Open the layout window as a singleton-like child of ``parent``."""
|
||||
key = "_layout_window_singleton"
|
||||
ex = getattr(parent, key, None)
|
||||
if ex and ex.winfo_exists():
|
||||
|
||||
Reference in New Issue
Block a user