from datetime import datetime from decimal import Decimal from sqlalchemy import Boolean, DateTime, ForeignKey, Integer, Numeric, String, Text, UniqueConstraint, func from sqlalchemy.orm import Mapped, mapped_column, relationship from app.db.database import Base class InventoryCategory(Base): __tablename__ = "inventory_categories" id: Mapped[int] = mapped_column(primary_key=True) name: Mapped[str] = mapped_column(String(160), unique=True, index=True) slug: Mapped[str] = mapped_column(String(180), unique=True, index=True) description: Mapped[str | None] = mapped_column(Text, nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now()) items: Mapped[list["InventoryItem"]] = relationship(back_populates="category") class InventoryLocation(Base): __tablename__ = "inventory_locations" id: Mapped[int] = mapped_column(primary_key=True) name: Mapped[str] = mapped_column(String(160), unique=True, index=True) description: Mapped[str | None] = mapped_column(Text, nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now()) items: Mapped[list["InventoryItem"]] = relationship(back_populates="location") class InventorySupplier(Base): __tablename__ = "inventory_suppliers" id: Mapped[int] = mapped_column(primary_key=True) name: Mapped[str] = mapped_column(String(180), unique=True, index=True) contact_name: Mapped[str | None] = mapped_column(String(180), nullable=True) email: Mapped[str | None] = mapped_column(String(255), nullable=True) phone: Mapped[str | None] = mapped_column(String(80), nullable=True) website: Mapped[str | None] = mapped_column(String(255), nullable=True) customer_number: Mapped[str | None] = mapped_column(String(120), nullable=True) notes: Mapped[str | None] = mapped_column(Text, nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now()) items: Mapped[list["InventoryItem"]] = relationship(back_populates="supplier") class InventoryItem(Base): __tablename__ = "inventory_items" __table_args__ = (UniqueConstraint("sku", name="uq_inventory_items_sku"),) id: Mapped[int] = mapped_column(primary_key=True) sku: Mapped[str] = mapped_column(String(40), unique=True, index=True) name: Mapped[str] = mapped_column(String(255), index=True) description: Mapped[str | None] = mapped_column(Text, nullable=True) category_id: Mapped[int | None] = mapped_column(ForeignKey("inventory_categories.id", ondelete="SET NULL"), nullable=True, index=True) manufacturer: Mapped[str | None] = mapped_column(String(180), nullable=True, index=True) manufacturer_part_number: Mapped[str | None] = mapped_column(String(180), nullable=True, index=True) supplier_id: Mapped[int | None] = mapped_column(ForeignKey("inventory_suppliers.id", ondelete="SET NULL"), nullable=True, index=True) supplier_part_number: Mapped[str | None] = mapped_column(String(180), nullable=True, index=True) compatible_devices: Mapped[str | None] = mapped_column(Text, nullable=True) location_id: Mapped[int | None] = mapped_column(ForeignKey("inventory_locations.id", ondelete="SET NULL"), nullable=True, index=True) quantity_on_hand: Mapped[int] = mapped_column(Integer, default=0, server_default="0") quantity_reserved: Mapped[int] = mapped_column(Integer, default=0, server_default="0") quantity_available: Mapped[int] = mapped_column(Integer, default=0, server_default="0", index=True) reorder_level: Mapped[int] = mapped_column(Integer, default=0, server_default="0") unit: Mapped[str] = mapped_column(String(40), default="Stk.", server_default="Stk.") purchase_price_cents: Mapped[int | None] = mapped_column(Integer, nullable=True) selling_price_cents: Mapped[int | None] = mapped_column(Integer, nullable=True) currency: Mapped[str] = mapped_column(String(3), default="EUR", server_default="EUR") tax_rate_percent: Mapped[Decimal] = mapped_column(Numeric(5, 2), default=Decimal("19.00"), server_default="19.00") notes: Mapped[str | None] = mapped_column(Text, nullable=True) is_active: Mapped[bool] = mapped_column(Boolean, default=True, server_default="true", index=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now()) category: Mapped[InventoryCategory | None] = relationship(back_populates="items", lazy="joined") supplier: Mapped[InventorySupplier | None] = relationship(back_populates="items", lazy="joined") location: Mapped[InventoryLocation | None] = relationship(back_populates="items", lazy="joined") movements: Mapped[list["InventoryStockMovement"]] = relationship( back_populates="item", cascade="all, delete-orphan", order_by="InventoryStockMovement.created_at.desc()", ) class InventoryStockMovement(Base): __tablename__ = "inventory_stock_movements" id: Mapped[int] = mapped_column(primary_key=True) item_id: Mapped[int] = mapped_column(ForeignKey("inventory_items.id", ondelete="CASCADE"), index=True) movement_type: Mapped[str] = mapped_column(String(40), index=True) quantity: Mapped[int] = mapped_column(Integer) reason: Mapped[str] = mapped_column(String(255), default="", server_default="") reference_type: Mapped[str | None] = mapped_column(String(80), nullable=True, index=True) reference_id: Mapped[int | None] = mapped_column(Integer, nullable=True, index=True) note: Mapped[str | None] = mapped_column(Text, nullable=True) actor_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True, index=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) item: Mapped[InventoryItem] = relationship(back_populates="movements") actor = relationship( "User", foreign_keys="InventoryStockMovement.actor_user_id", lazy="joined", ) @property def actor_username(self) -> str: return self.actor.username if self.actor is not None else ""