# Modulo `gestione_pickinglist.py` ## Scopo Il modulo implementa la finestra operativa "Gestione Picking List". La finestra serve al backoffice/magazzino per: - visualizzare le picking list ancora lavorabili; - selezionare una sola lista alla volta; - visualizzare le UDC residue della lista selezionata; - prenotare una lista per darle priorita' alta sul barcode; - s-prenotare la lista correntemente prenotata; - mantenere UI reattiva tramite query asincrone, overlay e spinner. La finestra lavora sulla vista residuale `dbo.py_ViewPackingListRestante`, quindi mostra cio' che resta da scaricare, non necessariamente il documento ERP completo. Il documento completo e' invece esposto dalla form "Storico Picking List". ## Processo logico ### Apertura finestra 1. `open_pickinglist_window` crea o riporta in primo piano una singola finestra. 2. La finestra viene inizialmente nascosta con `withdraw` e alpha `0.0`. 3. Viene creato `GestionePickingListFrame`. 4. Dopo la stabilizzazione layout, `_first_show` avvia il primo caricamento. 5. La finestra viene mostrata solo quando il layout e' pronto, riducendo flicker. ### Caricamento lista alta 1. `reload_from_db` esegue `SQL_PL`. 2. `SQL_PL` legge da `dbo.py_ViewPackingListRestante`. 3. Il risultato viene normalizzato con `_rows_to_dicts`. 4. `_refresh_mid_rows` ricrea la tabella alta. 5. Ogni riga e' rappresentata da un `PLRow`, che incapsula dizionario dati e checkbox. ### Selezione lista 1. Il click sul checkbox chiama `on_row_checked`. 2. La selezione e' esclusiva: eventuali altre righe selezionate vengono deselezionate. 3. Il documento selezionato viene salvato in `detail_doc`. 4. Viene eseguita `SQL_PL_DETAILS`. 5. Il dettaglio viene messo in cache in `_detail_cache`. 6. `_refresh_details_incremental` ordina e renderizza il dettaglio con `tksheet`. ### Ricarica `reload_from_db` preserva il documento selezionato quando possibile. Questo e' importante per il caso operativo in cui il barcode sta scaricando UDC mentre l'operatore aggiorna la finestra. Flusso: 1. `_selected_documento_for_reload` determina il documento da mantenere. 2. La griglia alta viene ricaricata. 3. `_reselect_documento_after_reload` riseleziona la stessa lista se ancora presente. 4. Il dettaglio viene ricaricato dalla vista residuale. ### Prenotazione 1. `on_prenota` verifica il permesso `pickinglist.prenota`. 2. Richiede una riga selezionata. 3. Se `IDStato == 1`, l'operazione e' no-op. 4. Verifica sessione operatore valida. 5. Chiama `sp_xExePackingListPallet_async(..., Azione="P")`. 6. Se `rc == 0`, logga audit e ricarica la griglia mantenendo il documento. ### S-prenotazione 1. `on_sprenota` verifica il permesso `pickinglist.sprenota`. 2. Richiede una riga selezionata. 3. Se `IDStato == 0`, l'operazione e' no-op. 4. Chiama `sp_xExePackingListPallet_async(..., Azione="S")`. 5. Se `rc == 0`, logga audit e ricarica. ## Semantica operativa La UI non implementa toggle implicito. I pulsanti hanno semantica esplicita: | Pulsante | Effetto | | --- | --- | | `Prenota` | porta la lista selezionata a priorita' alta se non gia' prenotata | | `S-prenota` | libera la lista selezionata se e' la lista attiva | Premere piu' volte `Prenota` sulla stessa lista gia' prenotata non la libera. Premere piu' volte `S-prenota` su lista non prenotata non la prenota. ## Strutture dati principali | Nome | Tipo | Ruolo | | --- | --- | --- | | `rows_models` | `list[PLRow]` | modelli riga della griglia alta | | `_detail_cache` | `dict[Any, list]` | cache righe dettaglio per documento | | `detail_doc` | `Any` | documento correntemente selezionato | | `_detail_sort_key` | `str | None` | colonna dettaglio ordinata | | `_detail_sort_reverse` | `bool` | verso ordinamento dettaglio | | `pl_table` | `ScrollTable` | tabella custom griglia alta | | `detail_sheet` | `tksheet.Sheet` | griglia dettaglio ad alto volume | | `spinner` | `ToolbarSpinner` | feedback leggero toolbar | | `busy` | `InlineBusyOverlay` | overlay query/operazioni lente | ## Funzioni e classi principali | Oggetto | Firma | Ruolo | | --- | --- | --- | | `_rows_to_dicts` | `_rows_to_dicts(res: Dict[str, Any]) -> List[Dict[str, Any]]` | normalizza payload DB | | `_s` | `_s(v) -> str` | converte `None` in stringa vuota | | `_first` | `_first(d: Dict[str, Any], keys: List[str], default: str = "")` | estrae il primo campo valorizzato | | `ColSpec` | `dataclass(title, key, width, anchor)` | descrive una colonna tabellare | | `ToolbarSpinner` | classe | animazione leggera toolbar | | `ScrollTable` | classe | tabella custom per griglia alta | | `PLRow` | classe | modello riga PL + checkbox | | `GestionePickingListFrame` | classe | frame principale | | `_build_detail_sheet` | `_build_detail_sheet(self)` | crea tksheet dettaglio | | `_detail_rows_to_sheet_data` | `_detail_rows_to_sheet_data(self, rows)` | converte righe DB in righe tksheet | | `_load_detail_sheet_data` | `_load_detail_sheet_data(self, data)` | applica headers, dati e zebra | | `_refresh_mid_rows` | `_refresh_mid_rows(self, rows)` | ricrea griglia alta | | `_get_selected_model` | `_get_selected_model(self) -> Optional[PLRow]` | ritorna riga selezionata | | `on_row_checked` | `on_row_checked(self, model: PLRow, is_checked: bool)` | carica dettaglio della lista | | `reload_from_db` | `reload_from_db(self, first=False, reselect_documento=None)` | aggiorna griglia alta | | `_refresh_details` | `_refresh_details(self)` | ridisegna dettaglio da cache | | `_refresh_details_incremental` | `_refresh_details_incremental(self, batch_size=25)` | render dettaglio con overlay | | `on_prenota` | `on_prenota(self)` | prenota lista selezionata | | `on_sprenota` | `on_sprenota(self)` | s-prenota lista selezionata | | `create_frame` | `create_frame(parent, *, db_client=None, conn_str=None, session=None)` | factory frame | | `open_pickinglist_window` | `open_pickinglist_window(parent, db_client, session=None) -> tk.Misc` | entry point finestra | ## Query runtime ### `SQL_PL` Aggrega `dbo.py_ViewPackingListRestante` per documento. Conta UDC con: ```sql COUNT(DISTINCT NULLIF(LTRIM(RTRIM(CAST(Pallet AS varchar(32)))), '')) AS Pallet ``` Questo evita il doppio conteggio di UDC multi-lotto. ### `SQL_PL_DETAILS` Ritorna tutte le righe residuali della lista selezionata, ordinate per `Ordinamento`. ## Effetti sul database Il modulo UI non aggiorna direttamente tabelle. Tutte le modifiche passano da `sp_xExePackingListPallet_async`, che esegue `dbo.py_sp_xExePackingListPallet`. Effetti indiretti: - aggiorna `dbo.PyPickingListReservation`; - aggiorna `dbo.Celle.IDStato`; - scrive audit applicativo con `log_user_action`; - la stored puo' scrivere log legacy tramite `sp_LogPackingList`. ## Rischi e note review - La vista residuale cambia mentre il barcode scarica UDC: per questo `Ricarica` conserva la selezione e ricarica il dettaglio. - La tabella alta e il dettaglio possono non avere lo stesso numero di righe dello storico, perche' qui si vedono solo residui. - La convivenza C#/Python dipende dalla presenza degli oggetti `py_*`. - La selezione e' esclusiva lato UI, ma il secondo livello F2 barcode dipende dalla logica barcode/stato DB, non da una seconda prenotazione backoffice.