Files
Vorrania/backend/tests/test_fefo.py
Scarriffle 639126468f Schritt 1: Fundament der Lebensmittel-Lagerverwaltung (Pantry)
Backend (FastAPI + PostgreSQL): Chargen mit MHD, FEFO-Auslagern,
Einheiten-Umrechnung (Stueck/g/ml + Packungen), Open-Food-Facts-Lookup
mit lokalem Fallback, JWT-Auth mit Rollen (Admin/Nutzer), erster Admin
beim Setup, Einkaufsliste, Ablaufwarnung, Lagerorte, pytest fuer FEFO.

Web-UI (React/Vite): Login, Dashboard, Ein-/Auslagern, Produkte,
Lagerorte, Benutzerverwaltung, Einkaufsliste - rollenabhaengig.

Deploy: docker-compose + install.sh (Docker-Autoinstall, Secrets),
README und Roadmap fuer Schritt 2 (iOS) und Schritt 3.

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

82 lines
2.4 KiB
Python

from datetime import date, timedelta
import pytest
from app.services.stock import StockError, check_in, check_out, current_stock
def _checkin(db, product, qty, unit, best_before=None):
lot = check_in(db, product, qty, unit, best_before, None, None)
db.commit()
return lot
def test_checkin_creates_lot_in_base_units(db, rice):
_checkin(db, rice, 3, "package") # 1500 g
assert current_stock(db, rice.id) == 1500
def test_checkout_partial_amount(db, rice):
_checkin(db, rice, 3, "package") # 1500 g
check_out(db, rice, 200, "g", None)
db.commit()
assert current_stock(db, rice.id) == 1300
def test_fefo_takes_earliest_expiry_first(db, rice):
soon = date.today() + timedelta(days=5)
later = date.today() + timedelta(days=60)
# Zuerst die später ablaufende Charge einlagern ...
_checkin(db, rice, 500, "g", best_before=later)
# ... dann die früher ablaufende.
_checkin(db, rice, 500, "g", best_before=soon)
check_out(db, rice, 400, "g", None)
db.commit()
# Die früh ablaufende Charge (soon) muss zuerst reduziert worden sein.
from app.models import Lot
lots = {l.best_before: l.quantity for l in db.query(Lot).all()}
assert lots[soon] == 100
assert lots[later] == 500
def test_fefo_spans_multiple_lots_and_removes_empty(db, rice):
soon = date.today() + timedelta(days=5)
later = date.today() + timedelta(days=60)
_checkin(db, rice, 500, "g", best_before=soon)
_checkin(db, rice, 500, "g", best_before=later)
check_out(db, rice, 700, "g", None) # 500 (soon) + 200 (later)
db.commit()
from app.models import Lot
lots = db.query(Lot).all()
assert len(lots) == 1 # leere soon-Charge entfernt
assert lots[0].best_before == later
assert lots[0].quantity == 300
def test_null_best_before_is_last(db, rice):
dated = date.today() + timedelta(days=30)
_checkin(db, rice, 300, "g", best_before=None)
_checkin(db, rice, 300, "g", best_before=dated)
check_out(db, rice, 300, "g", None)
db.commit()
from app.models import Lot
remaining = db.query(Lot).all()
# Die datierte Charge wird zuerst genommen, die NULL-Charge bleibt.
assert len(remaining) == 1
assert remaining[0].best_before is None
def test_checkout_more_than_available_raises(db, rice):
_checkin(db, rice, 100, "g")
with pytest.raises(StockError):
check_out(db, rice, 200, "g", None)