Merge lavoro/daitarn-trafilo-editor-quote
This commit is contained in:
@@ -1,4 +1,7 @@
|
|||||||
"""Maker blueprint - recipe creation and editing."""
|
"""Maker blueprint - recipe creation and editing."""
|
||||||
|
import json
|
||||||
|
import math
|
||||||
|
|
||||||
import requests as http_requests
|
import requests as http_requests
|
||||||
|
|
||||||
from flask import Blueprint, flash, jsonify, redirect, render_template, request, session, url_for
|
from flask import Blueprint, flash, jsonify, redirect, render_template, request, session, url_for
|
||||||
@@ -118,7 +121,41 @@ def task_editor(recipe_id: int):
|
|||||||
else:
|
else:
|
||||||
recipe["tasks"] = []
|
recipe["tasks"] = []
|
||||||
|
|
||||||
return render_template("maker/task_editor.html", recipe=recipe)
|
# #47/#48: liste e tolleranze generali dalle Impostazioni; chiave assente,
|
||||||
|
# vuota o non valida -> None -> il template usa le voci scritte a mano
|
||||||
|
settings = api_client.get("/api/settings")
|
||||||
|
if not isinstance(settings, dict) or settings.get("error"):
|
||||||
|
settings = {}
|
||||||
|
|
||||||
|
return render_template(
|
||||||
|
"maker/task_editor.html",
|
||||||
|
recipe=recipe,
|
||||||
|
list_measurement_types=_settings_string_list(settings.get("list_measurement_types")),
|
||||||
|
list_units=_settings_string_list(settings.get("list_units")),
|
||||||
|
general_tolerance_plus=_settings_tolerance(settings.get("general_tolerance_plus")),
|
||||||
|
general_tolerance_minus=_settings_tolerance(settings.get("general_tolerance_minus")),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _settings_string_list(value):
|
||||||
|
"""#47: array JSON di stringhe non vuote dalle Impostazioni, altrimenti None."""
|
||||||
|
try:
|
||||||
|
items = json.loads(value) if isinstance(value, str) else None
|
||||||
|
except ValueError:
|
||||||
|
return None
|
||||||
|
if not isinstance(items, list):
|
||||||
|
return None
|
||||||
|
items = [i.strip() for i in items if isinstance(i, str) and i.strip()]
|
||||||
|
return items or None
|
||||||
|
|
||||||
|
|
||||||
|
def _settings_tolerance(value):
|
||||||
|
"""#48: tolleranza generale (decimale >= 0) dalle Impostazioni, altrimenti None."""
|
||||||
|
try:
|
||||||
|
number = float(str(value).strip())
|
||||||
|
except ValueError:
|
||||||
|
return None
|
||||||
|
return number if math.isfinite(number) and number >= 0 else None
|
||||||
|
|
||||||
|
|
||||||
@maker_bp.route("/recipes/<int:recipe_id>/tasks/<int:task_id>/drawing")
|
@maker_bp.route("/recipes/<int:recipe_id>/tasks/<int:task_id>/drawing")
|
||||||
|
|||||||
@@ -767,18 +767,14 @@
|
|||||||
<!-- Measurement Type -->
|
<!-- Measurement Type -->
|
||||||
<div>
|
<div>
|
||||||
<label class="tmf-label text-xs">{{ _('Tipo') }}</label>
|
<label class="tmf-label text-xs">{{ _('Tipo') }}</label>
|
||||||
|
<!-- #47: lista da Impostazioni (list_measurement_types) o voci di default; un valore salvato fuori lista resta selezionabile -->
|
||||||
<select x-model="newSubtask.measurement_type"
|
<select x-model="newSubtask.measurement_type"
|
||||||
class="tmf-input">
|
class="tmf-input">
|
||||||
<option value="">-</option>
|
<option value="">-</option>
|
||||||
<option value="linear">{{ _('Lineare') }}</option>
|
<template x-for="o in measurementTypeOptions()" :key="o.value">
|
||||||
<option value="diameter">{{ _('Diametro') }}</option>
|
<option :value="o.value" :selected="o.value === newSubtask.measurement_type"
|
||||||
<option value="radius">{{ _('Raggio') }}</option>
|
x-text="o.label"></option>
|
||||||
<option value="angle">{{ _('Angolo') }}</option>
|
</template>
|
||||||
<option value="roughness">{{ _('Rugosita') }}</option>
|
|
||||||
<option value="torque">{{ _('Coppia') }}</option>
|
|
||||||
<option value="force">{{ _('Forza') }}</option>
|
|
||||||
<option value="weight">{{ _('Peso') }}</option>
|
|
||||||
<option value="other">{{ _('Altro') }}</option>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<!-- Nominal -->
|
<!-- Nominal -->
|
||||||
@@ -786,24 +782,20 @@
|
|||||||
<label class="tmf-label text-xs">{{ _('Nominale') }}</label>
|
<label class="tmf-label text-xs">{{ _('Nominale') }}</label>
|
||||||
<input type="number" step="any"
|
<input type="number" step="any"
|
||||||
x-model.number="newSubtask.nominal"
|
x-model.number="newSubtask.nominal"
|
||||||
|
@input="onNominalInput($event)"
|
||||||
class="tmf-input measure-value text-right"
|
class="tmf-input measure-value text-right"
|
||||||
placeholder="0.000">
|
placeholder="0.000">
|
||||||
</div>
|
</div>
|
||||||
<!-- Unit -->
|
<!-- Unit -->
|
||||||
<div>
|
<div>
|
||||||
<label class="tmf-label text-xs">{{ _('Unita') }}</label>
|
<label class="tmf-label text-xs">{{ _('Unita') }}</label>
|
||||||
|
<!-- #47: lista da Impostazioni (list_units) o voci di default; un valore salvato fuori lista resta selezionabile -->
|
||||||
<select x-model="newSubtask.unit"
|
<select x-model="newSubtask.unit"
|
||||||
class="tmf-input">
|
class="tmf-input">
|
||||||
<option value="mm">mm</option>
|
<template x-for="o in unitOptions()" :key="o.value">
|
||||||
<option value="cm">cm</option>
|
<option :value="o.value" :selected="o.value === newSubtask.unit"
|
||||||
<option value="m">m</option>
|
x-text="o.label"></option>
|
||||||
<option value="μm">μm</option>
|
</template>
|
||||||
<option value="in">in</option>
|
|
||||||
<option value="°">°</option>
|
|
||||||
<option value="g">g</option>
|
|
||||||
<option value="kg">kg</option>
|
|
||||||
<option value="N">N</option>
|
|
||||||
<option value="Nm">Nm</option>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -814,6 +806,7 @@
|
|||||||
<label class="tmf-label text-xs text-red-500">{{ _('LTL') }}</label>
|
<label class="tmf-label text-xs text-red-500">{{ _('LTL') }}</label>
|
||||||
<input type="number" step="any"
|
<input type="number" step="any"
|
||||||
x-model.number="newSubtask.ltl"
|
x-model.number="newSubtask.ltl"
|
||||||
|
@input="tolAuto = false"
|
||||||
class="tmf-input measure-value text-right"
|
class="tmf-input measure-value text-right"
|
||||||
placeholder="{{ _('Lim. Tol. Inf.') }}">
|
placeholder="{{ _('Lim. Tol. Inf.') }}">
|
||||||
</div>
|
</div>
|
||||||
@@ -835,10 +828,14 @@
|
|||||||
<label class="tmf-label text-xs text-red-500">{{ _('UTL') }}</label>
|
<label class="tmf-label text-xs text-red-500">{{ _('UTL') }}</label>
|
||||||
<input type="number" step="any"
|
<input type="number" step="any"
|
||||||
x-model.number="newSubtask.utl"
|
x-model.number="newSubtask.utl"
|
||||||
|
@input="tolAuto = false"
|
||||||
class="tmf-input measure-value text-right"
|
class="tmf-input measure-value text-right"
|
||||||
placeholder="{{ _('Lim. Tol. Sup.') }}">
|
placeholder="{{ _('Lim. Tol. Sup.') }}">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- #48: promemoria delle tolleranze generali (Impostazioni) -->
|
||||||
|
<p class="text-xs text-[var(--text-muted)] mt-2"
|
||||||
|
x-show="generalTolText()" x-text="generalTolText()"></p>
|
||||||
|
|
||||||
<!-- Form Actions -->
|
<!-- Form Actions -->
|
||||||
<div class="flex items-center gap-2 justify-end mt-4">
|
<div class="flex items-center gap-2 justify-end mt-4">
|
||||||
@@ -1227,6 +1224,27 @@ function taskEditor() {
|
|||||||
// Uscite numeriche della ricetta visione per task (#46), riempite da vision_recipe.js
|
// Uscite numeriche della ricetta visione per task (#46), riempite da vision_recipe.js
|
||||||
visionOutputs: {},
|
visionOutputs: {},
|
||||||
|
|
||||||
|
// ---- Liste e tolleranze generali dalle Impostazioni (#47, #48); null = non configurato ----
|
||||||
|
configuredTypes: {{ list_measurement_types|default(none)|tojson }},
|
||||||
|
configuredUnits: {{ list_units|default(none)|tojson }},
|
||||||
|
generalTolPlus: {{ general_tolerance_plus|default(none)|tojson }},
|
||||||
|
generalTolMinus: {{ general_tolerance_minus|default(none)|tojson }},
|
||||||
|
// #48: true = utl/ltl si riempiono da soli dal nominale (solo quota nuova, finche' non li tocca l'utente)
|
||||||
|
tolAuto: false,
|
||||||
|
// Voci scritte a mano usate quando la lista non e' configurata (#47)
|
||||||
|
defaultTypeOptions: [
|
||||||
|
{value: 'linear', label: {{ _('Lineare')|tojson }}},
|
||||||
|
{value: 'diameter', label: {{ _('Diametro')|tojson }}},
|
||||||
|
{value: 'radius', label: {{ _('Raggio')|tojson }}},
|
||||||
|
{value: 'angle', label: {{ _('Angolo')|tojson }}},
|
||||||
|
{value: 'roughness', label: {{ _('Rugosita')|tojson }}},
|
||||||
|
{value: 'torque', label: {{ _('Coppia')|tojson }}},
|
||||||
|
{value: 'force', label: {{ _('Forza')|tojson }}},
|
||||||
|
{value: 'weight', label: {{ _('Peso')|tojson }}},
|
||||||
|
{value: 'other', label: {{ _('Altro')|tojson }}}
|
||||||
|
],
|
||||||
|
defaultUnits: ['mm', 'cm', 'm', '\u03bcm', 'in', '\u00b0', 'g', 'kg', 'N', 'Nm'],
|
||||||
|
|
||||||
// ---- Delete modals ----
|
// ---- Delete modals ----
|
||||||
deleteTaskModal: false,
|
deleteTaskModal: false,
|
||||||
deleteTargetTask: null,
|
deleteTargetTask: null,
|
||||||
@@ -1698,8 +1716,61 @@ function taskEditor() {
|
|||||||
return Math.max(...task.subtasks.map(s => s.marker_number || 0)) + 1;
|
return Math.max(...task.subtasks.map(s => s.marker_number || 0)) + 1;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// #47: opzioni della select Tipo; il valore corrente fuori lista viene aggiunto per non perderlo
|
||||||
|
measurementTypeOptions() {
|
||||||
|
const known = v => (this.defaultTypeOptions.find(d => d.value === v) || {label: v}).label;
|
||||||
|
const opts = this.configuredTypes
|
||||||
|
? this.configuredTypes.map(v => ({value: v, label: known(v)})) // un tipo di default noto mantiene l'etichetta tradotta
|
||||||
|
: this.defaultTypeOptions.slice();
|
||||||
|
const cur = this.newSubtask.measurement_type;
|
||||||
|
if (cur && !opts.some(o => o.value === cur)) opts.push({value: cur, label: known(cur)});
|
||||||
|
return opts;
|
||||||
|
},
|
||||||
|
|
||||||
|
// #47: opzioni della select Unita, stessa logica del Tipo
|
||||||
|
unitOptions() {
|
||||||
|
const opts = (this.configuredUnits || this.defaultUnits).map(v => ({value: v, label: v}));
|
||||||
|
const cur = this.newSubtask.unit;
|
||||||
|
if (cur && !opts.some(o => o.value === cur)) opts.push({value: cur, label: cur});
|
||||||
|
return opts;
|
||||||
|
},
|
||||||
|
|
||||||
|
// #47: unita di partenza di una quota nuova: la prima della lista configurata, altrimenti mm
|
||||||
|
defaultUnit() {
|
||||||
|
return this.configuredUnits ? this.configuredUnits[0] : 'mm';
|
||||||
|
},
|
||||||
|
|
||||||
|
// #48: testo "Tolleranze generali: +x / -y" sotto i campi; vuoto se non configurate
|
||||||
|
generalTolText() {
|
||||||
|
const parts = [];
|
||||||
|
if (this.generalTolPlus !== null) parts.push('+' + this.generalTolPlus);
|
||||||
|
if (this.generalTolMinus !== null) parts.push('\u2212' + this.generalTolMinus);
|
||||||
|
return parts.length ? {{ _('Tolleranze generali')|tojson }} + ': ' + parts.join(' / ') : '';
|
||||||
|
},
|
||||||
|
|
||||||
|
// #48: su quota nuova, scrivendo il nominale riempie utl = nominale + plus e ltl = nominale - minus
|
||||||
|
onNominalInput(ev) {
|
||||||
|
if (!this.tolAuto || this.editingSubtaskId) return;
|
||||||
|
const raw = String(ev.target.value).trim();
|
||||||
|
const nominal = parseFloat(raw);
|
||||||
|
if (raw === '' || !isFinite(nominal)) {
|
||||||
|
// nominale svuotato: i limiti riempiti da noi tornano vuoti
|
||||||
|
this.newSubtask.utl = null;
|
||||||
|
this.newSubtask.ltl = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// decimali: quelli del nominale o delle tolleranze generali, al massimo 4
|
||||||
|
const dec = s => (String(s).split('.')[1] || '').length;
|
||||||
|
const decimals = Math.min(4, Math.max(dec(raw), dec(this.generalTolPlus), dec(this.generalTolMinus)));
|
||||||
|
const round = v => Number(v.toFixed(decimals));
|
||||||
|
if (this.generalTolPlus !== null) this.newSubtask.utl = round(nominal + this.generalTolPlus);
|
||||||
|
if (this.generalTolMinus !== null) this.newSubtask.ltl = round(nominal - this.generalTolMinus);
|
||||||
|
},
|
||||||
|
|
||||||
resetNewSubtask(taskId) {
|
resetNewSubtask(taskId) {
|
||||||
const task = this.tasks.find(t => t.id === taskId);
|
const task = this.tasks.find(t => t.id === taskId);
|
||||||
|
// #48: automatismo attivo solo se c'e' almeno una tolleranza generale
|
||||||
|
this.tolAuto = this.generalTolPlus !== null || this.generalTolMinus !== null;
|
||||||
this.newSubtask = {
|
this.newSubtask = {
|
||||||
marker_number: task ? this.nextMarkerNumber(task) : 1,
|
marker_number: task ? this.nextMarkerNumber(task) : 1,
|
||||||
description: '',
|
description: '',
|
||||||
@@ -1709,7 +1780,7 @@ function taskEditor() {
|
|||||||
uwl: null,
|
uwl: null,
|
||||||
lwl: null,
|
lwl: null,
|
||||||
ltl: null,
|
ltl: null,
|
||||||
unit: 'mm',
|
unit: this.defaultUnit(), // #47
|
||||||
vision_output: ''
|
vision_output: ''
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -1779,6 +1850,7 @@ function taskEditor() {
|
|||||||
// Open the same form used for adding, but pre-filled with existing data
|
// Open the same form used for adding, but pre-filled with existing data
|
||||||
this.editingSubtaskId = subtask.id;
|
this.editingSubtaskId = subtask.id;
|
||||||
this.addingSubtaskForTask = taskId;
|
this.addingSubtaskForTask = taskId;
|
||||||
|
this.tolAuto = false; // #48: una quota esistente non viene mai ricalcolata
|
||||||
this.newSubtask = {
|
this.newSubtask = {
|
||||||
marker_number: subtask.marker_number,
|
marker_number: subtask.marker_number,
|
||||||
description: subtask.description || '',
|
description: subtask.description || '',
|
||||||
|
|||||||
@@ -0,0 +1,164 @@
|
|||||||
|
"""#47/#48: liste Tipo/Unita e tolleranze generali dalle Impostazioni nel task editor."""
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from tests.conftest import api_get_router
|
||||||
|
|
||||||
|
_RECIPE_ROUTES = {
|
||||||
|
"/api/recipes/3": {"id": 3, "code": "R", "name": "R"},
|
||||||
|
"/api/recipes/3/tasks": [{
|
||||||
|
"id": 11, "title": "T", "task_type": "measure", "subtasks": [],
|
||||||
|
"file_path": None, "order_index": 1,
|
||||||
|
}],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _render(logged_in_client, mock_api_client, settings):
|
||||||
|
mock_api_client.get.side_effect = api_get_router({**_RECIPE_ROUTES, "/api/settings": settings})
|
||||||
|
resp = logged_in_client.get("/maker/recipes/3/tasks")
|
||||||
|
assert resp.status_code == 200
|
||||||
|
return resp.get_data(as_text=True)
|
||||||
|
|
||||||
|
|
||||||
|
class TestTaskEditorSettings:
|
||||||
|
def test_configured_lists_reach_template(self, logged_in_client, mock_api_client):
|
||||||
|
html = _render(logged_in_client, mock_api_client, {
|
||||||
|
"list_measurement_types": json.dumps(["Planarita", "Concentricita"]),
|
||||||
|
"list_units": json.dumps(["mm", "thou"]),
|
||||||
|
})
|
||||||
|
assert 'configuredTypes: ["Planarita", "Concentricita"]' in html
|
||||||
|
assert 'configuredUnits: ["mm", "thou"]' in html
|
||||||
|
# le select leggono le liste, non hanno piu' le option scritte a mano
|
||||||
|
assert 'x-for="o in measurementTypeOptions()"' in html
|
||||||
|
assert 'x-for="o in unitOptions()"' in html
|
||||||
|
assert '<option value="diameter">' not in html
|
||||||
|
|
||||||
|
def test_missing_keys_keep_hand_written_values(self, logged_in_client, mock_api_client):
|
||||||
|
for settings in ({}, {"list_measurement_types": "", "list_units": "non json"},
|
||||||
|
{"list_units": json.dumps({"a": 1})}, {"list_units": "[]"}):
|
||||||
|
html = _render(logged_in_client, mock_api_client, settings)
|
||||||
|
assert "configuredTypes: null," in html
|
||||||
|
assert "configuredUnits: null," in html
|
||||||
|
# le voci di oggi restano come default
|
||||||
|
assert "value: 'diameter'" in html
|
||||||
|
assert "defaultUnits: ['mm', 'cm'" in html
|
||||||
|
|
||||||
|
def test_settings_error_keeps_defaults(self, logged_in_client, mock_api_client):
|
||||||
|
html = _render(logged_in_client, mock_api_client, {"error": True, "detail": "x"})
|
||||||
|
assert "configuredTypes: null," in html
|
||||||
|
assert "generalTolPlus: null," in html
|
||||||
|
|
||||||
|
def test_general_tolerances_reach_template(self, logged_in_client, mock_api_client):
|
||||||
|
html = _render(logged_in_client, mock_api_client, {
|
||||||
|
"general_tolerance_plus": "0.1", "general_tolerance_minus": "0.25",
|
||||||
|
})
|
||||||
|
assert "generalTolPlus: 0.1," in html
|
||||||
|
assert "generalTolMinus: 0.25," in html
|
||||||
|
|
||||||
|
def test_invalid_tolerances_are_none(self, logged_in_client, mock_api_client):
|
||||||
|
for plus, minus in (("", "abc"), ("-1", "nan"), ("inf", None)):
|
||||||
|
settings = {"general_tolerance_plus": plus}
|
||||||
|
if minus is not None:
|
||||||
|
settings["general_tolerance_minus"] = minus
|
||||||
|
html = _render(logged_in_client, mock_api_client, settings)
|
||||||
|
assert "generalTolPlus: null," in html
|
||||||
|
assert "generalTolMinus: null," in html
|
||||||
|
|
||||||
|
|
||||||
|
# ---- comportamento JS (node): opzioni fuori lista e automatismo delle tolleranze ----
|
||||||
|
|
||||||
|
_NODE = shutil.which("node")
|
||||||
|
|
||||||
|
|
||||||
|
def _run_js(html, body):
|
||||||
|
"""Esegue taskEditor() estratto dalla pagina e poi `body` (che usa `ed`); stampa JSON."""
|
||||||
|
scripts = re.findall(r"<script(?![^>]*\bsrc=)[^>]*>(.*?)</script>", html, re.S | re.I)
|
||||||
|
src = next(s for s in scripts if "function taskEditor()" in s)
|
||||||
|
program = src + "\nconst ed = taskEditor();\nconst out = (function(){" + body + "})();\nconsole.log(JSON.stringify(out));"
|
||||||
|
# su file: la riga di comando di Windows non regge lo script intero
|
||||||
|
with tempfile.NamedTemporaryFile("w", suffix=".js", delete=False, encoding="utf-8") as fh:
|
||||||
|
fh.write(program)
|
||||||
|
try:
|
||||||
|
res = subprocess.run([_NODE, fh.name], capture_output=True, text=True,
|
||||||
|
encoding="utf-8", timeout=30)
|
||||||
|
finally:
|
||||||
|
os.unlink(fh.name)
|
||||||
|
assert res.returncode == 0, res.stderr
|
||||||
|
return json.loads(res.stdout)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(_NODE is None, reason="node non disponibile")
|
||||||
|
class TestTaskEditorJs:
|
||||||
|
def test_out_of_list_value_stays_selectable(self, logged_in_client, mock_api_client):
|
||||||
|
html = _render(logged_in_client, mock_api_client, {
|
||||||
|
"list_measurement_types": json.dumps(["Planarita"]),
|
||||||
|
"list_units": json.dumps(["thou"]),
|
||||||
|
})
|
||||||
|
out = _run_js(html, """
|
||||||
|
ed.newSubtask.measurement_type = 'Vecchio'; ed.newSubtask.unit = 'mm';
|
||||||
|
return {t: ed.measurementTypeOptions().map(o => o.value),
|
||||||
|
u: ed.unitOptions().map(o => o.value), d: ed.defaultUnit()};
|
||||||
|
""")
|
||||||
|
assert out["t"] == ["Planarita", "Vecchio"]
|
||||||
|
assert out["u"] == ["thou", "mm"]
|
||||||
|
assert out["d"] == "thou"
|
||||||
|
|
||||||
|
def test_default_lists_when_not_configured(self, logged_in_client, mock_api_client):
|
||||||
|
html = _render(logged_in_client, mock_api_client, {})
|
||||||
|
out = _run_js(html, """
|
||||||
|
return {t: ed.measurementTypeOptions().map(o => o.value),
|
||||||
|
u: ed.unitOptions().map(o => o.value), d: ed.defaultUnit()};
|
||||||
|
""")
|
||||||
|
assert out["t"][0] == "linear" and out["t"][-1] == "other"
|
||||||
|
assert out["u"][0] == "mm" and "Nm" in out["u"]
|
||||||
|
assert out["d"] == "mm"
|
||||||
|
|
||||||
|
def test_tolerance_autofill(self, logged_in_client, mock_api_client):
|
||||||
|
html = _render(logged_in_client, mock_api_client, {
|
||||||
|
"general_tolerance_plus": "0.1", "general_tolerance_minus": "0.2",
|
||||||
|
})
|
||||||
|
out = _run_js(html, """
|
||||||
|
ed.tasks = [{id: 1, subtasks: []}];
|
||||||
|
ed.resetNewSubtask(1);
|
||||||
|
const r = {};
|
||||||
|
ed.newSubtask.nominal = 10; ed.onNominalInput({target: {value: '10'}});
|
||||||
|
r.first = [ed.newSubtask.utl, ed.newSubtask.ltl];
|
||||||
|
// cambia ancora il nominale prima di toccare i limiti: ricalcola
|
||||||
|
ed.onNominalInput({target: {value: '12.5'}});
|
||||||
|
r.second = [ed.newSubtask.utl, ed.newSubtask.ltl];
|
||||||
|
// modifica a mano utl: smette di riempire
|
||||||
|
ed.newSubtask.utl = 99; ed.tolAuto = false;
|
||||||
|
ed.onNominalInput({target: {value: '20'}});
|
||||||
|
r.third = [ed.newSubtask.utl, ed.newSubtask.ltl];
|
||||||
|
r.text = ed.generalTolText();
|
||||||
|
return r;
|
||||||
|
""")
|
||||||
|
assert out["first"] == [10.1, 9.8]
|
||||||
|
assert out["second"] == [12.6, 12.3]
|
||||||
|
assert out["third"] == [99, 12.3]
|
||||||
|
assert "+0.1 / −0.2" in out["text"]
|
||||||
|
|
||||||
|
def test_no_autofill_without_tolerances_or_when_editing(self, logged_in_client, mock_api_client):
|
||||||
|
html = _render(logged_in_client, mock_api_client, {})
|
||||||
|
out = _run_js(html, """
|
||||||
|
ed.tasks = [{id: 1, subtasks: []}];
|
||||||
|
ed.resetNewSubtask(1);
|
||||||
|
ed.onNominalInput({target: {value: '10'}});
|
||||||
|
return {utl: ed.newSubtask.utl, ltl: ed.newSubtask.ltl, auto: ed.tolAuto,
|
||||||
|
text: ed.generalTolText()};
|
||||||
|
""")
|
||||||
|
assert out == {"utl": None, "ltl": None, "auto": False, "text": ""}
|
||||||
|
|
||||||
|
html = _render(logged_in_client, mock_api_client, {"general_tolerance_plus": "0.1"})
|
||||||
|
out = _run_js(html, """
|
||||||
|
ed.startEditSubtask({id: 5, marker_number: 1, nominal: 10, utl: null, ltl: null}, 1);
|
||||||
|
ed.onNominalInput({target: {value: '10'}});
|
||||||
|
return {utl: ed.newSubtask.utl, auto: ed.tolAuto};
|
||||||
|
""")
|
||||||
|
assert out == {"utl": None, "auto": False}
|
||||||
Reference in New Issue
Block a user