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,7 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from 'next/server';
|
|
2
2
|
import { auth } from '@/lib/auth';
|
|
3
3
|
import { prisma } from '@/lib/prisma';
|
|
4
|
+
import { checkPermission } from '@/lib/check-permission';
|
|
4
5
|
import { deleteFileFromDrive } from '@/lib/google-drive';
|
|
5
6
|
import { logFileDeleted } from '@/lib/contact-interactions';
|
|
6
7
|
|
|
@@ -18,9 +19,13 @@ export async function DELETE(
|
|
|
18
19
|
return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
|
|
19
20
|
}
|
|
20
21
|
|
|
22
|
+
const canDeleteFiles = await checkPermission('contacts.delete_files');
|
|
23
|
+
if (!canDeleteFiles) {
|
|
24
|
+
return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
|
|
25
|
+
}
|
|
26
|
+
|
|
21
27
|
const { id: contactId, fileId } = await params;
|
|
22
28
|
|
|
23
|
-
// Vérifier que le contact existe
|
|
24
29
|
const contact = await prisma.contact.findUnique({
|
|
25
30
|
where: { id: contactId },
|
|
26
31
|
});
|
|
@@ -29,7 +34,6 @@ export async function DELETE(
|
|
|
29
34
|
return NextResponse.json({ error: 'Contact non trouvé' }, { status: 404 });
|
|
30
35
|
}
|
|
31
36
|
|
|
32
|
-
// Vérifier que le fichier existe et appartient au contact
|
|
33
37
|
const file = await prisma.contactFile.findUnique({
|
|
34
38
|
where: { id: fileId },
|
|
35
39
|
});
|
|
@@ -42,26 +46,12 @@ export async function DELETE(
|
|
|
42
46
|
return NextResponse.json({ error: 'Fichier non associé à ce contact' }, { status: 403 });
|
|
43
47
|
}
|
|
44
48
|
|
|
45
|
-
// Vérifier les permissions (seul l'uploader ou un admin peut supprimer)
|
|
46
|
-
const user = await prisma.user.findUnique({
|
|
47
|
-
where: { id: session.user.id },
|
|
48
|
-
select: { role: true },
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
if (file.uploadedById !== session.user.id && user?.role !== 'ADMIN') {
|
|
52
|
-
return NextResponse.json(
|
|
53
|
-
{ error: "Vous n'avez pas la permission de supprimer ce fichier" },
|
|
54
|
-
{ status: 403 },
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
49
|
// Sauvegarder les informations du fichier avant suppression pour l'interaction
|
|
59
50
|
const fileName = file.fileName;
|
|
60
51
|
const fileSize = file.fileSize;
|
|
61
52
|
|
|
62
|
-
// Supprimer le fichier de Google Drive
|
|
53
|
+
// Supprimer le fichier de Google Drive
|
|
63
54
|
try {
|
|
64
|
-
// On passe un userId quelconque car deleteFileFromDrive utilise getAdminGoogleAccount()
|
|
65
55
|
await deleteFileFromDrive(session.user.id, file.googleDriveFileId);
|
|
66
56
|
} catch (error) {
|
|
67
57
|
console.error('Erreur lors de la suppression du fichier de Google Drive:', error);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from 'next/server';
|
|
2
2
|
import { auth } from '@/lib/auth';
|
|
3
3
|
import { prisma } from '@/lib/prisma';
|
|
4
|
+
import { checkPermission } from '@/lib/check-permission';
|
|
4
5
|
import { uploadFileToDrive, getFileInfo } from '@/lib/google-drive';
|
|
5
6
|
import { logFileUploaded, logFileReplaced } from '@/lib/contact-interactions';
|
|
6
7
|
|
|
@@ -15,6 +16,11 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
|
|
|
15
16
|
return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
|
|
16
17
|
}
|
|
17
18
|
|
|
19
|
+
const canUpload = await checkPermission('contacts.upload_files');
|
|
20
|
+
if (!canUpload) {
|
|
21
|
+
return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
|
|
22
|
+
}
|
|
23
|
+
|
|
18
24
|
const { id: contactId } = await params;
|
|
19
25
|
|
|
20
26
|
// Vérifier que le contact existe
|
|
@@ -31,22 +37,13 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
|
|
|
31
37
|
return NextResponse.json({ error: 'Contact non trouvé' }, { status: 404 });
|
|
32
38
|
}
|
|
33
39
|
|
|
34
|
-
// Vérifier
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
orderBy: { createdAt: 'asc' },
|
|
39
|
-
});
|
|
40
|
+
// Vérifier si c'est pour une transaction (via query param)
|
|
41
|
+
const { searchParams } = new URL(request.url);
|
|
42
|
+
const isForTransaction = searchParams.get('transaction') === 'true';
|
|
43
|
+
const isIdentityDocument = searchParams.get('isIdentityDocument') === 'true';
|
|
40
44
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
{
|
|
44
|
-
error:
|
|
45
|
-
'Aucun compte Google Drive configuré. Veuillez demander à un administrateur de connecter son compte Google Drive dans les paramètres.',
|
|
46
|
-
},
|
|
47
|
-
{ status: 400 },
|
|
48
|
-
);
|
|
49
|
-
}
|
|
45
|
+
// Récupérer le compte Google de l'admin (tous les utilisateurs utilisent la config admin)
|
|
46
|
+
// Note: uploadFileToDrive utilise déjà getAdminGoogleAccount, donc pas besoin de vérifier ici
|
|
50
47
|
|
|
51
48
|
// Récupérer le FormData
|
|
52
49
|
const formData = await request.formData();
|
|
@@ -80,17 +77,23 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
|
|
|
80
77
|
let contactFile;
|
|
81
78
|
|
|
82
79
|
if (existingFile) {
|
|
83
|
-
// Si le fichier existe déjà, supprimer l'ancien fichier de Google Drive
|
|
80
|
+
// Si le fichier existe déjà, supprimer l'ancien fichier de Google Drive
|
|
84
81
|
try {
|
|
85
82
|
const { deleteFileFromDrive } = await import('@/lib/google-drive');
|
|
86
|
-
await deleteFileFromDrive(
|
|
83
|
+
await deleteFileFromDrive(session.user.id, existingFile.googleDriveFileId);
|
|
87
84
|
} catch (error) {
|
|
88
85
|
console.error("Erreur lors de la suppression de l'ancien fichier:", error);
|
|
89
86
|
// On continue même si la suppression échoue
|
|
90
87
|
}
|
|
91
88
|
|
|
92
|
-
// Uploader le nouveau fichier vers Google Drive
|
|
93
|
-
const { fileId } = await uploadFileToDrive(
|
|
89
|
+
// Uploader le nouveau fichier vers Google Drive
|
|
90
|
+
const { fileId } = await uploadFileToDrive(
|
|
91
|
+
session.user.id,
|
|
92
|
+
contactId,
|
|
93
|
+
contactName,
|
|
94
|
+
file,
|
|
95
|
+
isForTransaction,
|
|
96
|
+
);
|
|
94
97
|
|
|
95
98
|
// Mettre à jour l'enregistrement existant
|
|
96
99
|
contactFile = await prisma.contactFile.update({
|
|
@@ -99,6 +102,7 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
|
|
|
99
102
|
fileSize: file.size,
|
|
100
103
|
mimeType: file.type || 'application/octet-stream',
|
|
101
104
|
googleDriveFileId: fileId,
|
|
105
|
+
isIdentityDocument,
|
|
102
106
|
uploadedById: session.user.id,
|
|
103
107
|
updatedAt: new Date(),
|
|
104
108
|
},
|
|
@@ -124,8 +128,14 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
|
|
|
124
128
|
// On continue même si l'interaction échoue
|
|
125
129
|
}
|
|
126
130
|
} else {
|
|
127
|
-
// Uploader le fichier vers Google Drive
|
|
128
|
-
const { fileId } = await uploadFileToDrive(
|
|
131
|
+
// Uploader le fichier vers Google Drive
|
|
132
|
+
const { fileId } = await uploadFileToDrive(
|
|
133
|
+
session.user.id,
|
|
134
|
+
contactId,
|
|
135
|
+
contactName,
|
|
136
|
+
file,
|
|
137
|
+
isForTransaction,
|
|
138
|
+
);
|
|
129
139
|
|
|
130
140
|
// Créer un nouvel enregistrement dans la base de données
|
|
131
141
|
contactFile = await prisma.contactFile.create({
|
|
@@ -135,6 +145,7 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
|
|
|
135
145
|
fileSize: file.size,
|
|
136
146
|
mimeType: file.type || 'application/octet-stream',
|
|
137
147
|
googleDriveFileId: fileId,
|
|
148
|
+
isIdentityDocument,
|
|
138
149
|
uploadedById: session.user.id,
|
|
139
150
|
},
|
|
140
151
|
include: {
|
|
@@ -157,15 +168,26 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
|
|
|
157
168
|
}
|
|
158
169
|
}
|
|
159
170
|
|
|
160
|
-
// Récupérer le webViewLink pour la réponse
|
|
161
|
-
const fileInfo = await getFileInfo(
|
|
171
|
+
// Récupérer le webViewLink pour la réponse
|
|
172
|
+
const fileInfo = await getFileInfo(session.user.id, contactFile.googleDriveFileId);
|
|
162
173
|
const webViewLink = fileInfo.webViewLink;
|
|
163
174
|
|
|
175
|
+
// Si c'est pour une transaction, mettre à jour automatiquement idDocumentUrl du contact
|
|
176
|
+
if (isForTransaction) {
|
|
177
|
+
await prisma.contact.update({
|
|
178
|
+
where: { id: contactId },
|
|
179
|
+
data: {
|
|
180
|
+
idDocumentUrl: webViewLink,
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
164
185
|
return NextResponse.json({
|
|
165
186
|
id: contactFile.id,
|
|
166
187
|
fileName: contactFile.fileName,
|
|
167
188
|
fileSize: contactFile.fileSize,
|
|
168
189
|
mimeType: contactFile.mimeType,
|
|
190
|
+
isIdentityDocument: contactFile.isIdentityDocument,
|
|
169
191
|
webViewLink,
|
|
170
192
|
uploadedBy: contactFile.uploadedBy,
|
|
171
193
|
createdAt: contactFile.createdAt,
|
|
@@ -173,6 +195,24 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
|
|
|
173
195
|
});
|
|
174
196
|
} catch (error: any) {
|
|
175
197
|
console.error("Erreur lors de l'upload du fichier:", error);
|
|
198
|
+
|
|
199
|
+
// Détecter les erreurs d'authentification Google (token expiré/révoqué)
|
|
200
|
+
if (
|
|
201
|
+
error.message?.includes('401') ||
|
|
202
|
+
error.message?.includes('UNAUTHENTICATED') ||
|
|
203
|
+
error.message?.includes('Invalid Credentials') ||
|
|
204
|
+
error.message?.includes('invalid_grant')
|
|
205
|
+
) {
|
|
206
|
+
return NextResponse.json(
|
|
207
|
+
{
|
|
208
|
+
error:
|
|
209
|
+
'🔒 La connexion Google Drive a expiré. Veuillez demander à un administrateur de reconnecter son compte Google dans Paramètres > Intégrations > Google Drive.',
|
|
210
|
+
},
|
|
211
|
+
{ status: 401 },
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Autres erreurs
|
|
176
216
|
return NextResponse.json(
|
|
177
217
|
{ error: error.message || "Erreur lors de l'upload du fichier" },
|
|
178
218
|
{ status: 500 },
|
|
@@ -191,6 +231,11 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
|
|
|
191
231
|
return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
|
|
192
232
|
}
|
|
193
233
|
|
|
234
|
+
const canViewFiles = await checkPermission('contacts.view_files');
|
|
235
|
+
if (!canViewFiles) {
|
|
236
|
+
return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
|
|
237
|
+
}
|
|
238
|
+
|
|
194
239
|
const { id: contactId } = await params;
|
|
195
240
|
|
|
196
241
|
// Vérifier que le contact existe
|
|
@@ -219,30 +264,23 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
|
|
|
219
264
|
},
|
|
220
265
|
});
|
|
221
266
|
|
|
222
|
-
//
|
|
223
|
-
const adminUser = await prisma.user.findFirst({
|
|
224
|
-
where: { role: 'ADMIN' },
|
|
225
|
-
include: { googleAccount: true },
|
|
226
|
-
orderBy: { createdAt: 'asc' },
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
// Pour chaque fichier, récupérer le lien de visualisation depuis Google Drive (via le compte admin)
|
|
267
|
+
// Pour chaque fichier, récupérer le lien de visualisation depuis Google Drive
|
|
230
268
|
const filesWithLinks = await Promise.all(
|
|
231
269
|
files.map(async (file) => {
|
|
232
270
|
try {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
}
|
|
271
|
+
// getFileInfo utilise déjà le compte admin
|
|
272
|
+
const fileInfo = await getFileInfo(session.user.id, file.googleDriveFileId);
|
|
273
|
+
return {
|
|
274
|
+
id: file.id,
|
|
275
|
+
fileName: file.fileName,
|
|
276
|
+
fileSize: file.fileSize,
|
|
277
|
+
mimeType: file.mimeType,
|
|
278
|
+
isIdentityDocument: file.isIdentityDocument,
|
|
279
|
+
webViewLink: fileInfo.webViewLink,
|
|
280
|
+
uploadedBy: file.uploadedBy,
|
|
281
|
+
createdAt: file.createdAt,
|
|
282
|
+
updatedAt: file.updatedAt,
|
|
283
|
+
};
|
|
246
284
|
} catch (error) {
|
|
247
285
|
console.error(
|
|
248
286
|
`Erreur lors de la récupération du lien pour le fichier ${file.id}:`,
|
|
@@ -255,6 +293,7 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
|
|
|
255
293
|
fileName: file.fileName,
|
|
256
294
|
fileSize: file.fileSize,
|
|
257
295
|
mimeType: file.mimeType,
|
|
296
|
+
isIdentityDocument: file.isIdentityDocument,
|
|
258
297
|
webViewLink: `https://drive.google.com/file/d/${file.googleDriveFileId}/view`,
|
|
259
298
|
uploadedBy: file.uploadedBy,
|
|
260
299
|
createdAt: file.createdAt,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from 'next/server';
|
|
2
2
|
import { auth } from '@/lib/auth';
|
|
3
3
|
import { prisma } from '@/lib/prisma';
|
|
4
|
+
import { checkPermission } from '@/lib/check-permission';
|
|
4
5
|
|
|
5
6
|
// GET /api/contacts/[id]/interactions - Récupérer les interactions d'un contact
|
|
6
7
|
export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
|
|
@@ -24,6 +25,24 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
|
|
|
24
25
|
return NextResponse.json({ error: 'Contact non trouvé' }, { status: 404 });
|
|
25
26
|
}
|
|
26
27
|
|
|
28
|
+
// Vérifier les permissions
|
|
29
|
+
const canViewAll = await checkPermission('contacts.view_all');
|
|
30
|
+
const canViewOwn = await checkPermission('contacts.view_own');
|
|
31
|
+
|
|
32
|
+
if (!canViewAll && !canViewOwn) {
|
|
33
|
+
return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Si l'utilisateur ne peut voir que ses propres contacts, vérifier l'assignation
|
|
37
|
+
if (!canViewAll && canViewOwn) {
|
|
38
|
+
const isAssigned =
|
|
39
|
+
contact.assignedCommercialId === session.user.id ||
|
|
40
|
+
contact.assignedTeleproId === session.user.id;
|
|
41
|
+
if (!isAssigned) {
|
|
42
|
+
return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
27
46
|
const interactions = await prisma.interaction.findMany({
|
|
28
47
|
where: { contactId: id },
|
|
29
48
|
include: {
|
|
@@ -78,6 +97,24 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
|
|
|
78
97
|
return NextResponse.json({ error: 'Contact non trouvé' }, { status: 404 });
|
|
79
98
|
}
|
|
80
99
|
|
|
100
|
+
// Vérifier les permissions d'édition
|
|
101
|
+
const canEditAll = await checkPermission('contacts.edit_all');
|
|
102
|
+
const canEditOwn = await checkPermission('contacts.edit_own');
|
|
103
|
+
|
|
104
|
+
if (!canEditAll && !canEditOwn) {
|
|
105
|
+
return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Si l'utilisateur ne peut éditer que ses propres contacts, vérifier l'assignation
|
|
109
|
+
if (!canEditAll && canEditOwn) {
|
|
110
|
+
const isAssigned =
|
|
111
|
+
contact.assignedCommercialId === session.user.id ||
|
|
112
|
+
contact.assignedTeleproId === session.user.id;
|
|
113
|
+
if (!isAssigned) {
|
|
114
|
+
return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
81
118
|
const interaction = await prisma.interaction.create({
|
|
82
119
|
data: {
|
|
83
120
|
contactId: id,
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { auth } from '@/lib/auth';
|
|
3
|
+
import { prisma } from '@/lib/prisma';
|
|
4
|
+
|
|
5
|
+
// PUT /api/contacts/[id]/kyc - Mettre à jour les informations KYC d'un contact
|
|
6
|
+
export async function PUT(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
|
|
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 { id } = await params;
|
|
17
|
+
const body = await request.json();
|
|
18
|
+
|
|
19
|
+
const { placeOfBirth, dateOfBirth, idType, idNumber, idExpiryDate, idDocumentUrl } = body;
|
|
20
|
+
|
|
21
|
+
// Vérifier que le contact existe
|
|
22
|
+
const contact = await prisma.contact.findUnique({
|
|
23
|
+
where: { id },
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
if (!contact) {
|
|
27
|
+
return NextResponse.json({ error: 'Contact non trouvé' }, { status: 404 });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Mettre à jour les informations KYC
|
|
31
|
+
const updatedContact = await prisma.contact.update({
|
|
32
|
+
where: { id },
|
|
33
|
+
data: {
|
|
34
|
+
placeOfBirth: placeOfBirth || null,
|
|
35
|
+
dateOfBirth: dateOfBirth ? new Date(dateOfBirth) : null,
|
|
36
|
+
idType: idType || null,
|
|
37
|
+
idNumber: idNumber || null,
|
|
38
|
+
idExpiryDate: idExpiryDate ? new Date(idExpiryDate) : null,
|
|
39
|
+
idDocumentUrl: idDocumentUrl || null,
|
|
40
|
+
// Réinitialiser la vérification si les informations changent
|
|
41
|
+
idVerifiedByAdmin: false,
|
|
42
|
+
},
|
|
43
|
+
include: {
|
|
44
|
+
status: true,
|
|
45
|
+
assignedCommercial: {
|
|
46
|
+
select: {
|
|
47
|
+
id: true,
|
|
48
|
+
name: true,
|
|
49
|
+
email: true,
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Mettre à jour le statut des transactions en attente de vérification
|
|
56
|
+
await prisma.transaction.updateMany({
|
|
57
|
+
where: {
|
|
58
|
+
contactId: id,
|
|
59
|
+
status: 'PENDING_ID_VERIFICATION',
|
|
60
|
+
},
|
|
61
|
+
data: {
|
|
62
|
+
status: 'PENDING_ID_VERIFICATION', // Reste en attente jusqu'à vérification admin
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
return NextResponse.json(updatedContact);
|
|
67
|
+
} catch (error: any) {
|
|
68
|
+
console.error('Erreur lors de la mise à jour des informations KYC:', error);
|
|
69
|
+
return NextResponse.json({ error: error.message || 'Erreur serveur' }, { status: 500 });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
extractMeetLink,
|
|
8
8
|
} from '@/lib/google-calendar';
|
|
9
9
|
import nodemailer from 'nodemailer';
|
|
10
|
-
import { decrypt } from '@/lib/encryption';
|
|
10
|
+
import { decrypt, encrypt } from '@/lib/encryption';
|
|
11
11
|
import { render } from '@react-email/render';
|
|
12
12
|
import { MeetConfirmationEmailTemplate } from '@/components/meet-confirmation-email-template';
|
|
13
13
|
import React from 'react';
|
|
@@ -63,14 +63,18 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
|
|
|
63
63
|
return NextResponse.json({ error: 'Contact non trouvé' }, { status: 404 });
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
//
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
66
|
+
// Récupérer le compte Google de l'utilisateur courant
|
|
67
|
+
const { getUserGoogleAccount } = await import('@/lib/google-calendar');
|
|
68
|
+
let googleAccount;
|
|
69
|
+
try {
|
|
70
|
+
googleAccount = await getUserGoogleAccount(session.user.id);
|
|
71
|
+
} catch (error: any) {
|
|
72
72
|
return NextResponse.json(
|
|
73
|
-
{
|
|
73
|
+
{
|
|
74
|
+
error:
|
|
75
|
+
error.message ||
|
|
76
|
+
'Veuillez connecter votre compte Google dans les paramètres pour utiliser Google Calendar.',
|
|
77
|
+
},
|
|
74
78
|
{ status: 400 },
|
|
75
79
|
);
|
|
76
80
|
}
|
|
@@ -89,7 +93,7 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
|
|
|
89
93
|
await prisma.userGoogleAccount.update({
|
|
90
94
|
where: { userId: session.user.id },
|
|
91
95
|
data: {
|
|
92
|
-
accessToken,
|
|
96
|
+
accessToken: encrypt(accessToken),
|
|
93
97
|
tokenExpiresAt,
|
|
94
98
|
},
|
|
95
99
|
});
|
|
@@ -112,28 +116,33 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
|
|
|
112
116
|
});
|
|
113
117
|
|
|
114
118
|
// Créer l'évènement Google Calendar avec Meet
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
119
|
+
let googleEvent;
|
|
120
|
+
try {
|
|
121
|
+
googleEvent = await createGoogleCalendarEvent(accessToken, {
|
|
122
|
+
summary: title,
|
|
123
|
+
description: description || '',
|
|
124
|
+
start: {
|
|
125
|
+
dateTime: startDate.toISOString(),
|
|
126
|
+
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
127
|
+
},
|
|
128
|
+
end: {
|
|
129
|
+
dateTime: endDate.toISOString(),
|
|
130
|
+
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
131
|
+
},
|
|
132
|
+
attendees: allAttendees.length > 0 ? allAttendees : undefined,
|
|
133
|
+
conferenceData: {
|
|
134
|
+
createRequest: {
|
|
135
|
+
requestId: `meet-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
136
|
+
conferenceSolutionKey: {
|
|
137
|
+
type: 'hangoutsMeet',
|
|
138
|
+
},
|
|
132
139
|
},
|
|
133
140
|
},
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
})
|
|
141
|
+
conferenceDataVersion: 1,
|
|
142
|
+
});
|
|
143
|
+
} catch (error: any) {
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
137
146
|
|
|
138
147
|
const meetLink = extractMeetLink(googleEvent);
|
|
139
148
|
|