from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.orm import Session from ..crud import product_to_out from ..database import get_db from ..deps import get_current_user, require_admin from ..models import BaseUnit, Product, User from ..off import lookup_barcode from ..schemas import LookupResult, ProductCreate, ProductOut, ProductUpdate from ..services.conversion import ConversionError, resolve_product_unit router = APIRouter(prefix="/products", tags=["products"]) @router.get("", response_model=list[ProductOut]) def list_products( q: str | None = None, db: Session = Depends(get_db), _: User = Depends(get_current_user), ) -> list[ProductOut]: query = db.query(Product) if q: like = f"%{q}%" query = query.filter(Product.name.ilike(like)) products = query.order_by(Product.name).all() return [product_to_out(db, p) for p in products] @router.get("/lookup", response_model=LookupResult) def lookup( barcode: str, db: Session = Depends(get_db), _: User = Depends(get_current_user), ) -> LookupResult: """Barcode: erst lokal, dann Open Food Facts. Nur Vorschlag, legt nichts an.""" existing = db.query(Product).filter(Product.barcode == barcode).first() if existing: return LookupResult(found=True, existing_product=product_to_out(db, existing)) suggestion = lookup_barcode(barcode) if suggestion: return LookupResult(found=True, suggestion=suggestion) return LookupResult(found=False) @router.get("/{product_id}", response_model=ProductOut) def get_product( product_id: int, db: Session = Depends(get_db), _: User = Depends(get_current_user), ) -> ProductOut: product = db.get(Product, product_id) if product is None: raise HTTPException(status.HTTP_404_NOT_FOUND, "Produkt nicht gefunden") return product_to_out(db, product) @router.post("", response_model=ProductOut, status_code=status.HTTP_201_CREATED) def create_product( payload: ProductCreate, db: Session = Depends(get_db), _: User = Depends(require_admin), ) -> ProductOut: if payload.barcode: exists = db.query(Product).filter(Product.barcode == payload.barcode).first() if exists: raise HTTPException( status.HTTP_409_CONFLICT, "Ein Produkt mit diesem Barcode existiert bereits" ) base_unit = payload.base_unit display_unit_id = None if payload.unit_id is not None: try: base_unit, display_unit_id = resolve_product_unit(db, payload.unit_id) except ConversionError as exc: raise HTTPException(status.HTTP_400_BAD_REQUEST, str(exc)) from exc product = Product( barcode=payload.barcode or None, name=payload.name, brand=payload.brand, image_url=payload.image_url, base_unit=base_unit, display_unit_id=display_unit_id, package_size=payload.package_size, package_label=payload.package_label or None, group_id=payload.group_id, min_stock=payload.min_stock, min_stock_unit_id=payload.min_stock_unit_id, min_stock_in_packages=bool(payload.min_stock_in_packages), source="manual", ) db.add(product) db.commit() db.refresh(product) return product_to_out(db, product) @router.patch("/{product_id}", response_model=ProductOut) def update_product( product_id: int, payload: ProductUpdate, db: Session = Depends(get_db), _: User = Depends(require_admin), ) -> ProductOut: product = db.get(Product, product_id) if product is None: raise HTTPException(status.HTTP_404_NOT_FOUND, "Produkt nicht gefunden") data = payload.model_dump(exclude_unset=True) if "barcode" in data and data["barcode"]: clash = ( db.query(Product) .filter(Product.barcode == data["barcode"], Product.id != product_id) .first() ) if clash: raise HTTPException( status.HTTP_409_CONFLICT, "Ein anderes Produkt hat diesen Barcode bereits" ) # Einheit: unit_id (falls gesetzt) bestimmt base_unit + Anzeigeeinheit. if "unit_id" in data: unit_id = data.pop("unit_id") data.pop("base_unit", None) # unit_id hat Vorrang if unit_id is None: product.display_unit_id = None else: try: product.base_unit, product.display_unit_id = resolve_product_unit(db, unit_id) except ConversionError as exc: raise HTTPException(status.HTTP_400_BAD_REQUEST, str(exc)) from exc if data.get("min_stock_in_packages") is None: data.pop("min_stock_in_packages", None) # Spalte ist NOT NULL for field, value in data.items(): setattr(product, field, value) db.commit() db.refresh(product) return product_to_out(db, product) @router.delete("/{product_id}", status_code=status.HTTP_204_NO_CONTENT) def delete_product( product_id: int, db: Session = Depends(get_db), _: User = Depends(require_admin), ) -> None: product = db.get(Product, product_id) if product is None: raise HTTPException(status.HTTP_404_NOT_FOUND, "Produkt nicht gefunden") db.delete(product) db.commit()