"""Mehrere Chargen auf einmal: komplett ausbuchen, Teilmenge, löschen.""" from datetime import date, timedelta import pytest from fastapi import HTTPException from app.models import BaseUnit, Lot, Movement, MovementType, Product, Role, User from app.routers.stock import bulk_lot_checkout, bulk_lot_delete from app.schemas import BulkLotCheckout, BulkLotDelete from app.services.stock import current_stock @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, tage=None): lot = Lot( product_id=product.id, quantity=menge, best_before=(date.today() + timedelta(days=tage)) if tage is not None else None, ) db.add(lot) db.commit() db.refresh(lot) return lot # -------------------------------------------------------------------------- # Komplett ausbuchen # -------------------------------------------------------------------------- def test_mehrere_chargen_komplett_ausbuchen(db, user, milch): a = _lot(db, milch, 1000, 5) b = _lot(db, milch, 2000, 9) anzahl = bulk_lot_checkout(BulkLotCheckout(lot_ids=[a.id, b.id]), db=db, user=user) assert anzahl == 2 assert db.query(Lot).count() == 0 assert current_stock(db, milch.id) == 0 def test_je_charge_eine_bewegung(db, user, milch): a = _lot(db, milch, 1000, 5) b = _lot(db, milch, 2000, 9) bulk_lot_checkout(BulkLotCheckout(lot_ids=[a.id, b.id]), db=db, user=user) bewegungen = db.query(Movement).all() assert len(bewegungen) == 2 assert {m.quantity for m in bewegungen} == {1000, 2000} assert all(m.type == MovementType.out for m in bewegungen) def test_notiz_landet_an_der_bewegung(db, user, milch): a = _lot(db, milch, 1000, 5) bulk_lot_checkout( BulkLotCheckout(lot_ids=[a.id], note="Grossputz"), db=db, user=user ) assert db.query(Movement).one().note == "Grossputz" def test_leere_auswahl_tut_nichts(db, user, milch): _lot(db, milch, 1000, 5) assert bulk_lot_checkout(BulkLotCheckout(lot_ids=[]), db=db, user=user) == 0 assert current_stock(db, milch.id) == 1000 def test_unbekannte_ids_werden_uebergangen(db, user, milch): a = _lot(db, milch, 1000, 5) anzahl = bulk_lot_checkout(BulkLotCheckout(lot_ids=[a.id, 999]), db=db, user=user) assert anzahl == 1 assert current_stock(db, milch.id) == 0 # -------------------------------------------------------------------------- # Teilmenge je Charge # -------------------------------------------------------------------------- def test_teilmenge_je_charge(db, user, milch): a = _lot(db, milch, 1000, 5) b = _lot(db, milch, 2000, 9) bulk_lot_checkout( BulkLotCheckout(lot_ids=[a.id, b.id], quantity=200, unit="ml"), db=db, user=user ) assert db.get(Lot, a.id).quantity == 800 assert db.get(Lot, b.id).quantity == 1800 def test_teilmenge_bei_gemischten_artikeln_wird_abgelehnt(db, user, milch): """„1 Packung" bei Milch und Brötchen sind verschiedene Dinge.""" broetchen = Product(name="Brötchen", base_unit=BaseUnit.piece, package_size=6, source="manual") db.add(broetchen) db.commit() a = _lot(db, milch, 1000, 5) b = _lot(db, broetchen, 6, 2) with pytest.raises(HTTPException) as fehler: bulk_lot_checkout( BulkLotCheckout(lot_ids=[a.id, b.id], quantity=1, unit="package"), db=db, user=user, ) assert fehler.value.status_code == 400 assert db.get(Lot, a.id).quantity == 1000 # nichts gebucht assert db.get(Lot, b.id).quantity == 6 def test_zu_wenig_bestand_rollt_alles_zurueck(db, user, milch): """Ein halb gebuchter Stand wäre schlimmer als gar keiner.""" viel = _lot(db, milch, 1000, 5) wenig = _lot(db, milch, 100, 9) with pytest.raises(HTTPException) as fehler: bulk_lot_checkout( BulkLotCheckout(lot_ids=[viel.id, wenig.id], quantity=500, unit="ml"), db=db, user=user, ) assert fehler.value.status_code == 409 assert current_stock(db, milch.id) == 1100 # unverändert assert db.query(Movement).count() == 0 def test_unbekannte_einheit_bucht_nichts(db, user, milch): a = _lot(db, milch, 1000, 5) with pytest.raises(HTTPException) as fehler: bulk_lot_checkout( BulkLotCheckout(lot_ids=[a.id], quantity=1, unit="furlong"), db=db, user=user ) assert fehler.value.status_code == 400 assert current_stock(db, milch.id) == 1000 def test_teilmenge_leert_und_entfernt_die_charge(db, user, milch): a = _lot(db, milch, 500, 5) bulk_lot_checkout( BulkLotCheckout(lot_ids=[a.id], quantity=500, unit="ml"), db=db, user=user ) assert db.get(Lot, a.id) is None # -------------------------------------------------------------------------- # Löschen als Korrektur # -------------------------------------------------------------------------- def test_mehrere_chargen_loeschen(db, user, milch): a = _lot(db, milch, 1000, 5) b = _lot(db, milch, 2000, 9) anzahl = bulk_lot_delete(BulkLotDelete(lot_ids=[a.id, b.id]), db=db, user=user) assert anzahl == 2 assert db.query(Lot).count() == 0 def test_loeschen_schreibt_korrektur_nicht_verbrauch(db, user, milch): a = _lot(db, milch, 1000, 5) bulk_lot_delete(BulkLotDelete(lot_ids=[a.id]), db=db, user=user) bewegung = db.query(Movement).one() assert bewegung.type == MovementType.adjust assert bewegung.quantity == -1000 assert bewegung.note == "Charge gelöscht" # Keine Charge mehr, an der die Bewegung haengen koennte. assert bewegung.lot_id is None def test_loeschen_mit_leerer_auswahl(db, user, milch): _lot(db, milch, 1000, 5) assert bulk_lot_delete(BulkLotDelete(lot_ids=[]), db=db, user=user) == 0 assert current_stock(db, milch.id) == 1000