feat(measure): l'operatore entra nella sequenza, non in un elenco
Punto 10. Sceglere una ricetta apriva la lista completa dei task e chiedeva all'operatore di decidere da dove cominciare prima ancora di aver fatto qualcosa. Ora AVVIA IN SEQUENZA porta dentro il primo task; la lista resta, un livello sotto, per quando serve vederla tutta o tornare indietro. - nuova rotta /measure/start/<ricetta>: memorizza lotto e seriale (prima lo faceva la lista, che ora si salta), verifica la tracciabilità obbligatoria e apre il primo task - GET /api/measurements/task-progress: quante quote ha già preso ogni task di misura, contate per quota e non per tentativo, delimitate dalla produzione aperta o, fuori produzione, dall'operatore - la lista distingue «Incompiuto 1/3» da «Completato 3/3»: un task lasciato a metà non somigliava più a uno mai aperto - «Visualizza singolo TASK» non è più riservato al Maker — dire «incompiuto» senza dare la strada per tornarci sarebbe una lamentela, non una funzione - nel task «Riepilogo» diventa «Completato» e si apre solo quando il ciclo è chiuso: altrimenti sarebbe la scorciatoia che rende facoltative le quote - «Fine ciclo misura» si vede da subito, spento, e dice quante quote mancano; prima compariva a task già finito, quando non serviva più saperlo Il pulsante verde del footer diventa «Task successivo»: due bottoni con la stessa parola addosso sono uno di troppo. Test: 334 (erano 309). Il side effect dei mock del client Flask ora risponde per endpoint invece che per turno — una lista posizionale si rompe appena una pagina fa una domanda in più al server, che è come crescono tutte. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -8,7 +8,9 @@ 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.task import (
|
||||
MEASURING_TASK_TYPES, RecipeSubtask, RecipeTask,
|
||||
)
|
||||
from src.backend.models.orm.user import User
|
||||
|
||||
|
||||
@@ -135,6 +137,71 @@ async def pending_authorisation(
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
|
||||
async def task_progress(
|
||||
db: AsyncSession,
|
||||
version_id: int,
|
||||
measured_by: int,
|
||||
production_run_id: int | None = None,
|
||||
) -> list[dict]:
|
||||
"""How far each measurement task of a version has got.
|
||||
|
||||
A task left half done is the thing the operator cannot see today: they walk
|
||||
away from it, come back to the list, and it looks exactly like one never
|
||||
started. Counting quotes against measurements is what tells them apart.
|
||||
|
||||
Documental tasks are not in here. A note cannot be half read, and marking one
|
||||
incomplete for ever would be noise on the only screen that has to stay legible.
|
||||
Which tasks count is read from the declared type, not from whether quotes happen
|
||||
to have been entered yet - the same rule point 2 settled everywhere else.
|
||||
"""
|
||||
rows = (await db.execute(
|
||||
select(RecipeTask.id, RecipeSubtask.id)
|
||||
.join(RecipeSubtask, RecipeSubtask.task_id == RecipeTask.id, isouter=True)
|
||||
.where(
|
||||
RecipeTask.version_id == version_id,
|
||||
RecipeTask.task_type.in_(MEASURING_TASK_TYPES),
|
||||
)
|
||||
)).all()
|
||||
if not rows:
|
||||
return []
|
||||
|
||||
by_task: dict[int, set[int]] = {}
|
||||
for task_id, subtask_id in rows:
|
||||
quotes = by_task.setdefault(task_id, set())
|
||||
if subtask_id is not None:
|
||||
quotes.add(subtask_id)
|
||||
|
||||
measured = set((await db.execute(
|
||||
_scope(
|
||||
select(Measurement.subtask_id).distinct(),
|
||||
version_id, measured_by, production_run_id,
|
||||
)
|
||||
)).scalars().all())
|
||||
|
||||
progress = []
|
||||
for task_id, subtask_ids in by_task.items():
|
||||
total = len(subtask_ids)
|
||||
done = len(subtask_ids & measured)
|
||||
if total == 0:
|
||||
# Declared as a measurement but with no quotes on it yet: nothing to
|
||||
# take, so nothing to report as missing either.
|
||||
state = "empty"
|
||||
elif done == total:
|
||||
state = "complete"
|
||||
elif done:
|
||||
state = "partial"
|
||||
else:
|
||||
state = "none"
|
||||
progress.append({
|
||||
"task_id": task_id,
|
||||
"quotes": total,
|
||||
"measured": done,
|
||||
"state": state,
|
||||
})
|
||||
progress.sort(key=lambda row: row["task_id"])
|
||||
return progress
|
||||
|
||||
|
||||
async def authorise_measurement(
|
||||
db: AsyncSession, measurement: Measurement, supervisor: User,
|
||||
) -> Measurement:
|
||||
|
||||
Reference in New Issue
Block a user