migrazione verso gitea

This commit is contained in:
2026-03-31 19:15:33 +02:00
parent 8806d598eb
commit f6a5b1b29f
118 changed files with 17197 additions and 459 deletions

View File

@@ -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():