commit 8806d598eb3d1d2e560c578bf82e5edf1f16602a Author: allebonvi Date: Mon Oct 27 17:18:09 2025 +0100 chore: initial commit diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..2a6eac1 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..d6a57ee --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..6c0b863 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/warehouse.iml b/.idea/warehouse.iml new file mode 100644 index 0000000..460d402 --- /dev/null +++ b/.idea/warehouse.iml @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/__pycache__/async_loop_singleton.cpython-313.pyc b/__pycache__/async_loop_singleton.cpython-313.pyc new file mode 100644 index 0000000..c366516 Binary files /dev/null and b/__pycache__/async_loop_singleton.cpython-313.pyc differ diff --git a/__pycache__/async_msssql_query.cpython-313.pyc b/__pycache__/async_msssql_query.cpython-313.pyc new file mode 100644 index 0000000..1c97cb0 Binary files /dev/null and b/__pycache__/async_msssql_query.cpython-313.pyc differ diff --git a/__pycache__/async_runner.cpython-313.pyc b/__pycache__/async_runner.cpython-313.pyc new file mode 100644 index 0000000..55c3ac3 Binary files /dev/null and b/__pycache__/async_runner.cpython-313.pyc differ diff --git a/__pycache__/db_async_singleton.cpython-313.pyc b/__pycache__/db_async_singleton.cpython-313.pyc new file mode 100644 index 0000000..47583db Binary files /dev/null and b/__pycache__/db_async_singleton.cpython-313.pyc differ diff --git a/__pycache__/gestione_aree_frame_async.cpython-313.pyc b/__pycache__/gestione_aree_frame_async.cpython-313.pyc new file mode 100644 index 0000000..f7f088f Binary files /dev/null and b/__pycache__/gestione_aree_frame_async.cpython-313.pyc differ diff --git a/__pycache__/gestione_pickinglist.cpython-313.pyc b/__pycache__/gestione_pickinglist.cpython-313.pyc new file mode 100644 index 0000000..ef992ed Binary files /dev/null and b/__pycache__/gestione_pickinglist.cpython-313.pyc differ diff --git a/__pycache__/layout_window.cpython-313.pyc b/__pycache__/layout_window.cpython-313.pyc new file mode 100644 index 0000000..2a5659d Binary files /dev/null and b/__pycache__/layout_window.cpython-313.pyc differ diff --git a/__pycache__/prenota_sprenota_sql.cpython-313.pyc b/__pycache__/prenota_sprenota_sql.cpython-313.pyc new file mode 100644 index 0000000..95518bc Binary files /dev/null and b/__pycache__/prenota_sprenota_sql.cpython-313.pyc differ diff --git a/__pycache__/reset_corsie.cpython-313.pyc b/__pycache__/reset_corsie.cpython-313.pyc new file mode 100644 index 0000000..0be68af Binary files /dev/null and b/__pycache__/reset_corsie.cpython-313.pyc differ diff --git a/__pycache__/search_pallets.cpython-313.pyc b/__pycache__/search_pallets.cpython-313.pyc new file mode 100644 index 0000000..775cdc1 Binary files /dev/null and b/__pycache__/search_pallets.cpython-313.pyc differ diff --git a/__pycache__/view_celle_multiple.cpython-313.pyc b/__pycache__/view_celle_multiple.cpython-313.pyc new file mode 100644 index 0000000..446d29f Binary files /dev/null and b/__pycache__/view_celle_multiple.cpython-313.pyc differ diff --git a/assets/fonts/Font Awesome 7 Brands-Regular-400.otf b/assets/fonts/Font Awesome 7 Brands-Regular-400.otf new file mode 100644 index 0000000..672a35b Binary files /dev/null and b/assets/fonts/Font Awesome 7 Brands-Regular-400.otf differ diff --git a/assets/fonts/Font Awesome 7 Free-Regular-400.otf b/assets/fonts/Font Awesome 7 Free-Regular-400.otf new file mode 100644 index 0000000..d96d2ea Binary files /dev/null and b/assets/fonts/Font Awesome 7 Free-Regular-400.otf differ diff --git a/assets/fonts/Font Awesome 7 Free-Solid-900.otf b/assets/fonts/Font Awesome 7 Free-Solid-900.otf new file mode 100644 index 0000000..90d72a6 Binary files /dev/null and b/assets/fonts/Font Awesome 7 Free-Solid-900.otf differ diff --git a/assets/icons/home.png b/assets/icons/home.png new file mode 100644 index 0000000..f10cc0d Binary files /dev/null and b/assets/icons/home.png differ diff --git a/async_loop_singleton.py b/async_loop_singleton.py new file mode 100644 index 0000000..ad765e3 --- /dev/null +++ b/async_loop_singleton.py @@ -0,0 +1,35 @@ +# async_loop_singleton.py +import asyncio, threading +from typing import Callable + +class _LoopHolder: + def __init__(self): + self.loop = None + self.thread = None + +_GLOBAL = _LoopHolder() + +def get_global_loop() -> asyncio.AbstractEventLoop: + """Start a single asyncio loop in a background thread and return it.""" + if _GLOBAL.loop: + return _GLOBAL.loop + + ready = threading.Event() + + def _run(): + _GLOBAL.loop = asyncio.new_event_loop() + asyncio.set_event_loop(_GLOBAL.loop) + ready.set() + _GLOBAL.loop.run_forever() + + _GLOBAL.thread = threading.Thread(target=_run, name="asyncio-bg-loop", daemon=True) + _GLOBAL.thread.start() + ready.wait() + return _GLOBAL.loop + +def stop_global_loop(): + if _GLOBAL.loop and _GLOBAL.loop.is_running(): + _GLOBAL.loop.call_soon_threadsafe(_GLOBAL.loop.stop) + _GLOBAL.thread.join(timeout=2) + _GLOBAL.loop = None + _GLOBAL.thread = None diff --git a/async_msssql_query.py b/async_msssql_query.py new file mode 100644 index 0000000..be00967 --- /dev/null +++ b/async_msssql_query.py @@ -0,0 +1,93 @@ +# async_msssql_query.py — loop-safe, compat rows=list, no pooling +from __future__ import annotations + +import asyncio, urllib.parse, time, logging +from typing import Any, Dict, Optional +from sqlalchemy.ext.asyncio import create_async_engine +from sqlalchemy.pool import NullPool +from sqlalchemy import text + +try: + import orjson as _json + def _dumps(obj: Any) -> str: return _json.dumps(obj, default=str).decode("utf-8") +except Exception: + import json as _json + def _dumps(obj: Any) -> str: return _json.dumps(obj, default=str) + +def make_mssql_dsn( + *, server: str, database: str, user: Optional[str]=None, password: Optional[str]=None, + driver: str="ODBC Driver 17 for SQL Server", trust_server_certificate: bool=True, + encrypt: Optional[str]=None, extra_odbc_kv: Optional[Dict[str,str]]=None +) -> str: + kv = {"DRIVER": driver, "SERVER": server, "DATABASE": database, + "TrustServerCertificate": "Yes" if trust_server_certificate else "No"} + if user: kv["UID"] = user + if password: kv["PWD"] = password + if encrypt: kv["Encrypt"] = encrypt + if extra_odbc_kv: kv.update(extra_odbc_kv) + odbc = ";".join(f"{k}={v}" for k,v in kv.items()) + ";" + return f"mssql+aioodbc:///?odbc_connect={urllib.parse.quote_plus(odbc)}" + +class AsyncMSSQLClient: + """ + Engine creato pigramente sul loop corrente, senza pool (NullPool). + Evita “Future attached to a different loop” nei reset/close del pool. + """ + def __init__(self, dsn: str, *, echo: bool=False, log: bool=True): + self._dsn = dsn + self._echo = echo + self._engine = None + self._engine_loop: Optional[asyncio.AbstractEventLoop] = None + self._logger = logging.getLogger("AsyncMSSQLClient") + if log and not self._logger.handlers: + h = logging.StreamHandler() + h.setFormatter(logging.Formatter("[%(levelname)s] %(message)s")) + self._logger.addHandler(h) + self._enable_log = log + + async def _ensure_engine(self): + if self._engine is not None: + return + loop = asyncio.get_running_loop() + self._engine = create_async_engine( + self._dsn, + echo=self._echo, + # IMPORTANTI: + poolclass=NullPool, # no pooling → no reset su loop “sbagliati” + connect_args={"loop": loop}, # usa il loop corrente in aioodbc + ) + self._engine_loop = loop + if self._enable_log: + self._logger.info("Engine created on loop %s", id(loop)) + + async def dispose(self): + if self._engine is None: + return + # sempre sullo stesso loop in cui è nato + if asyncio.get_running_loop() is self._engine_loop: + await self._engine.dispose() + else: + fut = asyncio.run_coroutine_threadsafe(self._engine.dispose(), self._engine_loop) + fut.result(timeout=2) + self._engine = None + if self._enable_log: + self._logger.info("Engine disposed") + + async def query_json(self, sql: str, params: Optional[Dict[str, Any]]=None, *, as_dict_rows: bool=False) -> Dict[str, Any]: + await self._ensure_engine() + t0 = time.perf_counter() + async with self._engine.connect() as conn: + res = await conn.execute(text(sql), params or {}) + rows = res.fetchall() + cols = list(res.keys()) + if as_dict_rows: + rows_out = [dict(zip(cols, r)) for r in rows] + else: + rows_out = [list(r) for r in rows] + return {"columns": cols, "rows": rows_out, "elapsed_ms": round((time.perf_counter()-t0)*1000, 3)} + + async def exec(self, sql: str, params: Optional[Dict[str, Any]]=None, *, commit: bool=False) -> int: + await self._ensure_engine() + async with (self._engine.begin() if commit else self._engine.connect()) as conn: + res = await conn.execute(text(sql), params or {}) + return res.rowcount or 0 diff --git a/async_runner.py b/async_runner.py new file mode 100644 index 0000000..983a183 --- /dev/null +++ b/async_runner.py @@ -0,0 +1,25 @@ +# async_runner.py +import asyncio +from typing import Callable + +class AsyncRunner: + """Esegue un awaitable sul loop globale e richiama i callback in Tk via .after.""" + def __init__(self, tk_root, loop: asyncio.AbstractEventLoop): + self.tk = tk_root + self.loop = loop + + def run(self, awaitable, on_ok: Callable, on_err: Callable, busy=None, message: str | None=None): + if busy: busy.show(message or "Lavoro in corso…") + fut = asyncio.run_coroutine_threadsafe(awaitable, self.loop) + self._poll(fut, on_ok, on_err, busy) + + def _poll(self, fut, on_ok, on_err, busy): + if fut.done(): + if busy: busy.hide() + try: + res = fut.result() + on_ok(res) + except Exception as ex: + on_err(ex) + return + self.tk.after(50, lambda: self._poll(fut, on_ok, on_err, busy)) diff --git a/backups/autosave.tcl.bak1 b/backups/autosave.tcl.bak1 new file mode 100644 index 0000000..1dc9340 --- /dev/null +++ b/backups/autosave.tcl.bak1 @@ -0,0 +1,148 @@ +############################################################################# +# Generated by PAGE version 8.0 +# in conjunction with Tcl version 8.6 +# Sep 15, 2025 05:35:31 PM CEST platform: Windows NT +set vTcl(timestamp) "" +if {![info exists vTcl(borrow)]} { + ::vTcl::MessageBox -title Error -message "You must open project files from within PAGE." + exit} + + +set vTcl(actual_gui_font_dft_desc) TkDefaultFont +set vTcl(actual_gui_font_dft_name) TkDefaultFont +set vTcl(actual_gui_font_text_desc) TkTextFont +set vTcl(actual_gui_font_text_name) TkTextFont +set vTcl(actual_gui_font_fixed_desc) TkFixedFont +set vTcl(actual_gui_font_fixed_name) TkFixedFont +set vTcl(actual_gui_font_menu_desc) TkMenuFont +set vTcl(actual_gui_font_menu_name) TkMenuFont +set vTcl(actual_gui_font_tooltip_desc) TkDefaultFont +set vTcl(actual_gui_font_tooltip_name) TkDefaultFont +set vTcl(actual_gui_font_treeview_desc) TkDefaultFont +set vTcl(actual_gui_font_treeview_name) TkDefaultFont +########################################### +set vTcl(actual_gui_bg) #d9d9d9 +set vTcl(actual_gui_fg) #000000 +set vTcl(actual_gui_analog) #ececec +set vTcl(actual_gui_menu_analog) #ececec +set vTcl(actual_gui_menu_bg) #d9d9d9 +set vTcl(actual_gui_menu_fg) #000000 +set vTcl(complement_color) gray40 +set vTcl(analog_color_p) #c3c3c3 +set vTcl(analog_color_m) beige +set vTcl(tabfg1) black +set vTcl(tabfg2) white +set vTcl(actual_gui_menu_active_bg) #ececec +set vTcl(actual_gui_menu_active_fg) #000000 +########################################### +set vTcl(pr,autoalias) 1 +set vTcl(pr,relative_placement) 1 +set vTcl(mode) Relative +set vTcl(project_theme) default + + + +proc vTclWindow.top1 {base} { + global vTcl + if {$base == ""} { + set base .top1 + } + if {[winfo exists $base]} { + wm deiconify $base; return + } + set top $base + set target $base + ################### + # CREATING WIDGETS + ################### + vTcl::widgets::core::toplevel::createCmd $top -class Toplevel \ + -background #d9d9d9 -highlightbackground #d9d9d9 \ + -highlightcolor #000000 + wm focusmodel $top passive + wm geometry $top 600x450+374+144 + update + # set in toplevel.wgt. + global vTcl + global img_list + set vTcl(save,dflt,origin) 0 + wm maxsize $top 3364 881 + wm minsize $top 120 1 + wm overrideredirect $top 0 + wm resizable $top 1 1 + wm deiconify $top + set toptitle "Toplevel 0" + wm title $top $toptitle + namespace eval ::widgets::${top}::ClassOption {} + set ::widgets::${top}::ClassOption(-toptitle) $toptitle + vTcl:DefineAlias "$top" "Toplevel1" vTcl:Toplevel:WidgetProc "" 1 + set vTcl(real_top) {} + button "$top.but47" \ + -activebackground #d9d9d9 -activeforeground black -background #d9d9d9 \ + -disabledforeground #a3a3a3 -font "-family {Segoe UI} -size 9" \ + -foreground #000000 -highlightbackground #d9d9d9 \ + -highlightcolor #000000 -text "Button" + vTcl:DefineAlias "$top.but47" "Button1" vTcl:WidgetProc "Toplevel1" 1 + frame "$top.fra48" \ + -borderwidth 2 -relief groove -background #d9d9d9 -height 355 \ + -highlightbackground #d9d9d9 -highlightcolor #000000 -width 565 + vTcl:DefineAlias "$top.fra48" "Frame1" vTcl:WidgetProc "Toplevel1" 1 + set site_3_0 $top.fra48 + vTcl::widgets::ttk::pnotebook::createCmd "$site_3_0.pNo49" \ + -width 512 -height 304 -style "PC.TNotebook" -style PC.TNotebook + vTcl:DefineAlias "$site_3_0.pNo49" "PNotebook1" vTcl:WidgetProc "Toplevel1" 1 + $site_3_0.pNo49 configure -style "PC.TNotebook" + bind $site_3_0.pNo49 { + _button_press + } + bind $site_3_0.pNo49 { + _button_release + } + bind $site_3_0.pNo49 { + _mouse_over + } + ttk::frame "$site_3_0.pNo49.t0" + vTcl:DefineAlias "$site_3_0.pNo49.t0" "PNotebook1_t1" vTcl:WidgetProc "Toplevel1" 1 + $site_3_0.pNo49 add $site_3_0.pNo49.t0 \ + -padding 0 -sticky nsew -state normal -text "Page 1" -image image6 \ + -compound right -underline -1 + set site_5_0 $site_3_0.pNo49.t0 + ttk::frame "$site_3_0.pNo49.t1" + vTcl:DefineAlias "$site_3_0.pNo49.t1" "PNotebook1_t2" vTcl:WidgetProc "Toplevel1" 1 + $site_3_0.pNo49 add $site_3_0.pNo49.t1 \ + -padding 0 -sticky nsew -state normal -text "Page 2" -image image6 \ + -compound right -underline -1 + set site_5_1 $site_3_0.pNo49.t1 + place $site_3_0.pNo49 \ + -in $site_3_0 -x 0 -relx 0.035 -y 0 -rely 0.056 -width 0 \ + -relwidth 0.906 -height 0 -relheight 0.856 -anchor nw \ + -bordermode ignore + ################### + # SETTING GEOMETRY + ################### + place $top.but47 \ + -in $top -x 0 -relx 0.033 -y 0 -rely 0.022 -width 97 -relwidth 0 \ + -height 46 -relheight 0 -anchor nw -bordermode ignore + place $top.fra48 \ + -in $top -x 0 -relx 0.033 -y 0 -rely 0.178 -width 0 -relwidth 0.942 \ + -height 0 -relheight 0.789 -anchor nw -bordermode ignore + + vTcl:FireEvent $base <> +} + +proc 36 {args} {return 1} + + +Window show . +set btop1 "" +if {$vTcl(borrow)} { + set btop1 .bor[expr int([expr rand() * 100])] + while {[lsearch $btop1 $vTcl(tops)] != -1} { + set btop1 .bor[expr int([expr rand() * 100])] + } +} +set vTcl(btop) $btop1 +Window show .top1 $btop1 +if {$vTcl(borrow)} { + $btop1 configure -background plum +} + diff --git a/backups/autosave.tcl.bak2 b/backups/autosave.tcl.bak2 new file mode 100644 index 0000000..13f28e9 --- /dev/null +++ b/backups/autosave.tcl.bak2 @@ -0,0 +1,148 @@ +############################################################################# +# Generated by PAGE version 8.0 +# in conjunction with Tcl version 8.6 +# Sep 15, 2025 05:34:52 PM CEST platform: Windows NT +set vTcl(timestamp) "" +if {![info exists vTcl(borrow)]} { + ::vTcl::MessageBox -title Error -message "You must open project files from within PAGE." + exit} + + +set vTcl(actual_gui_font_dft_desc) TkDefaultFont +set vTcl(actual_gui_font_dft_name) TkDefaultFont +set vTcl(actual_gui_font_text_desc) TkTextFont +set vTcl(actual_gui_font_text_name) TkTextFont +set vTcl(actual_gui_font_fixed_desc) TkFixedFont +set vTcl(actual_gui_font_fixed_name) TkFixedFont +set vTcl(actual_gui_font_menu_desc) TkMenuFont +set vTcl(actual_gui_font_menu_name) TkMenuFont +set vTcl(actual_gui_font_tooltip_desc) TkDefaultFont +set vTcl(actual_gui_font_tooltip_name) TkDefaultFont +set vTcl(actual_gui_font_treeview_desc) TkDefaultFont +set vTcl(actual_gui_font_treeview_name) TkDefaultFont +########################################### +set vTcl(actual_gui_bg) #d9d9d9 +set vTcl(actual_gui_fg) #000000 +set vTcl(actual_gui_analog) #ececec +set vTcl(actual_gui_menu_analog) #ececec +set vTcl(actual_gui_menu_bg) #d9d9d9 +set vTcl(actual_gui_menu_fg) #000000 +set vTcl(complement_color) gray40 +set vTcl(analog_color_p) #c3c3c3 +set vTcl(analog_color_m) beige +set vTcl(tabfg1) black +set vTcl(tabfg2) white +set vTcl(actual_gui_menu_active_bg) #ececec +set vTcl(actual_gui_menu_active_fg) #000000 +########################################### +set vTcl(pr,autoalias) 1 +set vTcl(pr,relative_placement) 1 +set vTcl(mode) Relative +set vTcl(project_theme) default + + + +proc vTclWindow.top1 {base} { + global vTcl + if {$base == ""} { + set base .top1 + } + if {[winfo exists $base]} { + wm deiconify $base; return + } + set top $base + set target $base + ################### + # CREATING WIDGETS + ################### + vTcl::widgets::core::toplevel::createCmd $top -class Toplevel \ + -background #d9d9d9 -highlightbackground #d9d9d9 \ + -highlightcolor #000000 + wm focusmodel $top passive + wm geometry $top 600x450+374+144 + update + # set in toplevel.wgt. + global vTcl + global img_list + set vTcl(save,dflt,origin) 0 + wm maxsize $top 3364 881 + wm minsize $top 120 1 + wm overrideredirect $top 0 + wm resizable $top 1 1 + wm deiconify $top + set toptitle "Toplevel 0" + wm title $top $toptitle + namespace eval ::widgets::${top}::ClassOption {} + set ::widgets::${top}::ClassOption(-toptitle) $toptitle + vTcl:DefineAlias "$top" "Toplevel1" vTcl:Toplevel:WidgetProc "" 1 + set vTcl(real_top) {} + button "$top.but47" \ + -activebackground #d9d9d9 -activeforeground black -background #d9d9d9 \ + -disabledforeground #a3a3a3 -font "-family {Segoe UI} -size 9" \ + -foreground #000000 -highlightbackground #d9d9d9 \ + -highlightcolor #000000 -text "Button" + vTcl:DefineAlias "$top.but47" "Button1" vTcl:WidgetProc "Toplevel1" 1 + frame "$top.fra48" \ + -borderwidth 2 -relief groove -background #d9d9d9 -height 355 \ + -highlightbackground #d9d9d9 -highlightcolor #000000 -width 565 + vTcl:DefineAlias "$top.fra48" "Frame1" vTcl:WidgetProc "Toplevel1" 1 + set site_3_0 $top.fra48 + vTcl::widgets::ttk::pnotebook::createCmd "$site_3_0.pNo49" \ + -width 300 -height 200 -style "PC.TNotebook" -style PC.TNotebook + vTcl:DefineAlias "$site_3_0.pNo49" "PNotebook1" vTcl:WidgetProc "Toplevel1" 1 + $site_3_0.pNo49 configure -style "PC.TNotebook" + bind $site_3_0.pNo49 { + _button_press + } + bind $site_3_0.pNo49 { + _button_release + } + bind $site_3_0.pNo49 { + _mouse_over + } + ttk::frame "$site_3_0.pNo49.t0" + vTcl:DefineAlias "$site_3_0.pNo49.t0" "PNotebook1_t1" vTcl:WidgetProc "Toplevel1" 1 + $site_3_0.pNo49 add $site_3_0.pNo49.t0 \ + -padding 0 -sticky nsew -state normal -text "Page 1" -image image6 \ + -compound right -underline -1 + set site_5_0 $site_3_0.pNo49.t0 + ttk::frame "$site_3_0.pNo49.t1" + vTcl:DefineAlias "$site_3_0.pNo49.t1" "PNotebook1_t2" vTcl:WidgetProc "Toplevel1" 1 + $site_3_0.pNo49 add $site_3_0.pNo49.t1 \ + -padding 0 -sticky nsew -state normal -text "Page 2" -image image6 \ + -compound right -underline -1 + set site_5_1 $site_3_0.pNo49.t1 + place $site_3_0.pNo49 \ + -in $site_3_0 -x 0 -relx 0.035 -y 0 -rely 0.056 -width 0 \ + -relwidth 0.535 -height 0 -relheight 0.631 -anchor nw \ + -bordermode ignore + ################### + # SETTING GEOMETRY + ################### + place $top.but47 \ + -in $top -x 0 -relx 0.033 -y 0 -rely 0.022 -width 97 -relwidth 0 \ + -height 46 -relheight 0 -anchor nw -bordermode ignore + place $top.fra48 \ + -in $top -x 0 -relx 0.033 -y 0 -rely 0.178 -width 0 -relwidth 0.942 \ + -height 0 -relheight 0.789 -anchor nw -bordermode ignore + + vTcl:FireEvent $base <> +} + +proc 36 {args} {return 1} + + +Window show . +set btop1 "" +if {$vTcl(borrow)} { + set btop1 .bor[expr int([expr rand() * 100])] + while {[lsearch $btop1 $vTcl(tops)] != -1} { + set btop1 .bor[expr int([expr rand() * 100])] + } +} +set vTcl(btop) $btop1 +Window show .top1 $btop1 +if {$vTcl(borrow)} { + $btop1 configure -background plum +} + diff --git a/baseline_Mediseawall.json b/baseline_Mediseawall.json new file mode 100644 index 0000000..40ffe9e --- /dev/null +++ b/baseline_Mediseawall.json @@ -0,0 +1,4 @@ +{ + "db": "Mediseawall", + "baseline": {} +} \ No newline at end of file diff --git a/db_async_singleton.py b/db_async_singleton.py new file mode 100644 index 0000000..535df6b --- /dev/null +++ b/db_async_singleton.py @@ -0,0 +1,33 @@ +# db_async_singleton.py +import asyncio +from sqlalchemy.ext.asyncio import create_async_engine +from sqlalchemy import text + +class AsyncDB: + def __init__(self, engine): + self.engine = engine + + async def query_json(self, sql: str, params: dict): + async with self.engine.connect() as conn: + result = await conn.execute(text(sql), params) + rows = [tuple(r) for r in result] + return {"rows": rows} + +_ENGINE = None + +async def _make_engine_async(conn_str: str): + return create_async_engine(conn_str, pool_pre_ping=True, future=True) + +def get_db(loop: asyncio.AbstractEventLoop, conn_str: str) -> AsyncDB: + """Crea l'engine UNA volta, dentro il loop globale, e restituisce il client.""" + global _ENGINE + if _ENGINE is None: + fut = asyncio.run_coroutine_threadsafe(_make_engine_async(conn_str), loop) + _ENGINE = fut.result() + return AsyncDB(_ENGINE) + +async def dispose_async(): + global _ENGINE + if _ENGINE is not None: + await _ENGINE.dispose() + _ENGINE = None diff --git a/fix_layout_window.py b/fix_layout_window.py new file mode 100644 index 0000000..b0589b9 --- /dev/null +++ b/fix_layout_window.py @@ -0,0 +1,40 @@ +import re +from pathlib import Path + +# Path default (modifica se serve) +p = Path("./layout_window.py") +if not p.exists(): + raise SystemExit(f"File non trovato: {p}") + +src = p.read_text(encoding="utf-8") + +# 1) Rimuovi i parametri border_color="transparent" nelle chiamate configure(...). +# Gestiamo i casi ", border_color='transparent'" e "border_color='transparent'," +patterns = [ + re.compile(r""",\s*border_color\s*=\s*["']transparent["']"""), # , border_color="transparent" + re.compile(r"""border_color\s*=\s*["']transparent["']\s*,\s*""") # border_color="transparent", +] +for pat in patterns: + src = pat.sub("", src) + +# 2) Se sono rimaste virgole prima della parentesi di chiusura: ", )" -> ")" +src = re.sub(r",\s*\)", ")", src) + +# 3) (opzionale/robusto) Rimuovi border_color=None se presente in qualche versione +patterns_none = [ + re.compile(r""",\s*border_color\s*=\s*None"""), + re.compile(r"""border_color\s*=\s*None\s*,\s*""") +] +for pat in patterns_none: + src = pat.sub("", src) +src = re.sub(r",\s*\)", ")", src) + +# 4) NOTE: manteniamo eventuali border_color="blue" per l’highlight + +# Scrivi backup e nuovo file +bak = p.with_suffix(".py.bak_fix_bc_transparent") +if not bak.exists(): + bak.write_text(Path(p).read_text(encoding="utf-8"), encoding="utf-8") + +p.write_text(src, encoding="utf-8") +print(f"Patch applicata a {p}. Backup: {bak}") diff --git a/fix_query.py b/fix_query.py new file mode 100644 index 0000000..165272e --- /dev/null +++ b/fix_query.py @@ -0,0 +1,87 @@ +from pathlib import Path +import re + +p = Path("./layout_window.py") +src = p.read_text(encoding="utf-8") + +backup = p.with_suffix(".py.bak_perf") +if not backup.exists(): + backup.write_text(src, encoding="utf-8") + +# 1) Rimuovi il bind su che innescava refresh continui. +src = src.replace( + ' self.bind("", lambda e: self.after_idle(self._refresh_stats))\n', + ' # disabilitato: il refresh ad ogni generava molte query/lag\n' + ' # self.bind("", lambda e: self.after_idle(self._refresh_stats))\n' +) + +# 2) Nel metodo _refresh_stats, elimina il blocco che interroga il DB "globale". +# Riconosciamo il blocco su "sql_tot = ..." e lo neutralizziamo. +src = re.sub( + r"\n\s*# globale dal DB[\s\S]*?self\._async\.run\(.*?\)\)\n", + "\n # [patch] rimosso refresh globale da DB: calcoliamo solo dalla matrice in memoria\n", + src, + flags=re.MULTILINE +) + +# 3) Aggiungi un flag di vita finestra e un destroy sicuro +# - settiamo self._alive = True in __init__ +# - override destroy() per annullare timer e marcare _alive=False +src = src.replace( + " self._last_req = 0\n", + " self._last_req = 0\n" + " self._alive = True\n" + " self._stats_after_id = None # se mai userai un refresh periodico, potremo cancellarlo qui\n" +) + +# aggiungi metodo destroy() se non esiste già +if "def destroy(self):" not in src: + insert_point = src.find("def open_layout_window(") + destroy_method = ( + "\n def destroy(self):\n" + " # evita nuovi refresh/async dopo destroy\n" + " self._alive = False\n" + " # cancella eventuali timer\n" + " try:\n" + " if self._stats_after_id is not None:\n" + " self.after_cancel(self._stats_after_id)\n" + " except Exception:\n" + " pass\n" + " # pulizia UI leggera\n" + " try:\n" + " for w in list(self.host.winfo_children()):\n" + " w.destroy()\n" + " except Exception:\n" + " pass\n" + " try:\n" + " super().destroy()\n" + " except Exception:\n" + " pass\n\n" + ) + src = src[:insert_point] + destroy_method + src[insert_point:] + +# 4) Nei callback _ok/_err delle query, assicurati che non facciano nulla se la finestra è chiusa +# => sostituiamo 'def _ok(res):' con un guard iniziale e idem per _err. +src = re.sub( + r"def _ok\(res\):\n", + "def _ok(res):\n" + " if not getattr(self, '_alive', True) or not self.winfo_exists():\n" + " return\n", + src +) +src = re.sub( + r"def _err\(ex\):\n", + "def _err(ex):\n" + " if not getattr(self, '_alive', True) or not self.winfo_exists():\n" + " return\n", + src +) + +# 5) Piccola robustezza: prima di schedulare highlight post-ricarica controlla ancora _alive +src = src.replace( + " if self._pending_focus and self._pending_focus[0] == corsia:\n", + " if getattr(self, '_alive', True) and self._pending_focus and self._pending_focus[0] == corsia:\n" +) + +p.write_text(src, encoding="utf-8") +print(f"Patch applicata a {p} (backup in {backup}).") diff --git a/gestione_aree_frame_async.py b/gestione_aree_frame_async.py new file mode 100644 index 0000000..cd65196 --- /dev/null +++ b/gestione_aree_frame_async.py @@ -0,0 +1,170 @@ +# gestione_aree_frame_async.py +from __future__ import annotations + +import asyncio +import threading +import tkinter as tk +import customtkinter as ctk +from typing import Any, Callable, Optional + +__VERSION__ = "GestioneAreeFrame v3.2.5-singleloop" +#print("[GestioneAreeFrame] loaded", __VERSION__) + +try: + from async_msssql_query import AsyncMSSQLClient # noqa: F401 +except Exception: + AsyncMSSQLClient = object # type: ignore + +# ======================== +# Global asyncio loop +# ======================== +class _LoopHolder: + def __init__(self): + self.loop: Optional[asyncio.AbstractEventLoop] = None + self.thread: Optional[threading.Thread] = None + self.ready = threading.Event() + +_GLOBAL = _LoopHolder() + +def _run_loop(): + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + _GLOBAL.loop = loop + _GLOBAL.ready.set() + loop.run_forever() + +def get_global_loop() -> asyncio.AbstractEventLoop: + if _GLOBAL.loop is not None: + return _GLOBAL.loop + _GLOBAL.thread = threading.Thread(target=_run_loop, name="warehouse-asyncio", daemon=True) + _GLOBAL.thread.start() + _GLOBAL.ready.wait(timeout=5.0) + if _GLOBAL.loop is None: + raise RuntimeError("Impossibile avviare l'event loop globale") + return _GLOBAL.loop + +def stop_global_loop(): + if _GLOBAL.loop and _GLOBAL.loop.is_running(): + _GLOBAL.loop.call_soon_threadsafe(_GLOBAL.loop.stop) + if _GLOBAL.thread: + _GLOBAL.thread.join(timeout=2.0) + _GLOBAL.loop = None + _GLOBAL.thread = None + _GLOBAL.ready.clear() + +# ======================== +# Busy overlay +# ======================== +class BusyOverlay: + def __init__(self, parent: tk.Misc): + self.parent = parent + self._top: Optional[ctk.CTkToplevel] = None + self._pb: Optional[ctk.CTkProgressBar] = None + self._lbl: Optional[ctk.CTkLabel] = None + self._bind_id = None + + def _reposition(self): + if not self._top: + return + root = self.parent.winfo_toplevel() + root.update_idletasks() + x, y = root.winfo_rootx(), root.winfo_rooty() + w, h = root.winfo_width(), root.winfo_height() + self._top.geometry(f"{w}x{h}+{x}+{y}") + + def show(self, message="Attendere…"): + if self._top: + if self._lbl: + self._lbl.configure(text=message) + return + root = self.parent.winfo_toplevel() + top = ctk.CTkToplevel(root) + self._top = top + top.overrideredirect(True) + try: + top.attributes("-alpha", 0.22) + except tk.TclError: + pass + try: + top.configure(fg_color="#000000") + except Exception: + top.configure(bg="#000000") + top.attributes("-topmost", True) + + wrap = ctk.CTkFrame(top, corner_radius=8) + wrap.place(relx=0.5, rely=0.5, anchor="center") + self._lbl = ctk.CTkLabel(wrap, text=message, font=("Segoe UI", 11, "bold")) + self._lbl.pack(pady=(0, 10)) + self._pb = ctk.CTkProgressBar(wrap, mode="indeterminate", width=260) + self._pb.pack(fill="x") + try: + self._pb.start() + except Exception: + pass + + self._reposition() + self._bind_id = root.bind("", lambda e: self._reposition(), add="+") + + def hide(self): + if self._pb: + try: + self._pb.stop() + except Exception: + pass + self._pb = None + if self._top: + try: + self._top.destroy() + except Exception: + pass + self._top = None + root = self.parent.winfo_toplevel() + if self._bind_id: + try: + root.unbind("", self._bind_id) + except Exception: + pass + self._bind_id = None + +# ======================== +# AsyncRunner (single-loop) +# ======================== +class AsyncRunner: + """Run awaitables on the single global loop and callback on Tk main thread.""" + def __init__(self, widget: tk.Misc): + self.widget = widget + self.loop = get_global_loop() + + def run( + self, + awaitable, + on_success: Callable[[Any], None], + on_error: Optional[Callable[[BaseException], None]] = None, + busy: Optional[BusyOverlay] = None, + message: str = "Operazione in corso…", + ): + if busy: + busy.show(message) + fut = asyncio.run_coroutine_threadsafe(awaitable, self.loop) + + def _poll(): + if fut.done(): + if busy: + busy.hide() + try: + res = fut.result() + except BaseException as ex: + if on_error: + self.widget.after(0, lambda e=ex: on_error(e)) + else: + print("[AsyncRunner] Unhandled error:", repr(ex)) + else: + self.widget.after(0, lambda r=res: on_success(r)) + else: + self.widget.after(60, _poll) + + _poll() + + def close(self): + # no-op: loop is global + pass diff --git a/gestione_pickinglist.py b/gestione_pickinglist.py new file mode 100644 index 0000000..48bb032 --- /dev/null +++ b/gestione_pickinglist.py @@ -0,0 +1,668 @@ +# =================== gestione_pickinglist.py (NO-FLICKER + UX TUNING + MICRO-SPINNER) =================== + +from __future__ import annotations +import tkinter as tk +import customtkinter as ctk +from tkinter import messagebox +from typing import Optional, Any, Dict, List, Callable +from dataclasses import dataclass + +# Usa overlay e runner "collaudati" +from gestione_aree_frame_async import BusyOverlay, AsyncRunner + +from async_loop_singleton import get_global_loop +from db_async_singleton import get_db as _get_db_singleton + +# === IMPORT procedura async prenota/s-prenota (no pyodbc qui) === +import asyncio +try: + from prenota_sprenota_sql import sp_xExePackingListPallet_async, SPResult +except Exception: + async def sp_xExePackingListPallet_async(*args, **kwargs): + raise RuntimeError("sp_xExePackingListPallet_async non importabile: verifica prenota_sprenota_sql.py") + class SPResult: + def __init__(self, rc=-1, message="Procedura non disponibile", id_result=None): + self.rc = rc; self.message = message; self.id_result = id_result + + +# -------------------- SQL -------------------- +SQL_PL = """ +SELECT + COUNT(DISTINCT Pallet) AS Pallet, + COUNT(DISTINCT Lotto) AS Lotto, + COUNT(DISTINCT Articolo) AS Articolo, + COUNT(DISTINCT Descrizione) AS Descrizione, + SUM(Qta) AS Qta, + Documento, + CodNazione, + NAZIONE, + Stato, + MAX(PalletCella) AS PalletCella, + MAX(Magazzino) AS Magazzino, + MAX(Area) AS Area, + MAX(Cella) AS Cella, + MIN(Ordinamento) AS Ordinamento, + MAX(IDStato) AS IDStato +FROM dbo.XMag_ViewPackingList +GROUP BY Documento, CodNazione, NAZIONE, Stato +ORDER BY MIN(Ordinamento), Documento, NAZIONE, Stato; +""" + +SQL_PL_DETAILS = """ +SELECT * +FROM ViewPackingListRestante +WHERE Documento = :Documento +ORDER BY Ordinamento; +""" + +# -------------------- helpers -------------------- +def _rows_to_dicts(res: Dict[str, Any]) -> List[Dict[str, Any]]: + """ + Converte il payload ritornato da query_json in lista di dict. + Supporta: + - res = [ {..}, {..} ] + - res = { "rows": [..], "columns": [...] } + - res = { "data": [..], "columns": [...] } + - res = { "rows": [tuple,..], "columns": [...] } + """ + if res is None: + return [] + + if isinstance(res, list): + if not res: + return [] + if isinstance(res[0], dict): + return res + return [] + + if isinstance(res, dict): + for rows_key in ("rows", "data", "result", "records"): + if rows_key in res and isinstance(res[rows_key], list): + rows = res[rows_key] + if not rows: + return [] + if isinstance(rows[0], dict): + return rows + cols = res.get("columns") or res.get("cols") or [] + out = [] + for r in rows: + if cols and isinstance(r, (list, tuple)): + out.append({ (cols[i] if i < len(cols) else f"c{i}") : r[i] + for i in range(min(len(cols), len(r))) }) + else: + if isinstance(r, (list, tuple)): + out.append({ f"c{i}": r[i] for i in range(len(r)) }) + return out + if res and all(not isinstance(v, (list, tuple, dict)) for v in res.values()): + return [res] + + return [] + +def _s(v) -> str: + """Stringify safe: None -> '', altrimenti str(v).""" + return "" if v is None else str(v) + +def _first(d: Dict[str, Any], keys: List[str], default: str = ""): + for k in keys: + if k in d and d[k] not in (None, ""): + return d[k] + return default + +# -------------------- column specs -------------------- +@dataclass +class ColSpec: + title: str + key: str + width: int + anchor: str # 'w' | 'e' | 'center' + +# Colonne PL (in alto) — include IDStato per la colorazione +PL_COLS: List[ColSpec] = [ + ColSpec("", "__check__", 36, "w"), + ColSpec("Documento", "Documento", 120, "w"), + ColSpec("NAZIONE", "NAZIONE", 240, "w"), + ColSpec("Stato", "Stato", 110, "w"), + ColSpec("IDStato", "IDStato", 80, "e"), # nuova colonna + ColSpec("#Pallet", "Pallet", 100, "e"), + ColSpec("#Lotti", "Lotto", 100, "e"), + ColSpec("#Articoli", "Articolo", 110, "e"), + ColSpec("Qta", "Qta", 120, "e"), +] + +DET_COLS: List[ColSpec] = [ + ColSpec("UDC/Pallet", "Pallet", 150, "w"), + ColSpec("Lotto", "Lotto", 130, "w"), + ColSpec("Articolo", "Articolo", 150, "w"), + ColSpec("Descrizione","Descrizione", 320, "w"), + ColSpec("Qta", "Qta", 110, "e"), + ColSpec("Ubicazione", "Ubicazione", 320, "w"), +] + +ROW_H = 28 + + +# -------------------- Micro spinner (toolbar) -------------------- +class ToolbarSpinner: + """ + Micro-animazione leggerissima per indicare attività: + mostra una label con frame: ◐ ◓ ◑ ◒ ... finché è attivo. + """ + FRAMES = ("◐", "◓", "◑", "◒") + def __init__(self, parent: tk.Widget): + self.parent = parent + self.lbl = ctk.CTkLabel(parent, text="", width=28) + self._i = 0 + self._active = False + self._job = None + + def widget(self) -> ctk.CTkLabel: + return self.lbl + + def start(self, text: str = ""): + if self._active: + return + self._active = True + self.lbl.configure(text=f"{self.FRAMES[self._i]} {text}".strip()) + self._tick() + + def stop(self): + self._active = False + if self._job is not None: + try: + self.parent.after_cancel(self._job) + except Exception: + pass + self._job = None + self.lbl.configure(text="") + + def _tick(self): + if not self._active: + return + self._i = (self._i + 1) % len(self.FRAMES) + current = self.lbl.cget("text") + # Mantieni l'eventuale testo dopo il simbolo + txt_suffix = "" + if isinstance(current, str) and len(current) > 2: + txt_suffix = current[2:] + self.lbl.configure(text=f"{self.FRAMES[self._i]}{txt_suffix}") + self._job = self.parent.after(120, self._tick) # 8 fps soft + + +# -------------------- Scrollable table -------------------- +class ScrollTable(ctk.CTkFrame): + GRID_COLOR = "#D0D5DD" + PADX_L = 8 + PADX_R = 8 + PADY = 2 + + def __init__(self, master, columns: List[ColSpec]): + super().__init__(master) + self.columns = columns + self.total_w = sum(c.width for c in self.columns) + + self.grid_rowconfigure(1, weight=1) + self.grid_columnconfigure(0, weight=1) + + # header + self.h_canvas = tk.Canvas(self, height=ROW_H, highlightthickness=0, bd=0) + self.h_inner = ctk.CTkFrame(self.h_canvas, fg_color="#f3f3f3", + height=ROW_H, width=self.total_w) + self.h_canvas.create_window((0,0), window=self.h_inner, anchor="nw", + width=self.total_w, height=ROW_H) + self.h_canvas.grid(row=0, column=0, sticky="ew") + + # body + self.b_canvas = tk.Canvas(self, highlightthickness=0, bd=0) + self.b_inner = ctk.CTkFrame(self.b_canvas, fg_color="transparent", + width=self.total_w) + self.body_window = self.b_canvas.create_window((0,0), window=self.b_inner, + anchor="nw", width=self.total_w) + self.b_canvas.grid(row=1, column=0, sticky="nsew") + + # scrollbars + self.vbar = tk.Scrollbar(self, orient="vertical", command=self.b_canvas.yview) + self.xbar = tk.Scrollbar(self, orient="horizontal", command=self._xscroll_both) + self.vbar.grid(row=1, column=1, sticky="ns") + self.xbar.grid(row=2, column=0, sticky="ew") + + # link scroll + self.b_canvas.configure(yscrollcommand=self.vbar.set, xscrollcommand=self._xscroll_set_both) + self.h_canvas.configure(xscrollcommand=self.xbar.set) + + # bind + self.h_inner.bind("", lambda e: self._sync_header_width()) + self.b_inner.bind("", lambda e: self._on_body_configure()) + + self._build_header() + + def _build_header(self): + for w in self.h_inner.winfo_children(): + w.destroy() + + row = ctk.CTkFrame(self.h_inner, fg_color="#f3f3f3", + height=ROW_H, width=self.total_w) + row.pack(fill="x", expand=False) + row.pack_propagate(False) + + for col in self.columns: + holder = ctk.CTkFrame( + row, fg_color="#f3f3f3", + width=col.width, height=ROW_H, + border_width=1, border_color=self.GRID_COLOR + ) + holder.pack(side="left", fill="y") + holder.pack_propagate(False) + + lbl = ctk.CTkLabel(holder, text=col.title, anchor="w") + lbl.pack(fill="both", padx=(self.PADX_L, self.PADX_R), pady=self.PADY) + + self.h_inner.configure(width=self.total_w, height=ROW_H) + self.h_canvas.configure(scrollregion=(0,0,self.total_w,ROW_H)) + + def _update_body_width(self): + self.b_canvas.itemconfigure(self.body_window, width=self.total_w) + sr = self.b_canvas.bbox("all") + if sr: + self.b_canvas.configure(scrollregion=(0,0,max(self.total_w, sr[2]), sr[3])) + else: + self.b_canvas.configure(scrollregion=(0,0,self.total_w,0)) + + def _on_body_configure(self): + self._update_body_width() + self._sync_header_width() + + def _sync_header_width(self): + first, _ = self.b_canvas.xview() + self.h_canvas.xview_moveto(first) + + def _xscroll_both(self, *args): + self.h_canvas.xview(*args) + self.b_canvas.xview(*args) + + def _xscroll_set_both(self, first, last): + self.h_canvas.xview_moveto(first) + self.xbar.set(first, last) + + def clear_rows(self): + for w in self.b_inner.winfo_children(): + w.destroy() + self._update_body_width() + + def add_row( + self, + values: List[str], + row_index: int, + anchors: Optional[List[str]] = None, + checkbox_builder: Optional[Callable[[tk.Widget], ctk.CTkCheckBox]] = None, + ): + row = ctk.CTkFrame(self.b_inner, fg_color="transparent", + height=ROW_H, width=self.total_w) + row.pack(fill="x", expand=False) + row.pack_propagate(False) + + for i, col in enumerate(self.columns): + holder = ctk.CTkFrame( + row, fg_color="transparent", + width=col.width, height=ROW_H, + border_width=1, border_color=self.GRID_COLOR + ) + holder.pack(side="left", fill="y") + holder.pack_propagate(False) + + if col.key == "__check__": + if checkbox_builder: + cb = checkbox_builder(holder) + cb.pack(padx=(self.PADX_L, self.PADX_R), pady=self.PADY, anchor="w") + else: + ctk.CTkLabel(holder, text="").pack(fill="both") + else: + anchor = (anchors[i] if anchors else col.anchor) + ctk.CTkLabel(holder, text=values[i], anchor=anchor).pack( + fill="both", padx=(self.PADX_L, self.PADX_R), pady=self.PADY + ) + + self._update_body_width() + + +# -------------------- PL row model -------------------- +class PLRow: + def __init__(self, pl: Dict[str, Any], on_check): + self.pl = pl + self.var = ctk.BooleanVar(value=False) + self._callback = on_check + def is_checked(self) -> bool: return self.var.get() + def set_checked(self, val: bool): self.var.set(val) + def build_checkbox(self, parent) -> ctk.CTkCheckBox: + return ctk.CTkCheckBox(parent, text="", variable=self.var, + command=lambda: self._callback(self, self.var.get())) + + +# -------------------- main frame (no-flicker + UX tuning + spinner) -------------------- +class GestionePickingListFrame(ctk.CTkFrame): + def __init__(self, master, *, db_client=None, conn_str=None): + super().__init__(master) + self.db_client = db_client or _get_db_singleton(get_global_loop(), conn_str) + self.runner = AsyncRunner(self) # runner condiviso (usa loop globale) + self.busy = BusyOverlay(self) # overlay collaudato + + self.rows_models: list[PLRow] = [] + self._detail_cache: Dict[Any, list] = {} + self.detail_doc = None + + self._first_loading: bool = False # flag per cursore d'attesa solo al primo load + + self._build_layout() + # 🔇 Niente reload immediato: carichiamo quando la finestra è idle (= già resa) + self.after_idle(self._first_show) + + def _first_show(self): + """Chiamato a finestra già resa → evitiamo sfarfallio del primo paint e mostriamo wait-cursor.""" + self._first_loading = True + try: + self.winfo_toplevel().configure(cursor="watch") + except Exception: + pass + # spinner inizia + self.spinner.start(" Carico…") + self.reload_from_db(first=True) + + # ---------- UI ---------- + def _build_layout(self): + for r in (1, 3): self.grid_rowconfigure(r, weight=1) + self.grid_columnconfigure(0, weight=1) + + top = ctk.CTkFrame(self) + top.grid(row=0, column=0, sticky="ew", padx=10, pady=(8,4)) + for i, (text, cmd) in enumerate([ + ("Ricarica", self.reload_from_db), + ("Prenota", self.on_prenota), + ("S-prenota", self.on_sprenota), + ("Esporta XLSX", self.on_export) + ]): + ctk.CTkButton(top, text=text, command=cmd).grid(row=0, column=i, padx=6) + + # --- 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 + + self.pl_table = ScrollTable(self, PL_COLS) + self.pl_table.grid(row=1, column=0, sticky="nsew", padx=10, pady=(4,8)) + + self.det_table = ScrollTable(self, DET_COLS) + self.det_table.grid(row=3, column=0, sticky="nsew", padx=10, pady=(4,10)) + + self._draw_details_hint() + + def _draw_details_hint(self): + self.det_table.clear_rows() + self.det_table.add_row( + values=["", "", "", "Seleziona una Picking List per vedere le UDC…", "", ""], + row_index=0, + anchors=["w"]*6 + ) + + def _apply_row_colors(self, rows: List[Dict[str, Any]]): + """Colorazione differita (after_idle) per evitare micro-jank durante l'inserimento righe.""" + try: + for idx, d in enumerate(rows): + row_widget = self.pl_table.b_inner.winfo_children()[idx] + if int(d.get("IDStato") or 0) == 1: + row_widget.configure(fg_color="#ffe6f2") # rosa tenue + else: + row_widget.configure(fg_color="transparent") + except Exception: + pass + + def _refresh_mid_rows(self, rows: List[Dict[str, Any]]): + self.pl_table.clear_rows() + self.rows_models.clear() + + for r, d in enumerate(rows): + model = PLRow(d, self.on_row_checked) + self.rows_models.append(model) + values = [ + "", # checkbox + _s(d.get("Documento")), + _s(d.get("NAZIONE")), + _s(d.get("Stato")), + _s(d.get("IDStato")), # nuova colonna visibile + _s(d.get("Pallet")), + _s(d.get("Lotto")), + _s(d.get("Articolo")), + _s(d.get("Qta")), + ] + self.pl_table.add_row( + values=values, + row_index=r, + anchors=[c.anchor for c in PL_COLS], + checkbox_builder=model.build_checkbox + ) + + # 🎯 Colora dopo che la UI è resa → no balzi visivi + self.after_idle(lambda: self._apply_row_colors(rows)) + + # ----- helpers ----- + def _get_selected_model(self) -> Optional[PLRow]: + for m in self.rows_models: + if m.is_checked(): + return m + return None + + def _recolor_row_by_documento(self, documento: str, idstato: int): + """Aggiorna colore riga e cella IDStato per il Documento indicato.""" + for idx, m in enumerate(self.rows_models): + if _s(m.pl.get("Documento")) == _s(documento): + m.pl["IDStato"] = idstato + def _paint(): + try: + row_widget = self.pl_table.b_inner.winfo_children()[idx] + row_widget.configure(fg_color="#ffe6f2" if idstato == 1 else "transparent") + row_children = row_widget.winfo_children() + if len(row_children) >= 5: + holder = row_children[4] + if holder.winfo_children(): + lbl = holder.winfo_children()[0] + if hasattr(lbl, "configure"): + lbl.configure(text=str(idstato)) + except Exception: + pass + # differisci la colorazione (smooth) + self.after_idle(_paint) + break + + def _reselect_documento_after_reload(self, documento: str): + """(Opzionale) Dopo un reload DB, riseleziona la PL con lo stesso Documento.""" + for m in self.rows_models: + if _s(m.pl.get("Documento")) == _s(documento): + m.set_checked(True) + self.on_row_checked(m, True) + break + + # ----- eventi ----- + def on_row_checked(self, model: PLRow, is_checked: bool): + # selezione esclusiva + if is_checked: + for m in self.rows_models: + if m is not model and m.is_checked(): + m.set_checked(False) + + self.detail_doc = model.pl.get("Documento") + self.spinner.start(" Carico dettagli…") # spinner ON + + async def _job(): + return await self.db_client.query_json(SQL_PL_DETAILS, {"Documento": self.detail_doc}) + + def _ok(res): + self.spinner.stop() # spinner OFF + self._detail_cache[self.detail_doc] = _rows_to_dicts(res) + # differisci il render dei dettagli (più fluido) + self.after_idle(self._refresh_details) + + def _err(ex): + self.spinner.stop() + messagebox.showerror("DB", f"Errore nel caricamento dettagli:\n{ex}") + + self.runner.run( + _job(), + on_success=_ok, + on_error=_err, + busy=self.busy, + message=f"Carico UDC per Documento {self.detail_doc}…" + ) + + else: + if not any(m.is_checked() for m in self.rows_models): + self.detail_doc = None + self._refresh_details() + + # ----- load PL ----- + def reload_from_db(self, first: bool = False): + self.spinner.start(" Carico…") # spinner ON + async def _job(): + return await self.db_client.query_json(SQL_PL, {}) + def _on_success(res): + rows = _rows_to_dicts(res) + self._refresh_mid_rows(rows) + self.spinner.stop() # spinner OFF + # se era il primo load, ripristina il cursore standard + if self._first_loading: + try: + self.winfo_toplevel().configure(cursor="") + except Exception: + pass + self._first_loading = False + def _on_error(ex): + self.spinner.stop() + if self._first_loading: + try: + self.winfo_toplevel().configure(cursor="") + except Exception: + pass + self._first_loading = False + messagebox.showerror("DB", f"Errore nel caricamento:\n{ex}") + + self.runner.run( + _job(), + on_success=_on_success, + on_error=_on_error, + busy=self.busy, + message="Caricamento Picking List…" if first else "Aggiornamento…" + ) + + def _refresh_details(self): + self.det_table.clear_rows() + if not self.detail_doc: + self._draw_details_hint() + return + + rows = self._detail_cache.get(self.detail_doc, []) + if not rows: + self.det_table.add_row(values=["", "", "", "Nessuna UDC trovata.", "", ""], + row_index=0, anchors=["w"]*6) + return + + for r, d in enumerate(rows): + pallet = _s(_first(d, ["Pallet", "UDC", "PalletID"])) + lotto = _s(_first(d, ["Lotto"])) + articolo = _s(_first(d, ["Articolo", "CodArticolo", "CodiceArticolo", "Art", "Codice"])) + descr = _s(_first(d, ["Descrizione", "Descr", "DescrArticolo", "DescArticolo", "DesArticolo"])) + qta = _s(_first(d, ["Qta", "Quantita", "Qty", "QTY"])) + ubi_raw = _first(d, ["Ubicazione", "Cella", "PalletCella"]) + loc = "Non scaffalata" if (ubi_raw is None or str(ubi_raw).strip()=="") else str(ubi_raw).strip() + + self.det_table.add_row( + values=[pallet, lotto, articolo, descr, qta, loc], + row_index=r, + anchors=[c.anchor for c in DET_COLS] + ) + + # ----- azioni ----- + def on_prenota(self): + model = self._get_selected_model() + if not model: + messagebox.showinfo("Prenota", "Seleziona una Picking List (checkbox) prima di prenotare.") + return + + documento = _s(model.pl.get("Documento")) + current = int(model.pl.get("IDStato") or 0) + desired = 1 + if current == desired: + messagebox.showinfo("Prenota", f"La Picking List {documento} è già prenotata.") + return + + id_operatore = 1 # TODO: recupera dal contesto reale + self.spinner.start(" Prenoto…") + + async def _job(): + return await sp_xExePackingListPallet_async(self.db_client, id_operatore, documento) + + def _ok(res: SPResult): + self.spinner.stop() + if res and res.rc == 0: + self._recolor_row_by_documento(documento, desired) + else: + msg = (res.message if res else "Errore sconosciuto") + messagebox.showerror("Prenota", f"Operazione non riuscita:\n{msg}") + + def _err(ex): + self.spinner.stop() + messagebox.showerror("Prenota", f"Errore:\n{ex}") + + self.runner.run( + _job(), + on_success=_ok, + on_error=_err, + busy=self.busy, + message=f"Prenoto la Picking List {documento}…" + ) + + def on_sprenota(self): + model = self._get_selected_model() + if not model: + messagebox.showinfo("S-prenota", "Seleziona una Picking List (checkbox) prima di s-prenotare.") + return + + documento = _s(model.pl.get("Documento")) + current = int(model.pl.get("IDStato") or 0) + desired = 0 + if current == desired: + messagebox.showinfo("S-prenota", f"La Picking List {documento} è già NON prenotata.") + return + + id_operatore = 1 # TODO: recupera dal contesto reale + self.spinner.start(" S-prenoto…") + + async def _job(): + return await sp_xExePackingListPallet_async(self.db_client, id_operatore, documento) + + def _ok(res: SPResult): + self.spinner.stop() + if res and res.rc == 0: + self._recolor_row_by_documento(documento, desired) + else: + msg = (res.message if res else "Errore sconosciuto") + messagebox.showerror("S-prenota", f"Operazione non riuscita:\n{msg}") + + def _err(ex): + self.spinner.stop() + messagebox.showerror("S-prenota", f"Errore:\n{ex}") + + self.runner.run( + _job(), + on_success=_ok, + on_error=_err, + busy=self.busy, + message=f"S-prenoto la Picking List {documento}…" + ) + + def on_export(self): + messagebox.showinfo("Esporta", "Stub esportazione.") + + +# factory per main +def create_frame(parent, *, db_client=None, conn_str=None) -> 'GestionePickingListFrame': + ctk.set_appearance_mode("light") + ctk.set_default_color_theme("green") + return GestionePickingListFrame(parent, db_client=db_client, conn_str=conn_str) + +# =================== /gestione_pickinglist.py =================== diff --git a/layout_window.py b/layout_window.py new file mode 100644 index 0000000..e460a93 --- /dev/null +++ b/layout_window.py @@ -0,0 +1,670 @@ +from __future__ import annotations +import tkinter as tk +from tkinter import Menu, messagebox, filedialog +import customtkinter as ctk +from datetime import datetime + +from gestione_aree_frame_async import BusyOverlay, AsyncRunner + +# ---- Color palette ---- +COLOR_EMPTY = "#B0B0B0" # grigio (vuota) +COLOR_FULL = "#FFA500" # arancione (una UDC) +COLOR_DOUBLE = "#D62728" # rosso (>=2 UDC) +FG_DARK = "#111111" +FG_LIGHT = "#FFFFFF" + + +def pct_text(p_full: float, p_double: float | None = None) -> str: + p_full = max(0.0, min(1.0, p_full)) + pf = round(p_full * 100, 1) + pe = round(100 - pf, 1) + if p_double and p_double > 0: + pd = round(p_double * 100, 1) + return f"Pieno {pf}% · Vuoto {pe}% (di cui doppie {pd}%)" + return f"Pieno {pf}% · Vuoto {pe}%" + + +class LayoutWindow(ctk.CTkToplevel): + """ + Visualizzazione layout corsie con matrice di celle. + - Ogni cella è un pulsante colorato (vuota/piena/doppia) + - Etichetta su DUE righe: + 1) "Corsia.Colonna.Fila" (una sola riga, senza andare a capo) + 2) barcode UDC (primo, se presente) + - Ricerca per barcode UDC con cambio automatico corsia + highlight cella + - Statistiche: globale e corsia selezionata + - Export XLSX + """ + def __init__(self, parent: tk.Widget, db_app): + super().__init__(parent) + self.title("Warehouse · Layout corsie") + self.geometry("1200x740") + self.minsize(980, 560) + self.resizable(True, True) + + self.db = db_app + self._busy = BusyOverlay(self) + self._async = AsyncRunner(self) + + # layout principale 5% / 80% / 15% + self.grid_rowconfigure(0, weight=5) + self.grid_rowconfigure(1, weight=80) + self.grid_rowconfigure(2, weight=15) + self.grid_columnconfigure(0, weight=1) + + # stato runtime + self.corsia_selezionata = tk.StringVar() + self.buttons: list[list[ctk.CTkButton]] = [] + self.btn_frames: list[list[ctk.CTkFrame]] = [] + self.matrix_state: list[list[int]] = [] # <— rinominata: prima era self.state + self.fila_txt: list[list[str]] = [] + self.col_txt: list[list[str]] = [] + self.desc: list[list[str]] = [] + self.udc1: list[list[str]] = [] # primo barcode UDC trovato (o "") + + # ricerca → focus differito (corsia, col, fila, barcode) + self._pending_focus: tuple[str, str, str, str] | None = None + self._highlighted: tuple[int, int] | None = None + + # anti-race: token per ignorare risposte vecchie + self._req_counter = 0 + self._last_req = 0 + self._alive = True + self._stats_after_id = None # se mai userai un refresh periodico, potremo cancellarlo qui + + self._build_top() + self._build_matrix_host() + self._build_stats() + + self._load_corsie() + # disabilitato: il refresh ad ogni generava molte query/lag + # self.bind("", lambda e: self.after_idle(self._refresh_stats)) + + # ---------------- TOP BAR ---------------- + def _build_top(self): + top = ctk.CTkFrame(self) + top.grid(row=0, column=0, sticky="nsew", padx=8, pady=6) + for i in range(4): + top.grid_columnconfigure(i, weight=0) + top.grid_columnconfigure(1, weight=1) + + # lista corsie + lf = ctk.CTkFrame(top) + lf.grid(row=0, column=0, sticky="nsw") + lf.grid_columnconfigure(0, weight=1) + ctk.CTkLabel(lf, text="Corsie", font=("", 12, "bold")).grid(row=0, column=0, sticky="w", padx=6, pady=(6, 2)) + self.lb = tk.Listbox(lf, height=6, exportselection=False) + self.lb.grid(row=1, column=0, sticky="nsw", padx=6, pady=(0, 6)) + self.lb.bind("<>", self._on_select) + + # search by barcode + srch = ctk.CTkFrame(top) + srch.grid(row=0, column=1, sticky="nsew", padx=(10, 10)) + self.search_var = tk.StringVar() + self.search_entry = ctk.CTkEntry(srch, textvariable=self.search_var, width=260) + self.search_entry.grid(row=0, column=0, sticky="w") + ctk.CTkButton(srch, text="Cerca per barcode UDC", command=self._search_udc).grid(row=0, column=1, padx=(8, 0)) + srch.grid_columnconfigure(0, weight=1) + + # toolbar + tb = ctk.CTkFrame(top) + tb.grid(row=0, column=3, sticky="ne") + ctk.CTkButton(tb, text="Aggiorna", command=self._refresh_current).grid(row=0, column=0, padx=4) + ctk.CTkButton(tb, text="Export XLSX", command=self._export_xlsx).grid(row=0, column=1, padx=4) + + # ---------------- MATRIX HOST ---------------- + def _build_matrix_host(self): + center = ctk.CTkFrame(self) + center.grid(row=1, column=0, sticky="nsew", padx=8, pady=(0, 6)) + center.grid_rowconfigure(0, weight=1) + center.grid_columnconfigure(0, weight=1) + self.host = ctk.CTkFrame(center) + self.host.grid(row=0, column=0, sticky="nsew", padx=4, pady=4) + + def _apply_cell_style(self, btn: ctk.CTkButton, state: int): + if state == 0: + btn.configure( + fg_color=COLOR_EMPTY, hover_color="#9A9A9A", + text_color=FG_DARK, border_width=0 + ) + elif state == 1: + btn.configure( + fg_color=COLOR_FULL, hover_color="#E69500", + text_color=FG_DARK, border_width=0 + ) + else: + btn.configure( + fg_color=COLOR_DOUBLE, hover_color="#B22222", + text_color=FG_LIGHT, border_width=0 + ) + + def _clear_highlight(self): + if self._highlighted and self.buttons: + r, c = self._highlighted + try: + if 0 <= r < len(self.buttons) and 0 <= c < len(self.buttons[r]): + btn = self.buttons[r][c] + if getattr(btn, "winfo_exists", None) and btn.winfo_exists(): + try: + btn.configure(border_width=0) + except Exception: + pass + # clear blue frame border + try: + fr = self.btn_frames[r][c] + if fr and getattr(fr, "winfo_exists", None) and fr.winfo_exists(): + fr.configure(border_width=0) + # in CTkFrame non esiste highlightthickness come in tk; border_* è corretto + except Exception: + pass + except Exception: + pass + self._highlighted = None + + def _rebuild_matrix(self, rows: int, cols: int, state, fila_txt, col_txt, desc, udc1, corsia): + # prima rimuovi highlight su vecchi bottoni + self._clear_highlight() + # ripulisci host + for w in self.host.winfo_children(): + w.destroy() + self.buttons.clear() + self.btn_frames.clear() + + # salva matrici + self.matrix_state, self.fila_txt, self.col_txt, self.desc, self.udc1 = state, fila_txt, col_txt, desc, udc1 + + # ridistribuisci pesi griglia + for r in range(rows): + self.host.grid_rowconfigure(r, weight=1) + for c in range(cols): + self.host.grid_columnconfigure(c, weight=1) + + # crea Frame+Button per cella (righe invertite: fila "a" in basso) + for r in range(rows): + row_btns = [] + row_frames = [] + for c in range(cols): + st = state[r][c] + code = f"{corsia}.{col_txt[r][c]}.{fila_txt[r][c]}" # PRIMA RIGA (in linea) + udc = udc1[r][c] or "" # SECONDA RIGA: barcode UDC + text = f"{code}\n{udc}" + cell = ctk.CTkFrame(self.host, corner_radius=6, border_width=0) + btn = ctk.CTkButton( + cell, + text=text, + corner_radius=6) + self._apply_cell_style(btn, st) + + rr = (rows - 1) - r # capovolgi + cell.grid(row=rr, column=c, padx=1, pady=1, sticky="nsew") + btn.pack(fill="both", expand=True) + btn.configure(command=lambda rr=r, cc=c: self._open_menu(None, rr, cc)) + btn.bind("", lambda e, rr=r, cc=c: self._open_menu(e, rr, cc)) + row_btns.append(btn) + row_frames.append(cell) + self.buttons.append(row_btns) + self.btn_frames.append(row_frames) + + # focus differito post-ricarica + if getattr(self, '_alive', True) and self._pending_focus and self._pending_focus[0] == corsia: + _, col, fila, _barcode = self._pending_focus + self._pending_focus = None + self._highlight_cell_by_labels(col, fila) + + # ---------------- CONTEXT MENU ---------------- + def _open_menu(self, event, r, c): + st = self.matrix_state[r][c] + corsia = self.corsia_selezionata.get() + label = f"{corsia}.{self.col_txt[r][c]}.{self.fila_txt[r][c]}" + m = Menu(self, tearoff=0) + m.add_command(label="Apri dettaglio", command=lambda: self._toast(f"Dettaglio {label}")) + if st == 0: + m.add_command(label="Segna pieno", command=lambda: self._set_cell(r, c, 1)) + m.add_command(label="Segna doppia", command=lambda: self._set_cell(r, c, 2)) + elif st == 1: + m.add_command(label="Segna vuoto", command=lambda: self._set_cell(r, c, 0)) + m.add_command(label="Segna doppia", command=lambda: self._set_cell(r, c, 2)) + else: + m.add_command(label="Segna vuoto", command=lambda: self._set_cell(r, c, 0)) + m.add_command(label="Segna pieno", command=lambda: self._set_cell(r, c, 1)) + m.add_separator() + m.add_command(label="Copia ubicazione", command=lambda: self._copy(label)) + x = self.winfo_pointerx() if event is None else event.x_root + y = self.winfo_pointery() if event is None else event.y_root + m.tk_popup(x, y) + + def _set_cell(self, r, c, val): + self.matrix_state[r][c] = val + btn = self.buttons[r][c] + self._apply_cell_style(btn, val) + self._refresh_stats() + + # ---------------- STATS ---------------- + def _build_stats(self): + bottom = ctk.CTkFrame(self) + bottom.grid(row=2, column=0, sticky="nsew", padx=8, pady=6) + bottom.grid_columnconfigure(0, weight=1) + + ctk.CTkLabel(bottom, text="Riempimento globale", font=("", 10, "bold")).grid(row=0, column=0, sticky="w", pady=(0, 2)) + self.tot_canvas = tk.Canvas(bottom, height=22, highlightthickness=0) + self.tot_canvas.grid(row=1, column=0, sticky="ew", padx=(0, 260)) + self.tot_text = ctk.CTkLabel(bottom, text=pct_text(0.0, 0.0)) + self.tot_text.grid(row=1, column=0, sticky="e") + + ctk.CTkLabel(bottom, text="Riempimento corsia selezionata", font=("", 10, "bold")).grid(row=2, column=0, sticky="w", pady=(10, 2)) + self.sel_canvas = tk.Canvas(bottom, height=22, highlightthickness=0) + self.sel_canvas.grid(row=3, column=0, sticky="ew", padx=(0, 260)) + self.sel_text = ctk.CTkLabel(bottom, text=pct_text(0.0, 0.0)) + self.sel_text.grid(row=3, column=0, sticky="e") + + leg = ctk.CTkFrame(bottom) + leg.grid(row=4, column=0, sticky="w", pady=(10, 0)) + ctk.CTkLabel(leg, text="Legenda celle:").grid(row=0, column=0, padx=(0, 8)) + self._legend(leg, 1, "Vuota", COLOR_EMPTY) + self._legend(leg, 3, "Piena", COLOR_FULL) + self._legend(leg, 5, "Doppia UDC", COLOR_DOUBLE) + + def _legend(self, parent, col, text, 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) + ctk.CTkLabel(parent, text=text).grid(row=0, column=col + 1, padx=(4, 12)) + + # ---------------- DATA LOADING ---------------- + def _load_corsie(self): + sql = """ + WITH C AS ( + SELECT DISTINCT LTRIM(RTRIM(Corsia)) AS Corsia + FROM dbo.Celle + WHERE ID <> 9999 AND (DelDataOra IS NULL) AND LTRIM(RTRIM(Corsia)) <> '7G' + ) + SELECT Corsia + FROM C + ORDER BY + CASE + WHEN LEFT(Corsia,3)='MAG' AND TRY_CONVERT(int, SUBSTRING(Corsia,4,50)) IS NOT NULL THEN 0 + WHEN TRY_CONVERT(int, Corsia) IS NOT NULL THEN 1 + ELSE 2 + END, + CASE WHEN LEFT(Corsia,3)='MAG' THEN TRY_CONVERT(int, SUBSTRING(Corsia,4,50)) END, + CASE WHEN TRY_CONVERT(int, Corsia) IS NOT NULL THEN TRY_CONVERT(int, Corsia) END, + CASE WHEN TRY_CONVERT(int, Corsia) IS NOT NULL THEN SUBSTRING(Corsia, LEN(CAST(TRY_CONVERT(int, Corsia) AS varchar(20)))+1, 50) END, + Corsia; + """ + def _ok(res): + if not getattr(self, '_alive', True) or not self.winfo_exists(): + return + rows = res.get("rows", []) if isinstance(res, dict) else [] + self.lb.delete(0, tk.END) + corsie = [r[0] for r in rows] + for c in corsie: + self.lb.insert(tk.END, c) + idx = corsie.index("1A") if "1A" in corsie else (0 if corsie else -1) + if idx >= 0: + self.lb.selection_clear(0, tk.END) + self.lb.selection_set(idx) + self.lb.see(idx) + self._on_select(None) + else: + self._toast("Nessuna corsia trovata.") + self._busy.hide() + def _err(ex): + if not getattr(self, '_alive', True) or not self.winfo_exists(): + return + self._busy.hide() + messagebox.showerror("Errore", f"Caricamento corsie fallito:\n{ex}") + self._async.run(self.db.query_json(sql, {}), _ok, _err, busy=self._busy, message="Carico corsie…") + + def _on_select(self, _): + sel = self.lb.curselection() + if not sel: + return + corsia = self.lb.get(sel[0]) + self.corsia_selezionata.set(corsia) + self._load_matrix(corsia) + + def _select_corsia_in_listbox(self, corsia: str): + for i in range(self.lb.size()): + if self.lb.get(i) == corsia: + self.lb.selection_clear(0, tk.END) + self.lb.selection_set(i) + self.lb.see(i) + break + + def _load_matrix(self, corsia: str): + # nuovo token richiesta → evita che risposte vecchie spazzino la UI + self._req_counter += 1 + req_id = self._req_counter + self._last_req = req_id + + sql = """ + WITH C AS ( + SELECT + ID, + LTRIM(RTRIM(Corsia)) AS Corsia, + LTRIM(RTRIM(Fila)) AS Fila, + LTRIM(RTRIM(Colonna)) AS Colonna, + Descrizione + FROM dbo.Celle + WHERE ID <> 9999 AND (DelDataOra IS NULL) + AND LTRIM(RTRIM(Corsia)) <> '7G' AND LTRIM(RTRIM(Corsia)) = :corsia + ), + R AS ( + SELECT Fila, + DENSE_RANK() OVER ( + ORDER BY CASE WHEN TRY_CONVERT(int, Fila) IS NULL THEN 1 ELSE 0 END, + TRY_CONVERT(int, Fila), Fila + ) AS RowN + FROM C GROUP BY Fila + ), + K AS ( + SELECT Colonna, + DENSE_RANK() OVER ( + ORDER BY CASE WHEN TRY_CONVERT(int, Colonna) IS NULL THEN 1 ELSE 0 END, + TRY_CONVERT(int, Colonna), Colonna + ) AS ColN + FROM C GROUP BY Colonna + ), + S AS ( + SELECT c.ID, COUNT(DISTINCT g.BarcodePallet) AS n + FROM C AS c + LEFT JOIN dbo.XMag_GiacenzaPallet AS g ON g.IDCella = c.ID + GROUP BY c.ID + ), + U AS ( + SELECT c.ID, MIN(g.BarcodePallet) AS FirstUDC + FROM C c + LEFT JOIN dbo.XMag_GiacenzaPallet g ON g.IDCella = c.ID + GROUP BY c.ID + ) + SELECT + r.RowN, k.ColN, + CASE WHEN s.n IS NULL OR s.n = 0 THEN 0 + WHEN s.n = 1 THEN 1 + ELSE 2 END AS Stato, + c.Descrizione, + LTRIM(RTRIM(c.Fila)) AS FilaTxt, + LTRIM(RTRIM(c.Colonna)) AS ColTxt, + U.FirstUDC + FROM C c + JOIN R r ON r.Fila = c.Fila + JOIN K k ON k.Colonna = c.Colonna + LEFT JOIN S s ON s.ID = c.ID + LEFT JOIN U ON U.ID = c.ID + ORDER BY r.RowN, k.ColN; + """ + def _ok(res): + if not getattr(self, '_alive', True) or not self.winfo_exists(): + return + # ignora risposte superate + if req_id < self._last_req: + return + rows = res.get("rows", []) if isinstance(res, dict) else [] + if not rows: + # mostra matrice vuota senza rimuovere il frame (evita "schermo bianco") + self._rebuild_matrix(0, 0, [], [], [], [], [], corsia) + self._refresh_stats() + self._busy.hide() + return + max_r = max_c = 0 + for row in rows: + rown, coln = row[0], row[1] + if rown and coln: + max_r = max(max_r, int(rown)) + max_c = max(max_c, int(coln)) + mat = [[0] * max_c for _ in range(max_r)] + fila = [[""] * max_c for _ in range(max_r)] + col = [[""] * max_c for _ in range(max_r)] + desc = [[""] * max_c for _ in range(max_r)] + udc = [[""] * max_c for _ in range(max_r)] + for row in rows: + rown, coln, stato, descr, fila_txt, col_txt, first_udc = row + r = int(rown) - 1 + c = int(coln) - 1 + mat[r][c] = int(stato) + fila[r][c] = str(fila_txt or "") + col[r][c] = str(col_txt or "") + desc[r][c] = str(descr or f"{corsia}.{col_txt}.{fila_txt}") + udc[r][c] = str(first_udc or "") + self._rebuild_matrix(max_r, max_c, mat, fila, col, desc, udc, corsia) + self._refresh_stats() + self._busy.hide() + def _err(ex): + if not getattr(self, '_alive', True) or not self.winfo_exists(): + return + if req_id < self._last_req: + return + self._busy.hide() + messagebox.showerror("Errore", f"Caricamento matrice {corsia} fallito:\n{ex}") + self._async.run(self.db.query_json(sql, {"corsia": corsia}), _ok, _err, busy=self._busy, message=f"Carico corsia {corsia}…") + + # ---------------- SEARCH ---------------- + def _search_udc(self): + barcode = (self.search_var.get() or "").strip() + if not barcode: + self._toast("Inserisci un barcode UDC da cercare.") + return + + # bump token per impedire che una vecchia _load_matrix cancelli UI + self._req_counter += 1 + search_req_id = self._req_counter + self._last_req = search_req_id + + sql = """ + SELECT TOP (1) + RTRIM(c.Corsia) AS Corsia, + RTRIM(c.Colonna) AS Colonna, + RTRIM(c.Fila) AS Fila, + c.ID AS IDCella + FROM dbo.XMag_GiacenzaPallet g + JOIN dbo.Celle c ON c.ID = g.IDCella + WHERE g.BarcodePallet = :barcode + AND c.ID <> 9999 AND RTRIM(c.Corsia) <> '7G' + """ + def _ok(res): + if not getattr(self, '_alive', True) or not self.winfo_exists(): + return + if search_req_id < self._last_req: + return + rows = res.get("rows", []) if isinstance(res, dict) else [] + if not rows: + messagebox.showinfo("Ricerca", f"UDC {barcode} non trovata.", parent=self) + return + corsia, col, fila, _idc = rows[0] + corsia = str(corsia).strip(); col = str(col).strip(); fila = str(fila).strip() + self._pending_focus = (corsia, col, fila, barcode) + + # sincronizza listbox e carica SEMPRE la corsia della UDC + self._select_corsia_in_listbox(corsia) + self.corsia_selezionata.set(corsia) + self._load_matrix(corsia) # highlight avverrà in _rebuild_matrix + def _err(ex): + if not getattr(self, '_alive', True) or not self.winfo_exists(): + return + if search_req_id < self._last_req: + return + messagebox.showerror("Ricerca", f"Errore ricerca UDC:\n{ex}", parent=self) + + 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: + 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: + self._clear_highlight() + btn = self.buttons[r][c] + btn.configure(border_width=3, border_color="blue") + try: + fr = self.btn_frames[r][c] + fr.configure(border_color="blue", border_width=2) + except Exception: + pass + self._highlighted = (r, c) + return True + return False + + def _highlight_cell_by_labels(self, col_txt: str, fila_txt: str): + if not self._try_highlight(col_txt, fila_txt): + self._toast("Cella trovata ma non mappabile a pulsante.") + + # ---------------- COMMANDS ---------------- + def _refresh_current(self): + if self.corsia_selezionata.get(): + self._load_matrix(self.corsia_selezionata.get()) + + def _export_xlsx(self): + if not self.matrix_state: + messagebox.showinfo("Export", "Nessuna matrice da esportare.") + return + corsia = self.corsia_selezionata.get() or "NA" + ts = datetime.now().strftime("%d_%m_%Y_%H-%M") + default = f"layout_matrice_{corsia}_{ts}.xlsx" + path = filedialog.asksaveasfilename( + title="Esporta matrice", + defaultextension=".xlsx", + initialfile=default, + filetypes=[("Excel", "*.xlsx")] + ) + if not path: + return + try: + from openpyxl import Workbook + from openpyxl.styles import PatternFill, Alignment, Font + except Exception as ex: + messagebox.showerror("Export", f"Manca openpyxl: {ex}\nInstalla con: pip install openpyxl") + return + rows = len(self.matrix_state) + cols = len(self.matrix_state[0]) if self.matrix_state else 0 + wb = Workbook() + ws1 = wb.active + ws1.title = f"Dettaglio {corsia}" + ws1.append(["Corsia", "FilaIdx", "ColIdx", "Stato", "Descrizione", "FilaTxt", "ColTxt", "UDC1"]) + for r in range(rows): + for c in range(cols): + st = self.matrix_state[r][c] + stato_lbl = "Vuota" if st == 0 else ("Piena" if st == 1 else "Doppia") + ws1.append([corsia, r + 1, c + 1, stato_lbl, + self.desc[r][c], self.fila_txt[r][c], self.col_txt[r][c], self.udc1[r][c]]) + for cell in ws1[1]: + cell.font = Font(bold=True) + + ws2 = wb.create_sheet(f"Matrice {corsia}") + fills = { + 0: PatternFill("solid", fgColor="B0B0B0"), + 1: PatternFill("solid", fgColor="FFA500"), + 2: PatternFill("solid", fgColor="D62728"), + } + center = Alignment(horizontal="center", vertical="center", wrap_text=True) + for r in range(rows): + for c in range(cols): + value = f"{corsia}.{self.col_txt[r][c]}.{self.fila_txt[r][c]}\n{self.udc1[r][c]}" + cell = ws2.cell(row=(rows - r), column=c + 1, value=value) # capovolto per avere 'a' in basso + cell.fill = fills.get(self.matrix_state[r][c], fills[0]) + cell.alignment = center + try: + wb.save(path) + self._toast(f"Esportato: {path}") + except Exception as ex: + messagebox.showerror("Export", f"Salvataggio fallito:\n{ex}") + + # ---------------- STATS ---------------- + def _refresh_stats(self): + # globale dal DB + sql_tot = """ + WITH C AS ( + SELECT ID + FROM dbo.Celle + WHERE ID <> 9999 AND (DelDataOra IS NULL) + AND LTRIM(RTRIM(Corsia)) <> '7G' + AND LTRIM(RTRIM(Fila)) IS NOT NULL + AND LTRIM(RTRIM(Colonna)) IS NOT NULL + ), + S AS ( + SELECT c.ID, COUNT(DISTINCT g.BarcodePallet) AS n + FROM C AS c LEFT JOIN dbo.XMag_GiacenzaPallet AS g ON g.IDCella = c.ID + GROUP BY c.ID + ) + SELECT + CAST(SUM(CASE WHEN s.n>0 THEN 1 ELSE 0 END) AS float)/NULLIF(COUNT(*),0) AS PercPieno, + CAST(SUM(CASE WHEN s.n>1 THEN 1 ELSE 0 END) AS float)/NULLIF(COUNT(*),0) AS PercDoppie + FROM C LEFT JOIN S s ON s.ID = C.ID; + """ + def _ok(res): + if not getattr(self, '_alive', True) or not self.winfo_exists(): + return + rows = res.get("rows", []) if isinstance(res, dict) else [] + p_full = float(rows[0][0] or 0.0) if rows else 0.0 + p_dbl = float(rows[0][1] or 0.0) if rows else 0.0 + self._draw_bar(self.tot_canvas, p_full) + self.tot_text.configure(text=pct_text(p_full, p_dbl)) + self._async.run(self.db.query_json(sql_tot, {}), _ok, lambda e: None, busy=None, message=None) + + # selezionata dalla matrice in memoria + if self.matrix_state: + tot = sum(len(r) for r in self.matrix_state) + full = sum(1 for row in self.matrix_state for v in row if v in (1, 2)) + doubles = sum(1 for row in self.matrix_state for v in row if v == 2) + p_full = (full / tot) if tot else 0.0 + p_dbl = (doubles / tot) if tot else 0.0 + else: + p_full = p_dbl = 0.0 + self._draw_bar(self.sel_canvas, p_full) + self.sel_text.configure(text=pct_text(p_full, p_dbl)) + + def _draw_bar(self, cv: tk.Canvas, p_full: float): + cv.delete("all") + w = max(300, cv.winfo_width() or 600) + h = 18 + fw = int(w * max(0.0, min(1.0, p_full))) + cv.create_rectangle(2, 2, 2 + fw, 2 + h, fill="#D62728", width=0) + cv.create_rectangle(2 + fw, 2, 2 + w, 2 + h, fill="#2CA02C", width=0) + cv.create_rectangle(2, 2, 2 + w, 2 + h, outline="#555", width=1) + + # ---------------- UTIL ---------------- + def _toast(self, msg, ms=1400): + if not hasattr(self, "_status"): + self._status = ctk.CTkLabel(self, anchor="w") + self._status.grid(row=3, column=0, sticky="ew") + self._status.configure(text=msg) + self.after(ms, lambda: self._status.configure(text="")) + + def _copy(self, txt: str): + self.clipboard_clear() + self.clipboard_append(txt) + self._toast(f"Copiato: {txt}") + + + + def destroy(self): + # evita nuovi refresh/async dopo destroy + self._alive = False + # cancella eventuali timer + try: + if self._stats_after_id is not None: + self.after_cancel(self._stats_after_id) + except Exception: + pass + # pulizia UI leggera + try: + for w in list(self.host.winfo_children()): + w.destroy() + except Exception: + pass + try: + super().destroy() + except Exception: + pass + +def open_layout_window(parent, db_app): + key = "_layout_window_singleton" + ex = getattr(parent, key, None) + if ex and ex.winfo_exists(): + try: + ex.lift() + ex.focus_force() + return ex + except Exception: + pass + w = LayoutWindow(parent, db_app) + setattr(parent, key, w) + return w diff --git a/layout_window.py.bak_fix_bc_transparent b/layout_window.py.bak_fix_bc_transparent new file mode 100644 index 0000000..bdf1fc7 --- /dev/null +++ b/layout_window.py.bak_fix_bc_transparent @@ -0,0 +1,633 @@ +from __future__ import annotations +import tkinter as tk +from tkinter import Menu, messagebox, filedialog +import customtkinter as ctk +from datetime import datetime + +from gestione_aree_frame_async import BusyOverlay, AsyncRunner + +# ---- Color palette ---- +COLOR_EMPTY = "#B0B0B0" # grigio (vuota) +COLOR_FULL = "#FFA500" # arancione (una UDC) +COLOR_DOUBLE = "#D62728" # rosso (>=2 UDC) +FG_DARK = "#111111" +FG_LIGHT = "#FFFFFF" + + +def pct_text(p_full: float, p_double: float | None = None) -> str: + p_full = max(0.0, min(1.0, p_full)) + pf = round(p_full * 100, 1) + pe = round(100 - pf, 1) + if p_double and p_double > 0: + pd = round(p_double * 100, 1) + return f"Pieno {pf}% · Vuoto {pe}% (di cui doppie {pd}%)" + return f"Pieno {pf}% · Vuoto {pe}%" + + +class LayoutWindow(ctk.CTkToplevel): + """ + Visualizzazione layout corsie con matrice di celle. + - Ogni cella è un pulsante colorato (vuota/piena/doppia) + - Etichetta su DUE righe: + 1) "Corsia.Colonna.Fila" (una sola riga, senza andare a capo) + 2) barcode UDC (primo, se presente) + - Ricerca per barcode UDC con cambio automatico corsia + highlight cella + - Statistiche: globale e corsia selezionata + - Export XLSX + """ + def __init__(self, parent: tk.Widget, db_app): + super().__init__(parent) + self.title("Warehouse · Layout corsie") + self.geometry("1200x740") + self.minsize(980, 560) + self.resizable(True, True) + + self.db = db_app + self._busy = BusyOverlay(self) + self._async = AsyncRunner(self) + + # layout principale 5% / 80% / 15% + self.grid_rowconfigure(0, weight=5) + self.grid_rowconfigure(1, weight=80) + self.grid_rowconfigure(2, weight=15) + self.grid_columnconfigure(0, weight=1) + + # stato runtime + self.corsia_selezionata = tk.StringVar() + self.buttons: list[list[ctk.CTkButton]] = [] + self.btn_frames: list[list[ctk.CTkFrame]] = [] + self.matrix_state: list[list[int]] = [] # <— rinominata: prima era self.state + self.fila_txt: list[list[str]] = [] + self.col_txt: list[list[str]] = [] + self.desc: list[list[str]] = [] + self.udc1: list[list[str]] = [] # primo barcode UDC trovato (o "") + + # ricerca → focus differito (corsia, col, fila, barcode) + self._pending_focus: tuple[str, str, str, str] | None = None + self._highlighted: tuple[int, int] | None = None + + # anti-race: token per ignorare risposte vecchie + self._req_counter = 0 + self._last_req = 0 + + self._build_top() + self._build_matrix_host() + self._build_stats() + + self._load_corsie() + self.bind("", lambda e: self.after_idle(self._refresh_stats)) + + # ---------------- TOP BAR ---------------- + def _build_top(self): + top = ctk.CTkFrame(self) + top.grid(row=0, column=0, sticky="nsew", padx=8, pady=6) + for i in range(4): + top.grid_columnconfigure(i, weight=0) + top.grid_columnconfigure(1, weight=1) + + # lista corsie + lf = ctk.CTkFrame(top) + lf.grid(row=0, column=0, sticky="nsw") + lf.grid_columnconfigure(0, weight=1) + ctk.CTkLabel(lf, text="Corsie", font=("", 12, "bold")).grid(row=0, column=0, sticky="w", padx=6, pady=(6, 2)) + self.lb = tk.Listbox(lf, height=6, exportselection=False) + self.lb.grid(row=1, column=0, sticky="nsw", padx=6, pady=(0, 6)) + self.lb.bind("<>", self._on_select) + + # search by barcode + srch = ctk.CTkFrame(top) + srch.grid(row=0, column=1, sticky="nsew", padx=(10, 10)) + self.search_var = tk.StringVar() + self.search_entry = ctk.CTkEntry(srch, textvariable=self.search_var, width=260) + self.search_entry.grid(row=0, column=0, sticky="w") + ctk.CTkButton(srch, text="Cerca per barcode UDC", command=self._search_udc).grid(row=0, column=1, padx=(8, 0)) + srch.grid_columnconfigure(0, weight=1) + + # toolbar + tb = ctk.CTkFrame(top) + tb.grid(row=0, column=3, sticky="ne") + ctk.CTkButton(tb, text="Aggiorna", command=self._refresh_current).grid(row=0, column=0, padx=4) + ctk.CTkButton(tb, text="Export XLSX", command=self._export_xlsx).grid(row=0, column=1, padx=4) + + # ---------------- MATRIX HOST ---------------- + def _build_matrix_host(self): + center = ctk.CTkFrame(self) + center.grid(row=1, column=0, sticky="nsew", padx=8, pady=(0, 6)) + center.grid_rowconfigure(0, weight=1) + center.grid_columnconfigure(0, weight=1) + self.host = ctk.CTkFrame(center) + self.host.grid(row=0, column=0, sticky="nsew", padx=4, pady=4) + + def _apply_cell_style(self, btn: ctk.CTkButton, state: int): + if state == 0: + btn.configure( + fg_color=COLOR_EMPTY, hover_color="#9A9A9A", + text_color=FG_DARK, border_width=0, border_color="transparent" + ) + elif state == 1: + btn.configure( + fg_color=COLOR_FULL, hover_color="#E69500", + text_color=FG_DARK, border_width=0, border_color="transparent" + ) + else: + btn.configure( + fg_color=COLOR_DOUBLE, hover_color="#B22222", + text_color=FG_LIGHT, border_width=0, border_color="transparent" + ) + + def _clear_highlight(self): + if self._highlighted and self.buttons: + r, c = self._highlighted + try: + if 0 <= r < len(self.buttons) and 0 <= c < len(self.buttons[r]): + btn = self.buttons[r][c] + if getattr(btn, "winfo_exists", None) and btn.winfo_exists(): + try: + btn.configure(border_width=0, border_color="transparent") + except Exception: + pass + # clear blue frame border + try: + fr = self.btn_frames[r][c] + if fr and getattr(fr, "winfo_exists", None) and fr.winfo_exists(): + fr.configure(border_width=0, border_color="transparent") + # in CTkFrame non esiste highlightthickness come in tk; border_* è corretto + except Exception: + pass + except Exception: + pass + self._highlighted = None + + def _rebuild_matrix(self, rows: int, cols: int, state, fila_txt, col_txt, desc, udc1, corsia): + # prima rimuovi highlight su vecchi bottoni + self._clear_highlight() + # ripulisci host + for w in self.host.winfo_children(): + w.destroy() + self.buttons.clear() + self.btn_frames.clear() + + # salva matrici + self.matrix_state, self.fila_txt, self.col_txt, self.desc, self.udc1 = state, fila_txt, col_txt, desc, udc1 + + # ridistribuisci pesi griglia + for r in range(rows): + self.host.grid_rowconfigure(r, weight=1) + for c in range(cols): + self.host.grid_columnconfigure(c, weight=1) + + # crea Frame+Button per cella (righe invertite: fila "a" in basso) + for r in range(rows): + row_btns = [] + row_frames = [] + for c in range(cols): + st = state[r][c] + code = f"{corsia}.{col_txt[r][c]}.{fila_txt[r][c]}" # PRIMA RIGA (in linea) + udc = udc1[r][c] or "" # SECONDA RIGA: barcode UDC + text = f"{code}\n{udc}" + cell = ctk.CTkFrame(self.host, corner_radius=6, border_width=0) + btn = ctk.CTkButton( + cell, + text=text, + corner_radius=6, + ) + self._apply_cell_style(btn, st) + + rr = (rows - 1) - r # capovolgi + cell.grid(row=rr, column=c, padx=1, pady=1, sticky="nsew") + btn.pack(fill="both", expand=True) + btn.configure(command=lambda rr=r, cc=c: self._open_menu(None, rr, cc)) + btn.bind("", lambda e, rr=r, cc=c: self._open_menu(e, rr, cc)) + row_btns.append(btn) + row_frames.append(cell) + self.buttons.append(row_btns) + self.btn_frames.append(row_frames) + + # focus differito post-ricarica + if self._pending_focus and self._pending_focus[0] == corsia: + _, col, fila, _barcode = self._pending_focus + self._pending_focus = None + self._highlight_cell_by_labels(col, fila) + + # ---------------- CONTEXT MENU ---------------- + def _open_menu(self, event, r, c): + st = self.matrix_state[r][c] + corsia = self.corsia_selezionata.get() + label = f"{corsia}.{self.col_txt[r][c]}.{self.fila_txt[r][c]}" + m = Menu(self, tearoff=0) + m.add_command(label="Apri dettaglio", command=lambda: self._toast(f"Dettaglio {label}")) + if st == 0: + m.add_command(label="Segna pieno", command=lambda: self._set_cell(r, c, 1)) + m.add_command(label="Segna doppia", command=lambda: self._set_cell(r, c, 2)) + elif st == 1: + m.add_command(label="Segna vuoto", command=lambda: self._set_cell(r, c, 0)) + m.add_command(label="Segna doppia", command=lambda: self._set_cell(r, c, 2)) + else: + m.add_command(label="Segna vuoto", command=lambda: self._set_cell(r, c, 0)) + m.add_command(label="Segna pieno", command=lambda: self._set_cell(r, c, 1)) + m.add_separator() + m.add_command(label="Copia ubicazione", command=lambda: self._copy(label)) + x = self.winfo_pointerx() if event is None else event.x_root + y = self.winfo_pointery() if event is None else event.y_root + m.tk_popup(x, y) + + def _set_cell(self, r, c, val): + self.matrix_state[r][c] = val + btn = self.buttons[r][c] + self._apply_cell_style(btn, val) + self._refresh_stats() + + # ---------------- STATS ---------------- + def _build_stats(self): + bottom = ctk.CTkFrame(self) + bottom.grid(row=2, column=0, sticky="nsew", padx=8, pady=6) + bottom.grid_columnconfigure(0, weight=1) + + ctk.CTkLabel(bottom, text="Riempimento globale", font=("", 10, "bold")).grid(row=0, column=0, sticky="w", pady=(0, 2)) + self.tot_canvas = tk.Canvas(bottom, height=22, highlightthickness=0) + self.tot_canvas.grid(row=1, column=0, sticky="ew", padx=(0, 260)) + self.tot_text = ctk.CTkLabel(bottom, text=pct_text(0.0, 0.0)) + self.tot_text.grid(row=1, column=0, sticky="e") + + ctk.CTkLabel(bottom, text="Riempimento corsia selezionata", font=("", 10, "bold")).grid(row=2, column=0, sticky="w", pady=(10, 2)) + self.sel_canvas = tk.Canvas(bottom, height=22, highlightthickness=0) + self.sel_canvas.grid(row=3, column=0, sticky="ew", padx=(0, 260)) + self.sel_text = ctk.CTkLabel(bottom, text=pct_text(0.0, 0.0)) + self.sel_text.grid(row=3, column=0, sticky="e") + + leg = ctk.CTkFrame(bottom) + leg.grid(row=4, column=0, sticky="w", pady=(10, 0)) + ctk.CTkLabel(leg, text="Legenda celle:").grid(row=0, column=0, padx=(0, 8)) + self._legend(leg, 1, "Vuota", COLOR_EMPTY) + self._legend(leg, 3, "Piena", COLOR_FULL) + self._legend(leg, 5, "Doppia UDC", COLOR_DOUBLE) + + def _legend(self, parent, col, text, 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) + ctk.CTkLabel(parent, text=text).grid(row=0, column=col + 1, padx=(4, 12)) + + # ---------------- DATA LOADING ---------------- + def _load_corsie(self): + sql = """ + WITH C AS ( + SELECT DISTINCT LTRIM(RTRIM(Corsia)) AS Corsia + FROM dbo.Celle + WHERE ID <> 9999 AND (DelDataOra IS NULL) AND LTRIM(RTRIM(Corsia)) <> '7G' + ) + SELECT Corsia + FROM C + ORDER BY + CASE + WHEN LEFT(Corsia,3)='MAG' AND TRY_CONVERT(int, SUBSTRING(Corsia,4,50)) IS NOT NULL THEN 0 + WHEN TRY_CONVERT(int, Corsia) IS NOT NULL THEN 1 + ELSE 2 + END, + CASE WHEN LEFT(Corsia,3)='MAG' THEN TRY_CONVERT(int, SUBSTRING(Corsia,4,50)) END, + CASE WHEN TRY_CONVERT(int, Corsia) IS NOT NULL THEN TRY_CONVERT(int, Corsia) END, + CASE WHEN TRY_CONVERT(int, Corsia) IS NOT NULL THEN SUBSTRING(Corsia, LEN(CAST(TRY_CONVERT(int, Corsia) AS varchar(20)))+1, 50) END, + Corsia; + """ + def _ok(res): + rows = res.get("rows", []) if isinstance(res, dict) else [] + self.lb.delete(0, tk.END) + corsie = [r[0] for r in rows] + for c in corsie: + self.lb.insert(tk.END, c) + idx = corsie.index("1A") if "1A" in corsie else (0 if corsie else -1) + if idx >= 0: + self.lb.selection_clear(0, tk.END) + self.lb.selection_set(idx) + self.lb.see(idx) + self._on_select(None) + else: + self._toast("Nessuna corsia trovata.") + self._busy.hide() + def _err(ex): + self._busy.hide() + messagebox.showerror("Errore", f"Caricamento corsie fallito:\n{ex}") + self._async.run(self.db.query_json(sql, {}), _ok, _err, busy=self._busy, message="Carico corsie…") + + def _on_select(self, _): + sel = self.lb.curselection() + if not sel: + return + corsia = self.lb.get(sel[0]) + self.corsia_selezionata.set(corsia) + self._load_matrix(corsia) + + def _select_corsia_in_listbox(self, corsia: str): + for i in range(self.lb.size()): + if self.lb.get(i) == corsia: + self.lb.selection_clear(0, tk.END) + self.lb.selection_set(i) + self.lb.see(i) + break + + def _load_matrix(self, corsia: str): + # nuovo token richiesta → evita che risposte vecchie spazzino la UI + self._req_counter += 1 + req_id = self._req_counter + self._last_req = req_id + + sql = """ + WITH C AS ( + SELECT + ID, + LTRIM(RTRIM(Corsia)) AS Corsia, + LTRIM(RTRIM(Fila)) AS Fila, + LTRIM(RTRIM(Colonna)) AS Colonna, + Descrizione + FROM dbo.Celle + WHERE ID <> 9999 AND (DelDataOra IS NULL) + AND LTRIM(RTRIM(Corsia)) <> '7G' AND LTRIM(RTRIM(Corsia)) = :corsia + ), + R AS ( + SELECT Fila, + DENSE_RANK() OVER ( + ORDER BY CASE WHEN TRY_CONVERT(int, Fila) IS NULL THEN 1 ELSE 0 END, + TRY_CONVERT(int, Fila), Fila + ) AS RowN + FROM C GROUP BY Fila + ), + K AS ( + SELECT Colonna, + DENSE_RANK() OVER ( + ORDER BY CASE WHEN TRY_CONVERT(int, Colonna) IS NULL THEN 1 ELSE 0 END, + TRY_CONVERT(int, Colonna), Colonna + ) AS ColN + FROM C GROUP BY Colonna + ), + S AS ( + SELECT c.ID, COUNT(DISTINCT g.BarcodePallet) AS n + FROM C AS c + LEFT JOIN dbo.XMag_GiacenzaPallet AS g ON g.IDCella = c.ID + GROUP BY c.ID + ), + U AS ( + SELECT c.ID, MIN(g.BarcodePallet) AS FirstUDC + FROM C c + LEFT JOIN dbo.XMag_GiacenzaPallet g ON g.IDCella = c.ID + GROUP BY c.ID + ) + SELECT + r.RowN, k.ColN, + CASE WHEN s.n IS NULL OR s.n = 0 THEN 0 + WHEN s.n = 1 THEN 1 + ELSE 2 END AS Stato, + c.Descrizione, + LTRIM(RTRIM(c.Fila)) AS FilaTxt, + LTRIM(RTRIM(c.Colonna)) AS ColTxt, + U.FirstUDC + FROM C c + JOIN R r ON r.Fila = c.Fila + JOIN K k ON k.Colonna = c.Colonna + LEFT JOIN S s ON s.ID = c.ID + LEFT JOIN U ON U.ID = c.ID + ORDER BY r.RowN, k.ColN; + """ + def _ok(res): + # ignora risposte superate + if req_id < self._last_req: + return + rows = res.get("rows", []) if isinstance(res, dict) else [] + if not rows: + # mostra matrice vuota senza rimuovere il frame (evita "schermo bianco") + self._rebuild_matrix(0, 0, [], [], [], [], [], corsia) + self._refresh_stats() + self._busy.hide() + return + max_r = max_c = 0 + for row in rows: + rown, coln = row[0], row[1] + if rown and coln: + max_r = max(max_r, int(rown)) + max_c = max(max_c, int(coln)) + mat = [[0] * max_c for _ in range(max_r)] + fila = [[""] * max_c for _ in range(max_r)] + col = [[""] * max_c for _ in range(max_r)] + desc = [[""] * max_c for _ in range(max_r)] + udc = [[""] * max_c for _ in range(max_r)] + for row in rows: + rown, coln, stato, descr, fila_txt, col_txt, first_udc = row + r = int(rown) - 1 + c = int(coln) - 1 + mat[r][c] = int(stato) + fila[r][c] = str(fila_txt or "") + col[r][c] = str(col_txt or "") + desc[r][c] = str(descr or f"{corsia}.{col_txt}.{fila_txt}") + udc[r][c] = str(first_udc or "") + self._rebuild_matrix(max_r, max_c, mat, fila, col, desc, udc, corsia) + self._refresh_stats() + self._busy.hide() + def _err(ex): + if req_id < self._last_req: + return + self._busy.hide() + messagebox.showerror("Errore", f"Caricamento matrice {corsia} fallito:\n{ex}") + self._async.run(self.db.query_json(sql, {"corsia": corsia}), _ok, _err, busy=self._busy, message=f"Carico corsia {corsia}…") + + # ---------------- SEARCH ---------------- + def _search_udc(self): + barcode = (self.search_var.get() or "").strip() + if not barcode: + self._toast("Inserisci un barcode UDC da cercare.") + return + + # bump token per impedire che una vecchia _load_matrix cancelli UI + self._req_counter += 1 + search_req_id = self._req_counter + self._last_req = search_req_id + + sql = """ + SELECT TOP (1) + RTRIM(c.Corsia) AS Corsia, + RTRIM(c.Colonna) AS Colonna, + RTRIM(c.Fila) AS Fila, + c.ID AS IDCella + FROM dbo.XMag_GiacenzaPallet g + JOIN dbo.Celle c ON c.ID = g.IDCella + WHERE g.BarcodePallet = :barcode + AND c.ID <> 9999 AND RTRIM(c.Corsia) <> '7G' + """ + def _ok(res): + if search_req_id < self._last_req: + return + rows = res.get("rows", []) if isinstance(res, dict) else [] + if not rows: + messagebox.showinfo("Ricerca", f"UDC {barcode} non trovata.", parent=self) + return + corsia, col, fila, _idc = rows[0] + corsia = str(corsia).strip(); col = str(col).strip(); fila = str(fila).strip() + self._pending_focus = (corsia, col, fila, barcode) + + # sincronizza listbox e carica SEMPRE la corsia della UDC + self._select_corsia_in_listbox(corsia) + self.corsia_selezionata.set(corsia) + self._load_matrix(corsia) # highlight avverrà in _rebuild_matrix + def _err(ex): + if search_req_id < self._last_req: + return + messagebox.showerror("Ricerca", f"Errore ricerca UDC:\n{ex}", parent=self) + + 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: + 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: + self._clear_highlight() + btn = self.buttons[r][c] + btn.configure(border_width=3, border_color="blue") + try: + fr = self.btn_frames[r][c] + fr.configure(border_color="blue", border_width=2) + except Exception: + pass + self._highlighted = (r, c) + return True + return False + + def _highlight_cell_by_labels(self, col_txt: str, fila_txt: str): + if not self._try_highlight(col_txt, fila_txt): + self._toast("Cella trovata ma non mappabile a pulsante.") + + # ---------------- COMMANDS ---------------- + def _refresh_current(self): + if self.corsia_selezionata.get(): + self._load_matrix(self.corsia_selezionata.get()) + + def _export_xlsx(self): + if not self.matrix_state: + messagebox.showinfo("Export", "Nessuna matrice da esportare.") + return + corsia = self.corsia_selezionata.get() or "NA" + ts = datetime.now().strftime("%d_%m_%Y_%H-%M") + default = f"layout_matrice_{corsia}_{ts}.xlsx" + path = filedialog.asksaveasfilename( + title="Esporta matrice", + defaultextension=".xlsx", + initialfile=default, + filetypes=[("Excel", "*.xlsx")] + ) + if not path: + return + try: + from openpyxl import Workbook + from openpyxl.styles import PatternFill, Alignment, Font + except Exception as ex: + messagebox.showerror("Export", f"Manca openpyxl: {ex}\nInstalla con: pip install openpyxl") + return + rows = len(self.matrix_state) + cols = len(self.matrix_state[0]) if self.matrix_state else 0 + wb = Workbook() + ws1 = wb.active + ws1.title = f"Dettaglio {corsia}" + ws1.append(["Corsia", "FilaIdx", "ColIdx", "Stato", "Descrizione", "FilaTxt", "ColTxt", "UDC1"]) + for r in range(rows): + for c in range(cols): + st = self.matrix_state[r][c] + stato_lbl = "Vuota" if st == 0 else ("Piena" if st == 1 else "Doppia") + ws1.append([corsia, r + 1, c + 1, stato_lbl, + self.desc[r][c], self.fila_txt[r][c], self.col_txt[r][c], self.udc1[r][c]]) + for cell in ws1[1]: + cell.font = Font(bold=True) + + ws2 = wb.create_sheet(f"Matrice {corsia}") + fills = { + 0: PatternFill("solid", fgColor="B0B0B0"), + 1: PatternFill("solid", fgColor="FFA500"), + 2: PatternFill("solid", fgColor="D62728"), + } + center = Alignment(horizontal="center", vertical="center", wrap_text=True) + for r in range(rows): + for c in range(cols): + value = f"{corsia}.{self.col_txt[r][c]}.{self.fila_txt[r][c]}\n{self.udc1[r][c]}" + cell = ws2.cell(row=(rows - r), column=c + 1, value=value) # capovolto per avere 'a' in basso + cell.fill = fills.get(self.matrix_state[r][c], fills[0]) + cell.alignment = center + try: + wb.save(path) + self._toast(f"Esportato: {path}") + except Exception as ex: + messagebox.showerror("Export", f"Salvataggio fallito:\n{ex}") + + # ---------------- STATS ---------------- + def _refresh_stats(self): + # globale dal DB + sql_tot = """ + WITH C AS ( + SELECT ID + FROM dbo.Celle + WHERE ID <> 9999 AND (DelDataOra IS NULL) + AND LTRIM(RTRIM(Corsia)) <> '7G' + AND LTRIM(RTRIM(Fila)) IS NOT NULL + AND LTRIM(RTRIM(Colonna)) IS NOT NULL + ), + S AS ( + SELECT c.ID, COUNT(DISTINCT g.BarcodePallet) AS n + FROM C AS c LEFT JOIN dbo.XMag_GiacenzaPallet AS g ON g.IDCella = c.ID + GROUP BY c.ID + ) + SELECT + CAST(SUM(CASE WHEN s.n>0 THEN 1 ELSE 0 END) AS float)/NULLIF(COUNT(*),0) AS PercPieno, + CAST(SUM(CASE WHEN s.n>1 THEN 1 ELSE 0 END) AS float)/NULLIF(COUNT(*),0) AS PercDoppie + FROM C LEFT JOIN S s ON s.ID = C.ID; + """ + def _ok(res): + rows = res.get("rows", []) if isinstance(res, dict) else [] + p_full = float(rows[0][0] or 0.0) if rows else 0.0 + p_dbl = float(rows[0][1] or 0.0) if rows else 0.0 + self._draw_bar(self.tot_canvas, p_full) + self.tot_text.configure(text=pct_text(p_full, p_dbl)) + self._async.run(self.db.query_json(sql_tot, {}), _ok, lambda e: None, busy=None, message=None) + + # selezionata dalla matrice in memoria + if self.matrix_state: + tot = sum(len(r) for r in self.matrix_state) + full = sum(1 for row in self.matrix_state for v in row if v in (1, 2)) + doubles = sum(1 for row in self.matrix_state for v in row if v == 2) + p_full = (full / tot) if tot else 0.0 + p_dbl = (doubles / tot) if tot else 0.0 + else: + p_full = p_dbl = 0.0 + self._draw_bar(self.sel_canvas, p_full) + self.sel_text.configure(text=pct_text(p_full, p_dbl)) + + def _draw_bar(self, cv: tk.Canvas, p_full: float): + cv.delete("all") + w = max(300, cv.winfo_width() or 600) + h = 18 + fw = int(w * max(0.0, min(1.0, p_full))) + cv.create_rectangle(2, 2, 2 + fw, 2 + h, fill="#D62728", width=0) + cv.create_rectangle(2 + fw, 2, 2 + w, 2 + h, fill="#2CA02C", width=0) + cv.create_rectangle(2, 2, 2 + w, 2 + h, outline="#555", width=1) + + # ---------------- UTIL ---------------- + def _toast(self, msg, ms=1400): + if not hasattr(self, "_status"): + self._status = ctk.CTkLabel(self, anchor="w") + self._status.grid(row=3, column=0, sticky="ew") + self._status.configure(text=msg) + self.after(ms, lambda: self._status.configure(text="")) + + def _copy(self, txt: str): + self.clipboard_clear() + self.clipboard_append(txt) + self._toast(f"Copiato: {txt}") + + +def open_layout_window(parent, db_app): + key = "_layout_window_singleton" + ex = getattr(parent, key, None) + if ex and ex.winfo_exists(): + try: + ex.lift() + ex.focus_force() + return ex + except Exception: + pass + w = LayoutWindow(parent, db_app) + setattr(parent, key, w) + return w diff --git a/layout_window.py.bak_perf b/layout_window.py.bak_perf new file mode 100644 index 0000000..fd94d5e --- /dev/null +++ b/layout_window.py.bak_perf @@ -0,0 +1,632 @@ +from __future__ import annotations +import tkinter as tk +from tkinter import Menu, messagebox, filedialog +import customtkinter as ctk +from datetime import datetime + +from gestione_aree_frame_async import BusyOverlay, AsyncRunner + +# ---- Color palette ---- +COLOR_EMPTY = "#B0B0B0" # grigio (vuota) +COLOR_FULL = "#FFA500" # arancione (una UDC) +COLOR_DOUBLE = "#D62728" # rosso (>=2 UDC) +FG_DARK = "#111111" +FG_LIGHT = "#FFFFFF" + + +def pct_text(p_full: float, p_double: float | None = None) -> str: + p_full = max(0.0, min(1.0, p_full)) + pf = round(p_full * 100, 1) + pe = round(100 - pf, 1) + if p_double and p_double > 0: + pd = round(p_double * 100, 1) + return f"Pieno {pf}% · Vuoto {pe}% (di cui doppie {pd}%)" + return f"Pieno {pf}% · Vuoto {pe}%" + + +class LayoutWindow(ctk.CTkToplevel): + """ + Visualizzazione layout corsie con matrice di celle. + - Ogni cella è un pulsante colorato (vuota/piena/doppia) + - Etichetta su DUE righe: + 1) "Corsia.Colonna.Fila" (una sola riga, senza andare a capo) + 2) barcode UDC (primo, se presente) + - Ricerca per barcode UDC con cambio automatico corsia + highlight cella + - Statistiche: globale e corsia selezionata + - Export XLSX + """ + def __init__(self, parent: tk.Widget, db_app): + super().__init__(parent) + self.title("Warehouse · Layout corsie") + self.geometry("1200x740") + self.minsize(980, 560) + self.resizable(True, True) + + self.db = db_app + self._busy = BusyOverlay(self) + self._async = AsyncRunner(self) + + # layout principale 5% / 80% / 15% + self.grid_rowconfigure(0, weight=5) + self.grid_rowconfigure(1, weight=80) + self.grid_rowconfigure(2, weight=15) + self.grid_columnconfigure(0, weight=1) + + # stato runtime + self.corsia_selezionata = tk.StringVar() + self.buttons: list[list[ctk.CTkButton]] = [] + self.btn_frames: list[list[ctk.CTkFrame]] = [] + self.matrix_state: list[list[int]] = [] # <— rinominata: prima era self.state + self.fila_txt: list[list[str]] = [] + self.col_txt: list[list[str]] = [] + self.desc: list[list[str]] = [] + self.udc1: list[list[str]] = [] # primo barcode UDC trovato (o "") + + # ricerca → focus differito (corsia, col, fila, barcode) + self._pending_focus: tuple[str, str, str, str] | None = None + self._highlighted: tuple[int, int] | None = None + + # anti-race: token per ignorare risposte vecchie + self._req_counter = 0 + self._last_req = 0 + + self._build_top() + self._build_matrix_host() + self._build_stats() + + self._load_corsie() + self.bind("", lambda e: self.after_idle(self._refresh_stats)) + + # ---------------- TOP BAR ---------------- + def _build_top(self): + top = ctk.CTkFrame(self) + top.grid(row=0, column=0, sticky="nsew", padx=8, pady=6) + for i in range(4): + top.grid_columnconfigure(i, weight=0) + top.grid_columnconfigure(1, weight=1) + + # lista corsie + lf = ctk.CTkFrame(top) + lf.grid(row=0, column=0, sticky="nsw") + lf.grid_columnconfigure(0, weight=1) + ctk.CTkLabel(lf, text="Corsie", font=("", 12, "bold")).grid(row=0, column=0, sticky="w", padx=6, pady=(6, 2)) + self.lb = tk.Listbox(lf, height=6, exportselection=False) + self.lb.grid(row=1, column=0, sticky="nsw", padx=6, pady=(0, 6)) + self.lb.bind("<>", self._on_select) + + # search by barcode + srch = ctk.CTkFrame(top) + srch.grid(row=0, column=1, sticky="nsew", padx=(10, 10)) + self.search_var = tk.StringVar() + self.search_entry = ctk.CTkEntry(srch, textvariable=self.search_var, width=260) + self.search_entry.grid(row=0, column=0, sticky="w") + ctk.CTkButton(srch, text="Cerca per barcode UDC", command=self._search_udc).grid(row=0, column=1, padx=(8, 0)) + srch.grid_columnconfigure(0, weight=1) + + # toolbar + tb = ctk.CTkFrame(top) + tb.grid(row=0, column=3, sticky="ne") + ctk.CTkButton(tb, text="Aggiorna", command=self._refresh_current).grid(row=0, column=0, padx=4) + ctk.CTkButton(tb, text="Export XLSX", command=self._export_xlsx).grid(row=0, column=1, padx=4) + + # ---------------- MATRIX HOST ---------------- + def _build_matrix_host(self): + center = ctk.CTkFrame(self) + center.grid(row=1, column=0, sticky="nsew", padx=8, pady=(0, 6)) + center.grid_rowconfigure(0, weight=1) + center.grid_columnconfigure(0, weight=1) + self.host = ctk.CTkFrame(center) + self.host.grid(row=0, column=0, sticky="nsew", padx=4, pady=4) + + def _apply_cell_style(self, btn: ctk.CTkButton, state: int): + if state == 0: + btn.configure( + fg_color=COLOR_EMPTY, hover_color="#9A9A9A", + text_color=FG_DARK, border_width=0 + ) + elif state == 1: + btn.configure( + fg_color=COLOR_FULL, hover_color="#E69500", + text_color=FG_DARK, border_width=0 + ) + else: + btn.configure( + fg_color=COLOR_DOUBLE, hover_color="#B22222", + text_color=FG_LIGHT, border_width=0 + ) + + def _clear_highlight(self): + if self._highlighted and self.buttons: + r, c = self._highlighted + try: + if 0 <= r < len(self.buttons) and 0 <= c < len(self.buttons[r]): + btn = self.buttons[r][c] + if getattr(btn, "winfo_exists", None) and btn.winfo_exists(): + try: + btn.configure(border_width=0) + except Exception: + pass + # clear blue frame border + try: + fr = self.btn_frames[r][c] + if fr and getattr(fr, "winfo_exists", None) and fr.winfo_exists(): + fr.configure(border_width=0) + # in CTkFrame non esiste highlightthickness come in tk; border_* è corretto + except Exception: + pass + except Exception: + pass + self._highlighted = None + + def _rebuild_matrix(self, rows: int, cols: int, state, fila_txt, col_txt, desc, udc1, corsia): + # prima rimuovi highlight su vecchi bottoni + self._clear_highlight() + # ripulisci host + for w in self.host.winfo_children(): + w.destroy() + self.buttons.clear() + self.btn_frames.clear() + + # salva matrici + self.matrix_state, self.fila_txt, self.col_txt, self.desc, self.udc1 = state, fila_txt, col_txt, desc, udc1 + + # ridistribuisci pesi griglia + for r in range(rows): + self.host.grid_rowconfigure(r, weight=1) + for c in range(cols): + self.host.grid_columnconfigure(c, weight=1) + + # crea Frame+Button per cella (righe invertite: fila "a" in basso) + for r in range(rows): + row_btns = [] + row_frames = [] + for c in range(cols): + st = state[r][c] + code = f"{corsia}.{col_txt[r][c]}.{fila_txt[r][c]}" # PRIMA RIGA (in linea) + udc = udc1[r][c] or "" # SECONDA RIGA: barcode UDC + text = f"{code}\n{udc}" + cell = ctk.CTkFrame(self.host, corner_radius=6, border_width=0) + btn = ctk.CTkButton( + cell, + text=text, + corner_radius=6) + self._apply_cell_style(btn, st) + + rr = (rows - 1) - r # capovolgi + cell.grid(row=rr, column=c, padx=1, pady=1, sticky="nsew") + btn.pack(fill="both", expand=True) + btn.configure(command=lambda rr=r, cc=c: self._open_menu(None, rr, cc)) + btn.bind("", lambda e, rr=r, cc=c: self._open_menu(e, rr, cc)) + row_btns.append(btn) + row_frames.append(cell) + self.buttons.append(row_btns) + self.btn_frames.append(row_frames) + + # focus differito post-ricarica + if self._pending_focus and self._pending_focus[0] == corsia: + _, col, fila, _barcode = self._pending_focus + self._pending_focus = None + self._highlight_cell_by_labels(col, fila) + + # ---------------- CONTEXT MENU ---------------- + def _open_menu(self, event, r, c): + st = self.matrix_state[r][c] + corsia = self.corsia_selezionata.get() + label = f"{corsia}.{self.col_txt[r][c]}.{self.fila_txt[r][c]}" + m = Menu(self, tearoff=0) + m.add_command(label="Apri dettaglio", command=lambda: self._toast(f"Dettaglio {label}")) + if st == 0: + m.add_command(label="Segna pieno", command=lambda: self._set_cell(r, c, 1)) + m.add_command(label="Segna doppia", command=lambda: self._set_cell(r, c, 2)) + elif st == 1: + m.add_command(label="Segna vuoto", command=lambda: self._set_cell(r, c, 0)) + m.add_command(label="Segna doppia", command=lambda: self._set_cell(r, c, 2)) + else: + m.add_command(label="Segna vuoto", command=lambda: self._set_cell(r, c, 0)) + m.add_command(label="Segna pieno", command=lambda: self._set_cell(r, c, 1)) + m.add_separator() + m.add_command(label="Copia ubicazione", command=lambda: self._copy(label)) + x = self.winfo_pointerx() if event is None else event.x_root + y = self.winfo_pointery() if event is None else event.y_root + m.tk_popup(x, y) + + def _set_cell(self, r, c, val): + self.matrix_state[r][c] = val + btn = self.buttons[r][c] + self._apply_cell_style(btn, val) + self._refresh_stats() + + # ---------------- STATS ---------------- + def _build_stats(self): + bottom = ctk.CTkFrame(self) + bottom.grid(row=2, column=0, sticky="nsew", padx=8, pady=6) + bottom.grid_columnconfigure(0, weight=1) + + ctk.CTkLabel(bottom, text="Riempimento globale", font=("", 10, "bold")).grid(row=0, column=0, sticky="w", pady=(0, 2)) + self.tot_canvas = tk.Canvas(bottom, height=22, highlightthickness=0) + self.tot_canvas.grid(row=1, column=0, sticky="ew", padx=(0, 260)) + self.tot_text = ctk.CTkLabel(bottom, text=pct_text(0.0, 0.0)) + self.tot_text.grid(row=1, column=0, sticky="e") + + ctk.CTkLabel(bottom, text="Riempimento corsia selezionata", font=("", 10, "bold")).grid(row=2, column=0, sticky="w", pady=(10, 2)) + self.sel_canvas = tk.Canvas(bottom, height=22, highlightthickness=0) + self.sel_canvas.grid(row=3, column=0, sticky="ew", padx=(0, 260)) + self.sel_text = ctk.CTkLabel(bottom, text=pct_text(0.0, 0.0)) + self.sel_text.grid(row=3, column=0, sticky="e") + + leg = ctk.CTkFrame(bottom) + leg.grid(row=4, column=0, sticky="w", pady=(10, 0)) + ctk.CTkLabel(leg, text="Legenda celle:").grid(row=0, column=0, padx=(0, 8)) + self._legend(leg, 1, "Vuota", COLOR_EMPTY) + self._legend(leg, 3, "Piena", COLOR_FULL) + self._legend(leg, 5, "Doppia UDC", COLOR_DOUBLE) + + def _legend(self, parent, col, text, 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) + ctk.CTkLabel(parent, text=text).grid(row=0, column=col + 1, padx=(4, 12)) + + # ---------------- DATA LOADING ---------------- + def _load_corsie(self): + sql = """ + WITH C AS ( + SELECT DISTINCT LTRIM(RTRIM(Corsia)) AS Corsia + FROM dbo.Celle + WHERE ID <> 9999 AND (DelDataOra IS NULL) AND LTRIM(RTRIM(Corsia)) <> '7G' + ) + SELECT Corsia + FROM C + ORDER BY + CASE + WHEN LEFT(Corsia,3)='MAG' AND TRY_CONVERT(int, SUBSTRING(Corsia,4,50)) IS NOT NULL THEN 0 + WHEN TRY_CONVERT(int, Corsia) IS NOT NULL THEN 1 + ELSE 2 + END, + CASE WHEN LEFT(Corsia,3)='MAG' THEN TRY_CONVERT(int, SUBSTRING(Corsia,4,50)) END, + CASE WHEN TRY_CONVERT(int, Corsia) IS NOT NULL THEN TRY_CONVERT(int, Corsia) END, + CASE WHEN TRY_CONVERT(int, Corsia) IS NOT NULL THEN SUBSTRING(Corsia, LEN(CAST(TRY_CONVERT(int, Corsia) AS varchar(20)))+1, 50) END, + Corsia; + """ + def _ok(res): + rows = res.get("rows", []) if isinstance(res, dict) else [] + self.lb.delete(0, tk.END) + corsie = [r[0] for r in rows] + for c in corsie: + self.lb.insert(tk.END, c) + idx = corsie.index("1A") if "1A" in corsie else (0 if corsie else -1) + if idx >= 0: + self.lb.selection_clear(0, tk.END) + self.lb.selection_set(idx) + self.lb.see(idx) + self._on_select(None) + else: + self._toast("Nessuna corsia trovata.") + self._busy.hide() + def _err(ex): + self._busy.hide() + messagebox.showerror("Errore", f"Caricamento corsie fallito:\n{ex}") + self._async.run(self.db.query_json(sql, {}), _ok, _err, busy=self._busy, message="Carico corsie…") + + def _on_select(self, _): + sel = self.lb.curselection() + if not sel: + return + corsia = self.lb.get(sel[0]) + self.corsia_selezionata.set(corsia) + self._load_matrix(corsia) + + def _select_corsia_in_listbox(self, corsia: str): + for i in range(self.lb.size()): + if self.lb.get(i) == corsia: + self.lb.selection_clear(0, tk.END) + self.lb.selection_set(i) + self.lb.see(i) + break + + def _load_matrix(self, corsia: str): + # nuovo token richiesta → evita che risposte vecchie spazzino la UI + self._req_counter += 1 + req_id = self._req_counter + self._last_req = req_id + + sql = """ + WITH C AS ( + SELECT + ID, + LTRIM(RTRIM(Corsia)) AS Corsia, + LTRIM(RTRIM(Fila)) AS Fila, + LTRIM(RTRIM(Colonna)) AS Colonna, + Descrizione + FROM dbo.Celle + WHERE ID <> 9999 AND (DelDataOra IS NULL) + AND LTRIM(RTRIM(Corsia)) <> '7G' AND LTRIM(RTRIM(Corsia)) = :corsia + ), + R AS ( + SELECT Fila, + DENSE_RANK() OVER ( + ORDER BY CASE WHEN TRY_CONVERT(int, Fila) IS NULL THEN 1 ELSE 0 END, + TRY_CONVERT(int, Fila), Fila + ) AS RowN + FROM C GROUP BY Fila + ), + K AS ( + SELECT Colonna, + DENSE_RANK() OVER ( + ORDER BY CASE WHEN TRY_CONVERT(int, Colonna) IS NULL THEN 1 ELSE 0 END, + TRY_CONVERT(int, Colonna), Colonna + ) AS ColN + FROM C GROUP BY Colonna + ), + S AS ( + SELECT c.ID, COUNT(DISTINCT g.BarcodePallet) AS n + FROM C AS c + LEFT JOIN dbo.XMag_GiacenzaPallet AS g ON g.IDCella = c.ID + GROUP BY c.ID + ), + U AS ( + SELECT c.ID, MIN(g.BarcodePallet) AS FirstUDC + FROM C c + LEFT JOIN dbo.XMag_GiacenzaPallet g ON g.IDCella = c.ID + GROUP BY c.ID + ) + SELECT + r.RowN, k.ColN, + CASE WHEN s.n IS NULL OR s.n = 0 THEN 0 + WHEN s.n = 1 THEN 1 + ELSE 2 END AS Stato, + c.Descrizione, + LTRIM(RTRIM(c.Fila)) AS FilaTxt, + LTRIM(RTRIM(c.Colonna)) AS ColTxt, + U.FirstUDC + FROM C c + JOIN R r ON r.Fila = c.Fila + JOIN K k ON k.Colonna = c.Colonna + LEFT JOIN S s ON s.ID = c.ID + LEFT JOIN U ON U.ID = c.ID + ORDER BY r.RowN, k.ColN; + """ + def _ok(res): + # ignora risposte superate + if req_id < self._last_req: + return + rows = res.get("rows", []) if isinstance(res, dict) else [] + if not rows: + # mostra matrice vuota senza rimuovere il frame (evita "schermo bianco") + self._rebuild_matrix(0, 0, [], [], [], [], [], corsia) + self._refresh_stats() + self._busy.hide() + return + max_r = max_c = 0 + for row in rows: + rown, coln = row[0], row[1] + if rown and coln: + max_r = max(max_r, int(rown)) + max_c = max(max_c, int(coln)) + mat = [[0] * max_c for _ in range(max_r)] + fila = [[""] * max_c for _ in range(max_r)] + col = [[""] * max_c for _ in range(max_r)] + desc = [[""] * max_c for _ in range(max_r)] + udc = [[""] * max_c for _ in range(max_r)] + for row in rows: + rown, coln, stato, descr, fila_txt, col_txt, first_udc = row + r = int(rown) - 1 + c = int(coln) - 1 + mat[r][c] = int(stato) + fila[r][c] = str(fila_txt or "") + col[r][c] = str(col_txt or "") + desc[r][c] = str(descr or f"{corsia}.{col_txt}.{fila_txt}") + udc[r][c] = str(first_udc or "") + self._rebuild_matrix(max_r, max_c, mat, fila, col, desc, udc, corsia) + self._refresh_stats() + self._busy.hide() + def _err(ex): + if req_id < self._last_req: + return + self._busy.hide() + messagebox.showerror("Errore", f"Caricamento matrice {corsia} fallito:\n{ex}") + self._async.run(self.db.query_json(sql, {"corsia": corsia}), _ok, _err, busy=self._busy, message=f"Carico corsia {corsia}…") + + # ---------------- SEARCH ---------------- + def _search_udc(self): + barcode = (self.search_var.get() or "").strip() + if not barcode: + self._toast("Inserisci un barcode UDC da cercare.") + return + + # bump token per impedire che una vecchia _load_matrix cancelli UI + self._req_counter += 1 + search_req_id = self._req_counter + self._last_req = search_req_id + + sql = """ + SELECT TOP (1) + RTRIM(c.Corsia) AS Corsia, + RTRIM(c.Colonna) AS Colonna, + RTRIM(c.Fila) AS Fila, + c.ID AS IDCella + FROM dbo.XMag_GiacenzaPallet g + JOIN dbo.Celle c ON c.ID = g.IDCella + WHERE g.BarcodePallet = :barcode + AND c.ID <> 9999 AND RTRIM(c.Corsia) <> '7G' + """ + def _ok(res): + if search_req_id < self._last_req: + return + rows = res.get("rows", []) if isinstance(res, dict) else [] + if not rows: + messagebox.showinfo("Ricerca", f"UDC {barcode} non trovata.", parent=self) + return + corsia, col, fila, _idc = rows[0] + corsia = str(corsia).strip(); col = str(col).strip(); fila = str(fila).strip() + self._pending_focus = (corsia, col, fila, barcode) + + # sincronizza listbox e carica SEMPRE la corsia della UDC + self._select_corsia_in_listbox(corsia) + self.corsia_selezionata.set(corsia) + self._load_matrix(corsia) # highlight avverrà in _rebuild_matrix + def _err(ex): + if search_req_id < self._last_req: + return + messagebox.showerror("Ricerca", f"Errore ricerca UDC:\n{ex}", parent=self) + + 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: + 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: + self._clear_highlight() + btn = self.buttons[r][c] + btn.configure(border_width=3, border_color="blue") + try: + fr = self.btn_frames[r][c] + fr.configure(border_color="blue", border_width=2) + except Exception: + pass + self._highlighted = (r, c) + return True + return False + + def _highlight_cell_by_labels(self, col_txt: str, fila_txt: str): + if not self._try_highlight(col_txt, fila_txt): + self._toast("Cella trovata ma non mappabile a pulsante.") + + # ---------------- COMMANDS ---------------- + def _refresh_current(self): + if self.corsia_selezionata.get(): + self._load_matrix(self.corsia_selezionata.get()) + + def _export_xlsx(self): + if not self.matrix_state: + messagebox.showinfo("Export", "Nessuna matrice da esportare.") + return + corsia = self.corsia_selezionata.get() or "NA" + ts = datetime.now().strftime("%d_%m_%Y_%H-%M") + default = f"layout_matrice_{corsia}_{ts}.xlsx" + path = filedialog.asksaveasfilename( + title="Esporta matrice", + defaultextension=".xlsx", + initialfile=default, + filetypes=[("Excel", "*.xlsx")] + ) + if not path: + return + try: + from openpyxl import Workbook + from openpyxl.styles import PatternFill, Alignment, Font + except Exception as ex: + messagebox.showerror("Export", f"Manca openpyxl: {ex}\nInstalla con: pip install openpyxl") + return + rows = len(self.matrix_state) + cols = len(self.matrix_state[0]) if self.matrix_state else 0 + wb = Workbook() + ws1 = wb.active + ws1.title = f"Dettaglio {corsia}" + ws1.append(["Corsia", "FilaIdx", "ColIdx", "Stato", "Descrizione", "FilaTxt", "ColTxt", "UDC1"]) + for r in range(rows): + for c in range(cols): + st = self.matrix_state[r][c] + stato_lbl = "Vuota" if st == 0 else ("Piena" if st == 1 else "Doppia") + ws1.append([corsia, r + 1, c + 1, stato_lbl, + self.desc[r][c], self.fila_txt[r][c], self.col_txt[r][c], self.udc1[r][c]]) + for cell in ws1[1]: + cell.font = Font(bold=True) + + ws2 = wb.create_sheet(f"Matrice {corsia}") + fills = { + 0: PatternFill("solid", fgColor="B0B0B0"), + 1: PatternFill("solid", fgColor="FFA500"), + 2: PatternFill("solid", fgColor="D62728"), + } + center = Alignment(horizontal="center", vertical="center", wrap_text=True) + for r in range(rows): + for c in range(cols): + value = f"{corsia}.{self.col_txt[r][c]}.{self.fila_txt[r][c]}\n{self.udc1[r][c]}" + cell = ws2.cell(row=(rows - r), column=c + 1, value=value) # capovolto per avere 'a' in basso + cell.fill = fills.get(self.matrix_state[r][c], fills[0]) + cell.alignment = center + try: + wb.save(path) + self._toast(f"Esportato: {path}") + except Exception as ex: + messagebox.showerror("Export", f"Salvataggio fallito:\n{ex}") + + # ---------------- STATS ---------------- + def _refresh_stats(self): + # globale dal DB + sql_tot = """ + WITH C AS ( + SELECT ID + FROM dbo.Celle + WHERE ID <> 9999 AND (DelDataOra IS NULL) + AND LTRIM(RTRIM(Corsia)) <> '7G' + AND LTRIM(RTRIM(Fila)) IS NOT NULL + AND LTRIM(RTRIM(Colonna)) IS NOT NULL + ), + S AS ( + SELECT c.ID, COUNT(DISTINCT g.BarcodePallet) AS n + FROM C AS c LEFT JOIN dbo.XMag_GiacenzaPallet AS g ON g.IDCella = c.ID + GROUP BY c.ID + ) + SELECT + CAST(SUM(CASE WHEN s.n>0 THEN 1 ELSE 0 END) AS float)/NULLIF(COUNT(*),0) AS PercPieno, + CAST(SUM(CASE WHEN s.n>1 THEN 1 ELSE 0 END) AS float)/NULLIF(COUNT(*),0) AS PercDoppie + FROM C LEFT JOIN S s ON s.ID = C.ID; + """ + def _ok(res): + rows = res.get("rows", []) if isinstance(res, dict) else [] + p_full = float(rows[0][0] or 0.0) if rows else 0.0 + p_dbl = float(rows[0][1] or 0.0) if rows else 0.0 + self._draw_bar(self.tot_canvas, p_full) + self.tot_text.configure(text=pct_text(p_full, p_dbl)) + self._async.run(self.db.query_json(sql_tot, {}), _ok, lambda e: None, busy=None, message=None) + + # selezionata dalla matrice in memoria + if self.matrix_state: + tot = sum(len(r) for r in self.matrix_state) + full = sum(1 for row in self.matrix_state for v in row if v in (1, 2)) + doubles = sum(1 for row in self.matrix_state for v in row if v == 2) + p_full = (full / tot) if tot else 0.0 + p_dbl = (doubles / tot) if tot else 0.0 + else: + p_full = p_dbl = 0.0 + self._draw_bar(self.sel_canvas, p_full) + self.sel_text.configure(text=pct_text(p_full, p_dbl)) + + def _draw_bar(self, cv: tk.Canvas, p_full: float): + cv.delete("all") + w = max(300, cv.winfo_width() or 600) + h = 18 + fw = int(w * max(0.0, min(1.0, p_full))) + cv.create_rectangle(2, 2, 2 + fw, 2 + h, fill="#D62728", width=0) + cv.create_rectangle(2 + fw, 2, 2 + w, 2 + h, fill="#2CA02C", width=0) + cv.create_rectangle(2, 2, 2 + w, 2 + h, outline="#555", width=1) + + # ---------------- UTIL ---------------- + def _toast(self, msg, ms=1400): + if not hasattr(self, "_status"): + self._status = ctk.CTkLabel(self, anchor="w") + self._status.grid(row=3, column=0, sticky="ew") + self._status.configure(text=msg) + self.after(ms, lambda: self._status.configure(text="")) + + def _copy(self, txt: str): + self.clipboard_clear() + self.clipboard_append(txt) + self._toast(f"Copiato: {txt}") + + +def open_layout_window(parent, db_app): + key = "_layout_window_singleton" + ex = getattr(parent, key, None) + if ex and ex.winfo_exists(): + try: + ex.lift() + ex.focus_force() + return ex + except Exception: + pass + w = LayoutWindow(parent, db_app) + setattr(parent, key, w) + return w diff --git a/main.py b/main.py new file mode 100644 index 0000000..615011c --- /dev/null +++ b/main.py @@ -0,0 +1,156 @@ + +import sys +import asyncio +import tkinter as tk +import customtkinter as ctk + +from async_msssql_query import AsyncMSSQLClient, make_mssql_dsn +from async_loop_singleton import get_global_loop + +from layout_window import open_layout_window +from view_celle_multiple import open_celle_multiple_window +from reset_corsie import open_reset_corsie_window +from search_pallets import open_search_window + +# Try factory, else frame, else app (senza passare conn_str all'App) +try: + from gestione_pickinglist import create_frame as create_pickinglist_frame +except Exception: + try: + from gestione_pickinglist import GestionePickingListFrame as _PLFrame + import customtkinter as ctk + def create_pickinglist_frame(parent, db_client=None, conn_str=None): + ctk.set_appearance_mode("light") + ctk.set_default_color_theme("green") + return _PLFrame(parent, db_client=db_client, conn_str=conn_str) + except Exception: + # Ultimo fallback: alcune versioni espongono solo la App e NON accettano conn_str + # Ultimo fallback: alcune versioni espongono solo la App e NON accettano parametri + from gestione_pickinglist import GestionePickingListApp as _PLApp + def create_pickinglist_frame(parent, db_client=None, conn_str=None): + app = _PLApp() # <-- niente parametri qui + app.mainloop() + return tk.Frame(parent) + + + +# ---- Config ---- +SERVER = r"mde3\gesterp" +DBNAME = "Mediseawall" +USER = "sa" +PASSWORD = "1Password1" + +if sys.platform.startswith("win"): + try: + asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) + except Exception: + pass + +# Create ONE global loop and make it the default everywhere +_loop = get_global_loop() +asyncio.set_event_loop(_loop) + +# --- DPI tracker compatibility --- +def _noop(*args, **kwargs): + return None +if not hasattr(tk.Toplevel, "block_update_dimensions_event"): + tk.Toplevel.block_update_dimensions_event = _noop # type: ignore[attr-defined] +if not hasattr(tk.Toplevel, "unblock_update_dimensions_event"): + tk.Toplevel.unblock_update_dimensions_event = _noop # type: ignore[attr-defined] + +dsn_app = make_mssql_dsn(server=SERVER, database=DBNAME, user=USER, password=PASSWORD) +db_app = AsyncMSSQLClient(dsn_app) + + +def open_pickinglist_window(parent: tk.Misc, db_client: AsyncMSSQLClient): + win = ctk.CTkToplevel(parent) + win.title("Gestione Picking List") + win.geometry("1200x700+0+100") + win.minsize(1000, 560) + + # 1) tieni la toplevel fuori scena mentre componi + try: + win.withdraw() + # opzionale: rendila invisibile anche se il WM la “intr intravede” + win.attributes("-alpha", 0.0) + except Exception: + pass + + # 2) costruisci tutto il contenuto + frame = create_pickinglist_frame(win, db_client=db_client) + try: + frame.pack(fill="both", expand=True) + except Exception: + pass + + # 3) quando è pronta, mostra "a scatto" davanti, senza topmost + try: + win.update_idletasks() + try: + win.transient(parent) # z-order legato alla main + except Exception: + pass + try: + win.deiconify() + except Exception: + pass + win.lift() + try: + win.focus_force() + except Exception: + pass + # ripristina opacità + try: + win.attributes("-alpha", 1.0) + except Exception: + pass + except Exception: + pass + + win.bind("", lambda e: win.destroy()) + win.protocol("WM_DELETE_WINDOW", win.destroy) + return win + + + + +class Launcher(ctk.CTk): + def __init__(self): + super().__init__() + self.title("Warehouse 1.0.0") + self.geometry("1200x70+0+0") + + wrap = ctk.CTkFrame(self) + wrap.pack(pady=10, fill="x") + + ctk.CTkButton(wrap, text="Gestione Corsie", + command=lambda: open_reset_corsie_window(self, db_app)).grid(row=0, column=0, padx=6, pady=6, sticky="ew") + ctk.CTkButton(wrap, text="Gestione Layout", + command=lambda: open_layout_window(self, db_app)).grid(row=0, column=1, padx=6, pady=6, sticky="ew") + ctk.CTkButton(wrap, text="UDC Fantasma", + command=lambda: open_celle_multiple_window(self, db_app)).grid(row=0, column=2, padx=6, pady=6, sticky="ew") + ctk.CTkButton(wrap, text="Ricerca UDC", + command=lambda: open_search_window(self, db_app)).grid(row=0, column=3, padx=6, pady=6, sticky="ew") + ctk.CTkButton(wrap, text="Gestione Picking List", + command=lambda: open_pickinglist_window(self, db_app)).grid(row=0, column=4, padx=6, pady=6, sticky="ew") + + for i in range(5): + wrap.grid_columnconfigure(i, weight=1) + + def _on_close(): + try: + fut = asyncio.run_coroutine_threadsafe(db_app.dispose(), _loop) + try: + fut.result(timeout=2) + except Exception: + pass + finally: + self.destroy() + + self.protocol("WM_DELETE_WINDOW", _on_close) + + +if __name__ == "__main__": + ctk.set_appearance_mode("light") + ctk.set_default_color_theme("green") + Launcher().mainloop() diff --git a/pickinglist.sql b/pickinglist.sql new file mode 100644 index 0000000..1aecde5 --- /dev/null +++ b/pickinglist.sql @@ -0,0 +1,62 @@ +SELECT * FROM vViewMappaturaDescrizioneCorsia WHERE ( Area > 0) ORDER BY Area Desc + +SELECT * FROM Celle WHERE ( ID > 0) ORDER BY ID Desc + +SELECT TOP 1000 [mc_X] ,[minFila] ,[maxFila] ,[minColonna] ,[maxColonna] ,[mc_Numero_Magazzino] ,[mc_Numero_Area] FROM vViewMappaturaPosizCorsia WHERE [mc_Numero_Area] =1 + + +SELECT CASE WHEN Nota = 'ASC' THEN 0 ELSE CASE WHEN Nota = 'DESC' THEN 1 END END AS Nota FROM MagLayout WHERE (IDArea = 1) + +SELECT ID, Descrizione FROM Magazzini ORDER BY Descrizione + +SELECT ID, Descrizione FROM Celle ORDER BY Descrizione + +SELECT [ID],[CorsiaDescrizione] FROM vViewMappaturaDescrizioneCorsia + + + SELECT COUNT(DISTINCT Pallet) AS Pallet, COUNT(DISTINCT Lotto) AS Lotto, COUNT(DISTINCT Articolo) AS Articolo, COUNT(DISTINCT Descrizione) AS Descrizione, SUM(Qta) AS Qta, Documento, CodNazione, NAZIONE, Stato, MAX(PalletCella) AS PalletCella, MAX(Magazzino) AS Magazzino, MAX(Area) AS Area, MAX(Cella) AS Cella, MIN(Ordinamento) AS Ordinamento, MAX(IDStato) AS IDStato FROM dbo.XMag_ViewPackingList GROUP BY Documento, CodNazione, NAZIONE, Stato + + SELECT * FROM vViewPackingListRestante WHERE Documento = 237 ORDER BY Ordinamento + SELECT * FROM ViewPackingListRestante WHERE Documento = 246 ORDER BY Ordinamento + + + + +USE master; +GO + +-- (facoltativo) terminare connessioni attive +ALTER DATABASE SAMA1 SET SINGLE_USER WITH ROLLBACK IMMEDIATE; +GO + +-- Ripristino dal tuo snapshot +RESTORE DATABASE SAMA1 +FROM DATABASE_SNAPSHOT = 'SAMA1_SNAP_20251014_112623'; +GO + +-- Riporta il DB in multi-user +ALTER DATABASE SAMA1 SET MULTI_USER; +GO + + + +SELECT @@SERVERNAME AS server_name, DB_NAME() AS database_name; + +SELECT name, create_date +FROM sys.databases +WHERE source_database_id IS NOT NULL; + + +SELECT COUNT(*) AS righe FROM dbo.XMag_ViewPackingList; + +SELECT COUNT(*) AS righe FROM dbo.ViewPackingListRestante; + +SELECT Documento, Stato, IDStato, NAZIONE, COUNT(*) AS righe +FROM dbo.ViewPackingListRestante +WHERE Documento IN (240,241) +GROUP BY Documento, Stato, IDStato, NAZIONE; + + + +EXEC sp_refreshview N'dbo.XMag_ViewPackingList'; +EXEC sp_refreshview N'dbo.ViewPackingListRestante'; \ No newline at end of file diff --git a/prenota_sprenota_sql.py b/prenota_sprenota_sql.py new file mode 100644 index 0000000..5629f2b --- /dev/null +++ b/prenota_sprenota_sql.py @@ -0,0 +1,189 @@ +from __future__ import annotations +from dataclasses import dataclass +from typing import Optional, Any, Dict, List + + +@dataclass +class SPResult: + rc: int = 0 # equivalente a @RC OUTPUT + message: Optional[str] = "" # eventuale messaggio/errore + id_result: Optional[int] = None # ID del record inserito in LogPackingList + + +# --- helpers per il client async (senza conoscere l'API esatta forniamo fallback robusti) --- +async def _query_one_value(db, sql: str, params: Dict[str, Any]) -> Optional[Any]: + """ + Ritorna la prima colonna della prima riga, oppure None. + Tenta prima query_json(...), poi altri metodi comuni. + """ + if hasattr(db, "query_json"): + res = await db.query_json(sql, params) + # res può essere una lista di dict o un payload con rows/columns + if isinstance(res, list) and res: + row0 = res[0] + if isinstance(row0, dict): + # prima colonna disponibile + return next(iter(row0.values()), None) + elif isinstance(res, dict): + rows = None + for k in ("rows", "data", "result", "records"): + if k in res and isinstance(res[k], list): + rows = res[k] + break + if rows: + r0 = rows[0] + if isinstance(r0, dict): + return next(iter(r0.values()), None) + if isinstance(r0, (list, tuple)) and r0: + return r0[0] + return None + + # fallback: altri metodi (se esistono) + if hasattr(db, "query_value"): + return await db.query_value(sql, params) + if hasattr(db, "scalar"): + return await db.scalar(sql, params) + raise RuntimeError("Il client DB non espone query_json/query_value/scalar") + + +async def _query_all(db, sql: str, params: Dict[str, Any]) -> List[Dict[str, Any]]: + """Ritorna una lista di dict {col:val}.""" + if hasattr(db, "query_json"): + res = await db.query_json(sql, params) + if res is None: + return [] + if isinstance(res, list): + return res if res and isinstance(res[0], dict) else [] + if isinstance(res, dict): + for k in ("rows", "data", "result", "records"): + if k in res and isinstance(res[k], list): + rows = res[k] + if rows and isinstance(rows[0], dict): + return rows + cols = res.get("columns") or res.get("cols") or [] + out = [] + for r in rows: + if isinstance(r, (list, tuple)) and cols: + out.append({ (cols[i] if i < len(cols) else f"c{i}") : r[i] + for i in range(min(len(cols), len(r))) }) + return out + return [] + # fallback + if hasattr(db, "fetch_all"): + return await db.fetch_all(sql, params) + raise RuntimeError("Il client DB non espone query_json/fetch_all") + + +async def _execute(db, sql: str, params: Dict[str, Any]) -> int: + """ + Esegue DML e ritorna rowcount (se disponibile). + Prova .execute / .exec / .execute_non_query / altrimenti usa query_json. + """ + for name in ("execute", "exec", "execute_non_query"): + if hasattr(db, name): + rc = await getattr(db, name)(sql, params) + # alcuni client ritornano None, altri rowcount, altri payload + if isinstance(rc, int): + return rc + return 0 + # fallback rozzo: molti back-end accettano anche DML in query_json + if hasattr(db, "query_json"): + await db.query_json(sql, params) + return 0 + raise RuntimeError("Il client DB non espone metodi di esecuzione DML noti") + + +# --- Procedura portata in async, usando il client DB passato dall'app --- +async def sp_xExePackingListPallet_async(db, IDOperatore: int, Documento: str) -> SPResult: + """ + Porting asincrono di [dbo].[sp_xExePackingListPallet] usando il client DB già aperto dall'app. + Logica: + 1) Recupera LOGIN operatore + 2) Elenca le celle (DISTINCT Cella da XMag_ViewPackingList per Documento) + 3) Per ogni cella: leggi IDStato e toggla 0<->1 + aggiorna ModUtente/ModDataOra + 4) Description = TOP 1 NAZIONE per Documento + 5) Inserisci LogPackingList(Code=Documento, Description, IDInsUser=IDOperatore, InsDateTime=GETDATE()) + """ + try: + # 1) LOGIN operatore (se manca, prosegue come da SP originaria) + nominativo = await _query_one_value( + db, + "SELECT LOGIN FROM Operatori WHERE id = :IDOperatore", + {"IDOperatore": IDOperatore} + ) or "" + + # 2) Celle da trattare + celle = await _query_all( + db, + """ + SELECT DISTINCT Cella + FROM dbo.XMag_ViewPackingList + WHERE Documento = :Documento + """, + {"Documento": Documento} + ) + id_celle = [r.get("Cella") for r in celle if "Cella" in r] + + # 3) Toggle stato per ogni cella + for id_cella in id_celle: + if id_cella is None: + continue + stato = await _query_one_value( + db, + "SELECT IDStato FROM Celle WHERE ID = :IDC", + {"IDC": id_cella} + ) + if stato == 0: + await _execute( + db, + """ + UPDATE Celle + SET IDStato = 1, + ModUtente = :N, + ModDataOra = GETDATE() + WHERE ID = :IDC + """, + {"N": nominativo, "IDC": id_cella} + ) + else: + await _execute( + db, + """ + UPDATE Celle + SET IDStato = 0, + ModUtente = :N, + ModDataOra = GETDATE() + WHERE ID = :IDC + """, + {"N": nominativo, "IDC": id_cella} + ) + + # 4) Description = NAZIONE (TOP 1) + description = await _query_one_value( + db, + """ + SELECT TOP 1 NAZIONE + FROM dbo.XMag_ViewPackingList + WHERE Documento = :Documento + GROUP BY Documento, NAZIONE + ORDER BY NAZIONE + """, + {"Documento": Documento} + ) + + # 5) LogPackingList + await _execute( + db, + """ + INSERT INTO dbo.LogPackingList (Code, Description, IDInsUser, InsDateTime) + VALUES (:Code, :Descr, :IDInsUser, GETDATE()); + """, + {"Code": Documento, "Descr": description, "IDInsUser": IDOperatore} + ) + + # Se vuoi proprio l'ID appena inserito: + new_id = await _query_one_value(db, "SELECT SCOPE_IDENTITY() AS ID", {}) + return SPResult(rc=0, message="", id_result=int(new_id) if new_id is not None else None) + + except Exception as e: + return SPResult(rc=-1, message=str(e), id_result=None) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..a333a0c --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,16 @@ +[project] +name = "warehouse" +version = "0.0.1" +requires-python = ">=3.12" +dependencies = [ + "sqlalchemy[asyncio]>=2.0", + "aioodbc>=0.3.3", + # "orjson>=3.9" # opzionale: il tuo codice fa fallback su json puro +] + +[project.optional-dependencies] +dev = ["pytest", "pytest-cov", "mypy", "black", "flake8"] + +[tool.pytest.ini_options] +addopts = "-q --maxfail=1" +pythonpath = ["."] diff --git a/readme.txt b/readme.txt new file mode 100644 index 0000000..d411d61 --- /dev/null +++ b/readme.txt @@ -0,0 +1,50 @@ +-- dbo.XMag_DettaglioPallet source + +ALTER VIEW [dbo].[XMag_DettaglioPallet] +AS +SELECT dbo.MagazziniPallet.ID, dbo.MagazziniPallet.Tipo, dbo.MagazziniPallet.IDRiferimento, dbo.MagazziniPallet.NumeroPallet, + dbo.MagazziniPallet.IDMagazzino, dbo.MagazziniPallet.IDArea, dbo.MagazziniPallet.IDCella, dbo.MagazziniPallet.DataMagazzino, + dbo.MagazziniPallet.PesoUnitario, dbo.MagazziniPallet.Tara, dbo.MagazziniPallet.Attributo, dbo.Celle.IDStato +FROM dbo.MagazziniPallet INNER JOIN + dbo.Celle ON dbo.MagazziniPallet.IDCella = dbo.Celle.ID +WHERE (dbo.MagazziniPallet.Tipo = 'V') +UNION ALL +SELECT MagazziniPallet_1.ID, MagazziniPallet_1.Tipo, MagazziniPallet_1.IDRiferimento, MagazziniPallet_1.NumeroPallet, MagazziniPallet_1.IDMagazzino, + MagazziniPallet_1.IDArea, MagazziniPallet_1.IDCella, MagazziniPallet_1.DataMagazzino, - (1 * MagazziniPallet_1.PesoUnitario) AS PesoUnitario, + MagazziniPallet_1.Tara, MagazziniPallet_1.Attributo, Celle_1.IDStato +FROM dbo.MagazziniPallet AS MagazziniPallet_1 INNER JOIN + dbo.Celle AS Celle_1 ON MagazziniPallet_1.IDCella = Celle_1.ID +WHERE (MagazziniPallet_1.Tipo = 'P'); + +:commento +Dalla tabella Celle e MagazziniPallet ottengo l'insieme delle celle con tipo V = vuota e p=piena, le celle in questo caso possono essere di qualunque tipo cioè fare riferimento a pallet ancora in deposito oppure già spediti, o piene (1A.15.0e) o spedite (7G.01.01). + + +ALTER VIEW dbo.XMag_GiacenzaPallet +AS +SELECT Attributo AS BarcodePallet, NumeroPallet, IDMagazzino, IDArea, IDCella, SUM(PesoUnitario) AS Peso, Attributo AS CodiceProdotto, IDStato +FROM dbo.XMag_DettaglioPallet +GROUP BY NumeroPallet, IDMagazzino, IDArea, IDCella, Attributo, IDStato +HAVING (SUM(PesoUnitario) > 0); +commento: +Ricomprimo la vista ..dettaglio perchè capita che una udc sia stata allocata più volte sulla stessa cella, la riga in questione appare doppia se non si tiene conto della data in cui è avvenuta la lettura .SI potrebbe pensare di prendere sempre la data più alta , cioè l'ultima a parità di unità di carico. + + +ALTER VIEW [dbo].[XMag_GiacenzaPalletxUbicazioneCella] +AS +SELECT dbo.Celle.ID as IDCella, dbo.vXTracciaProdotti.Pallet, dbo.vXTracciaProdotti.Lotto, dbo.vXTracciaProdotti.Prodotto, dbo.vXTracciaProdotti.Descrizione, UPPER(REPLICATE('0', + 3 - DATALENGTH(dbo.Celle.Corsia)) + dbo.Celle.Corsia + '.' + + REPLICATE('0', 2 - DATALENGTH(dbo.Celle.Colonna)) + + dbo.Celle.Colonna + '.' + REPLICATE('0', 2 - DATALENGTH(dbo.Celle.Fila)) + dbo.Celle.Fila) AS Ubicazione +FROM dbo.XMag_GiacenzaPallet +INNER JOIN + dbo.vXTracciaProdotti ON dbo.XMag_GiacenzaPallet.BarcodePallet = dbo.vXTracciaProdotti.Pallet COLLATE Latin1_General_CI_AS + LEFT OUTER JOIN + dbo.Celle ON dbo.XMag_GiacenzaPallet.IDCella = dbo.Celle.ID; + + :commento +qui commbino una vista proveniente da sam in cui vado a cercare i dati della unità di carico e li combino con la tabella vista giacenzapallet, in pratica definisco dove in quale ubicazione si trova il pallet + +quindi in pratica ho + +tabella vista vista +magazzinipallet ---->xmag_Dettagliopallet--->Xmag_giacenzapallet \ No newline at end of file diff --git a/reset_corsie.py b/reset_corsie.py new file mode 100644 index 0000000..3fe0127 --- /dev/null +++ b/reset_corsie.py @@ -0,0 +1,245 @@ +# reset_corsie.py +import tkinter as tk +from tkinter import ttk, messagebox, simpledialog +import customtkinter as ctk +from datetime import datetime + +from gestione_aree_frame_async import BusyOverlay, AsyncRunner + +# ---------------- SQL ---------------- +SQL_CORSIE = """ + WITH C AS ( + SELECT DISTINCT LTRIM(RTRIM(Corsia)) AS Corsia + FROM dbo.Celle + WHERE ID <> 9999 AND (DelDataOra IS NULL) AND LTRIM(RTRIM(Corsia)) <> '7G' + ) + SELECT Corsia + FROM C + ORDER BY + CASE + WHEN LEFT(Corsia,3)='MAG' AND TRY_CONVERT(int, SUBSTRING(Corsia,4,50)) IS NOT NULL THEN 0 + WHEN TRY_CONVERT(int, Corsia) IS NOT NULL THEN 1 + ELSE 2 + END, + CASE WHEN LEFT(Corsia,3)='MAG' THEN TRY_CONVERT(int, SUBSTRING(Corsia,4,50)) END, + CASE WHEN TRY_CONVERT(int, Corsia) IS NOT NULL THEN TRY_CONVERT(int, Corsia) END, + CASE WHEN TRY_CONVERT(int, Corsia) IS NOT NULL THEN SUBSTRING(Corsia, LEN(CAST(TRY_CONVERT(int, Corsia) AS varchar(20)))+1, 50) END, + Corsia; +""" + +SQL_RIEPILOGO = """ +WITH C AS ( + SELECT ID, LTRIM(RTRIM(Corsia)) AS Corsia, + LTRIM(RTRIM(Colonna)) AS Colonna, + LTRIM(RTRIM(Fila)) AS Fila + FROM dbo.Celle + WHERE ID <> 9999 AND (DelDataOra IS NULL) + AND LTRIM(RTRIM(Corsia)) = :corsia +), +S AS ( + SELECT c.ID, COUNT(DISTINCT g.BarcodePallet) AS n + FROM C AS c LEFT JOIN dbo.XMag_GiacenzaPallet AS g ON g.IDCella = c.ID + GROUP BY c.ID +) +SELECT + COUNT(*) AS TotCelle, + SUM(CASE WHEN s.n>0 THEN 1 ELSE 0 END) AS CelleOccupate, + SUM(CASE WHEN s.n>1 THEN 1 ELSE 0 END) AS CelleDoppie, + SUM(COALESCE(s.n,0)) AS TotPallet +FROM C LEFT JOIN S s ON s.ID = C.ID; +""" + +SQL_DETTAGLIO = """ +WITH C AS ( + SELECT ID, LTRIM(RTRIM(Corsia)) AS Corsia, + LTRIM(RTRIM(Colonna)) AS Colonna, + LTRIM(RTRIM(Fila)) AS Fila + FROM dbo.Celle + WHERE ID <> 9999 AND (DelDataOra IS NULL) + AND LTRIM(RTRIM(Corsia)) = :corsia +), +S AS ( + SELECT c.ID, COUNT(DISTINCT g.BarcodePallet) AS n + FROM C AS c LEFT JOIN dbo.XMag_GiacenzaPallet AS g ON g.IDCella = c.ID + GROUP BY c.ID +) +SELECT c.ID AS IDCella, + CONCAT(c.Corsia, '.', c.Colonna, '.', c.Fila) AS Ubicazione, + COALESCE(s.n,0) AS NumUDC +FROM C c LEFT JOIN S s ON s.ID = c.ID +WHERE COALESCE(s.n,0) > 0 +ORDER BY TRY_CONVERT(int,c.Colonna), c.Colonna, TRY_CONVERT(int,c.Fila), c.Fila; +""" + +SQL_COUNT_DELETE = """ +SELECT COUNT(*) AS RowsToDelete +FROM dbo.MagazziniPallet mp +JOIN dbo.Celle c ON c.ID = mp.IDCella +WHERE c.ID <> 9999 AND LTRIM(RTRIM(c.Corsia)) = :corsia; +""" + +SQL_DELETE = """ +DELETE mp +FROM dbo.MagazziniPallet mp +JOIN dbo.Celle c ON c.ID = mp.IDCella +WHERE c.ID <> 9999 AND LTRIM(RTRIM(c.Corsia)) = :corsia; +""" + +class ResetCorsieWindow(ctk.CTkToplevel): + """ + Finestra per: + - selezionare una corsia + - vedere riepilogo occupazione / doppie / pallet + - vedere l'elenco celle occupate + - svuotare (DELETE MagazziniPallet) tutte le celle della corsia selezionata + """ + def __init__(self, parent, db_client): + super().__init__(parent) + self.title("Reset Corsie — svuotamento celle per corsia") + self.geometry("1000x680") + self.minsize(880, 560) + self.resizable(True, True) + + self.db = db_client + self._busy = BusyOverlay(self) + self._async = AsyncRunner(self) + + self._build_ui() + self._load_corsie() + + # ---------- UI ---------- + def _build_ui(self): + top = ctk.CTkFrame(self); top.pack(fill="x", padx=8, pady=8) + ctk.CTkLabel(top, text="Corsia:").pack(side="left") + self.cmb = ctk.CTkComboBox(top, width=140, values=[]) + self.cmb.pack(side="left", padx=(6,10)) + ctk.CTkButton(top, text="Carica", command=self.refresh).pack(side="left") + ctk.CTkButton(top, text="Svuota corsia…", command=self._ask_reset).pack(side="right") + + mid = ctk.CTkFrame(self); mid.pack(fill="both", expand=True, padx=8, pady=(0,8)) + mid.grid_columnconfigure(0, weight=1); mid.grid_rowconfigure(0, weight=1) + + self.tree = ttk.Treeview(mid, columns=("Ubicazione","NumUDC"), show="headings", selectmode="browse") + self.tree.heading("Ubicazione", text="Ubicazione") + self.tree.heading("NumUDC", text="UDC in cella") + self.tree.column("Ubicazione", width=240, anchor="w") + self.tree.column("NumUDC", width=120, anchor="e") + + sy = ttk.Scrollbar(mid, orient="vertical", command=self.tree.yview) + sx = ttk.Scrollbar(mid, orient="horizontal", command=self.tree.xview) + self.tree.configure(yscrollcommand=sy.set, xscrollcommand=sx.set) + self.tree.grid(row=0, column=0, sticky="nsew") + sy.grid(row=0, column=1, sticky="ns") + sx.grid(row=1, column=0, sticky="ew") + + bottom = ctk.CTkFrame(self) + bottom.pack(fill="x", padx=8, pady=(0,8)) + ctk.CTkLabel(bottom, text="Riepilogo", font=("Segoe UI", 12, "bold")).pack(anchor="w", padx=8, pady=(8,0)) + + g = ctk.CTkFrame(bottom) + g.pack(fill="x", padx=8, pady=8) + self.var_tot_celle = tk.StringVar(value="0") + self.var_occ = tk.StringVar(value="0") + self.var_dbl = tk.StringVar(value="0") + self.var_pallet = tk.StringVar(value="0") + + def _kv(parent, label, var, col): + ctk.CTkLabel(parent, text=label, font=("Segoe UI", 9, "bold")).grid(row=0, column=col*2, sticky="w", padx=(0,6)) + ctk.CTkLabel(parent, textvariable=var).grid(row=0, column=col*2+1, sticky="w", padx=(0,18)) + + g.grid_columnconfigure(7, weight=1) + _kv(g, "Tot. celle:", self.var_tot_celle, 0) + _kv(g, "Celle occupate:", self.var_occ, 1) + _kv(g, "Celle doppie:", self.var_dbl, 2) + _kv(g, "Tot. pallet:", self.var_pallet, 3) + + # ---------- Data ---------- + def _load_corsie(self): + def _ok(res): + rows = res.get("rows", []) if isinstance(res, dict) else [] + items = [r[0] for r in rows] + self.cmb.configure(values=items) + if items: + # auto 1A se presente + sel = "1A" if "1A" in items else items[0] + self.cmb.set(sel) + self.refresh() + else: + messagebox.showinfo("Info", "Nessuna corsia trovata.", parent=self) + def _err(ex): + messagebox.showerror("Errore", f"Caricamento corsie fallito:\n{ex}", parent=self) + self._async.run(self.db.query_json(SQL_CORSIE, {}), _ok, _err, busy=self._busy, message="Carico corsie…") + + def refresh(self): + corsia = self.cmb.get().strip() + if not corsia: + return + # riepilogo + def _ok_sum(res): + rows = res.get("rows", []) if isinstance(res, dict) else [] + if rows: + tot, occ, dbl, pallet = rows[0] + self.var_tot_celle.set(str(tot or 0)) + self.var_occ.set(str(occ or 0)) + self.var_dbl.set(str(dbl or 0)) + self.var_pallet.set(str(pallet or 0)) + else: + self.var_tot_celle.set("0"); self.var_occ.set("0"); self.var_dbl.set("0"); self.var_pallet.set("0") + def _err_sum(ex): + messagebox.showerror("Errore", f"Riepilogo fallito:\n{ex}", parent=self) + + self._async.run(self.db.query_json(SQL_RIEPILOGO, {"corsia": corsia}), _ok_sum, _err_sum, busy=self._busy, message=f"Riepilogo {corsia}…") + + # dettaglio + def _ok_det(res): + rows = res.get("rows", []) if isinstance(res, dict) else [] + for i in self.tree.get_children(): self.tree.delete(i) + for idc, ubi, n in rows: + self.tree.insert("", "end", values=(ubi, n)) + def _err_det(ex): + messagebox.showerror("Errore", f"Dettaglio fallito:\n{ex}", parent=self) + + self._async.run(self.db.query_json(SQL_DETTAGLIO, {"corsia": corsia}), _ok_det, _err_det, busy=None, message=None) + + # ---------- Reset ---------- + def _ask_reset(self): + corsia = self.cmb.get().strip() + if not corsia: + return + # Primo: quante righe verrebbero cancellate? + def _ok_count(res): + rows = res.get("rows", []) if isinstance(res, dict) else [] + n = int(rows[0][0]) if rows else 0 + if n <= 0: + messagebox.showinfo("Svuota corsia", f"Nessun pallet da rimuovere per la corsia {corsia}.", parent=self) + return + # doppia conferma + msg = (f"Verranno cancellati {n} record da MagazziniPallet per la corsia {corsia}.", + "Questa operazione è irreversibile.", + "Digitare il nome della corsia per confermare:") + confirm = simpledialog.askstring("Conferma", "\n".join(msg), parent=self) + if confirm is None: + return + if confirm.strip().upper() != corsia.upper(): + messagebox.showwarning("Annullato", "Testo di conferma non corrispondente.", parent=self) + return + self._do_reset(corsia) + def _err_count(ex): + messagebox.showerror("Errore", f"Conteggio righe da cancellare fallito:\n{ex}", parent=self) + + self._async.run(self.db.query_json(SQL_COUNT_DELETE, {"corsia": corsia}), _ok_count, _err_count, busy=self._busy, message="Verifico…") + + def _do_reset(self, corsia: str): + def _ok_del(_): + messagebox.showinfo("Completato", f"Corsia {corsia}: svuotamento completato.", parent=self) + self.refresh() + def _err_del(ex): + messagebox.showerror("Errore", f"Svuotamento fallito:\n{ex}", parent=self) + + self._async.run(self.db.query_json(SQL_DELETE, {"corsia": corsia}), _ok_del, _err_del, busy=self._busy, message=f"Svuoto {corsia}…") + + +def open_reset_corsie_window(parent, db_app): + win = ResetCorsieWindow(parent, db_app) + win.lift(); win.focus_set() + return win diff --git a/riassunto_ricerca_udc_20251005.pdf b/riassunto_ricerca_udc_20251005.pdf new file mode 100644 index 0000000..54a6f2a --- /dev/null +++ b/riassunto_ricerca_udc_20251005.pdf @@ -0,0 +1,68 @@ +%PDF-1.4 +% ReportLab Generated PDF document http://www.reportlab.com +1 0 obj +<< +/F1 2 0 R +>> +endobj +2 0 obj +<< +/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font +>> +endobj +3 0 obj +<< +/Contents 7 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 6 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +4 0 obj +<< +/PageMode /UseNone /Pages 6 0 R /Type /Catalog +>> +endobj +5 0 obj +<< +/Author (\(anonymous\)) /CreationDate (D:20251005172731+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20251005172731+00'00') /Producer (ReportLab PDF Library - www.reportlab.com) + /Subject (\(unspecified\)) /Title (\(anonymous\)) /Trapped /False +>> +endobj +6 0 obj +<< +/Count 1 /Kids [ 3 0 R ] /Type /Pages +>> +endobj +7 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1709 +>> +stream +Gat=*@;jjj&H1Hm0iGadU(Bku9dAJL2C.[\TU.Q9Q]mO#@Lm(Sp9&>cjoW[1`N*qiBFUbb:t$ZD@Ec$Rrc:#E>K^+uO]`H_<@f%R>]SYAN9dkSI:dJ(cUE,*SE%^0`$he:F5;`W^eb4D@^V>9!LX9O&Hf@H2%Yf@!>R8'^*;WLukGdAr#Q>u-o`@`@"VE16M1;YuZ(o-:h*+":B/Ybk#e6*##Bcd,69%*OtBbPWf7OqUEkie@Y+BgS`]d.rA7b-0AP&_j;*bmRA=L.#s7qe+MJk^jiTMBcL@L"Ri>^8cc#JW(b\&oP2+nLb[hu0fkQ`48tkRO3EIoNR&K@h*B8ViJ@0Gr4K<43)Ekb?5464etE>.40O/bH4]],f/an%VUh8lchsj8W2]X67"->O_#1`--2`?Z]kl)4Pm;7fUW`MRnbo*+d.RFV474lEba!uPu]6?1o(iK$eRp!Lg?Ck,p;N,]1c)ABNkcc8G":n'(1;aqN]ptL$EM4FLg"DB]oe9TuiHl`DE^R$,08$nE?+HEqVm[1\P-;6bHfD>n1b\Tr0Yg'WM1s8?n'JM8;KiGA\H0lUna6N)B7:k@&]:Yite,/M5d!,b-RLX_NbV^[\U.;:mWF?aP=o%s=ppcCl9E)X?7?5oq_DO?hLgOZGHVN6FWNW[b.&HZ"CaYrR^CM8?#;,`AK2f-^b7/K4n[PK@i'>Q0?.o(2$'Ze=7ko8h9jSPWUHe0>@9.!CYSo[8XE#Y1TX-qnH!XIfSck8%_PGV>)b=3.pqLM^Ubm$:@\%E*mpC).A.o]>sl$KXV]V)!n0m8Q2'@1R@fdm?KEa&pRc7^YEZMMqR!Zq3%&8Y[e&,D$Zoe-G$8LW,u$h1pP;3.dZB6Bh^4n]F$bicJtuA>RX;U3Lhk-7qu1ehU[4iBG7D.W)e'`FR>]a`HY:(/2lN`7fRPUHLgHmjD"ihb'_R_3(bf2OcpSbMY.8godBKC"pn`t5'>J_Sagk`EjYrUPj6Yf1)NJ/WnkE'+]?P`OYUD81gAqBphrVcZT%q/F)GC5d0Fl:Q_WIV/@9N\A+rmqX-An>"Oo1C;X%ng]E;AUL*;rnFt9^FllJqBW&Q'44ZXq<1!>.;%,L^=?Mdp2+\VY1!Woqu34/]bIsdonQ87"Ln:Ou>oUI/Mq^bb\DmJ6,'CW1H$^d>[[CQogFddhjW4K;jdn@#"PA+0>>cl1_#F't5f\4e(kendstream +endobj +xref +0 8 +0000000000 65535 f +0000000073 00000 n +0000000104 00000 n +0000000211 00000 n +0000000414 00000 n +0000000482 00000 n +0000000765 00000 n +0000000824 00000 n +trailer +<< +/ID +[<54fac52a7e33f1aa3f45091c2782a3eb><54fac52a7e33f1aa3f45091c2782a3eb>] +% ReportLab generated PDF document -- digest (http://www.reportlab.com) + +/Info 5 0 R +/Root 4 0 R +/Size 8 +>> +startxref +2624 +%%EOF diff --git a/script.sql b/script.sql new file mode 100644 index 0000000..8f17d85 Binary files /dev/null and b/script.sql differ diff --git a/search_pallets.py b/search_pallets.py new file mode 100644 index 0000000..3823218 --- /dev/null +++ b/search_pallets.py @@ -0,0 +1,453 @@ +# search_pallets.py +# Finestra di ricerca su UDC / Lotto / Codice Prodotto +# - Tre campi: UDC, Lotto, Codice Prodotto (AND tra i campi valorizzati) +# - Ricerca su TUTTE le celle, incluse IDCella=9999 e corsia 7G +# - Risultati in una griglia: IDCella, Ubicazione, UDC, Lotto, Codice, Descrizione +# - Se la ricerca restituisce >0 righe, i campi input vengono svuotati + +from __future__ import annotations +import tkinter as tk +from tkinter import ttk, messagebox + +import customtkinter as ctk + +from gestione_aree_frame_async import BusyOverlay, AsyncRunner +from tkinter import filedialog + +# opzionale export xlsx +try: + from openpyxl import Workbook + from openpyxl.styles import Font, Alignment + _HAS_XLSX = True +except Exception: + _HAS_XLSX = False + +# opzionale: usare tksheet per avere griglie con bordi di cella +try: + from tksheet import Sheet +except Exception: + Sheet = None + +SQL_SEARCH = r""" +WITH BASE AS ( + SELECT + g.IDCella, + -- forza stringa per ricerche LIKE + CONCAT(g.BarcodePallet, '') AS UDC, + c.Corsia, + c.Colonna, + c.Fila + FROM dbo.XMag_GiacenzaPallet AS g + LEFT JOIN dbo.Celle AS c ON c.ID = g.IDCella + -- NB: qui NON escludiamo IDCella=9999 né '7G' +), +JOINED AS ( + SELECT + b.IDCella, + b.UDC, + b.Corsia, + b.Colonna, + b.Fila, + t.Lotto, + t.Prodotto, + t.Descrizione + FROM BASE b + LEFT JOIN dbo.vXTracciaProdotti AS t + ON t.Pallet COLLATE Latin1_General_CI_AS = LEFT(b.UDC, 6) COLLATE Latin1_General_CI_AS +) +SELECT + j.IDCella, + UPPER( + CONCAT( + COALESCE(LTRIM(RTRIM(j.Corsia)), 'NA'), '.', + COALESCE(LTRIM(RTRIM(CAST(j.Colonna AS varchar(32)))), 'NA'), '.', + COALESCE(LTRIM(RTRIM(CAST(j.Fila AS varchar(32)))), 'NA') + ) + ) AS Ubicazione, + j.UDC, + j.Lotto, + j.Prodotto, + j.Descrizione +FROM JOINED j +WHERE 1=1 + AND ( :udc IS NULL OR j.UDC COLLATE Latin1_General_CI_AS LIKE CONCAT('%', :udc, '%') ) + AND ( :lotto IS NULL OR j.Lotto COLLATE Latin1_General_CI_AS LIKE CONCAT('%', :lotto, '%') ) + AND ( :codice IS NULL OR j.Prodotto COLLATE Latin1_General_CI_AS LIKE CONCAT('%', :codice, '%') ) +ORDER BY + CASE WHEN j.IDCella = 9999 THEN 1 ELSE 0 END, + j.Corsia, j.Colonna, j.Fila, j.UDC, j.Lotto, j.Prodotto; +""" + +class SearchWindow(ctk.CTkToplevel): + def __init__(self, parent: tk.Widget, db_app): + super().__init__(parent) + self.title("Warehouse · Ricerca UDC/Lotto/Codice") + self.geometry("1100x720") + self.minsize(900, 560) + self.resizable(True, True) + + self.db = db_app + self._busy = BusyOverlay(self) + self._async = AsyncRunner(self) + + # stato ordinamento colonne (col -> reverse bool) + self._sort_state: dict[str, bool] = {} + + self._build_ui() + + def _build_ui(self): + # layout griglia principale + self.grid_rowconfigure(0, weight=0) + self.grid_rowconfigure(1, weight=1) + self.grid_columnconfigure(0, weight=1) + + # --- barra di ricerca --- + top = ctk.CTkFrame(self) + top.grid(row=0, column=0, sticky="nsew", padx=8, pady=8) + for i in range(8): + top.grid_columnconfigure(i, weight=0) + top.grid_columnconfigure(7, weight=1) + + ctk.CTkLabel(top, text="UDC:").grid(row=0, column=0, sticky="w") + self.var_udc = tk.StringVar() + e_udc = ctk.CTkEntry(top, textvariable=self.var_udc, width=160) + e_udc.grid(row=0, column=1, sticky="w", padx=(4, 12)) + + ctk.CTkLabel(top, text="Lotto:").grid(row=0, column=2, sticky="w") + self.var_lotto = tk.StringVar() + e_lotto = ctk.CTkEntry(top, textvariable=self.var_lotto, width=140) + e_lotto.grid(row=0, column=3, sticky="w", padx=(4, 12)) + + ctk.CTkLabel(top, text="Codice prodotto:").grid(row=0, column=4, sticky="w") + self.var_codice = tk.StringVar() + e_cod = ctk.CTkEntry(top, textvariable=self.var_codice, width=160) + e_cod.grid(row=0, column=5, sticky="w", padx=(4, 12)) + + btn = ctk.CTkButton(top, text="Cerca", command=self._do_search) + btn.grid(row=0, column=6, sticky="w") + + btn_exp = ctk.CTkButton(top, text="Esporta XLSX", command=self._export_xlsx) + btn_exp.grid(row=0, column=7, sticky="e") + + # --- griglia risultati --- + wrap = ctk.CTkFrame(self) + wrap.grid(row=1, column=0, sticky="nsew", padx=8, pady=(0, 8)) + wrap.grid_rowconfigure(0, weight=1) + wrap.grid_columnconfigure(0, weight=1) + + # forza modalità Treeview (niente tksheet) per stabilità + self.use_sheet = False + + cols = ("IDCella", "Ubicazione", "UDC", "Lotto", "Codice", "Descrizione") + self.tree = ttk.Treeview(wrap, columns=cols, show="headings") + # stile: zebra + header leggibile + self._style = ttk.Style(self) + try: + self._style.theme_use(self._style.theme_use()) + except Exception: + pass + self._style.configure("Search.Treeview", rowheight=22, font=("", 9)) + self._style.configure("Search.Treeview.Heading", font=("", 9, "bold"), background="#F3F4F6") + self._style.map("Search.Treeview", background=[("selected", "#DCEBFF")]) + self.tree.configure(style="Search.Treeview") + # tag per righe alternate + id9999 evidenziate + self.tree.tag_configure("even", background="#FFFFFF") + self.tree.tag_configure("odd", background="#F7F9FC") + # evidenzia spediti (IDCella=9999) in rosato tenue + self.tree.tag_configure("id9999", background="#FFECEC", foreground="#B00020") + + sy = ttk.Scrollbar(wrap, orient="vertical", command=self.tree.yview) + sx = ttk.Scrollbar(wrap, orient="horizontal", command=self.tree.xview) + self.tree.configure(yscrollcommand=sy.set, xscrollcommand=sx.set) + + self.tree.grid(row=0, column=0, sticky="nsew") + sy.grid(row=0, column=1, sticky="ns") + sx.grid(row=1, column=0, sticky="ew") + + # doppio click → copia UDC; gestione click header + self.tree.bind("", self._on_dclick) + self.tree.bind("", self._maybe_handle_heading_click, add=True) + self.tree.bind("", self._on_heading_double_click, add=True) + + def _apply_zebra(self): + for i, iid in enumerate(self.tree.get_children("")): + vals = self.tree.item(iid, "values") + zebra = "even" if i % 2 == 0 else "odd" + is9999 = False + if vals: + try: + is9999 = int(vals[0]) == 9999 + except Exception: + is9999 = False + tags = ("id9999", zebra) if is9999 else (zebra,) + self.tree.item(iid, tags=tags) + + # ---------------- AZIONI ---------------- + def _export_xlsx(self): + # raccogli dati dalla griglia + rows = [] + for iid in self.tree.get_children(""): + rows.append(self.tree.item(iid, "values")) + if not rows: + messagebox.showinfo("Esporta", "Non ci sono righe da esportare.", parent=self) + return + if not _HAS_XLSX: + messagebox.showerror("Esporta", "Per l'esportazione serve 'openpyxl' (pip install openpyxl).", parent=self) + return + # dialog salvataggio + from datetime import datetime + ts = datetime.now().strftime("%d_%m_%Y_%H-%M") + default_name = f"esportazione_ricerca_{ts}.xlsx" + fname = filedialog.asksaveasfilename(parent=self, title="Esporta in Excel", + defaultextension=".xlsx", + filetypes=[("Excel Workbook","*.xlsx")], + initialfile=default_name) + if not fname: + return + # crea workbook e scrivi + try: + wb = Workbook() + ws = wb.active + ws.title = "Risultati" + headers = ("IDCella","Ubicazione","UDC","Lotto","Codice","Descrizione") + for j, h in enumerate(headers, start=1): + c = ws.cell(row=1, column=j, value=h) + c.font = Font(bold=True) + c.alignment = Alignment(horizontal="center", vertical="center") + r = 2 + for row in rows: + for j, v in enumerate(row, start=1): + ws.cell(row=r, column=j, value=v) + r += 1 + # autosize + widths = {} + for row in ws.iter_rows(values_only=True): + for j, val in enumerate(row, start=1): + s = "" if val is None else str(val) + widths[j] = max(widths.get(j, 0), len(s)) + from openpyxl.utils import get_column_letter + for j, w in widths.items(): + ws.column_dimensions[get_column_letter(j)].width = min(max(w + 2, 10), 60) + wb.save(fname) + messagebox.showinfo("Esporta", f"File creato:\n{fname}", parent=self) + + except Exception as ex: + messagebox.showerror("Esporta", f"Errore durante l'esportazione:{ex}", parent=self) + def _on_dclick(self, evt): + # copia UDC solo se il doppio click avviene su una cella, non sull'header + region = self.tree.identify("region", evt.x, evt.y) + if region != "cell": + return + sel = self.tree.focus() + if not sel: + return + vals = self.tree.item(sel, "values") + if len(vals) >= 3 and vals[2]: # UDC + try: + self.clipboard_clear() + self.clipboard_append(vals[2]) + except Exception: + pass + + # --------------- ORDINAMENTO COLONNE --------------- + def _maybe_handle_heading_click(self, evt): + # evita che il click sulle intestazioni selezioni una riga fantasma + region = self.tree.identify("region", evt.x, evt.y) + if region == "heading": + return "break" + + def _on_heading_double_click(self, evt): + # doppio click su intestazione: ordina la colonna corrispondente + region = self.tree.identify("region", evt.x, evt.y) + if region != "heading": + return + col_id = self.tree.identify_column(evt.x) # es. '#1' + try: + idx = int(col_id.replace('#','')) - 1 + except Exception: + return "break" + cols = ("IDCella", "Ubicazione", "UDC", "Lotto", "Codice", "Descrizione") + if 0 <= idx < len(cols): + self._sort_by_column(cols[idx]) + return "break" + + def _sort_key_for_col(self, col: str, val: str): + if val is None: + return (1, "") # None in fondo + s = str(val) + if col in ("IDCella",): + # prova numero + try: + return (0, int(s)) + except Exception: + return (0, s.lower()) + if col in ("Lotto", "Codice", "UDC"): + return (0, s.lower()) + return (0, s.lower()) + + def _sort_by_column(self, col: str): + try: + # toggle reverse + rev = self._sort_state.get(col, False) + self._sort_state[col] = not rev + + # raccogli dati correnti + rows = [] + for iid in self.tree.get_children(""): + vals = self.tree.item(iid, "values") + row = {"iid": iid, + "IDCella": vals[0] if len(vals) > 0 else None, + "Ubicazione": vals[1] if len(vals) > 1 else None, + "UDC": vals[2] if len(vals) > 2 else None, + "Lotto": vals[3] if len(vals) > 3 else None, + "Codice": vals[4] if len(vals) > 4 else None, + "Descrizione": vals[5] if len(vals) > 5 else None} + rows.append(row) + + rows.sort(key=lambda r: self._sort_key_for_col(col, r.get(col)), reverse=rev) + + def _apply_moves(): + for index, r in enumerate(rows): + self.tree.move(r["iid"], "", index) + # aggiorna indicatori visuali nelle heading (▲/▼) + for k in ("IDCella","Ubicazione","UDC","Lotto","Codice","Descrizione"): + base = { + "IDCella": "IDCella", + "Ubicazione": "Ubicazione", + "UDC": "UDC / Barcode", + "Lotto": "Lotto", + "Codice": "Codice prodotto", + "Descrizione": "Descrizione prodotto", + }[k] + if k == col: + arrow = " ▼" if not rev else " ▲" + self.tree.heading(k, text=base + arrow) + else: + self.tree.heading(k, text=base) + self._apply_zebra() + + # posticipa le move per evitare re‑entrancy su doppio click + self.after_idle(_apply_moves) + except Exception: + # in caso di problemi non lasciamo la finestra in stato incoerente + pass + + # --- ordinamento per tksheet (doppio click sui titoli) --- + def _on_sheet_header_double_click(self, event_dict): + try: + c = event_dict.get("column") + except Exception: + return + if c is None: + return + headers = ["IDCella","Ubicazione","UDC","Lotto","Codice","Descrizione"] + if not (0 <= c < len(headers)): + return + colname = headers[c] + rev = self._sort_state.get(colname, False) + self._sort_state[colname] = not rev + + data = self.sheet.get_sheet_data(return_copy=True) + def keyf(row): + val = row[c] if c < len(row) else None + if val is None: + return (1, "") + s = str(val) + if colname == "IDCella": + try: + return (0, int(s)) + except Exception: + return (0, s.lower()) + return (0, s.lower()) + data.sort(key=keyf, reverse=rev) + self.sheet.set_sheet_data(data) + # evidenzia l'header ordinato (semplice: cambia testo temporaneamente) + try: + arrow = " ▼" if not rev else " ▲" + hdrs = list(headers) + hdrs[c] = hdrs[c] + arrow + self.sheet.headers(hdrs) + except Exception: + pass + + def _do_search(self): + udc = (self.var_udc.get() or "").strip() + lotto = (self.var_lotto.get() or "").strip() + codice = (self.var_codice.get() or "").strip() + + # Se nessun filtro, chiedi conferma (evita estrazione enorme non voluta) + if not (udc or lotto or codice): + if not messagebox.askyesno( + "Conferma", + "Nessun filtro impostato. Vuoi cercare su TUTTO il magazzino?", + parent=self, + ): + return + + # Parametri: passa NULL se campo vuoto -> i filtri "si spengono" + params = { + "udc": (udc if udc else None), + "lotto": (lotto if lotto else None), + "codice": (codice if codice else None), + } + + def _ok(res): + rows = res.get("rows", []) if isinstance(res, dict) else [] + # --- popola UI --- + if self.use_sheet: + try: + data = [] + for r in rows: + idc, ubi, udc_v, lot_v, cod_v, desc_v = r + data.append([idc, ubi, udc_v, lot_v, cod_v, desc_v]) + self.sheet.set_sheet_data(data) + self.sheet.set_all_cell_sizes_to_text() + except Exception as ex: + # fallback di sicurezza su Treeview + self.use_sheet = False + if not self.use_sheet: + # Treeview + for iid in self.tree.get_children(): + self.tree.delete(iid) + for idx, r in enumerate(rows): + idc, ubi, udc_v, lot_v, cod_v, desc_v = r + zebra = "even" if idx % 2 == 0 else "odd" + try: + is9999 = int(idc) == 9999 + except Exception: + is9999 = False + tags = ("id9999", zebra) if is9999 else (zebra,) + self.tree.insert("", "end", values=(idc, ubi, udc_v, lot_v, cod_v, desc_v), tags=tags) + + # --- feedback utente --- + if not rows: + messagebox.showinfo( + "Nessun risultato", + "Nessuna corrispondenza trovata con le chiavi di ricerca inserite.", + parent=self, + ) + else: + # reset campi se risultato non vuoto + self.var_udc.set("") + self.var_lotto.set("") + self.var_codice.set("") + self._busy.hide() + + def _err(ex): + self._busy.hide() + messagebox.showerror("Errore ricerca", str(ex), parent=self) + + self._async.run(self.db.query_json(SQL_SEARCH, params), _ok, _err, busy=self._busy, message="Cerco…") + + +def open_search_window(parent, db_app): + key = "_search_window_singleton" + ex = getattr(parent, key, None) + if ex and ex.winfo_exists(): + try: + ex.lift(); ex.focus_force(); return ex + except Exception: + pass + w = SearchWindow(parent, db_app) + setattr(parent, key, w) + return w diff --git a/sql.txt b/sql.txt new file mode 100644 index 0000000..4930170 --- /dev/null +++ b/sql.txt @@ -0,0 +1,21 @@ +SELECT * FROM vViewMappaturaDescrizioneCorsia WHERE ( Area > 0) ORDER BY Area Desc + +SELECT * FROM Celle WHERE ( ID > 0) ORDER BY ID Desc + +SELECT TOP 1000 [mc_X] ,[minFila] ,[maxFila] ,[minColonna] ,[maxColonna] ,[mc_Numero_Magazzino] ,[mc_Numero_Area] FROM vViewMappaturaPosizCorsia WHERE [mc_Numero_Area] =1 + + +SELECT CASE WHEN Nota = 'ASC' THEN 0 ELSE CASE WHEN Nota = 'DESC' THEN 1 END END AS Nota FROM MagLayout WHERE (IDArea = 1) + +SELECT ID, Descrizione FROM Magazzini ORDER BY Descrizione + +SELECT ID, Descrizione FROM Celle ORDER BY Descrizione + +SELECT [ID],[CorsiaDescrizione] FROM vViewMappaturaDescrizioneCorsia + + + SELECT COUNT(DISTINCT Pallet) AS Pallet, COUNT(DISTINCT Lotto) AS Lotto, COUNT(DISTINCT Articolo) AS Articolo, COUNT(DISTINCT Descrizione) AS Descrizione, SUM(Qta) AS Qta, Documento, CodNazione, NAZIONE, Stato, MAX(PalletCella) AS PalletCella, MAX(Magazzino) AS Magazzino, MAX(Area) AS Area, MAX(Cella) AS Cella, MIN(Ordinamento) AS Ordinamento, MAX(IDStato) AS IDStato FROM dbo.XMag_ViewPackingList GROUP BY Documento, CodNazione, NAZIONE, Stato + + SELECT * FROM vViewPackingListRestante WHERE Documento = 237 ORDER BY Ordinamento + SELECT * FROM vViewPackingListRestante WHERE Documento = 240 ORDER BY Ordinamento + \ No newline at end of file diff --git a/view_celle_multiple.py b/view_celle_multiple.py new file mode 100644 index 0000000..eff88eb --- /dev/null +++ b/view_celle_multiple.py @@ -0,0 +1,346 @@ +# view_celle_multiple.py +import json +import tkinter as tk +from tkinter import ttk, messagebox, filedialog +import customtkinter as ctk +from datetime import datetime + +from openpyxl import Workbook +from openpyxl.styles import Font, Alignment + +from gestione_aree_frame_async import AsyncRunner + +def _json_obj(res): + if isinstance(res, str): + try: + res = json.loads(res) + except Exception as ex: + raise RuntimeError(f"Risposta non JSON: {ex}\nRaw: {res!r}") + if isinstance(res, dict) and "error" in res: + err = res.get("error") or "Errore sconosciuto" + detail = res.get("sql") or "" + raise RuntimeError(f"{err}\n{detail}") + return res if isinstance(res, dict) else {"rows": res} + +UBI_B = ( + "UPPER(" + " CONCAT(" + " RTRIM(b.Corsia), '.', RTRIM(CAST(b.Colonna AS varchar(32))), '.', RTRIM(CAST(b.Fila AS varchar(32)))" + " )" + ")" +) + +BASE_CTE = """ +WITH base AS ( + SELECT + g.IDCella, + g.BarcodePallet, + RTRIM(c.Corsia) AS Corsia, + c.Colonna, + c.Fila + FROM dbo.XMag_GiacenzaPallet AS g + JOIN dbo.Celle AS c ON c.ID = g.IDCella + WHERE g.IDCella <> 9999 AND RTRIM(c.Corsia) <> '7G' +) +""" + +SQL_CORSIE = BASE_CTE + """ +, dup_celle AS ( + SELECT IDCCella = b.IDCella + FROM base b + GROUP BY b.IDCella + HAVING COUNT(DISTINCT b.BarcodePallet) > 1 +) +SELECT DISTINCT b.Corsia +FROM base b +WHERE EXISTS (SELECT 1 FROM dup_celle d WHERE d.IDCCella = b.IDCella) +ORDER BY b.Corsia; +""" + +SQL_CELLE_DUP_PER_CORSIA = BASE_CTE + f""" +, dup_celle AS ( + SELECT b.IDCella, COUNT(DISTINCT b.BarcodePallet) AS NumUDC + FROM base b + GROUP BY b.IDCella + HAVING COUNT(DISTINCT b.BarcodePallet) > 1 +) +SELECT dc.IDCella, + {UBI_B} AS Ubicazione, + b.Colonna, b.Fila, b.Corsia, + dc.NumUDC +FROM dup_celle dc +JOIN base b ON b.IDCella = dc.IDCella +WHERE b.Corsia = RTRIM(:corsia) +GROUP BY dc.IDCella, {UBI_B}, b.Colonna, b.Fila, b.Corsia, dc.NumUDC +ORDER BY b.Colonna, b.Fila; +""" + +SQL_PALLET_IN_CELLA = BASE_CTE + """ +SELECT + b.BarcodePallet AS Pallet, + ta.Descrizione, + ta.Lotto +FROM base b +OUTER APPLY ( + SELECT TOP (1) t.Descrizione, t.Lotto + FROM dbo.vXTracciaProdotti AS t + WHERE t.Pallet = b.BarcodePallet COLLATE Latin1_General_CI_AS + ORDER BY t.Lotto +) AS ta +WHERE b.IDCella = :idcella +GROUP BY b.BarcodePallet, ta.Descrizione, ta.Lotto +ORDER BY b.BarcodePallet; +""" + +SQL_RIEPILOGO_PERCENTUALI = BASE_CTE + """ +, tot AS ( + SELECT b.Corsia, COUNT(DISTINCT b.IDCella) AS TotCelle + FROM base b GROUP BY b.Corsia +), +dup_celle AS ( + SELECT b.Corsia, b.IDCella + FROM base b + GROUP BY b.Corsia, b.IDCella + HAVING COUNT(DISTINCT b.BarcodePallet) > 1 +), +per_corsia AS ( + SELECT t.Corsia, t.TotCelle, COALESCE(d.CelleMultiple, 0) AS CelleMultiple + FROM tot t + LEFT JOIN ( + SELECT Corsia, COUNT(IDCella) AS CelleMultiple + FROM dup_celle GROUP BY Corsia + ) d ON d.Corsia = t.Corsia +), +unione AS ( + SELECT Corsia, TotCelle, CelleMultiple, + CAST(100.0 * CelleMultiple / NULLIF(TotCelle, 0) AS decimal(5,2)) AS Percentuale, + CAST(0 AS int) AS Ord + FROM per_corsia + UNION ALL + SELECT 'TOTALE' AS Corsia, + SUM(TotCelle), SUM(CelleMultiple), + CAST(100.0 * SUM(CelleMultiple) / NULLIF(SUM(TotCelle), 0) AS decimal(5,2)), + CAST(1 AS int) AS Ord + FROM per_corsia +) +SELECT Corsia, TotCelle, CelleMultiple, Percentuale +FROM unione +ORDER BY Ord, Corsia; +""" + +class CelleMultipleWindow(ctk.CTkToplevel): + def __init__(self, root, db_client, runner: AsyncRunner | None = None): + super().__init__(root) + self.title("Celle con più pallet") + self.geometry("1100x700"); self.minsize(900,550); self.resizable(True, True) + + self.db = db_client + self.runner = runner or AsyncRunner(self) + + self._build_layout() + self._bind_events() + self.refresh_all() + + def _build_layout(self): + self.grid_rowconfigure(0, weight=5) + self.grid_rowconfigure(1, weight=70) + self.grid_rowconfigure(2, weight=25, minsize=160) + self.grid_columnconfigure(0, weight=1) + + toolbar = ctk.CTkFrame(self); toolbar.grid(row=0, column=0, sticky="nsew") + ctk.CTkButton(toolbar, text="Aggiorna", command=self.refresh_all).pack(side="left", padx=6, pady=4) + ctk.CTkButton(toolbar, text="Espandi tutto", command=self.expand_all).pack(side="left", padx=6, pady=4) + ctk.CTkButton(toolbar, text="Comprimi tutto", command=self.collapse_all).pack(side="left", padx=6, pady=4) + ctk.CTkButton(toolbar, text="Esporta in XLSX", command=self.export_to_xlsx).pack(side="left", padx=6, pady=4) + + f = ctk.CTkFrame(self); f.grid(row=1, column=0, sticky="nsew", padx=6, pady=(0,6)) + f.grid_rowconfigure(0, weight=1); f.grid_columnconfigure(0, weight=1) + self.tree = ttk.Treeview(f, columns=("col2","col3"), show="tree headings", selectmode="browse") + self.tree.heading("#0", text="Nodo"); self.tree.heading("col2", text="Descrizione"); self.tree.heading("col3", text="Lotto") + y = ttk.Scrollbar(f, orient="vertical", command=self.tree.yview) + x = ttk.Scrollbar(f, orient="horizontal", command=self.tree.xview) + self.tree.configure(yscrollcommand=y.set, xscrollcommand=x.set) + self.tree.grid(row=0, column=0, sticky="nsew"); y.grid(row=0, column=1, sticky="ns"); x.grid(row=1, column=0, sticky="ew") + + sumf = ctk.CTkFrame(self) + sumf.grid(row=2, column=0, sticky="nsew", padx=6, pady=(0,6)) + ctk.CTkLabel(sumf, text="Riepilogo % celle multiple per corsia", font=("Segoe UI", 12, "bold")).pack(anchor="w", padx=8, pady=(8, 0)) + inner = ctk.CTkFrame(sumf) + inner.pack(fill="both", expand=True, padx=6, pady=6) + inner.grid_rowconfigure(0, weight=1); inner.grid_columnconfigure(0, weight=1) + self.sum_tbl = ttk.Treeview(inner, columns=("Corsia","TotCelle","CelleMultiple","Percentuale"), show="headings") + for k,t,w,a in (("Corsia","Corsia",100,"center"), + ("TotCelle","Totale celle",120,"e"), + ("CelleMultiple",">1 UDC",120,"e"), + ("Percentuale","%",80,"e")): + self.sum_tbl.heading(k, text=t); self.sum_tbl.column(k, width=w, anchor=a) + y2 = ttk.Scrollbar(inner, orient="vertical", command=self.sum_tbl.yview) + x2 = ttk.Scrollbar(inner, orient="horizontal", command=self.sum_tbl.xview) + self.sum_tbl.configure(yscrollcommand=y2.set, xscrollcommand=x2.set) + self.sum_tbl.grid(row=0, column=0, sticky="nsew"); y2.grid(row=0, column=1, sticky="ns"); x2.grid(row=1, column=0, sticky="ew") + + def _bind_events(self): + self.tree.bind("<>", self._on_open_node) + + def refresh_all(self): + self._load_corsie(); self._load_riepilogo() + + def _load_corsie(self): + self.tree.delete(*self.tree.get_children()) + async def _q(db): return await db.query_json(SQL_CORSIE, as_dict_rows=True) + self.runner.run(_q(self.db), self._fill_corsie, lambda e: messagebox.showerror("Errore", str(e), parent=self)) + + def _fill_corsie(self, res): + rows = _json_obj(res).get("rows", []) + for r in rows: + corsia = r.get("Corsia"); + if not corsia: continue + node_id = f"corsia:{corsia}" + self.tree.insert("", "end", iid=node_id, text=f"Corsia {corsia}", values=("", ""), open=False, tags=("corsia",)) + self.tree.insert(node_id, "end", iid=f"{node_id}::lazy", text="...", values=("", "")) + + def _on_open_node(self, _evt): + sel = self.tree.focus() + if not sel: return + if sel.startswith("corsia:"): + lazy_id = f"{sel}::lazy" + if lazy_id in self.tree.get_children(sel): + self.tree.delete(lazy_id) + corsia = sel.split(":",1)[1] + self._load_celle_for_corsia(sel, corsia) + elif sel.startswith("cella:"): + lazy_id = f"{sel}::lazy" + if lazy_id in self.tree.get_children(sel): + self.tree.delete(lazy_id) + idcella = int(sel.split(":",1)[1]) + for child in self.tree.get_children(sel): + self.tree.delete(child) + self._load_pallet_for_cella(sel, idcella) + + def _load_celle_for_corsia(self, parent_iid, corsia): + async def _q(db): return await db.query_json(SQL_CELLE_DUP_PER_CORSIA, params={"corsia": corsia}, as_dict_rows=True) + self.runner.run(_q(self.db), lambda res: self._fill_celle(parent_iid, res), + lambda e: messagebox.showerror("Errore", str(e), parent=self)) + + def _fill_celle(self, parent_iid, res): + rows = _json_obj(res).get("rows", []) + if not rows: + self.tree.insert(parent_iid, "end", text="(nessuna cella con >1 UDC)", values=("", "")); return + for r in rows: + idc = r["IDCella"]; ubi = r["Ubicazione"]; corsia = r.get("Corsia"); num = r.get("NumUDC", 0) + node_id = f"cella:{idc}"; label = f"{ubi} [x{num}]" + if self.tree.exists(node_id): + self.tree.item(node_id, text=label, values=(f"IDCella {idc}", "")) + else: + self.tree.insert(parent_iid, "end", iid=node_id, text=label, + values=(f"IDCella {idc}", ""), open=False, tags=("cella", f"corsia:{corsia}")) + if not any(ch.endswith("::lazy") for ch in self.tree.get_children(node_id)): + self.tree.insert(node_id, "end", iid=f"{node_id}::lazy", text="...", values=("", "")) + + def _load_pallet_for_cella(self, parent_iid, idcella: int): + async def _q(db): return await db.query_json(SQL_PALLET_IN_CELLA, params={"idcella": idcella}, as_dict_rows=True) + self.runner.run(_q(self.db), lambda res: self._fill_pallet(parent_iid, res), + lambda e: messagebox.showerror("Errore", str(e), parent=self)) + + def _fill_pallet(self, parent_iid, res): + rows = _json_obj(res).get("rows", []) + if not rows: + self.tree.insert(parent_iid, "end", text="(nessun pallet)", values=("", "")); return + parent_tags = self.tree.item(parent_iid, "tags") or () + corsia_tag = next((t for t in parent_tags if t.startswith("corsia:")), None) + corsia_val = corsia_tag.split(":",1)[1] if corsia_tag else "" + cella_ubi = self.tree.item(parent_iid, "text") + idcella_txt = self.tree.item(parent_iid, "values")[0] + idcella_num = int(idcella_txt.split()[-1]) if idcella_txt else None + + for r in rows: + pallet = r.get("Pallet", ""); desc = r.get("Descrizione", ""); lotto = r.get("Lotto", "") + leaf_id = f"pallet:{idcella_num}:{pallet}" + if self.tree.exists(leaf_id): + self.tree.item(leaf_id, text=str(pallet), values=(desc, lotto)); continue + self.tree.insert(parent_iid, "end", iid=leaf_id, text=str(pallet), + values=(desc, lotto), + tags=("pallet", f"corsia:{corsia_val}", f"ubicazione:{cella_ubi}", f"idcella:{idcella_num}")) + + def _load_riepilogo(self): + async def _q(db): return await db.query_json(SQL_RIEPILOGO_PERCENTUALI, as_dict_rows=True) + self.runner.run(_q(self.db), self._fill_riepilogo, lambda e: messagebox.showerror("Errore", str(e), parent=self)) + + def _fill_riepilogo(self, res): + rows = _json_obj(res).get("rows", []) + for i in self.sum_tbl.get_children(): self.sum_tbl.delete(i) + for r in rows: + self.sum_tbl.insert("", "end", values=(r.get("Corsia"), r.get("TotCelle",0), + r.get("CelleMultiple",0), f"{r.get('Percentuale',0):.2f}")) + + def expand_all(self): + for iid in self.tree.get_children(""): + self.tree.item(iid, open=True) + if f"{iid}::lazy" in self.tree.get_children(iid): + self.tree.delete(f"{iid}::lazy") + corsia = iid.split(":",1)[1] + self._load_celle_for_corsia(iid, corsia) + + def collapse_all(self): + for iid in self.tree.get_children(""): + self.tree.item(iid, open=False) + + def export_to_xlsx(self): + ts = datetime.now().strftime("%d_%m_%Y_%H-%M") + default_name = f"esportazione_celle_udc_multiple_{ts}.xlsx" + fname = filedialog.asksaveasfilename(parent=self, title="Esporta in Excel", + defaultextension=".xlsx", + filetypes=[("Excel Workbook","*.xlsx")], + initialfile=default_name) + if not fname: return + try: + wb = Workbook() + ws_det = wb.active; ws_det.title = "Dettaglio" + ws_sum = wb.create_sheet("Riepilogo") + det_headers = ["Corsia", "Ubicazione", "IDCella", "Pallet", "Descrizione", "Lotto"] + sum_headers = ["Corsia", "TotCelle", "CelleMultiple", "Percentuale"] + def _hdr(ws, headers): + for j,h in enumerate(headers, start=1): + cell = ws.cell(row=1, column=j, value=h) + cell.font = Font(bold=True); cell.alignment = Alignment(horizontal="center", vertical="center") + _hdr(ws_det, det_headers); _hdr(ws_sum, sum_headers) + + r = 2 + for corsia_node in self.tree.get_children(""): + for cella_node in self.tree.get_children(corsia_node): + for pallet_node in self.tree.get_children(cella_node): + tags = self.tree.item(pallet_node, "tags") or () + if "pallet" not in tags: continue + corsia = next((t.split(":",1)[1] for t in tags if t.startswith("corsia:")), "") + ubi = next((t.split(":",1)[1] for t in tags if t.startswith("ubicazione:")), "") + idcella = next((t.split(":",1)[1] for t in tags if t.startswith("idcella:")), "") + pallet = self.tree.item(pallet_node, "text") + desc, lotto = self.tree.item(pallet_node, "values") + for j,v in enumerate([corsia, ubi, idcella, pallet, desc, lotto], start=1): + ws_det.cell(row=r, column=j, value=v) + r += 1 + + r2 = 2 + for iid in self.sum_tbl.get_children(""): + vals = self.sum_tbl.item(iid, "values") + for j, v in enumerate(vals, start=1): + ws_sum.cell(row=r2, column=j, value=v) + r2 += 1 + + def _autosize(ws): + widths = {} + for row in ws.iter_rows(values_only=True): + for j, val in enumerate(row, start=1): + val_s = "" if val is None else str(val) + widths[j] = max(widths.get(j, 0), len(val_s)) + from openpyxl.utils import get_column_letter + for j, w in widths.items(): + ws.column_dimensions[get_column_letter(j)].width = min(max(w + 2, 10), 60) + + _autosize(ws_det); _autosize(ws_sum) + wb.save(fname); messagebox.showinfo("Esportazione completata", f"File creato:\n{fname}", parent=self) + except Exception as ex: + messagebox.showerror("Errore esportazione", str(ex), parent=self) + +def open_celle_multiple_window(root: tk.Tk, db_client, runner: AsyncRunner | None = None): + win = CelleMultipleWindow(root, db_client, runner=runner); win.lift(); win.focus_set(); return win diff --git a/warehouse_sp_python.py b/warehouse_sp_python.py new file mode 100644 index 0000000..22de872 --- /dev/null +++ b/warehouse_sp_python.py @@ -0,0 +1,1712 @@ +""" +Auto-generated Python port of selected SQL Server stored procedures. +Generated on 2025-10-14T11:57:38 +Notes: +- Synchronous, no threading/async. +- Uses pyodbc; install with `pip install pyodbc` and configure the connection string. +- Two procedures are fully implemented in Python (inserting into Log tables). +- All other procedures are provided as stubs with signatures and embedded T-SQL (for later porting). +""" +from __future__ import annotations +from dataclasses import dataclass +from typing import Optional, Tuple, Any +from datetime import datetime +import pyodbc + + +@dataclass +class SPResult: + message: str | None = None + id_result: int | None = None + + +def get_connection() -> pyodbc.Connection: + """Return a new pyodbc connection. + Adjust the connection string to your environment (Trusted Connection or SQL Auth). + """ + conn = pyodbc.connect( + 'DRIVER={ODBC Driver 18 for SQL Server};' + 'SERVER=localhost;' 'DATABASE=Mediseawall;' 'Trusted_Connection=Yes;' 'TrustServerCertificate=Yes;' + ) + conn.autocommit = False + return conn + + +def _insert_log(table: str, code: str | None, description: str | None) -> Tuple[int, str]: + """Internal helper used by log_operation/log_packing_list.""" + sql = f'''INSERT INTO {table} (Code, Description, IDInsUser, InsDateTime) + VALUES (?, ?, ?, GETDATE()); SELECT SCOPE_IDENTITY();''' + with get_connection() as cxn: + try: + cur = cxn.cursor() + cur.execute(sql, code, description, 1) + new_id = int(cur.fetchval()) + cxn.commit() + return new_id, '' + except Exception as e: + cxn.rollback() + return -1, str(e) + + +def sp_LogOperation(ID: int | None, Code: str | None, Description: str | None) -> SPResult: + """Python port of [dbo].[sp_LogOperation].""" + new_id, err = _insert_log('LogOperation', Code, Description) + if new_id < 0: + return SPResult(message=err, id_result=None) + return SPResult(message='', id_result=new_id) + +def sp_LogPackingList(ID: int | None, Code: str | None, Description: str | None) -> SPResult: + """Python port of [dbo].[sp_LogPackingList].""" + new_id, err = _insert_log('LogPackingList', Code, Description) + if new_id < 0: + return SPResult(message=err, id_result=None) + return SPResult(message='', id_result=new_id) + +def BackupDB() -> Any: + """ + Python stub for [dbo].[BackupDB]. + Original T-SQL for reference: + + BEGIN + -- SET NOCOUNT ON added to prevent extra result sets from + -- interfering with SELECT statements. + SET NOCOUNT ON; + + + + + + DECLARE @RC int + DECLARE @ID int + DECLARE @Descrizione varchar(32) + DECLARE @IDArea int + DECLARE @IDDimensione int + DECLARE @IDStato int + DECLARE @Ordinamento float + DECLARE @X int + DECLARE @Y int + DECLARE @Z int + DECLARE @Corsia varchar(8) + DECLARE @Colonna varchar(8) + DECLARE @Fila varchar(8) + DECLARE @PortataMassimaCella float + DECLARE @PortataMassimaColonna float + DECLARE @UnitaVolumeOccupata float + DECLARE @InsUtente varchar(50) + DECLARE @InsDataOra datetime + DECLARE @ModUtente varchar(50) + DECLARE @ModDataOra datetime + + SET @IDArea = 4 + SET @Corsia = ' 4D' + SET @Colonna = '18' + + + SET @ID = 1001 + SET @Descrizione = '1001 : : 1A - 1 - a' + SET @IDDimensione = 1 + SET @IDStato = 1 + SET @Ordinamento = 1.001000000000000e+003 + SET @X = 0 + SET @Y = 0 + SET @Z = 0 + + SET @PortataMassimaCella = 0.000000000000000e+000 + SET @PortataMassimaColonna = 0.000000000000000e+000 + SET @UnitaVolumeOccupata = 0.000000000000000e+000 + SET @InsUtente = 'raf' + SET @InsDataOra = 'set 11 2010 5:25PM' + SET @ModUtente = 'raf' + SET @ModDataOra = 'set 11 2010 5:25PM' + + declare @IDDaCopiare int + declare @IDNuovo int + + -- Imposta valori dei parametri + + -- Declare an inner cursor based + -- on au_id from the outer cursor. + + DECLARE IDDaCopiare_cursor CURSOR FOR + + + SELECT [ID] + FROM [MediSeawall].[dbo].[Aree] + + OPEN IDDaCopiare_cursor + FETCH NEXT FROM IDDaCopiare_cursor INTO @IDDaCopiare + + WHILE @@FETCH_STATUS = 0 + BEGIN + + if @IDDaCopiare = 1 BEGIN SET @Fila = 'a' END + if @IDDaCopiare = 2 BEGIN SET @Fila = 'b' END + if @IDDaCopiare = 3 BEGIN SET @Fila = 'c' END + if @IDDaCopiare = 4 BEGIN SET @Fila = 'd' END + if @IDDaCopiare = 5 BEGIN SET @Fila = 'e' END + if @IDDaCopiare = 6 BEGIN SET @Fila = 'f' END + + SET @ID = (SELECT MAX([ID]) FROM [MediSeawall].[dbo].[Celle]) + 1 + + SET @Descrizione = CAST(@ID as char(4)) + ' cors: ' + @Corsia+ ' col: ' + @Colonna + ' fil: ' + @Fila + + EXECUTE @RC = [MediSeawall].[dbo].[spt_SaveCelle] + @ID + ,@Descrizione + ,@IDArea + ,@IDDimensione + ,@IDStato + ,@Ordinamento + ,@X + ,@Y + ,@Z + ,@Corsia + ,@Colonna + ,@Fila + ,@PortataMassimaCella + ,@PortataMassimaColonna + ,@UnitaVolumeOccupata + ,@InsUtente + ,@InsDataOra + ,@ModUtente + ,@ModDataOra + + FETCH NEXT FROM IDDaCopiare_cursor INTO @IDDaCopiare + + END + + CLOSE IDDaCopiare_cursor + DEALLOCATE IDDaCopiare_cursor + + + + + + END + """ + raise NotImplementedError('Port this stored procedure logic into Python (step-by-step SQL).') + +def CreaNuoveCelleMDE6(Locazioni: int | None, Piani: int | None) -> Any: + """ + Python stub for [dbo].[CreaNuoveCelleMDE6]. + Original T-SQL for reference: + + char(4)) + ' cors: ' + @Corsia+ ' col: ' + @Colonna + ' fil: ' + @Fila + + EXECUTE @RC = [MediSeawall].[dbo].[spt_SaveCelle] + @ID + ,@Descrizione + ,@IDArea + ,@IDDimensione + ,@IDStato + ,@Ordinamento + ,@X + ,@Y + ,@Z + ,@Corsia + ,@Colonna + ,@Fila + ,@PortataMassimaCella + ,@PortataMassimaColonna + ,@UnitaVolumeOccupata + ,@InsUtente + ,@InsDataOra + ,@ModUtente + ,@ModDataOra + + + + SET @Piani_value = @Piani_value + 1 + END; + + SET @site_value = @site_value + 1 + END; + + + + FETCH NEXT FROM IDDaCopiare_cursor INTO @IDDaCopiare + + END + + CLOSE IDDaCopiare_cursor + DEALLOCATE IDDaCopiare_cursor + + + + + + END + """ + raise NotImplementedError('Port this stored procedure logic into Python (step-by-step SQL).') + +def CreaNuoveCelleMDE6_BIS(Locazioni: int | None, Piani: int | None) -> Any: + """ + Python stub for [dbo].[CreaNuoveCelleMDE6_BIS]. + Original T-SQL for reference: + + char(4)) + ' cors: ' + @Corsia+ ' col: ' + @Colonna + ' fil: ' + @Fila + + EXECUTE @RC = [MediSeawall].[dbo].[spt_SaveCelle] + @ID + ,@Descrizione + ,@IDArea + ,@IDDimensione + ,@IDStato + ,@Ordinamento + ,@X + ,@Y + ,@Z + ,@Corsia + ,@Colonna + ,@Fila + ,@PortataMassimaCella + ,@PortataMassimaColonna + ,@UnitaVolumeOccupata + ,@InsUtente + ,@InsDataOra + ,@ModUtente + ,@ModDataOra + + + + SET @Piani_value = @Piani_value + 1 + END; + + SET @site_value = @site_value + 1 + END; + + + + FETCH NEXT FROM IDDaCopiare_cursor INTO @IDDaCopiare + + END + + CLOSE IDDaCopiare_cursor + DEALLOCATE IDDaCopiare_cursor + """ + raise NotImplementedError('Port this stored procedure logic into Python (step-by-step SQL).') + +def CreateLoopArea(IDArea: int | None) -> Any: + """ + Python stub for [dbo].[CreateLoopArea]. + Original T-SQL for reference: + + BEGIN + -- SET NOCOUNT ON added to prevent extra result sets from + -- interfering with SELECT statements. + SET NOCOUNT ON; + + + DECLARE @ElaboraLoopBarcodePallet varchar(16) + + DECLARE @RC int + DECLARE @IDOperatore int + DECLARE @BarcodeCella varchar(8) = '9000000' + DECLARE @BarcodePallet varchar(16) = '' + DECLARE @NumeroCella int = 9000000 + + DECLARE ElaboraLoopBarcodePallet_cursor CURSOR FOR + + SELECT [BarcodePallet] + FROM [dbo].[XMag_GiacenzaPallet] + WHERE [IDArea] = @IDArea + + + OPEN ElaboraLoopBarcodePallet_cursor + FETCH NEXT FROM ElaboraLoopBarcodePallet_cursor INTO @ElaboraLoopBarcodePallet + + WHILE @@FETCH_STATUS = 0 + BEGIN + + SET @BarcodePallet = @ElaboraLoopBarcodePallet + + -- TODO: impostare qui i valori dei parametri. + + EXECUTE @RC = [sp_xMagGestioneMagazziniPallet] + @IDOperatore + ,@BarcodeCella + ,@BarcodePallet + ,@NumeroCella + ,@RC OUTPUT + + FETCH NEXT FROM ElaboraLoopBarcodePallet_cursor INTO @ElaboraLoopBarcodePallet + + END + + CLOSE ElaboraLoopBarcodePallet_cursor + DEALLOCATE ElaboraLoopBarcodePallet_cursor + + + END + """ + raise NotImplementedError('Port this stored procedure logic into Python (step-by-step SQL).') + +def sp_ControllaPrenotazionePackingListPallet(RC: str | None) -> Any: + """ + Python stub for [dbo].[sp_ControllaPrenotazionePackingListPallet]. + Original T-SQL for reference: + + BEGIN + + + DECLARE @RC int = 0 + DECLARE @IDOperatore int = 0 + DECLARE @IDCellaPrenotata int = 0 + DECLARE @Documento varchar(8) = '' + DECLARE @Nominativo varchar(50) = '' + + SELECT TOP 1 @IDCellaPrenotata = [ID] + FROM [Mediseawall].[dbo].[Celle] + where IDStato = 1 + + SELECT TOP 1 @Documento = Documento, @IDCellaPrenotata = Cella + FROM XMag_ViewPackingList + GROUP BY Documento, IDStato,Cella + HAVING (IDStato = 1) + --and Cella <> 9999 + AND Documento IN (SELECT TOP (1) CAST([Code] as int) FROM [LogPackingList] ORDER By ID DESC) + ORDer BY Documento + + IF @IDCellaPrenotata > 0 + BEGIN + + SELECT TOP 1 @Nominativo = [ModUtente] + FROM [Mediseawall].[dbo].[Celle] + where IDStato = 1 + + SELECT @IDOperatore = ID FROM Operatori WHERE LOGIN = @Nominativo + + --SELECT TOP 1 @Documento = Documento + --FROM dbo.XMag_ViewPackingList + --GROUP BY Documento, CodNazione, NAZIONE, Stato, Magazzino, Area, Cella + --HAVING (Cella = @IDCellaPrenotata) + + SELECT TOP 1 @Documento = Documento + FROM XMag_ViewPackingList + GROUP BY Documento, IDStato + HAVING (IDStato = 1) + AND Documento IN (SELECT TOP (1) CAST([Code] as int) FROM [LogPackingList] ORDER By ID DESC) + ORDER BY Documento + + EXECUTE [dbo].sp_xExePackingListPalletPrenota + @IDOperatore + ,@Documento + ,@RC OUTPUT + END + + + END + """ + raise NotImplementedError('Port this stored procedure logic into Python (step-by-step SQL).') + +def sp_OrdinaCelle() -> Any: + """ + Python stub for [dbo].[sp_OrdinaCelle]. + Original T-SQL for reference: + + INT) DESC + + OPEN IDDaCopiare_cursor + FETCH NEXT FROM IDDaCopiare_cursor INTO @IDDaCopiare + + WHILE @@FETCH_STATUS = 0 + BEGIN + + SET @IDNuovo = @IDNuovo + 1 + + + UPDATE [MediSeawall].[dbo].[Celle] + SET [Ordinamento] = @IDNuovo + WHERE [ID] = @IDDaCopiare + + FETCH NEXT FROM IDDaCopiare_cursor INTO @IDDaCopiare + + END + + CLOSE IDDaCopiare_cursor + DEALLOCATE IDDaCopiare_cursor + """ + raise NotImplementedError('Port this stored procedure logic into Python (step-by-step SQL).') + +def sp_xExePackingListPallet(IDOperatore: int | None, Documento: str | None, RC: int | None) -> Any: + """ + Python stub for [dbo].[sp_xExePackingListPallet]. + Original T-SQL for reference: + + SET @RC = 0 + -- Recupera operatore + DECLARE @Nominativo varchar(50) + SELECT @Nominativo = LOGIN FROM Operatori WHERE id = @IDOperatore + DECLARE @Stato int + PRINT @Nominativo + + + declare @IDDaCopiare int + declare @IDNuovo int + + -- Imposta valori dei parametri + + -- Declare an inner cursor based + -- on au_id from the outer cursor. + + DECLARE IDDaCopiare_cursor CURSOR FOR + + SELECT Cella + FROM dbo.XMag_ViewPackingList + GROUP BY Documento, CodNazione, NAZIONE, Stato, Magazzino, Area, Cella + HAVING (Documento = @Documento) + + OPEN IDDaCopiare_cursor + FETCH NEXT FROM IDDaCopiare_cursor INTO @IDDaCopiare + + WHILE @@FETCH_STATUS = 0 + BEGIN + + SELECT @Stato = [IDStato] FROM [Celle] WHERE [ID] = @IDDaCopiare + + if @Stato = 0 + BEGIN + UPDATE [Celle] SET [IDStato] = 1 + ,[ModUtente] = @Nominativo + ,[ModDataOra] = GETDATE() + WHERE [ID] = @IDDaCopiare + END + ELSE + BEGIN + UPDATE [Celle] SET [IDStato] = 0 + ,[ModUtente] = @Nominativo + ,[ModDataOra] = GETDATE() + WHERE [ID] = @IDDaCopiare + END + + FETCH NEXT FROM IDDaCopiare_cursor INTO @IDDaCopiare + + END + + CLOSE IDDaCopiare_cursor + DEALLOCATE IDDaCopiare_cursor + + + + DECLARE @ID int = 0 + DECLARE @Code varchar(64) = @Documento + DECLARE @Description varchar(255) + DECLARE @Message varchar(255) + DECLARE @IDResult int + + SELECT TOP 1 @Description = NAZIONE + FROM dbo.XMag_ViewPackingList + GROUP by Documento, NAZIONE + HAVING Documento = @Documento + -- TODO: impostare qui i valori dei parametri. + + EXECUTE @RC = [dbo].[sp_LogPackingList] + @ID + ,@Code + ,@Description + ,@Message OUTPUT + ,@IDResult OUTPUT + """ + raise NotImplementedError('Port this stored procedure logic into Python (step-by-step SQL).') + +def sp_xExePackingListPalletPrenota(IDOperatore: int | None, Documento: str | None, RC: int | None) -> Any: + """ + Python stub for [dbo].[sp_xExePackingListPalletPrenota]. + Original T-SQL for reference: + + SET @RC = 0 + -- Recupera operatore + DECLARE @Nominativo varchar(50) + SELECT @Nominativo = LOGIN FROM Operatori WHERE id = @IDOperatore + DECLARE @Stato int + PRINT @Nominativo + + + declare @IDDaCopiare int + declare @IDNuovo int + + -- Imposta valori dei parametri + + -- Declare an inner cursor based + -- on au_id from the outer cursor. + + DECLARE IDDaCopiare_cursor CURSOR FOR + + SELECT Cella + FROM dbo.XMag_ViewPackingList + GROUP BY Documento, CodNazione, NAZIONE, Stato, Magazzino, Area, Cella + HAVING (Documento = @Documento) + + OPEN IDDaCopiare_cursor + FETCH NEXT FROM IDDaCopiare_cursor INTO @IDDaCopiare + + WHILE @@FETCH_STATUS = 0 + BEGIN + + + UPDATE [Celle] SET [IDStato] = 1 + ,[ModUtente] = @Nominativo + ,[ModDataOra] = GETDATE() + WHERE [ID] = @IDDaCopiare + + + + FETCH NEXT FROM IDDaCopiare_cursor INTO @IDDaCopiare + + END + + CLOSE IDDaCopiare_cursor + DEALLOCATE IDDaCopiare_cursor + """ + raise NotImplementedError('Port this stored procedure logic into Python (step-by-step SQL).') + +def sp_xMagGestioneAccettazione(IDOperatore: int | None, BarcodeCella: str | None, BarcodePallet: str | None, NumeroCella: int | None, RC: int | None, Out1: str | None, Out2: str | None, Out3: str | None, Out4: str | None, Out5: str | None, Out6: str | None, Out7: str | None) -> Any: + """ + Python stub for [dbo].[sp_xMagGestioneAccettazione]. + Original T-SQL for reference: + + --DECLARE @RC int + --DECLARE @IDOperatore int + --DECLARE @BarcodeCella varchar(8) + --DECLARE @BarcodePallet varchar(16) + --DECLARE @NumeroCella int + --SET @IDOperatore = 1 + SET @BarcodeCella = '9009000' + --SET @BarcodePallet = '100782' + SET @NumeroCella = 9000 + + SET @RC = 0 + -- Recupera operatore + DECLARE @Output1 varchar(16) + DECLARE @Output2 varchar(16) + DECLARE @Output3 varchar(16) + DECLARE @Output4 varchar(16) + + DECLARE @Nominativo varchar(50) + SELECT @Nominativo = LOGIN FROM Operatori WHERE id = @IDOperatore + + PRINT @Nominativo + PRINT SUBSTRING(@BarcodeCella,1,3) + PRINT SUBSTRING(@BarcodeCella,4,2) + PRINT SUBSTRING(@BarcodeCella,6,2) + -- Cerca la cella + DECLARE @IDMagazzino int ,@IDArea int, @IDCella int, @NumeroPallet int, @NewIDCella int + SET @NewIDCella = -1*(9000000 - @NumeroCella) + SET @IDMagazzino = 0 + SET @IDArea = 0 + SET @IDCella = 0 + SET @NumeroPallet = 0 + + SELECT @IDCella = [ID], @IDArea = [IDArea] + FROM [Celle] + WHERE ID = @NewIDCella + + + + if @IDArea > 0 + BEGIN + -- Magazzino da area + SELECT @IDMagazzino = [IDMagazzino] + FROM [Aree] + WHERE [ID] = @IDArea + END + ELSE + BEGIN + -- se non trova la cella recupera il magazzino di default + SELECT TOP 1 @IDMagazzino = UnitaProduzione.IDMagVersamento + FROM Operatori INNER JOIN UnitaProduzione ON Operatori.CodiceUnita = UnitaProduzione.Codice + WHERE (Operatori.ID = @IDOperatore) + + SELECT @IDCella = [ID], @IDArea = [IDArea] + FROM [Celle] + WHERE ID = 9000 + + END + + -- Recupera il peso unitario e la tara + DECLARE @PesoUnitario float, @Tara float, @IDRiferimento int + SET @PesoUnitario = 1 + SET @Tara = 0 + SET @IDRiferimento = 0 + + SELECT TOP 1 @IDRiferimento = ID + FROM [Accettazione] + WHERE (IDMagazzino = @IDMagazzino) AND (Attributo = @BarcodePallet) -- AND (NumeroPallet = @NumeroPallet) + AND PesoUnitario > 0 AND Tipo = 'V' AND [IDRiferimento] = 0 + ORDER BY ID DESC + + + UPDATE [Accettazione] + SET [ModUtente] = @Nominativo, [ModDataOra] = GETDATE(), [IDRiferimento] = @IDRiferimento + WHERE ID = @IDRiferimento AND [IDRiferimento] = 0 + + --WHERE (IDMagazzino = @IDMagazzino) AND (Attributo = @BarcodePallet) -- AND (NumeroPallet = @NumeroPallet) + --AND PesoUnitario > 0 AND Tipo = 'V' + --ORDER BY ID DESC + + IF @@ROWCOUNT > 0 -- SE TROVO QUALCOSA LA PRELIEVO - Poi Trasferisco in area transito + BEGIN + + INSERT INTO [Accettazione] ([Tipo],[IDRiferimento] ,[NumeroPallet], Attributo + ,[IDMagazzino] ,[IDArea] ,[IDCella] ,[DataMagazzino] + ,[PesoUnitario] ,[Tara],[InsUtente] ,[InsDataOra]) + + + + SELECT 'P' ,@IDRiferimento,[NumeroPallet], Attributo + ,[IDMagazzino] ,[IDArea] ,[IDCella] ,GETDATE() + ,[PesoUnitario],[Tara], @Nominativo ,GETDATE() + FROM [Accettazione] + WHERE ID = @IDRiferimento + + SET @RC = @@IDENTITY + + SELECT @Output2 = Attributo, + @Output3 = [InsUtente] , @Output4 = CAST([InsDataOra] as CHAR(16)) + FROM [Accettazione] + WHERE ID = @IDRiferimento + + + SET @Out1 = 'DIS-ACCETTAZIONE' + SET @Out2 = @Output2 + SET @Out3 = @Output3 + SET @Out4 = @Output4 + SET @Out5 = 5 + SET @Out6 = @BarcodeCella + SET @Out7 = '' + + END + ELSE + BEGIN + + -- VERSA in area cella [IDMagazzino],[IDArea],[IDCella] + INSERT INTO [Accettazione] ([Tipo] ,[IDRiferimento],[NumeroPallet], Attributo + ,[IDMagazzino],[IDArea],[IDCella],[DataMagazzino] + ,[PesoUnitario],[Tara],[InsUtente],[InsDataOra]) + VALUES ('V',@IDRiferimento,@NumeroPallet, @BarcodePallet + ,@IDMagazzino,@IDArea,@IDCella,GETDATE() + ,@PesoUnitario,@Tara,@Nominativo,GETDATE()) + + + SET @RC = @@IDENTITY + + SET @Out1 = 'ACCETTATO' + SET @Out2 = @BarcodePallet + SET @Out3 = @Nominativo + SET @Out4 = CAST(GETDATE() as CHAR(16)) + SET @Out5 = 7 + SET @Out6 = @BarcodeCella + SET @Out7 = '' + + END + """ + raise NotImplementedError('Port this stored procedure logic into Python (step-by-step SQL).') + +def sp_xMagGestioneAccettazioneOLD(IDOperatore: int | None, BarcodeCella: str | None, BarcodePallet: str | None, NumeroCella: int | None, RC: int | None, Out1: str | None, Out2: str | None, Out3: str | None, Out4: str | None, Out5: str | None, Out6: str | None, Out7: str | None) -> Any: + """ + Python stub for [dbo].[sp_xMagGestioneAccettazioneOLD]. + Original T-SQL for reference: + + --DECLARE @RC int + --DECLARE @IDOperatore int + --DECLARE @BarcodeCella varchar(8) + --DECLARE @BarcodePallet varchar(16) + --DECLARE @NumeroCella int + --SET @IDOperatore = 1 + SET @BarcodeCella = '9009000' + --SET @BarcodePallet = '100782' + SET @NumeroCella = 9000 + + SET @RC = 0 + -- Recupera operatore + DECLARE @Output1 varchar(16) + DECLARE @Output2 varchar(16) + DECLARE @Output3 varchar(16) + DECLARE @Output4 varchar(16) + + DECLARE @Nominativo varchar(50) + SELECT @Nominativo = LOGIN FROM Operatori WHERE id = @IDOperatore + + PRINT @Nominativo + PRINT SUBSTRING(@BarcodeCella,1,3) + PRINT SUBSTRING(@BarcodeCella,4,2) + PRINT SUBSTRING(@BarcodeCella,6,2) + -- Cerca la cella + DECLARE @IDMagazzino int ,@IDArea int, @IDCella int, @NumeroPallet int, @NewIDCella int + SET @NewIDCella = -1*(9000000 - @NumeroCella) + SET @IDMagazzino = 0 + SET @IDArea = 0 + SET @IDCella = 0 + SET @NumeroPallet = 0 + + SELECT @IDCella = [ID], @IDArea = [IDArea] + FROM [Celle] + WHERE ID = @NewIDCella + + + + if @IDArea > 0 + BEGIN + -- Magazzino da area + SELECT @IDMagazzino = [IDMagazzino] + FROM [Aree] + WHERE [ID] = @IDArea + END + ELSE + BEGIN + -- se non trova la cella recupera il magazzino di default + SELECT TOP 1 @IDMagazzino = UnitaProduzione.IDMagVersamento + FROM Operatori INNER JOIN UnitaProduzione ON Operatori.CodiceUnita = UnitaProduzione.Codice + WHERE (Operatori.ID = @IDOperatore) + + SELECT @IDCella = [ID], @IDArea = [IDArea] + FROM [Celle] + WHERE ID = 9000 + + END + + -- Recupera il peso unitario e la tara + DECLARE @PesoUnitario float, @Tara float, @IDRiferimento int + SET @PesoUnitario = 1 + SET @Tara = 0 + SET @IDRiferimento = 0 + + SELECT TOP 1 @IDRiferimento = ID + FROM [Accettazione] + WHERE (IDMagazzino = @IDMagazzino) AND (Attributo = @BarcodePallet) -- AND (NumeroPallet = @NumeroPallet) + AND PesoUnitario > 0 AND Tipo = 'V' + ORDER BY ID DESC + + + UPDATE [Accettazione] + SET [ModUtente] = @Nominativo, [ModDataOra] = GETDATE() + WHERE ID = @IDRiferimento + + --WHERE (IDMagazzino = @IDMagazzino) AND (Attributo = @BarcodePallet) -- AND (NumeroPallet = @NumeroPallet) + --AND PesoUnitario > 0 AND Tipo = 'V' + --ORDER BY ID DESC + + IF @@ROWCOUNT > 0 -- SE TROVO QUALCOSA LA PRELIEVO - Poi Trasferisco in area transito + BEGIN + + INSERT INTO [Accettazione] ([Tipo],[IDRiferimento] ,[NumeroPallet], Attributo + ,[IDMagazzino] ,[IDArea] ,[IDCella] ,[DataMagazzino] + ,[PesoUnitario] ,[Tara],[InsUtente] ,[InsDataOra]) + + + + SELECT 'P' ,@IDRiferimento,[NumeroPallet], Attributo + ,[IDMagazzino] ,[IDArea] ,[IDCella] ,GETDATE() + ,[PesoUnitario],[Tara], @Nominativo ,GETDATE() + FROM [Accettazione] + WHERE ID = @IDRiferimento + + SET @RC = @@IDENTITY + + SELECT @Output2 = Attributo, + @Output3 = [InsUtente] , @Output4 = CAST([InsDataOra] as CHAR(16)) + FROM [Accettazione] + WHERE ID = @IDRiferimento + + + SET @Out1 = 'ATTENZIONE' + SET @Out2 = @Output2 + SET @Out3 = @Output3 + SET @Out4 = @Output4 + SET @Out5 = 5 + SET @Out6 = @BarcodeCella + SET @Out7 = '' + + END + ELSE + BEGIN + + -- VERSA in area cella [IDMagazzino],[IDArea],[IDCella] + INSERT INTO [Accettazione] ([Tipo] ,[IDRiferimento],[NumeroPallet], Attributo + ,[IDMagazzino],[IDArea],[IDCella],[DataMagazzino] + ,[PesoUnitario],[Tara],[InsUtente],[InsDataOra]) + VALUES ('V',@IDRiferimento,@NumeroPallet, @BarcodePallet + ,@IDMagazzino,@IDArea,@IDCella,GETDATE() + ,@PesoUnitario,@Tara,@Nominativo,GETDATE()) + + + SET @RC = @@IDENTITY + + SET @Out1 = 'ACCETTATO' + SET @Out2 = @BarcodePallet + SET @Out3 = @Nominativo + SET @Out4 = CAST(GETDATE() as CHAR(16)) + SET @Out5 = 7 + SET @Out6 = @BarcodeCella + SET @Out7 = '' + + END + """ + raise NotImplementedError('Port this stored procedure logic into Python (step-by-step SQL).') + +def sp_xMagGestioneMagazziniPallet(IDOperatore: int | None, BarcodeCella: str | None, BarcodePallet: str | None, NumeroCella: int | None, RC: int | None) -> Any: + """ + Python stub for [dbo].[sp_xMagGestioneMagazziniPallet]. + Original T-SQL for reference: + + --DECLARE @RC int + --DECLARE @IDOperatore int + --DECLARE @BarcodeCella varchar(8) + --DECLARE @BarcodePallet varchar(16) + --DECLARE @NumeroCella int + --SET @IDOperatore = 1 + --SET @BarcodeCella = ' 1056' + --SET @BarcodePallet = '100782' + --SET @NumeroCella = 1056 + + SET @RC = 0 + -- Recupera operatore + DECLARE @Nominativo varchar(50) + SELECT @Nominativo = LOGIN FROM Operatori WHERE id = @IDOperatore + + PRINT @Nominativo + PRINT SUBSTRING(@BarcodeCella,1,3) + PRINT SUBSTRING(@BarcodeCella,4,2) + PRINT SUBSTRING(@BarcodeCella,6,2) + -- Cerca la cella + DECLARE @IDMagazzino int ,@IDArea int, @IDCella int, @NumeroPallet int, @NewIDCella int + SET @NewIDCella = -1*(9000000 - @NumeroCella) + SET @IDMagazzino = 0 + SET @IDArea = 0 + SET @IDCella = 0 + SET @NumeroPallet = 0 + + SELECT @IDCella = [Celle].[ID], @IDArea = [IDArea], @IDMagazzino = IDMagazzino + FROM [Celle], Aree + WHERE [Celle].ID = @NewIDCella + AND IDArea = Aree.ID + + + --WHERE [Corsia] = SUBSTRING(@BarcodeCella,1,3) + -- AND [Colonna] = SUBSTRING(@BarcodeCella,4,2) + -- AND [Fila] = SUBSTRING(@BarcodeCella,6,2) + PRINT @NumeroCella + print 'Barcode Cella' + PRINT @NewIDCella + print 'NEWNumero Cella' + + PRINT @IDCella + print 'Numero Cella' + PRINT @IDArea + print 'Numero @IDArea' + + + DECLARE @ID int + DECLARE @Code varchar(64) + DECLARE @Description varchar(255) + DECLARE @Message varchar(255) + DECLARE @IDResult int + + SET @Code = @BarcodeCella +' - '+ @BarcodePallet +' - '+ CAST(@NumeroCella as CHAR(10)) + SET @Description = CAST(@IDArea as CHAR(10)) +' - '+ CAST(@IDCella as CHAR(10)) + SET @ID= 0 + + -- TODO: impostare qui i valori dei parametri. + + EXECUTE sp_LogOperation @ID ,@Code ,@Description ,@Message OUTPUT ,@IDResult OUTPUT + + SET @ID= @IDResult + + + + if @IDArea > 0 + BEGIN + -- Magazzino da area + SELECT @IDMagazzino = [IDMagazzino] + FROM [Aree] + WHERE [ID] = @IDArea + END + ELSE + BEGIN + -- se non trova la cella recupera il magazzino di default + SELECT TOP 1 @IDMagazzino = UnitaProduzione.IDMagVersamento + FROM Operatori INNER JOIN UnitaProduzione ON Operatori.CodiceUnita = UnitaProduzione.Codice + WHERE (Operatori.ID = @IDOperatore) + + SELECT @IDCella = [Celle].[ID], @IDArea = [IDArea], @IDMagazzino = IDMagazzino + FROM [Celle], Aree + WHERE [Celle].ID = 9999 + AND IDArea = Aree.ID + END + + -- Recupera il peso unitario e la tara + DECLARE @PesoUnitario float, @Tara float, @IDRiferimento int + SET @PesoUnitario = 1 + SET @Tara = 0 + SET @IDRiferimento = 0 + + SELECT TOP 1 @IDRiferimento = ID + FROM [MagazziniPallet] + WHERE 1= 1 + --AND (IDMagazzino = @IDMagazzino) + AND (Attributo = @BarcodePallet) -- AND (NumeroPallet = @NumeroPallet) + AND PesoUnitario > 0 AND Tipo = 'V' + ORDER BY ID DESC + + + UPDATE [MagazziniPallet] + SET [ModUtente] = @Nominativo, [ModDataOra] = GETDATE() + WHERE ID = @IDRiferimento + + --WHERE (IDMagazzino = @IDMagazzino) AND (Attributo = @BarcodePallet) -- AND (NumeroPallet = @NumeroPallet) + --AND PesoUnitario > 0 AND Tipo = 'V' + --ORDER BY ID DESC + + IF @@ROWCOUNT > 0 -- SE TROVO QUALCOSA LA PRELIEVO - Poi Trasferisco in area transito + BEGIN + + INSERT INTO [MagazziniPallet] ([Tipo],[IDRiferimento] ,[NumeroPallet], Attributo + ,[IDMagazzino] ,[IDArea] ,[IDCella] ,[DataMagazzino] + ,[PesoUnitario] ,[Tara],[InsUtente] ,[InsDataOra]) + + SELECT 'P' ,@IDRiferimento,[NumeroPallet], Attributo + ,[IDMagazzino] ,[IDArea] ,[IDCella] ,GETDATE() + ,[PesoUnitario],[Tara], @Nominativo ,GETDATE() + FROM [MagazziniPallet] + WHERE ID = @IDRiferimento + -- WHERE (IDMagazzino = @IDMagazzino) AND (Attributo = @BarcodePallet) -- AND (NumeroPallet = @NumeroPallet) + -- AND PesoUnitario > 0 AND Tipo = 'V' + -- ORDER BY ID DESC + + -- disimpegna + UPDATE [Celle] SET + [IDStato] = 0 , [ModUtente] = @Nominativo ,[ModDataOra] = GETDATE() + WHERE [ID] = (SELECT [IDCella] + FROM [MagazziniPallet] + WHERE ID = @IDRiferimento) + + -- Trasferisci in area cella [IDMagazzino],[IDArea],[IDCella] + INSERT INTO [MagazziniPallet] ([Tipo] ,[IDRiferimento],[NumeroPallet], Attributo + ,[IDMagazzino],[IDArea],[IDCella],[DataMagazzino] + ,[PesoUnitario],[Tara],[InsUtente],[InsDataOra]) + VALUES ('V',@IDRiferimento,@NumeroPallet, @BarcodePallet + ,@IDMagazzino,@IDArea,@IDCella,GETDATE() + ,@PesoUnitario,@Tara,@Nominativo,GETDATE()) + + + SET @RC = @@IDENTITY + + + END + ELSE + BEGIN + + -- VERSA in area cella [IDMagazzino],[IDArea],[IDCella] + INSERT INTO [MagazziniPallet] ([Tipo] ,[IDRiferimento],[NumeroPallet], Attributo + ,[IDMagazzino],[IDArea],[IDCella],[DataMagazzino] + ,[PesoUnitario],[Tara],[InsUtente],[InsDataOra]) + VALUES ('V',@IDRiferimento,@NumeroPallet, @BarcodePallet + ,@IDMagazzino,@IDArea,@IDCella,GETDATE() + ,@PesoUnitario,@Tara,@Nominativo,GETDATE()) + + -- disimpegna + --UPDATE [Celle] SET + -- [IDStato] = 0 , [ModUtente] = @Nominativo ,[ModDataOra] = GETDATE() + --WHERE [ID] = @IDCella + + SET @RC = @@IDENTITY + + + END + + + --EXECUTE [dbo].[sp_ControllaPrenotazionePackingListPallet] + EXECUTE [dbo].[sp_ControllaPrenotazionePackingLi + -- [TRUNCATED] -- + """ + raise NotImplementedError('Port this stored procedure logic into Python (step-by-step SQL).') + +def spt_DeleteAree(ID: int | None, DelUtente: str | None, DelDataOra: datetime | None) -> Any: + """ + Python stub for [dbo].[spt_DeleteAree]. + Original T-SQL for reference: + + SET + NOCOUNT ON + + UPDATE Aree + SET ID = @ID + ,DelUtente = @DelUtente + ,DelDataOra = @DelDataOra + WHERE (ID = @ID) + """ + raise NotImplementedError('Port this stored procedure logic into Python (step-by-step SQL).') + +def spt_DeleteCelle(ID: int | None, DelUtente: str | None, DelDataOra: datetime | None) -> Any: + """ + Python stub for [dbo].[spt_DeleteCelle]. + Original T-SQL for reference: + + SET + NOCOUNT ON + + UPDATE Celle + SET ID = @ID + ,DelUtente = @DelUtente + ,DelDataOra = @DelDataOra + WHERE (ID = @ID) + """ + raise NotImplementedError('Port this stored procedure logic into Python (step-by-step SQL).') + +def spt_DeleteCelleDimensione(ID: int | None, DelUtente: str | None, DelDataOra: datetime | None) -> Any: + """ + Python stub for [dbo].[spt_DeleteCelleDimensione]. + Original T-SQL for reference: + + SET + NOCOUNT ON + + UPDATE CelleDimensione + SET ID = @ID + ,DelUtente = @DelUtente + ,DelDataOra = @DelDataOra + WHERE (ID = @ID) + """ + raise NotImplementedError('Port this stored procedure logic into Python (step-by-step SQL).') + +def spt_DeleteCelleStati(ID: int | None, DelUtente: str | None, DelDataOra: datetime | None) -> Any: + """ + Python stub for [dbo].[spt_DeleteCelleStati]. + Original T-SQL for reference: + + SET + NOCOUNT ON + + UPDATE CelleStati + SET ID = @ID + ,DelUtente = @DelUtente + ,DelDataOra = @DelDataOra + WHERE (ID = @ID) + """ + raise NotImplementedError('Port this stored procedure logic into Python (step-by-step SQL).') + +def spt_DeleteDivisioni(Codice: str | None, DelUtente: str | None, DelDataOra: datetime | None) -> Any: + """ + Python stub for [dbo].[spt_DeleteDivisioni]. + Original T-SQL for reference: + + SET + NOCOUNT ON + + UPDATE Divisioni + SET Codice = @Codice + ,DelUtente = @DelUtente + ,DelDataOra = @DelDataOra + WHERE (Codice = @Codice) + """ + raise NotImplementedError('Port this stored procedure logic into Python (step-by-step SQL).') + +def spt_DeleteMagazzini(ID: int | None, DelUtente: str | None, DelDataOra: datetime | None) -> Any: + """ + Python stub for [dbo].[spt_DeleteMagazzini]. + Original T-SQL for reference: + + SET + NOCOUNT ON + + UPDATE Magazzini + SET DelUtente = @DelUtente + ,DelDataOra = @DelDataOra + WHERE (ID = @ID) + """ + raise NotImplementedError('Port this stored procedure logic into Python (step-by-step SQL).') + +def spt_DeleteOperatori(ID: int | None, DelUtente: str | None, DelDataOra: datetime | None) -> Any: + """ + Python stub for [dbo].[spt_DeleteOperatori]. + Original T-SQL for reference: + + SET + NOCOUNT ON + + UPDATE Operatori + SET DelUtente = @DelUtente + ,DelDataOra = @DelDataOra + WHERE (ID = @ID) + """ + raise NotImplementedError('Port this stored procedure logic into Python (step-by-step SQL).') + +def spt_DeleteReparti(Codice: str | None, DelUtente: str | None, DelDataOra: datetime | None) -> Any: + """ + Python stub for [dbo].[spt_DeleteReparti]. + Original T-SQL for reference: + + SET + NOCOUNT ON + + UPDATE Reparti + SET Codice = @Codice + ,DelUtente = @DelUtente + ,DelDataOra = @DelDataOra + WHERE (Codice = @Codice) + """ + raise NotImplementedError('Port this stored procedure logic into Python (step-by-step SQL).') + +def spt_DeleteStabilimenti(Codice: str | None, DelUtente: str | None, DelDataOra: datetime | None) -> Any: + """ + Python stub for [dbo].[spt_DeleteStabilimenti]. + Original T-SQL for reference: + + SET + NOCOUNT ON + + UPDATE Stabilimenti + SET Codice = @Codice + ,DelUtente = @DelUtente + ,DelDataOra = @DelDataOra + WHERE (Codice = @Codice) + """ + raise NotImplementedError('Port this stored procedure logic into Python (step-by-step SQL).') + +def spt_DeleteUnitaProduzione(Codice: str | None, DelUtente: str | None, DelDataOra: datetime | None) -> Any: + """ + Python stub for [dbo].[spt_DeleteUnitaProduzione]. + Original T-SQL for reference: + + SET + NOCOUNT ON + + UPDATE UnitaProduzione + SET Codice = @Codice + ,DelUtente = @DelUtente + ,DelDataOra = @DelDataOra + WHERE (Codice = @Codice) + """ + raise NotImplementedError('Port this stored procedure logic into Python (step-by-step SQL).') + +def spt_SaveAree(ID: int | None, IDMagazzino: int | None, Descrizione: str | None, InsUtente: str | None, InsDataOra: datetime | None, ModUtente: str | None, ModDataOra: datetime | None) -> Any: + """ + Python stub for [dbo].[spt_SaveAree]. + Original T-SQL for reference: + + SET + NOCOUNT ON + + UPDATE Aree + SET ID = @ID + ,IDMagazzino = @IDMagazzino + ,Descrizione = @Descrizione + ,InsUtente = @InsUtente + ,InsDataOra = @InsDataOra + ,ModUtente = @ModUtente + ,ModDataOra = @ModDataOra + WHERE ID = @ID + + IF @@ROWCOUNT = 0 + BEGIN + + INSERT INTO Aree + (ID + ,IDMagazzino + ,Descrizione + ,InsUtente + ,InsDataOra + ,ModUtente + ,ModDataOra + ) VALUES + ( @ID + , @IDMagazzino + , @Descrizione + , @InsUtente + , @InsDataOra + , @ModUtente + , @ModDataOra + ) + END + """ + raise NotImplementedError('Port this stored procedure logic into Python (step-by-step SQL).') + +def spt_SaveCelle(ID: int | None, Descrizione: str | None, IDArea: int | None, IDDimensione: int | None, IDStato: int | None, Ordinamento: float | None, X: int | None, Y: int | None, Z: int | None, Corsia: str | None, Colonna: str | None, Fila: str | None, PortataMassimaCella: float | None, PortataMassimaColonna: float | None, UnitaVolumeOccupata: float | None, InsUtente: str | None, InsDataOra: datetime | None, ModUtente: str | None, ModDataOra: datetime | None) -> Any: + """ + Python stub for [dbo].[spt_SaveCelle]. + Original T-SQL for reference: + + SET + NOCOUNT ON + + UPDATE Celle + SET ID = @ID + ,Descrizione = @Descrizione + ,IDArea = @IDArea + ,IDDimensione = @IDDimensione + ,IDStato = @IDStato + ,Ordinamento = @Ordinamento + ,X = @X + ,Y = @Y + ,Z = @Z + ,Corsia = @Corsia + ,Colonna = @Colonna + ,Fila = @Fila + ,PortataMassimaCella = @PortataMassimaCella + ,PortataMassimaColonna = @PortataMassimaColonna + ,UnitaVolumeOccupata = @UnitaVolumeOccupata + ,InsUtente = @InsUtente + ,InsDataOra = @InsDataOra + ,ModUtente = @ModUtente + ,ModDataOra = @ModDataOra + WHERE ID = @ID + + IF @@ROWCOUNT = 0 + BEGIN + + INSERT INTO Celle + (ID + ,Descrizione + ,IDArea + ,IDDimensione + ,IDStato + ,Ordinamento + ,X + ,Y + ,Z + ,Corsia + ,Colonna + ,Fila + ,PortataMassimaCella + ,PortataMassimaColonna + ,UnitaVolumeOccupata + ,InsUtente + ,InsDataOra + ,ModUtente + ,ModDataOra + ) VALUES + ( @ID + , @Descrizione + , @IDArea + , @IDDimensione + , @IDStato + , @Ordinamento + , @X + , @Y + , @Z + , @Corsia + , @Colonna + , @Fila + , @PortataMassimaCella + , @PortataMassimaColonna + , @UnitaVolumeOccupata + , @InsUtente + , @InsDataOra + , @ModUtente + , @ModDataOra + ) + END + """ + raise NotImplementedError('Port this stored procedure logic into Python (step-by-step SQL).') + +def spt_SaveCelleDimensione(ID: int | None, Descrizione: str | None, Dimensione: float | None, UnitaVolume: float | None, A: float | None, B: float | None, C: float | None, IDSolido: int | None, InsUtente: str | None, InsDataOra: datetime | None, ModUtente: str | None, ModDataOra: datetime | None) -> Any: + """ + Python stub for [dbo].[spt_SaveCelleDimensione]. + Original T-SQL for reference: + + SET + NOCOUNT ON + + UPDATE CelleDimensione + SET + Descrizione = @Descrizione + ,Dimensione = @Dimensione + ,UnitaVolume = @UnitaVolume + ,A = @A + ,B = @B + ,C = @C + ,IDSolido = @IDSolido + ,InsUtente = @InsUtente + ,InsDataOra = @InsDataOra + ,ModUtente = @ModUtente + ,ModDataOra = @ModDataOra + WHERE ID = @ID + + IF @@ROWCOUNT = 0 + BEGIN + + INSERT INTO CelleDimensione + (ID + ,Descrizione + ,Dimensione + ,UnitaVolume + ,A + ,B + ,C + ,IDSolido + ,InsUtente + ,InsDataOra + ,ModUtente + ,ModDataOra + ) VALUES + ( @ID + , @Descrizione + , @Dimensione + , @UnitaVolume + , @A + , @B + , @C + , @IDSolido + , @InsUtente + , @InsDataOra + , @ModUtente + , @ModDataOra + ) + END + """ + raise NotImplementedError('Port this stored procedure logic into Python (step-by-step SQL).') + +def spt_SaveCelleStati(ID: int | None, Descrizione: str | None, InsUtente: str | None, InsDataOra: datetime | None, ModUtente: str | None, ModDataOra: datetime | None) -> Any: + """ + Python stub for [dbo].[spt_SaveCelleStati]. + Original T-SQL for reference: + + SET + NOCOUNT ON + + UPDATE CelleStati + SET + Descrizione = @Descrizione + ,InsUtente = @InsUtente + ,InsDataOra = @InsDataOra + ,ModUtente = @ModUtente + ,ModDataOra = @ModDataOra + WHERE ID = @ID + + IF @@ROWCOUNT = 0 + BEGIN + + INSERT INTO CelleStati + (ID + ,Descrizione + ,InsUtente + ,InsDataOra + ,ModUtente + ,ModDataOra + ) VALUES + ( @ID + , @Descrizione + , @InsUtente + , @InsDataOra + , @ModUtente + , @ModDataOra + ) + END + """ + raise NotImplementedError('Port this stored procedure logic into Python (step-by-step SQL).') + +def spt_SaveDivisioni(Codice: str | None, Descrizione: str | None, InsUtente: str | None, InsDataOra: datetime | None, ModUtente: str | None, ModDataOra: datetime | None) -> Any: + """ + Python stub for [dbo].[spt_SaveDivisioni]. + Original T-SQL for reference: + + SET + NOCOUNT ON + + UPDATE Divisioni + SET Codice = @Codice + ,Descrizione = @Descrizione + ,InsUtente = @InsUtente + ,InsDataOra = @InsDataOra + ,ModUtente = @ModUtente + ,ModDataOra = @ModDataOra + WHERE (Codice = @Codice) + + IF @@ROWCOUNT = 0 + BEGIN + + INSERT INTO Divisioni + (Codice + ,Descrizione + ,InsUtente + ,InsDataOra + ,ModUtente + ,ModDataOra + ) VALUES + ( @Codice + , @Descrizione + , @InsUtente + , @InsDataOra + , @ModUtente + , @ModDataOra + ) + END + """ + raise NotImplementedError('Port this stored procedure logic into Python (step-by-step SQL).') + +def spt_SaveMagazzini(ID: int | None, Codice: str | None, CodiceDivisione: str | None, NomeBreve: str | None, Descrizione: str | None, FreqRadio: str | None, Informatizzato: str | None, ProgressivoPallet: int | None, CodiceStabilimento: str | None, InsUtente: str | None, InsDataOra: datetime | None, ModUtente: str | None, ModDataOra: datetime | None) -> Any: + """ + Python stub for [dbo].[spt_SaveMagazzini]. + Original T-SQL for reference: + + SET + NOCOUNT ON + + UPDATE Magazzini + SET ID = @ID + ,Codice = @Codice + ,CodiceDivisione = @CodiceDivisione + ,NomeBreve = @NomeBreve + ,Descrizione = @Descrizione + ,FreqRadio = @FreqRadio + ,Informatizzato = @Informatizzato + ,ProgressivoPallet = @ProgressivoPallet + ,CodiceStabilimento = @CodiceStabilimento + ,InsUtente = @InsUtente + ,InsDataOra = @InsDataOra + ,ModUtente = @ModUtente + ,ModDataOra = @ModDataOra + WHERE ID = @ID + + IF @@ROWCOUNT = 0 + BEGIN + + INSERT INTO Magazzini + (ID + ,Codice + ,CodiceDivisione + ,NomeBreve + ,Descrizione + ,FreqRadio + ,Informatizzato + ,ProgressivoPallet + ,CodiceStabilimento + ,InsUtente + ,InsDataOra + ,ModUtente + ,ModDataOra + ) VALUES + ( @ID + , @Codice + , @CodiceDivisione + , @NomeBreve + , @Descrizione + , @FreqRadio + , @Informatizzato + , @ProgressivoPallet + , @CodiceStabilimento + , @InsUtente + , @InsDataOra + , @ModUtente + , @ModDataOra + ) + END + """ + raise NotImplementedError('Port this stored procedure logic into Python (step-by-step SQL).') + +def spt_SaveOperatori(ID: int | None, Login: str | None, Nominativo: str | None, CodiceUnita: str | None, InsUtente: str | None, InsDataOra: datetime | None, ModUtente: str | None, ModDataOra: datetime | None) -> Any: + """ + Python stub for [dbo].[spt_SaveOperatori]. + Original T-SQL for reference: + + SET + NOCOUNT ON + + UPDATE Operatori + SET ID = @ID + ,Login = @Login + ,Nominativo = @Nominativo + ,CodiceUnita = @CodiceUnita + ,InsUtente = @InsUtente + ,InsDataOra = @InsDataOra + ,ModUtente = @ModUtente + ,ModDataOra = @ModDataOra + WHERE ID = @ID + + IF @@ROWCOUNT = 0 + BEGIN + + INSERT INTO Operatori + (ID + ,Login + ,Nominativo + ,CodiceUnita + ,InsUtente + ,InsDataOra + ,ModUtente + ,ModDataOra + ) VALUES + ( @ID + , @Login + , @Nominativo + , @CodiceUnita + , @InsUtente + , @InsDataOra + , @ModUtente + , @ModDataOra + ) + END + """ + raise NotImplementedError('Port this stored procedure logic into Python (step-by-step SQL).') + +def spt_SaveReparti(Codice: str | None, CodiceStabilimento: str | None, CodiceDivisione: str | None, Descrizione: str | None, PrfxLotto: int | None, PrfxAlias: str | None, PrfxCodiceLotto: str | None, IDFormato: int | None, Nota: str | None, IndiceSSCC: int | None, Prfx: str | None, VistaCommesse: str | None, CodiceAnagrafica: str | None, InsUtente: str | None, InsDataOra: datetime | None, ModUtente: str | None, ModDataOra: datetime | None) -> Any: + """ + Python stub for [dbo].[spt_SaveReparti]. + Original T-SQL for reference: + + SET + NOCOUNT ON + + UPDATE Reparti + SET Codice = @Codice + ,CodiceStabilimento = @CodiceStabilimento + ,CodiceDivisione = @CodiceDivisione + ,Descrizione = @Descrizione + ,PrfxLotto = @PrfxLotto + ,PrfxAlias = @PrfxAlias + ,PrfxCodiceLotto = @PrfxCodiceLotto + ,IDFormato = @IDFormato + ,Nota = @Nota + ,IndiceSSCC = @IndiceSSCC + ,Prfx = @Prfx + ,VistaCommesse = @VistaCommesse + ,CodiceAnagrafica = @CodiceAnagrafica + ,InsUtente = @InsUtente + ,InsDataOra = @InsDataOra + ,ModUtente = @ModUtente + ,ModDataOra = @ModDataOra + + WHERE Codice = @Codice + + IF @@ROWCOUNT = 0 + BEGIN + + INSERT INTO Reparti + (Codice + ,CodiceStabilimento + ,CodiceDivisione + ,Descrizione + ,PrfxLotto + ,PrfxAlias + ,PrfxCodiceLotto + ,IDFormato + ,Nota + ,IndiceSSCC + ,Prfx + ,VistaCommesse + ,CodiceAnagrafica + ,InsUtente + ,InsDataOra + ,ModUtente + ,ModDataOra + ) VALUES + ( @Codice + , @CodiceStabilimento + , @CodiceDivisione + , @Descrizione + , @PrfxLotto + , @PrfxAlias + , @PrfxCodiceLotto + , @IDFormato + , @Nota + , @IndiceSSCC + , @Prfx + , @VistaCommesse + , @CodiceAnagrafica + , @InsUtente + , @InsDataOra + , @ModUtente + , @ModDataOra + ) + END + """ + raise NotImplementedError('Port this stored procedure logic into Python (step-by-step SQL).') + +def spt_SaveStabilimenti(Codice: str | None, Descrizione: str | None, InsUtente: str | None, InsDataOra: datetime | None, ModUtente: str | None, ModDataOra: datetime | None) -> Any: + """ + Python stub for [dbo].[spt_SaveStabilimenti]. + Original T-SQL for reference: + + SET + NOCOUNT ON + + UPDATE Stabilimenti + SET Codice = @Codice + ,Descrizione = @Descrizione + ,InsUtente = @InsUtente + ,InsDataOra = @InsDataOra + ,ModUtente = @ModUtente + ,ModDataOra = @ModDataOra + WHERE (Codice = @Codice) + + IF @@ROWCOUNT = 0 + BEGIN + + INSERT INTO Stabilimenti + (Codice + ,Descrizione + ,InsUtente + ,InsDataOra + ,ModUtente + ,ModDataOra + ) VALUES + ( @Codice + , @Descrizione + , @InsUtente + , @InsDataOra + , @ModUtente + , @ModDataOra + ) + END + """ + raise NotImplementedError('Port this stored procedure logic into Python (step-by-step SQL).') + +def spt_SaveUnitaProduzione(Codice: str | None, CodiceReparto: str | None, Descrizione: str | None, IDMagPrelievo: int | None, IDMagVersamento: int | None, Nota: str | None, VistaCommesse: str | None, VistaDocumenti: str | None, VistaDettagli: str | None, InsUtente: str | None, InsDataOra: datetime | None, ModUtente: str | None, ModDataOra: datetime | None) -> Any: + """ + Python stub for [dbo].[spt_SaveUnitaProduzione]. + Original T-SQL for reference: + + SET + NOCOUNT ON + + UPDATE UnitaProduzione + SET Codice = @Codice + ,CodiceReparto = @CodiceReparto + ,Descrizione = @Descrizione + ,IDMagPrelievo = @IDMagPrelievo + ,IDMagVersamento = @IDMagVersamento + ,Nota = @Nota + ,VistaCommesse = @VistaCommesse + ,VistaDocumenti = @VistaDocumenti + ,VistaDettagli = @VistaDettagli + ,InsUtente = @InsUtente + ,InsDataOra = @InsDataOra + ,ModUtente = @ModUtente + ,ModDataOra = @ModDataOra + WHERE Codice = @Codice + + IF @@ROWCOUNT = 0 + BEGIN + + INSERT INTO UnitaProduzione + (Codice + ,CodiceReparto + ,Descrizione + ,IDMagPrelievo + ,IDMagVersamento + ,Nota + ,VistaCommesse + ,VistaDocumenti + ,VistaDettagli + ,InsUtente + ,InsDataOra + ,ModUtente + ,ModDataOra + ) VALUES + ( @Codice + , @CodiceReparto + , @Descrizione + , @IDMagPrelievo + , @IDMagVersamento + , @Nota + , @VistaCommesse + , @VistaDocumenti + , @VistaDettagli + , @InsUtente + , @InsDataOra + , @ModUtente + , @ModDataOra + ) + END + """ + raise NotImplementedError('Port this stored procedure logic into Python (step-by-step SQL).') diff --git a/warehouse_sql.log b/warehouse_sql.log new file mode 100644 index 0000000..a99d454 --- /dev/null +++ b/warehouse_sql.log @@ -0,0 +1,3270 @@ +[2025-09-19 13:35:35.058] PURPOSE: Rilevamento colonne Celle\n[2025-09-19 13:35:35.058] SQL:\n[2025-09-19 13:35:35.058] SELECT LOWER(name) FROM sys.columns WHERE object_id = OBJECT_ID('dbo.Celle');\n[2025-09-19 13:35:35.058] PARAMS: {}\n[2025-09-19 13:35:35.058] ELAPSED_MS: 0\n--------------------------------------------------------------------------------\n[2025-09-19 13:35:35.145] PURPOSE: Lista Aree\n[2025-09-19 13:35:35.145] INFO: START\n[2025-09-19 13:35:35.145] SQL:\n[2025-09-19 13:35:35.145] SELECT\n[2025-09-19 13:35:35.145] a.ID AS AreaID,\n[2025-09-19 13:35:35.145] a.Descrizione AS AreaNome,\n[2025-09-19 13:35:35.145] a.IDMagazzino AS MagID,\n[2025-09-19 13:35:35.145] m.Codice AS Magazzino\n[2025-09-19 13:35:35.145] FROM dbo.Aree a\n[2025-09-19 13:35:35.145] JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino\n[2025-09-19 13:35:35.145] WHERE a.DelDataOra IS NULL OR a.DelDataOra = '19000101'\n[2025-09-19 13:35:35.145] ORDER BY m.Codice, a.Descrizione;\n[2025-09-19 13:35:35.145] PARAMS: {}\n--------------------------------------------------------------------------------\n[2025-09-19 13:35:35.161] PURPOSE: Lista Aree\n[2025-09-19 13:35:35.161] INFO: END\n[2025-09-19 13:35:35.161] SQL:\n[2025-09-19 13:35:35.161] SELECT\n[2025-09-19 13:35:35.161] a.ID AS AreaID,\n[2025-09-19 13:35:35.161] a.Descrizione AS AreaNome,\n[2025-09-19 13:35:35.161] a.IDMagazzino AS MagID,\n[2025-09-19 13:35:35.161] m.Codice AS Magazzino\n[2025-09-19 13:35:35.161] FROM dbo.Aree a\n[2025-09-19 13:35:35.161] JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino\n[2025-09-19 13:35:35.161] WHERE a.DelDataOra IS NULL OR a.DelDataOra = '19000101'\n[2025-09-19 13:35:35.161] ORDER BY m.Codice, a.Descrizione;\n[2025-09-19 13:35:35.161] PARAMS: {}\n[2025-09-19 13:35:35.161] ELAPSED_MS: 14\n--------------------------------------------------------------------------------\n[2025-09-19 13:35:35.187] PURPOSE: Statistiche Scaffali per Area\n[2025-09-19 13:35:35.187] INFO: START\n[2025-09-19 13:35:35.187] SQL:\n[2025-09-19 13:35:35.187] WITH CELLE AS (\n[2025-09-19 13:35:35.187] SELECT c.ID AS IDCella, c.IDArea, CAST([Corsia] AS NVARCHAR(50)) AS Scaffale\n[2025-09-19 13:35:35.187] FROM dbo.Celle c\n[2025-09-19 13:35:35.187] WHERE c.IDArea = :area_id\n[2025-09-19 13:35:35.187] ),\n[2025-09-19 13:35:35.187] OCC AS (\n[2025-09-19 13:35:35.187] SELECT c.Scaffale, COUNT(*) AS CelleTotali\n[2025-09-19 13:35:35.187] FROM CELLE c\n[2025-09-19 13:35:35.187] GROUP BY c.Scaffale\n[2025-09-19 13:35:35.187] ),\n[2025-09-19 13:35:35.187] OCC2 AS (\n[2025-09-19 13:35:35.187] SELECT c.Scaffale,\n[2025-09-19 13:35:35.187] COUNT(DISTINCT mp.IDCella) AS CelleOccupate,\n[2025-09-19 13:35:35.187] COUNT(mp.ID) AS NPalet\n[2025-09-19 13:35:35.187] FROM CELLE c\n[2025-09-19 13:35:35.187] LEFT JOIN dbo.MagazziniPallet mp ON mp.IDCella = c.IDCella\n[2025-09-19 13:35:35.187] GROUP BY c.Scaffale\n[2025-09-19 13:35:35.187] )\n[2025-09-19 13:35:35.187] SELECT\n[2025-09-19 13:35:35.187] ROW_NUMBER() OVER (ORDER BY o.Scaffale) AS ID,\n[2025-09-19 13:35:35.187] o.Scaffale AS Scaffale,\n[2025-09-19 13:35:35.187] a.IDMagazzino AS MagID,\n[2025-09-19 13:35:35.187] m.Codice AS Magazzino,\n[2025-09-19 13:35:35.187] ISNULL(o2.NPalet,0) AS NPalet,\n[2025-09-19 13:35:35.187] o.CelleTotali AS CelleTotali,\n[2025-09-19 13:35:35.187] ISNULL(o2.CelleOccupate,0) AS CelleOccupate\n[2025-09-19 13:35:35.187] FROM OCC o\n[2025-09-19 13:35:35.187] LEFT JOIN OCC2 o2 ON o2.Scaffale = o.Scaffale\n[2025-09-19 13:35:35.187] JOIN dbo.Aree a ON a.ID = :area_id\n[2025-09-19 13:35:35.187] JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino\n[2025-09-19 13:35:35.187] WHERE o.Scaffale IS NOT NULL AND LTRIM(RTRIM(o.Scaffale)) <> ''\n[2025-09-19 13:35:35.187] ORDER BY o.Scaffale;\n[2025-09-19 13:35:35.187] PARAMS: {'area_id': 1}\n--------------------------------------------------------------------------------\n[2025-09-19 13:35:35.319] PURPOSE: Statistiche Scaffali per Area\n[2025-09-19 13:35:35.319] INFO: END\n[2025-09-19 13:35:35.319] SQL:\n[2025-09-19 13:35:35.319] WITH CELLE AS (\n[2025-09-19 13:35:35.319] SELECT c.ID AS IDCella, c.IDArea, CAST([Corsia] AS NVARCHAR(50)) AS Scaffale\n[2025-09-19 13:35:35.319] FROM dbo.Celle c\n[2025-09-19 13:35:35.319] WHERE c.IDArea = :area_id\n[2025-09-19 13:35:35.319] ),\n[2025-09-19 13:35:35.319] OCC AS (\n[2025-09-19 13:35:35.319] SELECT c.Scaffale, COUNT(*) AS CelleTotali\n[2025-09-19 13:35:35.319] FROM CELLE c\n[2025-09-19 13:35:35.319] GROUP BY c.Scaffale\n[2025-09-19 13:35:35.319] ),\n[2025-09-19 13:35:35.319] OCC2 AS (\n[2025-09-19 13:35:35.319] SELECT c.Scaffale,\n[2025-09-19 13:35:35.319] COUNT(DISTINCT mp.IDCella) AS CelleOccupate,\n[2025-09-19 13:35:35.319] COUNT(mp.ID) AS NPalet\n[2025-09-19 13:35:35.319] FROM CELLE c\n[2025-09-19 13:35:35.319] LEFT JOIN dbo.MagazziniPallet mp ON mp.IDCella = c.IDCella\n[2025-09-19 13:35:35.319] GROUP BY c.Scaffale\n[2025-09-19 13:35:35.319] )\n[2025-09-19 13:35:35.319] SELECT\n[2025-09-19 13:35:35.319] ROW_NUMBER() OVER (ORDER BY o.Scaffale) AS ID,\n[2025-09-19 13:35:35.319] o.Scaffale AS Scaffale,\n[2025-09-19 13:35:35.319] a.IDMagazzino AS MagID,\n[2025-09-19 13:35:35.319] m.Codice AS Magazzino,\n[2025-09-19 13:35:35.319] ISNULL(o2.NPalet,0) AS NPalet,\n[2025-09-19 13:35:35.319] o.CelleTotali AS CelleTotali,\n[2025-09-19 13:35:35.319] ISNULL(o2.CelleOccupate,0) AS CelleOccupate\n[2025-09-19 13:35:35.319] FROM OCC o\n[2025-09-19 13:35:35.319] LEFT JOIN OCC2 o2 ON o2.Scaffale = o.Scaffale\n[2025-09-19 13:35:35.319] JOIN dbo.Aree a ON a.ID = :area_id\n[2025-09-19 13:35:35.319] JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino\n[2025-09-19 13:35:35.319] WHERE o.Scaffale IS NOT NULL AND LTRIM(RTRIM(o.Scaffale)) <> ''\n[2025-09-19 13:35:35.319] ORDER BY o.Scaffale;\n[2025-09-19 13:35:35.319] PARAMS: {'area_id': 1}\n[2025-09-19 13:35:35.319] ELAPSED_MS: 131\n--------------------------------------------------------------------------------\n[2025-09-19 13:39:31.959] PURPOSE: Statistiche Scaffali per Area\n[2025-09-19 13:39:31.959] INFO: START\n[2025-09-19 13:39:31.959] SQL:\n[2025-09-19 13:39:31.959] WITH CELLE AS (\n[2025-09-19 13:39:31.959] SELECT c.ID AS IDCella, c.IDArea, CAST([Corsia] AS NVARCHAR(50)) AS Scaffale\n[2025-09-19 13:39:31.959] FROM dbo.Celle c\n[2025-09-19 13:39:31.959] WHERE c.IDArea = :area_id\n[2025-09-19 13:39:31.959] ),\n[2025-09-19 13:39:31.959] OCC AS (\n[2025-09-19 13:39:31.959] SELECT c.Scaffale, COUNT(*) AS CelleTotali\n[2025-09-19 13:39:31.959] FROM CELLE c\n[2025-09-19 13:39:31.959] GROUP BY c.Scaffale\n[2025-09-19 13:39:31.959] ),\n[2025-09-19 13:39:31.959] OCC2 AS (\n[2025-09-19 13:39:31.959] SELECT c.Scaffale,\n[2025-09-19 13:39:31.959] COUNT(DISTINCT mp.IDCella) AS CelleOccupate,\n[2025-09-19 13:39:31.959] COUNT(mp.ID) AS NPalet\n[2025-09-19 13:39:31.959] FROM CELLE c\n[2025-09-19 13:39:31.959] LEFT JOIN dbo.MagazziniPallet mp ON mp.IDCella = c.IDCella\n[2025-09-19 13:39:31.959] GROUP BY c.Scaffale\n[2025-09-19 13:39:31.959] )\n[2025-09-19 13:39:31.959] SELECT\n[2025-09-19 13:39:31.959] ROW_NUMBER() OVER (ORDER BY o.Scaffale) AS ID,\n[2025-09-19 13:39:31.959] o.Scaffale AS Scaffale,\n[2025-09-19 13:39:31.959] a.IDMagazzino AS MagID,\n[2025-09-19 13:39:31.959] m.Codice AS Magazzino,\n[2025-09-19 13:39:31.959] ISNULL(o2.NPalet,0) AS NPalet,\n[2025-09-19 13:39:31.959] o.CelleTotali AS CelleTotali,\n[2025-09-19 13:39:31.959] ISNULL(o2.CelleOccupate,0) AS CelleOccupate\n[2025-09-19 13:39:31.959] FROM OCC o\n[2025-09-19 13:39:31.959] LEFT JOIN OCC2 o2 ON o2.Scaffale = o.Scaffale\n[2025-09-19 13:39:31.959] JOIN dbo.Aree a ON a.ID = :area_id\n[2025-09-19 13:39:31.959] JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino\n[2025-09-19 13:39:31.959] WHERE o.Scaffale IS NOT NULL AND LTRIM(RTRIM(o.Scaffale)) <> ''\n[2025-09-19 13:39:31.959] ORDER BY o.Scaffale;\n[2025-09-19 13:39:31.959] PARAMS: {'area_id': 4}\n--------------------------------------------------------------------------------\n[2025-09-19 13:39:32.045] PURPOSE: Statistiche Scaffali per Area\n[2025-09-19 13:39:32.045] INFO: END\n[2025-09-19 13:39:32.045] SQL:\n[2025-09-19 13:39:32.045] WITH CELLE AS (\n[2025-09-19 13:39:32.045] SELECT c.ID AS IDCella, c.IDArea, CAST([Corsia] AS NVARCHAR(50)) AS Scaffale\n[2025-09-19 13:39:32.045] FROM dbo.Celle c\n[2025-09-19 13:39:32.045] WHERE c.IDArea = :area_id\n[2025-09-19 13:39:32.045] ),\n[2025-09-19 13:39:32.045] OCC AS (\n[2025-09-19 13:39:32.045] SELECT c.Scaffale, COUNT(*) AS CelleTotali\n[2025-09-19 13:39:32.045] FROM CELLE c\n[2025-09-19 13:39:32.045] GROUP BY c.Scaffale\n[2025-09-19 13:39:32.045] ),\n[2025-09-19 13:39:32.045] OCC2 AS (\n[2025-09-19 13:39:32.045] SELECT c.Scaffale,\n[2025-09-19 13:39:32.045] COUNT(DISTINCT mp.IDCella) AS CelleOccupate,\n[2025-09-19 13:39:32.045] COUNT(mp.ID) AS NPalet\n[2025-09-19 13:39:32.045] FROM CELLE c\n[2025-09-19 13:39:32.045] LEFT JOIN dbo.MagazziniPallet mp ON mp.IDCella = c.IDCella\n[2025-09-19 13:39:32.045] GROUP BY c.Scaffale\n[2025-09-19 13:39:32.045] )\n[2025-09-19 13:39:32.045] SELECT\n[2025-09-19 13:39:32.045] ROW_NUMBER() OVER (ORDER BY o.Scaffale) AS ID,\n[2025-09-19 13:39:32.045] o.Scaffale AS Scaffale,\n[2025-09-19 13:39:32.045] a.IDMagazzino AS MagID,\n[2025-09-19 13:39:32.045] m.Codice AS Magazzino,\n[2025-09-19 13:39:32.045] ISNULL(o2.NPalet,0) AS NPalet,\n[2025-09-19 13:39:32.045] o.CelleTotali AS CelleTotali,\n[2025-09-19 13:39:32.045] ISNULL(o2.CelleOccupate,0) AS CelleOccupate\n[2025-09-19 13:39:32.045] FROM OCC o\n[2025-09-19 13:39:32.045] LEFT JOIN OCC2 o2 ON o2.Scaffale = o.Scaffale\n[2025-09-19 13:39:32.045] JOIN dbo.Aree a ON a.ID = :area_id\n[2025-09-19 13:39:32.045] JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino\n[2025-09-19 13:39:32.045] WHERE o.Scaffale IS NOT NULL AND LTRIM(RTRIM(o.Scaffale)) <> ''\n[2025-09-19 13:39:32.045] ORDER BY o.Scaffale;\n[2025-09-19 13:39:32.045] PARAMS: {'area_id': 4}\n[2025-09-19 13:39:32.045] ELAPSED_MS: 85\n--------------------------------------------------------------------------------\n[2025-09-19 13:39:41.285] PURPOSE: SIMULAZIONE Azzeramento Area (conteggio)\n[2025-09-19 13:39:41.285] INFO: START\n[2025-09-19 13:39:41.285] SQL:\n[2025-09-19 13:39:41.285] SELECT COUNT(*) AS WouldDelete\n[2025-09-19 13:39:41.285] FROM dbo.MagazziniPallet mp\n[2025-09-19 13:39:41.285] JOIN dbo.Celle c ON c.ID = mp.IDCella\n[2025-09-19 13:39:41.285] WHERE c.IDArea = :area_id;\n[2025-09-19 13:39:41.285] PARAMS: {'area_id': 4}\n--------------------------------------------------------------------------------\n[2025-09-19 13:39:41.353] PURPOSE: SIMULAZIONE Azzeramento Area (conteggio)\n[2025-09-19 13:39:41.353] INFO: END\n[2025-09-19 13:39:41.353] SQL:\n[2025-09-19 13:39:41.353] SELECT COUNT(*) AS WouldDelete\n[2025-09-19 13:39:41.353] FROM dbo.MagazziniPallet mp\n[2025-09-19 13:39:41.353] JOIN dbo.Celle c ON c.ID = mp.IDCella\n[2025-09-19 13:39:41.353] WHERE c.IDArea = :area_id;\n[2025-09-19 13:39:41.353] PARAMS: {'area_id': 4}\n[2025-09-19 13:39:41.353] ELAPSED_MS: 67\n--------------------------------------------------------------------------------\n+------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-09-19 14:05:18.622 | +| PURPOSE : Rilevamento colonne Celle | +| ELAPSED : 0 ms | +| | +| SQL: | +| SELECT LOWER(name) FROM sys.columns WHERE object_id = OBJECT_ID('dbo.Celle'); | +| | +| PARAMS: | +| {} | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-09-19 14:05:18.663 | +| PURPOSE : Lista Aree | +| | +| SQL: | +| SELECT | +| a.ID AS AreaID, | +| a.Descrizione AS AreaNome, | +| a.IDMagazzino AS MagID, | +| m.Codice AS Magazzino | +| FROM dbo.Aree a | +| JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino | +| WHERE a.DelDataOra IS NULL OR a.DelDataOra = '19000101' | +| ORDER BY m.Codice, a.Descrizione; | +| | +| PARAMS: | +| {} | +| | +| COMMENT: | +| START | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-09-19 14:05:18.668 | +| PURPOSE : Lista Aree | +| ELAPSED : 3 ms | +| | +| SQL: | +| SELECT | +| a.ID AS AreaID, | +| a.Descrizione AS AreaNome, | +| a.IDMagazzino AS MagID, | +| m.Codice AS Magazzino | +| FROM dbo.Aree a | +| JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino | +| WHERE a.DelDataOra IS NULL OR a.DelDataOra = '19000101' | +| ORDER BY m.Codice, a.Descrizione; | +| | +| PARAMS: | +| {} | +| | +| COMMENT: | +| END | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-09-19 14:05:18.675 | +| PURPOSE : Statistiche Scaffali per Area | +| | +| SQL: | +| WITH CELLE AS ( | +| SELECT c.ID AS IDCella, c.IDArea, CAST([Corsia] AS NVARCHAR(50)) AS Scaffale | +| FROM dbo.Celle c | +| WHERE c.IDArea = :area_id | +| ), | +| OCC AS ( | +| SELECT c.Scaffale, COUNT(*) AS CelleTotali | +| FROM CELLE c | +| GROUP BY c.Scaffale | +| ), | +| OCC2 AS ( | +| SELECT c.Scaffale, | +| COUNT(DISTINCT mp.IDCella) AS CelleOccupate, | +| COUNT(mp.ID) AS NPalet | +| FROM CELLE c | +| LEFT JOIN dbo.MagazziniPallet mp ON mp.IDCella = c.IDCella | +| GROUP BY c.Scaffale | +| ) | +| SELECT | +| ROW_NUMBER() OVER (ORDER BY o.Scaffale) AS ID, | +| o.Scaffale AS Scaffale, | +| a.IDMagazzino AS MagID, | +| m.Codice AS Magazzino, | +| ISNULL(o2.NPalet,0) AS NPalet, | +| o.CelleTotali AS CelleTotali, | +| ISNULL(o2.CelleOccupate,0) AS CelleOccupate | +| FROM OCC o | +| LEFT JOIN OCC2 o2 ON o2.Scaffale = o.Scaffale | +| JOIN dbo.Aree a ON a.ID = :area_id | +| JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino | +| WHERE o.Scaffale IS NOT NULL AND LTRIM(RTRIM(o.Scaffale)) <> '' | +| ORDER BY o.Scaffale; | +| | +| PARAMS: | +| { | +| "area_id": 1 | +| } | +| | +| COMMENT: | +| START | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-09-19 14:05:18.801 | +| PURPOSE : Statistiche Scaffali per Area | +| ELAPSED : 125 ms | +| | +| SQL: | +| WITH CELLE AS ( | +| SELECT c.ID AS IDCella, c.IDArea, CAST([Corsia] AS NVARCHAR(50)) AS Scaffale | +| FROM dbo.Celle c | +| WHERE c.IDArea = :area_id | +| ), | +| OCC AS ( | +| SELECT c.Scaffale, COUNT(*) AS CelleTotali | +| FROM CELLE c | +| GROUP BY c.Scaffale | +| ), | +| OCC2 AS ( | +| SELECT c.Scaffale, | +| COUNT(DISTINCT mp.IDCella) AS CelleOccupate, | +| COUNT(mp.ID) AS NPalet | +| FROM CELLE c | +| LEFT JOIN dbo.MagazziniPallet mp ON mp.IDCella = c.IDCella | +| GROUP BY c.Scaffale | +| ) | +| SELECT | +| ROW_NUMBER() OVER (ORDER BY o.Scaffale) AS ID, | +| o.Scaffale AS Scaffale, | +| a.IDMagazzino AS MagID, | +| m.Codice AS Magazzino, | +| ISNULL(o2.NPalet,0) AS NPalet, | +| o.CelleTotali AS CelleTotali, | +| ISNULL(o2.CelleOccupate,0) AS CelleOccupate | +| FROM OCC o | +| LEFT JOIN OCC2 o2 ON o2.Scaffale = o.Scaffale | +| JOIN dbo.Aree a ON a.ID = :area_id | +| JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino | +| WHERE o.Scaffale IS NOT NULL AND LTRIM(RTRIM(o.Scaffale)) <> '' | +| ORDER BY o.Scaffale; | +| | +| PARAMS: | +| { | +| "area_id": 1 | +| } | +| | +| COMMENT: | +| END | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-09-19 14:05:38.222 | +| PURPOSE : SIMULAZIONE Azzeramento Area (conteggio) | +| | +| SQL: | +| SELECT COUNT(*) AS WouldDelete | +| FROM dbo.MagazziniPallet mp | +| JOIN dbo.Celle c ON c.ID = mp.IDCella | +| WHERE c.IDArea = :area_id; | +| | +| PARAMS: | +| { | +| "area_id": 1 | +| } | +| | +| COMMENT: | +| START | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-09-19 14:05:38.286 | +| PURPOSE : SIMULAZIONE Azzeramento Area (conteggio) | +| ELAPSED : 63 ms | +| | +| SQL: | +| SELECT COUNT(*) AS WouldDelete | +| FROM dbo.MagazziniPallet mp | +| JOIN dbo.Celle c ON c.ID = mp.IDCella | +| WHERE c.IDArea = :area_id; | +| | +| PARAMS: | +| { | +| "area_id": 1 | +| } | +| | +| COMMENT: | +| END | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-09-19 15:57:33.565 | +| PURPOSE : Rilevamento colonne Celle | +| ELAPSED : 0 ms | +| | +| SQL (copy/paste below): | +| | +SELECT LOWER(name) FROM sys.columns WHERE object_id = OBJECT_ID('dbo.Celle'); + +| PARAMS: | +| {} | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-09-29 19:16:04.836 | +| PURPOSE : Rilevamento colonne Celle | +| ELAPSED : 0 ms | +| | +| SQL (copy/paste below): | +| | +SELECT LOWER(name) FROM sys.columns WHERE object_id = OBJECT_ID('dbo.Celle'); + +| PARAMS: | +| {} | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-09-29 19:16:13.102 | +| PURPOSE : Lista Aree | +| | +| SQL (copy/paste below): | +| | +SELECT a.ID AS AreaID, a.Descrizione AS AreaNome, a.IDMagazzino AS MagID, m.Codice AS Magazzino +FROM dbo.Aree a +JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino +WHERE a.DelDataOra IS NULL OR a.DelDataOra = '19000101' +ORDER BY m.Codice, a.Descrizione; + +| PARAMS: | +| {} | +| | +| COMMENT: | +| START | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-09-29 19:16:13.103 | +| PURPOSE : Lista Aree | +| ELAPSED : 0 ms | +| | +| SQL (copy/paste below): | +| | +SELECT a.ID AS AreaID, a.Descrizione AS AreaNome, a.IDMagazzino AS MagID, m.Codice AS Magazzino +FROM dbo.Aree a +JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino +WHERE a.DelDataOra IS NULL OR a.DelDataOra = '19000101' +ORDER BY m.Codice, a.Descrizione; + +| PARAMS: | +| {} | +| | +| COMMENT: | +| END | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-09-29 19:20:14.522 | +| PURPOSE : Rilevamento colonne Celle | +| ELAPSED : 0 ms | +| | +| SQL (copy/paste below): | +| | +SELECT LOWER(name) FROM sys.columns WHERE object_id = OBJECT_ID('dbo.Celle'); + +| PARAMS: | +| {} | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-09-29 19:20:18.641 | +| PURPOSE : Lista Aree | +| | +| SQL (copy/paste below): | +| | +SELECT a.ID AS AreaID, a.Descrizione AS AreaNome, a.IDMagazzino AS MagID, m.Codice AS Magazzino +FROM dbo.Aree a +JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino +WHERE a.DelDataOra IS NULL OR a.DelDataOra = '19000101' +ORDER BY m.Codice, a.Descrizione; + +| PARAMS: | +| {} | +| | +| COMMENT: | +| START | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-09-29 19:20:18.642 | +| PURPOSE : Lista Aree | +| ELAPSED : 0 ms | +| | +| SQL (copy/paste below): | +| | +SELECT a.ID AS AreaID, a.Descrizione AS AreaNome, a.IDMagazzino AS MagID, m.Codice AS Magazzino +FROM dbo.Aree a +JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino +WHERE a.DelDataOra IS NULL OR a.DelDataOra = '19000101' +ORDER BY m.Codice, a.Descrizione; + +| PARAMS: | +| {} | +| | +| COMMENT: | +| END | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-09-29 19:20:47.270 | +| PURPOSE : Rilevamento colonne Celle | +| ELAPSED : 0 ms | +| | +| SQL (copy/paste below): | +| | +SELECT LOWER(name) FROM sys.columns WHERE object_id = OBJECT_ID('dbo.Celle'); + +| PARAMS: | +| {} | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-09-29 19:20:57.020 | +| PURPOSE : Lista Aree | +| | +| SQL (copy/paste below): | +| | +SELECT a.ID AS AreaID, a.Descrizione AS AreaNome, a.IDMagazzino AS MagID, m.Codice AS Magazzino +FROM dbo.Aree a +JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino +WHERE a.DelDataOra IS NULL OR a.DelDataOra = '19000101' +ORDER BY m.Codice, a.Descrizione; + +| PARAMS: | +| {} | +| | +| COMMENT: | +| START | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-09-29 19:20:57.021 | +| PURPOSE : Lista Aree | +| ELAPSED : 0 ms | +| | +| SQL (copy/paste below): | +| | +SELECT a.ID AS AreaID, a.Descrizione AS AreaNome, a.IDMagazzino AS MagID, m.Codice AS Magazzino +FROM dbo.Aree a +JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino +WHERE a.DelDataOra IS NULL OR a.DelDataOra = '19000101' +ORDER BY m.Codice, a.Descrizione; + +| PARAMS: | +| {} | +| | +| COMMENT: | +| END | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-09-29 19:23:52.920 | +| PURPOSE : Rilevamento colonne Celle | +| ELAPSED : 0 ms | +| | +| SQL (copy/paste below): | +| | +SELECT LOWER(name) FROM sys.columns WHERE object_id = OBJECT_ID('dbo.Celle'); + +| PARAMS: | +| {} | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-09-29 19:23:54.812 | +| PURPOSE : Lista Aree | +| | +| SQL (copy/paste below): | +| | +SELECT a.ID AS AreaID, a.Descrizione AS AreaNome, a.IDMagazzino AS MagID, m.Codice AS Magazzino +FROM dbo.Aree a +JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino +WHERE a.DelDataOra IS NULL OR a.DelDataOra = '19000101' +ORDER BY m.Codice, a.Descrizione; + +| PARAMS: | +| {} | +| | +| COMMENT: | +| START | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-09-29 19:23:54.813 | +| PURPOSE : Lista Aree | +| ELAPSED : 0 ms | +| | +| SQL (copy/paste below): | +| | +SELECT a.ID AS AreaID, a.Descrizione AS AreaNome, a.IDMagazzino AS MagID, m.Codice AS Magazzino +FROM dbo.Aree a +JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino +WHERE a.DelDataOra IS NULL OR a.DelDataOra = '19000101' +ORDER BY m.Codice, a.Descrizione; + +| PARAMS: | +| {} | +| | +| COMMENT: | +| END | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-09-29 19:38:09.469 | +| PURPOSE : Rilevamento colonne Celle | +| ELAPSED : 0 ms | +| | +| SQL (copy/paste below): | +| | +SELECT LOWER(name) FROM sys.columns WHERE object_id = OBJECT_ID('dbo.Celle'); + +| PARAMS: | +| {} | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-09-29 19:38:11.042 | +| PURPOSE : Lista Aree | +| | +| SQL (copy/paste below): | +| | +SELECT a.ID AS AreaID, a.Descrizione AS AreaNome, a.IDMagazzino AS MagID, m.Codice AS Magazzino +FROM dbo.Aree a +JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino +WHERE a.DelDataOra IS NULL OR a.DelDataOra = '19000101' +ORDER BY m.Codice, a.Descrizione; + +| PARAMS: | +| {} | +| | +| COMMENT: | +| START | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-09-29 19:38:11.043 | +| PURPOSE : Lista Aree | +| ELAPSED : 0 ms | +| | +| SQL (copy/paste below): | +| | +SELECT a.ID AS AreaID, a.Descrizione AS AreaNome, a.IDMagazzino AS MagID, m.Codice AS Magazzino +FROM dbo.Aree a +JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino +WHERE a.DelDataOra IS NULL OR a.DelDataOra = '19000101' +ORDER BY m.Codice, a.Descrizione; + +| PARAMS: | +| {} | +| | +| COMMENT: | +| END | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-09-30 11:33:49.156 | +| PURPOSE : Rilevamento colonne Celle | +| ELAPSED : 0 ms | +| | +| SQL (copy/paste below): | +| | +SELECT LOWER(name) FROM sys.columns WHERE object_id = OBJECT_ID('dbo.Celle'); + +| PARAMS: | +| {} | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-09-30 11:34:01.696 | +| PURPOSE : Rilevamento colonne Celle | +| ELAPSED : 0 ms | +| | +| SQL (copy/paste below): | +| | +SELECT LOWER(name) FROM sys.columns WHERE object_id = OBJECT_ID('dbo.Celle'); + +| PARAMS: | +| {} | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-09-30 11:34:16.430 | +| PURPOSE : Lista Aree | +| | +| SQL (copy/paste below): | +| | +SELECT a.ID AS AreaID, a.Descrizione AS AreaNome, a.IDMagazzino AS MagID, m.Codice AS Magazzino +FROM dbo.Aree a +JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino +WHERE a.DelDataOra IS NULL OR a.DelDataOra = '19000101' +ORDER BY m.Codice, a.Descrizione; + +| PARAMS: | +| {} | +| | +| COMMENT: | +| START | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-09-30 11:34:16.431 | +| PURPOSE : Lista Aree | +| ELAPSED : 0 ms | +| | +| SQL (copy/paste below): | +| | +SELECT a.ID AS AreaID, a.Descrizione AS AreaNome, a.IDMagazzino AS MagID, m.Codice AS Magazzino +FROM dbo.Aree a +JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino +WHERE a.DelDataOra IS NULL OR a.DelDataOra = '19000101' +ORDER BY m.Codice, a.Descrizione; + +| PARAMS: | +| {} | +| | +| COMMENT: | +| END | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-09-30 11:34:17.646 | +| PURPOSE : Lista Aree | +| | +| SQL (copy/paste below): | +| | +SELECT a.ID AS AreaID, a.Descrizione AS AreaNome, a.IDMagazzino AS MagID, m.Codice AS Magazzino +FROM dbo.Aree a +JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino +WHERE a.DelDataOra IS NULL OR a.DelDataOra = '19000101' +ORDER BY m.Codice, a.Descrizione; + +| PARAMS: | +| {} | +| | +| COMMENT: | +| START | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-09-30 11:34:17.647 | +| PURPOSE : Lista Aree | +| ELAPSED : 0 ms | +| | +| SQL (copy/paste below): | +| | +SELECT a.ID AS AreaID, a.Descrizione AS AreaNome, a.IDMagazzino AS MagID, m.Codice AS Magazzino +FROM dbo.Aree a +JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino +WHERE a.DelDataOra IS NULL OR a.DelDataOra = '19000101' +ORDER BY m.Codice, a.Descrizione; + +| PARAMS: | +| {} | +| | +| COMMENT: | +| END | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 12:38:38.587 | +| PURPOSE : Rilevamento colonne Celle | +| ELAPSED : 0 ms | +| | +| SQL (copy/paste below): | +| | +SELECT LOWER(name) FROM sys.columns WHERE object_id = OBJECT_ID('dbo.Celle'); + +| PARAMS: | +| {} | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 12:38:38.624 | +| PURPOSE : Lista Aree | +| | +| SQL (copy/paste below): | +| | +SELECT a.ID AS AreaID, a.Descrizione AS AreaNome, a.IDMagazzino AS MagID, m.Codice AS Magazzino +FROM dbo.Aree a +JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino +WHERE len(trim(a.Descrizione)) = 2; + +| PARAMS: | +| {} | +| | +| COMMENT: | +| START | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 12:38:38.630 | +| PURPOSE : Lista Aree | +| ELAPSED : 5 ms | +| | +| SQL (copy/paste below): | +| | +SELECT a.ID AS AreaID, a.Descrizione AS AreaNome, a.IDMagazzino AS MagID, m.Codice AS Magazzino +FROM dbo.Aree a +JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino +WHERE len(trim(a.Descrizione)) = 2; + +| PARAMS: | +| {} | +| | +| COMMENT: | +| END | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 12:38:38.718 | +| PURPOSE : Statistiche Scaffali per Area | +| | +| SQL (copy/paste below): | +| | + WITH CELLE AS ( + SELECT c.ID AS IDCella, c.IDArea, CAST([Corsia] AS NVARCHAR(50)) AS Scaffale + FROM dbo.Celle c + WHERE c.IDArea = :area_id + ), + OCC AS ( + SELECT c.Scaffale, COUNT(*) AS CelleTotali + FROM CELLE c + GROUP BY c.Scaffale + ), + OCC2 AS ( + SELECT c.Scaffale, + COUNT(DISTINCT mp.IDCella) AS CelleOccupate, + COUNT(mp.ID) AS NPalet + FROM CELLE c + LEFT JOIN dbo.MagazziniPallet mp ON mp.IDCella = c.IDCella + GROUP BY c.Scaffale + ) + SELECT + ROW_NUMBER() OVER (ORDER BY o.Scaffale) AS ID, + o.Scaffale AS Scaffale, + a.IDMagazzino AS MagID, + m.Codice AS Magazzino, + ISNULL(o2.NPalet,0) AS NPalet, + o.CelleTotali AS CelleTotali, + ISNULL(o2.CelleOccupate,0) AS CelleOccupate + FROM OCC o + LEFT JOIN OCC2 o2 ON o2.Scaffale = o.Scaffale + JOIN dbo.Aree a ON a.ID = :area_id + JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino + WHERE o.Scaffale IS NOT NULL AND LTRIM(RTRIM(o.Scaffale)) <> '' + ORDER BY o.Scaffale; + + +| PARAMS: | +| { | +| "area_id": 1 | +| } | +| | +| COMMENT: | +| START | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 12:38:38.804 | +| PURPOSE : Statistiche Scaffali per Area | +| ELAPSED : 85 ms | +| | +| SQL (copy/paste below): | +| | + WITH CELLE AS ( + SELECT c.ID AS IDCella, c.IDArea, CAST([Corsia] AS NVARCHAR(50)) AS Scaffale + FROM dbo.Celle c + WHERE c.IDArea = :area_id + ), + OCC AS ( + SELECT c.Scaffale, COUNT(*) AS CelleTotali + FROM CELLE c + GROUP BY c.Scaffale + ), + OCC2 AS ( + SELECT c.Scaffale, + COUNT(DISTINCT mp.IDCella) AS CelleOccupate, + COUNT(mp.ID) AS NPalet + FROM CELLE c + LEFT JOIN dbo.MagazziniPallet mp ON mp.IDCella = c.IDCella + GROUP BY c.Scaffale + ) + SELECT + ROW_NUMBER() OVER (ORDER BY o.Scaffale) AS ID, + o.Scaffale AS Scaffale, + a.IDMagazzino AS MagID, + m.Codice AS Magazzino, + ISNULL(o2.NPalet,0) AS NPalet, + o.CelleTotali AS CelleTotali, + ISNULL(o2.CelleOccupate,0) AS CelleOccupate + FROM OCC o + LEFT JOIN OCC2 o2 ON o2.Scaffale = o.Scaffale + JOIN dbo.Aree a ON a.ID = :area_id + JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino + WHERE o.Scaffale IS NOT NULL AND LTRIM(RTRIM(o.Scaffale)) <> '' + ORDER BY o.Scaffale; + + +| PARAMS: | +| { | +| "area_id": 1 | +| } | +| | +| COMMENT: | +| END | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 12:39:04.697 | +| PURPOSE : Statistiche Scaffali per Area | +| | +| SQL (copy/paste below): | +| | + WITH CELLE AS ( + SELECT c.ID AS IDCella, c.IDArea, CAST([Corsia] AS NVARCHAR(50)) AS Scaffale + FROM dbo.Celle c + WHERE c.IDArea = :area_id + ), + OCC AS ( + SELECT c.Scaffale, COUNT(*) AS CelleTotali + FROM CELLE c + GROUP BY c.Scaffale + ), + OCC2 AS ( + SELECT c.Scaffale, + COUNT(DISTINCT mp.IDCella) AS CelleOccupate, + COUNT(mp.ID) AS NPalet + FROM CELLE c + LEFT JOIN dbo.MagazziniPallet mp ON mp.IDCella = c.IDCella + GROUP BY c.Scaffale + ) + SELECT + ROW_NUMBER() OVER (ORDER BY o.Scaffale) AS ID, + o.Scaffale AS Scaffale, + a.IDMagazzino AS MagID, + m.Codice AS Magazzino, + ISNULL(o2.NPalet,0) AS NPalet, + o.CelleTotali AS CelleTotali, + ISNULL(o2.CelleOccupate,0) AS CelleOccupate + FROM OCC o + LEFT JOIN OCC2 o2 ON o2.Scaffale = o.Scaffale + JOIN dbo.Aree a ON a.ID = :area_id + JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino + WHERE o.Scaffale IS NOT NULL AND LTRIM(RTRIM(o.Scaffale)) <> '' + ORDER BY o.Scaffale; + + +| PARAMS: | +| { | +| "area_id": 4 | +| } | +| | +| COMMENT: | +| START | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 12:39:04.749 | +| PURPOSE : Statistiche Scaffali per Area | +| ELAPSED : 51 ms | +| | +| SQL (copy/paste below): | +| | + WITH CELLE AS ( + SELECT c.ID AS IDCella, c.IDArea, CAST([Corsia] AS NVARCHAR(50)) AS Scaffale + FROM dbo.Celle c + WHERE c.IDArea = :area_id + ), + OCC AS ( + SELECT c.Scaffale, COUNT(*) AS CelleTotali + FROM CELLE c + GROUP BY c.Scaffale + ), + OCC2 AS ( + SELECT c.Scaffale, + COUNT(DISTINCT mp.IDCella) AS CelleOccupate, + COUNT(mp.ID) AS NPalet + FROM CELLE c + LEFT JOIN dbo.MagazziniPallet mp ON mp.IDCella = c.IDCella + GROUP BY c.Scaffale + ) + SELECT + ROW_NUMBER() OVER (ORDER BY o.Scaffale) AS ID, + o.Scaffale AS Scaffale, + a.IDMagazzino AS MagID, + m.Codice AS Magazzino, + ISNULL(o2.NPalet,0) AS NPalet, + o.CelleTotali AS CelleTotali, + ISNULL(o2.CelleOccupate,0) AS CelleOccupate + FROM OCC o + LEFT JOIN OCC2 o2 ON o2.Scaffale = o.Scaffale + JOIN dbo.Aree a ON a.ID = :area_id + JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino + WHERE o.Scaffale IS NOT NULL AND LTRIM(RTRIM(o.Scaffale)) <> '' + ORDER BY o.Scaffale; + + +| PARAMS: | +| { | +| "area_id": 4 | +| } | +| | +| COMMENT: | +| END | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 12:39:08.033 | +| PURPOSE : Statistiche Scaffali per Area | +| | +| SQL (copy/paste below): | +| | + WITH CELLE AS ( + SELECT c.ID AS IDCella, c.IDArea, CAST([Corsia] AS NVARCHAR(50)) AS Scaffale + FROM dbo.Celle c + WHERE c.IDArea = :area_id + ), + OCC AS ( + SELECT c.Scaffale, COUNT(*) AS CelleTotali + FROM CELLE c + GROUP BY c.Scaffale + ), + OCC2 AS ( + SELECT c.Scaffale, + COUNT(DISTINCT mp.IDCella) AS CelleOccupate, + COUNT(mp.ID) AS NPalet + FROM CELLE c + LEFT JOIN dbo.MagazziniPallet mp ON mp.IDCella = c.IDCella + GROUP BY c.Scaffale + ) + SELECT + ROW_NUMBER() OVER (ORDER BY o.Scaffale) AS ID, + o.Scaffale AS Scaffale, + a.IDMagazzino AS MagID, + m.Codice AS Magazzino, + ISNULL(o2.NPalet,0) AS NPalet, + o.CelleTotali AS CelleTotali, + ISNULL(o2.CelleOccupate,0) AS CelleOccupate + FROM OCC o + LEFT JOIN OCC2 o2 ON o2.Scaffale = o.Scaffale + JOIN dbo.Aree a ON a.ID = :area_id + JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino + WHERE o.Scaffale IS NOT NULL AND LTRIM(RTRIM(o.Scaffale)) <> '' + ORDER BY o.Scaffale; + + +| PARAMS: | +| { | +| "area_id": 11 | +| } | +| | +| COMMENT: | +| START | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 12:39:08.686 | +| PURPOSE : Statistiche Scaffali per Area | +| ELAPSED : 652 ms | +| | +| SQL (copy/paste below): | +| | + WITH CELLE AS ( + SELECT c.ID AS IDCella, c.IDArea, CAST([Corsia] AS NVARCHAR(50)) AS Scaffale + FROM dbo.Celle c + WHERE c.IDArea = :area_id + ), + OCC AS ( + SELECT c.Scaffale, COUNT(*) AS CelleTotali + FROM CELLE c + GROUP BY c.Scaffale + ), + OCC2 AS ( + SELECT c.Scaffale, + COUNT(DISTINCT mp.IDCella) AS CelleOccupate, + COUNT(mp.ID) AS NPalet + FROM CELLE c + LEFT JOIN dbo.MagazziniPallet mp ON mp.IDCella = c.IDCella + GROUP BY c.Scaffale + ) + SELECT + ROW_NUMBER() OVER (ORDER BY o.Scaffale) AS ID, + o.Scaffale AS Scaffale, + a.IDMagazzino AS MagID, + m.Codice AS Magazzino, + ISNULL(o2.NPalet,0) AS NPalet, + o.CelleTotali AS CelleTotali, + ISNULL(o2.CelleOccupate,0) AS CelleOccupate + FROM OCC o + LEFT JOIN OCC2 o2 ON o2.Scaffale = o.Scaffale + JOIN dbo.Aree a ON a.ID = :area_id + JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino + WHERE o.Scaffale IS NOT NULL AND LTRIM(RTRIM(o.Scaffale)) <> '' + ORDER BY o.Scaffale; + + +| PARAMS: | +| { | +| "area_id": 11 | +| } | +| | +| COMMENT: | +| END | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 12:39:12.809 | +| PURPOSE : Statistiche Scaffali per Area | +| | +| SQL (copy/paste below): | +| | + WITH CELLE AS ( + SELECT c.ID AS IDCella, c.IDArea, CAST([Corsia] AS NVARCHAR(50)) AS Scaffale + FROM dbo.Celle c + WHERE c.IDArea = :area_id + ), + OCC AS ( + SELECT c.Scaffale, COUNT(*) AS CelleTotali + FROM CELLE c + GROUP BY c.Scaffale + ), + OCC2 AS ( + SELECT c.Scaffale, + COUNT(DISTINCT mp.IDCella) AS CelleOccupate, + COUNT(mp.ID) AS NPalet + FROM CELLE c + LEFT JOIN dbo.MagazziniPallet mp ON mp.IDCella = c.IDCella + GROUP BY c.Scaffale + ) + SELECT + ROW_NUMBER() OVER (ORDER BY o.Scaffale) AS ID, + o.Scaffale AS Scaffale, + a.IDMagazzino AS MagID, + m.Codice AS Magazzino, + ISNULL(o2.NPalet,0) AS NPalet, + o.CelleTotali AS CelleTotali, + ISNULL(o2.CelleOccupate,0) AS CelleOccupate + FROM OCC o + LEFT JOIN OCC2 o2 ON o2.Scaffale = o.Scaffale + JOIN dbo.Aree a ON a.ID = :area_id + JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino + WHERE o.Scaffale IS NOT NULL AND LTRIM(RTRIM(o.Scaffale)) <> '' + ORDER BY o.Scaffale; + + +| PARAMS: | +| { | +| "area_id": 11 | +| } | +| | +| COMMENT: | +| START | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 12:39:12.873 | +| PURPOSE : Statistiche Scaffali per Area | +| ELAPSED : 63 ms | +| | +| SQL (copy/paste below): | +| | + WITH CELLE AS ( + SELECT c.ID AS IDCella, c.IDArea, CAST([Corsia] AS NVARCHAR(50)) AS Scaffale + FROM dbo.Celle c + WHERE c.IDArea = :area_id + ), + OCC AS ( + SELECT c.Scaffale, COUNT(*) AS CelleTotali + FROM CELLE c + GROUP BY c.Scaffale + ), + OCC2 AS ( + SELECT c.Scaffale, + COUNT(DISTINCT mp.IDCella) AS CelleOccupate, + COUNT(mp.ID) AS NPalet + FROM CELLE c + LEFT JOIN dbo.MagazziniPallet mp ON mp.IDCella = c.IDCella + GROUP BY c.Scaffale + ) + SELECT + ROW_NUMBER() OVER (ORDER BY o.Scaffale) AS ID, + o.Scaffale AS Scaffale, + a.IDMagazzino AS MagID, + m.Codice AS Magazzino, + ISNULL(o2.NPalet,0) AS NPalet, + o.CelleTotali AS CelleTotali, + ISNULL(o2.CelleOccupate,0) AS CelleOccupate + FROM OCC o + LEFT JOIN OCC2 o2 ON o2.Scaffale = o.Scaffale + JOIN dbo.Aree a ON a.ID = :area_id + JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino + WHERE o.Scaffale IS NOT NULL AND LTRIM(RTRIM(o.Scaffale)) <> '' + ORDER BY o.Scaffale; + + +| PARAMS: | +| { | +| "area_id": 11 | +| } | +| | +| COMMENT: | +| END | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 12:39:16.609 | +| PURPOSE : Statistiche Scaffali per Area | +| | +| SQL (copy/paste below): | +| | + WITH CELLE AS ( + SELECT c.ID AS IDCella, c.IDArea, CAST([Corsia] AS NVARCHAR(50)) AS Scaffale + FROM dbo.Celle c + WHERE c.IDArea = :area_id + ), + OCC AS ( + SELECT c.Scaffale, COUNT(*) AS CelleTotali + FROM CELLE c + GROUP BY c.Scaffale + ), + OCC2 AS ( + SELECT c.Scaffale, + COUNT(DISTINCT mp.IDCella) AS CelleOccupate, + COUNT(mp.ID) AS NPalet + FROM CELLE c + LEFT JOIN dbo.MagazziniPallet mp ON mp.IDCella = c.IDCella + GROUP BY c.Scaffale + ) + SELECT + ROW_NUMBER() OVER (ORDER BY o.Scaffale) AS ID, + o.Scaffale AS Scaffale, + a.IDMagazzino AS MagID, + m.Codice AS Magazzino, + ISNULL(o2.NPalet,0) AS NPalet, + o.CelleTotali AS CelleTotali, + ISNULL(o2.CelleOccupate,0) AS CelleOccupate + FROM OCC o + LEFT JOIN OCC2 o2 ON o2.Scaffale = o.Scaffale + JOIN dbo.Aree a ON a.ID = :area_id + JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino + WHERE o.Scaffale IS NOT NULL AND LTRIM(RTRIM(o.Scaffale)) <> '' + ORDER BY o.Scaffale; + + +| PARAMS: | +| { | +| "area_id": 13 | +| } | +| | +| COMMENT: | +| START | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 12:39:16.664 | +| PURPOSE : Statistiche Scaffali per Area | +| ELAPSED : 54 ms | +| | +| SQL (copy/paste below): | +| | + WITH CELLE AS ( + SELECT c.ID AS IDCella, c.IDArea, CAST([Corsia] AS NVARCHAR(50)) AS Scaffale + FROM dbo.Celle c + WHERE c.IDArea = :area_id + ), + OCC AS ( + SELECT c.Scaffale, COUNT(*) AS CelleTotali + FROM CELLE c + GROUP BY c.Scaffale + ), + OCC2 AS ( + SELECT c.Scaffale, + COUNT(DISTINCT mp.IDCella) AS CelleOccupate, + COUNT(mp.ID) AS NPalet + FROM CELLE c + LEFT JOIN dbo.MagazziniPallet mp ON mp.IDCella = c.IDCella + GROUP BY c.Scaffale + ) + SELECT + ROW_NUMBER() OVER (ORDER BY o.Scaffale) AS ID, + o.Scaffale AS Scaffale, + a.IDMagazzino AS MagID, + m.Codice AS Magazzino, + ISNULL(o2.NPalet,0) AS NPalet, + o.CelleTotali AS CelleTotali, + ISNULL(o2.CelleOccupate,0) AS CelleOccupate + FROM OCC o + LEFT JOIN OCC2 o2 ON o2.Scaffale = o.Scaffale + JOIN dbo.Aree a ON a.ID = :area_id + JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino + WHERE o.Scaffale IS NOT NULL AND LTRIM(RTRIM(o.Scaffale)) <> '' + ORDER BY o.Scaffale; + + +| PARAMS: | +| { | +| "area_id": 13 | +| } | +| | +| COMMENT: | +| END | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 12:39:19.066 | +| PURPOSE : Statistiche Scaffali per Area | +| | +| SQL (copy/paste below): | +| | + WITH CELLE AS ( + SELECT c.ID AS IDCella, c.IDArea, CAST([Corsia] AS NVARCHAR(50)) AS Scaffale + FROM dbo.Celle c + WHERE c.IDArea = :area_id + ), + OCC AS ( + SELECT c.Scaffale, COUNT(*) AS CelleTotali + FROM CELLE c + GROUP BY c.Scaffale + ), + OCC2 AS ( + SELECT c.Scaffale, + COUNT(DISTINCT mp.IDCella) AS CelleOccupate, + COUNT(mp.ID) AS NPalet + FROM CELLE c + LEFT JOIN dbo.MagazziniPallet mp ON mp.IDCella = c.IDCella + GROUP BY c.Scaffale + ) + SELECT + ROW_NUMBER() OVER (ORDER BY o.Scaffale) AS ID, + o.Scaffale AS Scaffale, + a.IDMagazzino AS MagID, + m.Codice AS Magazzino, + ISNULL(o2.NPalet,0) AS NPalet, + o.CelleTotali AS CelleTotali, + ISNULL(o2.CelleOccupate,0) AS CelleOccupate + FROM OCC o + LEFT JOIN OCC2 o2 ON o2.Scaffale = o.Scaffale + JOIN dbo.Aree a ON a.ID = :area_id + JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino + WHERE o.Scaffale IS NOT NULL AND LTRIM(RTRIM(o.Scaffale)) <> '' + ORDER BY o.Scaffale; + + +| PARAMS: | +| { | +| "area_id": 14 | +| } | +| | +| COMMENT: | +| START | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 12:39:19.112 | +| PURPOSE : Statistiche Scaffali per Area | +| ELAPSED : 45 ms | +| | +| SQL (copy/paste below): | +| | + WITH CELLE AS ( + SELECT c.ID AS IDCella, c.IDArea, CAST([Corsia] AS NVARCHAR(50)) AS Scaffale + FROM dbo.Celle c + WHERE c.IDArea = :area_id + ), + OCC AS ( + SELECT c.Scaffale, COUNT(*) AS CelleTotali + FROM CELLE c + GROUP BY c.Scaffale + ), + OCC2 AS ( + SELECT c.Scaffale, + COUNT(DISTINCT mp.IDCella) AS CelleOccupate, + COUNT(mp.ID) AS NPalet + FROM CELLE c + LEFT JOIN dbo.MagazziniPallet mp ON mp.IDCella = c.IDCella + GROUP BY c.Scaffale + ) + SELECT + ROW_NUMBER() OVER (ORDER BY o.Scaffale) AS ID, + o.Scaffale AS Scaffale, + a.IDMagazzino AS MagID, + m.Codice AS Magazzino, + ISNULL(o2.NPalet,0) AS NPalet, + o.CelleTotali AS CelleTotali, + ISNULL(o2.CelleOccupate,0) AS CelleOccupate + FROM OCC o + LEFT JOIN OCC2 o2 ON o2.Scaffale = o.Scaffale + JOIN dbo.Aree a ON a.ID = :area_id + JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino + WHERE o.Scaffale IS NOT NULL AND LTRIM(RTRIM(o.Scaffale)) <> '' + ORDER BY o.Scaffale; + + +| PARAMS: | +| { | +| "area_id": 14 | +| } | +| | +| COMMENT: | +| END | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 12:39:21.897 | +| PURPOSE : Statistiche Scaffali per Area | +| | +| SQL (copy/paste below): | +| | + WITH CELLE AS ( + SELECT c.ID AS IDCella, c.IDArea, CAST([Corsia] AS NVARCHAR(50)) AS Scaffale + FROM dbo.Celle c + WHERE c.IDArea = :area_id + ), + OCC AS ( + SELECT c.Scaffale, COUNT(*) AS CelleTotali + FROM CELLE c + GROUP BY c.Scaffale + ), + OCC2 AS ( + SELECT c.Scaffale, + COUNT(DISTINCT mp.IDCella) AS CelleOccupate, + COUNT(mp.ID) AS NPalet + FROM CELLE c + LEFT JOIN dbo.MagazziniPallet mp ON mp.IDCella = c.IDCella + GROUP BY c.Scaffale + ) + SELECT + ROW_NUMBER() OVER (ORDER BY o.Scaffale) AS ID, + o.Scaffale AS Scaffale, + a.IDMagazzino AS MagID, + m.Codice AS Magazzino, + ISNULL(o2.NPalet,0) AS NPalet, + o.CelleTotali AS CelleTotali, + ISNULL(o2.CelleOccupate,0) AS CelleOccupate + FROM OCC o + LEFT JOIN OCC2 o2 ON o2.Scaffale = o.Scaffale + JOIN dbo.Aree a ON a.ID = :area_id + JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino + WHERE o.Scaffale IS NOT NULL AND LTRIM(RTRIM(o.Scaffale)) <> '' + ORDER BY o.Scaffale; + + +| PARAMS: | +| { | +| "area_id": 12 | +| } | +| | +| COMMENT: | +| START | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 12:39:21.939 | +| PURPOSE : Statistiche Scaffali per Area | +| ELAPSED : 40 ms | +| | +| SQL (copy/paste below): | +| | + WITH CELLE AS ( + SELECT c.ID AS IDCella, c.IDArea, CAST([Corsia] AS NVARCHAR(50)) AS Scaffale + FROM dbo.Celle c + WHERE c.IDArea = :area_id + ), + OCC AS ( + SELECT c.Scaffale, COUNT(*) AS CelleTotali + FROM CELLE c + GROUP BY c.Scaffale + ), + OCC2 AS ( + SELECT c.Scaffale, + COUNT(DISTINCT mp.IDCella) AS CelleOccupate, + COUNT(mp.ID) AS NPalet + FROM CELLE c + LEFT JOIN dbo.MagazziniPallet mp ON mp.IDCella = c.IDCella + GROUP BY c.Scaffale + ) + SELECT + ROW_NUMBER() OVER (ORDER BY o.Scaffale) AS ID, + o.Scaffale AS Scaffale, + a.IDMagazzino AS MagID, + m.Codice AS Magazzino, + ISNULL(o2.NPalet,0) AS NPalet, + o.CelleTotali AS CelleTotali, + ISNULL(o2.CelleOccupate,0) AS CelleOccupate + FROM OCC o + LEFT JOIN OCC2 o2 ON o2.Scaffale = o.Scaffale + JOIN dbo.Aree a ON a.ID = :area_id + JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino + WHERE o.Scaffale IS NOT NULL AND LTRIM(RTRIM(o.Scaffale)) <> '' + ORDER BY o.Scaffale; + + +| PARAMS: | +| { | +| "area_id": 12 | +| } | +| | +| COMMENT: | +| END | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 12:39:37.725 | +| PURPOSE : Statistiche Scaffali per Area | +| | +| SQL (copy/paste below): | +| | + WITH CELLE AS ( + SELECT c.ID AS IDCella, c.IDArea, CAST([Corsia] AS NVARCHAR(50)) AS Scaffale + FROM dbo.Celle c + WHERE c.IDArea = :area_id + ), + OCC AS ( + SELECT c.Scaffale, COUNT(*) AS CelleTotali + FROM CELLE c + GROUP BY c.Scaffale + ), + OCC2 AS ( + SELECT c.Scaffale, + COUNT(DISTINCT mp.IDCella) AS CelleOccupate, + COUNT(mp.ID) AS NPalet + FROM CELLE c + LEFT JOIN dbo.MagazziniPallet mp ON mp.IDCella = c.IDCella + GROUP BY c.Scaffale + ) + SELECT + ROW_NUMBER() OVER (ORDER BY o.Scaffale) AS ID, + o.Scaffale AS Scaffale, + a.IDMagazzino AS MagID, + m.Codice AS Magazzino, + ISNULL(o2.NPalet,0) AS NPalet, + o.CelleTotali AS CelleTotali, + ISNULL(o2.CelleOccupate,0) AS CelleOccupate + FROM OCC o + LEFT JOIN OCC2 o2 ON o2.Scaffale = o.Scaffale + JOIN dbo.Aree a ON a.ID = :area_id + JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino + WHERE o.Scaffale IS NOT NULL AND LTRIM(RTRIM(o.Scaffale)) <> '' + ORDER BY o.Scaffale; + + +| PARAMS: | +| { | +| "area_id": 13 | +| } | +| | +| COMMENT: | +| START | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 12:39:37.772 | +| PURPOSE : Statistiche Scaffali per Area | +| ELAPSED : 46 ms | +| | +| SQL (copy/paste below): | +| | + WITH CELLE AS ( + SELECT c.ID AS IDCella, c.IDArea, CAST([Corsia] AS NVARCHAR(50)) AS Scaffale + FROM dbo.Celle c + WHERE c.IDArea = :area_id + ), + OCC AS ( + SELECT c.Scaffale, COUNT(*) AS CelleTotali + FROM CELLE c + GROUP BY c.Scaffale + ), + OCC2 AS ( + SELECT c.Scaffale, + COUNT(DISTINCT mp.IDCella) AS CelleOccupate, + COUNT(mp.ID) AS NPalet + FROM CELLE c + LEFT JOIN dbo.MagazziniPallet mp ON mp.IDCella = c.IDCella + GROUP BY c.Scaffale + ) + SELECT + ROW_NUMBER() OVER (ORDER BY o.Scaffale) AS ID, + o.Scaffale AS Scaffale, + a.IDMagazzino AS MagID, + m.Codice AS Magazzino, + ISNULL(o2.NPalet,0) AS NPalet, + o.CelleTotali AS CelleTotali, + ISNULL(o2.CelleOccupate,0) AS CelleOccupate + FROM OCC o + LEFT JOIN OCC2 o2 ON o2.Scaffale = o.Scaffale + JOIN dbo.Aree a ON a.ID = :area_id + JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino + WHERE o.Scaffale IS NOT NULL AND LTRIM(RTRIM(o.Scaffale)) <> '' + ORDER BY o.Scaffale; + + +| PARAMS: | +| { | +| "area_id": 13 | +| } | +| | +| COMMENT: | +| END | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 12:39:41.282 | +| PURPOSE : Statistiche Scaffali per Area | +| | +| SQL (copy/paste below): | +| | + WITH CELLE AS ( + SELECT c.ID AS IDCella, c.IDArea, CAST([Corsia] AS NVARCHAR(50)) AS Scaffale + FROM dbo.Celle c + WHERE c.IDArea = :area_id + ), + OCC AS ( + SELECT c.Scaffale, COUNT(*) AS CelleTotali + FROM CELLE c + GROUP BY c.Scaffale + ), + OCC2 AS ( + SELECT c.Scaffale, + COUNT(DISTINCT mp.IDCella) AS CelleOccupate, + COUNT(mp.ID) AS NPalet + FROM CELLE c + LEFT JOIN dbo.MagazziniPallet mp ON mp.IDCella = c.IDCella + GROUP BY c.Scaffale + ) + SELECT + ROW_NUMBER() OVER (ORDER BY o.Scaffale) AS ID, + o.Scaffale AS Scaffale, + a.IDMagazzino AS MagID, + m.Codice AS Magazzino, + ISNULL(o2.NPalet,0) AS NPalet, + o.CelleTotali AS CelleTotali, + ISNULL(o2.CelleOccupate,0) AS CelleOccupate + FROM OCC o + LEFT JOIN OCC2 o2 ON o2.Scaffale = o.Scaffale + JOIN dbo.Aree a ON a.ID = :area_id + JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino + WHERE o.Scaffale IS NOT NULL AND LTRIM(RTRIM(o.Scaffale)) <> '' + ORDER BY o.Scaffale; + + +| PARAMS: | +| { | +| "area_id": 1 | +| } | +| | +| COMMENT: | +| START | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 12:39:41.345 | +| PURPOSE : Statistiche Scaffali per Area | +| ELAPSED : 63 ms | +| | +| SQL (copy/paste below): | +| | + WITH CELLE AS ( + SELECT c.ID AS IDCella, c.IDArea, CAST([Corsia] AS NVARCHAR(50)) AS Scaffale + FROM dbo.Celle c + WHERE c.IDArea = :area_id + ), + OCC AS ( + SELECT c.Scaffale, COUNT(*) AS CelleTotali + FROM CELLE c + GROUP BY c.Scaffale + ), + OCC2 AS ( + SELECT c.Scaffale, + COUNT(DISTINCT mp.IDCella) AS CelleOccupate, + COUNT(mp.ID) AS NPalet + FROM CELLE c + LEFT JOIN dbo.MagazziniPallet mp ON mp.IDCella = c.IDCella + GROUP BY c.Scaffale + ) + SELECT + ROW_NUMBER() OVER (ORDER BY o.Scaffale) AS ID, + o.Scaffale AS Scaffale, + a.IDMagazzino AS MagID, + m.Codice AS Magazzino, + ISNULL(o2.NPalet,0) AS NPalet, + o.CelleTotali AS CelleTotali, + ISNULL(o2.CelleOccupate,0) AS CelleOccupate + FROM OCC o + LEFT JOIN OCC2 o2 ON o2.Scaffale = o.Scaffale + JOIN dbo.Aree a ON a.ID = :area_id + JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino + WHERE o.Scaffale IS NOT NULL AND LTRIM(RTRIM(o.Scaffale)) <> '' + ORDER BY o.Scaffale; + + +| PARAMS: | +| { | +| "area_id": 1 | +| } | +| | +| COMMENT: | +| END | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 12:44:05.736 | +| PURPOSE : Rilevamento colonne Celle | +| ELAPSED : 0 ms | +| | +| SQL (copy/paste below): | +| | +SELECT LOWER(name) FROM sys.columns WHERE object_id = OBJECT_ID('dbo.Celle'); + +| PARAMS: | +| {} | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 12:44:05.806 | +| PURPOSE : Lista Aree | +| | +| SQL (copy/paste below): | +| | +SELECT a.ID AS AreaID, a.Descrizione AS AreaNome, a.IDMagazzino AS MagID, m.Codice AS Magazzino +FROM dbo.Aree a +JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino +WHERE len(trim(a.Descrizione)) = 2; + +| PARAMS: | +| {} | +| | +| COMMENT: | +| START | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 12:44:05.809 | +| PURPOSE : Lista Aree | +| ELAPSED : 3 ms | +| | +| SQL (copy/paste below): | +| | +SELECT a.ID AS AreaID, a.Descrizione AS AreaNome, a.IDMagazzino AS MagID, m.Codice AS Magazzino +FROM dbo.Aree a +JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino +WHERE len(trim(a.Descrizione)) = 2; + +| PARAMS: | +| {} | +| | +| COMMENT: | +| END | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 12:44:05.812 | +| PURPOSE : Statistiche Scaffali per Area | +| | +| SQL (copy/paste below): | +| | + WITH CELLE AS ( + SELECT c.ID AS IDCella, c.IDArea, CAST([Corsia] AS NVARCHAR(50)) AS Scaffale + FROM dbo.Celle c + WHERE c.IDArea = :area_id + ), + OCC AS ( + SELECT c.Scaffale, COUNT(*) AS CelleTotali + FROM CELLE c + GROUP BY c.Scaffale + ), + OCC2 AS ( + SELECT c.Scaffale, + COUNT(DISTINCT mp.IDCella) AS CelleOccupate, + COUNT(mp.ID) AS NPalet + FROM CELLE c + LEFT JOIN dbo.MagazziniPallet mp ON mp.IDCella = c.IDCella + GROUP BY c.Scaffale + ) + SELECT + ROW_NUMBER() OVER (ORDER BY o.Scaffale) AS ID, + o.Scaffale AS Scaffale, + a.IDMagazzino AS MagID, + m.Codice AS Magazzino, + ISNULL(o2.NPalet,0) AS NPalet, + o.CelleTotali AS CelleTotali, + ISNULL(o2.CelleOccupate,0) AS CelleOccupate + FROM OCC o + LEFT JOIN OCC2 o2 ON o2.Scaffale = o.Scaffale + JOIN dbo.Aree a ON a.ID = :area_id + JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino + WHERE o.Scaffale IS NOT NULL AND LTRIM(RTRIM(o.Scaffale)) <> '' + ORDER BY o.Scaffale; + + +| PARAMS: | +| { | +| "area_id": 1 | +| } | +| | +| COMMENT: | +| START | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 12:44:05.889 | +| PURPOSE : Statistiche Scaffali per Area | +| ELAPSED : 77 ms | +| | +| SQL (copy/paste below): | +| | + WITH CELLE AS ( + SELECT c.ID AS IDCella, c.IDArea, CAST([Corsia] AS NVARCHAR(50)) AS Scaffale + FROM dbo.Celle c + WHERE c.IDArea = :area_id + ), + OCC AS ( + SELECT c.Scaffale, COUNT(*) AS CelleTotali + FROM CELLE c + GROUP BY c.Scaffale + ), + OCC2 AS ( + SELECT c.Scaffale, + COUNT(DISTINCT mp.IDCella) AS CelleOccupate, + COUNT(mp.ID) AS NPalet + FROM CELLE c + LEFT JOIN dbo.MagazziniPallet mp ON mp.IDCella = c.IDCella + GROUP BY c.Scaffale + ) + SELECT + ROW_NUMBER() OVER (ORDER BY o.Scaffale) AS ID, + o.Scaffale AS Scaffale, + a.IDMagazzino AS MagID, + m.Codice AS Magazzino, + ISNULL(o2.NPalet,0) AS NPalet, + o.CelleTotali AS CelleTotali, + ISNULL(o2.CelleOccupate,0) AS CelleOccupate + FROM OCC o + LEFT JOIN OCC2 o2 ON o2.Scaffale = o.Scaffale + JOIN dbo.Aree a ON a.ID = :area_id + JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino + WHERE o.Scaffale IS NOT NULL AND LTRIM(RTRIM(o.Scaffale)) <> '' + ORDER BY o.Scaffale; + + +| PARAMS: | +| { | +| "area_id": 1 | +| } | +| | +| COMMENT: | +| END | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 12:46:21.269 | +| PURPOSE : Rilevamento colonne Celle | +| ELAPSED : 0 ms | +| | +| SQL (copy/paste below): | +| | +SELECT LOWER(name) FROM sys.columns WHERE object_id = OBJECT_ID('dbo.Celle'); + +| PARAMS: | +| {} | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 12:46:21.359 | +| PURPOSE : Lista Aree | +| | +| SQL (copy/paste below): | +| | +SELECT a.ID AS AreaID, a.Descrizione AS AreaNome, a.IDMagazzino AS MagID, m.Codice AS Magazzino +FROM dbo.Aree a +JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino +WHERE len(trim(a.Descrizione)) = 2; + +| PARAMS: | +| {} | +| | +| COMMENT: | +| START | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 12:46:21.363 | +| PURPOSE : Lista Aree | +| ELAPSED : 3 ms | +| | +| SQL (copy/paste below): | +| | +SELECT a.ID AS AreaID, a.Descrizione AS AreaNome, a.IDMagazzino AS MagID, m.Codice AS Magazzino +FROM dbo.Aree a +JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino +WHERE len(trim(a.Descrizione)) = 2; + +| PARAMS: | +| {} | +| | +| COMMENT: | +| END | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 12:46:21.448 | +| PURPOSE : Statistiche Scaffali per Area | +| | +| SQL (copy/paste below): | +| | + WITH CELLE AS ( + SELECT c.ID AS IDCella, c.IDArea, CAST([Corsia] AS NVARCHAR(50)) AS Scaffale + FROM dbo.Celle c + WHERE c.IDArea = :area_id + ), + OCC AS ( + SELECT c.Scaffale, COUNT(*) AS CelleTotali + FROM CELLE c + GROUP BY c.Scaffale + ), + OCC2 AS ( + SELECT c.Scaffale, + COUNT(DISTINCT mp.IDCella) AS CelleOccupate, + COUNT(mp.ID) AS NPalet + FROM CELLE c + LEFT JOIN dbo.MagazziniPallet mp ON mp.IDCella = c.IDCella + GROUP BY c.Scaffale + ) + SELECT + ROW_NUMBER() OVER (ORDER BY o.Scaffale) AS ID, + o.Scaffale AS Scaffale, + a.IDMagazzino AS MagID, + m.Codice AS Magazzino, + ISNULL(o2.NPalet,0) AS NPalet, + o.CelleTotali AS CelleTotali, + ISNULL(o2.CelleOccupate,0) AS CelleOccupate + FROM OCC o + LEFT JOIN OCC2 o2 ON o2.Scaffale = o.Scaffale + JOIN dbo.Aree a ON a.ID = :area_id + JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino + WHERE o.Scaffale IS NOT NULL AND LTRIM(RTRIM(o.Scaffale)) <> '' + ORDER BY o.Scaffale; + + +| PARAMS: | +| { | +| "area_id": 1 | +| } | +| | +| COMMENT: | +| START | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 12:46:21.526 | +| PURPOSE : Statistiche Scaffali per Area | +| ELAPSED : 77 ms | +| | +| SQL (copy/paste below): | +| | + WITH CELLE AS ( + SELECT c.ID AS IDCella, c.IDArea, CAST([Corsia] AS NVARCHAR(50)) AS Scaffale + FROM dbo.Celle c + WHERE c.IDArea = :area_id + ), + OCC AS ( + SELECT c.Scaffale, COUNT(*) AS CelleTotali + FROM CELLE c + GROUP BY c.Scaffale + ), + OCC2 AS ( + SELECT c.Scaffale, + COUNT(DISTINCT mp.IDCella) AS CelleOccupate, + COUNT(mp.ID) AS NPalet + FROM CELLE c + LEFT JOIN dbo.MagazziniPallet mp ON mp.IDCella = c.IDCella + GROUP BY c.Scaffale + ) + SELECT + ROW_NUMBER() OVER (ORDER BY o.Scaffale) AS ID, + o.Scaffale AS Scaffale, + a.IDMagazzino AS MagID, + m.Codice AS Magazzino, + ISNULL(o2.NPalet,0) AS NPalet, + o.CelleTotali AS CelleTotali, + ISNULL(o2.CelleOccupate,0) AS CelleOccupate + FROM OCC o + LEFT JOIN OCC2 o2 ON o2.Scaffale = o.Scaffale + JOIN dbo.Aree a ON a.ID = :area_id + JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino + WHERE o.Scaffale IS NOT NULL AND LTRIM(RTRIM(o.Scaffale)) <> '' + ORDER BY o.Scaffale; + + +| PARAMS: | +| { | +| "area_id": 1 | +| } | +| | +| COMMENT: | +| END | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 12:46:37.304 | +| PURPOSE : SIMULAZIONE Azzeramento Scaffale (conteggio) | +| | +| SQL (copy/paste below): | +| | +SELECT COUNT(*) AS WouldDelete +FROM dbo.MagazziniPallet mp +JOIN dbo.Celle c ON c.ID = mp.IDCella +WHERE c.IDArea = :area_id AND CAST([Corsia] AS NVARCHAR(50)) = :scaffale; + +| PARAMS: | +| { | +| "area_id": 1, | +| "scaffale": " 1A" | +| } | +| | +| COMMENT: | +| START | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 12:46:37.355 | +| PURPOSE : SIMULAZIONE Azzeramento Scaffale (conteggio) | +| ELAPSED : 50 ms | +| | +| SQL (copy/paste below): | +| | +SELECT COUNT(*) AS WouldDelete +FROM dbo.MagazziniPallet mp +JOIN dbo.Celle c ON c.ID = mp.IDCella +WHERE c.IDArea = :area_id AND CAST([Corsia] AS NVARCHAR(50)) = :scaffale; + +| PARAMS: | +| { | +| "area_id": 1, | +| "scaffale": " 1A" | +| } | +| | +| COMMENT: | +| END | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 12:46:45.607 | +| PURPOSE : SIMULAZIONE Azzeramento Scaffale (conteggio) | +| | +| SQL (copy/paste below): | +| | +SELECT COUNT(*) AS WouldDelete +FROM dbo.MagazziniPallet mp +JOIN dbo.Celle c ON c.ID = mp.IDCella +WHERE c.IDArea = :area_id AND CAST([Corsia] AS NVARCHAR(50)) = :scaffale; + +| PARAMS: | +| { | +| "area_id": 1, | +| "scaffale": " 1A" | +| } | +| | +| COMMENT: | +| START | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 12:46:45.658 | +| PURPOSE : SIMULAZIONE Azzeramento Scaffale (conteggio) | +| ELAPSED : 51 ms | +| | +| SQL (copy/paste below): | +| | +SELECT COUNT(*) AS WouldDelete +FROM dbo.MagazziniPallet mp +JOIN dbo.Celle c ON c.ID = mp.IDCella +WHERE c.IDArea = :area_id AND CAST([Corsia] AS NVARCHAR(50)) = :scaffale; + +| PARAMS: | +| { | +| "area_id": 1, | +| "scaffale": " 1A" | +| } | +| | +| COMMENT: | +| END | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 12:56:37.640 | +| PURPOSE : Rilevamento colonne Celle | +| ELAPSED : 0 ms | +| | +| SQL (copy/paste below): | +| | +SELECT LOWER(name) FROM sys.columns WHERE object_id = OBJECT_ID('dbo.Celle'); + +| PARAMS: | +| {} | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 12:56:37.706 | +| PURPOSE : Lista Aree | +| | +| SQL (copy/paste below): | +| | +SELECT a.ID AS AreaID, a.Descrizione AS AreaNome, a.IDMagazzino AS MagID, m.Codice AS Magazzino +FROM dbo.Aree a +JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino +WHERE len(trim(a.Descrizione)) = 2; + +| PARAMS: | +| {} | +| | +| COMMENT: | +| START | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 12:56:37.711 | +| PURPOSE : Lista Aree | +| ELAPSED : 3 ms | +| | +| SQL (copy/paste below): | +| | +SELECT a.ID AS AreaID, a.Descrizione AS AreaNome, a.IDMagazzino AS MagID, m.Codice AS Magazzino +FROM dbo.Aree a +JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino +WHERE len(trim(a.Descrizione)) = 2; + +| PARAMS: | +| {} | +| | +| COMMENT: | +| END | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 12:56:37.800 | +| PURPOSE : Statistiche Scaffali per Area | +| | +| SQL (copy/paste below): | +| | + WITH CELLE AS ( + SELECT c.ID AS IDCella, c.IDArea, CAST([Corsia] AS NVARCHAR(50)) AS Scaffale + FROM dbo.Celle c + WHERE c.IDArea = :area_id + ), + OCC AS ( + SELECT c.Scaffale, COUNT(*) AS CelleTotali + FROM CELLE c + GROUP BY c.Scaffale + ), + OCC2 AS ( + SELECT c.Scaffale, + COUNT(DISTINCT mp.IDCella) AS CelleOccupate, + COUNT(mp.ID) AS NPalet + FROM CELLE c + LEFT JOIN dbo.MagazziniPallet mp ON mp.IDCella = c.IDCella + GROUP BY c.Scaffale + ) + SELECT + ROW_NUMBER() OVER (ORDER BY o.Scaffale) AS ID, + o.Scaffale AS Scaffale, + a.IDMagazzino AS MagID, + m.Codice AS Magazzino, + ISNULL(o2.NPalet,0) AS NPalet, + o.CelleTotali AS CelleTotali, + ISNULL(o2.CelleOccupate,0) AS CelleOccupate + FROM OCC o + LEFT JOIN OCC2 o2 ON o2.Scaffale = o.Scaffale + JOIN dbo.Aree a ON a.ID = :area_id + JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino + WHERE o.Scaffale IS NOT NULL AND LTRIM(RTRIM(o.Scaffale)) <> '' + ORDER BY o.Scaffale; + + +| PARAMS: | +| { | +| "area_id": 1 | +| } | +| | +| COMMENT: | +| START | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 12:56:37.880 | +| PURPOSE : Statistiche Scaffali per Area | +| ELAPSED : 79 ms | +| | +| SQL (copy/paste below): | +| | + WITH CELLE AS ( + SELECT c.ID AS IDCella, c.IDArea, CAST([Corsia] AS NVARCHAR(50)) AS Scaffale + FROM dbo.Celle c + WHERE c.IDArea = :area_id + ), + OCC AS ( + SELECT c.Scaffale, COUNT(*) AS CelleTotali + FROM CELLE c + GROUP BY c.Scaffale + ), + OCC2 AS ( + SELECT c.Scaffale, + COUNT(DISTINCT mp.IDCella) AS CelleOccupate, + COUNT(mp.ID) AS NPalet + FROM CELLE c + LEFT JOIN dbo.MagazziniPallet mp ON mp.IDCella = c.IDCella + GROUP BY c.Scaffale + ) + SELECT + ROW_NUMBER() OVER (ORDER BY o.Scaffale) AS ID, + o.Scaffale AS Scaffale, + a.IDMagazzino AS MagID, + m.Codice AS Magazzino, + ISNULL(o2.NPalet,0) AS NPalet, + o.CelleTotali AS CelleTotali, + ISNULL(o2.CelleOccupate,0) AS CelleOccupate + FROM OCC o + LEFT JOIN OCC2 o2 ON o2.Scaffale = o.Scaffale + JOIN dbo.Aree a ON a.ID = :area_id + JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino + WHERE o.Scaffale IS NOT NULL AND LTRIM(RTRIM(o.Scaffale)) <> '' + ORDER BY o.Scaffale; + + +| PARAMS: | +| { | +| "area_id": 1 | +| } | +| | +| COMMENT: | +| END | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 12:57:09.968 | +| PURPOSE : Rilevamento colonne Celle | +| ELAPSED : 0 ms | +| | +| SQL (copy/paste below): | +| | +SELECT LOWER(name) FROM sys.columns WHERE object_id = OBJECT_ID('dbo.Celle'); + +| PARAMS: | +| {} | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 12:57:10.044 | +| PURPOSE : Lista Aree | +| | +| SQL (copy/paste below): | +| | +SELECT a.ID AS AreaID, a.Descrizione AS AreaNome, a.IDMagazzino AS MagID, m.Codice AS Magazzino +FROM dbo.Aree a +JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino +WHERE len(trim(a.Descrizione)) = 2; + +| PARAMS: | +| {} | +| | +| COMMENT: | +| START | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 12:57:10.051 | +| PURPOSE : Lista Aree | +| ELAPSED : 6 ms | +| | +| SQL (copy/paste below): | +| | +SELECT a.ID AS AreaID, a.Descrizione AS AreaNome, a.IDMagazzino AS MagID, m.Codice AS Magazzino +FROM dbo.Aree a +JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino +WHERE len(trim(a.Descrizione)) = 2; + +| PARAMS: | +| {} | +| | +| COMMENT: | +| END | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 12:57:10.138 | +| PURPOSE : Statistiche Scaffali per Area | +| | +| SQL (copy/paste below): | +| | + WITH CELLE AS ( + SELECT c.ID AS IDCella, c.IDArea, CAST([Corsia] AS NVARCHAR(50)) AS Scaffale + FROM dbo.Celle c + WHERE c.IDArea = :area_id + ), + OCC AS ( + SELECT c.Scaffale, COUNT(*) AS CelleTotali + FROM CELLE c + GROUP BY c.Scaffale + ), + OCC2 AS ( + SELECT c.Scaffale, + COUNT(DISTINCT mp.IDCella) AS CelleOccupate, + COUNT(mp.ID) AS NPalet + FROM CELLE c + LEFT JOIN dbo.MagazziniPallet mp ON mp.IDCella = c.IDCella + GROUP BY c.Scaffale + ) + SELECT + ROW_NUMBER() OVER (ORDER BY o.Scaffale) AS ID, + o.Scaffale AS Scaffale, + a.IDMagazzino AS MagID, + m.Codice AS Magazzino, + ISNULL(o2.NPalet,0) AS NPalet, + o.CelleTotali AS CelleTotali, + ISNULL(o2.CelleOccupate,0) AS CelleOccupate + FROM OCC o + LEFT JOIN OCC2 o2 ON o2.Scaffale = o.Scaffale + JOIN dbo.Aree a ON a.ID = :area_id + JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino + WHERE o.Scaffale IS NOT NULL AND LTRIM(RTRIM(o.Scaffale)) <> '' + ORDER BY o.Scaffale; + + +| PARAMS: | +| { | +| "area_id": 1 | +| } | +| | +| COMMENT: | +| START | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 12:57:10.221 | +| PURPOSE : Statistiche Scaffali per Area | +| ELAPSED : 82 ms | +| | +| SQL (copy/paste below): | +| | + WITH CELLE AS ( + SELECT c.ID AS IDCella, c.IDArea, CAST([Corsia] AS NVARCHAR(50)) AS Scaffale + FROM dbo.Celle c + WHERE c.IDArea = :area_id + ), + OCC AS ( + SELECT c.Scaffale, COUNT(*) AS CelleTotali + FROM CELLE c + GROUP BY c.Scaffale + ), + OCC2 AS ( + SELECT c.Scaffale, + COUNT(DISTINCT mp.IDCella) AS CelleOccupate, + COUNT(mp.ID) AS NPalet + FROM CELLE c + LEFT JOIN dbo.MagazziniPallet mp ON mp.IDCella = c.IDCella + GROUP BY c.Scaffale + ) + SELECT + ROW_NUMBER() OVER (ORDER BY o.Scaffale) AS ID, + o.Scaffale AS Scaffale, + a.IDMagazzino AS MagID, + m.Codice AS Magazzino, + ISNULL(o2.NPalet,0) AS NPalet, + o.CelleTotali AS CelleTotali, + ISNULL(o2.CelleOccupate,0) AS CelleOccupate + FROM OCC o + LEFT JOIN OCC2 o2 ON o2.Scaffale = o.Scaffale + JOIN dbo.Aree a ON a.ID = :area_id + JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino + WHERE o.Scaffale IS NOT NULL AND LTRIM(RTRIM(o.Scaffale)) <> '' + ORDER BY o.Scaffale; + + +| PARAMS: | +| { | +| "area_id": 1 | +| } | +| | +| COMMENT: | +| END | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 12:57:40.974 | +| PURPOSE : Rilevamento colonne Celle | +| ELAPSED : 0 ms | +| | +| SQL (copy/paste below): | +| | +SELECT LOWER(name) FROM sys.columns WHERE object_id = OBJECT_ID('dbo.Celle'); + +| PARAMS: | +| {} | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 12:57:41.012 | +| PURPOSE : Lista Aree | +| | +| SQL (copy/paste below): | +| | +SELECT a.ID AS AreaID, a.Descrizione AS AreaNome, a.IDMagazzino AS MagID, m.Codice AS Magazzino +FROM dbo.Aree a +JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino +WHERE len(trim(a.Descrizione)) = 2; + +| PARAMS: | +| {} | +| | +| COMMENT: | +| START | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 12:57:41.017 | +| PURPOSE : Lista Aree | +| ELAPSED : 4 ms | +| | +| SQL (copy/paste below): | +| | +SELECT a.ID AS AreaID, a.Descrizione AS AreaNome, a.IDMagazzino AS MagID, m.Codice AS Magazzino +FROM dbo.Aree a +JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino +WHERE len(trim(a.Descrizione)) = 2; + +| PARAMS: | +| {} | +| | +| COMMENT: | +| END | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 12:57:41.022 | +| PURPOSE : Statistiche Scaffali per Area | +| | +| SQL (copy/paste below): | +| | + WITH CELLE AS ( + SELECT c.ID AS IDCella, c.IDArea, CAST([Corsia] AS NVARCHAR(50)) AS Scaffale + FROM dbo.Celle c + WHERE c.IDArea = :area_id + ), + OCC AS ( + SELECT c.Scaffale, COUNT(*) AS CelleTotali + FROM CELLE c + GROUP BY c.Scaffale + ), + OCC2 AS ( + SELECT c.Scaffale, + COUNT(DISTINCT mp.IDCella) AS CelleOccupate, + COUNT(mp.ID) AS NPalet + FROM CELLE c + LEFT JOIN dbo.MagazziniPallet mp ON mp.IDCella = c.IDCella + GROUP BY c.Scaffale + ) + SELECT + ROW_NUMBER() OVER (ORDER BY o.Scaffale) AS ID, + o.Scaffale AS Scaffale, + a.IDMagazzino AS MagID, + m.Codice AS Magazzino, + ISNULL(o2.NPalet,0) AS NPalet, + o.CelleTotali AS CelleTotali, + ISNULL(o2.CelleOccupate,0) AS CelleOccupate + FROM OCC o + LEFT JOIN OCC2 o2 ON o2.Scaffale = o.Scaffale + JOIN dbo.Aree a ON a.ID = :area_id + JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino + WHERE o.Scaffale IS NOT NULL AND LTRIM(RTRIM(o.Scaffale)) <> '' + ORDER BY o.Scaffale; + + +| PARAMS: | +| { | +| "area_id": 1 | +| } | +| | +| COMMENT: | +| START | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 12:57:41.089 | +| PURPOSE : Statistiche Scaffali per Area | +| ELAPSED : 66 ms | +| | +| SQL (copy/paste below): | +| | + WITH CELLE AS ( + SELECT c.ID AS IDCella, c.IDArea, CAST([Corsia] AS NVARCHAR(50)) AS Scaffale + FROM dbo.Celle c + WHERE c.IDArea = :area_id + ), + OCC AS ( + SELECT c.Scaffale, COUNT(*) AS CelleTotali + FROM CELLE c + GROUP BY c.Scaffale + ), + OCC2 AS ( + SELECT c.Scaffale, + COUNT(DISTINCT mp.IDCella) AS CelleOccupate, + COUNT(mp.ID) AS NPalet + FROM CELLE c + LEFT JOIN dbo.MagazziniPallet mp ON mp.IDCella = c.IDCella + GROUP BY c.Scaffale + ) + SELECT + ROW_NUMBER() OVER (ORDER BY o.Scaffale) AS ID, + o.Scaffale AS Scaffale, + a.IDMagazzino AS MagID, + m.Codice AS Magazzino, + ISNULL(o2.NPalet,0) AS NPalet, + o.CelleTotali AS CelleTotali, + ISNULL(o2.CelleOccupate,0) AS CelleOccupate + FROM OCC o + LEFT JOIN OCC2 o2 ON o2.Scaffale = o.Scaffale + JOIN dbo.Aree a ON a.ID = :area_id + JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino + WHERE o.Scaffale IS NOT NULL AND LTRIM(RTRIM(o.Scaffale)) <> '' + ORDER BY o.Scaffale; + + +| PARAMS: | +| { | +| "area_id": 1 | +| } | +| | +| COMMENT: | +| END | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 12:58:05.716 | +| PURPOSE : Rilevamento colonne Celle | +| ELAPSED : 0 ms | +| | +| SQL (copy/paste below): | +| | +SELECT LOWER(name) FROM sys.columns WHERE object_id = OBJECT_ID('dbo.Celle'); + +| PARAMS: | +| {} | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 12:58:05.807 | +| PURPOSE : Lista Aree | +| | +| SQL (copy/paste below): | +| | +SELECT a.ID AS AreaID, a.Descrizione AS AreaNome, a.IDMagazzino AS MagID, m.Codice AS Magazzino +FROM dbo.Aree a +JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino +WHERE len(trim(a.Descrizione)) = 2; + +| PARAMS: | +| {} | +| | +| COMMENT: | +| START | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 12:58:05.812 | +| PURPOSE : Lista Aree | +| ELAPSED : 4 ms | +| | +| SQL (copy/paste below): | +| | +SELECT a.ID AS AreaID, a.Descrizione AS AreaNome, a.IDMagazzino AS MagID, m.Codice AS Magazzino +FROM dbo.Aree a +JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino +WHERE len(trim(a.Descrizione)) = 2; + +| PARAMS: | +| {} | +| | +| COMMENT: | +| END | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 17:11:06.431 | +| PURPOSE : Rilevamento colonne Celle | +| ELAPSED : 0 ms | +| | +| SQL (copy/paste below): | +| | +SELECT LOWER(name) FROM sys.columns WHERE object_id = OBJECT_ID('dbo.Celle'); + +| PARAMS: | +| {} | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 17:11:06.462 | +| PURPOSE : Lista Aree | +| | +| SQL (copy/paste below): | +| | +SELECT a.ID AS AreaID, a.Descrizione AS AreaNome, a.IDMagazzino AS MagID, m.Codice AS Magazzino +FROM dbo.Aree a +JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino +WHERE len(trim(a.Descrizione)) = 2; + +| PARAMS: | +| {} | +| | +| COMMENT: | +| START | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 17:11:06.468 | +| PURPOSE : Lista Aree | +| ELAPSED : 5 ms | +| | +| SQL (copy/paste below): | +| | +SELECT a.ID AS AreaID, a.Descrizione AS AreaNome, a.IDMagazzino AS MagID, m.Codice AS Magazzino +FROM dbo.Aree a +JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino +WHERE len(trim(a.Descrizione)) = 2; + +| PARAMS: | +| {} | +| | +| COMMENT: | +| END | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 17:11:06.471 | +| PURPOSE : Statistiche Scaffali per Area | +| | +| SQL (copy/paste below): | +| | + WITH CELLE AS ( + SELECT c.ID AS IDCella, c.IDArea, CAST([Corsia] AS NVARCHAR(50)) AS Scaffale + FROM dbo.Celle c + WHERE c.IDArea = :area_id + ), + OCC AS ( + SELECT c.Scaffale, COUNT(*) AS CelleTotali + FROM CELLE c + GROUP BY c.Scaffale + ), + OCC2 AS ( + SELECT c.Scaffale, + COUNT(DISTINCT mp.IDCella) AS CelleOccupate, + COUNT(mp.ID) AS NPalet + FROM CELLE c + LEFT JOIN dbo.MagazziniPallet mp ON mp.IDCella = c.IDCella + GROUP BY c.Scaffale + ) + SELECT + ROW_NUMBER() OVER (ORDER BY o.Scaffale) AS ID, + o.Scaffale AS Scaffale, + a.IDMagazzino AS MagID, + m.Codice AS Magazzino, + ISNULL(o2.NPalet,0) AS NPalet, + o.CelleTotali AS CelleTotali, + ISNULL(o2.CelleOccupate,0) AS CelleOccupate + FROM OCC o + LEFT JOIN OCC2 o2 ON o2.Scaffale = o.Scaffale + JOIN dbo.Aree a ON a.ID = :area_id + JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino + WHERE o.Scaffale IS NOT NULL AND LTRIM(RTRIM(o.Scaffale)) <> '' + ORDER BY o.Scaffale; + + +| PARAMS: | +| { | +| "area_id": 1 | +| } | +| | +| COMMENT: | +| START | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-01 17:11:06.555 | +| PURPOSE : Statistiche Scaffali per Area | +| ELAPSED : 83 ms | +| | +| SQL (copy/paste below): | +| | + WITH CELLE AS ( + SELECT c.ID AS IDCella, c.IDArea, CAST([Corsia] AS NVARCHAR(50)) AS Scaffale + FROM dbo.Celle c + WHERE c.IDArea = :area_id + ), + OCC AS ( + SELECT c.Scaffale, COUNT(*) AS CelleTotali + FROM CELLE c + GROUP BY c.Scaffale + ), + OCC2 AS ( + SELECT c.Scaffale, + COUNT(DISTINCT mp.IDCella) AS CelleOccupate, + COUNT(mp.ID) AS NPalet + FROM CELLE c + LEFT JOIN dbo.MagazziniPallet mp ON mp.IDCella = c.IDCella + GROUP BY c.Scaffale + ) + SELECT + ROW_NUMBER() OVER (ORDER BY o.Scaffale) AS ID, + o.Scaffale AS Scaffale, + a.IDMagazzino AS MagID, + m.Codice AS Magazzino, + ISNULL(o2.NPalet,0) AS NPalet, + o.CelleTotali AS CelleTotali, + ISNULL(o2.CelleOccupate,0) AS CelleOccupate + FROM OCC o + LEFT JOIN OCC2 o2 ON o2.Scaffale = o.Scaffale + JOIN dbo.Aree a ON a.ID = :area_id + JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino + WHERE o.Scaffale IS NOT NULL AND LTRIM(RTRIM(o.Scaffale)) <> '' + ORDER BY o.Scaffale; + + +| PARAMS: | +| { | +| "area_id": 1 | +| } | +| | +| COMMENT: | +| END | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-03 20:49:07.101 | +| PURPOSE : Rilevamento colonne Celle | +| ELAPSED : 0 ms | +| | +| SQL (copy/paste below): | +| | +SELECT LOWER(name) FROM sys.columns WHERE object_id = OBJECT_ID('dbo.Celle'); + +| PARAMS: | +| {} | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-03 20:49:07.151 | +| PURPOSE : Lista Aree | +| | +| SQL (copy/paste below): | +| | +SELECT a.ID AS AreaID, a.Descrizione AS AreaNome, a.IDMagazzino AS MagID, m.Codice AS Magazzino +FROM dbo.Aree a +JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino +WHERE len(trim(a.Descrizione)) = 2; + +| PARAMS: | +| {} | +| | +| COMMENT: | +| START | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-03 20:49:07.157 | +| PURPOSE : Lista Aree | +| ELAPSED : 5 ms | +| | +| SQL (copy/paste below): | +| | +SELECT a.ID AS AreaID, a.Descrizione AS AreaNome, a.IDMagazzino AS MagID, m.Codice AS Magazzino +FROM dbo.Aree a +JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino +WHERE len(trim(a.Descrizione)) = 2; + +| PARAMS: | +| {} | +| | +| COMMENT: | +| END | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-03 20:49:07.159 | +| PURPOSE : Statistiche Scaffali per Area | +| | +| SQL (copy/paste below): | +| | + WITH CELLE AS ( + SELECT c.ID AS IDCella, c.IDArea, CAST([Corsia] AS NVARCHAR(50)) AS Scaffale + FROM dbo.Celle c + WHERE c.IDArea = :area_id + ), + OCC AS ( + SELECT c.Scaffale, COUNT(*) AS CelleTotali + FROM CELLE c + GROUP BY c.Scaffale + ), + OCC2 AS ( + SELECT c.Scaffale, + COUNT(DISTINCT mp.IDCella) AS CelleOccupate, + COUNT(mp.ID) AS NPalet + FROM CELLE c + LEFT JOIN dbo.MagazziniPallet mp ON mp.IDCella = c.IDCella + GROUP BY c.Scaffale + ) + SELECT + ROW_NUMBER() OVER (ORDER BY o.Scaffale) AS ID, + o.Scaffale AS Scaffale, + a.IDMagazzino AS MagID, + m.Codice AS Magazzino, + ISNULL(o2.NPalet,0) AS NPalet, + o.CelleTotali AS CelleTotali, + ISNULL(o2.CelleOccupate,0) AS CelleOccupate + FROM OCC o + LEFT JOIN OCC2 o2 ON o2.Scaffale = o.Scaffale + JOIN dbo.Aree a ON a.ID = :area_id + JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino + WHERE o.Scaffale IS NOT NULL AND LTRIM(RTRIM(o.Scaffale)) <> '' + ORDER BY o.Scaffale; + + +| PARAMS: | +| { | +| "area_id": 1 | +| } | +| | +| COMMENT: | +| START | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-03 20:49:07.239 | +| PURPOSE : Statistiche Scaffali per Area | +| ELAPSED : 79 ms | +| | +| SQL (copy/paste below): | +| | + WITH CELLE AS ( + SELECT c.ID AS IDCella, c.IDArea, CAST([Corsia] AS NVARCHAR(50)) AS Scaffale + FROM dbo.Celle c + WHERE c.IDArea = :area_id + ), + OCC AS ( + SELECT c.Scaffale, COUNT(*) AS CelleTotali + FROM CELLE c + GROUP BY c.Scaffale + ), + OCC2 AS ( + SELECT c.Scaffale, + COUNT(DISTINCT mp.IDCella) AS CelleOccupate, + COUNT(mp.ID) AS NPalet + FROM CELLE c + LEFT JOIN dbo.MagazziniPallet mp ON mp.IDCella = c.IDCella + GROUP BY c.Scaffale + ) + SELECT + ROW_NUMBER() OVER (ORDER BY o.Scaffale) AS ID, + o.Scaffale AS Scaffale, + a.IDMagazzino AS MagID, + m.Codice AS Magazzino, + ISNULL(o2.NPalet,0) AS NPalet, + o.CelleTotali AS CelleTotali, + ISNULL(o2.CelleOccupate,0) AS CelleOccupate + FROM OCC o + LEFT JOIN OCC2 o2 ON o2.Scaffale = o.Scaffale + JOIN dbo.Aree a ON a.ID = :area_id + JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino + WHERE o.Scaffale IS NOT NULL AND LTRIM(RTRIM(o.Scaffale)) <> '' + ORDER BY o.Scaffale; + + +| PARAMS: | +| { | +| "area_id": 1 | +| } | +| | +| COMMENT: | +| END | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-03 20:49:17.431 | +| PURPOSE : Rilevamento colonne Celle | +| ELAPSED : 0 ms | +| | +| SQL (copy/paste below): | +| | +SELECT LOWER(name) FROM sys.columns WHERE object_id = OBJECT_ID('dbo.Celle'); + +| PARAMS: | +| {} | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-03 20:49:19.489 | +| PURPOSE : Lista Aree | +| | +| SQL (copy/paste below): | +| | +SELECT a.ID AS AreaID, a.Descrizione AS AreaNome, a.IDMagazzino AS MagID, m.Codice AS Magazzino +FROM dbo.Aree a +JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino +WHERE len(trim(a.Descrizione)) = 2; + +| PARAMS: | +| {} | +| | +| COMMENT: | +| START | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-03 20:49:19.495 | +| PURPOSE : Lista Aree | +| ELAPSED : 5 ms | +| | +| SQL (copy/paste below): | +| | +SELECT a.ID AS AreaID, a.Descrizione AS AreaNome, a.IDMagazzino AS MagID, m.Codice AS Magazzino +FROM dbo.Aree a +JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino +WHERE len(trim(a.Descrizione)) = 2; + +| PARAMS: | +| {} | +| | +| COMMENT: | +| END | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-03 20:49:19.500 | +| PURPOSE : Statistiche Scaffali per Area | +| | +| SQL (copy/paste below): | +| | +SELECT TOP 0 1 AS ID, '' AS Scaffale, 0 AS MagID, '' AS Magazzino, 0 AS NPalet, 0 AS CelleTotali, 0 AS CelleOccupate + +| PARAMS: | +| { | +| "area_id": 1 | +| } | +| | +| COMMENT: | +| START | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-03 20:49:19.503 | +| PURPOSE : Statistiche Scaffali per Area | +| ELAPSED : 2 ms | +| | +| SQL (copy/paste below): | +| | +SELECT TOP 0 1 AS ID, '' AS Scaffale, 0 AS MagID, '' AS Magazzino, 0 AS NPalet, 0 AS CelleTotali, 0 AS CelleOccupate + +| PARAMS: | +| { | +| "area_id": 1 | +| } | +| | +| COMMENT: | +| END | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-03 20:49:23.854 | +| PURPOSE : Rilevamento colonne Celle | +| ELAPSED : 0 ms | +| | +| SQL (copy/paste below): | +| | +SELECT LOWER(name) FROM sys.columns WHERE object_id = OBJECT_ID('dbo.Celle'); + +| PARAMS: | +| {} | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-03 20:49:23.861 | +| PURPOSE : Lista Aree | +| | +| SQL (copy/paste below): | +| | +SELECT a.ID AS AreaID, a.Descrizione AS AreaNome, a.IDMagazzino AS MagID, m.Codice AS Magazzino +FROM dbo.Aree a +JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino +WHERE len(trim(a.Descrizione)) = 2; + +| PARAMS: | +| {} | +| | +| COMMENT: | +| START | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-03 20:49:23.863 | +| PURPOSE : Lista Aree | +| ELAPSED : 1 ms | +| | +| SQL (copy/paste below): | +| | +SELECT a.ID AS AreaID, a.Descrizione AS AreaNome, a.IDMagazzino AS MagID, m.Codice AS Magazzino +FROM dbo.Aree a +JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino +WHERE len(trim(a.Descrizione)) = 2; + +| PARAMS: | +| {} | +| | +| COMMENT: | +| END | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-03 20:49:23.869 | +| PURPOSE : Statistiche Scaffali per Area | +| | +| SQL (copy/paste below): | +| | + WITH CELLE AS ( + SELECT c.ID AS IDCella, c.IDArea, CAST([Corsia] AS NVARCHAR(50)) AS Scaffale + FROM dbo.Celle c + WHERE c.IDArea = :area_id + ), + OCC AS ( + SELECT c.Scaffale, COUNT(*) AS CelleTotali + FROM CELLE c + GROUP BY c.Scaffale + ), + OCC2 AS ( + SELECT c.Scaffale, + COUNT(DISTINCT mp.IDCella) AS CelleOccupate, + COUNT(mp.ID) AS NPalet + FROM CELLE c + LEFT JOIN dbo.MagazziniPallet mp ON mp.IDCella = c.IDCella + GROUP BY c.Scaffale + ) + SELECT + ROW_NUMBER() OVER (ORDER BY o.Scaffale) AS ID, + o.Scaffale AS Scaffale, + a.IDMagazzino AS MagID, + m.Codice AS Magazzino, + ISNULL(o2.NPalet,0) AS NPalet, + o.CelleTotali AS CelleTotali, + ISNULL(o2.CelleOccupate,0) AS CelleOccupate + FROM OCC o + LEFT JOIN OCC2 o2 ON o2.Scaffale = o.Scaffale + JOIN dbo.Aree a ON a.ID = :area_id + JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino + WHERE o.Scaffale IS NOT NULL AND LTRIM(RTRIM(o.Scaffale)) <> '' + ORDER BY o.Scaffale; + + +| PARAMS: | +| { | +| "area_id": 1 | +| } | +| | +| COMMENT: | +| START | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-03 20:49:23.930 | +| PURPOSE : Statistiche Scaffali per Area | +| ELAPSED : 60 ms | +| | +| SQL (copy/paste below): | +| | + WITH CELLE AS ( + SELECT c.ID AS IDCella, c.IDArea, CAST([Corsia] AS NVARCHAR(50)) AS Scaffale + FROM dbo.Celle c + WHERE c.IDArea = :area_id + ), + OCC AS ( + SELECT c.Scaffale, COUNT(*) AS CelleTotali + FROM CELLE c + GROUP BY c.Scaffale + ), + OCC2 AS ( + SELECT c.Scaffale, + COUNT(DISTINCT mp.IDCella) AS CelleOccupate, + COUNT(mp.ID) AS NPalet + FROM CELLE c + LEFT JOIN dbo.MagazziniPallet mp ON mp.IDCella = c.IDCella + GROUP BY c.Scaffale + ) + SELECT + ROW_NUMBER() OVER (ORDER BY o.Scaffale) AS ID, + o.Scaffale AS Scaffale, + a.IDMagazzino AS MagID, + m.Codice AS Magazzino, + ISNULL(o2.NPalet,0) AS NPalet, + o.CelleTotali AS CelleTotali, + ISNULL(o2.CelleOccupate,0) AS CelleOccupate + FROM OCC o + LEFT JOIN OCC2 o2 ON o2.Scaffale = o.Scaffale + JOIN dbo.Aree a ON a.ID = :area_id + JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino + WHERE o.Scaffale IS NOT NULL AND LTRIM(RTRIM(o.Scaffale)) <> '' + ORDER BY o.Scaffale; + + +| PARAMS: | +| { | +| "area_id": 1 | +| } | +| | +| COMMENT: | +| END | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-04 11:48:50.855 | +| PURPOSE : Rilevamento colonne Celle | +| ELAPSED : 0 ms | +| | +| SQL (copy/paste below): | +| | +SELECT LOWER(name) FROM sys.columns WHERE object_id = OBJECT_ID('dbo.Celle'); + +| PARAMS: | +| {} | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-04 11:48:50.925 | +| PURPOSE : Lista Aree | +| | +| SQL (copy/paste below): | +| | +SELECT a.ID AS AreaID, a.Descrizione AS AreaNome, a.IDMagazzino AS MagID, m.Codice AS Magazzino +FROM dbo.Aree a +JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino +WHERE len(trim(a.Descrizione)) = 2; + +| PARAMS: | +| {} | +| | +| COMMENT: | +| START | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-04 11:48:50.935 | +| PURPOSE : Lista Aree | +| ELAPSED : 9 ms | +| | +| SQL (copy/paste below): | +| | +SELECT a.ID AS AreaID, a.Descrizione AS AreaNome, a.IDMagazzino AS MagID, m.Codice AS Magazzino +FROM dbo.Aree a +JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino +WHERE len(trim(a.Descrizione)) = 2; + +| PARAMS: | +| {} | +| | +| COMMENT: | +| END | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-04 11:48:50.952 | +| PURPOSE : Statistiche Scaffali per Area | +| | +| SQL (copy/paste below): | +| | + WITH CELLE AS ( + SELECT c.ID AS IDCella, c.IDArea, CAST([Corsia] AS NVARCHAR(50)) AS Scaffale + FROM dbo.Celle c + WHERE c.IDArea = :area_id + ), + OCC AS ( + SELECT c.Scaffale, COUNT(*) AS CelleTotali + FROM CELLE c + GROUP BY c.Scaffale + ), + OCC2 AS ( + SELECT c.Scaffale, + COUNT(DISTINCT mp.IDCella) AS CelleOccupate, + COUNT(mp.ID) AS NPalet + FROM CELLE c + LEFT JOIN dbo.MagazziniPallet mp ON mp.IDCella = c.IDCella + GROUP BY c.Scaffale + ) + SELECT + ROW_NUMBER() OVER (ORDER BY o.Scaffale) AS ID, + o.Scaffale AS Scaffale, + a.IDMagazzino AS MagID, + m.Codice AS Magazzino, + ISNULL(o2.NPalet,0) AS NPalet, + o.CelleTotali AS CelleTotali, + ISNULL(o2.CelleOccupate,0) AS CelleOccupate + FROM OCC o + LEFT JOIN OCC2 o2 ON o2.Scaffale = o.Scaffale + JOIN dbo.Aree a ON a.ID = :area_id + JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino + WHERE o.Scaffale IS NOT NULL AND LTRIM(RTRIM(o.Scaffale)) <> '' + ORDER BY o.Scaffale; + + +| PARAMS: | +| { | +| "area_id": 1 | +| } | +| | +| COMMENT: | +| START | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-04 11:48:51.050 | +| PURPOSE : Statistiche Scaffali per Area | +| ELAPSED : 98 ms | +| | +| SQL (copy/paste below): | +| | + WITH CELLE AS ( + SELECT c.ID AS IDCella, c.IDArea, CAST([Corsia] AS NVARCHAR(50)) AS Scaffale + FROM dbo.Celle c + WHERE c.IDArea = :area_id + ), + OCC AS ( + SELECT c.Scaffale, COUNT(*) AS CelleTotali + FROM CELLE c + GROUP BY c.Scaffale + ), + OCC2 AS ( + SELECT c.Scaffale, + COUNT(DISTINCT mp.IDCella) AS CelleOccupate, + COUNT(mp.ID) AS NPalet + FROM CELLE c + LEFT JOIN dbo.MagazziniPallet mp ON mp.IDCella = c.IDCella + GROUP BY c.Scaffale + ) + SELECT + ROW_NUMBER() OVER (ORDER BY o.Scaffale) AS ID, + o.Scaffale AS Scaffale, + a.IDMagazzino AS MagID, + m.Codice AS Magazzino, + ISNULL(o2.NPalet,0) AS NPalet, + o.CelleTotali AS CelleTotali, + ISNULL(o2.CelleOccupate,0) AS CelleOccupate + FROM OCC o + LEFT JOIN OCC2 o2 ON o2.Scaffale = o.Scaffale + JOIN dbo.Aree a ON a.ID = :area_id + JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino + WHERE o.Scaffale IS NOT NULL AND LTRIM(RTRIM(o.Scaffale)) <> '' + ORDER BY o.Scaffale; + + +| PARAMS: | +| { | +| "area_id": 1 | +| } | +| | +| COMMENT: | +| END | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-04 11:49:34.467 | +| PURPOSE : Rilevamento colonne Celle | +| ELAPSED : 0 ms | +| | +| SQL (copy/paste below): | +| | +SELECT LOWER(name) FROM sys.columns WHERE object_id = OBJECT_ID('dbo.Celle'); + +| PARAMS: | +| {} | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-04 11:49:37.275 | +| PURPOSE : Lista Aree | +| | +| SQL (copy/paste below): | +| | +SELECT a.ID AS AreaID, a.Descrizione AS AreaNome, a.IDMagazzino AS MagID, m.Codice AS Magazzino +FROM dbo.Aree a +JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino +WHERE len(trim(a.Descrizione)) = 2; + +| PARAMS: | +| {} | +| | +| COMMENT: | +| START | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-04 11:49:37.284 | +| PURPOSE : Lista Aree | +| ELAPSED : 8 ms | +| | +| SQL (copy/paste below): | +| | +SELECT a.ID AS AreaID, a.Descrizione AS AreaNome, a.IDMagazzino AS MagID, m.Codice AS Magazzino +FROM dbo.Aree a +JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino +WHERE len(trim(a.Descrizione)) = 2; + +| PARAMS: | +| {} | +| | +| COMMENT: | +| END | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-04 11:49:45.245 | +| PURPOSE : Rilevamento colonne Celle | +| ELAPSED : 0 ms | +| | +| SQL (copy/paste below): | +| | +SELECT LOWER(name) FROM sys.columns WHERE object_id = OBJECT_ID('dbo.Celle'); + +| PARAMS: | +| {} | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-04 11:49:45.324 | +| PURPOSE : Lista Aree | +| | +| SQL (copy/paste below): | +| | +SELECT a.ID AS AreaID, a.Descrizione AS AreaNome, a.IDMagazzino AS MagID, m.Codice AS Magazzino +FROM dbo.Aree a +JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino +WHERE len(trim(a.Descrizione)) = 2; + +| PARAMS: | +| {} | +| | +| COMMENT: | +| START | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-04 11:49:45.327 | +| PURPOSE : Lista Aree | +| ELAPSED : 2 ms | +| | +| SQL (copy/paste below): | +| | +SELECT a.ID AS AreaID, a.Descrizione AS AreaNome, a.IDMagazzino AS MagID, m.Codice AS Magazzino +FROM dbo.Aree a +JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino +WHERE len(trim(a.Descrizione)) = 2; + +| PARAMS: | +| {} | +| | +| COMMENT: | +| END | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-04 11:49:45.335 | +| PURPOSE : Statistiche Scaffali per Area | +| | +| SQL (copy/paste below): | +| | + WITH CELLE AS ( + SELECT c.ID AS IDCella, c.IDArea, CAST([Corsia] AS NVARCHAR(50)) AS Scaffale + FROM dbo.Celle c + WHERE c.IDArea = :area_id + ), + OCC AS ( + SELECT c.Scaffale, COUNT(*) AS CelleTotali + FROM CELLE c + GROUP BY c.Scaffale + ), + OCC2 AS ( + SELECT c.Scaffale, + COUNT(DISTINCT mp.IDCella) AS CelleOccupate, + COUNT(mp.ID) AS NPalet + FROM CELLE c + LEFT JOIN dbo.MagazziniPallet mp ON mp.IDCella = c.IDCella + GROUP BY c.Scaffale + ) + SELECT + ROW_NUMBER() OVER (ORDER BY o.Scaffale) AS ID, + o.Scaffale AS Scaffale, + a.IDMagazzino AS MagID, + m.Codice AS Magazzino, + ISNULL(o2.NPalet,0) AS NPalet, + o.CelleTotali AS CelleTotali, + ISNULL(o2.CelleOccupate,0) AS CelleOccupate + FROM OCC o + LEFT JOIN OCC2 o2 ON o2.Scaffale = o.Scaffale + JOIN dbo.Aree a ON a.ID = :area_id + JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino + WHERE o.Scaffale IS NOT NULL AND LTRIM(RTRIM(o.Scaffale)) <> '' + ORDER BY o.Scaffale; + + +| PARAMS: | +| { | +| "area_id": 1 | +| } | +| | +| COMMENT: | +| START | +| | ++------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------+ +| TIMESTAMP : 2025-10-04 11:49:45.412 | +| PURPOSE : Statistiche Scaffali per Area | +| ELAPSED : 76 ms | +| | +| SQL (copy/paste below): | +| | + WITH CELLE AS ( + SELECT c.ID AS IDCella, c.IDArea, CAST([Corsia] AS NVARCHAR(50)) AS Scaffale + FROM dbo.Celle c + WHERE c.IDArea = :area_id + ), + OCC AS ( + SELECT c.Scaffale, COUNT(*) AS CelleTotali + FROM CELLE c + GROUP BY c.Scaffale + ), + OCC2 AS ( + SELECT c.Scaffale, + COUNT(DISTINCT mp.IDCella) AS CelleOccupate, + COUNT(mp.ID) AS NPalet + FROM CELLE c + LEFT JOIN dbo.MagazziniPallet mp ON mp.IDCella = c.IDCella + GROUP BY c.Scaffale + ) + SELECT + ROW_NUMBER() OVER (ORDER BY o.Scaffale) AS ID, + o.Scaffale AS Scaffale, + a.IDMagazzino AS MagID, + m.Codice AS Magazzino, + ISNULL(o2.NPalet,0) AS NPalet, + o.CelleTotali AS CelleTotali, + ISNULL(o2.CelleOccupate,0) AS CelleOccupate + FROM OCC o + LEFT JOIN OCC2 o2 ON o2.Scaffale = o.Scaffale + JOIN dbo.Aree a ON a.ID = :area_id + JOIN dbo.Magazzini m ON m.ID = a.IDMagazzino + WHERE o.Scaffale IS NOT NULL AND LTRIM(RTRIM(o.Scaffale)) <> '' + ORDER BY o.Scaffale; + + +| PARAMS: | +| { | +| "area_id": 1 | +| } | +| | +| COMMENT: | +| END | +| | ++------------------------------------------------------------------------------------------------------------+