Files
Vorrania/backend/app/schemas.py
Scarriffle 2f1be8e2dd Verlauf zeigt Mengen in Artikeleinheiten (Gebinde) statt nur Basiseinheit
MovementOut liefert jetzt package_size, package_label sowie Name/Faktor der
Anzeigeeinheit mit. Die Verlauf-Tabelle zeigt die Bewegung als Leitangabe in
Gebinden (z.B. "1 Glas") und darunter klein die Basiseinheit ("195 g").
Ohne Gebinde/abweichende Einheit bleibt es bei einer Zeile.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 13:58:50 +02:00

284 lines
7.1 KiB
Python

from __future__ import annotations
from datetime import date, datetime
from pydantic import BaseModel, ConfigDict, Field
from .models import BaseUnit, Role, UnitKind
# ---- Units ----
class UnitOut(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: int
name: str
kind: UnitKind
factor: float
is_builtin: bool
class UnitCreate(BaseModel):
name: str = Field(min_length=1, max_length=64)
kind: UnitKind
factor: float = Field(gt=0)
# ---- 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
min_stock_unit_id: int | None = None
# angereichert:
product_count: int = 0
stock: float = 0.0 # Bestand in Basiseinheiten
min_stock_unit_name: str | None = None
kind: str | None = None # Art der Mindestbestand-Einheit
class GroupCreate(BaseModel):
name: str = Field(min_length=1, max_length=120)
min_stock: float | None = Field(default=None, ge=0)
min_stock_unit_id: int | None = None
class GroupUpdate(BaseModel):
name: str | None = Field(default=None, min_length=1, max_length=120)
min_stock: float | None = Field(default=None, ge=0)
min_stock_unit_id: int | 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
# Optionale verwaltete Einheit; setzt base_unit anhand ihrer Art und die Anzeigeeinheit.
unit_id: int | None = None
package_size: float | None = Field(default=None, gt=0)
# Bezeichnung eines Gebindes ("Packung", "Glas", "Tüte", …)
package_label: str | None = Field(default=None, max_length=32)
group_id: int | None = None
min_stock: float | None = Field(default=None, ge=0) # immer in Basiseinheiten
# Nur für die Anzeige: in welcher Einheit der Mindestbestand erfasst wurde.
min_stock_unit_id: int | None = None
min_stock_in_packages: bool = False
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
unit_id: int | None = None
package_size: float | None = Field(default=None, gt=0)
package_label: str | None = Field(default=None, max_length=32)
group_id: int | None = None
min_stock: float | None = Field(default=None, ge=0)
min_stock_unit_id: int | None = None
min_stock_in_packages: bool | None = None
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
display_unit_id: int | None = None
package_size: float | None
package_label: str | None = None
group_id: int | None
min_stock: float | None
min_stock_unit_id: int | None = None
min_stock_in_packages: bool = False
source: str
created_at: datetime
# angereichert:
stock: float = 0.0
expired_count: int = 0
kind: str = ""
unit_name: str = ""
unit_factor: float = 1.0
# Mindestbestand in der erfassten Einheit (für die Anzeige):
min_stock_display: float | None = None
min_stock_unit_label: str = ""
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
# Optional: gezielt aus dieser Charge abbuchen (sonst automatisch FEFO).
lot_id: int | None = None
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 LotUpdate(BaseModel):
"""Korrektur einer Charge (Vertipper beim Einlagern o.ä.)."""
quantity: float | None = Field(default=None, gt=0)
best_before: date | None = None
location_id: int | None = None
class CheckInResponse(BaseModel):
lot: LotOut
product_stock: float
class CheckInLine(BaseModel):
"""Eine Charge innerhalb eines Sammel-Einlagerns (Menge + eigenes MHD)."""
quantity: float = Field(gt=0)
best_before: date | None = None
location_id: int | None = None
class BatchCheckInRequest(BaseModel):
product_id: int | None = None
barcode: str | None = None
unit: str
lines: list[CheckInLine] = Field(min_length=1)
note: str | None = None
class BatchCheckInResponse(BaseModel):
lots: list[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
package_size: float | None = None
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 GroupShoppingItem(BaseModel):
group_id: int
name: str
stock: float
min_stock: float
deficit: float
unit_name: str = ""
product_count: int
class MovementOut(BaseModel):
id: int
product_id: int
product_name: str
type: str
quantity: float # in Basiseinheiten
base_unit: BaseUnit
unit_used: str
username: str | None
note: str | None
created_at: datetime
# Für die Anzeige in Artikeleinheiten:
package_size: float | None = None
package_label: str | None = None
unit_name: str = ""
unit_factor: float = 1.0
class SettingOut(BaseModel):
key: str
value: str