feat: FASE 3 - Flusso MeasurementTec (selezione ricetta, esecuzione misure, riepilogo)

Implementazione completa del flusso operativo per il ruolo MeasurementTec:

Blueprint measure.py:
- select_recipe: selezione ricetta con ricerca e barcode
- task_list: lista task con conteggi subtask e allegati
- task_execute: esecuzione misure con numpad, calibro USB, feedback real-time
- task_complete: riepilogo con statistiche pass/fail e export CSV
- API AJAX: lookup-barcode, save-traceability, save-measurement
- Autorizzazione role_required("MeasurementTec") su tutte le route

Componenti riutilizzabili:
- numpad.html/js/css: tastierino numerico touch-friendly con keyboard support
- caliper_status.html + caliper.js: integrazione calibro USB via Web Serial API
- barcode_scanner.html + barcode.js: scansione barcode con html5-qrcode
- measurement_feedback.html: feedback visivo pass/warning/fail in tempo reale
- next_measurement.html: indicatore prossima misurazione
- annotation-viewer.js: visualizzatore canvas con marker su disegni tecnici
- csv-export.js: export CSV con locale italiano (delimitatore ;, decimale ,)

Sicurezza:
- Decoratore role_required(*roles) per autorizzazione basata su ruoli
- CSRF token su tutti i POST AJAX
- |tojson per prevenire XSS su annotations_json
- Validazione input lato client e server

i18n: 23+ nuove chiavi tradotte IT/EN per tutti i template FASE 3

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Adriano
2026-02-07 08:40:58 +01:00
parent edd4580a5a
commit a386986c17
19 changed files with 3905 additions and 16 deletions
+20 -1
View File
@@ -1,7 +1,7 @@
"""Authentication blueprint - login, logout, profile."""
from functools import wraps
from flask import Blueprint, flash, redirect, render_template, request, session, url_for
from flask import Blueprint, abort, flash, redirect, render_template, request, session, url_for
from flask_babel import gettext as _
from services.api_client import api_client
@@ -20,6 +20,25 @@ def login_required(f):
return decorated
def role_required(*roles):
"""Decorator to require one of the specified roles.
Usage:
@role_required("MeasurementTec", "Metrologist")
def my_view(): ...
"""
def decorator(f):
@wraps(f)
def decorated(*args, **kwargs):
user = session.get("user", {})
user_roles = user.get("roles", [])
if not any(r in user_roles for r in roles):
abort(403)
return f(*args, **kwargs)
return decorated
return decorator
@auth_bp.route("/login", methods=["GET", "POST"])
def login():
"""Login page and form handler."""