create-crm-tmp 1.1.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/template/.prettierignore +2 -0
- package/template/README.md +53 -67
- package/template/components.json +22 -0
- package/template/exemple-contacts.csv +54 -0
- package/template/next.config.ts +27 -1
- package/template/package.json +64 -27
- package/template/prisma/schema.prisma +821 -72
- package/template/skills-lock.json +25 -0
- package/template/src/app/(auth)/invite/[token]/page.tsx +21 -24
- package/template/src/app/(auth)/reset-password/complete/page.tsx +12 -21
- package/template/src/app/(auth)/reset-password/page.tsx +12 -8
- package/template/src/app/(auth)/reset-password/verify/page.tsx +12 -8
- package/template/src/app/(auth)/signin/page.tsx +20 -17
- package/template/src/app/(dashboard)/agenda/page.tsx +2231 -2188
- package/template/src/app/(dashboard)/automatisation/[id]/page.tsx +10 -7
- package/template/src/app/(dashboard)/automatisation/_components/workflow-editor.tsx +680 -323
- package/template/src/app/(dashboard)/automatisation/new/page.tsx +11 -8
- package/template/src/app/(dashboard)/automatisation/page.tsx +473 -180
- package/template/src/app/(dashboard)/closing/page.tsx +500 -468
- package/template/src/app/(dashboard)/contacts/[id]/page.tsx +5035 -4126
- package/template/src/app/(dashboard)/contacts/companies/[id]/page.tsx +1703 -0
- package/template/src/app/(dashboard)/contacts/loading.tsx +13 -0
- package/template/src/app/(dashboard)/contacts/page.tsx +3776 -2064
- package/template/src/app/(dashboard)/dashboard/page.tsx +37 -519
- package/template/src/app/(dashboard)/error.tsx +37 -0
- package/template/src/app/(dashboard)/layout.tsx +1 -1
- package/template/src/app/(dashboard)/loading.tsx +5 -0
- package/template/src/app/(dashboard)/settings/loading.tsx +19 -0
- package/template/src/app/(dashboard)/settings/page.tsx +2685 -2489
- package/template/src/app/(dashboard)/templates/page.tsx +500 -300
- package/template/src/app/(dashboard)/users/list/page.tsx +356 -350
- package/template/src/app/(dashboard)/users/page.tsx +279 -310
- package/template/src/app/(dashboard)/users/permissions/page.tsx +104 -99
- package/template/src/app/(dashboard)/users/roles/page.tsx +164 -137
- package/template/src/app/api/audit-logs/route.ts +1 -1
- package/template/src/app/api/auth/google/callback/route.ts +8 -5
- package/template/src/app/api/auth/google/disconnect/route.ts +2 -2
- package/template/src/app/api/companies/[id]/activities/route.ts +131 -0
- package/template/src/app/api/companies/[id]/route.ts +195 -0
- package/template/src/app/api/companies/export/route.ts +206 -0
- package/template/src/app/api/companies/route.ts +166 -0
- package/template/src/app/api/contact-views/[id]/pin/route.ts +69 -0
- package/template/src/app/api/contact-views/[id]/route.ts +197 -0
- package/template/src/app/api/contact-views/route.ts +146 -0
- package/template/src/app/api/contacts/[id]/files/[fileId]/preview/route.ts +77 -0
- package/template/src/app/api/contacts/[id]/files/[fileId]/route.ts +7 -17
- package/template/src/app/api/contacts/[id]/files/route.ts +83 -44
- package/template/src/app/api/contacts/[id]/interactions/route.ts +37 -0
- package/template/src/app/api/contacts/[id]/kyc/route.ts +71 -0
- package/template/src/app/api/contacts/[id]/meet/route.ts +38 -29
- package/template/src/app/api/contacts/[id]/route.ts +111 -20
- package/template/src/app/api/contacts/[id]/send-email/route.ts +6 -0
- package/template/src/app/api/contacts/[id]/workflows/run/route.ts +61 -0
- package/template/src/app/api/contacts/export/route.ts +12 -17
- package/template/src/app/api/contacts/import/route.ts +22 -19
- package/template/src/app/api/contacts/import-preview/route.ts +139 -0
- package/template/src/app/api/contacts/route.ts +202 -49
- package/template/src/app/api/dashboard/stats/route.ts +9 -292
- package/template/src/app/api/integrations/google-sheet/sync/route.ts +203 -185
- package/template/src/app/api/invite/complete/route.ts +20 -23
- package/template/src/app/api/reminders/route.ts +1 -0
- package/template/src/app/api/reset-password/complete/route.ts +11 -13
- package/template/src/app/api/send/route.ts +9 -85
- package/template/src/app/api/settings/closing-reasons/[id]/route.ts +10 -21
- package/template/src/app/api/settings/closing-reasons/route.ts +10 -21
- package/template/src/app/api/settings/company/route.ts +19 -26
- package/template/src/app/api/settings/google-ads/[id]/route.ts +20 -23
- package/template/src/app/api/settings/google-ads/route.ts +20 -23
- package/template/src/app/api/settings/google-sheet/[id]/route.ts +20 -23
- package/template/src/app/api/settings/google-sheet/auto-map/route.ts +23 -32
- package/template/src/app/api/settings/google-sheet/preview/route.ts +104 -0
- package/template/src/app/api/settings/google-sheet/route.ts +20 -23
- package/template/src/app/api/settings/meta-leads/[id]/route.ts +20 -23
- package/template/src/app/api/settings/meta-leads/route.ts +20 -23
- package/template/src/app/api/settings/statuses/[id]/route.ts +33 -23
- package/template/src/app/api/settings/statuses/route.ts +24 -22
- package/template/src/app/api/statuses/route.ts +2 -5
- package/template/src/app/api/tasks/[id]/attendees/route.ts +14 -7
- package/template/src/app/api/tasks/[id]/route.ts +161 -137
- package/template/src/app/api/tasks/meet/route.ts +11 -8
- package/template/src/app/api/tasks/route.ts +155 -95
- package/template/src/app/api/templates/[id]/route.ts +22 -13
- package/template/src/app/api/templates/route.ts +22 -5
- package/template/src/app/api/users/[id]/resend-invite/route.ts +95 -0
- package/template/src/app/api/users/[id]/route.ts +16 -1
- package/template/src/app/api/users/commercials/route.ts +38 -0
- package/template/src/app/api/users/for-agenda/route.ts +1 -2
- package/template/src/app/api/users/route.ts +94 -55
- package/template/src/app/api/webhooks/google-ads/route.ts +20 -1
- package/template/src/app/api/webhooks/meta-leads/route.ts +18 -1
- package/template/src/app/api/workflows/[id]/route.ts +33 -6
- package/template/src/app/api/workflows/process/route.ts +509 -146
- package/template/src/app/api/workflows/route.ts +46 -4
- package/template/src/app/globals.css +210 -101
- package/template/src/app/layout.tsx +19 -8
- package/template/src/app/page.tsx +37 -7
- package/template/src/components/address-autocomplete.tsx +232 -0
- package/template/src/components/contacts/filter-bar.tsx +181 -0
- package/template/src/components/contacts/filter-builder.tsx +589 -0
- package/template/src/components/contacts/save-view-dialog.tsx +160 -0
- package/template/src/components/contacts/views-tab-bar.tsx +440 -0
- package/template/src/components/dashboard/activity-chart.tsx +31 -39
- package/template/src/components/dashboard/dashboard-content.tsx +79 -0
- package/template/src/components/dashboard/stat-card.tsx +40 -42
- package/template/src/components/dashboard/tasks-pie-chart.tsx +34 -37
- package/template/src/components/dashboard/upcoming-tasks-list.tsx +78 -72
- package/template/src/components/date-picker.tsx +396 -0
- package/template/src/components/editor.tsx +27 -13
- package/template/src/components/email-template.tsx +4 -2
- package/template/src/components/global-search.tsx +358 -0
- package/template/src/components/header.tsx +57 -62
- package/template/src/components/invitation-email-template.tsx +4 -2
- package/template/src/components/lazy-editor.tsx +11 -0
- package/template/src/components/meet-cancellation-email-template.tsx +11 -3
- package/template/src/components/meet-confirmation-email-template.tsx +10 -3
- package/template/src/components/meet-update-email-template.tsx +10 -3
- package/template/src/components/page-header.tsx +19 -15
- package/template/src/components/protected-page.tsx +94 -0
- package/template/src/components/reset-password-email-template.tsx +4 -2
- package/template/src/components/sidebar.tsx +92 -94
- package/template/src/components/skeleton.tsx +128 -42
- package/template/src/components/ui/accordion.tsx +64 -0
- package/template/src/components/ui/alert-dialog.tsx +139 -0
- package/template/src/components/ui/button.tsx +60 -0
- package/template/src/components/view-as-banner.tsx +1 -1
- package/template/src/components/view-as-modal.tsx +21 -16
- package/template/src/config/nav-pages.ts +108 -0
- package/template/src/contexts/app-toast-context.tsx +174 -0
- package/template/src/contexts/sidebar-context.tsx +16 -47
- package/template/src/contexts/task-reminder-context.tsx +6 -6
- package/template/src/contexts/view-as-context.tsx +11 -16
- package/template/src/hooks/use-alert.tsx +65 -0
- package/template/src/hooks/use-confirm.tsx +87 -0
- package/template/src/hooks/use-contact-views.ts +140 -0
- package/template/src/hooks/use-contacts.ts +69 -0
- package/template/src/hooks/use-fetch.ts +17 -0
- package/template/src/hooks/use-focus-trap.ts +73 -0
- package/template/src/hooks/use-statuses.ts +22 -0
- package/template/src/lib/address-api.ts +155 -0
- package/template/src/lib/cache.ts +73 -0
- package/template/src/lib/check-permission.ts +12 -177
- package/template/src/lib/contact-interactions.ts +3 -1
- package/template/src/lib/contact-view-filters.ts +341 -0
- package/template/src/lib/dashboard-stats.ts +224 -0
- package/template/src/lib/date-utils.ts +49 -0
- package/template/src/lib/get-auth-user.ts +25 -0
- package/template/src/lib/google-calendar.ts +54 -12
- package/template/src/lib/google-drive.ts +796 -75
- package/template/src/lib/google-fetch.ts +63 -0
- package/template/src/lib/local-storage.ts +34 -0
- package/template/src/lib/permissions.ts +245 -47
- package/template/src/lib/prisma.ts +11 -11
- package/template/src/lib/roles.ts +14 -39
- package/template/src/lib/template-variables.ts +67 -33
- package/template/src/lib/utils.ts +26 -2
- package/template/src/lib/workflow-executor.ts +445 -229
- package/template/src/proxy.ts +34 -73
- package/template/src/types/contact-views.ts +351 -0
- package/template/src/types/yousign.ts +52 -0
- package/template/vercel.json +12 -0
- package/template/WORKFLOWS_CRON.md +0 -185
- package/template/prisma/migrations/20251126144728_init/migration.sql +0 -78
- package/template/prisma/migrations/20251126155204_add_user_roles/migration.sql +0 -5
- package/template/prisma/migrations/20251128095126_add_company_info/migration.sql +0 -19
- package/template/prisma/migrations/20251128123321_add_smtp_config/migration.sql +0 -22
- package/template/prisma/migrations/20251128132303_add_status/migration.sql +0 -23
- package/template/prisma/migrations/20251201102207_add_user_active/migration.sql +0 -75
- package/template/prisma/migrations/20251201105507_add_email_signature/migration.sql +0 -2
- package/template/prisma/migrations/20251201151122_add_tasks/migration.sql +0 -45
- package/template/prisma/migrations/20251202111854_add_task_reminder/migration.sql +0 -2
- package/template/prisma/migrations/20251202135859_add_google_meet_integration/migration.sql +0 -27
- package/template/prisma/migrations/20251203103317_add_meta_lead_integration/migration.sql +0 -20
- package/template/prisma/migrations/20251203104002_add_google_ads_integration/migration.sql +0 -18
- package/template/prisma/migrations/20251203112122_add_google_sheet_integration/migration.sql +0 -32
- package/template/prisma/migrations/20251203153853_allow_multiple_integration_configs/migration.sql +0 -20
- package/template/prisma/migrations/20251205141705_update_user_roles/migration.sql +0 -12
- package/template/prisma/migrations/20251205150000_add_commercial_and_telepro_assignment/migration.sql +0 -21
- package/template/prisma/migrations/20251205160000_add_interaction_logging/migration.sql +0 -11
- package/template/prisma/migrations/20251208090314_add_automatic_interaction_types/migration.sql +0 -12
- package/template/prisma/migrations/20251208094843_mg/migration.sql +0 -14
- package/template/prisma/migrations/20251208100000_add_company_support/migration.sql +0 -14
- package/template/prisma/migrations/20251208110000_add_templates/migration.sql +0 -26
- package/template/prisma/migrations/20251208141304_add_video_conference_task_type/migration.sql +0 -2
- package/template/prisma/migrations/20251209104759_add_internal_note_to_task/migration.sql +0 -2
- package/template/prisma/migrations/20251209134803_add_company_field/migration.sql +0 -2
- package/template/prisma/migrations/20251209150000_rename_company_to_company_name/migration.sql +0 -3
- package/template/prisma/migrations/20251209150016_add_email_tracking/migration.sql +0 -21
- package/template/prisma/migrations/20251209155908_add_notify_contact_to_task/migration.sql +0 -2
- package/template/prisma/migrations/20251210110019_add_appointment_types/migration.sql +0 -10
- package/template/prisma/migrations/20251210113928_add_contact_files/migration.sql +0 -26
- package/template/prisma/migrations/20251212132339_add_custom_roles/migration.sql +0 -24
- package/template/prisma/migrations/20251215104448_add_file_interaction_types/migration.sql +0 -11
- package/template/prisma/migrations/20251215145616_add_closing_reasons/migration.sql +0 -12
- package/template/prisma/migrations/20251216140850_add_log_users/migration.sql +0 -25
- package/template/prisma/migrations/20251216151000_rename_perdu_to_ferme/migration.sql +0 -8
- package/template/prisma/migrations/20251216162318_add_column_mappings_to_google_sheet/migration.sql +0 -2
- package/template/prisma/migrations/20251216185127_add_workflows/migration.sql +0 -80
- package/template/prisma/migrations/20251216192237_add_scheduled_workflow_actions/migration.sql +0 -32
- package/template/prisma/migrations/20251220000000_add_task_interaction_type/migration.sql +0 -4
- package/template/prisma/migrations/20251221000000_add_task_type/migration.sql +0 -3
- package/template/prisma/migrations/20251221000001_add_event_color/migration.sql +0 -23
- package/template/prisma/migrations/20260210114913_add_dashboard_widget/migration.sql +0 -20
- package/template/prisma/migrations/migration_lock.toml +0 -3
- package/template/src/app/(dashboard)/users/layout.tsx +0 -30
- package/template/src/app/api/dashboard/widgets/[id]/route.ts +0 -47
- package/template/src/app/api/dashboard/widgets/route.ts +0 -181
- package/template/src/components/dashboard/add-widget-dialog.tsx +0 -161
- package/template/src/components/dashboard/color-picker.tsx +0 -65
- package/template/src/components/dashboard/contacts-chart.tsx +0 -69
- package/template/src/components/dashboard/interactions-by-type-chart.tsx +0 -121
- package/template/src/components/dashboard/recent-activity.tsx +0 -157
- package/template/src/components/dashboard/sales-analytics-chart.tsx +0 -77
- package/template/src/components/dashboard/status-distribution-chart.tsx +0 -82
- package/template/src/components/dashboard/top-contacts-list.tsx +0 -119
- package/template/src/components/dashboard/widget-wrapper.tsx +0 -39
- package/template/src/contexts/dashboard-theme-context.tsx +0 -58
- package/template/src/lib/dashboard-themes.ts +0 -140
- package/template/src/lib/default-widgets.ts +0 -14
- package/template/src/lib/widget-registry.ts +0 -177
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from 'next/server';
|
|
2
2
|
import { prisma } from '@/lib/prisma';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
changeStatusImmediate,
|
|
5
|
+
createTaskImmediate,
|
|
6
|
+
assignContactImmediate,
|
|
7
|
+
addNoteImmediate,
|
|
8
|
+
notifyUserImmediate,
|
|
9
|
+
} from '@/lib/workflow-executor';
|
|
4
10
|
import { decrypt } from '@/lib/encryption';
|
|
5
11
|
import nodemailer from 'nodemailer';
|
|
6
12
|
import { replaceTemplateVariables } from '@/lib/template-variables';
|
|
@@ -8,12 +14,10 @@ import { replaceTemplateVariables } from '@/lib/template-variables';
|
|
|
8
14
|
/**
|
|
9
15
|
* GET /api/workflows/process
|
|
10
16
|
* Endpoint appelé par Vercel Cron pour traiter les actions planifiées
|
|
17
|
+
* et les workflows TIME_BASED
|
|
11
18
|
*/
|
|
12
19
|
export async function GET(request: NextRequest) {
|
|
13
20
|
try {
|
|
14
|
-
// Vérifier l'authentification Vercel Cron
|
|
15
|
-
// Vercel envoie automatiquement le header Authorization: Bearer <secret>
|
|
16
|
-
// si CRON_SECRET est défini dans les variables d'environnement
|
|
17
21
|
const cronSecret = process.env.CRON_SECRET;
|
|
18
22
|
|
|
19
23
|
if (cronSecret) {
|
|
@@ -21,9 +25,7 @@ export async function GET(request: NextRequest) {
|
|
|
21
25
|
const expectedAuth = `Bearer ${cronSecret}`;
|
|
22
26
|
|
|
23
27
|
if (authHeader !== expectedAuth) {
|
|
24
|
-
// En développement, on peut être plus permissif pour les tests
|
|
25
28
|
const isDevelopment = process.env.NODE_ENV === 'development';
|
|
26
|
-
|
|
27
29
|
if (!isDevelopment) {
|
|
28
30
|
return NextResponse.json(
|
|
29
31
|
{ error: 'Unauthorized - Secret manquant ou invalide' },
|
|
@@ -33,158 +35,532 @@ export async function GET(request: NextRequest) {
|
|
|
33
35
|
}
|
|
34
36
|
}
|
|
35
37
|
|
|
36
|
-
// Récupérer les actions à exécuter maintenant
|
|
37
|
-
// On utilise une petite marge de 5 secondes pour éviter les problèmes de timing
|
|
38
38
|
const now = new Date();
|
|
39
|
-
const margin = new Date(now.getTime() + 5000); // 5 secondes de marge
|
|
40
39
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
40
|
+
// 1) Traiter les actions planifiées (scheduled)
|
|
41
|
+
const scheduledResults = await processScheduledActions(now);
|
|
42
|
+
|
|
43
|
+
// 2) Traiter les workflows TIME_BASED
|
|
44
|
+
const timeBasedResults = await processTimeBasedWorkflows(now);
|
|
45
|
+
|
|
46
|
+
return NextResponse.json({
|
|
47
|
+
success: true,
|
|
48
|
+
scheduled: scheduledResults,
|
|
49
|
+
timeBased: timeBasedResults,
|
|
50
|
+
timestamp: now.toISOString(),
|
|
51
|
+
executionTime: `${Date.now() - now.getTime()}ms`,
|
|
52
|
+
});
|
|
53
|
+
} catch (error: any) {
|
|
54
|
+
console.error('Erreur lors du traitement des workflows:', error);
|
|
55
|
+
return NextResponse.json({ error: error.message || 'Erreur serveur' }, { status: 500 });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
// Traitement des actions planifiées (scheduled)
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
|
|
63
|
+
async function processScheduledActions(now: Date) {
|
|
64
|
+
const margin = new Date(now.getTime() + 5000);
|
|
65
|
+
|
|
66
|
+
const actionsToExecute = await prisma.scheduledWorkflowAction.findMany({
|
|
67
|
+
where: {
|
|
68
|
+
executed: false,
|
|
69
|
+
executeAt: { lte: margin },
|
|
70
|
+
},
|
|
71
|
+
include: {
|
|
72
|
+
contact: {
|
|
73
|
+
include: {
|
|
74
|
+
status: true,
|
|
75
|
+
company: { select: { name: true } },
|
|
46
76
|
},
|
|
47
77
|
},
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
include: {
|
|
51
|
-
status: true,
|
|
52
|
-
},
|
|
78
|
+
workflow: {
|
|
79
|
+
include: {
|
|
80
|
+
user: { include: { smtpConfig: true } },
|
|
53
81
|
},
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
orderBy: { executeAt: 'asc' },
|
|
85
|
+
take: 50,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
if (actionsToExecute.length === 0) {
|
|
89
|
+
return { processed: 0, success: 0, failed: 0, errors: [] };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const results = {
|
|
93
|
+
processed: actionsToExecute.length,
|
|
94
|
+
success: 0,
|
|
95
|
+
failed: 0,
|
|
96
|
+
errors: [] as Array<{ id: string; error: string }>,
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
for (const scheduledAction of actionsToExecute) {
|
|
100
|
+
try {
|
|
101
|
+
const actionData = scheduledAction.actionData as any;
|
|
102
|
+
const contact = scheduledAction.contact;
|
|
103
|
+
const workflow = scheduledAction.workflow;
|
|
104
|
+
|
|
105
|
+
switch (scheduledAction.actionType) {
|
|
106
|
+
case 'SEND_EMAIL':
|
|
107
|
+
await executeScheduledEmail(scheduledAction, actionData, contact, workflow);
|
|
108
|
+
break;
|
|
109
|
+
|
|
110
|
+
case 'SEND_SMS':
|
|
111
|
+
await executeScheduledSMS(scheduledAction, actionData, contact, workflow);
|
|
112
|
+
break;
|
|
113
|
+
|
|
114
|
+
case 'CHANGE_STATUS':
|
|
115
|
+
await changeStatusImmediate(contact.id, actionData.newStatusId);
|
|
116
|
+
break;
|
|
117
|
+
|
|
118
|
+
case 'CREATE_TASK':
|
|
119
|
+
await createTaskImmediate(
|
|
120
|
+
{
|
|
121
|
+
taskTitle: actionData.taskTitle,
|
|
122
|
+
taskDescription: actionData.taskDescription,
|
|
123
|
+
taskType: actionData.taskType,
|
|
124
|
+
taskPriority: actionData.taskPriority,
|
|
125
|
+
taskAssignedUserId: actionData.taskAssignedUserId,
|
|
60
126
|
},
|
|
61
|
-
|
|
127
|
+
workflow,
|
|
128
|
+
contact.id,
|
|
129
|
+
scheduledAction.executeAt,
|
|
130
|
+
);
|
|
131
|
+
break;
|
|
132
|
+
|
|
133
|
+
case 'ASSIGN_CONTACT':
|
|
134
|
+
await assignContactImmediate(
|
|
135
|
+
contact.id,
|
|
136
|
+
actionData.assignCommercialId,
|
|
137
|
+
actionData.assignTeleproId,
|
|
138
|
+
);
|
|
139
|
+
break;
|
|
140
|
+
|
|
141
|
+
case 'ADD_NOTE': {
|
|
142
|
+
const variables = {
|
|
143
|
+
firstName: contact.firstName || '',
|
|
144
|
+
lastName: contact.lastName || '',
|
|
145
|
+
civility: contact.civility || '',
|
|
146
|
+
email: contact.email || '',
|
|
147
|
+
phone: contact.phone || '',
|
|
148
|
+
secondaryPhone: contact.secondaryPhone || '',
|
|
149
|
+
address: contact.address || '',
|
|
150
|
+
city: contact.city || '',
|
|
151
|
+
postalCode: contact.postalCode || '',
|
|
152
|
+
companyName: contact.company?.name || '',
|
|
153
|
+
};
|
|
154
|
+
const noteContent = replaceTemplateVariables(actionData.noteContent || '', variables);
|
|
155
|
+
await addNoteImmediate(
|
|
156
|
+
contact.id,
|
|
157
|
+
noteContent,
|
|
158
|
+
actionData.userId || workflow.userId,
|
|
159
|
+
actionData.workflowName || workflow.name,
|
|
160
|
+
);
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
case 'NOTIFY_USER':
|
|
165
|
+
await notifyUserImmediate(
|
|
166
|
+
{
|
|
167
|
+
notifyUserId: actionData.notifyUserId,
|
|
168
|
+
taskTitle: actionData.taskTitle,
|
|
169
|
+
taskDescription: actionData.taskDescription,
|
|
170
|
+
},
|
|
171
|
+
workflow,
|
|
172
|
+
contact.id,
|
|
173
|
+
scheduledAction.executeAt,
|
|
174
|
+
);
|
|
175
|
+
break;
|
|
176
|
+
|
|
177
|
+
case 'WAIT':
|
|
178
|
+
break;
|
|
179
|
+
|
|
180
|
+
default:
|
|
181
|
+
throw new Error(`Type d'action inconnu: ${scheduledAction.actionType}`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
await prisma.scheduledWorkflowAction.update({
|
|
185
|
+
where: { id: scheduledAction.id },
|
|
186
|
+
data: { executed: true, executedAt: new Date() },
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
results.success++;
|
|
190
|
+
} catch (error: any) {
|
|
191
|
+
const errorMessage = error.message || 'Erreur inconnue';
|
|
192
|
+
|
|
193
|
+
console.error(
|
|
194
|
+
`[Workflow Cron] Erreur lors de l'exécution de l'action ${scheduledAction.id}:`,
|
|
195
|
+
{
|
|
196
|
+
actionType: scheduledAction.actionType,
|
|
197
|
+
contactId: scheduledAction.contactId,
|
|
198
|
+
workflowId: scheduledAction.workflowId,
|
|
199
|
+
error: errorMessage,
|
|
62
200
|
},
|
|
63
|
-
|
|
64
|
-
orderBy: {
|
|
65
|
-
executeAt: 'asc', // Traiter les actions dans l'ordre chronologique
|
|
66
|
-
},
|
|
67
|
-
take: 50, // Limiter à 50 actions par exécution pour éviter les timeouts (limite Vercel: 60s)
|
|
68
|
-
});
|
|
201
|
+
);
|
|
69
202
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
203
|
+
const truncatedError =
|
|
204
|
+
errorMessage.length > 500 ? errorMessage.substring(0, 500) + '...' : errorMessage;
|
|
205
|
+
|
|
206
|
+
await prisma.scheduledWorkflowAction.update({
|
|
207
|
+
where: { id: scheduledAction.id },
|
|
208
|
+
data: { executed: true, executedAt: new Date(), error: truncatedError },
|
|
76
209
|
});
|
|
210
|
+
|
|
211
|
+
results.failed++;
|
|
212
|
+
results.errors.push({ id: scheduledAction.id, error: truncatedError });
|
|
77
213
|
}
|
|
214
|
+
}
|
|
78
215
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
failed: 0,
|
|
82
|
-
errors: [] as Array<{ id: string; error: string }>,
|
|
83
|
-
};
|
|
216
|
+
return results;
|
|
217
|
+
}
|
|
84
218
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
219
|
+
// ---------------------------------------------------------------------------
|
|
220
|
+
// Traitement des workflows TIME_BASED
|
|
221
|
+
// ---------------------------------------------------------------------------
|
|
222
|
+
|
|
223
|
+
async function processTimeBasedWorkflows(now: Date) {
|
|
224
|
+
const workflows = await prisma.workflow.findMany({
|
|
225
|
+
where: { active: true, triggerType: 'TIME_BASED' },
|
|
226
|
+
include: {
|
|
227
|
+
actions: {
|
|
228
|
+
orderBy: { order: 'asc' },
|
|
229
|
+
include: {
|
|
230
|
+
emailTemplate: true,
|
|
231
|
+
newStatus: true,
|
|
232
|
+
conditionStatus: true,
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
user: { include: { smtpConfig: true } },
|
|
236
|
+
},
|
|
237
|
+
});
|
|
91
238
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
break;
|
|
239
|
+
if (workflows.length === 0) {
|
|
240
|
+
return { processed: 0, triggered: 0 };
|
|
241
|
+
}
|
|
96
242
|
|
|
97
|
-
|
|
98
|
-
await executeScheduledSMS(scheduledAction, actionData, contact, workflow);
|
|
99
|
-
break;
|
|
243
|
+
let triggered = 0;
|
|
100
244
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
245
|
+
for (const workflow of workflows) {
|
|
246
|
+
try {
|
|
247
|
+
const delayMs =
|
|
248
|
+
(workflow.triggerTimeDays || 0) * 24 * 60 * 60 * 1000 +
|
|
249
|
+
(workflow.triggerTimeHours || 0) * 60 * 60 * 1000;
|
|
104
250
|
|
|
105
|
-
|
|
106
|
-
await createTaskImmediate(
|
|
107
|
-
{
|
|
108
|
-
taskTitle: actionData.taskTitle,
|
|
109
|
-
taskDescription: actionData.taskDescription,
|
|
110
|
-
},
|
|
111
|
-
workflow,
|
|
112
|
-
contact.id,
|
|
113
|
-
scheduledAction.executeAt,
|
|
114
|
-
);
|
|
115
|
-
break;
|
|
251
|
+
if (delayMs <= 0) continue;
|
|
116
252
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
break;
|
|
253
|
+
const thresholdDate = new Date(now.getTime() - delayMs);
|
|
254
|
+
const windowStart = new Date(thresholdDate.getTime() - 120_000); // 2 min window
|
|
120
255
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
256
|
+
const contacts = await findContactsForTimeBased(
|
|
257
|
+
workflow.triggerTimeReference,
|
|
258
|
+
windowStart,
|
|
259
|
+
thresholdDate,
|
|
260
|
+
);
|
|
124
261
|
|
|
125
|
-
|
|
126
|
-
await prisma.scheduledWorkflowAction.
|
|
127
|
-
where: {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
262
|
+
for (const contact of contacts) {
|
|
263
|
+
const alreadyTriggered = await prisma.scheduledWorkflowAction.findFirst({
|
|
264
|
+
where: {
|
|
265
|
+
workflowId: workflow.id,
|
|
266
|
+
contactId: contact.id,
|
|
267
|
+
createdAt: { gte: new Date(now.getTime() - 24 * 60 * 60 * 1000) },
|
|
131
268
|
},
|
|
132
269
|
});
|
|
133
270
|
|
|
134
|
-
|
|
135
|
-
} catch (error: any) {
|
|
136
|
-
const errorMessage = error.message || 'Erreur inconnue';
|
|
137
|
-
const errorStack = error.stack || '';
|
|
138
|
-
|
|
139
|
-
console.error(
|
|
140
|
-
`[Workflow Cron] Erreur lors de l'exécution de l'action ${scheduledAction.id}:`,
|
|
141
|
-
{
|
|
142
|
-
actionType: scheduledAction.actionType,
|
|
143
|
-
contactId: scheduledAction.contactId,
|
|
144
|
-
workflowId: scheduledAction.workflowId,
|
|
145
|
-
error: errorMessage,
|
|
146
|
-
stack: errorStack,
|
|
147
|
-
},
|
|
148
|
-
);
|
|
149
|
-
|
|
150
|
-
// Marquer comme échouée avec le message d'erreur
|
|
151
|
-
// On limite la taille du message d'erreur pour éviter les problèmes de base de données
|
|
152
|
-
const truncatedError =
|
|
153
|
-
errorMessage.length > 500 ? errorMessage.substring(0, 500) + '...' : errorMessage;
|
|
154
|
-
|
|
155
|
-
await prisma.scheduledWorkflowAction.update({
|
|
156
|
-
where: { id: scheduledAction.id },
|
|
157
|
-
data: {
|
|
158
|
-
executed: true, // Marquer comme exécutée pour ne pas réessayer indéfiniment
|
|
159
|
-
executedAt: new Date(),
|
|
160
|
-
error: truncatedError,
|
|
161
|
-
},
|
|
162
|
-
});
|
|
271
|
+
if (alreadyTriggered) continue;
|
|
163
272
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
error: truncatedError,
|
|
168
|
-
});
|
|
273
|
+
const { executeWorkflowActions } = await getExecuteWorkflowActions();
|
|
274
|
+
await executeWorkflowActions(workflow, contact.id, contact);
|
|
275
|
+
triggered++;
|
|
169
276
|
}
|
|
277
|
+
} catch (error) {
|
|
278
|
+
console.error(`[TIME_BASED] Erreur pour workflow ${workflow.id}:`, error);
|
|
170
279
|
}
|
|
280
|
+
}
|
|
171
281
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
282
|
+
return { processed: workflows.length, triggered };
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
async function findContactsForTimeBased(
|
|
286
|
+
reference: string | null,
|
|
287
|
+
windowStart: Date,
|
|
288
|
+
windowEnd: Date,
|
|
289
|
+
) {
|
|
290
|
+
switch (reference) {
|
|
291
|
+
case 'LAST_STATUS_CHANGE': {
|
|
292
|
+
const interactions = await prisma.interaction.findMany({
|
|
293
|
+
where: {
|
|
294
|
+
type: 'STATUS_CHANGE',
|
|
295
|
+
createdAt: { gte: windowStart, lte: windowEnd },
|
|
296
|
+
},
|
|
297
|
+
select: { contactId: true },
|
|
298
|
+
distinct: ['contactId'],
|
|
299
|
+
take: 50,
|
|
300
|
+
});
|
|
301
|
+
const contactIds = interactions.map((i) => i.contactId);
|
|
302
|
+
if (contactIds.length === 0) return [];
|
|
303
|
+
return prisma.contact.findMany({
|
|
304
|
+
where: { id: { in: contactIds } },
|
|
305
|
+
include: { status: true, company: { select: { name: true, id: true } } },
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
case 'LAST_INTERACTION': {
|
|
310
|
+
const contacts = await prisma.$queryRaw<Array<{ id: string }>>`
|
|
311
|
+
SELECT c.id FROM contact c
|
|
312
|
+
WHERE (
|
|
313
|
+
SELECT MAX(i."createdAt") FROM interaction i WHERE i."contactId" = c.id
|
|
314
|
+
) BETWEEN ${windowStart} AND ${windowEnd}
|
|
315
|
+
LIMIT 50
|
|
316
|
+
`;
|
|
317
|
+
if (contacts.length === 0) return [];
|
|
318
|
+
return prisma.contact.findMany({
|
|
319
|
+
where: { id: { in: contacts.map((c) => c.id) } },
|
|
320
|
+
include: { status: true, company: { select: { name: true, id: true } } },
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
case 'CONTACT_CREATED_DATE':
|
|
325
|
+
default: {
|
|
326
|
+
return prisma.contact.findMany({
|
|
327
|
+
where: { createdAt: { gte: windowStart, lte: windowEnd } },
|
|
328
|
+
include: { status: true, company: { select: { name: true, id: true } } },
|
|
329
|
+
take: 50,
|
|
330
|
+
});
|
|
331
|
+
}
|
|
182
332
|
}
|
|
183
333
|
}
|
|
184
334
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
335
|
+
async function getExecuteWorkflowActions() {
|
|
336
|
+
const mod = await import('@/lib/workflow-executor');
|
|
337
|
+
return {
|
|
338
|
+
executeWorkflowActions: async (workflow: any, contactId: string, contact: any) => {
|
|
339
|
+
for (const action of workflow.actions) {
|
|
340
|
+
if (action.conditionOperator && action.conditionStatusId) {
|
|
341
|
+
const conditionMet =
|
|
342
|
+
action.conditionOperator === 'EQUALS'
|
|
343
|
+
? contact.statusId === action.conditionStatusId
|
|
344
|
+
: contact.statusId !== action.conditionStatusId;
|
|
345
|
+
if (!conditionMet) continue;
|
|
346
|
+
}
|
|
347
|
+
if (action.conditionOrigin && (contact.origin || '') !== action.conditionOrigin) continue;
|
|
348
|
+
if (action.conditionHasCompany === true && !contact.companyId) continue;
|
|
349
|
+
if (action.conditionHasCompany === false && contact.companyId) continue;
|
|
350
|
+
|
|
351
|
+
const delayMs =
|
|
352
|
+
(action.delayDays || 0) * 24 * 60 * 60 * 1000 + (action.delayHours || 0) * 60 * 60 * 1000;
|
|
353
|
+
const executeAt = new Date(Date.now() + delayMs);
|
|
354
|
+
|
|
355
|
+
const variables = {
|
|
356
|
+
firstName: contact.firstName || '',
|
|
357
|
+
lastName: contact.lastName || '',
|
|
358
|
+
civility: contact.civility || '',
|
|
359
|
+
email: contact.email || '',
|
|
360
|
+
phone: contact.phone || '',
|
|
361
|
+
secondaryPhone: contact.secondaryPhone || '',
|
|
362
|
+
address: contact.address || '',
|
|
363
|
+
city: contact.city || '',
|
|
364
|
+
postalCode: contact.postalCode || '',
|
|
365
|
+
companyName: contact.company?.name || '',
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
switch (action.actionType) {
|
|
369
|
+
case 'SEND_EMAIL':
|
|
370
|
+
if (action.emailTemplate && workflow.user.smtpConfig) {
|
|
371
|
+
const subject = replaceTemplateVariables(
|
|
372
|
+
action.emailTemplate.subject || '',
|
|
373
|
+
variables,
|
|
374
|
+
);
|
|
375
|
+
const content = replaceTemplateVariables(
|
|
376
|
+
action.emailTemplate.content || '',
|
|
377
|
+
variables,
|
|
378
|
+
);
|
|
379
|
+
if (executeAt <= new Date()) {
|
|
380
|
+
await mod.sendEmailImmediate(
|
|
381
|
+
workflow,
|
|
382
|
+
contact,
|
|
383
|
+
action.emailTemplate,
|
|
384
|
+
subject,
|
|
385
|
+
content,
|
|
386
|
+
);
|
|
387
|
+
} else {
|
|
388
|
+
await prisma.scheduledWorkflowAction.create({
|
|
389
|
+
data: {
|
|
390
|
+
workflowId: workflow.id,
|
|
391
|
+
actionId: action.id,
|
|
392
|
+
contactId: contact.id,
|
|
393
|
+
actionType: 'SEND_EMAIL',
|
|
394
|
+
actionData: {
|
|
395
|
+
emailTemplateId: action.emailTemplateId,
|
|
396
|
+
templateSubject: action.emailTemplate.subject,
|
|
397
|
+
templateContent: action.emailTemplate.content,
|
|
398
|
+
templateName: action.emailTemplate.name,
|
|
399
|
+
smtpConfigId: workflow.user.smtpConfig.id,
|
|
400
|
+
userId: workflow.userId,
|
|
401
|
+
workflowName: workflow.name,
|
|
402
|
+
},
|
|
403
|
+
executeAt,
|
|
404
|
+
},
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
break;
|
|
409
|
+
case 'CHANGE_STATUS':
|
|
410
|
+
if (action.newStatusId) {
|
|
411
|
+
if (executeAt <= new Date()) {
|
|
412
|
+
await mod.changeStatusImmediate(contactId, action.newStatusId);
|
|
413
|
+
} else {
|
|
414
|
+
await prisma.scheduledWorkflowAction.create({
|
|
415
|
+
data: {
|
|
416
|
+
workflowId: workflow.id,
|
|
417
|
+
actionId: action.id,
|
|
418
|
+
contactId,
|
|
419
|
+
actionType: 'CHANGE_STATUS',
|
|
420
|
+
actionData: { newStatusId: action.newStatusId, workflowName: workflow.name },
|
|
421
|
+
executeAt,
|
|
422
|
+
},
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
break;
|
|
427
|
+
case 'CREATE_TASK':
|
|
428
|
+
if (action.taskTitle) {
|
|
429
|
+
if (executeAt <= new Date()) {
|
|
430
|
+
await mod.createTaskImmediate(action, workflow, contactId, executeAt);
|
|
431
|
+
} else {
|
|
432
|
+
await prisma.scheduledWorkflowAction.create({
|
|
433
|
+
data: {
|
|
434
|
+
workflowId: workflow.id,
|
|
435
|
+
actionId: action.id,
|
|
436
|
+
contactId,
|
|
437
|
+
actionType: 'CREATE_TASK',
|
|
438
|
+
actionData: {
|
|
439
|
+
taskTitle: action.taskTitle,
|
|
440
|
+
taskDescription: action.taskDescription || '',
|
|
441
|
+
taskType: action.taskType || 'OTHER',
|
|
442
|
+
taskPriority: action.taskPriority || 'MEDIUM',
|
|
443
|
+
taskAssignedUserId: action.taskAssignedUserId || workflow.userId,
|
|
444
|
+
userId: workflow.userId,
|
|
445
|
+
workflowName: workflow.name,
|
|
446
|
+
},
|
|
447
|
+
executeAt,
|
|
448
|
+
},
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
break;
|
|
453
|
+
case 'ASSIGN_CONTACT':
|
|
454
|
+
if (action.assignCommercialId || action.assignTeleproId) {
|
|
455
|
+
if (executeAt <= new Date()) {
|
|
456
|
+
await mod.assignContactImmediate(
|
|
457
|
+
contactId,
|
|
458
|
+
action.assignCommercialId,
|
|
459
|
+
action.assignTeleproId,
|
|
460
|
+
);
|
|
461
|
+
} else {
|
|
462
|
+
await prisma.scheduledWorkflowAction.create({
|
|
463
|
+
data: {
|
|
464
|
+
workflowId: workflow.id,
|
|
465
|
+
actionId: action.id,
|
|
466
|
+
contactId,
|
|
467
|
+
actionType: 'ASSIGN_CONTACT',
|
|
468
|
+
actionData: {
|
|
469
|
+
assignCommercialId: action.assignCommercialId,
|
|
470
|
+
assignTeleproId: action.assignTeleproId,
|
|
471
|
+
workflowName: workflow.name,
|
|
472
|
+
},
|
|
473
|
+
executeAt,
|
|
474
|
+
},
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
break;
|
|
479
|
+
case 'ADD_NOTE':
|
|
480
|
+
if (action.noteContent) {
|
|
481
|
+
const noteContent = replaceTemplateVariables(action.noteContent, variables);
|
|
482
|
+
if (executeAt <= new Date()) {
|
|
483
|
+
await mod.addNoteImmediate(contactId, noteContent, workflow.userId, workflow.name);
|
|
484
|
+
} else {
|
|
485
|
+
await prisma.scheduledWorkflowAction.create({
|
|
486
|
+
data: {
|
|
487
|
+
workflowId: workflow.id,
|
|
488
|
+
actionId: action.id,
|
|
489
|
+
contactId,
|
|
490
|
+
actionType: 'ADD_NOTE',
|
|
491
|
+
actionData: {
|
|
492
|
+
noteContent: action.noteContent,
|
|
493
|
+
userId: workflow.userId,
|
|
494
|
+
workflowName: workflow.name,
|
|
495
|
+
},
|
|
496
|
+
executeAt,
|
|
497
|
+
},
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
break;
|
|
502
|
+
case 'NOTIFY_USER':
|
|
503
|
+
if (action.notifyUserId && action.taskTitle) {
|
|
504
|
+
if (executeAt <= new Date()) {
|
|
505
|
+
await mod.notifyUserImmediate(action, workflow, contactId, executeAt);
|
|
506
|
+
} else {
|
|
507
|
+
await prisma.scheduledWorkflowAction.create({
|
|
508
|
+
data: {
|
|
509
|
+
workflowId: workflow.id,
|
|
510
|
+
actionId: action.id,
|
|
511
|
+
contactId,
|
|
512
|
+
actionType: 'NOTIFY_USER',
|
|
513
|
+
actionData: {
|
|
514
|
+
notifyUserId: action.notifyUserId,
|
|
515
|
+
taskTitle: action.taskTitle,
|
|
516
|
+
taskDescription: action.taskDescription || '',
|
|
517
|
+
userId: workflow.userId,
|
|
518
|
+
workflowName: workflow.name,
|
|
519
|
+
},
|
|
520
|
+
executeAt,
|
|
521
|
+
},
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
break;
|
|
526
|
+
case 'SEND_SMS':
|
|
527
|
+
if (action.smsMessage && contact.phone) {
|
|
528
|
+
let message = action.smsMessage;
|
|
529
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
530
|
+
message = message.replace(new RegExp(`{${key}}`, 'g'), value as string);
|
|
531
|
+
}
|
|
532
|
+
if (executeAt <= new Date()) {
|
|
533
|
+
await mod.sendSMSImmediate(workflow, contact, message);
|
|
534
|
+
} else {
|
|
535
|
+
await prisma.scheduledWorkflowAction.create({
|
|
536
|
+
data: {
|
|
537
|
+
workflowId: workflow.id,
|
|
538
|
+
actionId: action.id,
|
|
539
|
+
contactId: contact.id,
|
|
540
|
+
actionType: 'SEND_SMS',
|
|
541
|
+
actionData: {
|
|
542
|
+
smsMessage: action.smsMessage,
|
|
543
|
+
userId: workflow.userId,
|
|
544
|
+
workflowName: workflow.name,
|
|
545
|
+
},
|
|
546
|
+
executeAt,
|
|
547
|
+
},
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
break;
|
|
552
|
+
case 'WAIT':
|
|
553
|
+
break;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
},
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// ---------------------------------------------------------------------------
|
|
561
|
+
// Helpers scheduled email / SMS
|
|
562
|
+
// ---------------------------------------------------------------------------
|
|
563
|
+
|
|
188
564
|
async function executeScheduledEmail(
|
|
189
565
|
scheduledAction: any,
|
|
190
566
|
actionData: any,
|
|
@@ -195,7 +571,6 @@ async function executeScheduledEmail(
|
|
|
195
571
|
throw new Error('Configuration SMTP non trouvée');
|
|
196
572
|
}
|
|
197
573
|
|
|
198
|
-
// Remplacer les variables dans le sujet et le contenu
|
|
199
574
|
const variables = {
|
|
200
575
|
firstName: contact.firstName || '',
|
|
201
576
|
lastName: contact.lastName || '',
|
|
@@ -206,29 +581,24 @@ async function executeScheduledEmail(
|
|
|
206
581
|
address: contact.address || '',
|
|
207
582
|
city: contact.city || '',
|
|
208
583
|
postalCode: contact.postalCode || '',
|
|
209
|
-
companyName: contact.
|
|
584
|
+
companyName: contact.company?.name || '',
|
|
210
585
|
};
|
|
211
586
|
|
|
212
587
|
const subject = replaceTemplateVariables(actionData.templateSubject || '', variables);
|
|
213
588
|
const content = replaceTemplateVariables(actionData.templateContent || '', variables);
|
|
214
589
|
|
|
215
|
-
// Déchiffrer le mot de passe SMTP
|
|
216
590
|
let password: string;
|
|
217
591
|
try {
|
|
218
592
|
password = decrypt(workflow.user.smtpConfig.password);
|
|
219
|
-
} catch
|
|
593
|
+
} catch {
|
|
220
594
|
password = workflow.user.smtpConfig.password;
|
|
221
595
|
}
|
|
222
596
|
|
|
223
|
-
// Créer le transporteur SMTP
|
|
224
597
|
const transporter = nodemailer.createTransport({
|
|
225
598
|
host: workflow.user.smtpConfig.host,
|
|
226
599
|
port: workflow.user.smtpConfig.port,
|
|
227
600
|
secure: workflow.user.smtpConfig.secure,
|
|
228
|
-
auth: {
|
|
229
|
-
user: workflow.user.smtpConfig.username,
|
|
230
|
-
pass: password,
|
|
231
|
-
},
|
|
601
|
+
auth: { user: workflow.user.smtpConfig.username, pass: password },
|
|
232
602
|
});
|
|
233
603
|
|
|
234
604
|
await transporter.sendMail({
|
|
@@ -240,7 +610,6 @@ async function executeScheduledEmail(
|
|
|
240
610
|
html: content,
|
|
241
611
|
});
|
|
242
612
|
|
|
243
|
-
// Créer une interaction pour tracer l'email envoyé
|
|
244
613
|
await prisma.interaction.create({
|
|
245
614
|
data: {
|
|
246
615
|
contactId: contact.id,
|
|
@@ -253,33 +622,27 @@ async function executeScheduledEmail(
|
|
|
253
622
|
});
|
|
254
623
|
}
|
|
255
624
|
|
|
256
|
-
/**
|
|
257
|
-
* Exécute une action SMS planifiée
|
|
258
|
-
*/
|
|
259
625
|
async function executeScheduledSMS(
|
|
260
626
|
scheduledAction: any,
|
|
261
627
|
actionData: any,
|
|
262
628
|
contact: any,
|
|
263
629
|
workflow: any,
|
|
264
630
|
) {
|
|
265
|
-
// Remplacer les variables dans le message
|
|
266
631
|
let message = actionData.smsMessage || '';
|
|
267
632
|
const variables: Record<string, string> = {
|
|
268
633
|
firstName: contact.firstName || '',
|
|
269
634
|
lastName: contact.lastName || '',
|
|
270
635
|
email: contact.email || '',
|
|
271
636
|
phone: contact.phone || '',
|
|
272
|
-
companyName: contact.
|
|
637
|
+
companyName: contact.company?.name || '',
|
|
273
638
|
};
|
|
274
639
|
|
|
275
640
|
for (const [key, value] of Object.entries(variables)) {
|
|
276
641
|
message = message.replace(new RegExp(`{${key}}`, 'g'), value);
|
|
277
642
|
}
|
|
278
643
|
|
|
279
|
-
// TODO: Implémenter l'envoi de SMS (nécessite une API SMS)
|
|
280
644
|
console.log(`SMS à envoyer à ${contact.phone}: ${message}`);
|
|
281
645
|
|
|
282
|
-
// Créer une interaction pour tracer le SMS
|
|
283
646
|
await prisma.interaction.create({
|
|
284
647
|
data: {
|
|
285
648
|
contactId: contact.id,
|