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,258 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { prisma } from '@/lib/prisma';
|
|
3
|
+
import { decrypt } from '@/lib/encryption';
|
|
4
|
+
import { handleContactDuplicate } from '@/lib/contact-duplicate';
|
|
5
|
+
import { normalizePhoneNumber } from '@/lib/utils';
|
|
6
|
+
|
|
7
|
+
interface MetaLeadChange {
|
|
8
|
+
field: string;
|
|
9
|
+
value: {
|
|
10
|
+
leadgen_id: string;
|
|
11
|
+
form_id: string;
|
|
12
|
+
created_time: number;
|
|
13
|
+
page_id: string;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// GET /api/webhooks/meta-leads - Vérification du webhook Meta (subscription)
|
|
18
|
+
export async function GET(request: NextRequest) {
|
|
19
|
+
try {
|
|
20
|
+
const url = new URL(request.url);
|
|
21
|
+
const mode = url.searchParams.get('hub.mode');
|
|
22
|
+
const verifyToken = url.searchParams.get('hub.verify_token');
|
|
23
|
+
const challenge = url.searchParams.get('hub.challenge');
|
|
24
|
+
|
|
25
|
+
if (mode !== 'subscribe' || !verifyToken || !challenge) {
|
|
26
|
+
return NextResponse.json({ error: 'Requête invalide' }, { status: 400 });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Vérifier toutes les configurations actives pour trouver celle qui correspond au verifyToken
|
|
30
|
+
const configs = await prisma.metaLeadConfig.findMany({
|
|
31
|
+
where: { active: true },
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const config = configs.find((c) => c.verifyToken === verifyToken);
|
|
35
|
+
|
|
36
|
+
if (!config) {
|
|
37
|
+
return NextResponse.json({ error: 'Token de vérification invalide' }, { status: 403 });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return new Response(challenge, {
|
|
41
|
+
status: 200,
|
|
42
|
+
headers: { 'Content-Type': 'text/plain' },
|
|
43
|
+
});
|
|
44
|
+
} catch (error: any) {
|
|
45
|
+
console.error('Erreur lors de la vérification du webhook Meta Lead Ads:', error);
|
|
46
|
+
return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// POST /api/webhooks/meta-leads - Réception des leads Meta
|
|
51
|
+
export async function POST(request: NextRequest) {
|
|
52
|
+
try {
|
|
53
|
+
const body = await request.json();
|
|
54
|
+
|
|
55
|
+
if (body.object !== 'page' || !Array.isArray(body.entry)) {
|
|
56
|
+
return NextResponse.json({ received: true });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Récupérer toutes les configurations actives
|
|
60
|
+
const configs = await prisma.metaLeadConfig.findMany({
|
|
61
|
+
where: { active: true },
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
if (!configs || configs.length === 0) {
|
|
65
|
+
console.warn('Webhook Meta Lead Ads reçu mais aucune configuration active trouvée.');
|
|
66
|
+
return NextResponse.json({ received: true });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
for (const entry of body.entry) {
|
|
70
|
+
if (!Array.isArray(entry.changes)) continue;
|
|
71
|
+
|
|
72
|
+
for (const change of entry.changes as MetaLeadChange[]) {
|
|
73
|
+
if (change.field !== 'leadgen') continue;
|
|
74
|
+
|
|
75
|
+
const { leadgen_id: leadId, page_id: pageId } = change.value;
|
|
76
|
+
|
|
77
|
+
// Trouver la configuration correspondante à la page
|
|
78
|
+
const config = configs.find((c) => c.pageId === pageId);
|
|
79
|
+
|
|
80
|
+
if (!config) {
|
|
81
|
+
console.warn(
|
|
82
|
+
`Lead Meta reçu pour la page ${pageId} mais aucune configuration active trouvée pour cette page.`,
|
|
83
|
+
);
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
const accessToken = decrypt(config.accessToken);
|
|
89
|
+
|
|
90
|
+
// Récupérer les données du lead depuis l'API Graph
|
|
91
|
+
const leadResponse = await fetch(
|
|
92
|
+
`https://graph.facebook.com/v18.0/${leadId}?access_token=${encodeURIComponent(
|
|
93
|
+
accessToken,
|
|
94
|
+
)}`,
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
if (!leadResponse.ok) {
|
|
98
|
+
const errorText = await leadResponse.text();
|
|
99
|
+
console.error('Erreur lors de la récupération du lead Meta:', errorText);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const leadData = await leadResponse.json();
|
|
104
|
+
const fieldData: Array<{ name: string; values: string[] }> = leadData.field_data || [];
|
|
105
|
+
|
|
106
|
+
const getField = (name: string): string | undefined => {
|
|
107
|
+
const field = fieldData.find((f) => f.name === name);
|
|
108
|
+
return field?.values?.[0];
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
let firstName = getField('first_name') || undefined;
|
|
112
|
+
let lastName = getField('last_name') || undefined;
|
|
113
|
+
const fullName = getField('full_name') || getField('name');
|
|
114
|
+
const email = getField('email');
|
|
115
|
+
const phone = getField('phone_number') || getField('phone');
|
|
116
|
+
|
|
117
|
+
if ((!firstName || !lastName) && fullName) {
|
|
118
|
+
const parts = fullName.split(' ');
|
|
119
|
+
firstName = firstName || parts.slice(0, -1).join(' ') || parts[0];
|
|
120
|
+
lastName = lastName || parts.slice(-1).join(' ');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Le téléphone est obligatoire pour un contact dans ce CRM
|
|
124
|
+
if (!phone) {
|
|
125
|
+
console.warn(
|
|
126
|
+
'Lead Meta reçu sans numéro de téléphone, impossible de créer le contact. Lead ID:',
|
|
127
|
+
leadId,
|
|
128
|
+
);
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Normaliser le numéro de téléphone
|
|
133
|
+
const normalizedPhone = normalizePhoneNumber(phone);
|
|
134
|
+
|
|
135
|
+
// Déterminer l'utilisateur pour createdById (nécessaire pour créer le contact)
|
|
136
|
+
let createdById = config.defaultAssignedUserId || null;
|
|
137
|
+
if (!createdById) {
|
|
138
|
+
const adminUser = await prisma.user.findFirst({
|
|
139
|
+
where: { role: 'ADMIN' },
|
|
140
|
+
orderBy: { createdAt: 'asc' },
|
|
141
|
+
});
|
|
142
|
+
if (adminUser) {
|
|
143
|
+
createdById = adminUser.id;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (!createdById) {
|
|
148
|
+
console.warn(
|
|
149
|
+
'Lead Meta reçu mais aucun utilisateur pour créer le contact trouvé. Lead ID:',
|
|
150
|
+
leadId,
|
|
151
|
+
);
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Déterminer l'assignation selon le rôle de l'utilisateur par défaut
|
|
156
|
+
let assignedCommercialId: string | null = null;
|
|
157
|
+
let assignedTeleproId: string | null = null;
|
|
158
|
+
|
|
159
|
+
if (config.defaultAssignedUserId) {
|
|
160
|
+
const defaultUser = await prisma.user.findUnique({
|
|
161
|
+
where: { id: config.defaultAssignedUserId },
|
|
162
|
+
select: { role: true },
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
if (defaultUser) {
|
|
166
|
+
if (
|
|
167
|
+
defaultUser.role === 'COMMERCIAL' ||
|
|
168
|
+
defaultUser.role === 'ADMIN' ||
|
|
169
|
+
defaultUser.role === 'MANAGER'
|
|
170
|
+
) {
|
|
171
|
+
assignedCommercialId = config.defaultAssignedUserId;
|
|
172
|
+
} else if (defaultUser.role === 'TELEPRO') {
|
|
173
|
+
assignedTeleproId = config.defaultAssignedUserId;
|
|
174
|
+
}
|
|
175
|
+
// Sinon, on ne assigne pas (null pour les deux)
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Vérifier si c'est un doublon (nom, prénom ET email)
|
|
180
|
+
const duplicateContactId = await handleContactDuplicate(
|
|
181
|
+
firstName,
|
|
182
|
+
lastName,
|
|
183
|
+
email,
|
|
184
|
+
`Meta Lead Ads - ${config.name}`,
|
|
185
|
+
createdById,
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
let contact;
|
|
189
|
+
if (duplicateContactId) {
|
|
190
|
+
// C'est un doublon, récupérer le contact existant
|
|
191
|
+
contact = await prisma.contact.findUnique({
|
|
192
|
+
where: { id: duplicateContactId },
|
|
193
|
+
});
|
|
194
|
+
} else {
|
|
195
|
+
// Vérifier si un contact existe déjà (par téléphone uniquement)
|
|
196
|
+
contact = await prisma.contact.findFirst({
|
|
197
|
+
where: { phone: normalizedPhone },
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
if (!contact) {
|
|
201
|
+
contact = await prisma.contact.create({
|
|
202
|
+
data: {
|
|
203
|
+
firstName: firstName || null,
|
|
204
|
+
lastName: lastName || null,
|
|
205
|
+
email: email ? email.toLowerCase() : null,
|
|
206
|
+
phone: normalizedPhone,
|
|
207
|
+
origin: `Meta Lead Ads - ${config.name}`,
|
|
208
|
+
statusId: config.defaultStatusId || null,
|
|
209
|
+
assignedCommercialId: assignedCommercialId,
|
|
210
|
+
assignedTeleproId: assignedTeleproId,
|
|
211
|
+
createdById: createdById,
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
} else {
|
|
215
|
+
// Mettre à jour quelques infos si manquantes
|
|
216
|
+
await prisma.contact.update({
|
|
217
|
+
where: { id: contact.id },
|
|
218
|
+
data: {
|
|
219
|
+
firstName: contact.firstName || firstName || null,
|
|
220
|
+
lastName: contact.lastName || lastName || null,
|
|
221
|
+
email: contact.email || (email ? email.toLowerCase() : null),
|
|
222
|
+
origin: contact.origin || `Meta Lead Ads - ${config.name}`,
|
|
223
|
+
statusId: contact.statusId || config.defaultStatusId || null,
|
|
224
|
+
// Ne pas écraser les assignations existantes
|
|
225
|
+
assignedCommercialId: contact.assignedCommercialId || assignedCommercialId,
|
|
226
|
+
assignedTeleproId: contact.assignedTeleproId || assignedTeleproId,
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Créer une interaction "Lead Meta"
|
|
233
|
+
if (contact) {
|
|
234
|
+
await prisma.interaction.create({
|
|
235
|
+
data: {
|
|
236
|
+
contactId: contact.id,
|
|
237
|
+
type: 'NOTE',
|
|
238
|
+
title: `Lead Meta Lead Ads - ${config.name}`,
|
|
239
|
+
content: `Lead importé automatiquement depuis Meta Lead Ads (${config.name}, formulaire: ${
|
|
240
|
+
change.value.form_id
|
|
241
|
+
}).`,
|
|
242
|
+
userId: createdById,
|
|
243
|
+
date: new Date(change.value.created_time * 1000),
|
|
244
|
+
},
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
} catch (err: any) {
|
|
248
|
+
console.error('Erreur lors du traitement du lead Meta:', err);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return NextResponse.json({ received: true });
|
|
254
|
+
} catch (error: any) {
|
|
255
|
+
console.error('Erreur lors du traitement du webhook Meta Lead Ads:', error);
|
|
256
|
+
return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 });
|
|
257
|
+
}
|
|
258
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { auth } from '@/lib/auth';
|
|
3
|
+
import { prisma } from '@/lib/prisma';
|
|
4
|
+
|
|
5
|
+
// GET /api/workflows/[id] - Récupérer un workflow spécifique
|
|
6
|
+
export async function GET(
|
|
7
|
+
request: NextRequest,
|
|
8
|
+
{ params }: { params: Promise<{ id: string }> },
|
|
9
|
+
) {
|
|
10
|
+
try {
|
|
11
|
+
const session = await auth.api.getSession({
|
|
12
|
+
headers: request.headers,
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
if (!session) {
|
|
16
|
+
return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const { id } = await params;
|
|
20
|
+
|
|
21
|
+
const workflow = await prisma.workflow.findFirst({
|
|
22
|
+
where: {
|
|
23
|
+
id,
|
|
24
|
+
userId: session.user.id,
|
|
25
|
+
},
|
|
26
|
+
include: {
|
|
27
|
+
actions: {
|
|
28
|
+
orderBy: { order: 'asc' },
|
|
29
|
+
include: {
|
|
30
|
+
emailTemplate: true,
|
|
31
|
+
newStatus: true,
|
|
32
|
+
conditionStatus: true,
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
triggerStatus: true,
|
|
36
|
+
triggerToStatus: true,
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
if (!workflow) {
|
|
41
|
+
return NextResponse.json({ error: 'Workflow non trouvé' }, { status: 404 });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return NextResponse.json(workflow);
|
|
45
|
+
} catch (error: any) {
|
|
46
|
+
console.error('Erreur lors de la récupération du workflow:', error);
|
|
47
|
+
return NextResponse.json({ error: error.message || 'Erreur serveur' }, { status: 500 });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// PUT /api/workflows/[id] - Mettre à jour un workflow
|
|
52
|
+
export async function PUT(
|
|
53
|
+
request: NextRequest,
|
|
54
|
+
{ params }: { params: Promise<{ id: string }> },
|
|
55
|
+
) {
|
|
56
|
+
try {
|
|
57
|
+
const session = await auth.api.getSession({
|
|
58
|
+
headers: request.headers,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
if (!session) {
|
|
62
|
+
return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const { id } = await params;
|
|
66
|
+
const body = await request.json();
|
|
67
|
+
const {
|
|
68
|
+
name,
|
|
69
|
+
description,
|
|
70
|
+
active,
|
|
71
|
+
triggerType,
|
|
72
|
+
triggerFromStatusId,
|
|
73
|
+
triggerToStatusId,
|
|
74
|
+
triggerTimeDays,
|
|
75
|
+
triggerTimeHours,
|
|
76
|
+
actions = [],
|
|
77
|
+
} = body;
|
|
78
|
+
|
|
79
|
+
// Vérifier que le workflow appartient à l'utilisateur
|
|
80
|
+
const existingWorkflow = await prisma.workflow.findFirst({
|
|
81
|
+
where: {
|
|
82
|
+
id,
|
|
83
|
+
userId: session.user.id,
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
if (!existingWorkflow) {
|
|
88
|
+
return NextResponse.json({ error: 'Workflow non trouvé' }, { status: 404 });
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Validation
|
|
92
|
+
if (!name || !triggerType) {
|
|
93
|
+
return NextResponse.json(
|
|
94
|
+
{ error: 'Le nom et le type de déclencheur sont requis' },
|
|
95
|
+
{ status: 400 },
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Supprimer les anciennes actions
|
|
100
|
+
await prisma.workflowAction.deleteMany({
|
|
101
|
+
where: { workflowId: id },
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// Mettre à jour le workflow
|
|
105
|
+
const workflow = await prisma.workflow.update({
|
|
106
|
+
where: { id },
|
|
107
|
+
data: {
|
|
108
|
+
name,
|
|
109
|
+
description: description || null,
|
|
110
|
+
active: active !== undefined ? active : existingWorkflow.active,
|
|
111
|
+
triggerType,
|
|
112
|
+
triggerFromStatusId: triggerFromStatusId || null,
|
|
113
|
+
triggerToStatusId: triggerToStatusId || null,
|
|
114
|
+
triggerTimeDays: triggerTimeDays || null,
|
|
115
|
+
triggerTimeHours: triggerTimeHours || null,
|
|
116
|
+
actions: {
|
|
117
|
+
create: actions.map((action: any, index: number) => ({
|
|
118
|
+
actionType: action.actionType,
|
|
119
|
+
order: index,
|
|
120
|
+
delayDays: action.delayDays || 0,
|
|
121
|
+
delayHours: action.delayHours || 0,
|
|
122
|
+
emailTemplateId: action.emailTemplateId || null,
|
|
123
|
+
smsMessage: action.smsMessage || null,
|
|
124
|
+
newStatusId: action.newStatusId || null,
|
|
125
|
+
taskTitle: action.taskTitle || null,
|
|
126
|
+
taskDescription: action.taskDescription || null,
|
|
127
|
+
conditionOperator: action.conditionOperator || null,
|
|
128
|
+
conditionStatusId: action.conditionStatusId || null,
|
|
129
|
+
})),
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
include: {
|
|
133
|
+
actions: {
|
|
134
|
+
orderBy: { order: 'asc' },
|
|
135
|
+
include: {
|
|
136
|
+
emailTemplate: true,
|
|
137
|
+
newStatus: true,
|
|
138
|
+
conditionStatus: true,
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
triggerStatus: true,
|
|
142
|
+
triggerToStatus: true,
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
return NextResponse.json(workflow);
|
|
147
|
+
} catch (error: any) {
|
|
148
|
+
console.error('Erreur lors de la mise à jour du workflow:', error);
|
|
149
|
+
return NextResponse.json({ error: error.message || 'Erreur serveur' }, { status: 500 });
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// DELETE /api/workflows/[id] - Supprimer un workflow
|
|
154
|
+
export async function DELETE(
|
|
155
|
+
request: NextRequest,
|
|
156
|
+
{ params }: { params: Promise<{ id: string }> },
|
|
157
|
+
) {
|
|
158
|
+
try {
|
|
159
|
+
const session = await auth.api.getSession({
|
|
160
|
+
headers: request.headers,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
if (!session) {
|
|
164
|
+
return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const { id } = await params;
|
|
168
|
+
|
|
169
|
+
// Vérifier que le workflow appartient à l'utilisateur
|
|
170
|
+
const workflow = await prisma.workflow.findFirst({
|
|
171
|
+
where: {
|
|
172
|
+
id,
|
|
173
|
+
userId: session.user.id,
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
if (!workflow) {
|
|
178
|
+
return NextResponse.json({ error: 'Workflow non trouvé' }, { status: 404 });
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Supprimer le workflow (les actions seront supprimées en cascade)
|
|
182
|
+
await prisma.workflow.delete({
|
|
183
|
+
where: { id },
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
return NextResponse.json({ success: true });
|
|
187
|
+
} catch (error: any) {
|
|
188
|
+
console.error('Erreur lors de la suppression du workflow:', error);
|
|
189
|
+
return NextResponse.json({ error: error.message || 'Erreur serveur' }, { status: 500 });
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|