Aggiungi versamento residui 7G da storico pickinglist

This commit is contained in:
2026-06-17 11:24:05 +02:00
parent be7ce700d1
commit 77080db853
2 changed files with 146 additions and 2 deletions

View File

@@ -11,6 +11,7 @@ import customtkinter as ctk
from busy_overlay import InlineBusyOverlay from busy_overlay import InlineBusyOverlay
from gestione_aree import AsyncRunner from gestione_aree import AsyncRunner
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 ui_theme import theme_color, theme_font, theme_section, theme_value from ui_theme import theme_color, theme_font, theme_section, theme_value
from ui_tables import merge_tags, style_treeview, zebra_tag from ui_tables import merge_tags, style_treeview, zebra_tag
@@ -159,6 +160,9 @@ class StoricoPickingListWindow(ctk.CTkToplevel):
self._async = AsyncRunner(self) self._async = AsyncRunner(self)
self._busy = InlineBusyOverlay(self, self._theme) self._busy = InlineBusyOverlay(self, self._theme)
self.var_documento = tk.StringVar() self.var_documento = tk.StringVar()
self._selected_documento: str | None = None
self._selected_stato_operativo: str = ""
self._detail_rows: list[dict[str, Any]] = []
self.title(versioned_title(loc_text("history.picking.title", catalog=self._locale_catalog, default="Storico Picking List"), __name__)) 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")))
@@ -182,7 +186,7 @@ class StoricoPickingListWindow(ctk.CTkToplevel):
fg_color=theme_color(self._theme, "toolbar_frame_fg_color", ("#d7d7d7", "#3b3b3b")), fg_color=theme_color(self._theme, "toolbar_frame_fg_color", ("#d7d7d7", "#3b3b3b")),
) )
top.grid(row=0, column=0, sticky="ew", padx=8, pady=8) top.grid(row=0, column=0, sticky="ew", padx=8, pady=8)
top.grid_columnconfigure(3, weight=1) top.grid_columnconfigure(4, weight=1)
label_font = theme_font(self._theme, "toolbar_label_font", ("Segoe UI", 10)) label_font = theme_font(self._theme, "toolbar_label_font", ("Segoe UI", 10))
entry_font = theme_font(self._theme, "entry_font", ("Segoe UI", 10)) entry_font = theme_font(self._theme, "entry_font", ("Segoe UI", 10))
@@ -200,6 +204,18 @@ class StoricoPickingListWindow(ctk.CTkToplevel):
).grid( ).grid(
row=0, column=2, sticky="w" row=0, column=2, sticky="w"
) )
self.btn_ship_residuals = ctk.CTkButton(
top,
text=loc_text(
"history.picking.button.ship_residuals",
catalog=self._locale_catalog,
default="Versa residui in 7G.1.1",
),
command=self._ship_selected_residuals,
state="disabled",
font=button_font,
)
self.btn_ship_residuals.grid(row=0, column=3, sticky="w", padx=(12, 0))
self.master_tree = self._make_tree( self.master_tree = self._make_tree(
row=1, row=1,
@@ -266,12 +282,15 @@ class StoricoPickingListWindow(ctk.CTkToplevel):
def _load_master(self) -> None: def _load_master(self) -> None:
params = {"documento": str(self.var_documento.get() or "").strip() or None} params = {"documento": str(self.var_documento.get() or "").strip() or None}
previous_documento = self._selected_documento
async def _job(): async def _job():
return await self.db_client.query_json(SQL_STORICO_PL, params) return await self.db_client.query_json(SQL_STORICO_PL, params)
def _ok(res): def _ok(res):
self._fill_master(_rows_to_dicts(res)) self._fill_master(_rows_to_dicts(res))
if previous_documento:
self._restore_master_selection(previous_documento)
def _err(ex): def _err(ex):
messagebox.showerror( messagebox.showerror(
@@ -299,6 +318,10 @@ class StoricoPickingListWindow(ctk.CTkToplevel):
def _fill_master(self, rows: list[dict[str, Any]]) -> None: def _fill_master(self, rows: list[dict[str, Any]]) -> None:
self.master_tree.delete(*self.master_tree.get_children("")) self.master_tree.delete(*self.master_tree.get_children(""))
self.detail_tree.delete(*self.detail_tree.get_children("")) self.detail_tree.delete(*self.detail_tree.get_children(""))
self._selected_documento = None
self._selected_stato_operativo = ""
self._detail_rows = []
self._update_residual_button()
for index, row in enumerate(rows): for index, row in enumerate(rows):
stato = str(row.get("StatoOperativo") or "") stato = str(row.get("StatoOperativo") or "")
is_open_with_shipped = ( is_open_with_shipped = (
@@ -335,17 +358,26 @@ class StoricoPickingListWindow(ctk.CTkToplevel):
def _on_master_select(self, _event=None) -> None: def _on_master_select(self, _event=None) -> None:
selected = self.master_tree.selection() selected = self.master_tree.selection()
if not selected: if not selected:
self._selected_documento = None
self._selected_stato_operativo = ""
self._detail_rows = []
self._update_residual_button()
return return
values = self.master_tree.item(selected[0], "values") values = self.master_tree.item(selected[0], "values")
if not values: if not values:
return return
documento = values[0] documento = values[0]
self._selected_documento = str(documento)
self._selected_stato_operativo = str(values[7] if len(values) > 7 else "")
self._detail_rows = []
self._update_residual_button()
async def _job(): async def _job():
return await self.db_client.query_json(SQL_STORICO_PL_DETAILS, {"documento": documento}) return await self.db_client.query_json(SQL_STORICO_PL_DETAILS, {"documento": documento})
def _ok(res): def _ok(res):
self._fill_detail(_rows_to_dicts(res)) self._fill_detail(_rows_to_dicts(res))
self._update_residual_button()
def _err(ex): def _err(ex):
messagebox.showerror( messagebox.showerror(
@@ -372,6 +404,7 @@ class StoricoPickingListWindow(ctk.CTkToplevel):
def _fill_detail(self, rows: list[dict[str, Any]]) -> None: def _fill_detail(self, rows: list[dict[str, Any]]) -> None:
self.detail_tree.delete(*self.detail_tree.get_children("")) self.detail_tree.delete(*self.detail_tree.get_children(""))
self._detail_rows = rows
for index, row in enumerate(rows): for index, row in enumerate(rows):
is_open_shipped = str(row.get("StatoDocumento") or "") == "P" and int(row.get("Cella") or 0) == 9999 is_open_shipped = str(row.get("StatoDocumento") or "") == "P" and int(row.get("Cella") or 0) == 9999
done = str(row.get("StatoDocumento") or "") == "D" or int(row.get("Cella") or 0) == 9999 done = str(row.get("StatoDocumento") or "") == "D" or int(row.get("Cella") or 0) == 9999
@@ -394,6 +427,117 @@ class StoricoPickingListWindow(ctk.CTkToplevel):
tags=merge_tags(zebra_tag(index), tag), tags=merge_tags(zebra_tag(index), tag),
) )
def _update_residual_button(self) -> None:
"""Enable the bulk shipment button only for closed picking lists with residual UDCs."""
enabled = self._selected_stato_operativo == "Chiusa ERP con residui"
try:
self.btn_ship_residuals.configure(state="normal" if enabled else "disabled")
except Exception:
pass
def _restore_master_selection(self, documento: str) -> None:
"""Re-select a document after a reload, when it is still visible."""
for iid in self.master_tree.get_children(""):
values = self.master_tree.item(iid, "values")
if values and str(values[0]) == str(documento):
self.master_tree.selection_set(iid)
self.master_tree.focus(iid)
self.master_tree.see(iid)
self._on_master_select()
return
def _residual_pallets_from_rows(self, rows: list[dict[str, Any]]) -> list[str]:
"""Return distinct residual UDCs that are not already in 7G.1.1."""
pallets: list[str] = []
seen: set[str] = set()
for row in rows:
pallet = str(row.get("Pallet") or "").strip()
if not pallet or pallet in seen:
continue
try:
cella = int(row.get("Cella") or 0)
except Exception:
cella = 0
if cella == 9999:
continue
seen.add(pallet)
pallets.append(pallet)
return pallets
def _operator_login(self) -> str:
"""Return the user recorded on generated warehouse movements."""
login = str(getattr(self.session, "login", "") or "").strip()
return login or "warehouse_ui"
def _ship_selected_residuals(self) -> None:
"""Move all residual UDCs of the selected closed PL to the shipment cell 7G.1.1."""
if self._selected_stato_operativo != "Chiusa ERP con residui" or not self._selected_documento:
return
estimated = self._residual_pallets_from_rows(self._detail_rows)
count_text = str(len(estimated)) if estimated else "le"
if not messagebox.askyesno(
loc_text("history.picking.msg.title", catalog=self._locale_catalog, default="Storico Picking List"),
(
f"Documento {self._selected_documento}\n\n"
f"Verranno versate in 7G.1.1 {count_text} UDC residue della picking list chiusa.\n"
"L'operazione registra i movimenti nello storico UDC.\n\n"
"Procedere?"
),
parent=self,
):
return
documento = self._selected_documento
utente = self._operator_login()
async def _job():
detail_res = await self.db_client.query_json(SQL_STORICO_PL_DETAILS, {"documento": documento})
detail_rows = _rows_to_dicts(detail_res)
pallets = self._residual_pallets_from_rows(detail_rows)
results: list[dict[str, Any]] = []
for pallet in pallets:
result = await move_pallet_async(
self.db_client,
barcode_pallet=pallet,
target_idcella=9999,
target_barcode_cella="9000000",
utente=utente,
)
results.append(result)
return {"pallets": pallets, "results": results}
def _ok(res):
pallets = res.get("pallets", []) if isinstance(res, dict) else []
results = res.get("results", []) if isinstance(res, dict) else []
moved = sum(1 for row in results if int(row.get("ok") or 0) == 1)
messagebox.showinfo(
loc_text("history.picking.msg.title", catalog=self._locale_catalog, default="Storico Picking List"),
f"Documento {documento}\nUDC residue trovate: {len(pallets)}\nUDC versate in 7G.1.1: {moved}",
parent=self,
)
self._selected_documento = str(documento)
self._load_master()
def _err(ex):
messagebox.showerror(
loc_text("history.picking.msg.title", catalog=self._locale_catalog, default="Storico Picking List"),
f"Versamento residui fallito:\n{ex}",
parent=self,
)
self._async.run(
_job(),
_ok,
_err,
busy=self._busy,
message=f"Verso residui PL {documento} in 7G.1.1...",
)
def open_storico_pickinglist_window(parent: tk.Misc, db_client, session=None) -> tk.Misc: def open_storico_pickinglist_window(parent: tk.Misc, db_client, session=None) -> tk.Misc:
"""Open the picking-list history window.""" """Open the picking-list history window."""

View File

@@ -27,7 +27,7 @@ MODULE_VERSIONS: dict[str, str] = {
"prenota_sprenota_sql": "1.0.0", "prenota_sprenota_sql": "1.0.0",
"reset_corsie": "1.0.0", "reset_corsie": "1.0.0",
"search_pallets": "1.0.0", "search_pallets": "1.0.0",
"storico_pickinglist": "1.0.2", "storico_pickinglist": "1.0.3",
"storico_udc": "1.0.0", "storico_udc": "1.0.0",
"tooltips": "1.0.0", "tooltips": "1.0.0",
"ui_theme": "1.0.0", "ui_theme": "1.0.0",