Aggiungi versamento residui 7G da storico pickinglist
This commit is contained in:
@@ -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."""
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user