15 KiB
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:
- seleziona una picking list
- preme
Prenota - 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
F1sul barcode - le altre picking list restano non prenotate e alimentano la coda
F2
Comportamento attuale
Il comportamento effettivamente osservato è questo:
- l'operatore seleziona una sola picking list
- preme
Prenota - 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 chiama:
che esegue direttamente la stored procedure SQL legacy:
dbo.sp_xExePackingListPallet
Lato C#
La finestra legacy 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 aggrega così:
GROUP BY Documento, CodNazione, NAZIONE, StatoMAX(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:
Logica attuale
La stored:
- legge da
XMag_ViewPackingList - estrae le
Cellaassociate alDocumento - per ogni cella:
- se
IDStato = 0, la mette a1 - altrimenti la rimette a
0
- se
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 = 1sulla 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:
SELECT
Documento,
Cella,
COUNT(*) AS Righe
FROM dbo.XMag_ViewPackingList
WHERE Documento IN (<doc1>, <doc2>)
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:
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:
135 1000 16 16
137 1000 2 1
133 8057 1 1
135 8057 1 1
Interpretazione:
- documento
135e documento137condividono la cella1000 - documento
133e documento135condividono la cella8057
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:
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:
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:
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:
135 1000 16 16
137 1000 2 1
133 8057 1 1
135 8057 1 1
Interpretazione:
- documento
135e documento137condividono la cella1000 - documento
133e documento135condividono la cella8057
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.19999=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:
- la stored
sp_xExePackingListPalletlavora a livello cella - la UI mostra la prenotazione a livello documento
Quando esistono celle condivise tra documenti:
- la prenotazione di un documento produce
IDStato = 1anche 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:
- rimuovere la prenotazione da tutte le altre picking list
- 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:
- se il documento
Dè già prenotato, non succede nulla - se il documento
Dnon è prenotato:- tutte le celle prenotate appartenenti ad altri documenti vengono riportate a
IDStato = 0 - tutte le celle del documento
Dvengono portate aIDStato = 1 - il log della picking list resta aggiornato come oggi
- tutte le celle prenotate appartenenti ad altri documenti vengono riportate a
Effetto atteso:
- solo il documento
Drisulta prenotato nella griglia - solo il documento
Dalimenta la codaF1 - tutte le altre picking list alimentano
F2 - premere
Prenotapiù 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:
- se il documento
Dnon è prenotato, non succede nulla - se il documento
Dè prenotato:- le celle del documento
Dvengono riportate aIDStato = 0 - nessun altro documento viene automaticamente prenotato
- le celle del documento
Effetto atteso:
- nessuna picking list resta prenotata, salvo una nuova prenotazione esplicita
- premere
S-prenotapiù 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
@AzioneP= PrenotaS= S-prenota
Logica:
- determinare se il documento selezionato è già prenotato o no
- se
@Azione = 'P'- se il documento è già prenotato: non fare nulla
- altrimenti:
- azzerare
IDStato = 1sulle celle coinvolte da altri documenti prenotati - impostare
IDStato = 1sulle celle del documento selezionato
- azzerare
- se
@Azione = 'S'- se il documento non è prenotato: non fare nulla
- altrimenti:
- riportare a
0solo le celle del documento selezionato
- riportare a
Questa logica mantiene:
- la semantica esistente a livello cella
- ma aggiunge l'esclusività a livello documento
- e mantiene pulita la semantica distinta dei pulsanti
PrenotaeS-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:
- una sola picking list risulta prenotata in griglia
F1propone solo UDC del documento prenotatoF2propone UDC delle altre picking list- il problema di doppia prenotazione non si ripresenta più nemmeno in presenza di celle condivise come
1000o8057
E dopo S-prenota:
- il documento selezionato torna non prenotato
F1non 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.
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
IDStatosulle celle, così il barcode legacy può continuare a usareF1/F2con la semantica attuale.
Prossimo passo
Dopo approvazione di questo documento:
- modificare
sp_xExePackingListPalleto introdurre una nuova versione compatibile - testare il risultato con almeno 3 picking list attive
- verificare il comportamento sul backoffice
- verificare il comportamento sul barcode (
F1/F2)