Architecture Workflows – État technique et propositions
Dernière mise à jour : 2026-08-24
Ce document est la source de vérité de la brique Workflows (post v2) : modèle, orchestration, écarts doc/code, et propositions.
Voir aussi : WORKFLOW_TRIGGERS.md · WORKFLOW_VISUAL_EDITOR.md · QUEUE_SYSTEM.md · SESSION_INFO_RENSEIGNEMENTS.md
0. Vue d’ensemble
Un workflow est un playbook d’automatisation CRM : phases → tâches → actions, rattaché à une session. Exécution NestJS + BullMQ (WORKFLOW_TASKS). Ce n’est pas n8n ; Iris peut seulement passer un workflow_id à la création/màj de session.
flowchart LR
subgraph Design
T[Templates système]
U[Workflows user]
P[Phases / Tâches / Actions]
TR[Triggers]
T -->|fork| U
U --> P
U --> TR
end
subgraph Bind
ST[Session types<br/>default_workflow_id]
S[sessions.workflow_id]
ST --> S
U --> S
end
subgraph Run
I[session_workflow_instances]
R[session_workflow_task_runs]
Q[BullMQ workflow-tasks]
E[ExecutionService]
S --> I --> R --> Q --> E
end
TR -->|événements SESSION| R
| Couche | Rôle |
|---|---|
| Design | Templates admin → fork user → édition phases / tâches / actions / triggers |
| Bind | Type de séance ou apply-workflow → sessions.workflow_id |
| Run | 1 instance / session → task runs planifiés → cron 5 min → queue → exécution |
1. Modèle de données (post-migration 0173)
1.1 Relations (ER)
erDiagram
workflows ||--o{ workflow_phases : contains
workflow_phases ||--o{ workflow_tasks : contains
workflow_tasks ||--o{ workflow_task_actions : has
workflows ||--o{ workflow_triggers : triggers
workflow_triggers ||--o{ workflow_trigger_executions : logs
workflows ||--o{ workflows : "forked_from_id"
workflows ||--o| sessions : "sessions.workflow_id"
sessions ||--o| session_workflow_instances : "1:1"
session_workflow_instances ||--o{ session_workflow_task_runs : runs
session_workflow_task_runs ||--o{ session_workflow_action_runs : "schema only"
user_session_types }o--o| workflows : default_workflow_id
lead_forms }o--o| workflows : workflow_id
1.2 Tables principales
| Table | Rôle |
|---|---|
| workflows | Définitions unifiées : templates (is_template=true, owner_id=null) + copies user (owner_id, forked_from_id). is_published, display_order. |
| workflow_phases | Phases ordonnées. phase_type : CREATION, CONFIRMATION, DATE, CUSTOM. |
| workflow_tasks | Tâches : title, relative_days + relative_unit (HOURS/DAYS/WEEKS/MONTHS), is_manual. Pas de colonne task_type / action — dérivés des actions. |
| workflow_task_actions | 0–N actions / tâche. Types : SEND_EMAIL, SEND_SMS, CLIENT_PORTAL_INVITE, SEND_NOTIFICATION, CREATE_REMINDER, WEBHOOK, ASSIGN_FORM, CHANGE_SESSION_STATUS, SEND_SESSION_INFO_INVITE, SEND_SURVEY_INVITE. Config JSONB (email_template_id, sms_template_id, action ex. SESSION_NOTIFICATION:appointment-reminder-1w, survey_template_id, …). |
| workflow_triggers | trigger_event, conditions JSONB, schedule_config, is_active, execution_order. |
| workflow_trigger_executions | Audit trigger : PENDING / EXECUTED / FAILED / SKIPPED. Unique (trigger_id, entity_type, entity_id). |
| session_workflow_instances | 1 instance / session (UNIQUE(session_id)). Statut : ACTIVE / PAUSED / COMPLETED / CANCELLED. |
| session_workflow_task_runs | Runs : PENDING / SCHEDULED / COMPLETED / SKIPPED / FAILED + scheduled_at / executed_at / metadata. |
| session_workflow_action_runs | Prévu pour l’audit par action — schéma présent, non écrit par le runtime actuel. |
1.3 Tables legacy supprimées
Supprimées en 0173 : user_workflows, user_workflow_phases, user_workflow_tasks, default_workflows, default_workflow_phases, default_workflow_tasks, session_workflow_tasks.
Ne plus les référencer dans le runtime. Reliquats éventuels en base = dettes de schéma, pas de dépendances applicatives.
1.4 Conditions des triggers
Structure attendue par le code (pas d’imbrication de groupes) :
{
"operator": "AND",
"conditions": [
{ "field": "status", "operator": "equals", "value": "VALIDATED" }
]
}
Détail opérateurs / champs : WORKFLOW_TRIGGERS.md. Pour FORM_SUBMITTED / SURVEY_SUBMITTED, les réponses sont exposées dans answers.<fieldId> et answers.<label_slug>.
2. Orchestration
2.1 Flux bout-en-bout
flowchart TD
A[Événement domaine] --> B[evaluateTriggersForEvent]
B --> C{Triggers actifs<br/>owner + event}
C -->|aucun| Z[Stop]
C -->|chaque trigger| D{Déjà EXECUTED?}
D -->|oui| Z2[Skip]
D -->|non| E[ConditionEvaluator]
E -->|fail| F[Record SKIPPED]
E -->|pass| G[resolveSessionId]
G -->|CONTACT / FORM_SUBMISSION<br/>sans session| H[Record SKIPPED]
G -->|session sans workflow_id| H
G -->|session + workflow_id| I[createWorkflowTasksForSession + reminders]
I --> L[Record trigger EXECUTED]
M[Cron toutes les 5 min] --> N[getTasksReadyForExecution]
N --> O[BullMQ WORKFLOW_TASKS]
O --> P[WorkflowTasksProcessor]
P --> Q[WorkflowTasksExecutionService.executeTask]
Q --> R{Type dérivé des actions}
R -->|TASK| S[mark COMPLETED]
R -->|AUTOMATIC_EMAIL| T[email / portal / session-info / survey / notification]
R -->|AUTOMATIC_SMS| U[SmsService]
T --> V[COMPLETED ou FAILED]
U --> V
2.2 Rattachement session (sans trigger)
sequenceDiagram
participant User
participant UI as Session form / Tasks
participant API as Sessions API
participant Create as WorkflowTasksCreationService
participant DB
User->>UI: Choisit type de séance
UI->>UI: Préremplit workflow_id<br/>depuis default_workflow_id
User->>API: Create / update session
API->>Create: createWorkflowTasksForSession<br/>(onlyCreationPhase si create)
Create->>DB: Upsert instance + task runs CREATION
Note over API,DB: Statut → VALIDATED
API->>Create: recreate CONFIRMATION + DATE
Create->>DB: Delete/recreate runs phases concernées
Chemins d’attache :
- Type de séance →
user_session_types.default_workflow_id→sessions.workflow_id - Choix manuel / onglet Tasks /
PUT /sessions/:id/apply-workflow - Bootstrap user : fork templates + relink session types (
createDefaultWorkflowsForUser) - Lead form →
lead_forms.workflow_idpassé àsessions.createsi la soumission crée une session (champ DATE)
2.3 Déclenchement (triggers)
| Event | Caller | Entity | Exécution réelle ? |
|---|---|---|---|
SESSION_CREATED | SessionIntegrationService | SESSION | Oui* |
SESSION_STATUS_CHANGED | idem (y compris remap depuis SESSION_UPDATED) | SESSION | Oui* |
SESSION_INFO_SUBMITTED | SessionClientInfoService | SESSION | Oui* |
SURVEY_SUBMITTED | SurveySubmittedWorkflowTriggerHandler (@OnEvent) | SESSION | Oui* (entityData.answers) |
QUOTE_STATUS_CHANGED | Quotes CRUD | QUOTE | Oui* (via session_id) |
INVOICE_STATUS_CHANGED / PAYMENT_RECEIVED | Invoices CRUD | INVOICE | Oui* (via session_id) |
FORM_SUBMITTED | FormSubmittedWorkflowTriggerHandler (@OnEvent lead-form.submitted) | SESSION si session créée, sinon FORM_SUBMISSION | Oui* si session + workflow_id ; sinon SKIPPED |
CONTACT_CREATED | ContactsService | CONTACT | SKIPPED |
SCHEDULED | — | — | Non évalué |
*Oui = si conditions OK → résoudre la session liée → createWorkflowTasksForSession + createNotificationTasksForSession (nécessite sessions.workflow_id). Les triggers ne positionnent pas session.workflow_id = trigger.workflow_id. Exception rattachement : lead_forms.workflow_id est passé à sessions.create (hors moteur trigger).
flowchart LR
subgraph Actifs
SC[SESSION_CREATED]
SS[SESSION_STATUS_CHANGED]
SI[SESSION_INFO_SUBMITTED]
SU[SURVEY_SUBMITTED]
FS[FORM_SUBMITTED]
QS[QUOTE_STATUS_CHANGED]
IS[INVOICE_STATUS_CHANGED]
PR[PAYMENT_RECEIVED]
end
subgraph Audit_only["Évalués puis SKIPPED"]
CC[CONTACT_CREATED]
FS0[FORM_SUBMITTED sans session]
end
subgraph Absent
SCH[SCHEDULED]
end
Actifs -->|SESSION id ou session_id lié| Run[createWorkflowTasksForSession + reminders]
Audit_only --> Log[trigger_executions SKIPPED]
Absent --> X[Pas d'appel]
2.4 Création des task runs
- Service :
WorkflowTasksCreationService.createWorkflowTasksForSession(sessionId) - Upsert
session_workflow_instances, puis insert dessession_workflow_task_runsdepuis lesworkflow_tasks - Filtre phases :
- toujours : CREATION (+ CUSTOM)
- CONFIRMATION / DATE : seulement si session
VALIDATED(saufonlyCreationPhase)
createNotificationTasksForSessionrecrée des session_workflow_task_runs dédiés (task_idnull,metadata.kind = session_reminder,action = SESSION_NOTIFICATION:<id>) depuisuser_metadata.session_reminder_settingsscheduled_at = session.start_date − hours_before; runs passés ignorés- Nécessite
sessions.workflow_id(instance FK) ; sans workflow, skip - Les runs rappel ne sont pas effacés par le recreate des phases workflow (
phase_idnull)
Date de référence pour scheduled_at :
| Phase | Référence |
|---|---|
CREATION | session.created_at |
DATE | session.start_date (sinon now) |
CONFIRMATION / CUSTOM | now (à la création des runs) |
Puis : scheduled_at = référence ± relative_days / relative_unit.
2.5 Exécution des tâches (scheduler + queue)
sequenceDiagram
participant Cron as WorkflowTasksScheduler
participant Q as BullMQ WORKFLOW_TASKS
participant Proc as WorkflowTasksProcessor
participant Exec as WorkflowTasksExecutionService
participant Ext as Email / SMS / Invites
loop Toutes les 5 min
Cron->>Cron: getTasksReadyForExecution<br/>PENDING/SCHEDULED + scheduled_at ≤ now
Cron->>Q: addWorkflowTaskJob
end
Q->>Proc: consume job
Proc->>Exec: executeTask
Exec->>Exec: derive task_type from first action
alt TASK (is_manual)
Exec->>Exec: mark COMPLETED
else AUTOMATIC_EMAIL
Exec->>Ext: email / portal / session-info / survey / SESSION_NOTIFICATION
else AUTOMATIC_SMS
Exec->>Ext: SmsService
end
Dérivation runtime (WorkflowTasksQueryService.toExecutionTask) :
is_manual→TASK- première action
SEND_SMS→AUTOMATIC_SMS - actions email-like →
AUTOMATIC_EMAIL - actions spéciales :
CLIENT_PORTAL_INVITE,SEND_SESSION_INFO_INVITE,SEND_SURVEY_INVITE, ouconfig.action(SESSION_NOTIFICATION:…)
Note : les tâches « manuelles » sont quand même pickées par le cron à échéance et marquées COMPLETED (comportement checklist due-date, pas « attendre l’utilisateur »).
2.6 Cycle de vie (statuts)
stateDiagram-v2
[*] --> ACTIVE: instance créée / upsert
ACTIVE --> PAUSED
ACTIVE --> COMPLETED
ACTIVE --> CANCELLED: workflow retiré / runs deleted
state task_run {
[*] --> PENDING: scheduled_at calculé
PENDING --> SCHEDULED
PENDING --> COMPLETED: cron + queue
SCHEDULED --> COMPLETED
PENDING --> FAILED
SCHEDULED --> FAILED
PENDING --> SKIPPED
}
3. Surfaces API et frontend
flowchart TB
subgraph Backend
WC[WorkflowsController]
WA[WorkflowsAdminController]
WT[WorkflowTriggersController]
SA[PUT /sessions/:id/apply-workflow]
Mod[workflows/ + workflow-tasks/]
WC --> Mod
WA --> Mod
WT --> Mod
SA --> Mod
end
subgraph Frontend
WF[WorkflowFormPage — éditeur 2 colonnes]
DW[DefaultWorkflowsTab / Admin templates]
ST[Session TasksTab]
WF --> WC
DW --> WA
ST --> SA
end
- Workflows : CRUD user ; admin/defaults (templates, bootstrap user)
- Triggers : CRUD sous
/workflows/:workflowId/triggers - Session :
apply-workflowrattache + recrée les task runs - Frontend : éditeur Flow + panneau édition ; pas de dry-run / simulate ; pas de DnD de réordonnancement (décision produit)
4. Écarts documentation / code
| Doc / contrat | Écart |
|---|---|
| WORKFLOW_TRIGGERS.md | QUOTE/INVOICE exécutés via session_id ; FORM_SUBMITTED via session créée ; CONTACT SKIPPED ; SCHEDULED non évalué. |
| WORKFLOW_VISUAL_EDITOR.md | Aligné (2 colonnes, pas de DnD, validation champs obligatoires, sous-tâches 1 niveau). |
| WORKFLOWS_REVIEW_PLAN.md | Suivre le modèle 0173 (pas de session_workflow_tasks). |
| SESSION_NOTIFICATIONS (pré-v2) | Historique git uniquement — ne plus implémenter dessus. |
OpenAPI RelativeUnit | API : HOURS/DAYS/MONTHS/YEARS. DB : HOURS/DAYS/WEEKS/MONTHS. À aligner. |
lead_forms.workflow_id | Passé à sessions.create si un champ DATE crée une session ; FORM_SUBMITTED évalue ensuite les réponses. Sans session → SKIPPED. |
session_workflow_action_runs | Table morte côté runtime. |
5. Gotchas (comportement réel)
- Triggers ≠ assignation — ils régénèrent les runs du
workflow_idcourant de la session (SESSION directe, ou session liée pour QUOTE/INVOICE) ; sansworkflow_id/ sanssession_id, SKIPPED. - CONTACT_CREATED — évalué puis SKIPPED (pas de cible session unique).
FORM_SUBMITTEDsans session (pas de champ DATE / création échouée) idem. - 1 instance / session — changer de workflow met à jour la même ligne d’instance ; pas de multi-workflow parallèle.
- Re-apply / validate — delete + recreate des runs des phases concernées (historique reset) ; rappels session (
metadata.kind) sont régénérés à part. SESSION_UPDATED→ remapSESSION_STATUS_CHANGED— dédup via unique(trigger_id, entity_type, entity_id).- Couverture actions inégale — exécution centrée email / SMS / invites ;
WEBHOOK,ASSIGN_FORM,CHANGE_SESSION_STATUS,CREATE_REMINDERpeu (ou pas) branchés. - Conditions JSON : préférer
conditions(UI/DTO/seeds) ; d’anciennes tracesrulespossibles en migration.
6. Propositions d’amélioration
6.1 Documentation / contrats
- Garder ce fichier comme source active (modèle v2 + orchestration visuelle).
- Aligner WORKFLOW_TRIGGERS.md sur les events réellement exécutés (SESSION + INFO + SURVEY + FORM + QUOTE/INVOICE).
- Aligner OpenAPI
RelativeUnitavec la DB (WEEKSvsYEARS).
6.2 Fonctionnel (priorité produit : relances)
- Rappels session — ✅ Option A livrée (task runs metadata
SESSION_NOTIFICATION:…viacreateNotificationTasksForSession). Limite restante : session sansworkflow_id→ skip. Options B/C non retenues pour l’instant. - Triggers devis / factures / paiement — ✅ exécutés via
session_idlié (même moteur session).CONTACT_CREATEDreste SKIPPED. - SCHEDULED — cron dédié sur
schedule_config. - Lead forms — ✅
FORM_SUBMITTED+lead_forms.workflow_idà la soumission (session DATE). Sans session → SKIPPED.
6.3 Observabilité et robustesse
- Vue / endpoint exécutions récentes (trigger_executions + task_runs) par workflow.
- Logs / métriques standard :
workflowId,triggerId,taskId,sessionId. - Garde-fous validation phases/tâches (API + UI) ; alignement DTO ↔ DB ↔ frontend.
7. Fichiers clés
| Rôle | Emplacement |
|---|---|
| Module workflows | backend/src/workflows/ |
| CRUD / templates / publish | workflow-crud.service.ts, workflow-phases.service.ts, workflow-template.service.ts, workflow-publish-validation.service.ts |
| Controllers | workflows.controller.ts, workflows-admin.controller.ts, workflow-triggers.controller.ts |
| Triggers | workflow-triggers.service.ts, helpers/condition-evaluator.helper.ts, survey-submitted-workflow-trigger.handler.ts, form-submitted-workflow-trigger.handler.ts |
| Runtime tasks | backend/src/workflow-tasks/ — creation, execution, query, helpers, scheduler, facade |
| Queue | backend/src/queue/processors/workflow-tasks.processor.ts |
| Session glue | session-integration.service.ts, apply-workflow sur sessions |
| OpenAPI | openapi/components/workflows.yaml, openapi/paths/workflows*.yaml |
| Frontend | WorkflowsPage / WorkflowFormPage, TasksTab, admin default workflows, useWorkflows / useWorkflowTriggers |
8. Références
- Migrations :
0173–0176(v2),0194/0199/0201/0202/0203(session-info, survey, templates) - Docs : WORKFLOW_TRIGGERS.md, WORKFLOW_VISUAL_EDITOR.md, WORKFLOWS_REVIEW_PLAN.md, QUEUE_SYSTEM.md
- Produit : PRIORITES.md — priorité haute « Relances automatiques »