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>
This commit is contained in:
@@ -22,6 +22,15 @@
|
||||
(r.description || '').toLowerCase().includes(q)
|
||||
);
|
||||
},
|
||||
/* Which compulsory traceability fields this recipe is still missing.
|
||||
Empty means the operator can start. The server refuses the same thing
|
||||
when the production opens - this is only the earlier answer. */
|
||||
missingTraceability(recipe) {
|
||||
const missing = [];
|
||||
if (recipe.requires_lot && !this.lot_number.trim()) missing.push('{{ _('lotto') }}');
|
||||
if (recipe.requires_serial && !this.serial_number.trim()) missing.push('{{ _('seriale') }}');
|
||||
return missing;
|
||||
},
|
||||
buildTaskUrl(recipeId) {
|
||||
let url = '/measure/tasks/' + recipeId + '?';
|
||||
const params = [];
|
||||
@@ -47,6 +56,14 @@
|
||||
if (data.error) {
|
||||
this.barcodeError = data.detail || '{{ _("Ricetta non trovata") }}';
|
||||
} else {
|
||||
// The scanner is a way in like any other: a recipe that demands a lot
|
||||
// demands it here too, or the rule would last exactly as long as it
|
||||
// took someone to reach for the barcode reader.
|
||||
const missing = this.missingTraceability(data);
|
||||
if (missing.length) {
|
||||
this.barcodeError = '{{ _("Compila prima:") }} ' + missing.join(', ');
|
||||
return;
|
||||
}
|
||||
this.barcodeModal = false;
|
||||
this.barcodeInput = '';
|
||||
window.location.href = this.buildTaskUrl(data.id);
|
||||
@@ -152,7 +169,7 @@
|
||||
</label>
|
||||
<input type="text"
|
||||
x-model="lot_number"
|
||||
placeholder="{{ _('Es. LOT-2026-001 (opzionale)') }}"
|
||||
placeholder="{{ _('Es. LOT-2026-001') }}"
|
||||
class="tmf-input font-mono">
|
||||
</div>
|
||||
|
||||
@@ -166,7 +183,7 @@
|
||||
</label>
|
||||
<input type="text"
|
||||
x-model="serial_number"
|
||||
placeholder="{{ _('Es. SN-000123 (opzionale)') }}"
|
||||
placeholder="{{ _('Es. SN-000123') }}"
|
||||
class="tmf-input font-mono">
|
||||
</div>
|
||||
</div>
|
||||
@@ -248,9 +265,26 @@
|
||||
<!-- Card Footer -->
|
||||
<div class="px-5 py-3.5 border-t border-[var(--border-color)] bg-[var(--bg-secondary)]
|
||||
rounded-b-xl">
|
||||
<a :href="buildTaskUrl(recipe.id)"
|
||||
|
||||
{# What this recipe insists on before anyone measures. Said on the card
|
||||
rather than after the fact: a measurement taken without its lot cannot
|
||||
be traced back, and finding that out later is finding it out too late. #}
|
||||
<template x-if="missingTraceability(recipe).length">
|
||||
<p class="mb-2 flex items-start gap-1.5 text-xs text-amber-700 dark:text-amber-300">
|
||||
<svg class="w-3.5 h-3.5 shrink-0 mt-px" 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 x-text="'{{ _('Compila prima:') }} ' + missingTraceability(recipe).join(', ')"></span>
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<a :href="missingTraceability(recipe).length ? null : buildTaskUrl(recipe.id)"
|
||||
:aria-disabled="missingTraceability(recipe).length > 0"
|
||||
:class="missingTraceability(recipe).length
|
||||
? 'opacity-40 cursor-not-allowed pointer-events-none'
|
||||
: 'group-hover:shadow-md'"
|
||||
class="btn btn-primary w-full justify-center text-sm font-semibold
|
||||
group-hover:shadow-md transition-shadow duration-200">
|
||||
transition-shadow duration-200">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"/>
|
||||
</svg>
|
||||
|
||||
Reference in New Issue
Block a user