diff --git a/src/frontend/flask_app/blueprints/measure.py b/src/frontend/flask_app/blueprints/measure.py index e7466c2..1eef4d5 100644 --- a/src/frontend/flask_app/blueprints/measure.py +++ b/src/frontend/flask_app/blueprints/measure.py @@ -321,6 +321,33 @@ def save_measurement(): return jsonify(resp), 201 +# --------------------------------------------------------------------------- +# Route: Validate supervisor credentials (AJAX) +# --------------------------------------------------------------------------- +@measure_bp.route("/validate-supervisor", methods=["POST"]) +@login_required +@role_required("MeasurementTec") +def validate_supervisor(): + """Validate supervisor (capoturno) credentials for out-of-tolerance authorization.""" + data = request.get_json(silent=True) or {} + username = data.get("username", "").strip() + password = data.get("password", "") + + if not username or not password: + return jsonify({"error": True, "detail": "Username e password richiesti"}), 400 + + resp = api_client.post("/api/auth/login", data={"username": username, "password": password}) + + if resp.get("error"): + return jsonify({"error": True, "detail": "Credenziali non valide"}), 401 + + user = resp.get("user", {}) + if not user.get("is_admin"): + return jsonify({"error": True, "detail": "Utente non autorizzato (richiesto capoturno)"}), 403 + + return jsonify({"authorized": True, "supervisor": user.get("display_name", username)}), 200 + + # --------------------------------------------------------------------------- # Route: File proxy (browser can't send X-API-Key directly) # --------------------------------------------------------------------------- diff --git a/src/frontend/flask_app/templates/components/navbar.html b/src/frontend/flask_app/templates/components/navbar.html index 17331a8..d4c478c 100644 --- a/src/frontend/flask_app/templates/components/navbar.html +++ b/src/frontend/flask_app/templates/components/navbar.html @@ -213,6 +213,15 @@
+ {% if request.endpoint == 'measure.task_execute' %} + + + + + {{ _('Logout') }} + + {% else %} @@ -221,6 +230,7 @@ {{ _('Logout') }} + {% endif %} {% endif %} diff --git a/src/frontend/flask_app/templates/measure/task_execute.html b/src/frontend/flask_app/templates/measure/task_execute.html index f58a8c0..1b6010d 100644 --- a/src/frontend/flask_app/templates/measure/task_execute.html +++ b/src/frontend/flask_app/templates/measure/task_execute.html @@ -84,7 +84,7 @@ {# Task badge + title #} -
+
@@ -95,6 +95,45 @@
+ {# Lista task + Riepilogo buttons #} +
+ + + + + {{ _('Lista task') }} + + +
+ + {# Fermo linea + Fine produzione (measurement tasks only) #} + + {# Lot + Serial badges #}
{% if lot_number %} @@ -397,19 +436,47 @@ x-text="Math.round(progressPercent) + '%'">
- {# Right: Summary button #} - + {# Right: Fine ciclo misura (measurement tasks) / Completato (non-measurement or after cycle) #} + +
@@ -456,13 +523,77 @@ - +
+ + +
+ + + + {# ================================================================ + SUPERVISOR LOGIN MODAL + ================================================================ #} +
+
+
+
+ + + +
+
+

{{ _('Autorizzazione capoturno') }}

+

+
+
+ +
+
+ + +
+
+ + +
+

+
+ +
+ + +
@@ -498,6 +629,16 @@ function taskExecute() { errorMessage: '', showCompletionOverlay: false, + // ---- Cycle & workflow state ---- + cycleConfirmed: false, + showSupervisorModal: false, + supervisorAction: '', // 'out_of_tolerance', 'fermo_linea', 'fine_produzione' + supervisorUsername: '', + supervisorPassword: '', + supervisorError: '', + supervisorValidating: false, + pendingAdvance: false, + // ---- Value from numpad / caliper ---- currentValue: null, @@ -650,15 +791,17 @@ function taskExecute() { // Pause to show result feedback await new Promise(r => setTimeout(r, 1000)); - // Check if all done + // Out-of-tolerance: block advancement, require supervisor + if (pf === 'fail') { + this.pendingAdvance = true; + this.supervisorAction = 'out_of_tolerance'; + this.showSupervisorModal = true; + return; + } + + // Check if all done — show overlay but do NOT auto-navigate if (this.completedCount >= this.totalSubtasks) { - const taskIds = window.__allTaskIds || []; - const currentIdx = taskIds.indexOf(this.task.id); - if (currentIdx >= 0 && currentIdx < taskIds.length - 1) { - window.location.href = '{{ url_for("measure.task_execute", task_id=0) }}'.replace('/0', '/' + taskIds[currentIdx + 1]); - } else { - this.showCompletionOverlay = true; - } + this.showCompletionOverlay = true; return; } @@ -705,6 +848,73 @@ function taskExecute() { } }, + // ---- Confirm measurement cycle (Fine ciclo misura) ---- + confirmCycle() { + this.cycleConfirmed = true; + this.showCompletionOverlay = false; + }, + + // ---- Navigate to next task (Completato) ---- + goToNextTask() { + const taskIds = window.__allTaskIds || []; + const currentIdx = taskIds.indexOf(this.task.id); + if (currentIdx >= 0 && currentIdx < taskIds.length - 1) { + window.location.href = '{{ url_for("measure.task_execute", task_id=0) }}'.replace('/0', '/' + taskIds[currentIdx + 1]); + } else { + this.goToSummary(); + } + }, + + // ---- Supervisor reason text ---- + get supervisorReason() { + if (this.supervisorAction === 'out_of_tolerance') return '{{ _("Misurazione fuori tolleranza") }}'; + if (this.supervisorAction === 'fermo_linea') return '{{ _("Fermo linea richiesto") }}'; + if (this.supervisorAction === 'fine_produzione') return '{{ _("Fine produzione richiesta") }}'; + return ''; + }, + + // ---- Validate supervisor credentials ---- + async validateSupervisor() { + this.supervisorError = ''; + this.supervisorValidating = true; + + try { + const csrfToken = document.querySelector('meta[name=csrf-token]')?.content || ''; + const resp = await fetch('{{ url_for("measure.validate_supervisor") }}', { + method: 'POST', + headers: { 'Content-Type': 'application/json', 'X-CSRFToken': csrfToken }, + body: JSON.stringify({ username: this.supervisorUsername, password: this.supervisorPassword }) + }); + const data = await resp.json(); + + if (!resp.ok || data.error) { + this.supervisorError = data.detail || '{{ _("Credenziali non valide o utente non autorizzato") }}'; + this.supervisorValidating = false; + return; + } + + // Authorized — close modal and proceed + this.showSupervisorModal = false; + this.supervisorUsername = ''; + this.supervisorPassword = ''; + this.supervisorValidating = false; + + if (this.supervisorAction === 'out_of_tolerance') { + this.pendingAdvance = false; + if (this.completedCount >= this.totalSubtasks) { + this.showCompletionOverlay = true; + } else { + this.advanceToNext(); + } + } + // fermo_linea and fine_produzione are handled by GAIA integration (future) + + } catch (err) { + this.supervisorError = '{{ _("Errore di connessione") }}'; + this.supervisorValidating = false; + } + }, + // ---- Go to summary ---- goToSummary() { const recipeId = this.task.recipe_id || 0; diff --git a/src/frontend/flask_app/templates/measure/task_list.html b/src/frontend/flask_app/templates/measure/task_list.html index 57740ae..db5cad5 100644 --- a/src/frontend/flask_app/templates/measure/task_list.html +++ b/src/frontend/flask_app/templates/measure/task_list.html @@ -60,8 +60,19 @@ {% endif %} - -
+ +
+ {% if tasks %} + + + + + {{ _('AVVIA') }} + + {% endif %} {% if lot_number %}
- {{ _('Inizia Misure') }} + {{ _('Visualizza Task') }} diff --git a/src/frontend/flask_app/translations/en/LC_MESSAGES/messages.po b/src/frontend/flask_app/translations/en/LC_MESSAGES/messages.po index ac8b390..141b85b 100644 --- a/src/frontend/flask_app/translations/en/LC_MESSAGES/messages.po +++ b/src/frontend/flask_app/translations/en/LC_MESSAGES/messages.po @@ -699,6 +699,54 @@ msgstr "Save settings" msgid "Salvato" msgstr "Saved" +msgid "AVVIA" +msgstr "START" + +msgid "Visualizza Task" +msgstr "View Task" + +msgid "Lista task" +msgstr "Task list" + +msgid "Fermo linea" +msgstr "Line stop" + +msgid "Fine Produzione" +msgstr "End Production" + +msgid "Fine ciclo misura" +msgstr "End measurement cycle" + +msgid "Completato" +msgstr "Completed" + +msgid "Conferma ciclo" +msgstr "Confirm cycle" + +msgid "Autorizzazione capoturno" +msgstr "Supervisor authorization" + +msgid "Misurazione fuori tolleranza" +msgstr "Measurement out of tolerance" + +msgid "Fermo linea richiesto" +msgstr "Line stop requested" + +msgid "Fine produzione richiesta" +msgstr "End of production requested" + +msgid "Username capoturno" +msgstr "Supervisor username" + +msgid "Credenziali non valide o utente non autorizzato" +msgstr "Invalid credentials or unauthorized user" + +msgid "Autorizza" +msgstr "Authorize" + +msgid "Logout bloccato durante le misurazioni" +msgstr "Logout blocked during measurements" + msgid "Sessione in scadenza" msgstr "Session expiring" diff --git a/src/frontend/flask_app/translations/it/LC_MESSAGES/messages.po b/src/frontend/flask_app/translations/it/LC_MESSAGES/messages.po index 5cf1eb8..b100668 100644 --- a/src/frontend/flask_app/translations/it/LC_MESSAGES/messages.po +++ b/src/frontend/flask_app/translations/it/LC_MESSAGES/messages.po @@ -729,6 +729,54 @@ msgstr "Salva impostazioni" msgid "Salvato" msgstr "Salvato" +msgid "AVVIA" +msgstr "AVVIA" + +msgid "Visualizza Task" +msgstr "Visualizza Task" + +msgid "Lista task" +msgstr "Lista task" + +msgid "Fermo linea" +msgstr "Fermo linea" + +msgid "Fine Produzione" +msgstr "Fine Produzione" + +msgid "Fine ciclo misura" +msgstr "Fine ciclo misura" + +msgid "Completato" +msgstr "Completato" + +msgid "Conferma ciclo" +msgstr "Conferma ciclo" + +msgid "Autorizzazione capoturno" +msgstr "Autorizzazione capoturno" + +msgid "Misurazione fuori tolleranza" +msgstr "Misurazione fuori tolleranza" + +msgid "Fermo linea richiesto" +msgstr "Fermo linea richiesto" + +msgid "Fine produzione richiesta" +msgstr "Fine produzione richiesta" + +msgid "Username capoturno" +msgstr "Username capoturno" + +msgid "Credenziali non valide o utente non autorizzato" +msgstr "Credenziali non valide o utente non autorizzato" + +msgid "Autorizza" +msgstr "Autorizza" + +msgid "Logout bloccato durante le misurazioni" +msgstr "Logout bloccato durante le misurazioni" + msgid "Sessione in scadenza" msgstr "Sessione in scadenza"