# Analisi bug prenotazione Picking List ## Obiettivo Documentare in modo chiaro: - il comportamento attuale della prenotazione picking list - il bug osservato - la causa tecnica del bug - la correzione proposta lato stored procedure Il documento serve come base di riferimento prima di modificare la logica SQL legacy. --- ## Contesto operativo L'operatore, dal backoffice: 1. seleziona una picking list 2. preme `Prenota` 3. si aspetta che **una sola** picking list risulti prenotata Il comportamento atteso del sistema è: - una sola picking list prenotata alla volta - quella picking list diventa la coda `F1` sul barcode - le altre picking list restano non prenotate e alimentano la coda `F2` --- ## Comportamento attuale Il comportamento effettivamente osservato è questo: 1. l'operatore seleziona una sola picking list 2. preme `Prenota` 3. risultano prenotate **due** picking list, non una sola Questo comportamento è stato osservato: - nell'applicazione Python - anche nell'applicazione C# Quindi il bug **non nasce nella UI Python**. --- ## Verifica di aderenza Python / C# ### Lato Python La finestra [gestione_pickinglist.py](C:/devel/python/ware_house/gestione_pickinglist.py) chiama: - [sp_xExePackingListPallet_async](C:/devel/python/ware_house/prenota_sprenota_sql.py) che esegue direttamente la stored procedure SQL legacy: - `dbo.sp_xExePackingListPallet` ### Lato C# La finestra legacy [FRMagViewLayout.cs](C:/devel/seawall_decompiled/decompiled/SWMagViewLayout/FRMagViewLayout.cs) chiama anch'essa: - `dbo.sp_xExePackingListPallet` ### Conclusione Python e C# usano la **stessa** logica DB per la prenotazione. Se il bug compare in entrambi, il problema è nella stored procedure o nel modello dati su cui essa si basa. --- ## Stato attuale della griglia picking list La query summary della picking list in [gestione_pickinglist.py](C:/devel/python/ware_house/gestione_pickinglist.py) aggrega così: - `GROUP BY Documento, CodNazione, NAZIONE, Stato` - `MAX(IDStato) AS IDStato` Questo significa: - se anche **una sola riga** del documento ha `IDStato = 1` - l'intera picking list appare come prenotata Questa scelta è coerente con l'idea UI di “documento prenotato”, ma diventa problematica se la stored non lavora in modo esclusivo per documento. --- ## Stored procedure attuale Stored coinvolta: - [sp_xExePackingListPallet](C:/devel/python/ware_house/script.sql) ### Logica attuale La stored: 1. legge da `XMag_ViewPackingList` 2. estrae le `Cella` associate al `Documento` 3. per ogni cella: - se `IDStato = 0`, la mette a `1` - altrimenti la rimette a `0` Quindi la stored non prenota davvero una picking list come entità esclusiva. Prenota invece: - **le celle** toccate dal documento selezionato ### Conseguenza tecnica Se due documenti condividono almeno una stessa cella: - la prenotazione della prima list mette `IDStato = 1` sulla cella condivisa - anche la seconda list, leggendo quella stessa cella, eredita `IDStato = 1` - la griglia alta, usando `MAX(IDStato)`, la mostra come prenotata --- ## Evidenze raccolte ### Query 1 - celle del sottoinsieme di documenti osservati Scopo: - verificare quali celle sono coinvolte dai documenti sospetti - controllare rapidamente se esistono sovrapposizioni evidenti Query: ```sql SELECT Documento, Cella, COUNT(*) AS Righe FROM dbo.XMag_ViewPackingList WHERE Documento IN (, ) GROUP BY Documento, Cella ORDER BY Cella, Documento; ``` Uso nel caso analizzato: - ha permesso di vedere, per esempio, che documenti diversi insistono sulla stessa cella `8057` ### Query di controllo celle condivise Risultato: ```text 1000 2 8057 2 ``` Interpretazione: - la cella `1000` è condivisa da 2 documenti - la cella `8057` è condivisa da 2 documenti ### Query di dettaglio documenti / celle condivise Risultato: ```text 135 1000 16 16 137 1000 2 1 133 8057 1 1 135 8057 1 1 ``` Interpretazione: - documento `135` e documento `137` condividono la cella `1000` - documento `133` e documento `135` condividono la cella `8057` Questa è una prova concreta del fatto che la prenotazione a livello cella può propagarsi a più documenti. ### Query 2 - ricerca globale delle celle condivise Scopo: - trovare quali celle sono condivise da più documenti - identificare i punti strutturalmente più critici del dataset Query: ```sql SELECT Cella, COUNT(DISTINCT Documento) AS NumDocumenti FROM dbo.XMag_ViewPackingList GROUP BY Cella HAVING COUNT(DISTINCT Documento) > 1 ORDER BY NumDocumenti DESC, Cella; ``` Risultato osservato: ```text 1000 2 8057 2 ``` Interpretazione: - la cella `1000` è condivisa da 2 documenti - la cella `8057` è condivisa da 2 documenti ### Query 3 - dettaglio completo delle collisioni sulle celle condivise Scopo: - capire esattamente quali documenti condividono le celle critiche - vedere quante righe e quanti pallet distinti insistono su quelle celle Query: ```sql SELECT Documento, Cella, COUNT(*) AS Righe, COUNT(DISTINCT Pallet) AS Pallet FROM dbo.XMag_ViewPackingList WHERE Cella IN (1000, 8057) GROUP BY Documento, Cella ORDER BY Cella, Documento; ``` Risultato osservato: ```text 135 1000 16 16 137 1000 2 1 133 8057 1 1 135 8057 1 1 ``` Interpretazione: - documento `135` e documento `137` condividono la cella `1000` - documento `133` e documento `135` condividono la cella `8057` Queste tre query, lette insieme, sono quelle che hanno permesso di evidenziare in modo oggettivo il bug. --- ## Ruolo delle celle convenzionali Dalle verifiche fatte con il magazziniere: - `1000` = `5E1.1` - `9999` = `7G.1.1` La cella `1000` è una locazione convenzionale delle UDC non scaffalate. Questo rende il problema ancora più frequente, perché: - più documenti possono convivere sulla stessa locazione convenzionale `1000` - quindi la prenotazione per cella tende naturalmente a contaminare più picking list --- ## Diagnosi finale del bug Il bug è dovuto alla combinazione di due scelte: 1. la stored `sp_xExePackingListPallet` lavora a livello **cella** 2. la UI mostra la prenotazione a livello **documento** Quando esistono celle condivise tra documenti: - la prenotazione di un documento produce `IDStato = 1` anche su righe lette da altri documenti - quindi più picking list risultano prenotate ### Forma sintetica del bug > Il sistema non sta prenotando una singola picking list in modo esclusivo. > Sta prenotando le celle associate a quel documento, e la UI deduce da lì lo stato della picking list. --- # Correzione proposta ## Obiettivo della correzione Allineare il comportamento del sistema alla regola di business desiderata: > una sola picking list prenotata per volta Questo implica che il comando `Prenota` deve produrre uno stato esclusivo a livello documento. --- ## Principio della nuova logica La stored non deve più comportarsi come un semplice toggle delle celle del documento selezionato. Deve invece: 1. rimuovere la prenotazione da tutte le altre picking list 2. applicare la prenotazione solo al documento selezionato In altre parole: - **prima** si resetta lo stato prenotato degli altri documenti - **poi** si prenota il documento scelto --- ## Comportamento desiderato dopo la correzione ### Caso `Prenota` Quando l'operatore prenota il documento `D`: 1. se il documento `D` è già prenotato, non succede nulla 2. se il documento `D` non è prenotato: - tutte le celle prenotate appartenenti ad altri documenti vengono riportate a `IDStato = 0` - tutte le celle del documento `D` vengono portate a `IDStato = 1` - il log della picking list resta aggiornato come oggi Effetto atteso: - solo il documento `D` risulta prenotato nella griglia - solo il documento `D` alimenta la coda `F1` - tutte le altre picking list alimentano `F2` - premere `Prenota` più volte sulla stessa picking list già prenotata non deve produrre toggle né effetti collaterali ### Caso `S-prenota` Quando l'operatore s-prenota il documento `D`: 1. se il documento `D` non è prenotato, non succede nulla 2. se il documento `D` è prenotato: - le celle del documento `D` vengono riportate a `IDStato = 0` - nessun altro documento viene automaticamente prenotato Effetto atteso: - nessuna picking list resta prenotata, salvo una nuova prenotazione esplicita - premere `S-prenota` più volte sulla stessa picking list già non prenotata non deve produrre toggle né effetti collaterali --- ## Strategia tecnica consigliata ### Approccio minimo e prudente La correzione più sicura, restando vicini al legacy, è mantenere una stored unica ma con una semantica esplicita guidata da un parametro azione. Proposta: - stored unica con parametro `@Azione` - `P` = Prenota - `S` = S-prenota Logica: 1. determinare se il documento selezionato è già prenotato o no 2. se `@Azione = 'P'` - se il documento è già prenotato: non fare nulla - altrimenti: - azzerare `IDStato = 1` sulle celle coinvolte da altri documenti prenotati - impostare `IDStato = 1` sulle celle del documento selezionato 3. se `@Azione = 'S'` - se il documento non è prenotato: non fare nulla - altrimenti: - riportare a `0` solo le celle del documento selezionato Questa logica mantiene: - la semantica esistente a livello cella - ma aggiunge l'esclusività a livello documento - e mantiene pulita la semantica distinta dei pulsanti `Prenota` e `S-prenota` ### Vantaggi - modifica contenuta - comportamento coerente con l'operatività reale - nessuna necessità immediata di cambiare UI Python o C# - comportamento idempotente dei pulsanti --- ## Rischi da considerare ### 1. Celle condivise tra documenti La presenza di celle condivise resta un'anomalia logica del dominio. Anche con la correzione proposta: - se una cella appartiene contemporaneamente a più documenti nella vista - la semantica “quale documento possiede davvero la prenotazione della cella” resta concettualmente debole Tuttavia la correzione proposta risolve il bug visibile di doppia picking list prenotata. ### 2. Effetti sul flusso barcode Il barcode usa `IDStato = 1` per la coda `F1`. Quindi la nuova esclusività deve essere verificata attentamente su: - proposta UDC in `F1` - proposta UDC in `F2` - avanzamento dopo prelievo - ri-prenotazione automatica residua via `sp_ControllaPrenotazionePackingListPalletNew` ### 3. Interazione con `LogPackingList` La stored attuale aggiorna il log del documento. Questa parte va mantenuta, perché il barcode e la logica di ri-prenotazione residua si appoggiano su quel log. --- ## Criteri di accettazione della correzione La correzione sarà considerata valida se, dopo `Prenota`: 1. una sola picking list risulta prenotata in griglia 2. `F1` propone solo UDC del documento prenotato 3. `F2` propone UDC delle altre picking list 4. il problema di doppia prenotazione non si ripresenta più nemmeno in presenza di celle condivise come `1000` o `8057` E dopo `S-prenota`: 1. il documento selezionato torna non prenotato 2. `F1` non propone più quella picking list --- ## Decisione consigliata La correzione lato stored procedure è consigliata e necessaria. Motivo: - il bug è reale - il bug è condiviso da Python e C# - il bug nasce dalla logica DB attuale - la prenotazione esclusiva è coerente con il modello operativo del magazzino --- ## Stored procedure proposta Di seguito una proposta documentale di stored unica con parametro `P` / `S`. ```sql CREATE OR ALTER PROCEDURE [dbo].[sp_xExePackingListPallet] @IDOperatore int, @Documento varchar(8), @Azione char(1), -- 'P' = Prenota, 'S' = S-prenota @RC int OUTPUT AS BEGIN SET NOCOUNT ON; SET @RC = 0; DECLARE @Nominativo varchar(50); DECLARE @DocumentoPrenotato bit = 0; DECLARE @Description varchar(255) = ''; DECLARE @Message varchar(255) = ''; DECLARE @IDResult int = 0; SELECT @Nominativo = [Login] FROM dbo.Operatori WHERE ID = @IDOperatore; IF @Azione NOT IN ('P', 'S') BEGIN SET @RC = -10; RETURN; END; IF OBJECT_ID('tempdb..#TargetCelle') IS NOT NULL DROP TABLE #TargetCelle; CREATE TABLE #TargetCelle ( IDCella int PRIMARY KEY ); INSERT INTO #TargetCelle (IDCella) SELECT DISTINCT Cella FROM dbo.XMag_ViewPackingList WHERE Documento = @Documento AND Cella IS NOT NULL; IF NOT EXISTS (SELECT 1 FROM #TargetCelle) BEGIN SET @RC = -20; RETURN; END; IF EXISTS ( SELECT 1 FROM dbo.XMag_ViewPackingList WHERE Documento = @Documento AND ISNULL(IDStato, 0) = 1 ) BEGIN SET @DocumentoPrenotato = 1; END; IF @Azione = 'P' BEGIN -- Gia' prenotata: no-op IF @DocumentoPrenotato = 1 RETURN; -- Azzera prenotazioni di altri documenti UPDATE c SET c.IDStato = 0, c.ModUtente = @Nominativo, c.ModDataOra = GETDATE() FROM dbo.Celle c WHERE c.ID IN ( SELECT DISTINCT Cella FROM dbo.XMag_ViewPackingList WHERE ISNULL(IDStato, 0) = 1 AND Documento <> @Documento AND Cella IS NOT NULL ); -- Prenota solo il documento target UPDATE c SET c.IDStato = 1, c.ModUtente = @Nominativo, c.ModDataOra = GETDATE() FROM dbo.Celle c INNER JOIN #TargetCelle t ON t.IDCella = c.ID; SELECT TOP 1 @Description = NAZIONE FROM dbo.XMag_ViewPackingList WHERE Documento = @Documento; EXEC dbo.sp_LogPackingList @ID = 0, @Code = @Documento, @Description = @Description, @Message = @Message OUTPUT, @IDResult = @IDResult OUTPUT; RETURN; END; IF @Azione = 'S' BEGIN -- Gia' non prenotata: no-op IF @DocumentoPrenotato = 0 RETURN; -- S-prenota solo il documento target UPDATE c SET c.IDStato = 0, c.ModUtente = @Nominativo, c.ModDataOra = GETDATE() FROM dbo.Celle c INNER JOIN #TargetCelle t ON t.IDCella = c.ID; RETURN; END; END; ``` ### Note sulla stored proposta - Non esiste più il toggle implicito. - `Prenota` è idempotente: - se la list è già prenotata, non cambia nulla. - `S-prenota` è idempotente: - se la list è già non prenotata, non cambia nulla. - L'esclusività viene garantita solo nel ramo `P`. - La logica continua a usare `IDStato` sulle celle, così il barcode legacy può continuare a usare `F1` / `F2` con la semantica attuale. --- ## Prossimo passo Dopo approvazione di questo documento: 1. modificare `sp_xExePackingListPallet` o introdurre una nuova versione compatibile 2. testare il risultato con almeno 3 picking list attive 3. verificare il comportamento sul backoffice 4. verificare il comportamento sul barcode (`F1` / `F2`) --- ## Revisione dopo test reale su documenti 133 / 135 / 137 Dopo avere applicato la prima correzione proposta solo sulla stored, il bug **resta presente**: - prenotando il documento `133` - la picking list `135` continua a risultare prenotata Questo comportamento è coerente con le evidenze raccolte: - `133` e `135` condividono la cella `8057` - `135` e `137` condividono la cella `1000` ### Conclusione aggiornata La correzione che agisce **solo** su `Celle.IDStato` non è sufficiente. Il motivo è strutturale: 1. la stored può anche rendere idempotente `Prenota` / `S-prenota` 2. ma se la UI e il barcode continuano a dedurre lo stato della prenotazione da `Celle.IDStato` 3. una cella condivisa continuerà a far risultare prenotati anche documenti diversi Quindi il modello corretto deve spostare la **sorgente di verità** della prenotazione: - **da:** stato della cella - **a:** documento prenotato attivo ### Nuova architettura proposta La soluzione corretta è questa: 1. introdurre una piccola tabella di stato, ad esempio `dbo.PickingListReservation` 2. salvare lì il **documento attualmente prenotato** 3. fare in modo che `XMag_ViewPackingList.IDStato` non legga più la prenotazione da `Celle.IDStato` 4. calcolare invece `IDStato` così: - `1` se `Documento = documento prenotato attivo` - `0` negli altri casi In questo modo: - il backoffice mostra prenotata una sola picking list - il barcode continua a usare `F1` = `IDStato 1` e `F2` = `IDStato 0` - le celle condivise `1000` / `8057` non contaminano più altri documenti ### Ruolo residuo di `Celle.IDStato` `Celle.IDStato` può restare utile come supporto: - per visualizzazioni legacy - per evidenziare le celle del documento attivo - per non rompere altre parti del sistema che si aspettano quel flag Ma non deve più essere la **fonte primaria** della prenotazione documento. ### Stored definitiva da realizzare La stored `sp_xExePackingListPallet` deve quindi: 1. mantenere il parametro `@Azione = 'P' | 'S'` 2. aggiornare la tabella `PickingListReservation` 3. opzionalmente riallineare anche `Celle.IDStato` 4. mantenere il log in `LogPackingList` Semantica definitiva: - `Prenota` - se il documento è già quello attivo: nessun effetto - altrimenti: - il documento diventa l’unico prenotato attivo - tutte le altre picking list risultano non prenotate - `S-prenota` - se il documento non è quello attivo: nessun effetto - se il documento è quello attivo: - la prenotazione viene rimossa - nessun altro documento viene acceso automaticamente ### Nota importante Questa revisione **supera** la proposta precedente basata soltanto sul reset/set di `Celle.IDStato`. La patch SQL definitiva deve quindi includere: 1. creazione della tabella `PickingListReservation` 2. `CREATE OR ALTER` della stored `sp_xExePackingListPallet` 3. `CREATE OR ALTER` della vista `XMag_ViewPackingList` Questo è il primo assetto realmente coerente con il requisito di business: > una sola picking list prenotata alla volta, anche in presenza di celle condivise. --- ## Evoluzione proposta: usare `ViewPackingListRestante` anche per la lista alta ### Situazione attuale Nel legacy C# e nel Python attuale la distinzione è questa: - la **griglia alta** legge la testata aggregata da `XMag_ViewPackingList` - la **griglia bassa** legge il dettaglio da `ViewPackingListRestante` La vista `ViewPackingListRestante` è definita come: ```sql SELECT ... FROM XMag_ViewPackingList WHERE Cella <> 9999 ``` Quindi: - `XMag_ViewPackingList` contiene anche le UDC già finite nella locazione convenzionale `9999 = 7G.1.1` - `ViewPackingListRestante` mostra invece solo le UDC ancora residue ### Conseguenza pratica All’istante `t = 0` della prenotazione: - se la picking list non è ancora stata lavorata, `XMag_ViewPackingList` e `ViewPackingListRestante` mostrano di fatto lo stesso insieme di UDC - le UDC non scaffalate **restano presenti** in entrambe, perché stanno nella cella convenzionale `1000 = 5E1.1` Dopo che il magazziniere ha iniziato i prelievi: - le UDC già spedite/prelevate finiscono in `9999 = 7G.1.1` - quindi spariscono da `ViewPackingListRestante` - ma restano ancora visibili in `XMag_ViewPackingList` ### Limite del comportamento attuale Con la summary alta basata su `XMag_ViewPackingList`: - il comando `Ricarica` può continuare a mostrare picking list già lavorate in parte - una picking list completamente esaurita può restare visibile nella lista alta - il contenuto della lista alta non coincide più con il “residuo operativo reale” In altre parole: - la griglia bassa mostra il residuo - la griglia alta mostra ancora lo storico completo ### Modifica proposta La proposta è: - usare `ViewPackingListRestante` anche come sorgente della **lista alta aggregata** quindi sostituire, nella query summary di [gestione_pickinglist.py](C:/devel/python/ware_house/gestione_pickinglist.py): - `FROM dbo.XMag_ViewPackingList` con: - `FROM dbo.ViewPackingListRestante` mantenendo invariati: - `GROUP BY Documento, CodNazione, NAZIONE, Stato` - `COUNT(DISTINCT Pallet)` - `COUNT(DISTINCT Lotto)` - `COUNT(DISTINCT Articolo)` - `SUM(Qta)` - `MIN(Ordinamento)` - `MAX(IDStato)` ### Effetti attesi Con questa modifica: 1. all’inizio una picking list nuova continua a comparire normalmente 2. durante il lavoro il pulsante `Ricarica` mostra solo ciò che resta davvero da prelevare 3. le UDC già finite in `7G.1.1` non inquinano più i conteggi della testata 4. una picking list completamente esaurita esce naturalmente dalla lista visibile 5. le UDC non scaffalate continuano a comparire correttamente, perché stanno in `1000 = 5E1.1`, non in `9999` ### Chiusura automatica della prenotazione Per mantenere coerenti: - griglia alta - stato della prenotazione - comportamento del barcode `F1` la prenotazione attiva non deve soltanto sparire dalla griglia: deve essere anche **azzerata logicamente** quando il residuo arriva a zero. La patch SQL definitiva quindi deve fare anche questo: - dopo ogni movimento, controllare se il documento prenotato ha ancora righe in `ViewPackingListRestante` - se non ne ha più: - la prenotazione attiva va portata a `NULL` / `0` - le eventuali celle residue marcate `IDStato = 1` vanno riportate a `0` Effetto operativo: - plist piena all’inizio - plist via via ridotta durante i prelievi - plist che, all’ultima UDC, sparisce dalla griglia alta - e nello stesso momento cessa anche di essere la coda `F1` ### Impatto funzionale Questa modifica: - **non è** una replica pedissequa del legacy C# - ma è un miglioramento operativo coerente con il comportamento atteso dal magazzino In particolare rende la lista alta: - più coerente con il significato di “picking list ancora da esaurire” - allineata al contenuto della griglia bassa - più utile nei refresh successivi durante il lavoro reale ### Impatto tecnico Il cambiamento richiesto è piccolo lato Python: - aggiornare la query summary in [gestione_pickinglist.py](C:/devel/python/ware_house/gestione_pickinglist.py) Non richiede, in prima battuta: - modifiche alla stored di prenotazione - modifiche al barcode - modifiche a `ViewPackingListRestante` ### Decisione progettuale Questa modifica va considerata come una scelta esplicita di evoluzione: - **se vogliamo restare 1:1 col C#**, la lista alta resta su `XMag_ViewPackingList` - **se vogliamo rendere il modulo più aderente al residuo operativo reale**, conviene passare a `ViewPackingListRestante`