Milestone ultima alpha

This commit is contained in:
2026-05-22 14:25:09 +02:00
parent 8489cd7459
commit a5e704c214
25 changed files with 3896 additions and 273 deletions

View 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`)