File size: 4,846 Bytes
6dd9bad
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99f6bca
6dd9bad
99f6bca
 
 
 
 
 
 
a8e18d6
6dd9bad
 
 
 
 
 
 
97f541f
 
 
 
 
 
 
 
 
 
 
 
6dd9bad
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2a3e2a1
 
 
 
 
 
6dd9bad
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
import { prisma } from './prisma';
import { whatsappQueue } from './queue';
import { ContactService } from './ContactService';

export class CRMService {
    static async importFromXlsx(organizationId: string, fileBuffer: Buffer, listName: string | null) {
        const XLSX = await import('xlsx');
        const workbook = XLSX.read(fileBuffer, { type: 'buffer' });
        const sheetName = workbook.SheetNames[0];
        const rows = XLSX.utils.sheet_to_json(workbook.Sheets[sheetName]);

        const finalListName = listName || `Liste du ${new Date().toLocaleDateString('fr-FR')}`;

        const broadcastList = await prisma.broadcastList.create({
            data: { name: finalListName, organizationId }
        });

        const results = { created: 0, updated: 0, errors: 0 };
        for (const row of rows as Record<string, unknown>[]) {
            try {
                const batch = await ContactService.bulkImport(organizationId, [row], broadcastList.id);
                results.created += batch.created;
                results.errors += batch.errors;
            } catch (err) {
                results.errors++;
            }
        }

        return { listId: broadcastList.id, listName: finalListName, results };
    }

    static async listContacts(organizationId: string, filterTags?: string[]) {
        return prisma.contact.findMany({
            where: {
                organizationId,
                ...(filterTags && filterTags.length > 0
                    ? { tags: { hasSome: filterTags } }
                    : {}),
            },
            orderBy: { createdAt: 'desc' },
            take: 1000,
        });
    }

    static async deleteContact(organizationId: string, contactId: string) {
        return prisma.contact.delete({ where: { id: contactId, organizationId } });
    }

    static async listMessages(organizationId: string, limit: number = 50, skip: number = 0) {
        const [messages, total] = await Promise.all([
            prisma.message.findMany({
                where: { organizationId },
                include: { contact: true },
                orderBy: { createdAt: 'desc' },
                take: Math.min(limit, 100),
                skip,
            }),
            prisma.message.count({ where: { organizationId } }),
        ]);
        return { data: messages, total, limit, skip };
    }

    static async replyToContact(organizationId: string, contactId: string, content: string) {
        const newMessage = await prisma.message.create({
            data: {
                organizationId,
                contactId,
                content,
                direction: 'OUTBOUND',
                channel: 'WHATSAPP'
            },
            include: { contact: true }
        });

        await whatsappQueue.add('send-direct-message', {
            messageId: newMessage.id,
            contactId,
            content,
            organizationId
        });

        return newMessage;
    }

    static async getCampaignHistory(organizationId: string, page: number, limit: number, status?: string) {
        const skip = (page - 1) * limit;
        const VALID_STATUSES = ['SENT', 'DELIVERED', 'READ', 'FAILED'] as const;
        type CampaignStatus = typeof VALID_STATUSES[number];
        const validStatus = status && (VALID_STATUSES as readonly string[]).includes(status)
            ? (status as CampaignStatus)
            : undefined;
        const where = { organizationId, ...(validStatus ? { status: validStatus } : {}) };

        const [records, total, statusCounts] = await Promise.all([
            prisma.campaignHistory.findMany({
                where,
                orderBy: { sentAt: 'desc' },
                skip,
                take: limit,
                include: { contact: { select: { name: true, phoneNumber: true } } }
            }),
            prisma.campaignHistory.count({ where }),
            prisma.campaignHistory.groupBy({
                by: ['status'],
                where: { organizationId },
                _count: { _all: true }
            })
        ]);

        const stats = { SENT: 0, DELIVERED: 0, READ: 0, FAILED: 0 } as Record<string, number>;
        for (const row of statusCounts) stats[row.status] = row._count._all;

        return { records, total, page, limit, stats };
    }

    static async bulkImportJson(organizationId: string, contacts: any[], listName?: string) {
        const finalListName = listName || `Import du ${new Date().toLocaleDateString('fr-FR')}`;

        const broadcastList = await prisma.broadcastList.create({
            data: { name: finalListName, organizationId }
        });

        const results = await ContactService.bulkImport(organizationId, contacts, broadcastList.id);
        return { listId: broadcastList.id, listName: finalListName, results };
    }
}