Files
Vorrania/backend/tests/test_ablauf_zwei_daten.py
ScarriffleandClaude Opus 5 afacf01f11 FEFO: angebrochene Packung immer zuerst
Ich hatte das nach dem frueheren wirksamen Datum sortiert, sodass eine
ungeoeffnete Packung mit knappem MHD vor einer offenen drankam. Das ist
falsch herum: Das MHD einer geschlossenen Packung ist konservativ und wird
real meist ueberlebt, eine offene verdirbt dagegen tatsaechlich. Und wer
die zweite aufmacht, waehrend die erste offen steht, hat am Ende zwei
angebrochene.

Angebrochenes kommt also immer zuerst; innerhalb beider Gruppen gilt
weiter FEFO ueber das naehere von MHD und Verbrauchsdatum.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-02 12:33:02 +02:00

180 lines
6.2 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.
"""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]