Files
TieMeasureFlow/src/backend/models/api/station.py
T
Adriano Dal Pastro 5aa3d595ad feat(recipes): la ricetta decide cosa pretendere e come si misura
Tre cose che prima non erano di nessuno diventano regole della ricetta, decise da
chi la scrive e fatte valere dal server.

Punto 8 — tracciabilita' obbligatoria. Lotto e seriale si dichiarano obbligatori
sulla ricetta. L'operatore li inserisce alla selezione, dove il pulsante non si
attiva finche' mancano, e la stessa regola vale sulla lista task, raggiungibile
anche per link diretto, e sul barcode: uno sbarramento che dura solo finche'
qualcuno prende in mano il lettore non e' uno sbarramento. La produzione non si
apre e la misura non si salva senza cio' che la ricetta pretende, perche' un
valore senza il suo lotto non e' riconducibile a niente, e accorgersene dopo
significa accorgersene tardi.

Punto 9 — inserimento manuale. Una ricetta puo' vietare i valori digitati, ed e'
il valore predefinito: il calibro e' lo strumento, digitare e' cio' che va
concesso. Dove e' vietato il tastierino non viene disegnato (non nascosto con i
CSS: il markup nascosto e' markup che si puo' rimostrare) e restano correzione e
conferma, perche' una lettura sbagliata va cancellata. Il controllo vero e' sul
server: una regola che vive solo nel frontend e' un consiglio.

Migliorato al passaggio il riconoscimento del calibro. Contava solo la raffica di
cifre, cosi' una lettura corta come "9.5" — tre battute — finiva registrata come
digitata a mano; ora conta anche l'Invio che il wedge manda dentro la stessa
raffica. Senza questa correzione il divieto avrebbe respinto misure legittime.

Punto 11 — formattazione delle descrizioni. A capo e grassetto sopravvivono: chi
scrive le ricette incolla dal PDF della scheda tecnica e il testo arrivava
appiattito, da risistemare a mano ogni volta. Nessun HTML viene accettato o
salvato — il testo viene escapato e gli unici tag nel risultato sono quelli
prodotti dal renderer. La sanificazione e' questa: non c'e' niente da sanificare
perche' non si accetta niente. Le stesse due regole in Jinja e in JS, cosi' una
descrizione si legge uguale ovunque. E la descrizione ora si vede anche in
esecuzione: era scritta per l'operatore e la vedeva solo chi la scriveva.

Migrazione 009: tre colonne sulla ricetta. Le due di tracciabilita' partono
false, che e' il comportamento di oggi; l'inserimento manuale parte *vero* sulle
ricette gia' esistenti — il default della colonna e' falso, quindi le ricette
nuove sono solo-calibro, ma spegnerlo d'ufficio su quelle in uso fermerebbe una
linea alla misura successiva. Chi possiede la ricetta lo decide dall'editor.

Test: +26 (291). Coprono il rifiuto sul server per lotto, seriale e valore
digitato, il calibro sempre ammesso, le regole che sopravvivono alla nuova
versione, il tastierino assente in pagina, l'Avvia sbarrato, e il renderer delle
descrizioni compreso il caso in cui si prova a farci passare un tag.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 20:27:19 +00:00

71 lines
1.9 KiB
Python

"""Pydantic schemas for Station and StationRecipeAssignment."""
from datetime import datetime
from typing import Optional
from pydantic import BaseModel, ConfigDict, Field
class StationCreate(BaseModel):
code: str = Field(..., min_length=1, max_length=100)
name: str = Field(..., min_length=1, max_length=255)
location: Optional[str] = Field(default=None, max_length=255)
notes: Optional[str] = None
active: bool = True
class StationUpdate(BaseModel):
name: Optional[str] = Field(default=None, min_length=1, max_length=255)
location: Optional[str] = Field(default=None, max_length=255)
notes: Optional[str] = None
active: Optional[bool] = None
class StationResponse(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: int
code: str
name: str
location: Optional[str]
notes: Optional[str]
active: bool
created_by: int
created_at: datetime
class StationRecipeAssignmentCreate(BaseModel):
recipe_id: int = Field(..., gt=0)
class StationRecipeAssignmentResponse(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: int
station_id: int
recipe_id: int
assigned_by: int
assigned_at: datetime
class RecipeSummary(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: int
code: str
name: str
active: bool
image_path: Optional[str] = None
description: Optional[str] = None
# The selection screen has to know before the operator starts: what a recipe
# demands is the difference between an enabled Avvia and a disabled one.
requires_lot: bool = False
requires_serial: bool = False
class StationWithRecipesResponse(StationResponse):
recipes: list[RecipeSummary] = Field(default_factory=list)
class StationResetResponse(BaseModel):
"""Outcome of clearing every recipe assignment of a station."""
station_id: int
removed: int