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:
@@ -8,7 +8,7 @@ from sqlalchemy import asc
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from ..models import Lot, Movement, MovementType, Product, User
|
||||
from .units import to_base_quantity
|
||||
from .conversion import to_base
|
||||
|
||||
|
||||
class StockError(ValueError):
|
||||
@@ -34,7 +34,7 @@ def check_in(
|
||||
note: str | None = None,
|
||||
) -> Lot:
|
||||
"""Legt eine neue Charge an und protokolliert die Bewegung."""
|
||||
quantity_base = to_base_quantity(product, quantity, unit)
|
||||
quantity_base = to_base(db, product, quantity, unit)
|
||||
|
||||
lot = Lot(
|
||||
product_id=product.id,
|
||||
@@ -59,6 +59,41 @@ def check_in(
|
||||
return lot
|
||||
|
||||
|
||||
def check_out_lot(
|
||||
db: Session,
|
||||
product: Product,
|
||||
lot: Lot,
|
||||
quantity: float,
|
||||
unit: str,
|
||||
user: User | None,
|
||||
note: str | None = None,
|
||||
) -> list[dict]:
|
||||
"""Bucht gezielt von EINER Charge ab (manuelle Auswahl statt FEFO)."""
|
||||
needed = to_base(db, product, quantity, unit)
|
||||
if needed > lot.quantity + 1e-9:
|
||||
raise StockError(
|
||||
f"Diese Charge hat nur {lot.quantity:g} {product.base_unit.value} "
|
||||
f"(benötigt {needed:g})."
|
||||
)
|
||||
|
||||
lot.quantity -= needed
|
||||
db.add(
|
||||
Movement(
|
||||
product_id=product.id,
|
||||
lot_id=lot.id,
|
||||
user_id=user.id if user else None,
|
||||
type=MovementType.out,
|
||||
quantity=needed,
|
||||
unit_used=unit,
|
||||
note=note,
|
||||
)
|
||||
)
|
||||
affected = [{"lot_id": lot.id, "quantity": needed}]
|
||||
if lot.quantity <= 1e-9:
|
||||
db.delete(lot)
|
||||
return affected
|
||||
|
||||
|
||||
def _fefo_lots(db: Session, product_id: int) -> list[Lot]:
|
||||
"""Lots eines Produkts, sortiert nach Ablaufdatum (NULL zuletzt), dann Alter."""
|
||||
lots = (
|
||||
@@ -88,7 +123,7 @@ def check_out(
|
||||
Gibt die Liste der betroffenen Chargen mit abgebuchter Menge zurück.
|
||||
Wirft StockError, wenn der Gesamtbestand nicht ausreicht.
|
||||
"""
|
||||
needed = to_base_quantity(product, quantity, unit)
|
||||
needed = to_base(db, product, quantity, unit)
|
||||
available = current_stock(db, product.id)
|
||||
if needed > available + 1e-9:
|
||||
raise StockError(
|
||||
|
||||
Reference in New Issue
Block a user