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:
co-authored by
Claude Opus 5
parent
dd57d3356a
commit
8de222072c
@@ -20,6 +20,7 @@ from typing import Any, Callable
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from .fields import options_list
|
||||
from ..models import (
|
||||
Category,
|
||||
EtikettenVorlage,
|
||||
@@ -192,7 +193,15 @@ def feld_spalten(db: Session, art: str, kategorie_ids: list[int]) -> list[dict]:
|
||||
continue
|
||||
gesehen.add(fd.id)
|
||||
titel = f"{fd.label} ({fd.unit})" if fd.unit else fd.label
|
||||
spalten.append({"schluessel": f"{FELD_PRAEFIX}{fd.id}", "titel": titel})
|
||||
# Art und Auswahlwerte kommen mit: Erst damit kann die Oberflaeche beim
|
||||
# Wertefilter eine Checkbox, ein Dropdown oder ein Zahlenfeld anbieten
|
||||
# statt eines nackten Textfelds, in das man sich vertippt.
|
||||
spalten.append({
|
||||
"schluessel": f"{FELD_PRAEFIX}{fd.id}",
|
||||
"titel": titel,
|
||||
"typ": fd.field_type,
|
||||
"optionen": options_list(fd),
|
||||
})
|
||||
return spalten
|
||||
|
||||
|
||||
@@ -242,7 +251,14 @@ def konfiguration_lesen(vorlage: EtikettenVorlage) -> dict:
|
||||
if not str(fid).lstrip("-").isdigit():
|
||||
continue
|
||||
wert = eintrag.get("wert", "")
|
||||
feldfilter.append({"feld_id": int(fid), "wert": str(wert) if wert is not None else ""})
|
||||
op = eintrag.get("op", "=")
|
||||
if op not in ("=", "!=", ">=", "<="):
|
||||
op = "="
|
||||
feldfilter.append({
|
||||
"feld_id": int(fid),
|
||||
"op": op,
|
||||
"wert": str(wert) if wert is not None else "",
|
||||
})
|
||||
return {
|
||||
"spalten": spalten,
|
||||
"kategorie_ids": kategorie_ids,
|
||||
@@ -274,7 +290,15 @@ def konfiguration_schreiben(
|
||||
)
|
||||
|
||||
|
||||
def _feld_passt(fd: FieldDefinition, bedingung: str, ist: str | None) -> bool:
|
||||
def _als_zahl(text: str | None) -> float | None:
|
||||
"""Zahl aus dem gespeicherten Text – Komma gilt wie Punkt."""
|
||||
try:
|
||||
return float(str(text).replace(",", "."))
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
|
||||
def _feld_passt(fd: FieldDefinition, op: str, bedingung: str, ist: str | None) -> bool:
|
||||
"""Trifft der gespeicherte Wert die Bedingung?
|
||||
|
||||
Ja/Nein ist der heikle Fall: Nimmt man den Haken weg, loescht
|
||||
@@ -289,8 +313,20 @@ def _feld_passt(fd: FieldDefinition, bedingung: str, ist: str | None) -> bool:
|
||||
if fd.field_type == FieldType.boolean.value:
|
||||
from .fields import coerce_value
|
||||
|
||||
return (coerce_value(fd, ist) or "false") == (coerce_value(fd, bedingung) or "false")
|
||||
return (ist or "").casefold() == (bedingung or "").strip().casefold()
|
||||
gleich = (coerce_value(fd, ist) or "false") == (coerce_value(fd, bedingung) or "false")
|
||||
return not gleich if op == "!=" else gleich
|
||||
|
||||
if op in (">=", "<="):
|
||||
# Groesser/kleiner gibt es nur auf Zahlen. Ohne Wert oder mit
|
||||
# unlesbarem Text trifft die Bedingung nie - „leer" ist nicht kleiner
|
||||
# als drei, es ist unvergleichbar.
|
||||
links, rechts = _als_zahl(ist), _als_zahl(bedingung)
|
||||
if links is None or rechts is None:
|
||||
return False
|
||||
return links >= rechts if op == ">=" else links <= rechts
|
||||
|
||||
gleich = (ist or "").casefold() == (bedingung or "").strip().casefold()
|
||||
return not gleich if op == "!=" else gleich
|
||||
|
||||
|
||||
def _nach_feldwerten(db: Session, art: str, zeilen: list, feldfilter: list[dict]) -> list:
|
||||
@@ -307,7 +343,7 @@ def _nach_feldwerten(db: Session, art: str, zeilen: list, feldfilter: list[dict]
|
||||
for eintrag in feldfilter:
|
||||
fd = db.get(FieldDefinition, eintrag["feld_id"])
|
||||
if fd is not None:
|
||||
bedingungen.append((fd, eintrag["wert"]))
|
||||
bedingungen.append((fd, eintrag.get("op", "="), eintrag["wert"]))
|
||||
if not bedingungen:
|
||||
return zeilen
|
||||
|
||||
@@ -316,7 +352,7 @@ def _nach_feldwerten(db: Session, art: str, zeilen: list, feldfilter: list[dict]
|
||||
for zeile in zeilen:
|
||||
produkt_id = zeile.product_id if art == "items" else getattr(zeile, "id", None)
|
||||
eigene = werte.get(produkt_id, {})
|
||||
if all(_feld_passt(fd, wert, eigene.get(fd.id)) for fd, wert in bedingungen):
|
||||
if all(_feld_passt(fd, op, wert, eigene.get(fd.id)) for fd, op, wert in bedingungen):
|
||||
passend.append(zeile)
|
||||
return passend
|
||||
|
||||
|
||||
Reference in New Issue
Block a user