claude code

This commit is contained in:
2026-01-28 19:22:30 +01:00
parent f9b1d43c81
commit bdbfa4e25a
104 changed files with 9591 additions and 261 deletions
+19
View File
@@ -0,0 +1,19 @@
"""Package des modèles SQLAlchemy.
Importe tous les modèles pour qu'ils soient disponibles pour Alembic.
"""
from app.models.category import Category
from app.models.document import Document, DocumentType
from app.models.item import Item, ItemStatus
from app.models.location import Location, LocationType
__all__ = [
"Category",
"Location",
"LocationType",
"Item",
"ItemStatus",
"Document",
"DocumentType",
]
+61
View File
@@ -0,0 +1,61 @@
"""Modèle SQLAlchemy pour les catégories d'objets.
Les catégories permettent de classer les objets par domaine d'utilisation
(bricolage, informatique, électronique, cuisine, etc.).
"""
from datetime import datetime
from typing import TYPE_CHECKING
from sqlalchemy import DateTime, String, Text
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.sql import func
from app.core.database import Base
if TYPE_CHECKING:
from app.models.item import Item
class Category(Base):
"""Catégorie d'objets.
Attributes:
id: Identifiant unique auto-incrémenté
name: Nom de la catégorie (ex: "Bricolage", "Informatique")
description: Description optionnelle de la catégorie
color: Couleur hexadécimale pour l'affichage (ex: "#3b82f6")
icon: Nom d'icône optionnel (ex: "wrench", "computer")
created_at: Date/heure de création (auto)
updated_at: Date/heure de dernière modification (auto)
items: Relation vers les objets de cette catégorie
"""
__tablename__ = "categories"
# Colonnes
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
name: Mapped[str] = mapped_column(String(100), unique=True, nullable=False, index=True)
description: Mapped[str | None] = mapped_column(Text, nullable=True)
color: Mapped[str | None] = mapped_column(String(7), nullable=True) # Format: #RRGGBB
icon: Mapped[str | None] = mapped_column(String(50), nullable=True)
# Timestamps
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now(), nullable=False
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
server_default=func.now(),
onupdate=func.now(),
nullable=False,
)
# Relations
items: Mapped[list["Item"]] = relationship(
"Item", back_populates="category", cascade="all, delete-orphan"
)
def __repr__(self) -> str:
"""Représentation string de la catégorie."""
return f"<Category(id={self.id}, name='{self.name}')>"
+85
View File
@@ -0,0 +1,85 @@
"""Modèle SQLAlchemy pour les documents attachés aux objets.
Les documents peuvent être des photos, notices d'utilisation, factures, etc.
Ils sont stockés sur le système de fichiers local.
"""
from datetime import datetime
from typing import TYPE_CHECKING
from sqlalchemy import DateTime, Enum, ForeignKey, Integer, String
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.sql import func
from app.core.database import Base
if TYPE_CHECKING:
from app.models.item import Item
import enum
class DocumentType(str, enum.Enum):
"""Type de document."""
PHOTO = "photo" # Photo de l'objet
MANUAL = "manual" # Notice d'utilisation
INVOICE = "invoice" # Facture d'achat
WARRANTY = "warranty" # Garantie
OTHER = "other" # Autre
class Document(Base):
"""Document attaché à un objet.
Attributes:
id: Identifiant unique auto-incrémenté
filename: Nom du fichier sur le disque (UUID + extension)
original_name: Nom original du fichier uploadé
type: Type de document (photo/manual/invoice/warranty/other)
mime_type: Type MIME (ex: "image/jpeg", "application/pdf")
size_bytes: Taille du fichier en octets
file_path: Chemin relatif du fichier (ex: "uploads/photos/uuid.jpg")
description: Description optionnelle
item_id: ID de l'objet associé (FK)
created_at: Date/heure de création (auto)
updated_at: Date/heure de dernière modification (auto)
item: Relation vers l'objet
"""
__tablename__ = "documents"
# Colonnes
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
filename: Mapped[str] = mapped_column(String(255), nullable=False, unique=True)
original_name: Mapped[str] = mapped_column(String(255), nullable=False)
type: Mapped[DocumentType] = mapped_column(
Enum(DocumentType, native_enum=False, length=20), nullable=False
)
mime_type: Mapped[str] = mapped_column(String(100), nullable=False)
size_bytes: Mapped[int] = mapped_column(Integer, nullable=False)
file_path: Mapped[str] = mapped_column(String(500), nullable=False)
description: Mapped[str | None] = mapped_column(String(500), nullable=True)
# Foreign Key
item_id: Mapped[int] = mapped_column(
Integer, ForeignKey("items.id", ondelete="CASCADE"), nullable=False, index=True
)
# Timestamps
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now(), nullable=False
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
server_default=func.now(),
onupdate=func.now(),
nullable=False,
)
# Relations
item: Mapped["Item"] = relationship("Item", back_populates="documents")
def __repr__(self) -> str:
"""Représentation string du document."""
return f"<Document(id={self.id}, type={self.type.value}, filename='{self.filename}')>"
+113
View File
@@ -0,0 +1,113 @@
"""Modèle SQLAlchemy pour les objets de l'inventaire.
Les objets (items) sont l'entité centrale de l'application.
Chaque objet appartient à une catégorie et est situé à un emplacement.
"""
from datetime import date, datetime
from decimal import Decimal
from typing import TYPE_CHECKING
from sqlalchemy import Date, DateTime, Enum, ForeignKey, Integer, Numeric, String, Text
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.sql import func
from app.core.database import Base
if TYPE_CHECKING:
from app.models.category import Category
from app.models.document import Document
from app.models.location import Location
import enum
class ItemStatus(str, enum.Enum):
"""Statut d'un objet."""
IN_STOCK = "in_stock" # En stock (non utilisé)
IN_USE = "in_use" # En cours d'utilisation
BROKEN = "broken" # Cassé/HS
SOLD = "sold" # Vendu
LENT = "lent" # Prêté
class Item(Base):
"""Objet de l'inventaire domestique.
Attributes:
id: Identifiant unique auto-incrémenté
name: Nom de l'objet (ex: "Perceuse sans fil Bosch")
description: Description détaillée optionnelle
quantity: Quantité en stock (défaut: 1)
price: Prix d'achat optionnel
purchase_date: Date d'achat optionnelle
status: Statut de l'objet (in_stock/in_use/broken/sold/lent)
brand: Marque optionnelle (ex: "Bosch", "Samsung")
model: Modèle optionnel (ex: "PSR 18 LI-2")
serial_number: Numéro de série optionnel
notes: Notes libres
category_id: ID de la catégorie (FK)
location_id: ID de l'emplacement (FK)
created_at: Date/heure de création (auto)
updated_at: Date/heure de dernière modification (auto)
category: Relation vers la catégorie
location: Relation vers l'emplacement
documents: Relation vers les documents (photos, notices, factures)
"""
__tablename__ = "items"
# Colonnes principales
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
name: Mapped[str] = mapped_column(String(200), nullable=False, index=True)
description: Mapped[str | None] = mapped_column(Text, nullable=True)
quantity: Mapped[int] = mapped_column(Integer, nullable=False, default=1)
status: Mapped[ItemStatus] = mapped_column(
Enum(ItemStatus, native_enum=False, length=20),
nullable=False,
default=ItemStatus.IN_STOCK,
)
# Informations produit
brand: Mapped[str | None] = mapped_column(String(100), nullable=True)
model: Mapped[str | None] = mapped_column(String(100), nullable=True)
serial_number: Mapped[str | None] = mapped_column(String(100), nullable=True, unique=True)
url: Mapped[str | None] = mapped_column(String(500), nullable=True) # Lien vers page produit
# Informations achat
price: Mapped[Decimal | None] = mapped_column(Numeric(10, 2), nullable=True)
purchase_date: Mapped[date | None] = mapped_column(Date, nullable=True)
# Notes
notes: Mapped[str | None] = mapped_column(Text, nullable=True)
# Foreign Keys
category_id: Mapped[int] = mapped_column(
Integer, ForeignKey("categories.id", ondelete="RESTRICT"), nullable=False, index=True
)
location_id: Mapped[int] = mapped_column(
Integer, ForeignKey("locations.id", ondelete="RESTRICT"), nullable=False, index=True
)
# Timestamps
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now(), nullable=False
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
server_default=func.now(),
onupdate=func.now(),
nullable=False,
)
# Relations
category: Mapped["Category"] = relationship("Category", back_populates="items")
location: Mapped["Location"] = relationship("Location", back_populates="items")
documents: Mapped[list["Document"]] = relationship(
"Document", back_populates="item", cascade="all, delete-orphan"
)
def __repr__(self) -> str:
"""Représentation string de l'objet."""
return f"<Item(id={self.id}, name='{self.name}', status={self.status.value})>"
+97
View File
@@ -0,0 +1,97 @@
"""Modèle SQLAlchemy pour les emplacements de stockage.
Les emplacements sont organisés de manière hiérarchique :
pièce → meuble → tiroir → boîte
Exemple : Garage → Étagère A → Tiroir 2 → Boîte visserie
"""
from datetime import datetime
from typing import TYPE_CHECKING
from sqlalchemy import DateTime, Enum, ForeignKey, Integer, String
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.sql import func
from app.core.database import Base
if TYPE_CHECKING:
from app.models.item import Item
import enum
class LocationType(str, enum.Enum):
"""Type d'emplacement dans la hiérarchie."""
ROOM = "room" # Pièce (ex: Garage, Cuisine)
FURNITURE = "furniture" # Meuble (ex: Étagère, Armoire)
DRAWER = "drawer" # Tiroir
BOX = "box" # Boîte/Bac de rangement
class Location(Base):
"""Emplacement de stockage hiérarchique.
Attributes:
id: Identifiant unique auto-incrémenté
name: Nom de l'emplacement (ex: "Garage", "Étagère A")
type: Type d'emplacement (room/furniture/drawer/box)
parent_id: ID du parent (None si racine)
path: Chemin complet calculé (ex: "Garage > Étagère A > Tiroir 2")
description: Description optionnelle
created_at: Date/heure de création (auto)
updated_at: Date/heure de dernière modification (auto)
parent: Relation vers le parent
children: Relation vers les enfants
items: Relation vers les objets à cet emplacement
"""
__tablename__ = "locations"
# Colonnes
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
name: Mapped[str] = mapped_column(String(100), nullable=False, index=True)
type: Mapped[LocationType] = mapped_column(
Enum(LocationType, native_enum=False, length=20), nullable=False
)
parent_id: Mapped[int | None] = mapped_column(
Integer, ForeignKey("locations.id", ondelete="CASCADE"), nullable=True, index=True
)
path: Mapped[str] = mapped_column(String(500), nullable=False, index=True)
description: Mapped[str | None] = mapped_column(String(500), nullable=True)
# Timestamps
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now(), nullable=False
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
server_default=func.now(),
onupdate=func.now(),
nullable=False,
)
# Relations
parent: Mapped["Location | None"] = relationship(
"Location", remote_side=[id], back_populates="children"
)
children: Mapped[list["Location"]] = relationship(
"Location", back_populates="parent", cascade="all, delete-orphan"
)
items: Mapped[list["Item"]] = relationship(
"Item", back_populates="location", cascade="all, delete-orphan"
)
def __repr__(self) -> str:
"""Représentation string de l'emplacement."""
return f"<Location(id={self.id}, name='{self.name}', type={self.type.value})>"
def calculate_path(self) -> str:
"""Calcule le chemin complet de l'emplacement.
Returns:
Chemin complet (ex: "Garage > Étagère A > Tiroir 2")
"""
if self.parent is None:
return self.name
return f"{self.parent.calculate_path()} > {self.name}"