IT-Stock/app/models.py

83 lines
3.2 KiB
Python
Raw Normal View History

"""
Modèles de données du stock IT.
Principe volontairement simple, demandé par le département IT : on ne suit
PAS des exemplaires individuels (pas de numéro de série, pas d'historique
détaillé par unité). On suit juste, pour chaque *type* de matériel, une
quantité en stock. Ex: "PC Dell XPS 13 modèle 9310" -> quantite=5,
"Câble RJ45 2m" -> quantite=42.
Hiérarchie : Categorie (arborescence à profondeur illimitée, auto-
référencée via parent_id) -> Materiel. Une catégorie peut avoir des
sous-catégories, qui peuvent elles-mêmes en avoir, etc. (ex: "Réseau" >
"Câbles" > "RJ45" > "2m"). Un matériel se rattache à n'importe quel nœud
de l'arborescence, pas forcément une feuille.
"""
from typing import Optional
from sqlmodel import Field, Relationship, SQLModel
class Categorie(SQLModel, table=True):
"""Nœud d'une arborescence de catégories à profondeur illimitée.
parent_id est vide pour une catégorie racine. La relation "parent" est
auto-référencée (une Categorie pointe vers une autre Categorie) : c'est
ce qui permet une profondeur arbitraire, contrairement à un modèle
Categorie/SousCategorie à deux niveaux fixes.
"""
id: Optional[int] = Field(default=None, primary_key=True)
nom: str
parent_id: Optional[int] = Field(default=None, foreign_key="categorie.id")
parent: Optional["Categorie"] = Relationship(
back_populates="enfants",
sa_relationship_kwargs={"remote_side": "Categorie.id"},
)
enfants: list["Categorie"] = Relationship(back_populates="parent")
materiels: list["Materiel"] = Relationship(back_populates="categorie")
def chemin(self) -> str:
"""Chemin complet depuis la racine, ex: 'Réseau / Câbles / RJ45'.
Utilisé partout on affiche une catégorie hors de son arbre
(liste de matériels, résultat de scan, recherche) pour garder le
contexte sans avoir à afficher l'arborescence entière.
"""
if self.parent is None:
return self.nom
return f"{self.parent.chemin()} / {self.nom}"
class Materiel(SQLModel, table=True):
"""Un type de matériel suivi en stock.
code_barre identifie le *type* de matériel (le "SKU"), pas un
exemplaire précis : scanner deux PC XPS 13 identiques renvoie le même
matériel, et chaque scan ajuste juste la quantité globale.
"""
id: Optional[int] = Field(default=None, primary_key=True)
nom: str
code_barre: Optional[str] = Field(default=None, index=True, unique=True)
quantite: int = Field(default=0)
# En dessous de ce seuil, une alerte email est envoyée (voir email_alerts.py).
# None = pas d'alerte configurée pour ce matériel.
seuil_alerte: Optional[int] = Field(default=None)
categorie_id: int = Field(foreign_key="categorie.id")
categorie: Optional[Categorie] = Relationship(back_populates="materiels")
class DestinataireAlerte(SQLModel, table=True):
"""Adresse email qui reçoit les alertes de stock bas.
Liste globale simple (pas de destinataires par matériel/catégorie) :
c'est suffisant pour une petite équipe IT et ça reste facile à gérer.
"""
id: Optional[int] = Field(default=None, primary_key=True)
email: str = Field(unique=True)