Files
ware_house/analisi_bug_prenotazione_pickinglist.md
2026-05-22 15:15:43 +02:00

22 KiB
Raw Blame History

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 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, 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:

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:

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 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:

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 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.

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 lunico 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:

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

Allistante 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:

  • 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. allinizio 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 allinizio
  • plist via via ridotta durante i prelievi
  • plist che, allultima 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:

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