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[]) { 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; 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 }; } }