Milestone ultima alpha
This commit is contained in:
588
analisi_bug_prenotazione_pickinglist.md
Normal file
588
analisi_bug_prenotazione_pickinglist.md
Normal file
@@ -0,0 +1,588 @@
|
||||
# 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 (<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:
|
||||
|
||||
```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`)
|
||||
Reference in New Issue
Block a user