create-crm-tmp 1.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/bin/create-crm-tmp.js +93 -0
- package/package.json +25 -0
- package/template/.prettierignore +33 -0
- package/template/.prettierrc.json +25 -0
- package/template/README.md +173 -0
- package/template/eslint.config.mjs +18 -0
- package/template/exemple-contacts.csv +11 -0
- package/template/next.config.ts +8 -0
- package/template/package.json +64 -0
- package/template/postcss.config.mjs +7 -0
- package/template/prisma/migrations/20251126144728_init/migration.sql +78 -0
- package/template/prisma/migrations/20251126155204_add_user_roles/migration.sql +5 -0
- package/template/prisma/migrations/20251128095126_add_company_info/migration.sql +19 -0
- package/template/prisma/migrations/20251128123321_add_smtp_config/migration.sql +22 -0
- package/template/prisma/migrations/20251128132303_add_status/migration.sql +23 -0
- package/template/prisma/migrations/20251201102207_add_user_active/migration.sql +75 -0
- package/template/prisma/migrations/20251201105507_add_email_signature/migration.sql +2 -0
- package/template/prisma/migrations/20251201151122_add_tasks/migration.sql +45 -0
- package/template/prisma/migrations/20251202111854_add_task_reminder/migration.sql +2 -0
- package/template/prisma/migrations/20251202135859_add_google_meet_integration/migration.sql +27 -0
- package/template/prisma/migrations/20251203103317_add_meta_lead_integration/migration.sql +20 -0
- package/template/prisma/migrations/20251203104002_add_google_ads_integration/migration.sql +18 -0
- package/template/prisma/migrations/20251203112122_add_google_sheet_integration/migration.sql +32 -0
- package/template/prisma/migrations/20251203153853_allow_multiple_integration_configs/migration.sql +20 -0
- package/template/prisma/migrations/20251205141705_update_user_roles/migration.sql +12 -0
- package/template/prisma/migrations/20251205150000_add_commercial_and_telepro_assignment/migration.sql +21 -0
- package/template/prisma/migrations/20251205160000_add_interaction_logging/migration.sql +11 -0
- package/template/prisma/migrations/20251208090314_add_automatic_interaction_types/migration.sql +12 -0
- package/template/prisma/migrations/20251208094843_mg/migration.sql +14 -0
- package/template/prisma/migrations/20251208100000_add_company_support/migration.sql +14 -0
- package/template/prisma/migrations/20251208110000_add_templates/migration.sql +26 -0
- package/template/prisma/migrations/20251208141304_add_video_conference_task_type/migration.sql +2 -0
- package/template/prisma/migrations/20251209104759_add_internal_note_to_task/migration.sql +2 -0
- package/template/prisma/migrations/20251209134803_add_company_field/migration.sql +2 -0
- package/template/prisma/migrations/20251209150000_rename_company_to_company_name/migration.sql +3 -0
- package/template/prisma/migrations/20251209150016_add_email_tracking/migration.sql +21 -0
- package/template/prisma/migrations/20251209155908_add_notify_contact_to_task/migration.sql +2 -0
- package/template/prisma/migrations/20251210110019_add_appointment_types/migration.sql +10 -0
- package/template/prisma/migrations/20251210113928_add_contact_files/migration.sql +26 -0
- package/template/prisma/migrations/20251212132339_add_custom_roles/migration.sql +24 -0
- package/template/prisma/migrations/20251215104448_add_file_interaction_types/migration.sql +11 -0
- package/template/prisma/migrations/20251215145616_add_closing_reasons/migration.sql +12 -0
- package/template/prisma/migrations/20251216140850_add_log_users/migration.sql +25 -0
- package/template/prisma/migrations/20251216151000_rename_perdu_to_ferme/migration.sql +8 -0
- package/template/prisma/migrations/20251216162318_add_column_mappings_to_google_sheet/migration.sql +2 -0
- package/template/prisma/migrations/20251216185127_add_workflows/migration.sql +80 -0
- package/template/prisma/migrations/20251216192237_add_scheduled_workflow_actions/migration.sql +32 -0
- package/template/prisma/migrations/migration_lock.toml +3 -0
- package/template/prisma/schema.prisma +582 -0
- package/template/prisma.config.ts +14 -0
- package/template/src/app/(auth)/invite/[token]/page.tsx +200 -0
- package/template/src/app/(auth)/layout.tsx +3 -0
- package/template/src/app/(auth)/reset-password/complete/page.tsx +213 -0
- package/template/src/app/(auth)/reset-password/page.tsx +146 -0
- package/template/src/app/(auth)/reset-password/verify/page.tsx +183 -0
- package/template/src/app/(auth)/signin/page.tsx +166 -0
- package/template/src/app/(dashboard)/agenda/page.tsx +3051 -0
- package/template/src/app/(dashboard)/automatisation/[id]/page.tsx +24 -0
- package/template/src/app/(dashboard)/automatisation/_components/workflow-editor.tsx +905 -0
- package/template/src/app/(dashboard)/automatisation/new/page.tsx +20 -0
- package/template/src/app/(dashboard)/automatisation/page.tsx +337 -0
- package/template/src/app/(dashboard)/closing/page.tsx +1052 -0
- package/template/src/app/(dashboard)/contacts/[id]/page.tsx +6028 -0
- package/template/src/app/(dashboard)/contacts/page.tsx +3713 -0
- package/template/src/app/(dashboard)/dashboard/page.tsx +186 -0
- package/template/src/app/(dashboard)/layout.tsx +30 -0
- package/template/src/app/(dashboard)/settings/page.tsx +4070 -0
- package/template/src/app/(dashboard)/templates/page.tsx +567 -0
- package/template/src/app/(dashboard)/users/list/page.tsx +507 -0
- package/template/src/app/(dashboard)/users/page.tsx +457 -0
- package/template/src/app/(dashboard)/users/permissions/page.tsx +181 -0
- package/template/src/app/(dashboard)/users/roles/page.tsx +434 -0
- package/template/src/app/api/audit-logs/route.ts +57 -0
- package/template/src/app/api/auth/[...all]/route.ts +4 -0
- package/template/src/app/api/auth/check-active/route.ts +31 -0
- package/template/src/app/api/auth/google/callback/route.ts +94 -0
- package/template/src/app/api/auth/google/disconnect/route.ts +32 -0
- package/template/src/app/api/auth/google/route.ts +34 -0
- package/template/src/app/api/auth/google/status/route.ts +32 -0
- package/template/src/app/api/closing-reasons/route.ts +27 -0
- package/template/src/app/api/contacts/[id]/files/[fileId]/route.ts +94 -0
- package/template/src/app/api/contacts/[id]/files/route.ts +269 -0
- package/template/src/app/api/contacts/[id]/interactions/[interactionId]/route.ts +91 -0
- package/template/src/app/api/contacts/[id]/interactions/route.ts +103 -0
- package/template/src/app/api/contacts/[id]/meet/route.ts +296 -0
- package/template/src/app/api/contacts/[id]/route.ts +322 -0
- package/template/src/app/api/contacts/[id]/send-email/route.ts +254 -0
- package/template/src/app/api/contacts/export/route.ts +270 -0
- package/template/src/app/api/contacts/import/route.ts +381 -0
- package/template/src/app/api/contacts/route.ts +283 -0
- package/template/src/app/api/dashboard/stats/route.ts +299 -0
- package/template/src/app/api/email/track/[id]/route.ts +68 -0
- package/template/src/app/api/integrations/google-sheet/sync/route.ts +526 -0
- package/template/src/app/api/invite/complete/route.ts +88 -0
- package/template/src/app/api/invite/validate/route.ts +55 -0
- package/template/src/app/api/reminders/route.ts +95 -0
- package/template/src/app/api/reset-password/complete/route.ts +73 -0
- package/template/src/app/api/reset-password/request/route.ts +84 -0
- package/template/src/app/api/reset-password/validate/route.ts +49 -0
- package/template/src/app/api/reset-password/verify/route.ts +74 -0
- package/template/src/app/api/roles/[id]/route.ts +183 -0
- package/template/src/app/api/roles/route.ts +140 -0
- package/template/src/app/api/send/route.ts +282 -0
- package/template/src/app/api/settings/change-password/route.ts +95 -0
- package/template/src/app/api/settings/closing-reasons/[id]/route.ts +84 -0
- package/template/src/app/api/settings/closing-reasons/route.ts +74 -0
- package/template/src/app/api/settings/company/route.ts +121 -0
- package/template/src/app/api/settings/google-ads/[id]/route.ts +117 -0
- package/template/src/app/api/settings/google-ads/route.ts +122 -0
- package/template/src/app/api/settings/google-sheet/[id]/route.ts +230 -0
- package/template/src/app/api/settings/google-sheet/auto-map/route.ts +196 -0
- package/template/src/app/api/settings/google-sheet/route.ts +254 -0
- package/template/src/app/api/settings/meta-leads/[id]/route.ts +123 -0
- package/template/src/app/api/settings/meta-leads/route.ts +132 -0
- package/template/src/app/api/settings/profile/route.ts +42 -0
- package/template/src/app/api/settings/smtp/route.ts +130 -0
- package/template/src/app/api/settings/smtp/test/route.ts +121 -0
- package/template/src/app/api/settings/statuses/[id]/route.ts +101 -0
- package/template/src/app/api/settings/statuses/route.ts +83 -0
- package/template/src/app/api/statuses/route.ts +25 -0
- package/template/src/app/api/tasks/[id]/attendees/route.ts +76 -0
- package/template/src/app/api/tasks/[id]/route.ts +728 -0
- package/template/src/app/api/tasks/meet/route.ts +240 -0
- package/template/src/app/api/tasks/route.ts +417 -0
- package/template/src/app/api/templates/[id]/route.ts +140 -0
- package/template/src/app/api/templates/route.ts +91 -0
- package/template/src/app/api/users/[id]/route.ts +168 -0
- package/template/src/app/api/users/list/route.ts +45 -0
- package/template/src/app/api/users/me/route.ts +48 -0
- package/template/src/app/api/users/route.ts +250 -0
- package/template/src/app/api/webhooks/google-ads/route.ts +208 -0
- package/template/src/app/api/webhooks/meta-leads/route.ts +258 -0
- package/template/src/app/api/workflows/[id]/route.ts +192 -0
- package/template/src/app/api/workflows/process/route.ts +293 -0
- package/template/src/app/api/workflows/route.ts +124 -0
- package/template/src/app/favicon.ico +0 -0
- package/template/src/app/globals.css +1416 -0
- package/template/src/app/layout.tsx +31 -0
- package/template/src/app/page.tsx +32 -0
- package/template/src/components/dashboard/activity-chart.tsx +67 -0
- package/template/src/components/dashboard/contacts-chart.tsx +63 -0
- package/template/src/components/dashboard/recent-activity.tsx +164 -0
- package/template/src/components/dashboard/sales-analytics-chart.tsx +81 -0
- package/template/src/components/dashboard/stat-card.tsx +61 -0
- package/template/src/components/dashboard/status-distribution-chart.tsx +45 -0
- package/template/src/components/dashboard/tasks-pie-chart.tsx +88 -0
- package/template/src/components/dashboard/top-contacts-list.tsx +129 -0
- package/template/src/components/dashboard/upcoming-tasks-list.tsx +126 -0
- package/template/src/components/editor.tsx +856 -0
- package/template/src/components/email-template.tsx +35 -0
- package/template/src/components/header.tsx +320 -0
- package/template/src/components/invitation-email-template.tsx +79 -0
- package/template/src/components/meet-cancellation-email-template.tsx +120 -0
- package/template/src/components/meet-confirmation-email-template.tsx +156 -0
- package/template/src/components/meet-update-email-template.tsx +209 -0
- package/template/src/components/page-header.tsx +61 -0
- package/template/src/components/reset-password-email-template.tsx +79 -0
- package/template/src/components/sidebar.tsx +294 -0
- package/template/src/components/skeleton.tsx +380 -0
- package/template/src/components/ui/commands.tsx +396 -0
- package/template/src/components/ui/components.tsx +150 -0
- package/template/src/components/ui/theme.tsx +5 -0
- package/template/src/components/view-as-banner.tsx +45 -0
- package/template/src/components/view-as-modal.tsx +186 -0
- package/template/src/contexts/mobile-menu-context.tsx +31 -0
- package/template/src/contexts/sidebar-context.tsx +107 -0
- package/template/src/contexts/task-reminder-context.tsx +239 -0
- package/template/src/contexts/view-as-context.tsx +84 -0
- package/template/src/hooks/use-user-role.ts +82 -0
- package/template/src/lib/audit-log.ts +45 -0
- package/template/src/lib/auth-client.ts +16 -0
- package/template/src/lib/auth.ts +35 -0
- package/template/src/lib/check-permission.ts +193 -0
- package/template/src/lib/contact-duplicate.ts +112 -0
- package/template/src/lib/contact-interactions.ts +371 -0
- package/template/src/lib/encryption.ts +99 -0
- package/template/src/lib/google-calendar.ts +300 -0
- package/template/src/lib/google-drive.ts +372 -0
- package/template/src/lib/permissions.ts +412 -0
- package/template/src/lib/prisma.ts +32 -0
- package/template/src/lib/roles.ts +120 -0
- package/template/src/lib/template-variables.ts +76 -0
- package/template/src/lib/utils.ts +46 -0
- package/template/src/lib/workflow-executor.ts +482 -0
- package/template/src/proxy.ts +91 -0
- package/template/tsconfig.json +34 -0
- package/template/vercel.json +8 -0
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { prisma } from '@/lib/prisma';
|
|
3
|
+
import { changeStatusImmediate, createTaskImmediate } from '@/lib/workflow-executor';
|
|
4
|
+
import { decrypt } from '@/lib/encryption';
|
|
5
|
+
import nodemailer from 'nodemailer';
|
|
6
|
+
import { replaceTemplateVariables } from '@/lib/template-variables';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* GET /api/workflows/process
|
|
10
|
+
* Endpoint appelé par Vercel Cron pour traiter les actions planifiées
|
|
11
|
+
*/
|
|
12
|
+
export async function GET(request: NextRequest) {
|
|
13
|
+
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
|
+
const cronSecret = process.env.CRON_SECRET;
|
|
18
|
+
|
|
19
|
+
if (cronSecret) {
|
|
20
|
+
const authHeader = request.headers.get('authorization');
|
|
21
|
+
const expectedAuth = `Bearer ${cronSecret}`;
|
|
22
|
+
|
|
23
|
+
if (authHeader !== expectedAuth) {
|
|
24
|
+
// En développement, on peut être plus permissif pour les tests
|
|
25
|
+
const isDevelopment = process.env.NODE_ENV === 'development';
|
|
26
|
+
|
|
27
|
+
if (!isDevelopment) {
|
|
28
|
+
return NextResponse.json(
|
|
29
|
+
{ error: 'Unauthorized - Secret manquant ou invalide' },
|
|
30
|
+
{ status: 401 },
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
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
|
+
const now = new Date();
|
|
39
|
+
const margin = new Date(now.getTime() + 5000); // 5 secondes de marge
|
|
40
|
+
|
|
41
|
+
const actionsToExecute = await prisma.scheduledWorkflowAction.findMany({
|
|
42
|
+
where: {
|
|
43
|
+
executed: false,
|
|
44
|
+
executeAt: {
|
|
45
|
+
lte: margin, // Date d'exécution <= maintenant + marge
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
include: {
|
|
49
|
+
contact: {
|
|
50
|
+
include: {
|
|
51
|
+
status: true,
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
workflow: {
|
|
55
|
+
include: {
|
|
56
|
+
user: {
|
|
57
|
+
include: {
|
|
58
|
+
smtpConfig: true,
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
},
|
|
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
|
+
});
|
|
69
|
+
|
|
70
|
+
if (actionsToExecute.length === 0) {
|
|
71
|
+
return NextResponse.json({
|
|
72
|
+
success: true,
|
|
73
|
+
processed: 0,
|
|
74
|
+
message: 'Aucune action à exécuter',
|
|
75
|
+
timestamp: now.toISOString(),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const results = {
|
|
80
|
+
success: 0,
|
|
81
|
+
failed: 0,
|
|
82
|
+
errors: [] as Array<{ id: string; error: string }>,
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// Exécuter chaque action
|
|
86
|
+
for (const scheduledAction of actionsToExecute) {
|
|
87
|
+
try {
|
|
88
|
+
const actionData = scheduledAction.actionData as any;
|
|
89
|
+
const contact = scheduledAction.contact;
|
|
90
|
+
const workflow = scheduledAction.workflow;
|
|
91
|
+
|
|
92
|
+
switch (scheduledAction.actionType) {
|
|
93
|
+
case 'SEND_EMAIL':
|
|
94
|
+
await executeScheduledEmail(scheduledAction, actionData, contact, workflow);
|
|
95
|
+
break;
|
|
96
|
+
|
|
97
|
+
case 'SEND_SMS':
|
|
98
|
+
await executeScheduledSMS(scheduledAction, actionData, contact, workflow);
|
|
99
|
+
break;
|
|
100
|
+
|
|
101
|
+
case 'CHANGE_STATUS':
|
|
102
|
+
await changeStatusImmediate(contact.id, actionData.newStatusId);
|
|
103
|
+
break;
|
|
104
|
+
|
|
105
|
+
case 'CREATE_TASK':
|
|
106
|
+
await createTaskImmediate(
|
|
107
|
+
{
|
|
108
|
+
taskTitle: actionData.taskTitle,
|
|
109
|
+
taskDescription: actionData.taskDescription,
|
|
110
|
+
},
|
|
111
|
+
workflow,
|
|
112
|
+
contact.id,
|
|
113
|
+
scheduledAction.executeAt,
|
|
114
|
+
);
|
|
115
|
+
break;
|
|
116
|
+
|
|
117
|
+
case 'WAIT':
|
|
118
|
+
// L'action "Attendre" ne fait rien, elle est juste là pour le délai
|
|
119
|
+
break;
|
|
120
|
+
|
|
121
|
+
default:
|
|
122
|
+
throw new Error(`Type d'action inconnu: ${scheduledAction.actionType}`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Marquer comme exécutée
|
|
126
|
+
await prisma.scheduledWorkflowAction.update({
|
|
127
|
+
where: { id: scheduledAction.id },
|
|
128
|
+
data: {
|
|
129
|
+
executed: true,
|
|
130
|
+
executedAt: new Date(),
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
results.success++;
|
|
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
|
+
});
|
|
163
|
+
|
|
164
|
+
results.failed++;
|
|
165
|
+
results.errors.push({
|
|
166
|
+
id: scheduledAction.id,
|
|
167
|
+
error: truncatedError,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return NextResponse.json({
|
|
173
|
+
success: true,
|
|
174
|
+
processed: actionsToExecute.length,
|
|
175
|
+
results,
|
|
176
|
+
timestamp: now.toISOString(),
|
|
177
|
+
executionTime: `${Date.now() - now.getTime()}ms`,
|
|
178
|
+
});
|
|
179
|
+
} catch (error: any) {
|
|
180
|
+
console.error('Erreur lors du traitement des workflows:', error);
|
|
181
|
+
return NextResponse.json({ error: error.message || 'Erreur serveur' }, { status: 500 });
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Exécute une action email planifiée
|
|
187
|
+
*/
|
|
188
|
+
async function executeScheduledEmail(
|
|
189
|
+
scheduledAction: any,
|
|
190
|
+
actionData: any,
|
|
191
|
+
contact: any,
|
|
192
|
+
workflow: any,
|
|
193
|
+
) {
|
|
194
|
+
if (!workflow.user.smtpConfig) {
|
|
195
|
+
throw new Error('Configuration SMTP non trouvée');
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Remplacer les variables dans le sujet et le contenu
|
|
199
|
+
const variables = {
|
|
200
|
+
firstName: contact.firstName || '',
|
|
201
|
+
lastName: contact.lastName || '',
|
|
202
|
+
civility: contact.civility || '',
|
|
203
|
+
email: contact.email || '',
|
|
204
|
+
phone: contact.phone || '',
|
|
205
|
+
secondaryPhone: contact.secondaryPhone || '',
|
|
206
|
+
address: contact.address || '',
|
|
207
|
+
city: contact.city || '',
|
|
208
|
+
postalCode: contact.postalCode || '',
|
|
209
|
+
companyName: contact.companyName || '',
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const subject = replaceTemplateVariables(actionData.templateSubject || '', variables);
|
|
213
|
+
const content = replaceTemplateVariables(actionData.templateContent || '', variables);
|
|
214
|
+
|
|
215
|
+
// Déchiffrer le mot de passe SMTP
|
|
216
|
+
let password: string;
|
|
217
|
+
try {
|
|
218
|
+
password = decrypt(workflow.user.smtpConfig.password);
|
|
219
|
+
} catch (error) {
|
|
220
|
+
password = workflow.user.smtpConfig.password;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Créer le transporteur SMTP
|
|
224
|
+
const transporter = nodemailer.createTransport({
|
|
225
|
+
host: workflow.user.smtpConfig.host,
|
|
226
|
+
port: workflow.user.smtpConfig.port,
|
|
227
|
+
secure: workflow.user.smtpConfig.secure,
|
|
228
|
+
auth: {
|
|
229
|
+
user: workflow.user.smtpConfig.username,
|
|
230
|
+
pass: password,
|
|
231
|
+
},
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
await transporter.sendMail({
|
|
235
|
+
from: workflow.user.smtpConfig.fromName
|
|
236
|
+
? `"${workflow.user.smtpConfig.fromName}" <${workflow.user.smtpConfig.fromEmail}>`
|
|
237
|
+
: workflow.user.smtpConfig.fromEmail,
|
|
238
|
+
to: contact.email,
|
|
239
|
+
subject,
|
|
240
|
+
html: content,
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
// Créer une interaction pour tracer l'email envoyé
|
|
244
|
+
await prisma.interaction.create({
|
|
245
|
+
data: {
|
|
246
|
+
contactId: contact.id,
|
|
247
|
+
type: 'EMAIL',
|
|
248
|
+
title: `Email automatique: ${actionData.templateName || 'Template'}`,
|
|
249
|
+
content: `Email envoyé automatiquement via le workflow "${actionData.workflowName || workflow.name}"`,
|
|
250
|
+
userId: actionData.userId || workflow.userId,
|
|
251
|
+
date: new Date(),
|
|
252
|
+
},
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Exécute une action SMS planifiée
|
|
258
|
+
*/
|
|
259
|
+
async function executeScheduledSMS(
|
|
260
|
+
scheduledAction: any,
|
|
261
|
+
actionData: any,
|
|
262
|
+
contact: any,
|
|
263
|
+
workflow: any,
|
|
264
|
+
) {
|
|
265
|
+
// Remplacer les variables dans le message
|
|
266
|
+
let message = actionData.smsMessage || '';
|
|
267
|
+
const variables: Record<string, string> = {
|
|
268
|
+
firstName: contact.firstName || '',
|
|
269
|
+
lastName: contact.lastName || '',
|
|
270
|
+
email: contact.email || '',
|
|
271
|
+
phone: contact.phone || '',
|
|
272
|
+
companyName: contact.companyName || '',
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
276
|
+
message = message.replace(new RegExp(`{${key}}`, 'g'), value);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// TODO: Implémenter l'envoi de SMS (nécessite une API SMS)
|
|
280
|
+
console.log(`SMS à envoyer à ${contact.phone}: ${message}`);
|
|
281
|
+
|
|
282
|
+
// Créer une interaction pour tracer le SMS
|
|
283
|
+
await prisma.interaction.create({
|
|
284
|
+
data: {
|
|
285
|
+
contactId: contact.id,
|
|
286
|
+
type: 'SMS',
|
|
287
|
+
title: 'SMS automatique',
|
|
288
|
+
content: message,
|
|
289
|
+
userId: actionData.userId || workflow.userId,
|
|
290
|
+
date: new Date(),
|
|
291
|
+
},
|
|
292
|
+
});
|
|
293
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { auth } from '@/lib/auth';
|
|
3
|
+
import { prisma } from '@/lib/prisma';
|
|
4
|
+
|
|
5
|
+
// GET /api/workflows - Récupérer tous les workflows de l'utilisateur
|
|
6
|
+
export async function GET(request: NextRequest) {
|
|
7
|
+
try {
|
|
8
|
+
const session = await auth.api.getSession({
|
|
9
|
+
headers: request.headers,
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
if (!session) {
|
|
13
|
+
return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const workflows = await prisma.workflow.findMany({
|
|
17
|
+
where: {
|
|
18
|
+
userId: session.user.id,
|
|
19
|
+
},
|
|
20
|
+
include: {
|
|
21
|
+
actions: {
|
|
22
|
+
orderBy: { order: 'asc' },
|
|
23
|
+
include: {
|
|
24
|
+
emailTemplate: true,
|
|
25
|
+
newStatus: true,
|
|
26
|
+
conditionStatus: true,
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
triggerStatus: true,
|
|
30
|
+
triggerToStatus: true,
|
|
31
|
+
},
|
|
32
|
+
orderBy: {
|
|
33
|
+
createdAt: 'desc',
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
return NextResponse.json(workflows);
|
|
38
|
+
} catch (error: any) {
|
|
39
|
+
console.error('Erreur lors de la récupération des workflows:', error);
|
|
40
|
+
return NextResponse.json({ error: error.message || 'Erreur serveur' }, { status: 500 });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// POST /api/workflows - Créer un nouveau workflow
|
|
45
|
+
export async function POST(request: NextRequest) {
|
|
46
|
+
try {
|
|
47
|
+
const session = await auth.api.getSession({
|
|
48
|
+
headers: request.headers,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
if (!session) {
|
|
52
|
+
return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const body = await request.json();
|
|
56
|
+
const {
|
|
57
|
+
name,
|
|
58
|
+
description,
|
|
59
|
+
active = true,
|
|
60
|
+
triggerType,
|
|
61
|
+
triggerFromStatusId,
|
|
62
|
+
triggerToStatusId,
|
|
63
|
+
triggerTimeDays,
|
|
64
|
+
triggerTimeHours,
|
|
65
|
+
actions = [],
|
|
66
|
+
} = body;
|
|
67
|
+
|
|
68
|
+
// Validation
|
|
69
|
+
if (!name || !triggerType) {
|
|
70
|
+
return NextResponse.json(
|
|
71
|
+
{ error: 'Le nom et le type de déclencheur sont requis' },
|
|
72
|
+
{ status: 400 },
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Créer le workflow
|
|
77
|
+
const workflow = await prisma.workflow.create({
|
|
78
|
+
data: {
|
|
79
|
+
name,
|
|
80
|
+
description: description || null,
|
|
81
|
+
active,
|
|
82
|
+
userId: session.user.id,
|
|
83
|
+
triggerType,
|
|
84
|
+
triggerFromStatusId: triggerFromStatusId || null,
|
|
85
|
+
triggerToStatusId: triggerToStatusId || null,
|
|
86
|
+
triggerTimeDays: triggerTimeDays || null,
|
|
87
|
+
triggerTimeHours: triggerTimeHours || null,
|
|
88
|
+
actions: {
|
|
89
|
+
create: actions.map((action: any, index: number) => ({
|
|
90
|
+
actionType: action.actionType,
|
|
91
|
+
order: index,
|
|
92
|
+
delayDays: action.delayDays || 0,
|
|
93
|
+
delayHours: action.delayHours || 0,
|
|
94
|
+
emailTemplateId: action.emailTemplateId || null,
|
|
95
|
+
smsMessage: action.smsMessage || null,
|
|
96
|
+
newStatusId: action.newStatusId || null,
|
|
97
|
+
taskTitle: action.taskTitle || null,
|
|
98
|
+
taskDescription: action.taskDescription || null,
|
|
99
|
+
conditionOperator: action.conditionOperator || null,
|
|
100
|
+
conditionStatusId: action.conditionStatusId || null,
|
|
101
|
+
})),
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
include: {
|
|
105
|
+
actions: {
|
|
106
|
+
orderBy: { order: 'asc' },
|
|
107
|
+
include: {
|
|
108
|
+
emailTemplate: true,
|
|
109
|
+
newStatus: true,
|
|
110
|
+
conditionStatus: true,
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
triggerStatus: true,
|
|
114
|
+
triggerToStatus: true,
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
return NextResponse.json(workflow, { status: 201 });
|
|
119
|
+
} catch (error: any) {
|
|
120
|
+
console.error('Erreur lors de la création du workflow:', error);
|
|
121
|
+
return NextResponse.json({ error: error.message || 'Erreur serveur' }, { status: 500 });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
Binary file
|