| 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 }; |
| } |
| } |
|
|