Aggiungi sequenza prelievo e gestisci destinazioni senza nazione

This commit is contained in:
2026-09-29 11:52:33 +02:00
parent 5dc26b21b1
commit 3a2893dc3d
17 changed files with 2329 additions and 19 deletions

View File

@@ -81,6 +81,7 @@ from ui_tables import (
from user_session import UserSession
from version_info import module_version, versioned_title
from window_placement import place_window_fullsize_below_parent_later
from pickinglist_sequence import open_pickinglist_sequence_window
__version__ = module_version(__name__)
@@ -227,18 +228,21 @@ SELECT
MAX(Magazzino) AS Magazzino,
MAX(Area) AS Area,
MAX(Cella) AS Cella,
MIN(OrdinePrelievoGruppo) AS OrdinePrelievoGruppo,
MIN(Ordinamento) AS Ordinamento,
MAX(IDStato) AS IDStato
FROM dbo.py_ViewPackingListRestante
FROM dbo.py_ViewPackingListPrelievo
WHERE DataDocumento > CAST((GETDATE() - :days) AS int)
GROUP BY Documento, CodNazione, NAZIONE, Stato
ORDER BY MIN(Ordinamento), Documento, NAZIONE, Stato;
ORDER BY MIN(OrdinePrelievoGruppo), MIN(Ordinamento), Documento, NAZIONE, Stato;
"""
SQL_PL_DETAILS = """
SELECT *
FROM dbo.py_ViewPackingListRestante
FROM dbo.py_ViewPackingListPrelievo
WHERE Documento = :Documento
ORDER BY Ordinamento;
AND DataDocumento > CAST((GETDATE() - :days) AS int)
ORDER BY OrdinePrelievoGruppo, Ordinamento, Pallet;
"""
# -------------------- helpers --------------------
@@ -649,6 +653,7 @@ class GestionePickingListFrame(ctk.CTkFrame):
self.rows_models: list[PLRow] = []
self._detail_cache: Dict[Any, list] = {}
self.detail_doc = None
self.days_var = tk.StringVar(value="10")
self._detail_sort_key: Optional[str] = None
self._detail_sort_reverse = False
self._detail_sorting = False
@@ -699,6 +704,7 @@ class GestionePickingListFrame(ctk.CTkFrame):
(loc_text("picking.button.reload", catalog=self._locale_catalog, default="Ricarica"), self.reload_from_db),
(loc_text("picking.button.prenota", catalog=self._locale_catalog, default="Prenota"), self.on_prenota),
(loc_text("picking.button.sprenota", catalog=self._locale_catalog, default="S-prenota"), self.on_sprenota),
(loc_text("picking.button.sequence", catalog=self._locale_catalog, default="Sequenza di prelievo"), self.on_sequence),
(loc_text("picking.button.export", catalog=self._locale_catalog, default="Esporta XLSX"), self.on_export)
]):
ctk.CTkButton(
@@ -708,6 +714,20 @@ class GestionePickingListFrame(ctk.CTkFrame):
font=theme_font(self._theme, "toolbar_button_font", ("Segoe UI", 10, "bold")),
).grid(row=0, column=i, padx=6)
ctk.CTkLabel(
top,
text=loc_text("picking.label.days", catalog=self._locale_catalog, default="Giorni:"),
font=theme_font(self._theme, "toolbar_label_font", ("Segoe UI", 10)),
).grid(row=0, column=5, padx=(18, 4), sticky="e")
ctk.CTkOptionMenu(
top,
values=["10", "15", "20", "30"],
variable=self.days_var,
width=82,
command=lambda _value: self.reload_from_db(),
font=theme_font(self._theme, "toolbar_button_font", ("Segoe UI", 10, "bold")),
).grid(row=0, column=6, padx=(0, 6), sticky="w")
# --- micro spinner a destra della toolbar ---
self.spinner = ToolbarSpinner(top)
self.spinner.widget().grid(row=0, column=10, padx=(8,0)) # largo spazio a destra
@@ -998,6 +1018,15 @@ class GestionePickingListFrame(ctk.CTkFrame):
documento = _s(self.detail_doc)
return documento or None
def _selected_days(self) -> int:
"""Return the selected look-back interval for operational picking lists."""
try:
days = int(str(self.days_var.get()).strip())
except Exception:
days = 10
return days if days in (10, 15, 20, 30) else 10
# ----- eventi -----
@_log_call()
def on_row_checked(self, model: PLRow, is_checked: bool):
@@ -1013,10 +1042,12 @@ class GestionePickingListFrame(ctk.CTkFrame):
self.detail_doc = model.pl.get("Documento")
_MODULE_LOGGER.info(f"Documento selezionato per il dettaglio: {self.detail_doc}")
self.spinner.start(" Carico dettagli…") # spinner ON
days = self._selected_days()
async def _job():
_log_sql("SQL_PL_DETAILS", SQL_PL_DETAILS, {"Documento": self.detail_doc})
return await self.db_client.query_json(SQL_PL_DETAILS, {"Documento": self.detail_doc})
params = {"Documento": self.detail_doc, "days": days}
_log_sql("SQL_PL_DETAILS", SQL_PL_DETAILS, params)
return await self.db_client.query_json(SQL_PL_DETAILS, params)
def _ok(res):
# NON fermare lo spinner subito: lo farà _refresh_details_incremental
@@ -1058,9 +1089,11 @@ class GestionePickingListFrame(ctk.CTkFrame):
# document selected when it is still visible.
reselect_documento = self._selected_documento_for_reload()
self.spinner.start(" Carico…") # spinner ON
days = self._selected_days()
async def _job():
_log_sql("SQL_PL", SQL_PL, {})
return await self.db_client.query_json(SQL_PL, {})
params = {"days": days}
_log_sql("SQL_PL", SQL_PL, params)
return await self.db_client.query_json(SQL_PL, params)
def _on_success(res):
rows = _rows_to_dicts(res)
_log_dataset("SQL_PL", rows)
@@ -1156,6 +1189,45 @@ class GestionePickingListFrame(ctk.CTkFrame):
self._load_detail_sheet_data(self._detail_rows_to_sheet_data(rows))
# ----- azioni -----
@_log_call()
def on_sequence(self):
"""Open the pickup-sequence editor for the selected picking list."""
if not self._can("pickinglist.sequence"):
messagebox.showwarning(
loc_text("picking.sequence.title", catalog=self._locale_catalog, default="Sequenza di prelievo"),
loc_text("picking.sequence.msg.denied", catalog=self._locale_catalog, default="Permesso negato."),
parent=self,
)
return
model = self._get_selected_model()
if not model:
messagebox.showinfo(
loc_text("picking.sequence.title", catalog=self._locale_catalog, default="Sequenza di prelievo"),
loc_text(
"picking.sequence.msg.select",
catalog=self._locale_catalog,
default="Seleziona una Picking List prima di definire la sequenza.",
),
parent=self,
)
return
documento = _s(model.pl.get("Documento"))
def _on_saved():
self._detail_cache.pop(documento, None)
self.reload_from_db(reselect_documento=documento)
open_pickinglist_sequence_window(
self.winfo_toplevel(),
self.db_client,
documento=documento,
session=self.session,
on_saved=_on_saved,
)
@_log_call()
def on_prenota(self):
"""Reserve the selected picking list."""