""" 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)