Backend: Einzelstücke (Items) mit UID je Gegenstand
Gegenstands-Produkte koennen als Einzelstuecke gefuehrt werden (Product.individual):
jedes physische Stueck ist ein Item mit eigener kurzer UID (fuer QR), eigenem
Lagerort, Kaufdatum, Garantie, Bezugsquelle und Notiz. So laesst sich dasselbe
Modell mehrfach getrennt fuehren (Powerbank 2024 + 2025).
Neu: models.Item + Product.individual (+Migration), Schemas, services/items.py
(UID-Erzeugung, Anreicherung), routers/items.py (CRUD, /items/by-uid/{uid} zur
QR-Aufloesung, /items/{id}/remove mit Grund als Bewegung). current_stock zaehlt
bei Einzelstueck-Produkten die Items. Tests + HTTP-Smoke gruen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -229,6 +229,10 @@ class Product(Base):
|
||||
ForeignKey("shops.id", ondelete="SET NULL"), nullable=True
|
||||
)
|
||||
product_url: Mapped[str | None] = mapped_column(String(1024), nullable=True)
|
||||
# Gegenstände: als Einzelstücke (Items mit eigener UID/QR) statt als Menge je
|
||||
# Lagerort verwalten. So bekommt jedes physische Stück ein eigenes Kaufdatum,
|
||||
# eine eigene Garantie und Bezugsquelle (z.B. dieselbe Powerbank 2024 + 2025).
|
||||
individual: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
|
||||
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_now)
|
||||
|
||||
@@ -498,3 +502,36 @@ class ProductFieldValue(Base):
|
||||
|
||||
product: Mapped[Product] = relationship(back_populates="field_values")
|
||||
field_definition: Mapped[FieldDefinition] = relationship()
|
||||
|
||||
|
||||
class Item(Base):
|
||||
"""Ein physisches Einzelstück eines Gegenstands (Instanz) mit eigener UID/QR.
|
||||
|
||||
Nur für Gegenstands-Produkte mit Einzelstück-Verwaltung (``Product.individual``).
|
||||
Je Stück ein eigener Lagerort, Kaufdatum, Garantie, Bezugsquelle und Notiz –
|
||||
so lässt sich dasselbe Modell mehrfach getrennt führen (Powerbank 2024 + 2025).
|
||||
Der QR-Code trägt die UID; ein Scan öffnet genau dieses Stück.
|
||||
"""
|
||||
|
||||
__tablename__ = "items"
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
# Kurzer, gut lesbarer Code für den QR (ohne verwechselbare Zeichen).
|
||||
uid: Mapped[str] = mapped_column(String(16), unique=True, index=True, nullable=False)
|
||||
product_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("products.id", ondelete="CASCADE"), nullable=False
|
||||
)
|
||||
location_id: Mapped[int | None] = mapped_column(
|
||||
ForeignKey("locations.id", ondelete="SET NULL"), nullable=True
|
||||
)
|
||||
shop_id: Mapped[int | None] = mapped_column(
|
||||
ForeignKey("shops.id", ondelete="SET NULL"), nullable=True
|
||||
)
|
||||
acquired_on: Mapped[date | None] = mapped_column(Date, nullable=True) # gekauft am
|
||||
warranty_until: Mapped[date | None] = mapped_column(Date, nullable=True) # Garantie bis
|
||||
note: Mapped[str | None] = mapped_column(String(255), nullable=True) # Notiz/Zustand
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_now)
|
||||
|
||||
product: Mapped[Product] = relationship()
|
||||
location: Mapped[Location | None] = relationship()
|
||||
shop: Mapped[Shop | None] = relationship()
|
||||
|
||||
Reference in New Issue
Block a user