""" Inventory item and storage-bin models. Domain context – "chaotic stockpiling" --------------------------------------- Small resellers often acquire items in bulk lots before they have time to fully catalog them. Items may sit in physical boxes/bins while waiting to be processed. This model supports that workflow: StorageBin – a physical or logical container (a shelf, a box, a room). Each bin belongs to a user (the person responsible for it). Item – a single sellable thing. It tracks the typical resale lifecycle: FOUND → LISTED → SOLD → ARCHIVED An item can optionally be placed in a StorageBin. Both tables record who created/last-modified them and when, so we can generate an audit trail (see issue #3 in the v1 Gitea project). """ import enum from datetime import datetime from sqlalchemy import ( DateTime, Enum, Float, ForeignKey, Integer, String, Text, func, ) from sqlalchemy.orm import Mapped, mapped_column, relationship from app.database import Base class ItemStatus(str, enum.Enum): """ Lifecycle states of an inventory item. Using a string enum means the value stored in the DB is human-readable ('found', 'listed', …) rather than an opaque integer, which makes it much easier to debug directly in the database file. """ FOUND = "found" # Acquired but not yet evaluated / photographed LISTED = "listed" # Posted for sale somewhere (eBay, Kleinanzeigen, …) SOLD = "sold" # Sold – waiting for shipment or already shipped ARCHIVED = "archived" # Off the market (broken, kept, donated, …) class StorageBin(Base): """ A physical or logical container for items. Customers create bins so they know where their chaotic pile actually is. Admins can see all bins; customers only see their own. """ __tablename__ = "storage_bins" id: Mapped[int] = mapped_column(primary_key=True, index=True) # Human-readable label: "Box 7", "Garage shelf B", etc. name: Mapped[str] = mapped_column(String(120), nullable=False) # Optional free-text description / location notes. description: Mapped[str | None] = mapped_column(Text, nullable=True) # The user who owns / is responsible for this bin. owner_id: Mapped[int] = mapped_column( ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True ) created_at: Mapped[datetime] = mapped_column( DateTime, server_default=func.now(), nullable=False ) updated_at: Mapped[datetime] = mapped_column( DateTime, server_default=func.now(), onupdate=func.now(), nullable=False, ) # Back-reference so we can do bin.items to get everything inside. items: Mapped[list["Item"]] = relationship( "Item", back_populates="storage_bin", lazy="select" ) def __repr__(self) -> str: return f"" class Item(Base): """ A single inventory item. Intentionally loose: the only required fields are title and status so that items can be created quickly ("dump mode") and detailed later. """ __tablename__ = "items" id: Mapped[int] = mapped_column(primary_key=True, index=True) # Short name shown in listings. Required. title: Mapped[str] = mapped_column(String(200), nullable=False, index=True) # Detailed description for listings. description: Mapped[str | None] = mapped_column(Text, nullable=True) # Current lifecycle state. New items default to FOUND. status: Mapped[ItemStatus] = mapped_column( Enum(ItemStatus), nullable=False, default=ItemStatus.FOUND, index=True ) # What we paid for this item (cost basis for profit tracking). purchase_price: Mapped[float | None] = mapped_column(Float, nullable=True) # What we're asking / what it sold for. listing_price: Mapped[float | None] = mapped_column(Float, nullable=True) sale_price: Mapped[float | None] = mapped_column(Float, nullable=True) # Optional link to where it's listed (eBay URL, Kleinanzeigen link, …). listing_url: Mapped[str | None] = mapped_column(String(500), nullable=True) # Optional SKU / internal reference number. sku: Mapped[str | None] = mapped_column( String(80), nullable=True, index=True, unique=True ) # Which bin this item is currently sitting in. Null = "somewhere". storage_bin_id: Mapped[int | None] = mapped_column( ForeignKey("storage_bins.id", ondelete="SET NULL"), nullable=True, index=True ) storage_bin: Mapped[StorageBin | None] = relationship( "StorageBin", back_populates="items" ) # Who created this item and who last touched it. created_by_id: Mapped[int] = mapped_column( ForeignKey("users.id", ondelete="SET NULL"), nullable=True, index=True ) updated_by_id: Mapped[int | None] = mapped_column( ForeignKey("users.id", ondelete="SET NULL"), nullable=True ) created_at: Mapped[datetime] = mapped_column( DateTime, server_default=func.now(), nullable=False ) updated_at: Mapped[datetime] = mapped_column( DateTime, server_default=func.now(), onupdate=func.now(), nullable=False, ) def __repr__(self) -> str: return f""