Olympus/backend/hermes/app/models/inventory.py
2026-07-05 11:32:06 +02:00

110 lines
6.4 KiB
Python

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", lazy="joined")
@property
def actor_username(self) -> str:
return self.actor.username if self.actor is not None else ""