feat(measure): rework workflow operatore — AVVIA, capoturno, fine ciclo, blocco logout
- task_list: pulsante AVVIA sequenziale + rinomina "Inizia Misure" → "Visualizza Task" - task_execute header: aggiunti "Lista task", "Riepilogo", "Fermo linea", "Fine Produzione" - task_execute footer: "Fine ciclo misura" (attivo quando tutte le quote misurate) + "Completato" - Rimosso auto-advance al task successivo, flusso ora esplicito via Completato - Misura fuori tolleranza: blocco avanzamento + modale login capoturno (validate_supervisor) - Blocco logout durante misurazioni attive (icona lucchetto su task_execute) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -321,6 +321,33 @@ def save_measurement():
|
|||||||
return jsonify(resp), 201
|
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)
|
# Route: File proxy (browser can't send X-API-Key directly)
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -213,6 +213,15 @@
|
|||||||
|
|
||||||
<div class="border-t border-[var(--border-color)] my-1"></div>
|
<div class="border-t border-[var(--border-color)] my-1"></div>
|
||||||
|
|
||||||
|
{% if request.endpoint == 'measure.task_execute' %}
|
||||||
|
<span class="flex items-center gap-2.5 px-4 py-2.5 text-sm text-[var(--text-muted)] opacity-50 cursor-not-allowed"
|
||||||
|
title="{{ _('Logout bloccato durante le misurazioni') }}">
|
||||||
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="1.75" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/>
|
||||||
|
</svg>
|
||||||
|
{{ _('Logout') }}
|
||||||
|
</span>
|
||||||
|
{% else %}
|
||||||
<a href="{{ url_for('auth.logout') }}"
|
<a href="{{ url_for('auth.logout') }}"
|
||||||
class="flex items-center gap-2.5 px-4 py-2.5 text-sm text-measure-fail
|
class="flex items-center gap-2.5 px-4 py-2.5 text-sm text-measure-fail
|
||||||
hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors">
|
hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors">
|
||||||
@@ -221,6 +230,7 @@
|
|||||||
</svg>
|
</svg>
|
||||||
{{ _('Logout') }}
|
{{ _('Logout') }}
|
||||||
</a>
|
</a>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -84,7 +84,7 @@
|
|||||||
</a>
|
</a>
|
||||||
|
|
||||||
{# Task badge + title #}
|
{# Task badge + title #}
|
||||||
<div class="flex items-center gap-2 min-w-0 flex-1">
|
<div class="flex items-center gap-2 min-w-0">
|
||||||
<span class="shrink-0 inline-flex items-center px-2 py-0.5 rounded text-xs font-bold
|
<span class="shrink-0 inline-flex items-center px-2 py-0.5 rounded text-xs font-bold
|
||||||
bg-primary-50 dark:bg-primary-900/30 text-primary-700 dark:text-primary-300
|
bg-primary-50 dark:bg-primary-900/30 text-primary-700 dark:text-primary-300
|
||||||
border border-primary-200 dark:border-primary-800">
|
border border-primary-200 dark:border-primary-800">
|
||||||
@@ -95,6 +95,45 @@
|
|||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{# Lista task + Riepilogo buttons #}
|
||||||
|
<div class="shrink-0 flex items-center gap-1.5">
|
||||||
|
<a href="{{ url_for('measure.task_list', recipe_id=task.recipe_id or 0) }}"
|
||||||
|
class="btn btn-secondary text-xs py-1 px-2.5 gap-1">
|
||||||
|
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M4 6h16M4 10h16M4 14h16M4 18h16"/>
|
||||||
|
</svg>
|
||||||
|
{{ _('Lista task') }}
|
||||||
|
</a>
|
||||||
|
<button @click="goToSummary()"
|
||||||
|
class="btn btn-secondary text-xs py-1 px-2.5 gap-1">
|
||||||
|
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M9 17v-2m3 2v-4m3 4v-6m2 10H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
|
||||||
|
</svg>
|
||||||
|
{{ _('Riepilogo') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# Fermo linea + Fine produzione (measurement tasks only) #}
|
||||||
|
<template x-if="subtasks.length > 0">
|
||||||
|
<div class="shrink-0 flex items-center gap-1.5">
|
||||||
|
<button @click="showSupervisorModal = true; supervisorAction = 'fermo_linea'"
|
||||||
|
class="btn text-xs py-1 px-2.5 gap-1 border-2 border-amber-500 text-amber-700 dark:text-amber-300 bg-amber-50 dark:bg-amber-900/20 hover:bg-amber-100 dark:hover:bg-amber-900/40">
|
||||||
|
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M10 9v6m4-6v6m7-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||||
|
</svg>
|
||||||
|
{{ _('Fermo linea') }}
|
||||||
|
</button>
|
||||||
|
<button @click="showSupervisorModal = true; supervisorAction = 'fine_produzione'"
|
||||||
|
class="btn text-xs py-1 px-2.5 gap-1 border-2 border-red-500 text-red-700 dark:text-red-300 bg-red-50 dark:bg-red-900/20 hover:bg-red-100 dark:hover:bg-red-900/40">
|
||||||
|
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M9 10a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1v-4z"/>
|
||||||
|
</svg>
|
||||||
|
{{ _('Fine Produzione') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
{# Lot + Serial badges #}
|
{# Lot + Serial badges #}
|
||||||
<div class="shrink-0 flex items-center gap-2">
|
<div class="shrink-0 flex items-center gap-2">
|
||||||
{% if lot_number %}
|
{% if lot_number %}
|
||||||
@@ -397,19 +436,47 @@
|
|||||||
x-text="Math.round(progressPercent) + '%'"></span>
|
x-text="Math.round(progressPercent) + '%'"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Right: Summary button #}
|
{# Right: Fine ciclo misura (measurement tasks) / Completato (non-measurement or after cycle) #}
|
||||||
<button x-show="isComplete"
|
<template x-if="subtasks.length > 0">
|
||||||
x-transition
|
<div class="flex items-center gap-1.5">
|
||||||
@click="goToSummary()"
|
<button x-show="isComplete && !cycleConfirmed"
|
||||||
class="btn btn-primary text-xs shrink-0 gap-1 py-1.5 px-2.5 shadow-md">
|
x-transition
|
||||||
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
@click="confirmCycle()"
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
class="btn text-xs shrink-0 gap-1 py-1.5 px-3 shadow-md
|
||||||
</svg>
|
bg-primary text-white hover:bg-primary-700">
|
||||||
{{ _('Riepilogo') }}
|
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||||||
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M14 5l7 7m0 0l-7 7m7-7H3"/>
|
</svg>
|
||||||
</svg>
|
{{ _('Fine ciclo misura') }}
|
||||||
</button>
|
</button>
|
||||||
|
<button x-show="cycleConfirmed"
|
||||||
|
x-transition
|
||||||
|
@click="goToNextTask()"
|
||||||
|
class="btn text-xs shrink-0 gap-1 py-1.5 px-3 shadow-md
|
||||||
|
bg-measure-pass text-white hover:opacity-90">
|
||||||
|
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/>
|
||||||
|
</svg>
|
||||||
|
{{ _('Completato') }}
|
||||||
|
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M14 5l7 7m0 0l-7 7m7-7H3"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template x-if="subtasks.length === 0">
|
||||||
|
<button @click="goToNextTask()"
|
||||||
|
class="btn text-xs shrink-0 gap-1 py-1.5 px-3 shadow-md
|
||||||
|
bg-measure-pass text-white hover:opacity-90">
|
||||||
|
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/>
|
||||||
|
</svg>
|
||||||
|
{{ _('Completato') }}
|
||||||
|
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M14 5l7 7m0 0l-7 7m7-7H3"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -456,13 +523,77 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button @click="goToSummary()"
|
<div class="flex gap-2">
|
||||||
class="btn btn-primary w-full justify-center gap-2">
|
<button @click="goToSummary()"
|
||||||
{{ _('Vai al Riepilogo') }}
|
class="btn btn-secondary flex-1 justify-center gap-2">
|
||||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
{{ _('Riepilogo') }}
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M14 5l7 7m0 0l-7 7m7-7H3"/>
|
</button>
|
||||||
</svg>
|
<button @click="showCompletionOverlay = false; cycleConfirmed = true"
|
||||||
</button>
|
class="btn btn-primary flex-1 justify-center gap-2">
|
||||||
|
{{ _('Conferma ciclo') }}
|
||||||
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# ================================================================
|
||||||
|
SUPERVISOR LOGIN MODAL
|
||||||
|
================================================================ #}
|
||||||
|
<div x-show="showSupervisorModal"
|
||||||
|
x-transition:enter="transition ease-out duration-200"
|
||||||
|
x-transition:enter-start="opacity-0"
|
||||||
|
x-transition:enter-end="opacity-100"
|
||||||
|
x-transition:leave="transition ease-in duration-150"
|
||||||
|
x-transition:leave-start="opacity-100"
|
||||||
|
x-transition:leave-end="opacity-0"
|
||||||
|
x-cloak
|
||||||
|
class="fixed inset-0 z-[60] flex items-center justify-center bg-black/50 backdrop-blur-sm"
|
||||||
|
@click.self="showSupervisorModal = false">
|
||||||
|
<div class="bg-[var(--bg-card)] rounded-2xl shadow-2xl p-6 max-w-sm mx-4 w-full border border-[var(--border-color)]">
|
||||||
|
<div class="flex items-center gap-3 mb-4">
|
||||||
|
<div class="w-10 h-10 rounded-full bg-amber-100 dark:bg-amber-900/30 flex items-center justify-center">
|
||||||
|
<svg class="w-5 h-5 text-amber-600" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 class="text-lg font-bold text-[var(--text-primary)]">{{ _('Autorizzazione capoturno') }}</h3>
|
||||||
|
<p class="text-xs text-[var(--text-secondary)]" x-text="supervisorReason"></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-3 mb-5">
|
||||||
|
<div>
|
||||||
|
<label class="tmf-label text-xs">{{ _('Username') }}</label>
|
||||||
|
<input type="text" x-model="supervisorUsername" class="tmf-input text-sm"
|
||||||
|
placeholder="{{ _('Username capoturno') }}" @keydown.enter="validateSupervisor()">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="tmf-label text-xs">{{ _('Password') }}</label>
|
||||||
|
<input type="password" x-model="supervisorPassword" class="tmf-input text-sm"
|
||||||
|
placeholder="••••••••" @keydown.enter="validateSupervisor()">
|
||||||
|
</div>
|
||||||
|
<p x-show="supervisorError" class="text-xs text-red-600" x-text="supervisorError"></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<button @click="showSupervisorModal = false; supervisorUsername = ''; supervisorPassword = ''; supervisorError = ''"
|
||||||
|
class="btn btn-secondary flex-1 text-sm">
|
||||||
|
{{ _('Annulla') }}
|
||||||
|
</button>
|
||||||
|
<button @click="validateSupervisor()"
|
||||||
|
:disabled="!supervisorUsername || !supervisorPassword || supervisorValidating"
|
||||||
|
class="btn btn-primary flex-1 text-sm gap-1">
|
||||||
|
<svg x-show="supervisorValidating" class="w-4 h-4 animate-spin" fill="none" viewBox="0 0 24 24">
|
||||||
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/>
|
||||||
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"/>
|
||||||
|
</svg>
|
||||||
|
{{ _('Autorizza') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -498,6 +629,16 @@ function taskExecute() {
|
|||||||
errorMessage: '',
|
errorMessage: '',
|
||||||
showCompletionOverlay: false,
|
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 ----
|
// ---- Value from numpad / caliper ----
|
||||||
currentValue: null,
|
currentValue: null,
|
||||||
|
|
||||||
@@ -650,15 +791,17 @@ function taskExecute() {
|
|||||||
// Pause to show result feedback
|
// Pause to show result feedback
|
||||||
await new Promise(r => setTimeout(r, 1000));
|
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) {
|
if (this.completedCount >= this.totalSubtasks) {
|
||||||
const taskIds = window.__allTaskIds || [];
|
this.showCompletionOverlay = true;
|
||||||
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;
|
|
||||||
}
|
|
||||||
return;
|
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 ----
|
// ---- Go to summary ----
|
||||||
goToSummary() {
|
goToSummary() {
|
||||||
const recipeId = this.task.recipe_id || 0;
|
const recipeId = this.task.recipe_id || 0;
|
||||||
|
|||||||
@@ -60,8 +60,19 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Right: Traceability Badges -->
|
<!-- Right: AVVIA + Traceability -->
|
||||||
<div class="flex flex-col gap-2 sm:items-end shrink-0">
|
<div class="flex flex-col gap-3 sm:items-end shrink-0">
|
||||||
|
{% if tasks %}
|
||||||
|
<a href="{{ url_for('measure.task_execute', task_id=tasks[0].id) }}"
|
||||||
|
class="btn gap-3 w-full sm:w-auto justify-center text-lg font-bold
|
||||||
|
bg-red-600 hover:bg-red-700 text-white shadow-lg hover:shadow-xl
|
||||||
|
px-8 py-3 rounded-xl transition-all duration-200">
|
||||||
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M5 3l14 9-14 9V3z"/>
|
||||||
|
</svg>
|
||||||
|
{{ _('AVVIA') }}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
{% if lot_number %}
|
{% if lot_number %}
|
||||||
<div class="inline-flex items-center gap-2 px-3 py-1.5 rounded-lg text-sm
|
<div class="inline-flex items-center gap-2 px-3 py-1.5 rounded-lg text-sm
|
||||||
bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800
|
bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800
|
||||||
@@ -185,7 +196,7 @@
|
|||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z"/>
|
<path stroke-linecap="round" stroke-linejoin="round" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z"/>
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
<path stroke-linecap="round" stroke-linejoin="round" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||||
</svg>
|
</svg>
|
||||||
{{ _('Inizia Misure') }}
|
{{ _('Visualizza Task') }}
|
||||||
<svg class="w-4 h-4 transition-transform group-hover:translate-x-0.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
<svg class="w-4 h-4 transition-transform group-hover:translate-x-0.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7"/>
|
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
@@ -699,6 +699,54 @@ msgstr "Save settings"
|
|||||||
msgid "Salvato"
|
msgid "Salvato"
|
||||||
msgstr "Saved"
|
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"
|
msgid "Sessione in scadenza"
|
||||||
msgstr "Session expiring"
|
msgstr "Session expiring"
|
||||||
|
|
||||||
|
|||||||
@@ -729,6 +729,54 @@ msgstr "Salva impostazioni"
|
|||||||
msgid "Salvato"
|
msgid "Salvato"
|
||||||
msgstr "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"
|
msgid "Sessione in scadenza"
|
||||||
msgstr "Sessione in scadenza"
|
msgstr "Sessione in scadenza"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user