Artikelsuche findet die Marke - und Loeschen fragt erst nach dem Bestand
Die Suche verglich bisher einen einzigen Textschnipsel gegen den Namen. Wer
"Vogel Jack and Jones" eintippte, bekam nichts: Dieser Ausdruck steht in
keinem Artikelnamen, und die Marke wurde ueberhaupt nie durchsucht. Jetzt
wird der Suchtext in Woerter zerlegt, jedes muss in Name oder Marke
vorkommen, die Reihenfolge ist egal.
Gefiltert wird in Python und nicht per ILIKE, weil normalisieren() Umlaute
aufloest ("Muehle" findet "Mühle") - auf der Spalte faende ein ILIKE den
gespeicherten Wert nicht wieder. Dieselbe Form nutzt recipes.zuordnung_ziele
schon laenger. Alle Artikellisten haengen an dieser einen Route, die
Verbesserung wirkt also auch bei Gegenstaenden und beim Ein-/Auslagern.
Die Artikelzeile in der App zeigt nun die Marke, sonst waere ein Treffer
darauf nicht zu erklaeren.
Dazu die zweite Haelfte: delete_product hatte keinerlei Schutz und riss
Chargen, Einzelstuecke, Barcodes, Bilder und den ganzen Bewegungsverlauf per
Kaskade mit. Jetzt lehnt der Server mit 409 ab, solange Einzelstuecke oder
Chargen dranhaengen. Bewegungen allein halten bewusst nicht auf - sonst waere
jeder je benutzte Artikel fuer immer unloeschbar. Der Bestaetigungstext im
Web versprach den Verlust des Bestands und stimmte damit nicht mehr.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
94f8ed4db9
commit
264d3886f3
@@ -0,0 +1,90 @@
|
||||
"""Artikelsuche über Name UND Marke, wortweise.
|
||||
|
||||
Vorher verglich ein einzelnes ILIKE den ganzen Suchtext gegen den Namen. Wer
|
||||
"Vogel Jack and Jones" eintippte, bekam nichts – dieser Ausdruck steht in
|
||||
keinem Artikelnamen. Gesucht wird jetzt Wort für Wort über Name und Marke
|
||||
zusammen, in beliebiger Reihenfolge.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from app.models import BaseUnit, Category, CategoryTracking, Product, Role, User
|
||||
from app.routers.products import list_products
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def nutzer(db):
|
||||
person = User(username="chef", password_hash="x", role=Role.admin)
|
||||
db.add(person)
|
||||
db.commit()
|
||||
db.refresh(person)
|
||||
return person
|
||||
|
||||
|
||||
def _kategorie(db, name, tracking=CategoryTracking.object):
|
||||
cat = Category(name=name, tracking=tracking.value)
|
||||
db.add(cat)
|
||||
db.commit()
|
||||
db.refresh(cat)
|
||||
return cat
|
||||
|
||||
|
||||
def _artikel(db, name, cat=None, **extra):
|
||||
product = Product(name=name, base_unit=BaseUnit.piece, package_size=1,
|
||||
category_id=(cat.id if cat else None), source="manual", **extra)
|
||||
db.add(product)
|
||||
db.commit()
|
||||
db.refresh(product)
|
||||
return product
|
||||
|
||||
|
||||
def _namen(db, nutzer, **kwargs):
|
||||
return [p.name for p in list_products(db=db, _=nutzer, **kwargs)]
|
||||
|
||||
|
||||
def test_suche_findet_ueber_marke_und_name_in_beliebiger_reihenfolge(db, nutzer):
|
||||
"""Der Fall, der vorher gar nichts lieferte."""
|
||||
_artikel(db, "Vogel-Shirt", brand="Jack and Jones")
|
||||
_artikel(db, "Hose", brand="Levis")
|
||||
|
||||
assert _namen(db, nutzer, q="vogel jack and jones") == ["Vogel-Shirt"]
|
||||
# Dieselben Wörter andersherum – die Reihenfolge darf nichts ändern.
|
||||
assert _namen(db, nutzer, q="jack and jones vogel") == ["Vogel-Shirt"]
|
||||
# Nur die Marke, ohne ein Wort aus dem Namen.
|
||||
assert _namen(db, nutzer, q="jones") == ["Vogel-Shirt"]
|
||||
|
||||
|
||||
def test_suche_loest_umlaute_auf(db, nutzer):
|
||||
"""Der Grund, warum gefiltert wird und nicht per ILIKE gesucht."""
|
||||
_artikel(db, "Weissmehl Typ 550", brand="Mühle Nord")
|
||||
_artikel(db, "Zucker")
|
||||
|
||||
assert _namen(db, nutzer, q="muehle") == ["Weissmehl Typ 550"]
|
||||
assert _namen(db, nutzer, q="MÜHLE") == ["Weissmehl Typ 550"]
|
||||
|
||||
|
||||
def test_suche_grenzt_weiterhin_auf_kategorie_ein(db, nutzer):
|
||||
"""Der Kategoriefilter bleibt in SQL – der Umbau darf ihn nicht verlieren."""
|
||||
kleidung = _kategorie(db, "Kleidung")
|
||||
werkzeug = _kategorie(db, "Werkzeug")
|
||||
_artikel(db, "Vogel-Shirt", kleidung, brand="Jack and Jones")
|
||||
_artikel(db, "Vogel-Hammer", werkzeug)
|
||||
|
||||
assert _namen(db, nutzer, q="vogel") == ["Vogel-Hammer", "Vogel-Shirt"]
|
||||
assert _namen(db, nutzer, q="vogel", category_id=kleidung.id) == ["Vogel-Shirt"]
|
||||
|
||||
|
||||
def test_artikel_ohne_marke_bleibt_auffindbar(db, nutzer):
|
||||
"""``brand`` ist nullbar – das darf den Vergleich nicht sprengen."""
|
||||
_artikel(db, "Hammer", brand=None)
|
||||
|
||||
assert _namen(db, nutzer, q="hammer") == ["Hammer"]
|
||||
|
||||
|
||||
def test_leere_suche_liefert_alles(db, nutzer):
|
||||
"""Auch eine Suche aus lauter Leerzeichen: Ohne Wörter wird nicht gefiltert."""
|
||||
_artikel(db, "Hammer")
|
||||
_artikel(db, "Zange")
|
||||
|
||||
assert _namen(db, nutzer) == ["Hammer", "Zange"]
|
||||
assert _namen(db, nutzer, q=" ") == ["Hammer", "Zange"]
|
||||
Reference in New Issue
Block a user