# SQL storico picking list

## Oggetti SQL coinvolti

La form "Storico Picking List" usa un ramo SQL Python-only:

- `dbo.py_vPreparaPackingListSAMA1`
- `dbo.py_vPreparaPackingList`
- `dbo.py_XMag_ViewPackingListStorico`
- `dbo.PyPickingListReservation`

Gli script principali sono:

- `apply_python_pickinglist_history_views.sql`
- `apply_online_history_forms_patch.sql`
- `rollback_python_pickinglist_history_views.sql`
- `rollback_online_history_forms_patch.sql`

## Motivazione del ramo Python-only

Il progetto deve convivere temporaneamente con l'applicazione C# legacy. Per questo lo storico Python non modifica:

- `dbo.vPreparaPackingListSAMA1`
- `dbo.vPreparaPackingList`
- `dbo.XMag_ViewPackingList`
- stored procedure legacy C#

Le viste prefissate `py_` permettono di evolvere il comportamento Python senza cambiare la semantica legacy.

## Vista `dbo.py_vPreparaPackingListSAMA1`

### Scopo

Legge dal database ERP `SAMA1` le informazioni documentali e di riga necessarie a ricostruire la picking list.

### Tabelle ERP coinvolte

- `SAMA1.dbo.LOTSER`
- `SAMA1.dbo.ARTICO`
- `SAMA1.dbo.FATRIG`
- `SAMA1.dbo.LOTTIBF`
- `SAMA1.dbo.BAMTES`
- `SAMA1.dbo.NAZIONI`
- `SAMA1.sam.EXTUC`
- `SAMA1.dbo.MTRASP`

### Campi principali

| Campo | Significato |
| --- | --- |
| `NUMLOT` | lotto |
| `CODICE` | codice articolo |
| `DESCR` | descrizione riga/articolo |
| `UDC` | prime 6 cifre di `LOTSER.NUMSER` |
| `Qta` | somma quantita giacente ERP |
| `NUMDOC` | numero documento/picking list |
| `DATDOC` | data documento ERP |
| `StatoDocumento` | stato ERP, filtrato su `P` o `D` |
| `NAZIONE` | trasporto + descrizione nazione |

### Filtri

- anno documento >= anno corrente;
- stato documento in `P`, `D`;
- lotto diverso da `00000000000`.

## Vista `dbo.py_vPreparaPackingList`

### Scopo

E' una vista di pulizia sopra `py_vPreparaPackingListSAMA1`.

### Filtro

Esclude righe con `NUMLOT = ''`.

## Vista `dbo.py_XMag_ViewPackingListStorico`

### Scopo

Unisce il contenuto ERP della picking list con la posizione WMS corrente della UDC.

### Tabelle WMS coinvolte

- `dbo.XMag_GiacenzaPallet`
- `dbo.Celle`
- `dbo.Aree`
- `dbo.PyPickingListReservation`

### Join principale

La vista usa una `RIGHT OUTER JOIN` tra posizione WMS e preparazione ERP:

```sql
RIGHT OUTER JOIN dbo.py_vPreparaPackingList prep
    ON g.BarcodePallet COLLATE SQL_Latin1_General_CP1_CI_AS = prep.UDC
```

Questo garantisce che una UDC presente nella picking list ERP compaia anche se non e' scaffalata nel WMS.

### Convenzioni celle

| IDCella | Ubicazione | Significato |
| --- | --- | --- |
| `9999` | `7G - 1 - 1` | spedizione / UDC scaricata |
| `1000` | `Non scaff.` | fallback per UDC non trovata in `XMag_GiacenzaPallet` |

La vista usa:

```sql
ISNULL(g.IDCella, 1000) AS Cella
ISNULL(c.Corsia + ' - ' + c.Colonna + ' - ' + c.Fila, 'Non scaff.') AS Ubicazione
```

Quindi le UDC ERP non presenti in giacenza WMS sono rappresentate come non scaffalate.

## Query Python `SQL_STORICO_PL`

### Scopo

Costruisce la griglia alta della form.

### CTE

| CTE | Ruolo |
| --- | --- |
| `base` | normalizza `Pallet` in `PalletKey` |
| `pallets` | aggrega righe multi-lotto per singola UDC |
| `meta` | calcola metadati documento |
| `agg` | calcola totale UDC, UDC spedite e UDC residue |

### Stato operativo

La query produce `StatoOperativo` con questa logica:

```sql
CASE
    WHEN m.StatoDocumento = 'D' AND COALESCE(a.RigheResidue, 0) > 0 THEN 'Chiusa ERP con residui'
    WHEN m.StatoDocumento = 'D' THEN 'Chiusa'
    WHEN COALESCE(a.RigheResidue, 0) = 0 THEN 'Esaurita'
    WHEN COALESCE(a.RigheSpedite, 0) > 0 THEN 'In corso'
    ELSE 'Da lavorare'
END
```

### Deduplica UDC

La deduplica avviene su `PalletKey`, non sulle righe. Questo evita che una UDC con piu' lotti venga contata come piu' pallet.

## Query Python `SQL_STORICO_PL_DETAILS`

### Scopo

Costruisce la griglia bassa della form.

### Output

| Campo | Ruolo |
| --- | --- |
| `Documento` | numero picking list |
| `Pallet` | UDC fisica |
| `Lotto` | lotto |
| `Articolo` | codice articolo |
| `Descrizione` | descrizione articolo/riga |
| `Qta` | quantita |
| `DataDocumento` | data ERP |
| `StatoDocumento` | stato ERP |
| `Cella` | cella WMS corrente/fallback |
| `Ubicazione` | descrizione cella |
| `Ordinamento` | ordine operativo |
| `IDStato` | prenotazione Python |

## Effetti SQL del versamento residui

La form non contiene direttamente update/insert SQL per il versamento residui. Chiama `move_pallet_async` in `gestione_scarico.py`.

La batch SQL associata e' `SQL_SCARICA_UDC` e produce:

1. ricerca dell'ultimo movimento `V` positivo della UDC;
2. update audit della riga sorgente;
3. insert movimento `P` sulla cella sorgente;
4. update `Celle.IDStato = 0` sulla cella sorgente;
5. insert movimento `V` sulla cella target;
6. esecuzione `dbo.py_sp_ControllaPrenotazionePackingListPalletNew`.

Per la funzione storico picking list il target e':

- `target_idcella = 9999`
- `target_barcode_cella = '9000000'`

## Note di consistenza dati

- Una UDC non scaffalata viene vista come `Cella = 1000`, quindi e' residua.
- Una UDC in `9999` non e' residua e viene esclusa dal movimento massivo.
- Una UDC multi-lotto puo' comparire piu' volte nel dettaglio ma deve generare un solo movimento fisico.
- Lo stato `Chiusa ERP con residui` evidenzia disallineamento tra documento ERP chiuso e WMS non completamente versato in spedizione.

## Query diagnostiche utili

Verificare righe documento:

```sql
SELECT *
FROM dbo.py_XMag_ViewPackingListStorico
WHERE Documento = 155
ORDER BY Ordinamento, Pallet;
```

Verificare UDC residue:

```sql
SELECT DISTINCT Pallet, Cella, Ubicazione
FROM dbo.py_XMag_ViewPackingListStorico
WHERE Documento = 155
  AND ISNULL(Cella, 0) <> 9999
ORDER BY Pallet;
```

Verificare movimenti UDC dopo versamento:

```sql
SELECT
    ID,
    Tipo,
    IDRiferimento,
    Attributo,
    IDCella,
    DataMagazzino,
    InsUtente,
    InsDataOra,
    ModUtente,
    ModDataOra
FROM dbo.MagazziniPallet
WHERE Attributo = '655560'
ORDER BY ID;
```
