feat(inventory): add spare parts management
This commit is contained in:
parent
abc6e308dd
commit
fb5c2cc26a
43 changed files with 2950 additions and 2 deletions
Binary file not shown.
110
backend/hermes/app/models/inventory.py
Normal file
110
backend/hermes/app/models/inventory.py
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
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 ""
|
||||
Loading…
Add table
Add a link
Reference in a new issue