Files
Vorrania/backend/tests/test_location_needs.py
Scarriffle f78ae924de Backend: verschachtelte Ort-Mindestbestaende hierarchisch verrechnen
shopping_list_by_location zaehlte Ober- und Unterort unabhaengig, obwohl der
Ober-Subtree den Unterort schon enthaelt - Bedarf wurde doppelt gemeldet. Jetzt
werden Bedarfe je Produkt/Gruppe von unten nach oben verrechnet (_netted_topups):
was in einen Unterort gekauft wird, deckt den Oberort mit. Im Mehl-Beispiel (Lemgo
5, Kueche 2, je 1 fehlend) meldet der Server nur noch 1 (Kueche) statt 2.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-30 09:32:45 +02:00

147 lines
5.4 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Bedarfe (Mindestbestände) je Lagerort und die Einkaufsliste je Ort."""
import pytest
from app.models import (
BaseUnit,
Location,
Lot,
Product,
ProductLocationMinStock,
Role,
User,
)
from app.routers.views import shopping_list_by_location
@pytest.fixture()
def user(db):
person = User(username="tester", password_hash="x", role=Role.admin)
db.add(person)
db.commit()
db.refresh(person)
return person
def test_bedarf_je_lagerort(db, user):
# package_size=1 -> Artikeleinheit == Basiseinheit, macht die Rechnung klar.
p = Product(name="Kaffee", base_unit=BaseUnit.gram, package_size=1)
db.add(p)
db.flush()
zuhause = Location(name="Zuhause")
ferien = Location(name="Ferienhaus")
db.add_all([zuhause, ferien])
db.flush()
# 1 Stück zuhause, nichts im Ferienhaus.
db.add(Lot(product_id=p.id, quantity=1, location_id=zuhause.id))
db.add(ProductLocationMinStock(product_id=p.id, location_id=zuhause.id, min_stock=3))
db.add(ProductLocationMinStock(product_id=p.id, location_id=ferien.id, min_stock=2))
db.commit()
needs = shopping_list_by_location(db=db, _=user)
nach_ort = {n.location_name: n for n in needs}
assert set(nach_ort) == {"Zuhause", "Ferienhaus"}
assert nach_ort["Zuhause"].products[0].deficit == 2 # 3 - 1
assert nach_ort["Ferienhaus"].products[0].deficit == 2 # 2 - 0
def test_gedeckter_ort_erscheint_nicht(db, user):
p = Product(name="Reis", base_unit=BaseUnit.gram, package_size=1)
db.add(p)
db.flush()
zuhause = Location(name="Zuhause")
db.add(zuhause)
db.flush()
db.add(Lot(product_id=p.id, quantity=5, location_id=zuhause.id))
db.add(ProductLocationMinStock(product_id=p.id, location_id=zuhause.id, min_stock=3))
db.commit()
# Bestand (5) >= Mindestbestand (3) -> kein Bedarf, kein Eintrag.
assert shopping_list_by_location(db=db, _=user) == []
def test_bestand_in_unterlagerort_zaehlt_zum_oberort(db, user):
p = Product(name="Konserve", base_unit=BaseUnit.gram, package_size=1)
db.add(p)
db.flush()
haus = Location(name="Hedingen")
db.add(haus)
db.flush()
keller = Location(name="Keller", parent_id=haus.id)
db.add(keller)
db.flush()
# Vorrat liegt im Unter-Lagerort; Mindestbestand ist auf dem Ober-Lagerort.
db.add(Lot(product_id=p.id, quantity=5, location_id=keller.id))
db.add(ProductLocationMinStock(product_id=p.id, location_id=haus.id, min_stock=3))
db.commit()
# 5 (im Keller) deckt die 3 auf Hedingen -> kein Bedarf.
assert shopping_list_by_location(db=db, _=user) == []
def test_unterlagerort_reicht_nicht_zeigt_restbedarf(db, user):
p = Product(name="Konserve", base_unit=BaseUnit.gram, package_size=1)
db.add(p)
db.flush()
haus = Location(name="Hedingen")
db.add(haus)
db.flush()
keller = Location(name="Keller", parent_id=haus.id)
db.add(keller)
db.flush()
db.add(Lot(product_id=p.id, quantity=2, location_id=keller.id))
db.add(ProductLocationMinStock(product_id=p.id, location_id=haus.id, min_stock=5))
db.commit()
needs = shopping_list_by_location(db=db, _=user)
assert needs[0].location_name == "Hedingen"
assert needs[0].products[0].deficit == 3 # 5 - 2
def test_verschachtelte_orte_werden_verrechnet(db, user):
"""Ober- UND Unterort haben einen Mindestbestand. Was in den Unterort gekauft
wird, liegt im Subtree des Oberorts und deckt ihn mit dann taucht der Oberort
nicht mehr auf (keine Doppelzählung)."""
p = Product(name="Mehl", base_unit=BaseUnit.gram, package_size=1)
db.add(p)
db.flush()
lemgo = Location(name="Lemgo")
db.add(lemgo)
db.flush()
kueche = Location(name="Kueche", parent_id=lemgo.id)
db.add(kueche)
db.flush()
# 3 direkt in Lemgo, 1 in der Kueche -> Lemgo-Subtree = 4, Kueche = 1.
db.add(Lot(product_id=p.id, quantity=3, location_id=lemgo.id))
db.add(Lot(product_id=p.id, quantity=1, location_id=kueche.id))
db.add(ProductLocationMinStock(product_id=p.id, location_id=lemgo.id, min_stock=5))
db.add(ProductLocationMinStock(product_id=p.id, location_id=kueche.id, min_stock=2))
db.commit()
nach_ort = {n.location_name: n for n in shopping_list_by_location(db=db, _=user)}
# 1 in die Kueche gekauft (2-1) hebt Lemgo auf 5 -> Lemgo verschwindet.
assert set(nach_ort) == {"Kueche"}
assert nach_ort["Kueche"].products[0].deficit == 1
def test_verschachtelte_orte_restbedarf_am_oberort(db, user):
"""Deckt der Unterort-Kauf den Oberort nicht ganz, bleibt der Restbedarf am
Oberort aber die Unterort-Menge wird nicht doppelt gezaehlt."""
p = Product(name="Mehl", base_unit=BaseUnit.gram, package_size=1)
db.add(p)
db.flush()
lemgo = Location(name="Lemgo")
db.add(lemgo)
db.flush()
kueche = Location(name="Kueche", parent_id=lemgo.id)
db.add(kueche)
db.flush()
db.add(Lot(product_id=p.id, quantity=1, location_id=kueche.id)) # nur 1 in der Kueche
db.add(ProductLocationMinStock(product_id=p.id, location_id=lemgo.id, min_stock=5))
db.add(ProductLocationMinStock(product_id=p.id, location_id=kueche.id, min_stock=2))
db.commit()
nach_ort = {n.location_name: n for n in shopping_list_by_location(db=db, _=user)}
assert nach_ort["Kueche"].products[0].deficit == 1 # 2 - 1
assert nach_ort["Lemgo"].products[0].deficit == 3 # 5 - (1 da + 1 aus Kueche-Kauf), nicht 4