L'alerte immédiate de stock bas inclut maintenant un résumé de tous les articles actuellement sous leur seuil, pas seulement celui qui l'a déclenchée. Ajout d'un second email, planifié via une expression cron (RESUME_STOCK_CRON, APScheduler intégré à l'app — pas de cron système à configurer) : stock bas en tête, puis le reste du stock groupé par catégorie. scripts/envoyer_resume_stock.py permet de le déclencher manuellement, ou de brancher l'envoi sur un cron système à la place.
51 lines
2 KiB
Python
51 lines
2 KiB
Python
"""
|
|
Planification de l'email récapitulatif de stock (voir
|
|
email_alerts.envoyer_resume_stock).
|
|
|
|
Choix : un planificateur qui tourne dans le process FastAPI lui-même
|
|
(APScheduler), configuré par une expression cron classique (RESUME_STOCK_CRON
|
|
dans .env, ex: "0 8 * * *" = tous les jours à 8h). Rien à configurer côté
|
|
système d'exploitation du serveur — tout reste dans ce dossier, comme le
|
|
reste du projet.
|
|
|
|
Limite à connaître : la tâche ne se déclenche que si le process est en
|
|
cours d'exécution au moment prévu, sans rattrapage si le serveur était
|
|
arrêté à ce moment-là. Pour un outil interne redémarré rarement, c'est un
|
|
compromis raisonnable face à la complexité d'un vrai cron système. Si ça
|
|
devient un problème, scripts/envoyer_resume_stock.py peut être branché
|
|
sur un cron/Planificateur de tâches Windows à la place.
|
|
"""
|
|
|
|
from apscheduler.schedulers.background import BackgroundScheduler
|
|
from apscheduler.triggers.cron import CronTrigger
|
|
from sqlmodel import Session
|
|
|
|
from app.config import settings
|
|
from app.database import engine
|
|
from app.email_alerts import envoyer_resume_stock
|
|
|
|
_scheduler = BackgroundScheduler()
|
|
|
|
|
|
def _tache_resume_stock() -> None:
|
|
"""Ouvre sa propre session DB : cette fonction est appelée par le
|
|
thread du planificateur, en dehors du cycle requête/réponse FastAPI où
|
|
la session habituelle (get_session) n'existe pas."""
|
|
with Session(engine) as session:
|
|
envoyer_resume_stock(session)
|
|
|
|
|
|
def demarrer_planification() -> None:
|
|
"""Démarre la tâche planifiée. Appelé une fois au démarrage de l'app
|
|
(voir lifespan dans main.py)."""
|
|
trigger = CronTrigger.from_crontab(settings.resume_stock_cron)
|
|
_scheduler.add_job(_tache_resume_stock, trigger, id="resume_stock", replace_existing=True)
|
|
if not _scheduler.running:
|
|
_scheduler.start()
|
|
|
|
|
|
def arreter_planification() -> None:
|
|
"""Arrête proprement le planificateur (appelé à l'extinction de l'app)."""
|
|
if _scheduler.running:
|
|
_scheduler.shutdown(wait=False)
|