Appearance
BDD-165 — Planifier l'exécution automatique des workflows
1. User Story
En tant qu'utilisateur, je veux pouvoir planifier l'exécution automatique d'un workflow (quotidienne, hebdomadaire ou mensuelle, à une heure de mon choix), afin de ne plus avoir à cliquer manuellement sur « Exécuter » pour les traitements récurrents.
2. Critères d'acceptance
| # | Critère | Statut |
|---|---|---|
| 1 | Depuis l'éditeur d'un workflow, l'utilisateur peut créer une règle de planification : fréquence (quotidienne / hebdomadaire / mensuelle) + heure libre (HH:MM) | ✅ |
| 2 | Pour une fréquence hebdomadaire, l'utilisateur sélectionne un ou plusieurs jours de la semaine | ✅ |
| 3 | Pour une fréquence mensuelle, l'utilisateur sélectionne un jour du mois (1-31) | ✅ |
| 4 | Si le jour du mois sélectionné n'existe pas dans un mois donné (ex. le 31 en février), le workflow s'exécute automatiquement le dernier jour de ce mois | ✅ |
| 5 | Un workflow peut avoir plusieurs règles de planification actives simultanément | ✅ |
| 6 | L'utilisateur peut activer/désactiver, modifier ou supprimer une règle existante | ✅ |
| 7 | L'exécution planifiée réutilise exactement le même moteur d'exécution que le bouton « Exécuter » manuel | ✅ |
| 8 | Chaque exécution planifiée apparaît dans l'historique des runs avec une distinction claire (manuelle / automatique) | ✅ |
3. Décision d'architecture : mono-processus (@nestjs/schedule)
Deux options ont été comparées avant tout codage (schémas soumis au PO) :
- Option 1 — mono-processus : le tick de planification tourne dans le même processus Node que l'API, via
@nestjs/schedule(@Cron). Aucune infrastructure supplémentaire (pas de Redis/BullMQ, pas de worker dédié). - Option 2 — Back + Worker séparés : un second processus dédié à l'exécution des jobs planifiés, avec une file (Redis/BullMQ) pour distribuer le travail entre plusieurs instances.
Décision retenue : Option 1, validée par le PO. Limitation actée : le tick de planification ne peut pas tourner sur plusieurs instances du processus API sans déclencher un même workflow plusieurs fois (pas de scale horizontal du tick). Cette limitation est documentée dans le ticket Jira comme un compromis accepté pour ce périmètre — une migration vers l'Option 2 reste possible ultérieurement sans changer le modèle de données (workflow_schedules et triggered_by/schedule_id sur workflow_runs ne dépendent pas du mécanisme de déclenchement). Voir ADR-07.
4. Décision de conception : next_run_at calculé, pas une expression cron
Le stockage sous forme d'expression cron brute a été envisagé puis écarté : les sémantiques cron standard ignorent un jour du mois qui n'existe pas (ex. 31 ne se déclenche jamais en février) au lieu de le ramener au dernier jour du mois — ce qui contredit directement le critère d'acceptance 4.
Choix retenu : chaque règle stocke ses paramètres structurés (frequency, time, days_of_week, day_of_month, timezone) et une colonne next_run_at calculée explicitement via Luxon, avec clampage du jour du mois au dernier jour disponible (workflow-schedule-next-run.util.ts).
5. Périmètre fonctionnel
Base de données
| Fichier | Rôle |
|---|---|
sql/tool_25_workflow_schedules.sql | Table workflow_schedules, enums WorkflowScheduleFrequency / WorkflowRunTriggeredBy, colonnes workflow_runs.triggered_by / workflow_runs.schedule_id |
back/prisma/schema.prisma | Modèles workflow_schedules, extension de workflow_runs et workflows |
Backend
| Fichier | Rôle |
|---|---|
back/src/api/workflows/scheduling/workflow-schedule-next-run.util.ts | Calcul pur de next_run_at (daily/weekly/monthly), clampage du jour du mois |
back/src/api/workflows/scheduling/dto/workflow-schedule.dto.ts | DTO create/update/response des règles de planification |
back/src/api/workflows/scheduling/workflow-schedule.service.ts | CRUD des règles, scoping entreprise, recalcul de next_run_at à la création/modification |
back/src/api/workflows/scheduling/workflow-schedule.controller.ts | POST/GET /workflows/:workflowUuid/schedules, PATCH/DELETE /workflows/:workflowUuid/schedules/:scheduleUuid |
back/src/api/workflows/scheduling/workflow-schedule-cron.service.ts | Tick @Cron (chaque minute) : réclame les règles à échéance (updateMany atomique), résout l'utilisateur créateur du workflow, déclenche run() avec triggeredBy=scheduled |
back/src/app.module.ts | Import conditionnel de ScheduleModule.forRoot() (désactivé si NODE_ENV=test) |
back/src/api/workflows/staging/workflow-run-staging-execution.service.ts | run()/executeRun() acceptent triggeredBy/scheduleId, transmis à persistRun() |
back/src/api/workflows/workflow-run-history.service.ts | persistRun() enregistre triggered_by/schedule_id ; listByWorkflow()/getRunDetail() exposent triggeredBy |
back/src/exceptions/workflow.exceptions.ts | WorkflowScheduleNotFoundException |
Frontend
| Fichier | Rôle |
|---|---|
front/src/stores/workflow-store.js | Actions fetchWorkflowSchedules, createWorkflowSchedule, updateWorkflowSchedule, deleteWorkflowSchedule |
front/src/composables/useWorkflowScheduling.js | Chargement/CRUD des règles, gestion des erreurs (toasts) |
front/src/pages/admin/workflows/workflow-scheduling.format.js | Libellés fréquence/jours, résumé de récurrence lisible |
front/src/components/workflows/WorkflowSchedulingDialog.vue | Dialogue « Planification » (formulaire + liste des règles), accessible depuis le menu « Autres actions » de l'éditeur |
front/src/pages/admin/workflows/WorkflowEditorPage.vue | Entrée de menu « Planifier l'exécution automatique » |
front/src/pages/admin/workflows/workflow-run-history.format.js | triggeredByLabel/triggeredByBadgeClass (badge Manuelle/Automatique) |
front/src/components/workflows/WorkflowRunHistoryPanels.vue | Affichage du badge manuel/automatique dans la liste des runs |
front/src/css/workflow-run-semantic.scss | Classes .wf-run-badge--manual / .wf-run-badge--scheduled |
6. Comportement du tick de planification
Le tick (WorkflowScheduleCronService.handleTick) s'exécute chaque minute :
- Recherche les règles
enabled=true,deleted_at IS NULL,next_run_at <= now. - Pour chaque règle due, réclame l'exécution via
UPDATE ... WHERE id = ? AND next_run_at <= now(le prochainnext_run_atest déjà calculé et écrit) — évite un double déclenchement si un tick précédent chevauche le suivant. - Résout l'utilisateur créateur du workflow (
workflows.creator) pour construire le contexte d'exécution (UserCache) — un workflow dont le créateur n'a plus de rôle/entreprise valide est ignoré (logwarn). - Déclenche
WorkflowRunStagingExecutionService.run(user, workflowUuid, { triggeredBy: 'scheduled', scheduleId })— le même moteur d'exécution que le bouton « Exécuter » (critère 7).
Un verrou en mémoire (ticking) empêche deux ticks de tourner en parallèle dans le même processus si un tick précédent dépasse la minute.
7. Tests
Backend
| Fichier | Couverture |
|---|---|
back/src/api/workflows/scheduling/workflow-schedule-next-run.util.spec.ts | daily/weekly/monthly, clampage du jour du mois (31 → 28/29/30 → retour à 31), erreurs heure/fuseau invalides |
back/src/api/workflows/scheduling/workflow-schedule.service.spec.ts | CRUD scopé entreprise, recalcul de next_run_at, désactivation |
back/src/api/workflows/scheduling/workflow-schedule-cron.service.spec.ts | Réclamation atomique, chevauchement de tick ignoré, workflow/créateur introuvable, échec de run n'interrompt pas le tick |
back/src/api/workflows/staging/workflow-run-staging-execution.service.spec.ts | Propagation de triggeredBy/scheduleId jusqu'à persistRun |
back/src/api/workflows/workflow-run-history.service.spec.ts | triggered_by par défaut manual, schedule_id persisté, exposition dans l'historique |
back/test/e2e/workflow-schedules-controller.e2e-spec.ts | 401/403/404/400, création/liste/modification/suppression, clamp mensuel |
Frontend
| Fichier | Couverture |
|---|---|
front/src/__tests__/stores/workflow-store.spec.js | 4 actions CRUD planification |
front/src/__tests__/composables/useWorkflowScheduling.spec.js | Chargement, création, modification, suppression, gestion des erreurs |
front/src/__tests__/pages/admin/workflows/workflow-scheduling.format.spec.js | Libellés fréquence/jours, résumé de récurrence |
front/src/__tests__/pages/admin/workflows/workflow-run-history.format.spec.js | Badge manuel/automatique |
front/src/__tests__/components/workflows/WorkflowSchedulingDialog.spec.js | Chargement, création, édition, suppression (avec confirmation) |
8. Hors périmètre
- Scale horizontal du tick de planification (plusieurs instances du processus API) — voir ADR-07.
- Historique des tentatives échouées avec retry automatique (une exécution planifiée qui échoue attend simplement la prochaine échéance).
- Notification (email, etc.) en cas d'échec d'une exécution planifiée.
