diff --git a/INSTALL_PRODUZIONE_20260610.md b/INSTALL_PRODUZIONE_20260610.md new file mode 100644 index 0000000..71fd094 --- /dev/null +++ b/INSTALL_PRODUZIONE_20260610.md @@ -0,0 +1,61 @@ +# Installazione produzione - Warehouse/FlyWMS bridge + +## Ordine consigliato + +1. Fare backup del database `Mediseawall`. +2. Copiare il contenuto dello zip in una cartella locale, ad esempio `C:\flywms`. +3. Installare le dipendenze Python: + +```bat +python -m pip install -r requirements.txt +``` + +4. In SSMS, sul database `Mediseawall`, lanciare: + +```text +apply_python_parallel_pickinglist_patch.sql +apply_online_history_forms_patch.sql +``` + +## Cosa fanno gli script + +- `apply_python_parallel_pickinglist_patch.sql` crea il ramo SQL Python per gestione picking list, senza modificare le stored procedure C# legacy. +- `apply_online_history_forms_patch.sql` crea le viste Python-only per "Storico Picking List". +- "Storico movimenti UDC" non richiede script dedicati: legge in sola lettura `MagazziniPallet`, `Celle` e `XMag_GiacenzaPalletPlistChiuse`. + +## Rollback SQL + +Se serve tornare indietro sugli oggetti Python, usare: + +```text +rollback_online_history_forms_patch.sql +rollback_python_parallel_pickinglist_patch.sql +``` + +La tabella `dbo.PyPickingListReservation`, se creata, puo' rimanere anche in caso di rollback perche' il C# legacy non la usa. + +## Avvio + +Backoffice con console: + +```bat +python main.py +``` + +Backoffice senza console: + +```bat +pythonw warehouse.pyw +``` + +Barcode senza console: + +```bat +pythonw barcode.pyw +``` + +Se si usa un collegamento Windows, impostare anche la cartella "Da" alla cartella dell'applicazione, ad esempio `C:\flywms`. + +## File esclusi dal pacchetto + +Il pacchetto non include `db_connection.json`, log, cache Python e vecchi zip locali. Alla prima apertura il programma chiedera' la configurazione DB se `db_connection.json` non esiste. diff --git a/gestione_pickinglist.py b/gestione_pickinglist.py index 503dca4..7a99142 100644 --- a/gestione_pickinglist.py +++ b/gestione_pickinglist.py @@ -203,7 +203,7 @@ if _MODULE_LOG_ENABLED: # -------------------- SQL -------------------- SQL_PL = """ SELECT - COUNT(DISTINCT Pallet) AS Pallet, + COUNT(DISTINCT NULLIF(LTRIM(RTRIM(CAST(Pallet AS varchar(32)))), '')) AS Pallet, COUNT(DISTINCT Lotto) AS Lotto, COUNT(DISTINCT Articolo) AS Articolo, COUNT(DISTINCT Descrizione) AS Descrizione, @@ -907,13 +907,25 @@ class GestionePickingListFrame(ctk.CTkFrame): self.after_idle(_paint) break - def _reselect_documento_after_reload(self, documento: str): - """(Opzionale) Dopo un reload DB, riseleziona la PL con lo stesso Documento.""" + def _reselect_documento_after_reload(self, documento: str) -> bool: + """After a reload, reselect the same document and reload its details.""" for m in self.rows_models: if _s(m.pl.get("Documento")) == _s(documento): + self._detail_cache.pop(documento, None) m.set_checked(True) self.on_row_checked(m, True) - break + return True + return False + + def _selected_documento_for_reload(self) -> str | None: + """Return the document that should survive a toolbar reload.""" + + selected = self._get_selected_model() + if selected is not None: + documento = _s(selected.pl.get("Documento")) + return documento or None + documento = _s(self.detail_doc) + return documento or None # ----- eventi ----- @_log_call() @@ -967,6 +979,8 @@ class GestionePickingListFrame(ctk.CTkFrame): @_log_call() def reload_from_db(self, first: bool = False, reselect_documento: str | None = None): """Load or reload the picking list summary table from the database.""" + if reselect_documento is None and not first: + reselect_documento = self._selected_documento_for_reload() self.spinner.start(" Carico…") # spinner ON async def _job(): _log_sql("SQL_PL", SQL_PL, {}) @@ -976,8 +990,18 @@ class GestionePickingListFrame(ctk.CTkFrame): _log_dataset("SQL_PL", rows) self._refresh_mid_rows(rows) if reselect_documento: - self.after_idle(lambda doc=reselect_documento: self._reselect_documento_after_reload(doc)) - self.spinner.stop() # spinner OFF + def _reselect_or_clear(doc=reselect_documento): + found = self._reselect_documento_after_reload(doc) + if not found: + self.detail_doc = None + self._draw_details_hint() + self.spinner.stop() + self.busy.hide() + self.after_idle(_reselect_or_clear) + else: + self.detail_doc = None + self._draw_details_hint() + self.spinner.stop() # spinner OFF # se era il primo load, ripristina il cursore standard if self._first_loading: try: diff --git a/storico_pickinglist.py b/storico_pickinglist.py index a5e96cf..e70d9a6 100644 --- a/storico_pickinglist.py +++ b/storico_pickinglist.py @@ -21,46 +21,66 @@ __version__ = module_version(__name__) SQL_STORICO_PL = """ WITH base AS ( - SELECT * + SELECT + *, + NULLIF(LTRIM(RTRIM(CAST(Pallet AS varchar(32)))), '') AS PalletKey FROM dbo.py_XMag_ViewPackingListStorico ), -agg AS ( +pallets AS ( + SELECT + Documento, + PalletKey, + MAX(CASE WHEN Cella <> 9999 OR Cella IS NULL THEN 1 ELSE 0 END) AS HasResiduo, + MAX(CASE WHEN Cella = 9999 THEN 1 ELSE 0 END) AS HasSpedito + FROM base + WHERE PalletKey IS NOT NULL + GROUP BY Documento, PalletKey +), +meta AS ( SELECT Documento, MAX(DataDocumento) AS DataDocumento, MAX(StatoDocumento) AS StatoDocumento, MAX(NAZIONE) AS NAZIONE, MAX(CodNazione) AS CodNazione, - COUNT(DISTINCT Pallet) AS TotUDC, - SUM(CASE WHEN Cella = 9999 THEN 1 ELSE 0 END) AS RigheSpedite, - SUM(CASE WHEN Cella <> 9999 OR Cella IS NULL THEN 1 ELSE 0 END) AS RigheResidue, COUNT(*) AS RigheTotali, MIN(Ordinamento) AS PrimoOrdine, MAX(IDStato) AS IDStato FROM base GROUP BY Documento +), +agg AS ( + SELECT + Documento, + COUNT(*) AS TotUDC, + SUM(CASE WHEN p.HasResiduo = 0 AND p.HasSpedito = 1 THEN 1 ELSE 0 END) AS RigheSpedite, + SUM(CASE WHEN p.HasResiduo = 1 THEN 1 ELSE 0 END) AS RigheResidue + FROM pallets p + GROUP BY Documento ) SELECT - Documento, - DataDocumento, - StatoDocumento, - NAZIONE, - CodNazione, - TotUDC, - RigheResidue, - RigheSpedite, - RigheTotali, + m.Documento, + m.DataDocumento, + m.StatoDocumento, + m.NAZIONE, + m.CodNazione, + COALESCE(a.TotUDC, 0) AS TotUDC, + COALESCE(a.RigheResidue, 0) AS RigheResidue, + COALESCE(a.RigheSpedite, 0) AS RigheSpedite, + m.RigheTotali, CASE - WHEN StatoDocumento = 'D' THEN 'Chiusa' - WHEN RigheResidue = 0 THEN 'Esaurita' - WHEN RigheSpedite > 0 THEN 'In corso' + 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 AS StatoOperativo, - IDStato, - PrimoOrdine -FROM agg -WHERE (:documento IS NULL OR CAST(Documento AS varchar(32)) LIKE CONCAT('%', :documento, '%')) -ORDER BY Documento DESC; + m.IDStato, + m.PrimoOrdine +FROM meta m +LEFT JOIN agg a ON a.Documento = m.Documento +WHERE (:documento IS NULL OR CAST(m.Documento AS varchar(32)) LIKE CONCAT('%', :documento, '%')) +ORDER BY m.Documento DESC; """ SQL_STORICO_PL_DETAILS = """ diff --git a/version_info.py b/version_info.py index 6b8858e..6bc1149 100644 --- a/version_info.py +++ b/version_info.py @@ -20,14 +20,14 @@ MODULE_VERSIONS: dict[str, str] = { "db_config": "1.0.0", "gestione_aree": "1.0.0", "gestione_layout": "1.0.0", - "gestione_pickinglist": "1.0.0", + "gestione_pickinglist": "1.0.2", "gestione_scarico": "1.0.0", "locale_text": "1.0.0", "login_window": "1.0.0", "prenota_sprenota_sql": "1.0.0", "reset_corsie": "1.0.0", "search_pallets": "1.0.0", - "storico_pickinglist": "1.0.0", + "storico_pickinglist": "1.0.2", "storico_udc": "1.0.0", "tooltips": "1.0.0", "ui_theme": "1.0.0",