from datetime import date, timedelta from fastapi import APIRouter, Depends from sqlalchemy.orm import Session from ..database import get_db from ..deps import get_current_user from ..models import Group, Lot, Movement, Product, User from ..schemas import ExpiringItem, GroupShoppingItem, MovementOut, ShoppingItem from ..services.conversion import BASE_OF_KIND, display_unit_info from ..services.stock import current_stock from .settings import get_expiry_warning_days router = APIRouter(tags=["views"]) @router.get("/shopping-list", response_model=list[ShoppingItem]) def shopping_list( db: Session = Depends(get_db), _: User = Depends(get_current_user) ) -> list[ShoppingItem]: """Produkte, deren Bestand unter dem Mindestbestand liegt.""" items: list[ShoppingItem] = [] products = ( db.query(Product) .filter(Product.min_stock.isnot(None), Product.min_stock > 0) .all() ) for product in products: stock = current_stock(db, product.id) if stock < product.min_stock: items.append( ShoppingItem( product_id=product.id, name=product.name, base_unit=product.base_unit, package_size=product.package_size, stock=stock, min_stock=product.min_stock, deficit=product.min_stock - stock, ) ) items.sort(key=lambda i: i.deficit, reverse=True) return items @router.get("/shopping-list/groups", response_model=list[GroupShoppingItem]) def group_shopping_list( db: Session = Depends(get_db), _: User = Depends(get_current_user) ) -> list[GroupShoppingItem]: """Gruppen, deren Gesamtbestand unter dem Gruppen-Mindestbestand liegt. Gruppen-Bestand = Summe der Produktbestände in der Gruppe (in Basiseinheiten). Sinnvoll, wenn die Produkte einer Gruppe dieselbe Basiseinheit teilen. """ items: list[GroupShoppingItem] = [] groups = ( db.query(Group).filter(Group.min_stock.isnot(None), Group.min_stock > 0).all() ) for group in groups: unit = group.min_stock_unit if unit is not None: base = BASE_OF_KIND[unit.kind] products = [p for p in group.products if p.base_unit == base] stock = float(sum(current_stock(db, p.id) for p in products)) / unit.factor unit_name = unit.name else: products = list(group.products) stock = float(sum(current_stock(db, p.id) for p in products)) unit_name = "" if stock < group.min_stock: items.append( GroupShoppingItem( group_id=group.id, name=group.name, stock=stock, min_stock=group.min_stock, deficit=group.min_stock - stock, unit_name=unit_name, product_count=len(products), ) ) items.sort(key=lambda i: i.deficit, reverse=True) return items @router.get("/expiring", response_model=list[ExpiringItem]) def expiring( days: int | None = None, db: Session = Depends(get_db), _: User = Depends(get_current_user), ) -> list[ExpiringItem]: """Chargen, die innerhalb der Warnfrist ablaufen (oder schon abgelaufen sind).""" if days is None: days = get_expiry_warning_days(db) today = date.today() threshold = today + timedelta(days=days) lots = ( db.query(Lot) .filter(Lot.best_before.isnot(None), Lot.best_before <= threshold, Lot.quantity > 0) .order_by(Lot.best_before) .all() ) result: list[ExpiringItem] = [] for lot in lots: product = lot.product result.append( ExpiringItem( lot_id=lot.id, product_id=product.id, product_name=product.name, quantity=lot.quantity, base_unit=product.base_unit, best_before=lot.best_before, days_left=(lot.best_before - today).days, ) ) return result @router.get("/movements", response_model=list[MovementOut]) def movements( limit: int = 100, product_id: int | None = None, db: Session = Depends(get_db), _: User = Depends(get_current_user), ) -> list[MovementOut]: """Bewegungsverlauf (wer hat wann was ein-/ausgelagert), neueste zuerst.""" limit = max(1, min(limit, 500)) query = ( db.query(Movement, Product, User) .join(Product, Movement.product_id == Product.id) .outerjoin(User, Movement.user_id == User.id) ) if product_id is not None: query = query.filter(Movement.product_id == product_id) rows = query.order_by(Movement.created_at.desc(), Movement.id.desc()).limit(limit).all() result: list[MovementOut] = [] for movement, product, user in rows: unit_name, unit_factor = display_unit_info(product) result.append( MovementOut( id=movement.id, product_id=product.id, product_name=product.name, type=movement.type.value, quantity=movement.quantity, base_unit=product.base_unit, unit_used=movement.unit_used, username=user.username if user else None, note=movement.note, created_at=movement.created_at, package_size=product.package_size, package_label=product.package_label, unit_name=unit_name, unit_factor=unit_factor, ) ) return result