Gebinde-Bezeichnung je Produkt + Spalte "Einheiten (ist / soll)"

- Neues Feld products.package_label: pro Artikel einstellbar, wie das Gebinde
  heisst (Packung, Glas, Tuete, Flasche, Dose, Tube, ...) - Eingabe mit
  Vorschlagsliste, frei ueberschreibbar. Migration per ADD COLUMN IF NOT EXISTS.
- Produkttabelle: Spalte "Packungen" heisst jetzt "Einheiten (ist / soll)" und
  zeigt Bestand und Mindestbestand als "11 / 12 Glas"; die separate Spalte
  "Mindest" entfaellt. Das "niedrig"-Badge sitzt jetzt dort.
- Die Bezeichnung wird ueberall verwendet (Chargenanzeige, Einheiten-Umschalter
  beim Bearbeiten, Mindestbestand-Auswahl, Umrechnungshinweis).
- Speichern/Abbrechen im Chargen-Editor sind jetzt gleich breit (.btn-pair).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-07-22 13:39:44 +02:00
parent c065b8879d
commit 3884919a4c
7 changed files with 54 additions and 20 deletions

View File

@@ -41,6 +41,7 @@ def _ensure_schema() -> None:
"REFERENCES units(id) ON DELETE SET NULL",
"ALTER TABLE products ADD COLUMN IF NOT EXISTS min_stock_in_packages BOOLEAN "
"NOT NULL DEFAULT FALSE",
"ALTER TABLE products ADD COLUMN IF NOT EXISTS package_label VARCHAR(32)",
]
with engine.begin() as conn:
for stmt in stmts:

View File

@@ -119,6 +119,8 @@ class Product(Base):
)
# Number of base units contained in one package (e.g. 500 g per package).
package_size: Mapped[float | None] = mapped_column(Float, nullable=True)
# Bezeichnung eines Gebindes: "Packung", "Glas", "Tüte", "Flasche", …
package_label: Mapped[str | None] = mapped_column(String(32), nullable=True)
group_id: Mapped[int | None] = mapped_column(
ForeignKey("groups.id", ondelete="SET NULL"), nullable=True

View File

@@ -82,6 +82,7 @@ def create_product(
base_unit=base_unit,
display_unit_id=display_unit_id,
package_size=payload.package_size,
package_label=payload.package_label or None,
group_id=payload.group_id,
min_stock=payload.min_stock,
min_stock_unit_id=payload.min_stock_unit_id,

View File

@@ -99,6 +99,8 @@ class ProductBase(BaseModel):
# 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.
@@ -118,6 +120,7 @@ class ProductUpdate(BaseModel):
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
@@ -134,6 +137,7 @@ class ProductOut(BaseModel):
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