"""Zwei Daten an einer Charge: aufgedrucktes MHD und „zu verbrauchen bis". Beide bleiben getrennt sichtbar, gewarnt wird nach dem früheren. Der heikle Fall ist die angebrochene Packung **ohne** aufgedrucktes MHD – die fiel vorher durch den Filter `best_before IS NOT NULL`. """ from datetime import date, timedelta import pytest from app.models import BaseUnit, Lot, Product, Role, User from app.routers.dashboard import _zustand from app.routers.stock import open_lot from app.routers.views import expiring from app.schemas import LotOpen from app.services.stock import _fefo_lots @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 @pytest.fixture() def milch(db): product = Product( name="Vollmilch", base_unit=BaseUnit.milliliter, package_size=1000, source="manual", ) db.add(product) db.commit() db.refresh(product) return product def _lot(db, product, menge, best_before=None, opened_on=None, consume_by=None): lot = Lot( product_id=product.id, quantity=menge, best_before=best_before, opened_on=opened_on, consume_by=consume_by, ) db.add(lot) db.commit() db.refresh(lot) return lot # -------------------------------------------------------------------------- # Ablaufliste # -------------------------------------------------------------------------- def test_angebrochene_charge_ohne_mhd_taucht_auf(db, user, milch): """Der Fall, den der alte Filter verschluckt hätte.""" _lot(db, milch, 500, best_before=None, opened_on=date.today(), consume_by=date.today() + timedelta(days=2)) liste = expiring(days=7, db=db, _=user) assert len(liste) == 1 assert liste[0].best_before is None assert liste[0].warn_source == "consume_by" assert liste[0].days_left == 2 def test_beide_daten_stehen_getrennt_in_der_liste(db, user, milch): mhd = date.today() + timedelta(days=30) verbrauch = date.today() + timedelta(days=3) _lot(db, milch, 500, best_before=mhd, opened_on=date.today(), consume_by=verbrauch) eintrag = expiring(days=7, db=db, _=user)[0] assert eintrag.best_before == mhd # bleibt sichtbar assert eintrag.consume_by == verbrauch assert eintrag.warn_date == verbrauch # gewarnt wird nach dem früheren assert eintrag.days_left == 3 def test_frueheres_mhd_gewinnt_gegen_verbrauchsdatum(db, user, milch): mhd = date.today() + timedelta(days=1) _lot(db, milch, 500, best_before=mhd, opened_on=date.today(), consume_by=date.today() + timedelta(days=5)) eintrag = expiring(days=7, db=db, _=user)[0] assert eintrag.warn_date == mhd assert eintrag.warn_source == "best_before" def test_ungeoeffnete_charge_verhaelt_sich_wie_bisher(db, user, milch): mhd = date.today() + timedelta(days=3) _lot(db, milch, 1000, best_before=mhd) eintrag = expiring(days=7, db=db, _=user)[0] assert eintrag.warn_date == mhd assert eintrag.warn_source == "best_before" assert eintrag.consume_by is None def test_ausserhalb_der_frist_bleibt_draussen(db, user, milch): _lot(db, milch, 500, opened_on=date.today(), consume_by=date.today() + timedelta(days=30)) assert expiring(days=7, db=db, _=user) == [] def test_liste_ist_nach_dem_wirksamen_datum_sortiert(db, user, milch): _lot(db, milch, 100, best_before=date.today() + timedelta(days=6)) _lot(db, milch, 100, opened_on=date.today(), consume_by=date.today() + timedelta(days=1)) _lot(db, milch, 100, best_before=date.today() + timedelta(days=3)) tage = [e.days_left for e in expiring(days=7, db=db, _=user)] assert tage == [1, 3, 6] # -------------------------------------------------------------------------- # Zustand fürs Dashboard # -------------------------------------------------------------------------- def test_zustand_angebrochen_ist_ein_eigener(db): heute = date.today() assert _zustand(heute + timedelta(days=30), heute, 7, angebrochen=True) == "opened" def test_dringlichkeit_schlaegt_angebrochen(db): heute = date.today() # Angebrochen UND bald ablaufend: die Warnung muss gewinnen. assert _zustand(heute + timedelta(days=2), heute, 7, angebrochen=True) == "soon" assert _zustand(heute - timedelta(days=1), heute, 7, angebrochen=True) == "expired" def test_zustand_ohne_daten_bleibt_no_date(db): heute = date.today() assert _zustand(None, heute, 7) == "no_date" # Angebrochen ohne jedes Datum ist trotzdem angebrochen. assert _zustand(None, heute, 7, angebrochen=True) == "opened" # -------------------------------------------------------------------------- # FEFO # -------------------------------------------------------------------------- def test_angebrochenes_wird_zuerst_geleert(db, milch): spaet = _lot(db, milch, 1000, best_before=date.today() + timedelta(days=30)) offen = _lot(db, milch, 500, best_before=date.today() + timedelta(days=30), opened_on=date.today(), consume_by=date.today() + timedelta(days=2)) reihenfolge = [lot.id for lot in _fefo_lots(db, milch.id)] assert reihenfolge == [offen.id, spaet.id] def test_angebrochenes_geht_auch_vor_knappem_mhd(db, milch): """Offen zuerst, selbst wenn eine geschlossene Packung früher datiert ist. Das MHD einer ungeöffneten Packung ist konservativ und wird real meist überlebt; eine offene verdirbt dagegen tatsächlich. Und wer die zweite aufmacht, während die erste offen steht, hat am Ende zwei angebrochene. """ morgen = _lot(db, milch, 1000, best_before=date.today() + timedelta(days=1)) offen = _lot(db, milch, 500, opened_on=date.today(), consume_by=date.today() + timedelta(days=5)) reihenfolge = [lot.id for lot in _fefo_lots(db, milch.id)] assert reihenfolge == [offen.id, morgen.id] def test_unter_den_angebrochenen_gilt_weiter_fefo(db, milch): spaet = _lot(db, milch, 500, opened_on=date.today(), consume_by=date.today() + timedelta(days=5)) frueh = _lot(db, milch, 500, opened_on=date.today(), consume_by=date.today() + timedelta(days=1)) reihenfolge = [lot.id for lot in _fefo_lots(db, milch.id)] assert reihenfolge == [frueh.id, spaet.id]