Verwaltbare Einheiten mit Umrechnung + Chargen bearbeiten + gezieltes Auslagern
Einheiten (neu):
- Tabelle units (name, kind=count|weight|volume, factor, is_builtin); eingebaut
Stueck/Gramm/Kilogramm/Milliliter/Liter, Admin kann eigene anlegen (z.B. Pfund=500g).
- Bestaende bleiben intern in kanonischer Basis (Stueck/Gramm/Milliliter);
Product.display_unit_id und Group.min_stock_unit_id als nullable FKs.
- Neuer Umrechnungs-Service (services/conversion.py) ersetzt die feste Einheitenlogik;
Ein-/Auslagern und Gruppen-Mindestbestand rechnen ueber den Faktor.
- Gruppen-Mindestbestand mit Einheit; Gruppenbestand summiert nur Produkte
passender Art. Neue Verwaltungsseite "Einheiten" (Admin).
- Schonende Migration beim Start: ADD COLUMN IF NOT EXISTS (Postgres), damit
bestehende Installationen ihre Daten behalten.
Chargen:
- PATCH /lots/{id} und DELETE /lots/{id}: Menge/MHD korrigieren, Charge loeschen
(wird als Korrektur-Bewegung protokolliert). Bearbeitung im Produktdetail.
Auslagern:
- Optionales lot_id: gezielt aus einer bestimmten Charge/MHD abbuchen statt FEFO;
Auswahl-Dropdown in der Auslagern-Seite.
Sonstiges: Roadmap aktualisiert, Tests fuer die Umrechnung.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -4,7 +4,23 @@ from datetime import date, datetime
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
from .models import BaseUnit, Role
|
||||
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 ----
|
||||
@@ -40,19 +56,24 @@ class GroupOut(BaseModel):
|
||||
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
|
||||
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 ----
|
||||
@@ -75,6 +96,8 @@ class ProductBase(BaseModel):
|
||||
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)
|
||||
group_id: int | None = None
|
||||
min_stock: float | None = Field(default=None, ge=0)
|
||||
@@ -90,6 +113,7 @@ class ProductUpdate(BaseModel):
|
||||
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)
|
||||
group_id: int | None = None
|
||||
min_stock: float | None = Field(default=None, ge=0)
|
||||
@@ -103,6 +127,7 @@ class ProductOut(BaseModel):
|
||||
brand: str | None
|
||||
image_url: str | None
|
||||
base_unit: BaseUnit
|
||||
display_unit_id: int | None = None
|
||||
package_size: float | None
|
||||
group_id: int | None
|
||||
min_stock: float | None
|
||||
@@ -111,6 +136,9 @@ class ProductOut(BaseModel):
|
||||
# angereichert:
|
||||
stock: float = 0.0
|
||||
expired_count: int = 0
|
||||
kind: str = ""
|
||||
unit_name: str = ""
|
||||
unit_factor: float = 1.0
|
||||
|
||||
|
||||
class LookupResult(BaseModel):
|
||||
@@ -135,6 +163,8 @@ class CheckOutRequest(BaseModel):
|
||||
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
|
||||
|
||||
|
||||
@@ -148,6 +178,13 @@ class LotOut(BaseModel):
|
||||
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
|
||||
@@ -205,6 +242,7 @@ class GroupShoppingItem(BaseModel):
|
||||
stock: float
|
||||
min_stock: float
|
||||
deficit: float
|
||||
unit_name: str = ""
|
||||
product_count: int
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user