IT-Stock/CLAUDE.md

147 lines
8.1 KiB
Markdown
Raw Normal View History

# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Projet
Application web interne pour le département IT d'une clinique privée : gestion
du stock de matériel informatique (arborescence de catégories + matériels),
avec recherche de matériel par scan de code-barres et alertes email quand le
stock passe sous un seuil défini.
Contraintes du domaine, volontairement strictes :
- **Pas d'authentification dans l'app** : l'accès est restreint par un
mécanisme externe (réseau interne, proxy...). Ne pas ajouter de login/session.
- **Pas de suivi d'exemplaires individuels.** Un `Materiel` est un *type*
d'objet (ex: "PC Dell XPS 13 modèle 9310", "Câble RJ45 2m") avec juste une
quantité en stock. Pas de numéro de série, pas d'historique par unité.
- **Douchette code-barres = clavier.** Les douchettes USB/Bluetooth émulent un
clavier (elles tapent le code + Entrée). Aucune intégration matérielle
n'est nécessaire côté app : un simple `<input>` seul dans un `<form>`
suffit, le navigateur soumet au Entrée.
- **Catégories à profondeur illimitée.** `Categorie` est auto-référencée
(`parent_id`), pas un modèle Catégorie/Sous-catégorie à deux niveaux fixes :
une catégorie peut avoir des sous-catégories qui en ont elles-mêmes, sans
limite (ex: Réseau > Câbles > RJ45 > 2m). Voir `Categorie.chemin()`.
- **Listes potentiellement longues.** Les pages Matériels/Catégories/
Destinataires sont conçues pour rester utilisables avec beaucoup
d'éléments : recherche texte côté serveur (`?q=`), lignes repliées par
défaut (`<details>`), jamais de tableau avec un `<input>` par colonne
affiché sur toutes les lignes à la fois (ça déborde horizontalement).
## Stack
Python + FastAPI + SQLModel (ORM) + SQLite (fichier `data/stock.db`) +
Jinja2 (rendu HTML côté serveur, formulaires classiques, pas de bundler JS).
Tout vit dans ce dossier : pas de service externe requis pour développer et
tester en local. Les emails d'alerte sont simulés dans la console tant
qu'aucun SMTP n'est configuré (voir `app/email_alerts.py` et `.env.example`).
## Commandes
```bash
source .venv/bin/activate # créer avec: python3 -m venv .venv
pip install -r requirements-dev.txt
uvicorn app.main:app --reload # lancer le serveur de dev (http://127.0.0.1:8000)
pytest # lancer les tests
ruff check . # linter
```
Les tests utilisent une base SQLite **en mémoire** (voir `tests/conftest.py`),
jamais le fichier `data/stock.db` du développement.
## Convention : commentaires détaillés
Contrairement à la préférence par défaut (commentaires minimaux), ce projet
doit rester compréhensible par d'autres membres du département IT qui n'ont
pas suivi son développement. **Commenter en français, de façon détaillée**,
en particulier le "pourquoi" des décisions non évidentes (ex: pourquoi la
douchette ne nécessite pas d'intégration, pourquoi les emails sont simulés en
l'absence de SMTP_HOST). Ne pas se limiter au commentaire minimal habituel.
## Architecture
```
app/
main.py point d'entrée FastAPI, montage des routers, démarrage du planificateur
config.py lecture des variables d'environnement (SMTP, cron du résumé...)
database.py connexion SQLite + dependency get_session()
models.py modèles SQLModel : Categorie (arborescence auto-référencée), Materiel, DestinataireAlerte
categories_arbre.py parcours de l'arborescence (racines, aplatissement pour un <select> indenté)
email_alerts.py alerte immédiate + résumé périodique de stock, envoie/simule l'email
scheduler.py planification (APScheduler) de l'email récapitulatif périodique
templates_engine.py instance Jinja2Templates partagée par tous les routers
routers/ une route FastAPI par ressource (categories, materiels, scan, destinataires)
templates/ pages HTML (Jinja2, formulaires HTML classiques)
static/ CSS
tests/ pytest + TestClient FastAPI + base SQLite en mémoire
data/ base SQLite locale, ignorée par git
```
Point d'attention Starlette : `Jinja2Templates.TemplateResponse()` prend
`request` en premier argument positionnel (`templates.TemplateResponse(request,
"page.html", {...})`), **pas** `{"request": request, ...}` dans le contexte —
c'est l'API de la version de Starlette installée ici.
Les listings (`materiels.html`, `categories.html`) utilisent `<details>`/
`<summary>` pour replier les lignes/branches par défaut plutôt qu'un
tableau tout-en-input : c'est ce qui évite le débordement horizontal et
garde la page utilisable avec beaucoup d'éléments. Le formulaire d'édition
complet d'un matériel ne s'affiche que dans la ligne dépliée (voir
`.formulaire-edition` dans `style.css`), pas sur toutes les lignes en
permanence.
`/materiels` (aussi servie sur `/`, voir `main.py`) combine recherche
(`?q=`), filtre par catégorie — elle et toutes ses sous-catégories, via
`ids_sous_arbre()` — filtre stock bas (`?stock_bas=1`) et tri (`?tri=`),
tous combinables. `tri=categorie` trie par chemin de catégorie puis nom,
ce qui rend les matériels d'une même catégorie consécutifs : le
regroupement visuel (en-têtes `.groupe-categorie-entete`) vient de ce tri,
pas d'une logique de regroupement séparée à maintenir.
L'écran de scan (`/scan`) gère 3 cas après un scan : matériel trouvé
(étiquette + contrôles de stock), code inconnu (formulaire de création
identique à celui de `/materiels`, avec le code-barre pré-rempli, pour
créer sans changer de page), ou matériel venant d'être créé (même rendu
que "trouvé"). Deux façons distinctes d'agir sur le stock, volontairement
séparées pour ne jamais laisser d'ambiguïté sur ce qu'un nombre représente :
`/scan/{id}/ajuster` applique un delta choisi (pas fixé à ±1) via un champ
« sens » (ajouter/retirer), `/scan/{id}/definir` fixe la quantité absolue
(utile après un inventaire physique).
## Emails de stock
Seuil configurable par matériel (`Materiel.seuil_alerte`), liste globale de
destinataires (`DestinataireAlerte`, gérée sur `/destinataires`). Si
`SMTP_HOST` n'est pas défini, rien n'est envoyé : l'email est juste affiché
dans la console du serveur — pratique pour tester sans serveur mail réel.
Deux emails distincts, tous les deux dans `app/email_alerts.py` :
- **Alerte immédiate** (`verifier_et_alerter`) : déclenchée après toute
modification de quantité qui fait passer un matériel sous son seuil.
Contient ce matériel, puis un résumé de *tous* les articles actuellement
en stock bas (`materiels_stock_bas()`), pas seulement celui qui a
déclenché l'alerte.
- **Résumé périodique** (`envoyer_resume_stock`) : stock bas en premier,
puis le reste du stock groupé par catégorie. Planifié par
`app/scheduler.py` (APScheduler, tourne dans le process de l'app, pas de
cron système à configurer), avec l'expression cron stockée en base
(`ParametrePlanification`, une seule ligne) et modifiable depuis
l'interface sur `/destinataires` — pas de fichier à éditer ni de
redémarrage nécessaire. Défaut : `0 9 * * mon` (tous les lundis à 9h).
Limite : ne se déclenche que si le serveur tourne au moment prévu, pas
de rattrapage. `scripts/envoyer_resume_stock.py` permet de le déclencher
manuellement (utile pour tester le contenu, ou pour brancher l'envoi sur
un cron système / Planificateur de tâches Windows à la place si cette
limite devient un problème).
**Piège APScheduler** : le champ "jour de la semaine" d'une expression
cron numérique NE SUIT PAS la convention Unix (0=dimanche). Chez
APScheduler, 0=lundi (convention `datetime.weekday()`), donc `0 9 * * 1`
tombe un **mardi**, pas un lundi. Pour éviter toute confusion, le code et
les exemples proposés à l'utilisateur (`EXEMPLES_CRON`) n'utilisent que
des noms de jours (`mon`, `tue`, ...), jamais de chiffres. Vérifié
empiriquement avec la version d'APScheduler installée (voir le
commentaire en tête de `app/scheduler.py`).