Lagerorte per 10-Zeichen-Code statt fortlaufender ID; iOS-Einlagern ohne Kamerazwang

Lagerort-IDs sind jetzt ein zufaelliger 10-Zeichen-Code (wie die Einzelstueck-UIDs) statt einer fortlaufenden Zahl - so kollidiert die Stammdaten-Sicherung zwischen zwei Instanzen praktisch nie mehr, und der Code ist zugleich der Inhalt des QR /l/<code>. Alle Fremdschluessel (lots, movements, items, Mindestbestaende, parent_id) ziehen mit; die Umstellung laeuft einmalig und transaktional beim Serverstart (_migrate_locations_to_code) und rollt bei Fehlern komplett zurueck. Vor dem Deploy ein DB-Backup machen.

iOS-Einlagern oeffnet nicht mehr sofort die Kamera, sondern ein Formular mit Artikelsuche; die Kamera kommt erst per Button. Im Formular laesst sich der Lagerort zusaetzlich per /l/-QR scannen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-07-27 12:15:25 +02:00
parent 6e48cd0a7a
commit d518b4aa23
23 changed files with 507 additions and 220 deletions

View File

@@ -111,13 +111,13 @@ class UserUpdate(BaseModel):
# ---- Groups ----
class LocationMinStockIn(BaseModel):
"""Ein Mindestbestand-Eintrag je Lagerort (Menge in Artikeleinheiten)."""
location_id: int
location_id: str
min_stock: float = Field(ge=0)
class LocationMinStockOut(BaseModel):
model_config = ConfigDict(from_attributes=True)
location_id: int
location_id: str
location_name: str | None = None
min_stock: float
@@ -239,19 +239,19 @@ class FieldDefinitionOut(BaseModel):
# ---- Locations ----
class LocationOut(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: int
id: str
name: str
parent_id: int | None = None
parent_id: str | None = None
class LocationCreate(BaseModel):
name: str = Field(min_length=1, max_length=120)
parent_id: int | None = None
parent_id: str | None = None
class LocationUpdate(BaseModel):
name: str | None = Field(default=None, min_length=1, max_length=120)
parent_id: int | None = None
parent_id: str | None = None
# ---- Gebinde (Packung, Glas, …) ----
@@ -409,7 +409,7 @@ class CheckInRequest(BaseModel):
best_before: date | None = None
# "month" legt das MHD auf den Monatsletzten (siehe services/dates.py).
best_before_precision: DatePrecision = DatePrecision.day
location_id: int | None = None
location_id: str | None = None
note: str | None = None
@@ -430,7 +430,7 @@ class LotOut(BaseModel):
quantity: float
best_before: date | None
best_before_precision: DatePrecision = DatePrecision.day
location_id: int | None
location_id: str | None
created_at: datetime
@@ -439,7 +439,7 @@ class LotUpdate(BaseModel):
quantity: float | None = Field(default=None, gt=0)
best_before: date | None = None
best_before_precision: DatePrecision | None = None
location_id: int | None = None
location_id: str | None = None
class CheckInResponse(BaseModel):
@@ -452,7 +452,7 @@ class CheckInLine(BaseModel):
quantity: float = Field(gt=0)
best_before: date | None = None
best_before_precision: DatePrecision = DatePrecision.day
location_id: int | None = None
location_id: str | None = None
class BatchCheckInRequest(BaseModel):
@@ -479,8 +479,8 @@ class RelocateRequest(BaseModel):
product_id: int | None = None
barcode: str | None = None
quantity: float = Field(gt=0)
from_location_id: int | None = None
to_location_id: int | None = None
from_location_id: str | None = None
to_location_id: str | None = None
note: str | None = None
@@ -489,7 +489,7 @@ class RemoveRequest(BaseModel):
product_id: int | None = None
barcode: str | None = None
quantity: float = Field(gt=0)
location_id: int | None = None
location_id: str | None = None
reason: RemovalReason
note: str | None = None
@@ -508,7 +508,7 @@ class RemovalStat(BaseModel):
class RemovalHistoryItem(BaseModel):
reason: RemovalReason
quantity: float
location_id: int | None = None
location_id: str | None = None
location_name: str | None = None
note: str | None = None
username: str | None = None
@@ -524,7 +524,7 @@ class RemovalSummary(BaseModel):
class ItemCreate(BaseModel):
"""Ein oder mehrere Einzelstücke mit gemeinsamen Startwerten anlegen."""
count: int = Field(default=1, ge=1, le=200)
location_id: int | None = None
location_id: str | None = None
shop_id: int | None = None
acquired_on: date | None = None
warranty_until: date | None = None
@@ -534,7 +534,7 @@ class ItemCreate(BaseModel):
class ItemUpdate(BaseModel):
location_id: int | None = None
location_id: str | None = None
shop_id: int | None = None
acquired_on: date | None = None
warranty_until: date | None = None
@@ -578,7 +578,7 @@ class ItemOut(BaseModel):
id: int
uid: str
product_id: int
location_id: int | None = None
location_id: str | None = None
location_name: str | None = None
shop_id: int | None = None
shop_name: str | None = None
@@ -652,7 +652,7 @@ class LocationNeedGroup(BaseModel):
class LocationNeeds(BaseModel):
"""Alle Bedarfe (Produkte + Gruppen) eines Lagerorts."""
location_id: int
location_id: str
location_name: str
products: list[LocationNeedProduct] = []
groups: list[LocationNeedGroup] = []