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 #}
+
+
+ {# 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"