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>
147 lines
5.4 KiB
Python
147 lines
5.4 KiB
Python
"""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
|