feat(measure): il fuori tolleranza si autorizza, non si chiude
Il punto 5 c'era a meta': la schermata apriva il modale del capoturno e bloccava l'avanzamento automatico, ma il modale si chiudeva con Annulla o con un click sullo sfondo, la misura era gia' salvata, e `pendingAdvance` veniva impostato e non letto da nessuno. Era una conferma, non uno sbarramento. Ora l'autorizzazione finisce sulla misura: due colonne, chi ha autorizzato e quando. Finche' una quota e' fuori tolleranza e nessuno l'ha autorizzata, il server rifiuta la misura della quota successiva e la chiusura del ciclo. Non e' la schermata a impedirlo: la schermata risparmia solo il viaggio. Rimisurare la stessa quota resta possibile — il calibro scivola, il pezzo si riposiziona — e una seconda lettura in tolleranza libera il blocco, perche' conta l'ultima lettura di ogni quota. Quante volte si possa riprovare e' il punto 4, e tutte le letture restano comunque a registro. Un warning non blocca: fuori dai limiti di attenzione ma dentro la tolleranza e' dentro la tolleranza. Ricaricare la pagina era il modo piu' semplice per scavalcare il vecchio gate. Non lo e' piu': la schermata chiede al server, all'apertura, se una quota sta aspettando, e si ritrova davanti lo stesso blocco. Sparisce /validate-supervisor, che verificava le credenziali e buttava via la risposta. Al suo posto un endpoint che le credenziali le usa per scrivere l'approvazione dove serve. Il controllo del capoturno si sposta in auth_service, accanto al resto delle credenziali: fermo linea, chiusura e fuori tolleranza fanno la stessa domanda, e solo una delle tre riguarda la produzione. Il file di statistica guadagna authorised_by e authorised_at: mostrava il fallimento e non la decisione, che e' la meta' che un auditor chiede. Migrazione 010: due colonne nullable sulle misure. Le righe esistenti restano nulle — retrodatare un'autorizzazione mai avvenuta sarebbe inventarsi un record di audit, e quelle produzioni sono chiuse da un pezzo. Test: +18 (309). Coprono il rifiuto della quota successiva, la rimisura ammessa, il rilascio del blocco con una lettura buona, il ciclo che non si chiude, il capoturno registrato sulla misura, le credenziali sbagliate e chi capoturno non e', il file di statistica, e il fatto che una produzione chiusa non blocchi la successiva. Aggiunto uploads/statistics/ al gitignore: i test che chiudono una produzione scrivevano nel repository. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -19,11 +19,13 @@ from src.backend.models.orm.recipe import RecipeVersion
|
||||
from src.backend.models.orm.setting import SystemSetting
|
||||
from src.backend.models.orm.user import User
|
||||
from src.backend.models.api.measurement import (
|
||||
MeasurementAuthorisation,
|
||||
MeasurementBatchCreate,
|
||||
MeasurementCreate,
|
||||
MeasurementListResponse,
|
||||
MeasurementResponse,
|
||||
)
|
||||
from src.backend.services import auth_service, measurement_service
|
||||
from src.backend.services.measurement_service import save_measurement
|
||||
|
||||
router = APIRouter(prefix="/api/measurements", tags=["measurements"])
|
||||
@@ -57,6 +59,60 @@ async def create_measurement(
|
||||
)
|
||||
|
||||
|
||||
@router.get("/pending-authorisation", response_model=MeasurementResponse | None)
|
||||
async def get_pending_authorisation(
|
||||
version_id: int = Query(..., gt=0),
|
||||
production_run_id: int | None = Query(None, gt=0),
|
||||
user: User = Depends(require_measurement_tec),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""The out-of-tolerance measurement holding this operator up, or null.
|
||||
|
||||
Asked on load, so that reloading the page is not a way past the gate: the
|
||||
screen finds the same block waiting for it that the server enforces.
|
||||
"""
|
||||
blocking = await measurement_service.pending_authorisation(
|
||||
db, version_id, user.id, production_run_id,
|
||||
)
|
||||
return MeasurementResponse.model_validate(blocking) if blocking else None
|
||||
|
||||
|
||||
@router.post("/{measurement_id}/authorise", response_model=MeasurementResponse)
|
||||
async def authorise_measurement(
|
||||
measurement_id: int,
|
||||
action: MeasurementAuthorisation,
|
||||
user: User = Depends(require_measurement_tec),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Let an out-of-tolerance value stand, on the capoturno's name.
|
||||
|
||||
Credentials go to this endpoint rather than to a separate check because the
|
||||
approval has to end up attached to the measurement. A check whose answer is
|
||||
thrown away is what this point had before: a modal that closed.
|
||||
"""
|
||||
result = await db.execute(
|
||||
select(Measurement).where(Measurement.id == measurement_id)
|
||||
)
|
||||
measurement = result.scalar_one_or_none()
|
||||
if measurement is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Measurement not found",
|
||||
)
|
||||
if measurement.pass_fail != "fail":
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
detail="This measurement is within tolerance: there is nothing to authorise",
|
||||
)
|
||||
|
||||
supervisor = await auth_service.authorise_supervisor(
|
||||
db, action.supervisor_username, action.supervisor_password,
|
||||
)
|
||||
measurement = await measurement_service.authorise_measurement(
|
||||
db, measurement, supervisor,
|
||||
)
|
||||
return MeasurementResponse.model_validate(measurement)
|
||||
|
||||
|
||||
@router.post("/batch", response_model=list[MeasurementResponse])
|
||||
async def create_measurement_batch(
|
||||
data: MeasurementBatchCreate,
|
||||
|
||||
@@ -20,7 +20,7 @@ from src.backend.models.api.production import (
|
||||
)
|
||||
from src.backend.models.orm.production import ProductionRun
|
||||
from src.backend.models.orm.user import User
|
||||
from src.backend.services import production_service
|
||||
from src.backend.services import auth_service, production_service
|
||||
|
||||
router = APIRouter(prefix="/api/production-runs", tags=["production"])
|
||||
|
||||
@@ -124,7 +124,7 @@ async def pause_production_run(
|
||||
):
|
||||
"""Fermo linea - requires a supervisor."""
|
||||
run = await production_service.get_run(db, run_id)
|
||||
supervisor = await production_service.authorise_supervisor(
|
||||
supervisor = await auth_service.authorise_supervisor(
|
||||
db, action.supervisor_username, action.supervisor_password,
|
||||
)
|
||||
run = await production_service.pause_run(db, run, user, supervisor, note=action.note)
|
||||
@@ -140,7 +140,7 @@ async def resume_production_run(
|
||||
):
|
||||
"""Restart a stopped line - requires a supervisor."""
|
||||
run = await production_service.get_run(db, run_id)
|
||||
supervisor = await production_service.authorise_supervisor(
|
||||
supervisor = await auth_service.authorise_supervisor(
|
||||
db, action.supervisor_username, action.supervisor_password,
|
||||
)
|
||||
run = await production_service.resume_run(db, run, user, supervisor, note=action.note)
|
||||
@@ -156,7 +156,7 @@ async def close_production_run(
|
||||
):
|
||||
"""Fine produzione - requires a supervisor. Stops the timer for good."""
|
||||
run = await production_service.get_run(db, run_id)
|
||||
supervisor = await production_service.authorise_supervisor(
|
||||
supervisor = await auth_service.authorise_supervisor(
|
||||
db, action.supervisor_username, action.supervisor_password,
|
||||
)
|
||||
run = await production_service.close_run(db, run, user, supervisor, note=action.note)
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
"""record who authorised an out-of-tolerance measurement
|
||||
|
||||
The supervisor gate existed on screen and nowhere else: the modal opened, the
|
||||
credentials were checked, the modal closed, and nothing was written down. Nothing
|
||||
depended on the answer either, so a value outside tolerance could be left behind
|
||||
by dismissing the modal.
|
||||
|
||||
These two columns are what the rule now stands on. A failed measurement with no
|
||||
supervisor on it is a measurement waiting for one, and while one is waiting the
|
||||
operator cannot move to the next quote.
|
||||
|
||||
Existing rows are left null. Backfilling an authorisation that never happened
|
||||
would be inventing an audit record; the fails already in the database belong to
|
||||
production runs that are over, and nothing is waiting on them.
|
||||
|
||||
Revision ID: 010_meas_authorisation
|
||||
Revises: 009_recipe_rules
|
||||
Create Date: 2026-07-28
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
revision: str = '010_meas_authorisation'
|
||||
down_revision: Union[str, None] = '009_recipe_rules'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
with op.batch_alter_table('measurements') as batch:
|
||||
batch.add_column(sa.Column('supervisor_id', sa.Integer(), nullable=True))
|
||||
batch.add_column(sa.Column('authorised_at', sa.DateTime(), nullable=True))
|
||||
batch.create_foreign_key(
|
||||
'fk_measurements_supervisor_id', 'users', ['supervisor_id'], ['id'],
|
||||
)
|
||||
batch.create_index(
|
||||
'ix_measurements_supervisor_id', ['supervisor_id'],
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
with op.batch_alter_table('measurements') as batch:
|
||||
batch.drop_index('ix_measurements_supervisor_id')
|
||||
batch.drop_constraint('fk_measurements_supervisor_id', type_='foreignkey')
|
||||
batch.drop_column('authorised_at')
|
||||
batch.drop_column('supervisor_id')
|
||||
@@ -39,6 +39,10 @@ class MeasurementResponse(BaseModel):
|
||||
input_method: str
|
||||
input_duration_ms: Optional[int] = None
|
||||
production_run_id: Optional[int] = None
|
||||
# Who let this value stand, when it was out of tolerance. Null on a value in
|
||||
# tolerance - and on one that is not and is still waiting for an answer.
|
||||
supervisor_id: Optional[int] = None
|
||||
authorised_at: Optional[datetime] = None
|
||||
measured_at: datetime
|
||||
synced_to_csv: bool
|
||||
|
||||
@@ -52,6 +56,13 @@ class MeasurementListResponse(BaseModel):
|
||||
pages: int
|
||||
|
||||
|
||||
class MeasurementAuthorisation(BaseModel):
|
||||
"""Credentials of the supervisor allowing an out-of-tolerance value to stand."""
|
||||
|
||||
supervisor_username: str = Field(..., min_length=1)
|
||||
supervisor_password: str = Field(..., min_length=1)
|
||||
|
||||
|
||||
class MeasurementQuery(BaseModel):
|
||||
"""Schema for measurement query filters."""
|
||||
recipe_id: Optional[int] = None
|
||||
|
||||
@@ -59,6 +59,16 @@ class Measurement(Base):
|
||||
index=True,
|
||||
)
|
||||
|
||||
# Who let an out-of-tolerance value stand, and when. Null on a value that is
|
||||
# within tolerance, and null on one that is not and has not been authorised -
|
||||
# which is exactly the state that stops the operator moving on to the next
|
||||
# quote. Before this the supervisor's approval was a modal that closed: nothing
|
||||
# recorded it, and nothing depended on it.
|
||||
supervisor_id: Mapped[Optional[int]] = mapped_column(
|
||||
Integer, ForeignKey("users.id"), nullable=True, index=True
|
||||
)
|
||||
authorised_at: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True)
|
||||
|
||||
# Timestamp
|
||||
measured_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, nullable=False, server_default=func.now(), index=True
|
||||
|
||||
@@ -3,6 +3,7 @@ import secrets
|
||||
from datetime import datetime
|
||||
|
||||
import bcrypt
|
||||
from fastapi import HTTPException, status
|
||||
from sqlalchemy import select, update
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
@@ -39,6 +40,31 @@ async def authenticate_user(
|
||||
return user
|
||||
|
||||
|
||||
async def authorise_supervisor(db: AsyncSession, username: str, password: str) -> User:
|
||||
"""Check the credentials of the supervisor authorising something.
|
||||
|
||||
Used wherever the shop floor needs a capoturno: a line stop, a closure, an
|
||||
out-of-tolerance value allowed to stand. Lives here rather than beside any one
|
||||
of them because it is the same question every time - who is this, and are they
|
||||
allowed to say yes.
|
||||
|
||||
Uses authenticate_user rather than a full login: logging in would rotate the
|
||||
supervisor's API key and knock out whatever session they have open elsewhere.
|
||||
"""
|
||||
supervisor = await authenticate_user(db, username, password)
|
||||
if supervisor is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid supervisor credentials",
|
||||
)
|
||||
if not supervisor.has_role("Supervisor") and not supervisor.is_admin:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="User is not authorised as supervisor",
|
||||
)
|
||||
return supervisor
|
||||
|
||||
|
||||
async def login_user(db: AsyncSession, user: User) -> str:
|
||||
"""Generate API key and update last_login for user."""
|
||||
api_key = generate_api_key()
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
"""Measurement service - pass/fail calculation, data storage."""
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
|
||||
from fastapi import HTTPException, status
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy import func, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from src.backend.models.orm.measurement import Measurement
|
||||
from src.backend.models.orm.recipe import Recipe, RecipeVersion
|
||||
from src.backend.models.orm.task import RecipeSubtask
|
||||
from src.backend.models.orm.user import User
|
||||
|
||||
|
||||
def calculate_pass_fail(
|
||||
@@ -83,6 +85,67 @@ def _enforce_recipe_rules(
|
||||
)
|
||||
|
||||
|
||||
def _scope(query, version_id: int, measured_by: int, production_run_id: int | None):
|
||||
"""Narrow a measurement query to the run in progress, or to this operator's work.
|
||||
|
||||
Inside a production the run is the boundary. Outside one - a recipe executed on
|
||||
its own - the boundary is the version and the person doing it, so two operators
|
||||
on the same recipe do not block each other.
|
||||
"""
|
||||
if production_run_id is not None:
|
||||
return query.where(Measurement.production_run_id == production_run_id)
|
||||
return query.where(
|
||||
Measurement.version_id == version_id,
|
||||
Measurement.measured_by == measured_by,
|
||||
Measurement.production_run_id.is_(None),
|
||||
)
|
||||
|
||||
|
||||
async def pending_authorisation(
|
||||
db: AsyncSession,
|
||||
version_id: int,
|
||||
measured_by: int,
|
||||
production_run_id: int | None = None,
|
||||
) -> Measurement | None:
|
||||
"""The out-of-tolerance measurement that is holding everything up, if any.
|
||||
|
||||
Only the *latest* reading of each quote counts. Measuring the same quote again
|
||||
is allowed - the caliper slips, the piece is reseated - and a second reading
|
||||
within tolerance releases the block. What is not allowed is moving on to the
|
||||
next quote while the one in hand is out of tolerance and nobody has said so.
|
||||
|
||||
How many attempts that permits is point 4's business, not this one's. Every
|
||||
reading stays in the record either way.
|
||||
"""
|
||||
latest_per_subtask = _scope(
|
||||
select(func.max(Measurement.id)),
|
||||
version_id, measured_by, production_run_id,
|
||||
).group_by(Measurement.subtask_id)
|
||||
|
||||
result = await db.execute(
|
||||
select(Measurement)
|
||||
.where(
|
||||
Measurement.id.in_(latest_per_subtask),
|
||||
Measurement.pass_fail == "fail",
|
||||
Measurement.supervisor_id.is_(None),
|
||||
)
|
||||
.order_by(Measurement.id)
|
||||
.limit(1)
|
||||
)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
|
||||
async def authorise_measurement(
|
||||
db: AsyncSession, measurement: Measurement, supervisor: User,
|
||||
) -> Measurement:
|
||||
"""Let an out-of-tolerance value stand, on the supervisor's name."""
|
||||
measurement.supervisor_id = supervisor.id
|
||||
measurement.authorised_at = datetime.now()
|
||||
await db.flush()
|
||||
await db.refresh(measurement)
|
||||
return measurement
|
||||
|
||||
|
||||
async def save_measurement(
|
||||
db: AsyncSession,
|
||||
subtask_id: int,
|
||||
@@ -107,6 +170,21 @@ async def save_measurement(
|
||||
recipe = await recipe_of_version(db, version_id)
|
||||
_enforce_recipe_rules(recipe, lot_number, serial_number, input_method)
|
||||
|
||||
# No moving on while a quote is out of tolerance and unauthorised. Measuring
|
||||
# that same quote again is the way out that does not need the capoturno; the
|
||||
# next quote is not.
|
||||
blocking = await pending_authorisation(
|
||||
db, version_id, measured_by, production_run_id,
|
||||
)
|
||||
if blocking is not None and blocking.subtask_id != subtask_id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
detail=(
|
||||
"A measurement is out of tolerance and awaiting the supervisor: "
|
||||
"authorise it or measure that quote again before going on"
|
||||
),
|
||||
)
|
||||
|
||||
pass_fail, deviation = calculate_pass_fail(value, subtask)
|
||||
|
||||
measurement = Measurement(
|
||||
|
||||
@@ -55,6 +55,11 @@ HEADER = [
|
||||
"input_duration_ms",
|
||||
"measured_by",
|
||||
"measured_at",
|
||||
# A value outside tolerance that was allowed to stand carries the name of who
|
||||
# allowed it. Without these two columns the file would show the failure and
|
||||
# not the decision, which is the half an auditor asks about.
|
||||
"authorised_by",
|
||||
"authorised_at",
|
||||
]
|
||||
|
||||
|
||||
@@ -126,6 +131,8 @@ async def build_statistics_csv(db: AsyncSession, run: ProductionRun) -> str:
|
||||
"" if measurement.input_duration_ms is None else measurement.input_duration_ms,
|
||||
measurement.measured_by,
|
||||
measurement.measured_at.isoformat() if measurement.measured_at else "",
|
||||
measurement.supervisor_id or "",
|
||||
measurement.authorised_at.isoformat() if measurement.authorised_at else "",
|
||||
])
|
||||
|
||||
return output.getvalue()
|
||||
|
||||
@@ -26,7 +26,9 @@ from src.backend.models.orm.recipe import Recipe, RecipeVersion
|
||||
from src.backend.models.orm.station import Station
|
||||
from src.backend.models.orm.task import MEASURING_TASK_TYPES, RecipeTask
|
||||
from src.backend.models.orm.user import User
|
||||
from src.backend.services import auth_service, production_export_service
|
||||
from src.backend.services import (
|
||||
auth_service, measurement_service, production_export_service,
|
||||
)
|
||||
|
||||
|
||||
def _now() -> datetime:
|
||||
@@ -137,27 +139,9 @@ async def list_run_events(db: AsyncSession, run_id: int) -> list[ProductionEvent
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Supervisor authorisation
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
async def authorise_supervisor(db: AsyncSession, username: str, password: str) -> User:
|
||||
"""Check the credentials of the supervisor authorising a stop or a close.
|
||||
|
||||
Uses authenticate_user rather than a full login: logging in would rotate the
|
||||
supervisor's API key and knock out whatever session they have open elsewhere.
|
||||
"""
|
||||
supervisor = await auth_service.authenticate_user(db, username, password)
|
||||
if supervisor is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid supervisor credentials",
|
||||
)
|
||||
if not supervisor.has_role("Supervisor") and not supervisor.is_admin:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="User is not authorised as supervisor",
|
||||
)
|
||||
return supervisor
|
||||
# The capoturno check lives in auth_service, next to the rest of the credential
|
||||
# handling: a line stop, a closure and an out-of-tolerance value all ask the same
|
||||
# question, and only one of the three is about production.
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -320,6 +304,21 @@ async def complete_cycle(
|
||||
"""
|
||||
_require_measurable(run)
|
||||
|
||||
# A cycle that contains a quote out of tolerance is not a finished cycle. This
|
||||
# is the same gate as on the next measurement: closing the cycle would be
|
||||
# another way of walking past it.
|
||||
blocking = await measurement_service.pending_authorisation(
|
||||
db, run.version_id, user.id, run.id,
|
||||
)
|
||||
if blocking is not None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
detail=(
|
||||
"A measurement is out of tolerance and awaiting the supervisor: "
|
||||
"the cycle cannot be closed until it is authorised"
|
||||
),
|
||||
)
|
||||
|
||||
measure_ids = await measurement_task_ids(db, run.version_id)
|
||||
closes_cycle = (
|
||||
task_id is None or task_id not in measure_ids or task_id == measure_ids[-1]
|
||||
|
||||
@@ -0,0 +1,411 @@
|
||||
"""Point 5: a quote out of tolerance stops the line until someone says otherwise.
|
||||
|
||||
The gate existed on screen and nowhere else - a modal that opened, checked a
|
||||
password and closed, while the measurement had already been saved. Dismissing it
|
||||
was enough to carry on. These tests are about the part that cannot be dismissed.
|
||||
"""
|
||||
from httpx import AsyncClient
|
||||
from sqlalchemy import select
|
||||
|
||||
from src.backend.models.orm.measurement import Measurement
|
||||
from src.backend.models.orm.recipe import RecipeVersion
|
||||
from src.backend.models.orm.station import Station
|
||||
from src.backend.models.orm.task import RecipeSubtask, RecipeTask
|
||||
from src.backend.services import auth_service
|
||||
from src.backend.tests.conftest import auth_headers, create_test_recipe
|
||||
|
||||
GOOD = 10.0 # inside every limit of the fixture quote
|
||||
OUT = 99.0 # far outside
|
||||
|
||||
|
||||
async def _supervisor(db_session, username="capo-tol", password="TurnoPwd1"):
|
||||
user = await auth_service.create_user(
|
||||
db_session, username=username, password=password,
|
||||
display_name="Capo Turno", roles=["Supervisor"],
|
||||
)
|
||||
await db_session.commit()
|
||||
return user, password
|
||||
|
||||
|
||||
async def _two_quotes(db_session, recipe_id: int):
|
||||
"""The fixture recipe plus a second quote, so there is a 'next' one."""
|
||||
version = (await db_session.execute(
|
||||
select(RecipeVersion).where(
|
||||
RecipeVersion.recipe_id == recipe_id,
|
||||
RecipeVersion.is_current == True, # noqa: E712
|
||||
)
|
||||
)).scalar_one()
|
||||
task = (await db_session.execute(
|
||||
select(RecipeTask).where(RecipeTask.version_id == version.id)
|
||||
)).scalars().first()
|
||||
first = (await db_session.execute(
|
||||
select(RecipeSubtask).where(RecipeSubtask.task_id == task.id)
|
||||
)).scalars().first()
|
||||
|
||||
second = RecipeSubtask(
|
||||
task_id=task.id, marker_number=2, description="Seconda quota",
|
||||
nominal=10.0, utl=10.5, uwl=10.3, lwl=9.7, ltl=9.5, unit="mm",
|
||||
)
|
||||
db_session.add(second)
|
||||
await db_session.commit()
|
||||
await db_session.refresh(second)
|
||||
return version.id, first, second
|
||||
|
||||
|
||||
async def _measure(client, user, subtask_id, version_id, value, run_id=None):
|
||||
body = {"subtask_id": subtask_id, "version_id": version_id, "value": value}
|
||||
if run_id is not None:
|
||||
body["production_run_id"] = run_id
|
||||
return await client.post(
|
||||
"/api/measurements/", headers=auth_headers(user), json=body,
|
||||
)
|
||||
|
||||
|
||||
async def _pending(client, user, version_id, run_id=None):
|
||||
params = {"version_id": version_id}
|
||||
if run_id is not None:
|
||||
params["production_run_id"] = run_id
|
||||
return await client.get(
|
||||
"/api/measurements/pending-authorisation",
|
||||
headers=auth_headers(user), params=params,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# The block
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
async def test_next_quote_is_refused_while_one_is_out_of_tolerance(
|
||||
client: AsyncClient, maker_user, measurement_tec_user, db_session,
|
||||
):
|
||||
recipe = await create_test_recipe(db_session, maker_user.id, code="TOL-NEXT")
|
||||
version_id, first, second = await _two_quotes(db_session, recipe.id)
|
||||
|
||||
bad = await _measure(client, measurement_tec_user, first.id, version_id, OUT)
|
||||
assert bad.status_code == 200
|
||||
assert bad.json()["pass_fail"] == "fail"
|
||||
|
||||
refused = await _measure(client, measurement_tec_user, second.id, version_id, GOOD)
|
||||
assert refused.status_code == 409
|
||||
assert "supervisor" in refused.json()["detail"].lower()
|
||||
|
||||
|
||||
async def test_the_same_quote_can_be_measured_again(
|
||||
client: AsyncClient, maker_user, measurement_tec_user, db_session,
|
||||
):
|
||||
"""The caliper slips, the piece is reseated. Trying again is not going on."""
|
||||
recipe = await create_test_recipe(db_session, maker_user.id, code="TOL-AGAIN")
|
||||
version_id, first, _second = await _two_quotes(db_session, recipe.id)
|
||||
|
||||
await _measure(client, measurement_tec_user, first.id, version_id, OUT)
|
||||
again = await _measure(client, measurement_tec_user, first.id, version_id, OUT)
|
||||
assert again.status_code == 200, "rimisurare la stessa quota deve restare possibile"
|
||||
|
||||
|
||||
async def test_a_good_reading_of_the_same_quote_releases_the_block(
|
||||
client: AsyncClient, maker_user, measurement_tec_user, db_session,
|
||||
):
|
||||
"""Only the latest reading of a quote counts: it is in tolerance now."""
|
||||
recipe = await create_test_recipe(db_session, maker_user.id, code="TOL-FIXED")
|
||||
version_id, first, second = await _two_quotes(db_session, recipe.id)
|
||||
|
||||
await _measure(client, measurement_tec_user, first.id, version_id, OUT)
|
||||
await _measure(client, measurement_tec_user, first.id, version_id, GOOD)
|
||||
|
||||
ahead = await _measure(client, measurement_tec_user, second.id, version_id, GOOD)
|
||||
assert ahead.status_code == 200
|
||||
|
||||
# Both readings stay on record - how many are allowed is point 4's business.
|
||||
rows = (await db_session.execute(
|
||||
select(Measurement).where(Measurement.subtask_id == first.id)
|
||||
)).scalars().all()
|
||||
assert len(rows) == 2
|
||||
|
||||
|
||||
async def test_a_warning_does_not_block(
|
||||
client: AsyncClient, maker_user, measurement_tec_user, db_session,
|
||||
):
|
||||
"""Outside the warning limits but inside tolerance is still inside tolerance."""
|
||||
recipe = await create_test_recipe(db_session, maker_user.id, code="TOL-WARN")
|
||||
version_id, first, second = await _two_quotes(db_session, recipe.id)
|
||||
|
||||
warned = await _measure(client, measurement_tec_user, first.id, version_id, 10.4)
|
||||
assert warned.json()["pass_fail"] == "warning"
|
||||
|
||||
ahead = await _measure(client, measurement_tec_user, second.id, version_id, GOOD)
|
||||
assert ahead.status_code == 200
|
||||
|
||||
|
||||
async def test_pending_authorisation_names_the_blocking_quote(
|
||||
client: AsyncClient, maker_user, measurement_tec_user, db_session,
|
||||
):
|
||||
"""What the screen asks on load, so a reload is not a way past the gate."""
|
||||
recipe = await create_test_recipe(db_session, maker_user.id, code="TOL-PEND")
|
||||
version_id, first, _second = await _two_quotes(db_session, recipe.id)
|
||||
|
||||
assert (await _pending(client, measurement_tec_user, version_id)).json() is None
|
||||
|
||||
bad = (await _measure(
|
||||
client, measurement_tec_user, first.id, version_id, OUT,
|
||||
)).json()
|
||||
|
||||
pending = (await _pending(client, measurement_tec_user, version_id)).json()
|
||||
assert pending["id"] == bad["id"]
|
||||
assert pending["subtask_id"] == first.id
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# The authorisation
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
async def test_supervisor_authorisation_is_recorded_and_releases_the_block(
|
||||
client: AsyncClient, maker_user, measurement_tec_user, db_session,
|
||||
):
|
||||
recipe = await create_test_recipe(db_session, maker_user.id, code="TOL-AUTH")
|
||||
version_id, first, second = await _two_quotes(db_session, recipe.id)
|
||||
supervisor, password = await _supervisor(db_session)
|
||||
|
||||
bad = (await _measure(
|
||||
client, measurement_tec_user, first.id, version_id, OUT,
|
||||
)).json()
|
||||
|
||||
authorised = await client.post(
|
||||
f"/api/measurements/{bad['id']}/authorise",
|
||||
headers=auth_headers(measurement_tec_user),
|
||||
json={
|
||||
"supervisor_username": supervisor.username,
|
||||
"supervisor_password": password,
|
||||
},
|
||||
)
|
||||
assert authorised.status_code == 200, authorised.text
|
||||
body = authorised.json()
|
||||
# Who said yes is on the measurement itself, which is the whole point.
|
||||
assert body["supervisor_id"] == supervisor.id
|
||||
assert body["authorised_at"] is not None
|
||||
|
||||
ahead = await _measure(client, measurement_tec_user, second.id, version_id, GOOD)
|
||||
assert ahead.status_code == 200
|
||||
|
||||
|
||||
async def test_authorisation_refuses_someone_who_is_not_a_supervisor(
|
||||
client: AsyncClient, maker_user, measurement_tec_user, db_session,
|
||||
):
|
||||
recipe = await create_test_recipe(db_session, maker_user.id, code="TOL-NOSUP")
|
||||
version_id, first, second = await _two_quotes(db_session, recipe.id)
|
||||
|
||||
bad = (await _measure(
|
||||
client, measurement_tec_user, first.id, version_id, OUT,
|
||||
)).json()
|
||||
|
||||
resp = await client.post(
|
||||
f"/api/measurements/{bad['id']}/authorise",
|
||||
headers=auth_headers(measurement_tec_user),
|
||||
json={
|
||||
"supervisor_username": maker_user.username,
|
||||
"supervisor_password": "testpassword123",
|
||||
},
|
||||
)
|
||||
assert resp.status_code in (401, 403)
|
||||
|
||||
# And the block is still there.
|
||||
refused = await _measure(client, measurement_tec_user, second.id, version_id, GOOD)
|
||||
assert refused.status_code == 409
|
||||
|
||||
|
||||
async def test_authorisation_refuses_wrong_credentials(
|
||||
client: AsyncClient, maker_user, measurement_tec_user, db_session,
|
||||
):
|
||||
recipe = await create_test_recipe(db_session, maker_user.id, code="TOL-BADPW")
|
||||
version_id, first, _second = await _two_quotes(db_session, recipe.id)
|
||||
supervisor, _password = await _supervisor(db_session, username="capo-badpw")
|
||||
|
||||
bad = (await _measure(
|
||||
client, measurement_tec_user, first.id, version_id, OUT,
|
||||
)).json()
|
||||
resp = await client.post(
|
||||
f"/api/measurements/{bad['id']}/authorise",
|
||||
headers=auth_headers(measurement_tec_user),
|
||||
json={
|
||||
"supervisor_username": supervisor.username,
|
||||
"supervisor_password": "sbagliata",
|
||||
},
|
||||
)
|
||||
assert resp.status_code == 401
|
||||
|
||||
|
||||
async def test_there_is_nothing_to_authorise_on_a_good_measurement(
|
||||
client: AsyncClient, maker_user, measurement_tec_user, db_session,
|
||||
):
|
||||
recipe = await create_test_recipe(db_session, maker_user.id, code="TOL-GOOD")
|
||||
version_id, first, _second = await _two_quotes(db_session, recipe.id)
|
||||
supervisor, password = await _supervisor(db_session, username="capo-good")
|
||||
|
||||
good = (await _measure(
|
||||
client, measurement_tec_user, first.id, version_id, GOOD,
|
||||
)).json()
|
||||
resp = await client.post(
|
||||
f"/api/measurements/{good['id']}/authorise",
|
||||
headers=auth_headers(measurement_tec_user),
|
||||
json={
|
||||
"supervisor_username": supervisor.username,
|
||||
"supervisor_password": password,
|
||||
},
|
||||
)
|
||||
assert resp.status_code == 409
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# The production run
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
async def test_cycle_cannot_close_over_an_unauthorised_fail(
|
||||
client: AsyncClient, maker_user, measurement_tec_user, admin_user, db_session,
|
||||
):
|
||||
"""Closing the cycle would be another way of walking past the quote."""
|
||||
recipe = await create_test_recipe(db_session, maker_user.id, code="TOL-CYC")
|
||||
recipe.measurement_interval_minutes = 10
|
||||
station = Station(
|
||||
code="ST-TOL", name="Stazione", active=True, created_by=admin_user.id,
|
||||
)
|
||||
db_session.add(station)
|
||||
await db_session.commit()
|
||||
version_id, first, _second = await _two_quotes(db_session, recipe.id)
|
||||
|
||||
run = (await client.post(
|
||||
"/api/production-runs",
|
||||
headers=auth_headers(measurement_tec_user),
|
||||
json={"station_code": station.code, "recipe_id": recipe.id},
|
||||
)).json()
|
||||
|
||||
await _measure(
|
||||
client, measurement_tec_user, first.id, version_id, OUT, run_id=run["id"],
|
||||
)
|
||||
|
||||
refused = await client.post(
|
||||
f"/api/production-runs/{run['id']}/cycle",
|
||||
headers=auth_headers(measurement_tec_user),
|
||||
)
|
||||
assert refused.status_code == 409
|
||||
|
||||
supervisor, password = await _supervisor(db_session, username="capo-cyc")
|
||||
blocking = (await _pending(
|
||||
client, measurement_tec_user, version_id, run_id=run["id"],
|
||||
)).json()
|
||||
await client.post(
|
||||
f"/api/measurements/{blocking['id']}/authorise",
|
||||
headers=auth_headers(measurement_tec_user),
|
||||
json={
|
||||
"supervisor_username": supervisor.username,
|
||||
"supervisor_password": password,
|
||||
},
|
||||
)
|
||||
|
||||
allowed = await client.post(
|
||||
f"/api/production-runs/{run['id']}/cycle",
|
||||
headers=auth_headers(measurement_tec_user),
|
||||
)
|
||||
assert allowed.status_code == 200
|
||||
assert allowed.json()["cycle_count"] == 1
|
||||
|
||||
|
||||
async def test_the_statistics_file_carries_the_decision(
|
||||
client: AsyncClient, maker_user, measurement_tec_user, admin_user, db_session,
|
||||
tmp_path, monkeypatch,
|
||||
):
|
||||
"""The file has to show the failure and who let it stand, not just the failure."""
|
||||
from src.backend.config import settings
|
||||
|
||||
monkeypatch.setattr(
|
||||
type(settings), "upload_path", property(lambda self: tmp_path), raising=False,
|
||||
)
|
||||
|
||||
recipe = await create_test_recipe(db_session, maker_user.id, code="TOL-CSV")
|
||||
station = Station(
|
||||
code="ST-CSV", name="Stazione", active=True, created_by=admin_user.id,
|
||||
)
|
||||
db_session.add(station)
|
||||
await db_session.commit()
|
||||
version_id, first, _second = await _two_quotes(db_session, recipe.id)
|
||||
supervisor, password = await _supervisor(db_session, username="capo-csv")
|
||||
creds = {
|
||||
"supervisor_username": supervisor.username, "supervisor_password": password,
|
||||
}
|
||||
|
||||
run = (await client.post(
|
||||
"/api/production-runs",
|
||||
headers=auth_headers(measurement_tec_user),
|
||||
json={"station_code": station.code, "recipe_id": recipe.id},
|
||||
)).json()
|
||||
bad = (await _measure(
|
||||
client, measurement_tec_user, first.id, version_id, OUT, run_id=run["id"],
|
||||
)).json()
|
||||
await client.post(
|
||||
f"/api/measurements/{bad['id']}/authorise",
|
||||
headers=auth_headers(measurement_tec_user), json=creds,
|
||||
)
|
||||
|
||||
closed = (await client.post(
|
||||
f"/api/production-runs/{run['id']}/close",
|
||||
headers=auth_headers(measurement_tec_user), json=creds,
|
||||
)).json()
|
||||
|
||||
text = (tmp_path / closed["statistics_path"]).read_text(encoding="utf-8-sig")
|
||||
header, row = [line for line in text.splitlines() if line.strip()]
|
||||
assert "authorised_by" in header and "authorised_at" in header
|
||||
assert str(supervisor.id) in row
|
||||
|
||||
|
||||
async def test_a_fail_from_another_production_does_not_block_this_one(
|
||||
client: AsyncClient, maker_user, measurement_tec_user, admin_user, db_session,
|
||||
tmp_path, monkeypatch,
|
||||
):
|
||||
"""The run is the boundary: an old production must not stop a new one."""
|
||||
# Closing a run writes the statistics file; pointed at tmp_path so the test
|
||||
# does not leave one in the repository's uploads directory.
|
||||
from src.backend.config import settings
|
||||
|
||||
monkeypatch.setattr(
|
||||
type(settings), "upload_path", property(lambda self: tmp_path), raising=False,
|
||||
)
|
||||
|
||||
recipe = await create_test_recipe(db_session, maker_user.id, code="TOL-SCOPE")
|
||||
station = Station(
|
||||
code="ST-SCOPE", name="Stazione", active=True, created_by=admin_user.id,
|
||||
)
|
||||
db_session.add(station)
|
||||
await db_session.commit()
|
||||
version_id, first, second = await _two_quotes(db_session, recipe.id)
|
||||
|
||||
first_run = (await client.post(
|
||||
"/api/production-runs",
|
||||
headers=auth_headers(measurement_tec_user),
|
||||
json={"station_code": station.code, "recipe_id": recipe.id},
|
||||
)).json()
|
||||
await _measure(
|
||||
client, measurement_tec_user, first.id, version_id, OUT,
|
||||
run_id=first_run["id"],
|
||||
)
|
||||
|
||||
supervisor, password = await _supervisor(db_session, username="capo-scope")
|
||||
await client.post(
|
||||
f"/api/production-runs/{first_run['id']}/close",
|
||||
headers=auth_headers(measurement_tec_user),
|
||||
json={
|
||||
"supervisor_username": supervisor.username,
|
||||
"supervisor_password": password,
|
||||
},
|
||||
)
|
||||
|
||||
second_run = (await client.post(
|
||||
"/api/production-runs",
|
||||
headers=auth_headers(measurement_tec_user),
|
||||
json={"station_code": station.code, "recipe_id": recipe.id},
|
||||
)).json()
|
||||
resp = await _measure(
|
||||
client, measurement_tec_user, second.id, version_id, GOOD,
|
||||
run_id=second_run["id"],
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
@@ -397,31 +397,53 @@ def save_measurement():
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Route: Validate supervisor credentials (AJAX)
|
||||
# Routes: Out-of-tolerance authorisation (point 5)
|
||||
# ---------------------------------------------------------------------------
|
||||
@measure_bp.route("/validate-supervisor", methods=["POST"])
|
||||
@measure_bp.route("/api/measurements/pending-authorisation", methods=["GET"])
|
||||
@login_required
|
||||
@role_required("MeasurementTec")
|
||||
def validate_supervisor():
|
||||
"""Validate supervisor (capoturno) credentials for out-of-tolerance authorization."""
|
||||
def api_pending_authorisation():
|
||||
"""Proxy: the out-of-tolerance measurement holding this operator up, or null.
|
||||
|
||||
Asked when the measurement screen loads, so reloading the page finds the same
|
||||
block the server enforces rather than a clean slate.
|
||||
"""
|
||||
params = {"version_id": request.args.get("version_id")}
|
||||
run_id = request.args.get("production_run_id")
|
||||
if run_id:
|
||||
params["production_run_id"] = run_id
|
||||
|
||||
resp = api_client.get("/api/measurements/pending-authorisation", params=params)
|
||||
if isinstance(resp, dict) and resp.get("error"):
|
||||
return jsonify(resp), resp.get("status_code", 500)
|
||||
return jsonify(resp), 200
|
||||
|
||||
|
||||
@measure_bp.route("/api/measurements/<int:measurement_id>/authorise", methods=["POST"])
|
||||
@login_required
|
||||
@role_required("MeasurementTec")
|
||||
def api_authorise_measurement(measurement_id: int):
|
||||
"""Proxy: the capoturno lets an out-of-tolerance value stand.
|
||||
|
||||
The credentials go to the endpoint that records the approval on the
|
||||
measurement. Checking them separately, as this used to, left the approval
|
||||
written down nowhere and nothing depending on it.
|
||||
"""
|
||||
data = request.get_json(silent=True) or {}
|
||||
username = data.get("username", "").strip()
|
||||
password = data.get("password", "")
|
||||
|
||||
username = (data.get("supervisor_username") or "").strip()
|
||||
password = data.get("supervisor_password") or ""
|
||||
if not username or not password:
|
||||
return jsonify({"error": True, "detail": _("Username e password richiesti")}), 400
|
||||
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", {})
|
||||
is_supervisor = "Supervisor" in (user.get("roles") or [])
|
||||
if not (is_supervisor or 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
|
||||
resp = api_client.post(
|
||||
f"/api/measurements/{measurement_id}/authorise",
|
||||
data={"supervisor_username": username, "supervisor_password": password},
|
||||
)
|
||||
if isinstance(resp, dict) and resp.get("error"):
|
||||
return jsonify(resp), resp.get("status_code", 500)
|
||||
return jsonify(resp), 200
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@@ -493,6 +493,32 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# ================================================================
|
||||
QUOTA FUORI TOLLERANZA — nothing moves from here without the capoturno or a
|
||||
new reading of this same quote. Kept on screen after the modal is dismissed,
|
||||
because the block outlives the modal: the server refuses the next value.
|
||||
================================================================ #}
|
||||
<div x-show="blockedByTolerance"
|
||||
x-transition
|
||||
x-cloak
|
||||
class="shrink-0 bg-red-50 dark:bg-red-900/30 border-t-2 border-red-500 px-4 py-2">
|
||||
<div class="flex flex-wrap items-center justify-center gap-x-3 gap-y-1">
|
||||
<svg class="w-5 h-5 text-red-600 shrink-0" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"/>
|
||||
</svg>
|
||||
<span class="text-sm font-semibold text-red-900 dark:text-red-100">
|
||||
{{ _('Quota fuori tolleranza') }}
|
||||
</span>
|
||||
<span class="text-xs text-red-800 dark:text-red-200">
|
||||
{{ _('serve il capoturno, oppure una nuova misura della stessa quota') }}
|
||||
</span>
|
||||
<button @click="openSupervisorModal('out_of_tolerance')"
|
||||
class="btn text-xs py-1 px-2.5 bg-red-600 hover:bg-red-700 text-white">
|
||||
{{ _('Autorizza') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# ================================================================
|
||||
LINEA FERMA — the countdown is frozen, not merely hidden. Stated plainly
|
||||
because a stopped line that looks like a running one is how a measurement
|
||||
@@ -826,9 +852,13 @@
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2">
|
||||
{# Out of tolerance, the way out of this modal is measuring the quote
|
||||
again - not cancelling. Closing it changes nothing: the quote stays
|
||||
blocked, and the banner behind says so. #}
|
||||
<button @click="closeSupervisorModal()"
|
||||
class="btn btn-secondary flex-1 text-sm">
|
||||
{{ _('Annulla') }}
|
||||
<span x-show="supervisorAction !== 'out_of_tolerance'">{{ _('Annulla') }}</span>
|
||||
<span x-show="supervisorAction === 'out_of_tolerance'">{{ _('Rimisura la quota') }}</span>
|
||||
</button>
|
||||
<button @click="submitSupervisor()"
|
||||
:disabled="!supervisorUsername || !supervisorPassword || supervisorValidating"
|
||||
@@ -909,7 +939,10 @@ function taskExecute() {
|
||||
supervisorPassword: '',
|
||||
supervisorError: '',
|
||||
supervisorValidating: false,
|
||||
pendingAdvance: false,
|
||||
/* The out-of-tolerance measurement waiting for the capoturno:
|
||||
{ id, subtask_id }, or null. While it is set the operator stays on that
|
||||
quote - the server refuses the next one anyway. */
|
||||
pendingFail: null,
|
||||
|
||||
// ---- Value from numpad / caliper ----
|
||||
currentValue: null,
|
||||
@@ -994,11 +1027,40 @@ function taskExecute() {
|
||||
},
|
||||
|
||||
// ---- Init ----
|
||||
init() {
|
||||
async init() {
|
||||
this.subtasks.sort((a, b) => (a.order_index || 0) - (b.order_index || 0));
|
||||
this.inputStartedAt = Date.now();
|
||||
// Rejoin whatever production is already open at this station.
|
||||
this.loadProductionRun();
|
||||
await this.loadProductionRun();
|
||||
await this.loadPendingAuthorisation();
|
||||
},
|
||||
|
||||
/* Is a quote already waiting for the capoturno?
|
||||
|
||||
Reloading the page was the simplest way past the old gate. It is not one
|
||||
now - the server refuses the next measurement either way - so the screen
|
||||
picks the block back up as soon as it opens, on the quote it belongs to. */
|
||||
async loadPendingAuthorisation() {
|
||||
if (!this.isMeasureTask || !this.subtasks.length) return;
|
||||
try {
|
||||
const params = new URLSearchParams({ version_id: this.task.version_id });
|
||||
if (this.productionRun) {
|
||||
params.set('production_run_id', this.productionRun.id);
|
||||
}
|
||||
const resp = await fetch(
|
||||
'{{ url_for("measure.api_pending_authorisation") }}?' + params.toString(),
|
||||
);
|
||||
if (!resp.ok) return;
|
||||
const pending = await resp.json();
|
||||
if (!pending || !pending.id) return;
|
||||
|
||||
this.pendingFail = { id: pending.id, subtask_id: pending.subtask_id };
|
||||
const idx = this.subtasks.findIndex(s => s.id === pending.subtask_id);
|
||||
if (idx !== -1) this.currentIndex = idx;
|
||||
this.openSupervisorModal('out_of_tolerance');
|
||||
} catch (e) {
|
||||
// Offline: the banner is missing, but so is the ability to save anything.
|
||||
}
|
||||
},
|
||||
|
||||
// ---- Production run: read the state back from the server ----
|
||||
@@ -1124,13 +1186,21 @@ function taskExecute() {
|
||||
// Pause to show result feedback
|
||||
await new Promise(r => setTimeout(r, 1000));
|
||||
|
||||
// Out-of-tolerance: block advancement, require supervisor
|
||||
// Out of tolerance: nothing moves until the capoturno says so, or until
|
||||
// this same quote is measured again and comes out inside tolerance.
|
||||
if (pf === 'fail') {
|
||||
this.pendingAdvance = true;
|
||||
this.pendingFail = { id: result.id, subtask_id: this.currentSubtask.id };
|
||||
this.openSupervisorModal('out_of_tolerance');
|
||||
return;
|
||||
}
|
||||
|
||||
// A second reading of the blocked quote, this time in tolerance: the quote
|
||||
// is in tolerance now, so the block goes. Both readings stay on record -
|
||||
// how many attempts are allowed is point 4's business, not this one's.
|
||||
if (this.pendingFail && this.pendingFail.subtask_id === this.currentSubtask.id) {
|
||||
this.pendingFail = null;
|
||||
}
|
||||
|
||||
// Check if all done
|
||||
if (this.completedCount >= this.totalSubtasks) {
|
||||
// START run (production not yet started): don't block with the
|
||||
@@ -1173,14 +1243,38 @@ function taskExecute() {
|
||||
}
|
||||
},
|
||||
|
||||
/* Out of tolerance and nobody has authorised it: the operator stays put.
|
||||
|
||||
This is the whole of point 5. The screen used to open a modal that could be
|
||||
dismissed, and the measurement was already saved by then, so a value outside
|
||||
tolerance could be left behind by clicking the backdrop. The server refuses
|
||||
the next measurement now; these checks only spare the operator the trip. */
|
||||
get blockedByTolerance() {
|
||||
return this.pendingFail !== null;
|
||||
},
|
||||
|
||||
/* Say why nothing is moving, and offer the way out. */
|
||||
refuseWhileBlocked() {
|
||||
// |tojson, not quotes: the Italian carries an apostrophe, and an apostrophe
|
||||
// inside a single-quoted literal is how every Alpine binding on the page dies.
|
||||
this.errorMessage = {{ _("Quota fuori tolleranza: serve l'autorizzazione del capoturno, oppure misurare di nuovo la quota")|tojson }};
|
||||
this.openSupervisorModal('out_of_tolerance');
|
||||
},
|
||||
|
||||
// ---- Navigation ----
|
||||
goToSubtask(index) {
|
||||
if (index >= 0 && index < this.totalSubtasks) {
|
||||
this.currentIndex = index;
|
||||
this.currentValue = null;
|
||||
this.errorMessage = '';
|
||||
this.inputStartedAt = Date.now();
|
||||
if (index < 0 || index >= this.totalSubtasks) return;
|
||||
// Moving to another quote is exactly what a blocked quote forbids; going
|
||||
// back to the blocked one to measure it again is not.
|
||||
if (this.blockedByTolerance
|
||||
&& this.subtasks[index].id !== this.pendingFail.subtask_id) {
|
||||
this.refuseWhileBlocked();
|
||||
return;
|
||||
}
|
||||
this.currentIndex = index;
|
||||
this.currentValue = null;
|
||||
this.errorMessage = '';
|
||||
this.inputStartedAt = Date.now();
|
||||
},
|
||||
|
||||
goToSubtaskByMarker(markerNumber) {
|
||||
@@ -1195,6 +1289,12 @@ function taskExecute() {
|
||||
it if it is the last. The server decides that - it knows the sequence - and
|
||||
the cycle count coming back tells us which of the two happened. */
|
||||
async confirmCycle() {
|
||||
// Closing the cycle would be another way of walking past a quote that is out
|
||||
// of tolerance. The server refuses it too.
|
||||
if (this.blockedByTolerance) {
|
||||
this.refuseWhileBlocked();
|
||||
return;
|
||||
}
|
||||
this.cycleConfirmed = true;
|
||||
this.showCompletionOverlay = false;
|
||||
|
||||
@@ -1441,6 +1541,10 @@ function taskExecute() {
|
||||
documental tasks that come before the measurement, the plain sequence
|
||||
applies - that is the run-up, not the loop. */
|
||||
goToNextTask() {
|
||||
if (this.blockedByTolerance) {
|
||||
this.refuseWhileBlocked();
|
||||
return;
|
||||
}
|
||||
if (this.productionStarted && this.isMeasurementTaskOfRun) {
|
||||
const next = this.nextMeasurementTaskId || this.measurementTaskIds[0];
|
||||
// A recipe with a single measurement task loops on the spot: reloading the
|
||||
@@ -1511,47 +1615,57 @@ function taskExecute() {
|
||||
|
||||
/* Route the modal's confirm to what the action actually does. Fermo linea,
|
||||
ripresa and fine produzione act on the production run; the out-of-tolerance
|
||||
gate stays a pure credential check. */
|
||||
one is recorded on the measurement itself. */
|
||||
async submitSupervisor() {
|
||||
if (this.supervisorAction === 'fermo_linea') return this.runSupervisedAction('pause');
|
||||
if (this.supervisorAction === 'ripresa') return this.runSupervisedAction('resume');
|
||||
if (this.supervisorAction === 'fine_produzione') return this.runSupervisedAction('close');
|
||||
return this.validateSupervisor();
|
||||
return this.authoriseOutOfTolerance();
|
||||
},
|
||||
|
||||
// ---- Validate supervisor credentials ----
|
||||
async validateSupervisor() {
|
||||
/* The capoturno lets an out-of-tolerance value stand.
|
||||
|
||||
The credentials go to the endpoint that writes the approval onto that
|
||||
measurement. It used to be a bare credential check whose answer was thrown
|
||||
away: nothing recorded who had said yes, and nothing depended on it, so
|
||||
closing the modal was enough to walk past a value out of tolerance. */
|
||||
async authoriseOutOfTolerance() {
|
||||
if (!this.pendingFail) {
|
||||
this.closeSupervisorModal();
|
||||
return;
|
||||
}
|
||||
this.supervisorError = '';
|
||||
this.supervisorValidating = true;
|
||||
|
||||
try {
|
||||
const csrfToken = document.querySelector('meta[name=csrf-token]')?.content || '';
|
||||
const resp = await fetch('{{ url_for("measure.validate_supervisor") }}', {
|
||||
const url = '{{ url_for("measure.api_authorise_measurement", measurement_id=0) }}'
|
||||
.replace('/0/', '/' + this.pendingFail.id + '/');
|
||||
const resp = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'X-CSRFToken': csrfToken },
|
||||
body: JSON.stringify({ username: this.supervisorUsername, password: this.supervisorPassword })
|
||||
body: JSON.stringify({
|
||||
supervisor_username: this.supervisorUsername,
|
||||
supervisor_password: this.supervisorPassword,
|
||||
}),
|
||||
});
|
||||
const data = await resp.json();
|
||||
const data = await resp.json().catch(() => null);
|
||||
|
||||
if (!resp.ok || data.error) {
|
||||
this.supervisorError = data.detail || '{{ _("Credenziali non valide o utente non autorizzato") }}';
|
||||
if (!resp.ok || (data && data.error)) {
|
||||
this.supervisorError = (data && 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;
|
||||
this.closeSupervisorModal();
|
||||
this.pendingFail = null;
|
||||
|
||||
if (this.supervisorAction === 'out_of_tolerance') {
|
||||
this.pendingAdvance = false;
|
||||
if (this.completedCount >= this.totalSubtasks) {
|
||||
this.showCompletionOverlay = true;
|
||||
} else {
|
||||
this.advanceToNext();
|
||||
}
|
||||
if (this.completedCount >= this.totalSubtasks) {
|
||||
this.showCompletionOverlay = true;
|
||||
} else {
|
||||
this.advanceToNext();
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
|
||||
@@ -70,6 +70,66 @@ class TestTaskComplete:
|
||||
assert b"productionClock(" in resp.data
|
||||
|
||||
|
||||
class TestOutOfTolerance:
|
||||
"""Proxies for point 5: the capoturno's answer has to reach the measurement."""
|
||||
|
||||
def test_authorise_forwards_the_credentials(self, logged_in_client, mock_api_client):
|
||||
mock_api_client.post.return_value = {"id": 9, "supervisor_id": 4}
|
||||
resp = logged_in_client.post(
|
||||
"/measure/api/measurements/9/authorise",
|
||||
json={"supervisor_username": "capo", "supervisor_password": "segreta"},
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
endpoint, kwargs = mock_api_client.post.call_args
|
||||
assert endpoint[0] == "/api/measurements/9/authorise"
|
||||
assert kwargs["data"]["supervisor_username"] == "capo"
|
||||
|
||||
def test_authorise_needs_credentials(self, logged_in_client, mock_api_client):
|
||||
resp = logged_in_client.post(
|
||||
"/measure/api/measurements/9/authorise", json={},
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
mock_api_client.post.assert_not_called()
|
||||
|
||||
def test_authorise_propagates_refusal(self, logged_in_client, mock_api_client):
|
||||
"""A operator who is not a supervisor must not authorise their own value."""
|
||||
mock_api_client.post.return_value = {
|
||||
"error": True, "status_code": 403, "detail": "not a supervisor",
|
||||
}
|
||||
resp = logged_in_client.post(
|
||||
"/measure/api/measurements/9/authorise",
|
||||
json={"supervisor_username": "tec", "supervisor_password": "x"},
|
||||
)
|
||||
assert resp.status_code == 403
|
||||
|
||||
def test_pending_passes_the_scope(self, logged_in_client, mock_api_client):
|
||||
mock_api_client.get.return_value = {"id": 9, "subtask_id": 21}
|
||||
resp = logged_in_client.get(
|
||||
"/measure/api/measurements/pending-authorisation"
|
||||
"?version_id=5&production_run_id=7",
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
assert resp.get_json()["id"] == 9
|
||||
endpoint, kwargs = mock_api_client.get.call_args
|
||||
assert endpoint[0] == "/api/measurements/pending-authorisation"
|
||||
assert kwargs["params"] == {"version_id": "5", "production_run_id": "7"}
|
||||
|
||||
def test_pending_without_a_run_omits_it(self, logged_in_client, mock_api_client):
|
||||
mock_api_client.get.return_value = None
|
||||
logged_in_client.get(
|
||||
"/measure/api/measurements/pending-authorisation?version_id=5",
|
||||
)
|
||||
assert mock_api_client.get.call_args[1]["params"] == {"version_id": "5"}
|
||||
|
||||
def test_routes_require_login(self, client):
|
||||
for method, url in (
|
||||
("get", "/measure/api/measurements/pending-authorisation?version_id=5"),
|
||||
("post", "/measure/api/measurements/9/authorise"),
|
||||
):
|
||||
resp = getattr(client, method)(url)
|
||||
assert resp.status_code in (302, 401), f"{method} {url}"
|
||||
|
||||
|
||||
class TestSaveMeasurement:
|
||||
"""POST /measure/save-measurement tests."""
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: TieMeasureFlow 1.0\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2026-07-28 20:16+0000\n"
|
||||
"POT-Creation-Date: 2026-07-28 20:48+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language: en\n"
|
||||
@@ -37,7 +37,7 @@ msgstr "Please log in to continue"
|
||||
msgid "Inserisci username e password"
|
||||
msgstr "Enter username and password"
|
||||
|
||||
#: blueprints/auth.py:81 blueprints/measure.py:417
|
||||
#: blueprints/auth.py:81
|
||||
msgid "Credenziali non valide"
|
||||
msgstr "Invalid credentials"
|
||||
|
||||
@@ -161,15 +161,11 @@ msgstr "Missing data: subtask_id, version_id and value are required"
|
||||
msgid "Errore nel salvataggio"
|
||||
msgstr "Error saving"
|
||||
|
||||
#: blueprints/measure.py:412 blueprints/measure.py:520
|
||||
#: blueprints/measure.py:437 blueprints/measure.py:542
|
||||
msgid "Username e password richiesti"
|
||||
msgstr "Username and password required"
|
||||
|
||||
#: blueprints/measure.py:422
|
||||
msgid "Utente non autorizzato (richiesto capoturno)"
|
||||
msgstr "User not authorized (shift supervisor required)"
|
||||
|
||||
#: blueprints/measure.py:441 blueprints/measure.py:458
|
||||
#: blueprints/measure.py:463 blueprints/measure.py:480
|
||||
#: templates/errors/station_not_configured.html:2
|
||||
#: templates/errors/station_not_configured.html:16
|
||||
msgid "Stazione non configurata"
|
||||
@@ -376,7 +372,7 @@ msgstr "Optional notes"
|
||||
#: templates/maker/task_editor.html:782 templates/maker/task_editor.html:901
|
||||
#: templates/maker/task_editor.html:966 templates/maker/task_editor.html:1071
|
||||
#: templates/measure/select_recipe.html:405
|
||||
#: templates/measure/task_execute.html:831
|
||||
#: templates/measure/task_execute.html:860
|
||||
msgid "Annulla"
|
||||
msgstr "Cancel"
|
||||
|
||||
@@ -521,7 +517,7 @@ msgstr "New User"
|
||||
#: 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:816
|
||||
#: templates/measure/task_execute.html:842
|
||||
msgid "Username"
|
||||
msgstr "Username"
|
||||
|
||||
@@ -573,7 +569,7 @@ msgstr "Username cannot be changed"
|
||||
|
||||
#: 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:821
|
||||
#: templates/measure/task_execute.html:847
|
||||
msgid "Password"
|
||||
msgstr "Password"
|
||||
|
||||
@@ -835,7 +831,7 @@ msgstr "Next measurement in"
|
||||
#: templates/components/production_clock.html:49
|
||||
#: templates/components/production_clock.html:62
|
||||
#: templates/measure/task_execute.html:463
|
||||
#: templates/measure/task_execute.html:516
|
||||
#: templates/measure/task_execute.html:542
|
||||
msgid "Misurazione in ritardo di"
|
||||
msgstr "Measurement overdue by"
|
||||
|
||||
@@ -844,7 +840,7 @@ msgid "Linea ferma — conto alla rovescia congelato a"
|
||||
msgstr "Line stopped — countdown frozen at"
|
||||
|
||||
#: templates/components/production_clock.html:62
|
||||
#: templates/measure/task_execute.html:510
|
||||
#: templates/measure/task_execute.html:536
|
||||
msgid "Linea ferma"
|
||||
msgstr "Line stopped"
|
||||
|
||||
@@ -854,17 +850,17 @@ msgid "Ciclo"
|
||||
msgstr "Cycle"
|
||||
|
||||
#: templates/components/production_clock.html:92
|
||||
#: templates/measure/task_execute.html:678
|
||||
#: templates/measure/task_execute.html:704
|
||||
msgid "È ora di misurare"
|
||||
msgstr "Time to measure"
|
||||
|
||||
#: templates/components/production_clock.html:95
|
||||
#: templates/measure/task_execute.html:680
|
||||
#: templates/measure/task_execute.html:706
|
||||
msgid "Ritorno alla misurazione tra"
|
||||
msgstr "Returning to the measurement in"
|
||||
|
||||
#: templates/components/production_clock.html:101
|
||||
#: templates/measure/task_execute.html:685
|
||||
#: templates/measure/task_execute.html:711
|
||||
msgid "Vai alla misura"
|
||||
msgstr "Go to the measurement"
|
||||
|
||||
@@ -917,8 +913,8 @@ msgstr "Preview"
|
||||
#: 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:171
|
||||
#: templates/measure/task_execute.html:589 templates/measure/task_list.html:2
|
||||
#: templates/measure/task_list.html:159
|
||||
#: templates/measure/task_execute.html:615 templates/measure/task_list.html:2
|
||||
#: templates/measure/task_list.html:180
|
||||
msgid "Task"
|
||||
msgstr "Task"
|
||||
|
||||
@@ -1094,8 +1090,8 @@ msgstr "Error during deletion"
|
||||
|
||||
# Recipe Selection Additional
|
||||
#: templates/maker/recipe_list.html:55 templates/measure/select_recipe.html:72
|
||||
#: templates/measure/task_execute.html:1262
|
||||
#: templates/measure/task_execute.html:1558
|
||||
#: templates/measure/task_execute.html:1362
|
||||
#: templates/measure/task_execute.html:1672
|
||||
msgid "Errore di connessione"
|
||||
msgstr "Connection Error"
|
||||
|
||||
@@ -1406,18 +1402,18 @@ msgid "Tipo"
|
||||
msgstr "Type"
|
||||
|
||||
#: templates/maker/task_editor.html:277 templates/maker/task_editor.html:543
|
||||
#: templates/measure/task_list.html:177
|
||||
#: templates/measure/task_list.html:198
|
||||
msgid "Nota"
|
||||
msgstr "Note"
|
||||
|
||||
#: templates/maker/task_editor.html:278 templates/maker/task_editor.html:544
|
||||
#: templates/measure/task_execute.html:309 templates/measure/task_list.html:175
|
||||
#: templates/measure/task_execute.html:309 templates/measure/task_list.html:196
|
||||
msgid "Misura"
|
||||
msgstr "Measure"
|
||||
|
||||
# Maker - Task Editor
|
||||
#: templates/maker/task_editor.html:279 templates/maker/task_editor.html:545
|
||||
#: templates/measure/task_list.html:176
|
||||
#: templates/measure/task_list.html:197
|
||||
msgid "Disegno"
|
||||
msgstr "Drawing"
|
||||
|
||||
@@ -1448,7 +1444,7 @@ msgstr "Drag to reorder"
|
||||
|
||||
#: templates/maker/task_editor.html:390
|
||||
#: templates/maker/version_history.html:188
|
||||
#: templates/measure/task_list.html:192
|
||||
#: templates/measure/task_list.html:213
|
||||
msgid "misurazioni"
|
||||
msgstr "measurements"
|
||||
|
||||
@@ -1714,7 +1710,7 @@ msgid "Misurazione aggiunta"
|
||||
msgstr "Measurement added"
|
||||
|
||||
#: templates/maker/task_editor.html:1685
|
||||
#: templates/measure/task_execute.html:1108
|
||||
#: templates/measure/task_execute.html:1170
|
||||
msgid "Errore nel salvataggio della misurazione"
|
||||
msgstr "Error saving measurement"
|
||||
|
||||
@@ -1782,16 +1778,16 @@ msgid "Seleziona Ricetta"
|
||||
msgstr "Select Recipe"
|
||||
|
||||
# Measure - Task List
|
||||
#: templates/measure/select_recipe.html:30
|
||||
#: templates/measure/select_recipe.html:30 templates/measure/task_list.html:86
|
||||
msgid "lotto"
|
||||
msgstr "lot"
|
||||
|
||||
#: templates/measure/select_recipe.html:31
|
||||
#: templates/measure/select_recipe.html:31 templates/measure/task_list.html:87
|
||||
msgid "seriale"
|
||||
msgstr "serial"
|
||||
|
||||
#: templates/measure/select_recipe.html:64
|
||||
#: templates/measure/select_recipe.html:277
|
||||
#: templates/measure/select_recipe.html:277 templates/measure/task_list.html:97
|
||||
msgid "Compila prima:"
|
||||
msgstr "Fill in first:"
|
||||
|
||||
@@ -1886,25 +1882,26 @@ msgstr "Search"
|
||||
#: templates/measure/task_complete.html:3
|
||||
#: templates/measure/task_complete.html:39
|
||||
#: templates/measure/task_execute.html:113
|
||||
#: templates/measure/task_execute.html:739
|
||||
#: templates/measure/task_execute.html:765
|
||||
#: templates/statistics/dashboard.html:139
|
||||
msgid "Riepilogo"
|
||||
msgstr "Summary"
|
||||
|
||||
#: templates/measure/task_complete.html:47
|
||||
#: templates/measure/task_execute.html:716
|
||||
#: templates/measure/task_execute.html:742
|
||||
msgid "Misurazioni Complete"
|
||||
msgstr "Measurements Complete"
|
||||
|
||||
# Measure - Task List
|
||||
#: templates/measure/task_complete.html:64
|
||||
#: templates/measure/task_complete.html:296 templates/measure/task_list.html:97
|
||||
#: templates/measure/task_complete.html:296
|
||||
#: templates/measure/task_list.html:118
|
||||
msgid "Lotto"
|
||||
msgstr "Lot"
|
||||
|
||||
#: templates/measure/task_complete.html:70
|
||||
#: templates/measure/task_complete.html:297
|
||||
#: templates/measure/task_list.html:109
|
||||
#: templates/measure/task_list.html:130
|
||||
msgid "Seriale"
|
||||
msgstr "Serial"
|
||||
|
||||
@@ -1913,12 +1910,12 @@ msgid "Totale"
|
||||
msgstr "Total"
|
||||
|
||||
#: templates/measure/task_complete.html:106
|
||||
#: templates/measure/task_execute.html:724
|
||||
#: templates/measure/task_execute.html:750
|
||||
msgid "Conformi"
|
||||
msgstr "Pass"
|
||||
|
||||
#: templates/measure/task_complete.html:123
|
||||
#: templates/measure/task_execute.html:728
|
||||
#: templates/measure/task_execute.html:754
|
||||
msgid "Attenzione"
|
||||
msgstr "Warning"
|
||||
|
||||
@@ -2114,153 +2111,172 @@ msgstr "Recorded"
|
||||
msgid "Task di misura senza quote configurate: la ricetta è incompleta"
|
||||
msgstr "Measurement task with no quotes configured: the recipe is incomplete"
|
||||
|
||||
#: templates/measure/task_execute.html:515
|
||||
msgid "Il conto alla rovescia è congelato a"
|
||||
msgstr "The countdown is frozen at"
|
||||
#: templates/measure/task_execute.html:510
|
||||
msgid "Quota fuori tolleranza"
|
||||
msgstr "Measurement out of tolerance"
|
||||
|
||||
#: templates/measure/task_execute.html:518
|
||||
msgid "serve il capoturno per riprendere"
|
||||
msgstr "the supervisor must authorise the restart"
|
||||
#: templates/measure/task_execute.html:513
|
||||
msgid "serve il capoturno, oppure una nuova misura della stessa quota"
|
||||
msgstr "the supervisor must authorise it, or the same quote must be measured again"
|
||||
|
||||
#: templates/measure/task_execute.html:537
|
||||
msgid "Produzione non registrata sul server"
|
||||
msgstr "Production not recorded on the server"
|
||||
|
||||
#: templates/measure/task_execute.html:558
|
||||
#: templates/measure/task_execute.html:767
|
||||
msgid "Avvio Produzione"
|
||||
msgstr "Production Start"
|
||||
|
||||
#: templates/measure/task_execute.html:562
|
||||
msgid "Invia segnale al gestionale per avviare il timer della linea"
|
||||
msgstr "Send a signal to the ERP system to start the line timer"
|
||||
|
||||
#: templates/measure/task_execute.html:573
|
||||
msgid "Produzione avviata"
|
||||
msgstr "Production started"
|
||||
|
||||
#: templates/measure/task_execute.html:618
|
||||
msgid "Fine ciclo misura"
|
||||
msgstr "Measurement cycle complete"
|
||||
|
||||
#: templates/measure/task_execute.html:629
|
||||
msgid "Rimisura"
|
||||
msgstr "Measure again"
|
||||
|
||||
#: templates/measure/task_execute.html:639
|
||||
#: templates/measure/task_execute.html:653
|
||||
msgid "Completato"
|
||||
msgstr "Completed"
|
||||
|
||||
#: templates/measure/task_execute.html:718
|
||||
msgid "Tutte le"
|
||||
msgstr "All"
|
||||
|
||||
#: templates/measure/task_execute.html:718
|
||||
msgid "misurazioni sono state registrate."
|
||||
msgstr "measurements have been recorded."
|
||||
|
||||
#: templates/measure/task_execute.html:732
|
||||
msgid "Non Conf."
|
||||
msgstr "Fail"
|
||||
|
||||
#: templates/measure/task_execute.html:747
|
||||
msgid "Conferma ciclo"
|
||||
msgstr "Confirm cycle"
|
||||
|
||||
#: templates/measure/task_execute.html:757
|
||||
msgid "Task successivo"
|
||||
msgstr "Next task"
|
||||
|
||||
#: templates/measure/task_execute.html:783
|
||||
msgid "Girare il pezzo e rimisurare"
|
||||
msgstr "Turn the piece over and measure again"
|
||||
|
||||
#: templates/measure/task_execute.html:809
|
||||
msgid "Autorizzazione capoturno"
|
||||
msgstr "Shift supervisor authorization"
|
||||
|
||||
#: templates/measure/task_execute.html:818
|
||||
msgid "Username capoturno"
|
||||
msgstr "Supervisor username"
|
||||
|
||||
#: templates/measure/task_execute.html:840
|
||||
#: templates/measure/task_execute.html:517
|
||||
#: templates/measure/task_execute.html:870
|
||||
msgid "Autorizza"
|
||||
msgstr "Authorize"
|
||||
|
||||
#: templates/measure/task_execute.html:1063
|
||||
#: templates/measure/task_execute.html:541
|
||||
msgid "Il conto alla rovescia è congelato a"
|
||||
msgstr "The countdown is frozen at"
|
||||
|
||||
#: templates/measure/task_execute.html:544
|
||||
msgid "serve il capoturno per riprendere"
|
||||
msgstr "the supervisor must authorise the restart"
|
||||
|
||||
#: templates/measure/task_execute.html:563
|
||||
msgid "Produzione non registrata sul server"
|
||||
msgstr "Production not recorded on the server"
|
||||
|
||||
#: templates/measure/task_execute.html:584
|
||||
#: templates/measure/task_execute.html:793
|
||||
msgid "Avvio Produzione"
|
||||
msgstr "Production Start"
|
||||
|
||||
#: templates/measure/task_execute.html:588
|
||||
msgid "Invia segnale al gestionale per avviare il timer della linea"
|
||||
msgstr "Send a signal to the ERP system to start the line timer"
|
||||
|
||||
#: templates/measure/task_execute.html:599
|
||||
msgid "Produzione avviata"
|
||||
msgstr "Production started"
|
||||
|
||||
#: templates/measure/task_execute.html:644
|
||||
msgid "Fine ciclo misura"
|
||||
msgstr "Measurement cycle complete"
|
||||
|
||||
#: templates/measure/task_execute.html:655
|
||||
msgid "Rimisura"
|
||||
msgstr "Measure again"
|
||||
|
||||
#: templates/measure/task_execute.html:665
|
||||
#: templates/measure/task_execute.html:679
|
||||
msgid "Completato"
|
||||
msgstr "Completed"
|
||||
|
||||
#: templates/measure/task_execute.html:744
|
||||
msgid "Tutte le"
|
||||
msgstr "All"
|
||||
|
||||
#: templates/measure/task_execute.html:744
|
||||
msgid "misurazioni sono state registrate."
|
||||
msgstr "measurements have been recorded."
|
||||
|
||||
#: templates/measure/task_execute.html:758
|
||||
msgid "Non Conf."
|
||||
msgstr "Fail"
|
||||
|
||||
#: templates/measure/task_execute.html:773
|
||||
msgid "Conferma ciclo"
|
||||
msgstr "Confirm cycle"
|
||||
|
||||
#: templates/measure/task_execute.html:783
|
||||
msgid "Task successivo"
|
||||
msgstr "Next task"
|
||||
|
||||
#: templates/measure/task_execute.html:809
|
||||
msgid "Girare il pezzo e rimisurare"
|
||||
msgstr "Turn the piece over and measure again"
|
||||
|
||||
#: templates/measure/task_execute.html:835
|
||||
msgid "Autorizzazione capoturno"
|
||||
msgstr "Shift supervisor authorization"
|
||||
|
||||
#: templates/measure/task_execute.html:844
|
||||
msgid "Username capoturno"
|
||||
msgstr "Supervisor username"
|
||||
|
||||
#: templates/measure/task_execute.html:861
|
||||
msgid "Rimisura la quota"
|
||||
msgstr "Measure this quote again"
|
||||
|
||||
#: templates/measure/task_execute.html:1125
|
||||
msgid "Questa ricetta non ammette valori digitati: usare il calibro"
|
||||
msgstr "This recipe does not accept typed values: use the caliper"
|
||||
|
||||
#: templates/measure/task_execute.html:1152
|
||||
#: templates/measure/task_execute.html:1222
|
||||
msgid "Errore di rete. Riprovare."
|
||||
msgstr "Network error. Please retry."
|
||||
|
||||
#: templates/measure/task_execute.html:1257
|
||||
#: templates/measure/task_execute.html:1260
|
||||
msgid ""
|
||||
"Quota fuori tolleranza: serve l'autorizzazione del capoturno, oppure "
|
||||
"misurare di nuovo la quota"
|
||||
msgstr "Out of tolerance: the supervisor must authorise it, or the quote must be measured again"
|
||||
|
||||
#: templates/measure/task_execute.html:1357
|
||||
msgid "Errore di comunicazione con il server"
|
||||
msgstr "Error communicating with the server"
|
||||
|
||||
#: templates/measure/task_execute.html:1505
|
||||
#: templates/measure/task_execute.html:1609
|
||||
msgid "Misurazione fuori tolleranza"
|
||||
msgstr "Measurement out of tolerance"
|
||||
|
||||
#: templates/measure/task_execute.html:1506
|
||||
#: templates/measure/task_execute.html:1610
|
||||
msgid "Fermo linea richiesto"
|
||||
msgstr "Line stop requested"
|
||||
|
||||
#: templates/measure/task_execute.html:1507
|
||||
#: templates/measure/task_execute.html:1611
|
||||
msgid "Ripresa della produzione"
|
||||
msgstr "Resuming production"
|
||||
|
||||
#: templates/measure/task_execute.html:1508
|
||||
#: templates/measure/task_execute.html:1612
|
||||
msgid "Fine produzione richiesta"
|
||||
msgstr "End of production requested"
|
||||
|
||||
#: templates/measure/task_execute.html:1537
|
||||
#: templates/measure/task_execute.html:1656
|
||||
msgid "Credenziali non valide o utente non autorizzato"
|
||||
msgstr "Invalid credentials or unauthorized user"
|
||||
|
||||
#: templates/measure/task_execute.html:1569
|
||||
#: templates/measure/task_execute.html:1683
|
||||
msgid "Nessuna produzione aperta su questa stazione"
|
||||
msgstr "No production open at this station"
|
||||
|
||||
#: templates/measure/task_execute.html:1586
|
||||
#: templates/measure/task_execute.html:1700
|
||||
msgid "Azione non riuscita"
|
||||
msgstr "Action failed"
|
||||
|
||||
#: templates/measure/task_list.html:87
|
||||
#: templates/measure/task_list.html:107
|
||||
msgid "AVVIA"
|
||||
msgstr "START"
|
||||
|
||||
#: templates/measure/task_list.html:124
|
||||
#: templates/measure/task_list.html:145
|
||||
msgid "Task da eseguire"
|
||||
msgstr "Tasks to execute"
|
||||
|
||||
#: templates/measure/task_list.html:136
|
||||
#: templates/measure/task_list.html:157
|
||||
msgid "misurazioni totali"
|
||||
msgstr "total measurements"
|
||||
|
||||
#: templates/measure/task_list.html:178
|
||||
#: templates/measure/task_list.html:199
|
||||
msgid "Confronto profilo"
|
||||
msgstr "Profile comparison"
|
||||
|
||||
#: templates/measure/task_list.html:179
|
||||
#: templates/measure/task_list.html:200
|
||||
msgid "Misura camera"
|
||||
msgstr "Camera measurement"
|
||||
|
||||
#: templates/measure/task_list.html:201
|
||||
#: templates/measure/task_list.html:222
|
||||
msgid "Allegato"
|
||||
msgstr "Attachment"
|
||||
|
||||
#: templates/measure/task_list.html:225
|
||||
#: templates/measure/task_list.html:246
|
||||
msgid "Visualizza Task"
|
||||
msgstr "View Tasks"
|
||||
|
||||
#: templates/measure/task_list.html:248
|
||||
#: templates/measure/task_list.html:269
|
||||
msgid "Nessun task disponibile"
|
||||
msgstr "No tasks available"
|
||||
|
||||
#: templates/measure/task_list.html:251
|
||||
#: templates/measure/task_list.html:272
|
||||
msgid "Questa ricetta non ha ancora task definiti."
|
||||
msgstr "This recipe has no tasks defined yet."
|
||||
|
||||
@@ -2681,3 +2697,6 @@ msgstr "Error generating report"
|
||||
#~ msgid "Inizia Misure"
|
||||
#~ msgstr "Start Measurements"
|
||||
|
||||
#~ msgid "Utente non autorizzato (richiesto capoturno)"
|
||||
#~ msgstr "User not authorized (shift supervisor required)"
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: TieMeasureFlow 1.0\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2026-07-28 20:16+0000\n"
|
||||
"POT-Creation-Date: 2026-07-28 20:48+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language: it\n"
|
||||
@@ -37,7 +37,7 @@ msgstr "Effettua il login per continuare"
|
||||
msgid "Inserisci username e password"
|
||||
msgstr "Inserisci username e password"
|
||||
|
||||
#: blueprints/auth.py:81 blueprints/measure.py:417
|
||||
#: blueprints/auth.py:81
|
||||
msgid "Credenziali non valide"
|
||||
msgstr "Credenziali non valide"
|
||||
|
||||
@@ -161,15 +161,11 @@ msgstr "Dati mancanti: subtask_id, version_id e value sono obbligatori"
|
||||
msgid "Errore nel salvataggio"
|
||||
msgstr "Errore nel salvataggio"
|
||||
|
||||
#: blueprints/measure.py:412 blueprints/measure.py:520
|
||||
#: blueprints/measure.py:437 blueprints/measure.py:542
|
||||
msgid "Username e password richiesti"
|
||||
msgstr "Username e password richiesti"
|
||||
|
||||
#: blueprints/measure.py:422
|
||||
msgid "Utente non autorizzato (richiesto capoturno)"
|
||||
msgstr "Utente non autorizzato (richiesto capoturno)"
|
||||
|
||||
#: blueprints/measure.py:441 blueprints/measure.py:458
|
||||
#: blueprints/measure.py:463 blueprints/measure.py:480
|
||||
#: templates/errors/station_not_configured.html:2
|
||||
#: templates/errors/station_not_configured.html:16
|
||||
msgid "Stazione non configurata"
|
||||
@@ -378,7 +374,7 @@ msgstr "Note opzionali"
|
||||
#: templates/maker/task_editor.html:782 templates/maker/task_editor.html:901
|
||||
#: templates/maker/task_editor.html:966 templates/maker/task_editor.html:1071
|
||||
#: templates/measure/select_recipe.html:405
|
||||
#: templates/measure/task_execute.html:831
|
||||
#: templates/measure/task_execute.html:860
|
||||
msgid "Annulla"
|
||||
msgstr "Annulla"
|
||||
|
||||
@@ -523,7 +519,7 @@ msgstr "Nuovo Utente"
|
||||
#: 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:816
|
||||
#: templates/measure/task_execute.html:842
|
||||
msgid "Username"
|
||||
msgstr "Username"
|
||||
|
||||
@@ -575,7 +571,7 @@ msgstr "Il nome utente non può essere modificato"
|
||||
|
||||
#: 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:821
|
||||
#: templates/measure/task_execute.html:847
|
||||
msgid "Password"
|
||||
msgstr "Password"
|
||||
|
||||
@@ -837,7 +833,7 @@ msgstr "Prossima misurazione tra"
|
||||
#: templates/components/production_clock.html:49
|
||||
#: templates/components/production_clock.html:62
|
||||
#: templates/measure/task_execute.html:463
|
||||
#: templates/measure/task_execute.html:516
|
||||
#: templates/measure/task_execute.html:542
|
||||
msgid "Misurazione in ritardo di"
|
||||
msgstr "Misurazione in ritardo di"
|
||||
|
||||
@@ -846,7 +842,7 @@ msgid "Linea ferma — conto alla rovescia congelato a"
|
||||
msgstr "Linea ferma — conto alla rovescia congelato a"
|
||||
|
||||
#: templates/components/production_clock.html:62
|
||||
#: templates/measure/task_execute.html:510
|
||||
#: templates/measure/task_execute.html:536
|
||||
msgid "Linea ferma"
|
||||
msgstr "Linea ferma"
|
||||
|
||||
@@ -856,17 +852,17 @@ msgid "Ciclo"
|
||||
msgstr "Ciclo"
|
||||
|
||||
#: templates/components/production_clock.html:92
|
||||
#: templates/measure/task_execute.html:678
|
||||
#: templates/measure/task_execute.html:704
|
||||
msgid "È ora di misurare"
|
||||
msgstr "È ora di misurare"
|
||||
|
||||
#: templates/components/production_clock.html:95
|
||||
#: templates/measure/task_execute.html:680
|
||||
#: templates/measure/task_execute.html:706
|
||||
msgid "Ritorno alla misurazione tra"
|
||||
msgstr "Ritorno alla misurazione tra"
|
||||
|
||||
#: templates/components/production_clock.html:101
|
||||
#: templates/measure/task_execute.html:685
|
||||
#: templates/measure/task_execute.html:711
|
||||
msgid "Vai alla misura"
|
||||
msgstr "Vai alla misura"
|
||||
|
||||
@@ -919,8 +915,8 @@ msgstr "Anteprima"
|
||||
#: 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:171
|
||||
#: templates/measure/task_execute.html:589 templates/measure/task_list.html:2
|
||||
#: templates/measure/task_list.html:159
|
||||
#: templates/measure/task_execute.html:615 templates/measure/task_list.html:2
|
||||
#: templates/measure/task_list.html:180
|
||||
msgid "Task"
|
||||
msgstr "Task"
|
||||
|
||||
@@ -1096,8 +1092,8 @@ msgstr "Errore durante eliminazione"
|
||||
|
||||
# Recipe Selection Additional
|
||||
#: templates/maker/recipe_list.html:55 templates/measure/select_recipe.html:72
|
||||
#: templates/measure/task_execute.html:1262
|
||||
#: templates/measure/task_execute.html:1558
|
||||
#: templates/measure/task_execute.html:1362
|
||||
#: templates/measure/task_execute.html:1672
|
||||
msgid "Errore di connessione"
|
||||
msgstr "Errore di connessione"
|
||||
|
||||
@@ -1408,18 +1404,18 @@ msgid "Tipo"
|
||||
msgstr "Tipo"
|
||||
|
||||
#: templates/maker/task_editor.html:277 templates/maker/task_editor.html:543
|
||||
#: templates/measure/task_list.html:177
|
||||
#: templates/measure/task_list.html:198
|
||||
msgid "Nota"
|
||||
msgstr "Nota"
|
||||
|
||||
#: templates/maker/task_editor.html:278 templates/maker/task_editor.html:544
|
||||
#: templates/measure/task_execute.html:309 templates/measure/task_list.html:175
|
||||
#: templates/measure/task_execute.html:309 templates/measure/task_list.html:196
|
||||
msgid "Misura"
|
||||
msgstr "Misura"
|
||||
|
||||
# Maker - Task Editor
|
||||
#: templates/maker/task_editor.html:279 templates/maker/task_editor.html:545
|
||||
#: templates/measure/task_list.html:176
|
||||
#: templates/measure/task_list.html:197
|
||||
msgid "Disegno"
|
||||
msgstr "Disegno"
|
||||
|
||||
@@ -1450,7 +1446,7 @@ msgstr "Trascina per riordinare"
|
||||
|
||||
#: templates/maker/task_editor.html:390
|
||||
#: templates/maker/version_history.html:188
|
||||
#: templates/measure/task_list.html:192
|
||||
#: templates/measure/task_list.html:213
|
||||
msgid "misurazioni"
|
||||
msgstr "misurazioni"
|
||||
|
||||
@@ -1716,7 +1712,7 @@ msgid "Misurazione aggiunta"
|
||||
msgstr "Misurazione aggiunta"
|
||||
|
||||
#: templates/maker/task_editor.html:1685
|
||||
#: templates/measure/task_execute.html:1108
|
||||
#: templates/measure/task_execute.html:1170
|
||||
msgid "Errore nel salvataggio della misurazione"
|
||||
msgstr "Errore nel salvataggio della misurazione"
|
||||
|
||||
@@ -1784,16 +1780,16 @@ msgid "Seleziona Ricetta"
|
||||
msgstr "Seleziona Ricetta"
|
||||
|
||||
# Measure - Task List
|
||||
#: templates/measure/select_recipe.html:30
|
||||
#: templates/measure/select_recipe.html:30 templates/measure/task_list.html:86
|
||||
msgid "lotto"
|
||||
msgstr "lotto"
|
||||
|
||||
#: templates/measure/select_recipe.html:31
|
||||
#: templates/measure/select_recipe.html:31 templates/measure/task_list.html:87
|
||||
msgid "seriale"
|
||||
msgstr "seriale"
|
||||
|
||||
#: templates/measure/select_recipe.html:64
|
||||
#: templates/measure/select_recipe.html:277
|
||||
#: templates/measure/select_recipe.html:277 templates/measure/task_list.html:97
|
||||
msgid "Compila prima:"
|
||||
msgstr "Compila prima:"
|
||||
|
||||
@@ -1890,25 +1886,26 @@ msgstr "Cerca"
|
||||
#: templates/measure/task_complete.html:3
|
||||
#: templates/measure/task_complete.html:39
|
||||
#: templates/measure/task_execute.html:113
|
||||
#: templates/measure/task_execute.html:739
|
||||
#: templates/measure/task_execute.html:765
|
||||
#: templates/statistics/dashboard.html:139
|
||||
msgid "Riepilogo"
|
||||
msgstr "Riepilogo"
|
||||
|
||||
#: templates/measure/task_complete.html:47
|
||||
#: templates/measure/task_execute.html:716
|
||||
#: templates/measure/task_execute.html:742
|
||||
msgid "Misurazioni Complete"
|
||||
msgstr "Misurazioni Complete"
|
||||
|
||||
# Measure - Task List
|
||||
#: templates/measure/task_complete.html:64
|
||||
#: templates/measure/task_complete.html:296 templates/measure/task_list.html:97
|
||||
#: templates/measure/task_complete.html:296
|
||||
#: templates/measure/task_list.html:118
|
||||
msgid "Lotto"
|
||||
msgstr "Lotto"
|
||||
|
||||
#: templates/measure/task_complete.html:70
|
||||
#: templates/measure/task_complete.html:297
|
||||
#: templates/measure/task_list.html:109
|
||||
#: templates/measure/task_list.html:130
|
||||
msgid "Seriale"
|
||||
msgstr "Seriale"
|
||||
|
||||
@@ -1917,12 +1914,12 @@ msgid "Totale"
|
||||
msgstr "Totale"
|
||||
|
||||
#: templates/measure/task_complete.html:106
|
||||
#: templates/measure/task_execute.html:724
|
||||
#: templates/measure/task_execute.html:750
|
||||
msgid "Conformi"
|
||||
msgstr "Conformi"
|
||||
|
||||
#: templates/measure/task_complete.html:123
|
||||
#: templates/measure/task_execute.html:728
|
||||
#: templates/measure/task_execute.html:754
|
||||
msgid "Attenzione"
|
||||
msgstr "Attenzione"
|
||||
|
||||
@@ -2118,153 +2115,172 @@ msgstr "Registrata"
|
||||
msgid "Task di misura senza quote configurate: la ricetta è incompleta"
|
||||
msgstr "Task di misura senza quote configurate: la ricetta è incompleta"
|
||||
|
||||
#: templates/measure/task_execute.html:515
|
||||
msgid "Il conto alla rovescia è congelato a"
|
||||
msgstr "Il conto alla rovescia è congelato a"
|
||||
#: templates/measure/task_execute.html:510
|
||||
msgid "Quota fuori tolleranza"
|
||||
msgstr "Quota fuori tolleranza"
|
||||
|
||||
#: templates/measure/task_execute.html:518
|
||||
msgid "serve il capoturno per riprendere"
|
||||
msgstr "serve il capoturno per riprendere"
|
||||
#: templates/measure/task_execute.html:513
|
||||
msgid "serve il capoturno, oppure una nuova misura della stessa quota"
|
||||
msgstr "serve il capoturno, oppure una nuova misura della stessa quota"
|
||||
|
||||
#: templates/measure/task_execute.html:537
|
||||
msgid "Produzione non registrata sul server"
|
||||
msgstr "Produzione non registrata sul server"
|
||||
|
||||
#: templates/measure/task_execute.html:558
|
||||
#: templates/measure/task_execute.html:767
|
||||
msgid "Avvio Produzione"
|
||||
msgstr "Avvio Produzione"
|
||||
|
||||
#: templates/measure/task_execute.html:562
|
||||
msgid "Invia segnale al gestionale per avviare il timer della linea"
|
||||
msgstr "Invia segnale al gestionale per avviare il timer della linea"
|
||||
|
||||
#: templates/measure/task_execute.html:573
|
||||
msgid "Produzione avviata"
|
||||
msgstr "Produzione avviata"
|
||||
|
||||
#: templates/measure/task_execute.html:618
|
||||
msgid "Fine ciclo misura"
|
||||
msgstr "Fine ciclo misura"
|
||||
|
||||
#: templates/measure/task_execute.html:629
|
||||
msgid "Rimisura"
|
||||
msgstr "Rimisura"
|
||||
|
||||
#: templates/measure/task_execute.html:639
|
||||
#: templates/measure/task_execute.html:653
|
||||
msgid "Completato"
|
||||
msgstr "Completato"
|
||||
|
||||
#: templates/measure/task_execute.html:718
|
||||
msgid "Tutte le"
|
||||
msgstr "Tutte le"
|
||||
|
||||
#: templates/measure/task_execute.html:718
|
||||
msgid "misurazioni sono state registrate."
|
||||
msgstr "misurazioni sono state registrate."
|
||||
|
||||
#: templates/measure/task_execute.html:732
|
||||
msgid "Non Conf."
|
||||
msgstr "Non Conf."
|
||||
|
||||
#: templates/measure/task_execute.html:747
|
||||
msgid "Conferma ciclo"
|
||||
msgstr "Conferma ciclo"
|
||||
|
||||
#: templates/measure/task_execute.html:757
|
||||
msgid "Task successivo"
|
||||
msgstr "Task successivo"
|
||||
|
||||
#: templates/measure/task_execute.html:783
|
||||
msgid "Girare il pezzo e rimisurare"
|
||||
msgstr "Girare il pezzo e rimisurare"
|
||||
|
||||
#: templates/measure/task_execute.html:809
|
||||
msgid "Autorizzazione capoturno"
|
||||
msgstr "Autorizzazione capoturno"
|
||||
|
||||
#: templates/measure/task_execute.html:818
|
||||
msgid "Username capoturno"
|
||||
msgstr "Username capoturno"
|
||||
|
||||
#: templates/measure/task_execute.html:840
|
||||
#: templates/measure/task_execute.html:517
|
||||
#: templates/measure/task_execute.html:870
|
||||
msgid "Autorizza"
|
||||
msgstr "Autorizza"
|
||||
|
||||
#: templates/measure/task_execute.html:1063
|
||||
#: templates/measure/task_execute.html:541
|
||||
msgid "Il conto alla rovescia è congelato a"
|
||||
msgstr "Il conto alla rovescia è congelato a"
|
||||
|
||||
#: templates/measure/task_execute.html:544
|
||||
msgid "serve il capoturno per riprendere"
|
||||
msgstr "serve il capoturno per riprendere"
|
||||
|
||||
#: templates/measure/task_execute.html:563
|
||||
msgid "Produzione non registrata sul server"
|
||||
msgstr "Produzione non registrata sul server"
|
||||
|
||||
#: templates/measure/task_execute.html:584
|
||||
#: templates/measure/task_execute.html:793
|
||||
msgid "Avvio Produzione"
|
||||
msgstr "Avvio Produzione"
|
||||
|
||||
#: templates/measure/task_execute.html:588
|
||||
msgid "Invia segnale al gestionale per avviare il timer della linea"
|
||||
msgstr "Invia segnale al gestionale per avviare il timer della linea"
|
||||
|
||||
#: templates/measure/task_execute.html:599
|
||||
msgid "Produzione avviata"
|
||||
msgstr "Produzione avviata"
|
||||
|
||||
#: templates/measure/task_execute.html:644
|
||||
msgid "Fine ciclo misura"
|
||||
msgstr "Fine ciclo misura"
|
||||
|
||||
#: templates/measure/task_execute.html:655
|
||||
msgid "Rimisura"
|
||||
msgstr "Rimisura"
|
||||
|
||||
#: templates/measure/task_execute.html:665
|
||||
#: templates/measure/task_execute.html:679
|
||||
msgid "Completato"
|
||||
msgstr "Completato"
|
||||
|
||||
#: templates/measure/task_execute.html:744
|
||||
msgid "Tutte le"
|
||||
msgstr "Tutte le"
|
||||
|
||||
#: templates/measure/task_execute.html:744
|
||||
msgid "misurazioni sono state registrate."
|
||||
msgstr "misurazioni sono state registrate."
|
||||
|
||||
#: templates/measure/task_execute.html:758
|
||||
msgid "Non Conf."
|
||||
msgstr "Non Conf."
|
||||
|
||||
#: templates/measure/task_execute.html:773
|
||||
msgid "Conferma ciclo"
|
||||
msgstr "Conferma ciclo"
|
||||
|
||||
#: templates/measure/task_execute.html:783
|
||||
msgid "Task successivo"
|
||||
msgstr "Task successivo"
|
||||
|
||||
#: templates/measure/task_execute.html:809
|
||||
msgid "Girare il pezzo e rimisurare"
|
||||
msgstr "Girare il pezzo e rimisurare"
|
||||
|
||||
#: templates/measure/task_execute.html:835
|
||||
msgid "Autorizzazione capoturno"
|
||||
msgstr "Autorizzazione capoturno"
|
||||
|
||||
#: templates/measure/task_execute.html:844
|
||||
msgid "Username capoturno"
|
||||
msgstr "Username capoturno"
|
||||
|
||||
#: templates/measure/task_execute.html:861
|
||||
msgid "Rimisura la quota"
|
||||
msgstr "Rimisura la quota"
|
||||
|
||||
#: templates/measure/task_execute.html:1125
|
||||
msgid "Questa ricetta non ammette valori digitati: usare il calibro"
|
||||
msgstr "Questa ricetta non ammette valori digitati: usare il calibro"
|
||||
|
||||
#: templates/measure/task_execute.html:1152
|
||||
#: templates/measure/task_execute.html:1222
|
||||
msgid "Errore di rete. Riprovare."
|
||||
msgstr "Errore di rete. Riprovare."
|
||||
|
||||
#: templates/measure/task_execute.html:1257
|
||||
#: templates/measure/task_execute.html:1260
|
||||
msgid ""
|
||||
"Quota fuori tolleranza: serve l'autorizzazione del capoturno, oppure "
|
||||
"misurare di nuovo la quota"
|
||||
msgstr "Quota fuori tolleranza: serve l'autorizzazione del capoturno, oppure misurare di nuovo la quota"
|
||||
|
||||
#: templates/measure/task_execute.html:1357
|
||||
msgid "Errore di comunicazione con il server"
|
||||
msgstr "Errore di comunicazione con il server"
|
||||
|
||||
#: templates/measure/task_execute.html:1505
|
||||
#: templates/measure/task_execute.html:1609
|
||||
msgid "Misurazione fuori tolleranza"
|
||||
msgstr "Misurazione fuori tolleranza"
|
||||
|
||||
#: templates/measure/task_execute.html:1506
|
||||
#: templates/measure/task_execute.html:1610
|
||||
msgid "Fermo linea richiesto"
|
||||
msgstr "Fermo linea richiesto"
|
||||
|
||||
#: templates/measure/task_execute.html:1507
|
||||
#: templates/measure/task_execute.html:1611
|
||||
msgid "Ripresa della produzione"
|
||||
msgstr "Ripresa della produzione"
|
||||
|
||||
#: templates/measure/task_execute.html:1508
|
||||
#: templates/measure/task_execute.html:1612
|
||||
msgid "Fine produzione richiesta"
|
||||
msgstr "Fine produzione richiesta"
|
||||
|
||||
#: templates/measure/task_execute.html:1537
|
||||
#: templates/measure/task_execute.html:1656
|
||||
msgid "Credenziali non valide o utente non autorizzato"
|
||||
msgstr "Credenziali non valide o utente non autorizzato"
|
||||
|
||||
#: templates/measure/task_execute.html:1569
|
||||
#: templates/measure/task_execute.html:1683
|
||||
msgid "Nessuna produzione aperta su questa stazione"
|
||||
msgstr "Nessuna produzione aperta su questa stazione"
|
||||
|
||||
#: templates/measure/task_execute.html:1586
|
||||
#: templates/measure/task_execute.html:1700
|
||||
msgid "Azione non riuscita"
|
||||
msgstr "Azione non riuscita"
|
||||
|
||||
#: templates/measure/task_list.html:87
|
||||
#: templates/measure/task_list.html:107
|
||||
msgid "AVVIA"
|
||||
msgstr "AVVIA"
|
||||
|
||||
#: templates/measure/task_list.html:124
|
||||
#: templates/measure/task_list.html:145
|
||||
msgid "Task da eseguire"
|
||||
msgstr "Task da eseguire"
|
||||
|
||||
#: templates/measure/task_list.html:136
|
||||
#: templates/measure/task_list.html:157
|
||||
msgid "misurazioni totali"
|
||||
msgstr "misurazioni totali"
|
||||
|
||||
#: templates/measure/task_list.html:178
|
||||
#: templates/measure/task_list.html:199
|
||||
msgid "Confronto profilo"
|
||||
msgstr "Confronto profilo"
|
||||
|
||||
#: templates/measure/task_list.html:179
|
||||
#: templates/measure/task_list.html:200
|
||||
msgid "Misura camera"
|
||||
msgstr "Misura camera"
|
||||
|
||||
#: templates/measure/task_list.html:201
|
||||
#: templates/measure/task_list.html:222
|
||||
msgid "Allegato"
|
||||
msgstr "Allegato"
|
||||
|
||||
#: templates/measure/task_list.html:225
|
||||
#: templates/measure/task_list.html:246
|
||||
msgid "Visualizza Task"
|
||||
msgstr "Visualizza Task"
|
||||
|
||||
#: templates/measure/task_list.html:248
|
||||
#: templates/measure/task_list.html:269
|
||||
msgid "Nessun task disponibile"
|
||||
msgstr "Nessun task disponibile"
|
||||
|
||||
#: templates/measure/task_list.html:251
|
||||
#: templates/measure/task_list.html:272
|
||||
msgid "Questa ricetta non ha ancora task definiti."
|
||||
msgstr "Questa ricetta non ha ancora task definiti."
|
||||
|
||||
@@ -2669,3 +2685,6 @@ msgstr "Errore nella generazione del report"
|
||||
#~ msgid "Inizia Misure"
|
||||
#~ msgstr "Inizia Misure"
|
||||
|
||||
#~ msgid "Utente non autorizzato (richiesto capoturno)"
|
||||
#~ msgstr "Utente non autorizzato (richiesto capoturno)"
|
||||
|
||||
|
||||
@@ -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 20:16+0000\n"
|
||||
"POT-Creation-Date: 2026-07-28 20:48+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"
|
||||
@@ -39,7 +39,7 @@ msgstr ""
|
||||
msgid "Inserisci username e password"
|
||||
msgstr ""
|
||||
|
||||
#: blueprints/auth.py:81 blueprints/measure.py:417
|
||||
#: blueprints/auth.py:81
|
||||
msgid "Credenziali non valide"
|
||||
msgstr ""
|
||||
|
||||
@@ -158,15 +158,11 @@ msgstr ""
|
||||
msgid "Errore nel salvataggio"
|
||||
msgstr ""
|
||||
|
||||
#: blueprints/measure.py:412 blueprints/measure.py:520
|
||||
#: blueprints/measure.py:437 blueprints/measure.py:542
|
||||
msgid "Username e password richiesti"
|
||||
msgstr ""
|
||||
|
||||
#: blueprints/measure.py:422
|
||||
msgid "Utente non autorizzato (richiesto capoturno)"
|
||||
msgstr ""
|
||||
|
||||
#: blueprints/measure.py:441 blueprints/measure.py:458
|
||||
#: blueprints/measure.py:463 blueprints/measure.py:480
|
||||
#: templates/errors/station_not_configured.html:2
|
||||
#: templates/errors/station_not_configured.html:16
|
||||
msgid "Stazione non configurata"
|
||||
@@ -369,7 +365,7 @@ msgstr ""
|
||||
#: templates/maker/task_editor.html:782 templates/maker/task_editor.html:901
|
||||
#: templates/maker/task_editor.html:966 templates/maker/task_editor.html:1071
|
||||
#: templates/measure/select_recipe.html:405
|
||||
#: templates/measure/task_execute.html:831
|
||||
#: templates/measure/task_execute.html:860
|
||||
msgid "Annulla"
|
||||
msgstr ""
|
||||
|
||||
@@ -513,7 +509,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:816
|
||||
#: templates/measure/task_execute.html:842
|
||||
msgid "Username"
|
||||
msgstr ""
|
||||
|
||||
@@ -565,7 +561,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:821
|
||||
#: templates/measure/task_execute.html:847
|
||||
msgid "Password"
|
||||
msgstr ""
|
||||
|
||||
@@ -818,7 +814,7 @@ msgstr ""
|
||||
#: templates/components/production_clock.html:49
|
||||
#: templates/components/production_clock.html:62
|
||||
#: templates/measure/task_execute.html:463
|
||||
#: templates/measure/task_execute.html:516
|
||||
#: templates/measure/task_execute.html:542
|
||||
msgid "Misurazione in ritardo di"
|
||||
msgstr ""
|
||||
|
||||
@@ -827,7 +823,7 @@ msgid "Linea ferma — conto alla rovescia congelato a"
|
||||
msgstr ""
|
||||
|
||||
#: templates/components/production_clock.html:62
|
||||
#: templates/measure/task_execute.html:510
|
||||
#: templates/measure/task_execute.html:536
|
||||
msgid "Linea ferma"
|
||||
msgstr ""
|
||||
|
||||
@@ -837,17 +833,17 @@ msgid "Ciclo"
|
||||
msgstr ""
|
||||
|
||||
#: templates/components/production_clock.html:92
|
||||
#: templates/measure/task_execute.html:678
|
||||
#: templates/measure/task_execute.html:704
|
||||
msgid "È ora di misurare"
|
||||
msgstr ""
|
||||
|
||||
#: templates/components/production_clock.html:95
|
||||
#: templates/measure/task_execute.html:680
|
||||
#: templates/measure/task_execute.html:706
|
||||
msgid "Ritorno alla misurazione tra"
|
||||
msgstr ""
|
||||
|
||||
#: templates/components/production_clock.html:101
|
||||
#: templates/measure/task_execute.html:685
|
||||
#: templates/measure/task_execute.html:711
|
||||
msgid "Vai alla misura"
|
||||
msgstr ""
|
||||
|
||||
@@ -896,8 +892,8 @@ 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:171
|
||||
#: templates/measure/task_execute.html:589 templates/measure/task_list.html:2
|
||||
#: templates/measure/task_list.html:159
|
||||
#: templates/measure/task_execute.html:615 templates/measure/task_list.html:2
|
||||
#: templates/measure/task_list.html:180
|
||||
msgid "Task"
|
||||
msgstr ""
|
||||
|
||||
@@ -1071,8 +1067,8 @@ msgid "Errore durante eliminazione"
|
||||
msgstr ""
|
||||
|
||||
#: templates/maker/recipe_list.html:55 templates/measure/select_recipe.html:72
|
||||
#: templates/measure/task_execute.html:1262
|
||||
#: templates/measure/task_execute.html:1558
|
||||
#: templates/measure/task_execute.html:1362
|
||||
#: templates/measure/task_execute.html:1672
|
||||
msgid "Errore di connessione"
|
||||
msgstr ""
|
||||
|
||||
@@ -1376,17 +1372,17 @@ msgid "Tipo"
|
||||
msgstr ""
|
||||
|
||||
#: templates/maker/task_editor.html:277 templates/maker/task_editor.html:543
|
||||
#: templates/measure/task_list.html:177
|
||||
#: templates/measure/task_list.html:198
|
||||
msgid "Nota"
|
||||
msgstr ""
|
||||
|
||||
#: templates/maker/task_editor.html:278 templates/maker/task_editor.html:544
|
||||
#: templates/measure/task_execute.html:309 templates/measure/task_list.html:175
|
||||
#: templates/measure/task_execute.html:309 templates/measure/task_list.html:196
|
||||
msgid "Misura"
|
||||
msgstr ""
|
||||
|
||||
#: templates/maker/task_editor.html:279 templates/maker/task_editor.html:545
|
||||
#: templates/measure/task_list.html:176
|
||||
#: templates/measure/task_list.html:197
|
||||
msgid "Disegno"
|
||||
msgstr ""
|
||||
|
||||
@@ -1417,7 +1413,7 @@ msgstr ""
|
||||
|
||||
#: templates/maker/task_editor.html:390
|
||||
#: templates/maker/version_history.html:188
|
||||
#: templates/measure/task_list.html:192
|
||||
#: templates/measure/task_list.html:213
|
||||
msgid "misurazioni"
|
||||
msgstr ""
|
||||
|
||||
@@ -1683,7 +1679,7 @@ msgid "Misurazione aggiunta"
|
||||
msgstr ""
|
||||
|
||||
#: templates/maker/task_editor.html:1685
|
||||
#: templates/measure/task_execute.html:1108
|
||||
#: templates/measure/task_execute.html:1170
|
||||
msgid "Errore nel salvataggio della misurazione"
|
||||
msgstr ""
|
||||
|
||||
@@ -1746,16 +1742,16 @@ msgstr ""
|
||||
msgid "Seleziona Ricetta"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/select_recipe.html:30
|
||||
#: templates/measure/select_recipe.html:30 templates/measure/task_list.html:86
|
||||
msgid "lotto"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/select_recipe.html:31
|
||||
#: templates/measure/select_recipe.html:31 templates/measure/task_list.html:87
|
||||
msgid "seriale"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/select_recipe.html:64
|
||||
#: templates/measure/select_recipe.html:277
|
||||
#: templates/measure/select_recipe.html:277 templates/measure/task_list.html:97
|
||||
msgid "Compila prima:"
|
||||
msgstr ""
|
||||
|
||||
@@ -1850,24 +1846,25 @@ msgstr ""
|
||||
#: templates/measure/task_complete.html:3
|
||||
#: templates/measure/task_complete.html:39
|
||||
#: templates/measure/task_execute.html:113
|
||||
#: templates/measure/task_execute.html:739
|
||||
#: templates/measure/task_execute.html:765
|
||||
#: templates/statistics/dashboard.html:139
|
||||
msgid "Riepilogo"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_complete.html:47
|
||||
#: templates/measure/task_execute.html:716
|
||||
#: templates/measure/task_execute.html:742
|
||||
msgid "Misurazioni Complete"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_complete.html:64
|
||||
#: templates/measure/task_complete.html:296 templates/measure/task_list.html:97
|
||||
#: templates/measure/task_complete.html:296
|
||||
#: templates/measure/task_list.html:118
|
||||
msgid "Lotto"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_complete.html:70
|
||||
#: templates/measure/task_complete.html:297
|
||||
#: templates/measure/task_list.html:109
|
||||
#: templates/measure/task_list.html:130
|
||||
msgid "Seriale"
|
||||
msgstr ""
|
||||
|
||||
@@ -1876,12 +1873,12 @@ msgid "Totale"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_complete.html:106
|
||||
#: templates/measure/task_execute.html:724
|
||||
#: templates/measure/task_execute.html:750
|
||||
msgid "Conformi"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_complete.html:123
|
||||
#: templates/measure/task_execute.html:728
|
||||
#: templates/measure/task_execute.html:754
|
||||
msgid "Attenzione"
|
||||
msgstr ""
|
||||
|
||||
@@ -2075,153 +2072,172 @@ msgstr ""
|
||||
msgid "Task di misura senza quote configurate: la ricetta è incompleta"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_execute.html:515
|
||||
msgid "Il conto alla rovescia è congelato a"
|
||||
#: templates/measure/task_execute.html:510
|
||||
msgid "Quota fuori tolleranza"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_execute.html:518
|
||||
msgid "serve il capoturno per riprendere"
|
||||
#: templates/measure/task_execute.html:513
|
||||
msgid "serve il capoturno, oppure una nuova misura della stessa quota"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_execute.html:537
|
||||
msgid "Produzione non registrata sul server"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_execute.html:558
|
||||
#: templates/measure/task_execute.html:767
|
||||
msgid "Avvio Produzione"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_execute.html:562
|
||||
msgid "Invia segnale al gestionale per avviare il timer della linea"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_execute.html:573
|
||||
msgid "Produzione avviata"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_execute.html:618
|
||||
msgid "Fine ciclo misura"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_execute.html:629
|
||||
msgid "Rimisura"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_execute.html:639
|
||||
#: templates/measure/task_execute.html:653
|
||||
msgid "Completato"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_execute.html:718
|
||||
msgid "Tutte le"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_execute.html:718
|
||||
msgid "misurazioni sono state registrate."
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_execute.html:732
|
||||
msgid "Non Conf."
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_execute.html:747
|
||||
msgid "Conferma ciclo"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_execute.html:757
|
||||
msgid "Task successivo"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_execute.html:783
|
||||
msgid "Girare il pezzo e rimisurare"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_execute.html:809
|
||||
msgid "Autorizzazione capoturno"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_execute.html:818
|
||||
msgid "Username capoturno"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_execute.html:840
|
||||
#: templates/measure/task_execute.html:517
|
||||
#: templates/measure/task_execute.html:870
|
||||
msgid "Autorizza"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_execute.html:1063
|
||||
#: templates/measure/task_execute.html:541
|
||||
msgid "Il conto alla rovescia è congelato a"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_execute.html:544
|
||||
msgid "serve il capoturno per riprendere"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_execute.html:563
|
||||
msgid "Produzione non registrata sul server"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_execute.html:584
|
||||
#: templates/measure/task_execute.html:793
|
||||
msgid "Avvio Produzione"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_execute.html:588
|
||||
msgid "Invia segnale al gestionale per avviare il timer della linea"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_execute.html:599
|
||||
msgid "Produzione avviata"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_execute.html:644
|
||||
msgid "Fine ciclo misura"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_execute.html:655
|
||||
msgid "Rimisura"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_execute.html:665
|
||||
#: templates/measure/task_execute.html:679
|
||||
msgid "Completato"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_execute.html:744
|
||||
msgid "Tutte le"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_execute.html:744
|
||||
msgid "misurazioni sono state registrate."
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_execute.html:758
|
||||
msgid "Non Conf."
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_execute.html:773
|
||||
msgid "Conferma ciclo"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_execute.html:783
|
||||
msgid "Task successivo"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_execute.html:809
|
||||
msgid "Girare il pezzo e rimisurare"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_execute.html:835
|
||||
msgid "Autorizzazione capoturno"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_execute.html:844
|
||||
msgid "Username capoturno"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_execute.html:861
|
||||
msgid "Rimisura la quota"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_execute.html:1125
|
||||
msgid "Questa ricetta non ammette valori digitati: usare il calibro"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_execute.html:1152
|
||||
#: templates/measure/task_execute.html:1222
|
||||
msgid "Errore di rete. Riprovare."
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_execute.html:1257
|
||||
#: templates/measure/task_execute.html:1260
|
||||
msgid ""
|
||||
"Quota fuori tolleranza: serve l'autorizzazione del capoturno, oppure "
|
||||
"misurare di nuovo la quota"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_execute.html:1357
|
||||
msgid "Errore di comunicazione con il server"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_execute.html:1505
|
||||
#: templates/measure/task_execute.html:1609
|
||||
msgid "Misurazione fuori tolleranza"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_execute.html:1506
|
||||
#: templates/measure/task_execute.html:1610
|
||||
msgid "Fermo linea richiesto"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_execute.html:1507
|
||||
#: templates/measure/task_execute.html:1611
|
||||
msgid "Ripresa della produzione"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_execute.html:1508
|
||||
#: templates/measure/task_execute.html:1612
|
||||
msgid "Fine produzione richiesta"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_execute.html:1537
|
||||
#: templates/measure/task_execute.html:1656
|
||||
msgid "Credenziali non valide o utente non autorizzato"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_execute.html:1569
|
||||
#: templates/measure/task_execute.html:1683
|
||||
msgid "Nessuna produzione aperta su questa stazione"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_execute.html:1586
|
||||
#: templates/measure/task_execute.html:1700
|
||||
msgid "Azione non riuscita"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_list.html:87
|
||||
#: templates/measure/task_list.html:107
|
||||
msgid "AVVIA"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_list.html:124
|
||||
#: templates/measure/task_list.html:145
|
||||
msgid "Task da eseguire"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_list.html:136
|
||||
#: templates/measure/task_list.html:157
|
||||
msgid "misurazioni totali"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_list.html:178
|
||||
#: templates/measure/task_list.html:199
|
||||
msgid "Confronto profilo"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_list.html:179
|
||||
#: templates/measure/task_list.html:200
|
||||
msgid "Misura camera"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_list.html:201
|
||||
#: templates/measure/task_list.html:222
|
||||
msgid "Allegato"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_list.html:225
|
||||
#: templates/measure/task_list.html:246
|
||||
msgid "Visualizza Task"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_list.html:248
|
||||
#: templates/measure/task_list.html:269
|
||||
msgid "Nessun task disponibile"
|
||||
msgstr ""
|
||||
|
||||
#: templates/measure/task_list.html:251
|
||||
#: templates/measure/task_list.html:272
|
||||
msgid "Questa ricetta non ha ancora task definiti."
|
||||
msgstr ""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user