Exportbedingungen: Vergleichsart je Feldart statt nacktem Textfeld

Bisher tippte man den Wert immer von Hand ein - auch bei einem Haken, wo man
wissen musste, dass intern "true" steht, und bei einer Auswahl, wo ein
Vertipper stillschweigend nichts fand. Das ist der unangenehmste Fehlschlag:
Die CSV kommt einfach leer heraus, ohne Hinweis warum.

Jetzt bringt jede Bedingung die passende Eingabe mit:

  Ja/Nein    ein Haken
  Auswahl    "ist gleich" / "ist nicht gleich" plus das Dropdown der Optionen
  Zahl       "ist groesser =" / "ist =" / "ist kleiner =" plus Zahlenfeld
  Text       wie bisher freie Eingabe, leer = ohne Wert

Damit die Oberflaeche das kann, traegt /etiketten/spalten jetzt Art und
Auswahlwerte je Feld mit - vorher gab es nur Schluessel und Titel.

Der Filter bekommt dafuer ein op (=, !=, >=, <=), Vorgabe "=", damit
bestehende Vorlagen unveraendert weiterlaufen. Groesser/kleiner vergleicht
als Zahl und nimmt Komma wie Punkt; Zeilen ohne Wert treffen dabei NIE -
"leer" ist nicht kleiner als drei, es ist unvergleichbar. Eine unbekannte
Vergleichsart faellt auf "=" zurueck statt den Export zu zerreissen.

Die Sicherung traegt das op mit, wie schon das Feld selbst.

Sieben neue Tests, 547 gruen. Diesmal zusaetzlich in der laufenden Oberflaeche
durchgespielt: Alle vier Feldarten zeigen die richtige Eingabe, der Filter
wird gespeichert, und die CSV enthaelt bei "Laenge >= 5" genau die Artikel
mit 5 und 12 - nicht den mit 2 und nicht den ohne Wert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-10-16 16:18:48 +02:00
co-authored by Claude Opus 5
parent dd57d3356a
commit 8de222072c
5 changed files with 250 additions and 31 deletions
+98 -3
View File
@@ -26,7 +26,7 @@ from app.models import (
User,
)
from app.routers.etiketten import _pruefe_lbx, datei_herunterladen, spalten
from app.services.etiketten import csv_erzeugen, konfiguration_lesen
from app.services.etiketten import csv_erzeugen, konfiguration_lesen, verfuegbare_spalten
@pytest.fixture()
@@ -608,7 +608,9 @@ def test_feldfilter_uebersteht_die_sicherung(db, haken):
inhalt = export_backup_json(db=db, _=person).body
gesichert = _json.loads(inhalt)["etiketten_vorlagen"][0]
# Nach Pfad und Label, nie nach ID.
assert gesichert["feldfilter"] == [{"feld": "Kleidung::Verliehen", "wert": "ja"}]
assert gesichert["feldfilter"] == [
{"feld": "Kleidung::Verliehen", "op": "=", "wert": "ja"}
]
from app.database import Base
from sqlalchemy import create_engine
@@ -635,6 +637,99 @@ def test_feldfilter_uebersteht_die_sicherung(db, haken):
).one()
konf = konfiguration_lesen(neu)
# Auf die ID der ZWEITEN Instanz umgeschrieben, Wert unveraendert.
assert konf["feldfilter"] == [{"feld_id": neues_feld.id, "wert": "ja"}]
assert konf["feldfilter"] == [{"feld_id": neues_feld.id, "op": "=", "wert": "ja"}]
assert _namen(zweite, neu) == ["Jacke"]
zweite.close()
def test_zahlenfeld_groesser_gleich(db, kabel):
cat, fd = kabel
_artikel(db, cat, "Kurz", fd, "2")
_artikel(db, cat, "Mittel", fd, "5")
_artikel(db, cat, "Lang", fd, "10")
v = _vorlage(db, ["name"], art="products",
feldfilter=[{"feld_id": fd.id, "op": ">=", "wert": "5"}])
assert sorted(_namen(db, v)) == ["Lang", "Mittel"]
def test_zahlenfeld_kleiner_gleich_mit_komma(db, kabel):
"""Gespeichert wird der getippte Text – „2,5" muss als Zahl gelten."""
cat, fd = kabel
_artikel(db, cat, "Kurz", fd, "2,5")
_artikel(db, cat, "Lang", fd, "10")
v = _vorlage(db, ["name"], art="products",
feldfilter=[{"feld_id": fd.id, "op": "<=", "wert": "3"}])
assert _namen(db, v) == ["Kurz"]
def test_groesser_gleich_trifft_artikel_ohne_wert_nicht(db, kabel):
"""„leer" ist nicht kleiner als drei – es ist unvergleichbar."""
cat, fd = kabel
_artikel(db, cat, "Mit Wert", fd, "9")
_artikel(db, cat, "Ohne Wert")
for op in (">=", "<="):
v = _vorlage(db, ["name"], art="products",
feldfilter=[{"feld_id": fd.id, "op": op, "wert": "3"}])
assert "Ohne Wert" not in _namen(db, v)
def test_ungleich_bei_auswahlfeld(db):
cat = Category(name="Kleidung", tracking=CategoryTracking.object.value)
db.add(cat)
db.flush()
fd = FieldDefinition(
category_id=cat.id, label="Größe", key="groesse", field_type="select",
options=json.dumps(["S", "M", "L"]),
)
db.add(fd)
db.commit()
db.refresh(cat)
db.refresh(fd)
_artikel(db, cat, "Klein", fd, "S")
_artikel(db, cat, "Mittel", fd, "M")
v = _vorlage(db, ["name"], art="products",
feldfilter=[{"feld_id": fd.id, "op": "!=", "wert": "S"}])
assert _namen(db, v) == ["Mittel"]
def test_ungleich_bei_ja_nein(db, haken):
cat, fd = haken
_artikel(db, cat, "Jacke", fd, "true")
_artikel(db, cat, "Hose", fd, "false")
v = _vorlage(db, ["name"], art="products",
feldfilter=[{"feld_id": fd.id, "op": "!=", "wert": "ja"}])
assert _namen(db, v) == ["Hose"]
def test_unbekannte_vergleichsart_faellt_auf_gleich_zurueck(db, haken):
cat, fd = haken
v = EtikettenVorlage(name="Krumm", art="products", konfiguration=json.dumps({
"spalten": ["name"],
"feldfilter": [{"feld_id": fd.id, "op": "~~", "wert": "ja"}],
}))
db.add(v)
db.commit()
assert konfiguration_lesen(v)["feldfilter"][0]["op"] == "="
def test_auswahlfeld_liefert_seine_optionen_zur_spalte(db):
"""Die Oberflaeche kann nur dann ein Dropdown anbieten."""
cat = Category(name="Kleidung", tracking=CategoryTracking.object.value)
db.add(cat)
db.flush()
db.add(FieldDefinition(
category_id=cat.id, label="Größe", key="groesse", field_type="select",
options=json.dumps(["S", "M"]),
))
db.commit()
spalten = verfuegbare_spalten(db, "products", [cat.id])
feld = [s for s in spalten if s["schluessel"].startswith("feld:")][0]
assert feld["typ"] == "select"
assert feld["optionen"] == ["S", "M"]