|
|
|
@@ -420,20 +420,34 @@
|
|
|
|
|
{# ================================================================
|
|
|
|
|
MEASUREMENT TIMER BANNER
|
|
|
|
|
================================================================ #}
|
|
|
|
|
{# Past zero the counter turns round: it stops saying how long is left and starts
|
|
|
|
|
saying how late the line is, in red. A delay has to be read, not deduced. #}
|
|
|
|
|
<div x-show="timerActive"
|
|
|
|
|
x-transition
|
|
|
|
|
x-cloak
|
|
|
|
|
class="shrink-0 bg-amber-50 dark:bg-amber-900/20 border-t border-amber-300 dark:border-amber-700 px-4 py-2">
|
|
|
|
|
class="shrink-0 border-t px-4 py-2"
|
|
|
|
|
:class="isOverdue
|
|
|
|
|
? 'bg-red-50 dark:bg-red-900/30 border-red-400 dark:border-red-700'
|
|
|
|
|
: 'bg-amber-50 dark:bg-amber-900/20 border-amber-300 dark:border-amber-700'">
|
|
|
|
|
<div class="flex items-center justify-center gap-3">
|
|
|
|
|
<svg class="w-5 h-5 text-amber-600 animate-pulse" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
|
|
|
<svg class="w-5 h-5 animate-pulse"
|
|
|
|
|
:class="isOverdue ? 'text-red-600' : 'text-amber-600'"
|
|
|
|
|
fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
|
|
|
|
</svg>
|
|
|
|
|
<span class="text-sm font-medium text-amber-800 dark:text-amber-200">
|
|
|
|
|
<span x-show="!isOverdue" class="text-sm font-medium text-amber-800 dark:text-amber-200">
|
|
|
|
|
{{ _('Prossima misurazione tra') }}
|
|
|
|
|
</span>
|
|
|
|
|
<span class="text-lg font-bold font-mono text-amber-900 dark:text-amber-100 bg-amber-100 dark:bg-amber-900/40 px-2 py-0.5 rounded"
|
|
|
|
|
<span x-show="isOverdue" class="text-sm font-bold text-red-800 dark:text-red-200">
|
|
|
|
|
{{ _('Misurazione in ritardo di') }}
|
|
|
|
|
</span>
|
|
|
|
|
<span class="text-lg font-bold font-mono px-2 py-0.5 rounded"
|
|
|
|
|
:class="isOverdue
|
|
|
|
|
? 'text-red-900 dark:text-red-100 bg-red-100 dark:bg-red-900/40'
|
|
|
|
|
: 'text-amber-900 dark:text-amber-100 bg-amber-100 dark:bg-amber-900/40'"
|
|
|
|
|
x-text="timerDisplay"></span>
|
|
|
|
|
<span class="text-xs text-amber-600 dark:text-amber-400">
|
|
|
|
|
<span class="text-xs"
|
|
|
|
|
:class="isOverdue ? 'text-red-600 dark:text-red-300' : 'text-amber-600 dark:text-amber-400'">
|
|
|
|
|
({{ _('Ciclo') }} #<span x-text="cycleCount"></span>)
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
@@ -474,8 +488,11 @@
|
|
|
|
|
<span class="text-sm font-semibold text-amber-900 dark:text-amber-100">
|
|
|
|
|
{{ _('Linea ferma') }}
|
|
|
|
|
</span>
|
|
|
|
|
{# A line stopped past its interval is frozen at a delay, not at a wait. Both
|
|
|
|
|
read as mm:ss, so the wording is what has to change. #}
|
|
|
|
|
<span class="text-xs text-amber-800 dark:text-amber-200">
|
|
|
|
|
{{ _('Il conto alla rovescia è congelato a') }}
|
|
|
|
|
<span x-show="timerRemaining > 0">{{ _('Il conto alla rovescia è congelato a') }}</span>
|
|
|
|
|
<span x-show="timerRemaining <= 0">{{ _('Misurazione in ritardo di') }}</span>
|
|
|
|
|
<span class="font-mono font-bold" x-text="timerDisplay"></span>
|
|
|
|
|
— {{ _('serve il capoturno per riprendere') }}
|
|
|
|
|
</span>
|
|
|
|
@@ -579,6 +596,17 @@
|
|
|
|
|
</svg>
|
|
|
|
|
{{ _('Fine ciclo misura') }}
|
|
|
|
|
</button>
|
|
|
|
|
{# Girare il pezzo e rimisurare, senza chiudere il ciclo: a second reading
|
|
|
|
|
of the same part is not a new one, and must not restart the interval. #}
|
|
|
|
|
<button x-show="isComplete && productionStarted"
|
|
|
|
|
x-transition
|
|
|
|
|
@click="remeasure()"
|
|
|
|
|
class="btn btn-secondary text-xs shrink-0 gap-1 py-1.5 px-3">
|
|
|
|
|
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M16.023 9.348h4.992V4.356M3.977 14.652H8.97v4.992m10.058-9.348a8.25 8.25 0 00-14.02-3.34L3.977 9.348m0 5.304a8.25 8.25 0 0014.02 3.34l1.03-1.03"/>
|
|
|
|
|
</svg>
|
|
|
|
|
{{ _('Rimisura') }}
|
|
|
|
|
</button>
|
|
|
|
|
<button x-show="cycleConfirmed"
|
|
|
|
|
x-transition
|
|
|
|
|
@click="goToNextTask()"
|
|
|
|
@@ -610,6 +638,37 @@
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{# ================================================================
|
|
|
|
|
È ORA DI MISURARE — the interval expired on a task that is not the
|
|
|
|
|
measurement. The operator is told why the screen is about to change, then
|
|
|
|
|
taken there; there is no way to dismiss it, because putting the measurement
|
|
|
|
|
off is exactly what the interval exists to prevent.
|
|
|
|
|
================================================================ #}
|
|
|
|
|
<div x-show="announcedRedirect"
|
|
|
|
|
x-transition
|
|
|
|
|
x-cloak
|
|
|
|
|
class="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm">
|
|
|
|
|
<div class="bg-[var(--bg-card)] rounded-2xl shadow-2xl p-8 max-w-sm mx-4 text-center border-2 border-red-500">
|
|
|
|
|
<div class="inline-flex items-center justify-center w-16 h-16 rounded-full bg-red-100 dark:bg-red-900/30 mb-4">
|
|
|
|
|
<svg class="w-8 h-8 text-red-600 animate-pulse" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24">
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
|
|
|
|
</svg>
|
|
|
|
|
</div>
|
|
|
|
|
<h3 class="text-lg font-bold text-[var(--text-primary)] mb-1">{{ _('È ora di misurare') }}</h3>
|
|
|
|
|
<p class="text-sm text-[var(--text-secondary)] mb-5">
|
|
|
|
|
{{ _('Ritorno alla misurazione tra') }}
|
|
|
|
|
<span class="font-bold font-mono" x-text="redirectIn"></span> s
|
|
|
|
|
</p>
|
|
|
|
|
<button @click="goToMeasurementTask()"
|
|
|
|
|
class="btn w-full justify-center gap-2 bg-red-600 hover:bg-red-700 text-white font-bold py-2.5">
|
|
|
|
|
{{ _('Vai alla misura') }}
|
|
|
|
|
<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 5l7 7-7 7"/>
|
|
|
|
|
</svg>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{# ================================================================
|
|
|
|
|
COMPLETION OVERLAY
|
|
|
|
|
================================================================ #}
|
|
|
|
@@ -659,9 +718,10 @@
|
|
|
|
|
{{ _('Riepilogo') }}
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
{# Production phase: confirm the cycle (starts the interval timer) #}
|
|
|
|
|
{# Production phase: confirm the cycle. Goes through confirmCycle so the
|
|
|
|
|
server records it - setting the flag here only looked like it had. #}
|
|
|
|
|
<button x-show="productionStarted"
|
|
|
|
|
@click="showCompletionOverlay = false; cycleConfirmed = true"
|
|
|
|
|
@click="confirmCycle()"
|
|
|
|
|
class="btn btn-primary flex-1 justify-center gap-2">
|
|
|
|
|
{{ _('Conferma ciclo') }}
|
|
|
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
|
|
@@ -689,6 +749,18 @@
|
|
|
|
|
</svg>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{# The piece can be turned over here, where the operator actually is when the
|
|
|
|
|
quotes are done - not only from the bar behind the overlay. #}
|
|
|
|
|
<button x-show="productionStarted"
|
|
|
|
|
@click="remeasure()"
|
|
|
|
|
class="mt-3 w-full text-sm text-[var(--text-secondary)] hover:text-primary
|
|
|
|
|
inline-flex items-center justify-center gap-1.5 py-1">
|
|
|
|
|
<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="M16.023 9.348h4.992V4.356M3.977 14.652H8.97v4.992m10.058-9.348a8.25 8.25 0 00-14.02-3.34L3.977 9.348m0 5.304a8.25 8.25 0 0014.02 3.34l1.03-1.03"/>
|
|
|
|
|
</svg>
|
|
|
|
|
{{ _('Girare il pezzo e rimisurare') }}
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
@@ -761,6 +833,9 @@
|
|
|
|
|
<script src="{{ url_for('static', filename='js/numpad.js') }}"></script>
|
|
|
|
|
<script src="{{ url_for('static', filename='js/annotation-viewer.js') }}?v=6"></script>
|
|
|
|
|
<script src="{{ url_for('static', filename='js/caliper.js') }}"></script>
|
|
|
|
|
{# The clock's rules - how a delay reads, when the buzzer goes, where the
|
|
|
|
|
measurement lives - are shared with the other screens rather than restated. #}
|
|
|
|
|
<script src="{{ url_for('static', filename='js/production-clock.js') }}"></script>
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
/**
|
|
|
|
@@ -797,6 +872,13 @@ function taskExecute() {
|
|
|
|
|
// This is read back from the server on every load instead.
|
|
|
|
|
productionRun: null,
|
|
|
|
|
productionError: '',
|
|
|
|
|
// The measurement tasks of this recipe, in order: the loop the operator stays
|
|
|
|
|
// in once production starts, and what says which task closes a cycle.
|
|
|
|
|
measurementTaskIds: [],
|
|
|
|
|
// The interval has elapsed and it has been said out loud - buzzer, and the
|
|
|
|
|
// operator on their way back to the measurement. Kept so it is said once.
|
|
|
|
|
dueAnnounced: false,
|
|
|
|
|
redirectIn: 0,
|
|
|
|
|
|
|
|
|
|
// ---- Cycle & workflow state ----
|
|
|
|
|
cycleConfirmed: false,
|
|
|
|
@@ -917,6 +999,7 @@ function taskExecute() {
|
|
|
|
|
this.productionRun = run;
|
|
|
|
|
this.productionStarted = run.status !== 'closed';
|
|
|
|
|
this.cycleCount = run.cycle_count;
|
|
|
|
|
this.measurementTaskIds = run.measurement_task_ids || [];
|
|
|
|
|
|
|
|
|
|
const seconds = run.seconds_to_next_measurement;
|
|
|
|
|
if (seconds === null || seconds === undefined) {
|
|
|
|
@@ -1078,17 +1161,26 @@ function taskExecute() {
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// ---- Confirm measurement cycle (Fine ciclo misura) ----
|
|
|
|
|
/* A cycle spans every measurement task of the recipe, so this one only closes
|
|
|
|
|
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() {
|
|
|
|
|
this.cycleConfirmed = true;
|
|
|
|
|
this.showCompletionOverlay = false;
|
|
|
|
|
|
|
|
|
|
// Recorded server-side so the count and the next deadline outlive this page.
|
|
|
|
|
if (this.productionRun) {
|
|
|
|
|
const countBefore = this.cycleCount;
|
|
|
|
|
const run = await this.postProduction(
|
|
|
|
|
'{{ url_for("measure.api_complete_cycle", run_id=0) }}'.replace('/0/', '/' + this.productionRun.id + '/'),
|
|
|
|
|
{ task_id: this.task.id },
|
|
|
|
|
);
|
|
|
|
|
if (run) {
|
|
|
|
|
this.adoptProductionRun(run);
|
|
|
|
|
// Cycle still open: on to the next measurement task, interval untouched.
|
|
|
|
|
if (run.cycle_count === countBefore && this.nextMeasurementTaskId) {
|
|
|
|
|
this.goToTask(this.nextMeasurementTaskId);
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@@ -1101,6 +1193,25 @@ function taskExecute() {
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/* Rimisura: turn the piece over and measure it again, inside the same cycle.
|
|
|
|
|
|
|
|
|
|
Not a new cycle - the deadline and the count stay where they are, so a second
|
|
|
|
|
reading of the same part does not buy another interval's worth of time. Both
|
|
|
|
|
readings stay in the statistics: that is what they are for. */
|
|
|
|
|
async remeasure() {
|
|
|
|
|
if (this.productionRun) {
|
|
|
|
|
const run = await this.postProduction(
|
|
|
|
|
'{{ url_for("measure.api_remeasure", run_id=0) }}'.replace('/0/', '/' + this.productionRun.id + '/'),
|
|
|
|
|
{ task_id: this.task.id },
|
|
|
|
|
);
|
|
|
|
|
// Refused - the line is stopped, or the production is closed. The screen
|
|
|
|
|
// stays as it is: clearing it would hide the reason.
|
|
|
|
|
if (!run) return;
|
|
|
|
|
this.adoptProductionRun(run);
|
|
|
|
|
}
|
|
|
|
|
this.resetForNewCycle();
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/* POST to a production endpoint, returning the updated run or null on failure. */
|
|
|
|
|
async postProduction(url, body) {
|
|
|
|
|
this.productionError = '';
|
|
|
|
@@ -1125,18 +1236,34 @@ function taskExecute() {
|
|
|
|
|
|
|
|
|
|
// ---- Measurement timer ----
|
|
|
|
|
/* Ticks locally for a smooth display, but the number it starts from always comes
|
|
|
|
|
from the server, and every page load resynchronises it. */
|
|
|
|
|
from the server, and every page load resynchronises it.
|
|
|
|
|
It does not stop at zero: past the deadline it keeps counting the other way,
|
|
|
|
|
and the banner reads the delay off it. */
|
|
|
|
|
startCountdownFrom(seconds) {
|
|
|
|
|
this.stopMeasurementTimer();
|
|
|
|
|
this.timerRemaining = seconds;
|
|
|
|
|
this.timerActive = true;
|
|
|
|
|
// Landing on a page that is already late is the same event as going late
|
|
|
|
|
// while sitting on it; being back in time re-arms the announcement.
|
|
|
|
|
if (seconds <= 0) {
|
|
|
|
|
this.onMeasurementDue();
|
|
|
|
|
} else {
|
|
|
|
|
this.dueAnnounced = false;
|
|
|
|
|
this.redirectIn = 0;
|
|
|
|
|
}
|
|
|
|
|
var self = this;
|
|
|
|
|
this._timerInterval = setInterval(function () {
|
|
|
|
|
self.timerRemaining--;
|
|
|
|
|
if (self.timerRemaining <= 0) {
|
|
|
|
|
self.onTimerExpired();
|
|
|
|
|
}
|
|
|
|
|
}, 1000);
|
|
|
|
|
this._timerInterval = setInterval(function () { self.onSecond(); }, 1000);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
onSecond() {
|
|
|
|
|
const wasAnnounced = this.dueAnnounced;
|
|
|
|
|
this.timerRemaining--;
|
|
|
|
|
if (this.timerRemaining <= 0) this.onMeasurementDue();
|
|
|
|
|
|
|
|
|
|
if (wasAnnounced && this.redirectIn > 0) {
|
|
|
|
|
this.redirectIn--;
|
|
|
|
|
if (this.redirectIn === 0) this.goToMeasurementTask();
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
stopMeasurementTimer() {
|
|
|
|
@@ -1147,32 +1274,79 @@ function taskExecute() {
|
|
|
|
|
this.timerActive = false;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
onTimerExpired() {
|
|
|
|
|
this.stopMeasurementTimer();
|
|
|
|
|
this.playBuzzer();
|
|
|
|
|
// Reset for new measurement cycle
|
|
|
|
|
/* The interval has run out. Said once, then acted on.
|
|
|
|
|
|
|
|
|
|
Someone already measuring is late, not lost: the banner turns red and they
|
|
|
|
|
are left alone. Taking them elsewhere mid-cycle would wipe quotes they can
|
|
|
|
|
see on the screen and gain nothing.
|
|
|
|
|
|
|
|
|
|
Otherwise a new cycle starts, and it starts at the first measurement task of
|
|
|
|
|
the recipe - re-arming wherever the operator happens to be standing would
|
|
|
|
|
skip whatever comes before it and still count as a full cycle. */
|
|
|
|
|
onMeasurementDue() {
|
|
|
|
|
if (this.dueAnnounced) return;
|
|
|
|
|
this.dueAnnounced = true;
|
|
|
|
|
window.ProductionClock.playBuzzer();
|
|
|
|
|
|
|
|
|
|
if (this.isMeasurementTaskOfRun && !this.cycleConfirmed) return;
|
|
|
|
|
|
|
|
|
|
if (this.task.id === this.measurementTaskIds[0]) {
|
|
|
|
|
this.resetForNewCycle();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
this.redirectIn = this.measurementTaskUrl ? 5 : 0;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/* Clear the screen for another pass over the same quotes.
|
|
|
|
|
Local only: everything already measured is on the server, and the next
|
|
|
|
|
reading is saved beside it rather than replacing it. */
|
|
|
|
|
resetForNewCycle() {
|
|
|
|
|
this.cycleConfirmed = false;
|
|
|
|
|
this.showCompletionOverlay = false;
|
|
|
|
|
this.measurements = [];
|
|
|
|
|
this.currentIndex = 0;
|
|
|
|
|
this.currentValue = null;
|
|
|
|
|
this.errorMessage = '';
|
|
|
|
|
this.inputStartedAt = Date.now();
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
playBuzzer() {
|
|
|
|
|
try {
|
|
|
|
|
var ctx = new (window.AudioContext || window.webkitAudioContext)();
|
|
|
|
|
// 3 short beeps
|
|
|
|
|
[0, 0.25, 0.5].forEach(function (delay) {
|
|
|
|
|
var osc = ctx.createOscillator();
|
|
|
|
|
var gain = ctx.createGain();
|
|
|
|
|
osc.connect(gain);
|
|
|
|
|
gain.connect(ctx.destination);
|
|
|
|
|
osc.frequency.value = 880;
|
|
|
|
|
osc.type = 'square';
|
|
|
|
|
gain.gain.value = 0.3;
|
|
|
|
|
osc.start(ctx.currentTime + delay);
|
|
|
|
|
osc.stop(ctx.currentTime + delay + 0.15);
|
|
|
|
|
});
|
|
|
|
|
} catch (_) {}
|
|
|
|
|
// ---- Where the measurement lives ----
|
|
|
|
|
|
|
|
|
|
get isMeasurementTaskOfRun() {
|
|
|
|
|
return this.measurementTaskIds.indexOf(this.task.id) !== -1;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
get measurementTaskUrl() {
|
|
|
|
|
return window.ProductionClock.measurementUrl(
|
|
|
|
|
this.productionRun, '{{ url_for("measure.task_execute", task_id=0) }}',
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/* The next measurement task of the recipe, or null if this is the last one -
|
|
|
|
|
which is the one that closes the cycle and restarts the interval. */
|
|
|
|
|
get nextMeasurementTaskId() {
|
|
|
|
|
const ids = this.measurementTaskIds;
|
|
|
|
|
const idx = ids.indexOf(this.task.id);
|
|
|
|
|
if (idx === -1 || idx >= ids.length - 1) return null;
|
|
|
|
|
return ids[idx + 1];
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
get isOverdue() {
|
|
|
|
|
return this.timerActive && this.timerRemaining <= 0;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
get announcedRedirect() {
|
|
|
|
|
return this.dueAnnounced && this.redirectIn > 0;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
goToMeasurementTask() {
|
|
|
|
|
const url = this.measurementTaskUrl;
|
|
|
|
|
if (url) window.location.href = url;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
goToTask(taskId) {
|
|
|
|
|
window.location.href =
|
|
|
|
|
'{{ url_for("measure.task_execute", task_id=0) }}'.replace('/0', '/' + taskId);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// ---- Avvio Produzione ----
|
|
|
|
@@ -1224,18 +1398,35 @@ function taskExecute() {
|
|
|
|
|
await this.confirmCycle();
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/* mm:ss of the distance from the deadline, either side of it. The direction is
|
|
|
|
|
carried by the wording in the banner, not by a minus sign. */
|
|
|
|
|
get timerDisplay() {
|
|
|
|
|
var m = Math.floor(this.timerRemaining / 60);
|
|
|
|
|
var s = this.timerRemaining % 60;
|
|
|
|
|
return (m < 10 ? '0' : '') + m + ':' + (s < 10 ? '0' : '') + s;
|
|
|
|
|
return window.ProductionClock.format(this.timerRemaining);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// ---- Navigate to next task (Completato) ----
|
|
|
|
|
/* With a production open the operator stays in the measurement loop until the
|
|
|
|
|
capoturno closes it: after the last measurement task they go back to the
|
|
|
|
|
first, not out to the summary. Before production starts, and on the
|
|
|
|
|
documental tasks that come before the measurement, the plain sequence
|
|
|
|
|
applies - that is the run-up, not the loop. */
|
|
|
|
|
goToNextTask() {
|
|
|
|
|
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
|
|
|
|
|
// same page to arrive at the same state would only cost a round trip.
|
|
|
|
|
if (next === this.task.id) {
|
|
|
|
|
this.resetForNewCycle();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
this.goToTask(next);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const taskIds = window.__allTaskIds || [];
|
|
|
|
|
const currentIdx = taskIds.indexOf(this.task.id);
|
|
|
|
|
if (currentIdx >= 0 && currentIdx < taskIds.length - 1) {
|
|
|
|
|
window.location.href = '{{ url_for("measure.task_execute", task_id=0) }}'.replace('/0', '/' + taskIds[currentIdx + 1]);
|
|
|
|
|
this.goToTask(taskIds[currentIdx + 1]);
|
|
|
|
|
} else {
|
|
|
|
|
this.goToSummary();
|
|
|
|
|
}
|
|
|
|
|