feat(production): dai una vita propria alla produzione, lato server

Punto 1 del documento modifiche del 28/07, il prerequisito su cui poggiano i
punti 3, 4 e 6.

Lo stato di una produzione viveva dentro una pagina del browser: timer, conteggio
cicli e flag "produzione avviata" erano variabili Alpine di task_execute.html, e
la navigazione fra task e' un ricaricamento completo, quindi cambiando task si
perdeva tutto. Da qui il loop di misura che non reggeva, il fermo linea che non
aveva nulla da fermare e l'assenza di storico.

Nuove tabelle production_runs e production_events (migrazione 005), endpoint REST
senza stato in memoria di processo - con un'app di stazione installata su ogni PC
il database e' l'unico posto condiviso - e il frontend che legge lo stato
all'apertura invece di tenerlo in memoria.

Tre scelte di modello:

- la scadenza e' un timestamp assoluto (next_measurement_at), non un contatore:
  il countdown si ricalcola da li' a ogni caricamento, e lasciarne andare la
  differenza sotto zero dara' gratis il contatore del ritardo del punto 3. Al
  client vanno i secondi gia' calcolati, non il timestamp: un datetime naive
  verrebbe letto nel fuso del browser e il conto sarebbe sfasato dell'offset UTC;
- l'intervallo di misura e' copiato sulla produzione, non referenziato: modificare
  la ricetta a produzione avviata non deve spostare una scadenza in corso;
- active_station_id rispecchia la stazione finche' la produzione e' aperta e va a
  NULL alla chiusura. Con un vincolo unico sopra, "una stazione = una produzione
  aperta" e' una garanzia del database e non un controllo soggetto a race; i NULL
  non collidono, quindi le produzioni chiuse si accumulano senza disturbo.

Il fermo linea congela il conto alla rovescia e alla ripresa la scadenza viene
traslata della durata del fermo, non ricalcolata: un fermo non regala ne' toglie
tempo all'operatore. L'autorizzazione del capoturno passa da authenticate_user e
non da un login, che rigenererebbe la sua API key buttando giu' la sessione che ha
aperta altrove.

La migrazione e' stata eseguita davvero, non solo scritta, su uno SQLite usa e
getta: upgrade e downgrade girano e le colonne coincidono con i modelli. La prova
ha trovato un difetto - create_unique_constraint dopo create_table e' un ALTER,
che SQLite rifiuta - ora il vincolo e' dichiarato dentro create_table.

Fuori da questo commit, per stare nei confini del punto 1: l'API espone gia'
pause, resume e close, ma i pulsanti fermo linea e fine produzione restano da
collegare (punto 6), e il rientro forzato sulla misura allo scadere e' il punto 3.

Corretti due difetti trovati strada facendo: env.py non importava ne' Station ne'
ProductionRun, quindi l'autogenerate di Alembic era gia' cieco sulle stazioni; e
task_execute.html, lo schermo con piu' JavaScript dell'applicazione, non era
coperto dal test di sintassi. Aggiungerlo ha richiesto di correggere l'helper, che
validava le espressioni Alpine solo come espressione singola e bocciava
@click="a = false; b = true", forma che Alpine accetta: ora prova entrambe le
letture e fallisce solo se cadono tutte e due.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Adriano Dal Pastro
2026-07-28 16:20:43 +00:00
parent e7889f80a3
commit 55e5e0153f
16 changed files with 1851 additions and 147 deletions
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2026-07-28 15:24+0000\n"
"POT-Creation-Date: 2026-07-28 16:15+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -166,6 +166,12 @@ msgstr ""
msgid "Utente non autorizzato (richiesto capoturno)"
msgstr ""
#: blueprints/measure.py:431 blueprints/measure.py:448
#: templates/errors/station_not_configured.html:2
#: templates/errors/station_not_configured.html:16
msgid "Stazione non configurata"
msgstr ""
#: templates/base.html:173
msgid "Sessione in scadenza"
msgstr ""
@@ -363,7 +369,7 @@ msgstr ""
#: templates/maker/task_editor.html:747 templates/maker/task_editor.html:866
#: templates/maker/task_editor.html:931 templates/maker/task_editor.html:1036
#: templates/measure/select_recipe.html:367
#: templates/measure/task_execute.html:664
#: templates/measure/task_execute.html:684
msgid "Annulla"
msgstr ""
@@ -507,7 +513,7 @@ msgstr ""
#: templates/admin/users.html:48 templates/admin/users.html:173
#: templates/admin/users.html:179 templates/auth/login.html:35
#: templates/auth/login.html:49 templates/auth/profile.html:36
#: templates/measure/task_execute.html:649
#: templates/measure/task_execute.html:669
msgid "Username"
msgstr ""
@@ -559,7 +565,7 @@ msgstr ""
#: templates/admin/users.html:206 templates/admin/users.html:214
#: templates/auth/login.html:57 templates/auth/login.html:71
#: templates/measure/task_execute.html:654
#: templates/measure/task_execute.html:674
msgid "Password"
msgstr ""
@@ -800,11 +806,6 @@ msgstr ""
msgid "Prossima misura"
msgstr ""
#: templates/errors/station_not_configured.html:2
#: templates/errors/station_not_configured.html:16
msgid "Stazione non configurata"
msgstr ""
#: templates/errors/station_not_configured.html:20
msgid "Questo client non ha impostato la variabile di ambiente STATION_CODE."
msgstr ""
@@ -842,7 +843,7 @@ msgstr ""
#: templates/maker/recipe_editor.html:128 templates/maker/recipe_list.html:246
#: templates/maker/task_drawing.html:106 templates/maker/task_editor.html:130
#: templates/measure/task_complete.html:168
#: templates/measure/task_execute.html:477 templates/measure/task_list.html:2
#: templates/measure/task_execute.html:497 templates/measure/task_list.html:2
#: templates/measure/task_list.html:156
msgid "Task"
msgstr ""
@@ -989,7 +990,8 @@ msgid "Errore durante eliminazione"
msgstr ""
#: templates/maker/recipe_list.html:55 templates/measure/select_recipe.html:55
#: templates/measure/task_execute.html:1121
#: templates/measure/task_execute.html:1057
#: templates/measure/task_execute.html:1241
msgid "Errore di connessione"
msgstr ""
@@ -1580,7 +1582,7 @@ msgid "Misurazione aggiunta"
msgstr ""
#: templates/maker/task_editor.html:1645
#: templates/measure/task_execute.html:870
#: templates/measure/task_execute.html:931
msgid "Errore nel salvataggio della misurazione"
msgstr ""
@@ -1734,13 +1736,13 @@ msgstr ""
#: templates/measure/task_complete.html:3
#: templates/measure/task_complete.html:36
#: templates/measure/task_execute.html:112
#: templates/measure/task_execute.html:585
#: templates/measure/task_execute.html:605
#: templates/statistics/dashboard.html:139
msgid "Riepilogo"
msgstr ""
#: templates/measure/task_complete.html:44
#: templates/measure/task_execute.html:562
#: templates/measure/task_execute.html:582
msgid "Misurazioni Complete"
msgstr ""
@@ -1760,12 +1762,12 @@ msgid "Totale"
msgstr ""
#: templates/measure/task_complete.html:103
#: templates/measure/task_execute.html:570
#: templates/measure/task_execute.html:590
msgid "Conformi"
msgstr ""
#: templates/measure/task_complete.html:120
#: templates/measure/task_execute.html:574
#: templates/measure/task_execute.html:594
msgid "Attenzione"
msgstr ""
@@ -1963,77 +1965,85 @@ msgstr ""
msgid "Ciclo"
msgstr ""
#: templates/measure/task_execute.html:446
#: templates/measure/task_execute.html:612
#: templates/measure/task_execute.html:445
msgid "Produzione non registrata sul server"
msgstr ""
#: templates/measure/task_execute.html:466
#: templates/measure/task_execute.html:632
msgid "Avvio Produzione"
msgstr ""
#: templates/measure/task_execute.html:450
#: templates/measure/task_execute.html:470
msgid "Invia segnale al gestionale per avviare il timer della linea"
msgstr ""
#: templates/measure/task_execute.html:461
#: templates/measure/task_execute.html:481
msgid "Produzione avviata"
msgstr ""
#: templates/measure/task_execute.html:506
#: templates/measure/task_execute.html:526
msgid "Fine ciclo misura"
msgstr ""
#: templates/measure/task_execute.html:516
#: templates/measure/task_execute.html:530
#: templates/measure/task_execute.html:536
#: templates/measure/task_execute.html:550
msgid "Completato"
msgstr ""
#: templates/measure/task_execute.html:564
#: templates/measure/task_execute.html:584
msgid "Tutte le"
msgstr ""
#: templates/measure/task_execute.html:564
#: templates/measure/task_execute.html:584
msgid "misurazioni sono state registrate."
msgstr ""
#: templates/measure/task_execute.html:578
#: templates/measure/task_execute.html:598
msgid "Non Conf."
msgstr ""
#: templates/measure/task_execute.html:592
#: templates/measure/task_execute.html:612
msgid "Conferma ciclo"
msgstr ""
#: templates/measure/task_execute.html:602
#: templates/measure/task_execute.html:622
msgid "Task successivo"
msgstr ""
#: templates/measure/task_execute.html:642
#: templates/measure/task_execute.html:662
msgid "Autorizzazione capoturno"
msgstr ""
#: templates/measure/task_execute.html:651
#: templates/measure/task_execute.html:671
msgid "Username capoturno"
msgstr ""
#: templates/measure/task_execute.html:673
#: templates/measure/task_execute.html:693
msgid "Autorizza"
msgstr ""
#: templates/measure/task_execute.html:914
#: templates/measure/task_execute.html:975
msgid "Errore di rete. Riprovare."
msgstr ""
#: templates/measure/task_execute.html:1078
#: templates/measure/task_execute.html:1052
msgid "Errore di comunicazione con il server"
msgstr ""
#: templates/measure/task_execute.html:1198
msgid "Misurazione fuori tolleranza"
msgstr ""
#: templates/measure/task_execute.html:1079
#: templates/measure/task_execute.html:1199
msgid "Fermo linea richiesto"
msgstr ""
#: templates/measure/task_execute.html:1080
#: templates/measure/task_execute.html:1200
msgid "Fine produzione richiesta"
msgstr ""
#: templates/measure/task_execute.html:1099
#: templates/measure/task_execute.html:1219
msgid "Credenziali non valide o utente non autorizzato"
msgstr ""