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>
This commit is contained in:
174
backend/app/schemas.py
Normal file
174
backend/app/schemas.py
Normal file
@@ -0,0 +1,174 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import date, datetime
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
from .models import BaseUnit, Role
|
||||
|
||||
|
||||
# ---- Auth / Users ----
|
||||
class Token(BaseModel):
|
||||
access_token: str
|
||||
token_type: str = "bearer"
|
||||
role: Role
|
||||
username: str
|
||||
|
||||
|
||||
class UserOut(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
id: int
|
||||
username: str
|
||||
role: Role
|
||||
created_at: datetime
|
||||
|
||||
|
||||
class UserCreate(BaseModel):
|
||||
username: str = Field(min_length=1, max_length=64)
|
||||
password: str = Field(min_length=4, max_length=255)
|
||||
role: Role = Role.user
|
||||
|
||||
|
||||
class UserUpdate(BaseModel):
|
||||
password: str | None = Field(default=None, min_length=4, max_length=255)
|
||||
role: Role | None = None
|
||||
|
||||
|
||||
# ---- Groups ----
|
||||
class GroupOut(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
id: int
|
||||
name: str
|
||||
min_stock: float | None = None
|
||||
|
||||
|
||||
class GroupCreate(BaseModel):
|
||||
name: str = Field(min_length=1, max_length=120)
|
||||
min_stock: float | None = None
|
||||
|
||||
|
||||
# ---- Locations ----
|
||||
class LocationOut(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
id: int
|
||||
name: str
|
||||
parent_id: int | None = None
|
||||
|
||||
|
||||
class LocationCreate(BaseModel):
|
||||
name: str = Field(min_length=1, max_length=120)
|
||||
parent_id: int | None = None
|
||||
|
||||
|
||||
# ---- Products ----
|
||||
class ProductBase(BaseModel):
|
||||
barcode: str | None = None
|
||||
name: str = Field(min_length=1, max_length=255)
|
||||
brand: str | None = None
|
||||
image_url: str | None = None
|
||||
base_unit: BaseUnit = BaseUnit.piece
|
||||
package_size: float | None = Field(default=None, gt=0)
|
||||
group_id: int | None = None
|
||||
min_stock: float | None = Field(default=None, ge=0)
|
||||
|
||||
|
||||
class ProductCreate(ProductBase):
|
||||
pass
|
||||
|
||||
|
||||
class ProductUpdate(BaseModel):
|
||||
barcode: str | None = None
|
||||
name: str | None = Field(default=None, min_length=1, max_length=255)
|
||||
brand: str | None = None
|
||||
image_url: str | None = None
|
||||
base_unit: BaseUnit | None = None
|
||||
package_size: float | None = Field(default=None, gt=0)
|
||||
group_id: int | None = None
|
||||
min_stock: float | None = Field(default=None, ge=0)
|
||||
|
||||
|
||||
class ProductOut(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
id: int
|
||||
barcode: str | None
|
||||
name: str
|
||||
brand: str | None
|
||||
image_url: str | None
|
||||
base_unit: BaseUnit
|
||||
package_size: float | None
|
||||
group_id: int | None
|
||||
min_stock: float | None
|
||||
source: str
|
||||
created_at: datetime
|
||||
# angereichert:
|
||||
stock: float = 0.0
|
||||
|
||||
|
||||
class LookupResult(BaseModel):
|
||||
found: bool
|
||||
existing_product: ProductOut | None = None
|
||||
suggestion: dict | None = None
|
||||
|
||||
|
||||
# ---- Stock movements ----
|
||||
class CheckInRequest(BaseModel):
|
||||
product_id: int | None = None
|
||||
barcode: str | None = None
|
||||
quantity: float = Field(gt=0)
|
||||
unit: str
|
||||
best_before: date | None = None
|
||||
location_id: int | None = None
|
||||
note: str | None = None
|
||||
|
||||
|
||||
class CheckOutRequest(BaseModel):
|
||||
product_id: int | None = None
|
||||
barcode: str | None = None
|
||||
quantity: float = Field(gt=0)
|
||||
unit: str
|
||||
note: str | None = None
|
||||
|
||||
|
||||
class LotOut(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
id: int
|
||||
product_id: int
|
||||
quantity: float
|
||||
best_before: date | None
|
||||
location_id: int | None
|
||||
created_at: datetime
|
||||
|
||||
|
||||
class CheckInResponse(BaseModel):
|
||||
lot: LotOut
|
||||
product_stock: float
|
||||
|
||||
|
||||
class CheckOutResponse(BaseModel):
|
||||
affected_lots: list[dict]
|
||||
product_stock: float
|
||||
|
||||
|
||||
# ---- Views ----
|
||||
class ShoppingItem(BaseModel):
|
||||
product_id: int
|
||||
name: str
|
||||
base_unit: BaseUnit
|
||||
stock: float
|
||||
min_stock: float
|
||||
deficit: float
|
||||
|
||||
|
||||
class ExpiringItem(BaseModel):
|
||||
lot_id: int
|
||||
product_id: int
|
||||
product_name: str
|
||||
quantity: float
|
||||
base_unit: BaseUnit
|
||||
best_before: date
|
||||
days_left: int
|
||||
|
||||
|
||||
class SettingOut(BaseModel):
|
||||
key: str
|
||||
value: str
|
||||
Reference in New Issue
Block a user