Barcodes:
- Neue Tabelle barcodes (code, note, product_id ODER group_id). Damit lassen sich
einem Produkt mehrere Codes geben und einer Gruppe beliebig viele Marken
zuordnen (z.B. alle Mehlmarken zu "Mehl"). Je Code eine Notiz wie
"Mehl bei Aldi".
- Lookup loest jetzt auch Alias-Codes auf; ist ein Code einer Gruppe zugeordnet,
liefert die API group_id/group_name zurueck. Beim Anlegen aus einem Scan wird
diese Gruppe automatisch gesetzt (Vorrang vor dem Kategorie-Rateversuch).
- Endpunkte: POST/DELETE /products/{id}/barcodes und /groups/{id}/barcodes.
- Web: wiederverwendbare BarcodeList-Komponente, eingebunden im Produktformular
und auf der Gruppen-Seite.
Gruppen:
- Namen lassen sich jetzt direkt in der Tabelle aendern (PATCH war vorhanden,
nur die Oberflaeche fehlte).
UI-Fix: Auswahlfelder in Tabellen richten sich nach ihrem Text statt nach der
Zellenbreite (Gruppen-/Benutzer-Dropdowns waren abgeschnitten).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
68 lines
2.3 KiB
Python
68 lines
2.3 KiB
Python
"""Kleine gemeinsame Helfer für Router."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import date
|
|
|
|
from fastapi import HTTPException, status
|
|
from sqlalchemy.orm import Session
|
|
|
|
from .models import Barcode, Lot, Product
|
|
from .schemas import BarcodeOut, ProductOut
|
|
from .services.conversion import KIND_OF_BASE, display_unit_info
|
|
from .services.stock import current_stock
|
|
|
|
|
|
def product_to_out(db: Session, product: Product) -> ProductOut:
|
|
out = ProductOut.model_validate(product)
|
|
out.stock = current_stock(db, product.id)
|
|
out.expired_count = (
|
|
db.query(Lot)
|
|
.filter(
|
|
Lot.product_id == product.id,
|
|
Lot.best_before.isnot(None),
|
|
Lot.best_before < date.today(),
|
|
Lot.quantity > 0,
|
|
)
|
|
.count()
|
|
)
|
|
out.barcodes = [
|
|
BarcodeOut.model_validate(b)
|
|
for b in db.query(Barcode).filter(Barcode.product_id == product.id).order_by(Barcode.id).all()
|
|
]
|
|
out.kind = KIND_OF_BASE[product.base_unit].value
|
|
name, factor = display_unit_info(product)
|
|
out.unit_name = name
|
|
out.unit_factor = factor
|
|
|
|
# Mindestbestand in der Einheit anzeigen, in der er erfasst wurde.
|
|
if product.min_stock is not None:
|
|
if product.min_stock_in_packages and product.package_size:
|
|
out.min_stock_display = product.min_stock / product.package_size
|
|
out.min_stock_unit_label = "Pkg"
|
|
elif product.min_stock_unit is not None:
|
|
out.min_stock_display = product.min_stock / product.min_stock_unit.factor
|
|
out.min_stock_unit_label = product.min_stock_unit.name
|
|
else:
|
|
out.min_stock_display = product.min_stock / factor
|
|
out.min_stock_unit_label = name
|
|
return out
|
|
|
|
|
|
def resolve_product(
|
|
db: Session, product_id: int | None, barcode: str | None
|
|
) -> Product:
|
|
"""Findet ein Produkt per ID oder Barcode; wirft 404, wenn keins passt."""
|
|
product: Product | None = None
|
|
if product_id is not None:
|
|
product = db.get(Product, product_id)
|
|
elif barcode:
|
|
product = db.query(Product).filter(Product.barcode == barcode).first()
|
|
else:
|
|
raise HTTPException(
|
|
status.HTTP_400_BAD_REQUEST, "product_id oder barcode erforderlich"
|
|
)
|
|
if product is None:
|
|
raise HTTPException(status.HTTP_404_NOT_FOUND, "Produkt nicht gefunden")
|
|
return product
|