2026-05-18 11:14:17 -03:00
|
|
|
import { Controller, Get, Post, Body, Delete, Param } from '@nestjs/common';
|
2026-05-28 13:37:53 -03:00
|
|
|
import { ApiBody, ApiOperation, ApiParam, ApiResponse, ApiTags } from '@nestjs/swagger';
|
2026-05-18 11:14:17 -03:00
|
|
|
import { WhatsappService } from './whatsapp.service';
|
2026-06-01 14:32:32 -03:00
|
|
|
import { AttendanceAssignmentService } from '../attendance/attendance-assignment.service';
|
2026-05-18 11:14:17 -03:00
|
|
|
|
2026-05-28 13:37:53 -03:00
|
|
|
@ApiTags('WhatsApp')
|
2026-05-18 11:14:17 -03:00
|
|
|
@Controller('whatsapp')
|
|
|
|
|
export class WhatsappController {
|
|
|
|
|
constructor(
|
|
|
|
|
private readonly whatsappService: WhatsappService,
|
2026-06-01 14:32:32 -03:00
|
|
|
private readonly assignmentService: AttendanceAssignmentService
|
2026-05-18 11:14:17 -03:00
|
|
|
) {}
|
|
|
|
|
|
2026-05-28 13:37:53 -03:00
|
|
|
@ApiOperation({
|
|
|
|
|
summary: 'Consulta status da conexao WhatsApp',
|
|
|
|
|
description: 'Retorna o estado atual da sessao WhatsApp usada pelo backend, como conectado, aguardando QR Code ou desconectado.',
|
|
|
|
|
})
|
|
|
|
|
@ApiResponse({ status: 200, description: 'Status atual da conexao retornado.' })
|
2026-05-18 11:14:17 -03:00
|
|
|
@Get('status')
|
|
|
|
|
getStatus() {
|
|
|
|
|
return { status: this.whatsappService.getStatus() };
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-28 13:37:53 -03:00
|
|
|
@ApiOperation({
|
|
|
|
|
summary: 'Lista conversas do WhatsApp',
|
|
|
|
|
description: 'Retorna os chats conhecidos pelo backend para alimentar o painel de atendimento em tempo real.',
|
|
|
|
|
})
|
|
|
|
|
@ApiResponse({ status: 200, description: 'Lista de conversas retornada.' })
|
2026-05-18 11:14:17 -03:00
|
|
|
@Get('chats')
|
|
|
|
|
async getChats() {
|
|
|
|
|
return this.whatsappService.getChats();
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-28 13:37:53 -03:00
|
|
|
@ApiOperation({
|
|
|
|
|
summary: 'Lista mensagens de uma conversa',
|
|
|
|
|
description: 'Busca o historico de mensagens de um chat especifico para abrir a conversa no painel do atendente.',
|
|
|
|
|
})
|
|
|
|
|
@ApiParam({ name: 'chatId', description: 'Identificador do chat no WhatsApp.' })
|
|
|
|
|
@ApiResponse({ status: 200, description: 'Mensagens do chat retornadas.' })
|
2026-05-18 11:14:17 -03:00
|
|
|
@Get('messages/:chatId')
|
|
|
|
|
async getChatMessages(@Param('chatId') chatId: string) {
|
|
|
|
|
return this.whatsappService.getChatMessages(chatId);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-28 13:37:53 -03:00
|
|
|
@ApiOperation({
|
|
|
|
|
summary: 'Baixa midia de uma mensagem',
|
|
|
|
|
description: 'Retorna a midia associada a uma mensagem do WhatsApp quando a mensagem possui anexo.',
|
|
|
|
|
})
|
|
|
|
|
@ApiParam({ name: 'chatId', description: 'Identificador do chat no WhatsApp.' })
|
|
|
|
|
@ApiParam({ name: 'messageId', description: 'Identificador da mensagem com midia.' })
|
|
|
|
|
@ApiResponse({ status: 200, description: 'Midia da mensagem retornada.' })
|
2026-05-18 11:14:17 -03:00
|
|
|
@Get('media/:chatId/:messageId')
|
|
|
|
|
async getMessageMedia(@Param('chatId') chatId: string, @Param('messageId') messageId: string) {
|
|
|
|
|
return this.whatsappService.getMessageMedia(chatId, messageId);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-28 13:37:53 -03:00
|
|
|
@ApiOperation({
|
|
|
|
|
summary: 'Envia mensagem no WhatsApp',
|
|
|
|
|
description: 'Envia uma mensagem ativa para um contato ou responde uma conversa existente, opcionalmente com midia em base64.',
|
|
|
|
|
})
|
|
|
|
|
@ApiBody({
|
|
|
|
|
schema: {
|
|
|
|
|
type: 'object',
|
|
|
|
|
required: ['to', 'message'],
|
|
|
|
|
properties: {
|
|
|
|
|
to: { type: 'string', example: '5511999999999@c.us' },
|
|
|
|
|
message: { type: 'string', example: 'Ola, como posso ajudar?' },
|
|
|
|
|
senderName: { type: 'string', example: 'Rafael Lopes' },
|
|
|
|
|
media: {
|
|
|
|
|
type: 'object',
|
|
|
|
|
nullable: true,
|
|
|
|
|
properties: {
|
|
|
|
|
data: { type: 'string', description: 'Arquivo em base64.' },
|
|
|
|
|
mimetype: { type: 'string', example: 'application/pdf' },
|
|
|
|
|
filename: { type: 'string', example: 'documento.pdf' },
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
@ApiResponse({ status: 201, description: 'Mensagem enviada.' })
|
2026-05-18 11:14:17 -03:00
|
|
|
@Post('send')
|
2026-05-19 15:28:23 -03:00
|
|
|
async sendMessage(@Body() body: { to: string; message: string; senderName?: string; media?: { data: string; mimetype: string; filename?: string } }) {
|
|
|
|
|
return this.whatsappService.sendMessage(body.to, body.message, body.media, body.senderName);
|
2026-05-18 11:14:17 -03:00
|
|
|
}
|
|
|
|
|
|
2026-05-28 13:37:53 -03:00
|
|
|
@ApiOperation({
|
|
|
|
|
summary: 'Inicia atendimento ativo',
|
|
|
|
|
description: 'Abre uma conversa ativa usando um template aprovado e registra o atendimento para acompanhamento no painel.',
|
|
|
|
|
})
|
|
|
|
|
@ApiBody({
|
|
|
|
|
schema: {
|
|
|
|
|
type: 'object',
|
|
|
|
|
required: ['to', 'templateId', 'userId'],
|
|
|
|
|
properties: {
|
|
|
|
|
to: { type: 'string', example: '5511999999999' },
|
|
|
|
|
templateId: { type: 'number', example: 1 },
|
|
|
|
|
userId: { type: 'number', example: 10 },
|
|
|
|
|
areaId: { type: 'number', nullable: true, example: 2 },
|
|
|
|
|
variables: { type: 'object', additionalProperties: { type: 'string', nullable: true } },
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
@ApiResponse({ status: 201, description: 'Atendimento ativo iniciado.' })
|
2026-05-20 13:56:23 -03:00
|
|
|
@Post('start-attendance')
|
|
|
|
|
async startAttendance(@Body() body: { to: string; templateId: number; userId: number; areaId?: number | null; variables?: Record<string, string | null | undefined> }) {
|
|
|
|
|
return this.whatsappService.startAttendance(body.to, body.templateId, body.userId, body.areaId, body.variables);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-28 13:37:53 -03:00
|
|
|
@ApiOperation({
|
|
|
|
|
summary: 'Assume uma conversa',
|
|
|
|
|
description: 'Vincula um chat a um atendente e, opcionalmente, a uma area de atendimento.',
|
|
|
|
|
})
|
|
|
|
|
@ApiBody({
|
|
|
|
|
schema: {
|
|
|
|
|
type: 'object',
|
|
|
|
|
required: ['chatId', 'userId'],
|
|
|
|
|
properties: {
|
|
|
|
|
chatId: { type: 'string', example: '5511999999999@c.us' },
|
|
|
|
|
userId: { type: 'string', example: '10' },
|
|
|
|
|
areaId: { type: 'string', example: '2' },
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
})
|
2026-05-18 11:14:17 -03:00
|
|
|
@Post('assign')
|
|
|
|
|
async assignChat(@Body() body: { chatId: string; userId: string; areaId?: string }) {
|
|
|
|
|
return this.assignmentService.assignChat(body.chatId, body.userId, body.areaId);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-28 13:37:53 -03:00
|
|
|
@ApiOperation({
|
|
|
|
|
summary: 'Transfere uma conversa',
|
|
|
|
|
description: 'Encaminha o chat para outra area ou responsavel e registra observacao para contextualizar o proximo atendimento.',
|
|
|
|
|
})
|
|
|
|
|
@ApiBody({
|
|
|
|
|
schema: {
|
|
|
|
|
type: 'object',
|
|
|
|
|
required: ['chatId', 'areaId'],
|
|
|
|
|
properties: {
|
|
|
|
|
chatId: { type: 'string', example: '5511999999999@c.us' },
|
|
|
|
|
areaId: { type: 'number', example: 2 },
|
|
|
|
|
userId: { type: 'number', nullable: true, example: 10 },
|
|
|
|
|
note: { type: 'string', nullable: true, example: 'Tony e um colaborador e quer saber sobre ferias.' },
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
})
|
2026-05-19 15:28:23 -03:00
|
|
|
@Post('transfer')
|
|
|
|
|
async transferChat(@Body() body: { chatId: string; areaId: number; userId?: number | null; note?: string | null }) {
|
|
|
|
|
return this.assignmentService.transferChat(body);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-28 13:37:53 -03:00
|
|
|
@ApiOperation({
|
|
|
|
|
summary: 'Libera conversa assumida',
|
|
|
|
|
description: 'Remove o vinculo de atendimento do chat, permitindo que outro atendente assuma a conversa.',
|
|
|
|
|
})
|
|
|
|
|
@ApiParam({ name: 'chatId', description: 'Identificador do chat no WhatsApp.' })
|
2026-05-18 11:14:17 -03:00
|
|
|
@Delete('release/:chatId')
|
|
|
|
|
async releaseChat(@Param('chatId') chatId: string) {
|
|
|
|
|
return this.assignmentService.releaseChat(chatId);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-28 13:37:53 -03:00
|
|
|
@ApiOperation({
|
|
|
|
|
summary: 'Fecha atendimento',
|
|
|
|
|
description: 'Finaliza o atendimento de um chat e remove o vinculo ativo com o atendente.',
|
|
|
|
|
})
|
|
|
|
|
@ApiBody({
|
|
|
|
|
schema: {
|
|
|
|
|
type: 'object',
|
|
|
|
|
required: ['chatId'],
|
|
|
|
|
properties: {
|
|
|
|
|
chatId: { type: 'string', example: '5511999999999@c.us' },
|
|
|
|
|
userId: { oneOf: [{ type: 'string' }, { type: 'number' }], nullable: true, example: 10 },
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
})
|
2026-05-26 09:08:20 -03:00
|
|
|
@Post('close')
|
|
|
|
|
async closeChat(@Body() body: { chatId: string; userId?: string | number | null }) {
|
|
|
|
|
return this.assignmentService.closeChat(body.chatId, body.userId);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-28 13:37:53 -03:00
|
|
|
@ApiOperation({
|
|
|
|
|
summary: 'Consulta atribuicao do chat',
|
|
|
|
|
description: 'Retorna qual atendente ou area esta responsavel pela conversa no momento.',
|
|
|
|
|
})
|
|
|
|
|
@ApiParam({ name: 'chatId', description: 'Identificador do chat no WhatsApp.' })
|
2026-05-18 11:14:17 -03:00
|
|
|
@Get('assignment/:chatId')
|
|
|
|
|
async getAssignment(@Param('chatId') chatId: string) {
|
|
|
|
|
return this.assignmentService.getAssignment(chatId);
|
|
|
|
|
}
|
2026-05-18 13:28:17 -03:00
|
|
|
|
2026-05-28 13:37:53 -03:00
|
|
|
@ApiOperation({
|
|
|
|
|
summary: 'Lista templates de atendimento',
|
|
|
|
|
description: 'Retorna os templates cadastrados para abertura ativa e mensagens padronizadas.',
|
|
|
|
|
})
|
2026-05-18 13:28:17 -03:00
|
|
|
@Get('templates')
|
|
|
|
|
async getTemplates() {
|
|
|
|
|
return this.whatsappService.getTemplates();
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-28 13:37:53 -03:00
|
|
|
@ApiOperation({
|
|
|
|
|
summary: 'Cria template de mensagem',
|
|
|
|
|
description: 'Cadastra um template para abertura ativa ou resposta padronizada. Pode depender de aprovacao conforme o perfil solicitante.',
|
|
|
|
|
})
|
|
|
|
|
@ApiBody({
|
|
|
|
|
schema: {
|
|
|
|
|
type: 'object',
|
|
|
|
|
required: ['name', 'content'],
|
|
|
|
|
properties: {
|
|
|
|
|
name: { type: 'string', example: 'Boas vindas' },
|
|
|
|
|
content: { type: 'string', example: 'Ola {{nome}}, tudo bem?' },
|
|
|
|
|
areaId: { type: 'number', nullable: true, example: 2 },
|
|
|
|
|
requestedByRole: { type: 'string', example: 'admin' },
|
|
|
|
|
category: { type: 'string', example: 'atendimento' },
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
})
|
2026-05-18 13:28:17 -03:00
|
|
|
@Post('templates')
|
2026-05-26 09:08:20 -03:00
|
|
|
async saveTemplate(@Body() body: { name: string; content: string; areaId?: number | null; requestedByRole?: string; category?: string }) {
|
|
|
|
|
return this.whatsappService.saveTemplate(body.name, body.content, body.areaId, body.requestedByRole, body.category);
|
2026-05-18 13:28:17 -03:00
|
|
|
}
|
|
|
|
|
|
2026-05-28 13:37:53 -03:00
|
|
|
@ApiOperation({
|
|
|
|
|
summary: 'Atualiza template de mensagem',
|
|
|
|
|
description: 'Altera nome, conteudo, area ou categoria de um template existente.',
|
|
|
|
|
})
|
|
|
|
|
@ApiParam({ name: 'id', description: 'ID do template.' })
|
2026-05-18 13:28:17 -03:00
|
|
|
@Post('templates/update/:id')
|
2026-05-26 09:08:20 -03:00
|
|
|
async updateTemplate(@Param('id') id: string, @Body() body: { name: string; content: string; areaId?: number | null; category?: string }) {
|
|
|
|
|
return this.whatsappService.updateTemplate(Number(id), body.name, body.content, body.areaId, body.category);
|
2026-05-22 10:51:44 -03:00
|
|
|
}
|
|
|
|
|
|
2026-05-28 13:37:53 -03:00
|
|
|
@ApiOperation({
|
|
|
|
|
summary: 'Aprova template pelo admin',
|
|
|
|
|
description: 'Marca um template como aprovado para uso nos fluxos de atendimento.',
|
|
|
|
|
})
|
|
|
|
|
@ApiParam({ name: 'id', description: 'ID do template.' })
|
2026-05-22 10:51:44 -03:00
|
|
|
@Post('templates/approve-admin/:id')
|
|
|
|
|
async approveTemplateByAdmin(@Param('id') id: string) {
|
|
|
|
|
return this.whatsappService.approveTemplateByAdmin(Number(id));
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-28 13:37:53 -03:00
|
|
|
@ApiOperation({
|
|
|
|
|
summary: 'Rejeita template pelo admin',
|
|
|
|
|
description: 'Marca um template como rejeitado, impedindo seu uso nos fluxos de atendimento.',
|
|
|
|
|
})
|
|
|
|
|
@ApiParam({ name: 'id', description: 'ID do template.' })
|
2026-05-22 10:51:44 -03:00
|
|
|
@Post('templates/reject-admin/:id')
|
|
|
|
|
async rejectTemplateByAdmin(@Param('id') id: string) {
|
|
|
|
|
return this.whatsappService.rejectTemplateByAdmin(Number(id));
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-28 13:37:53 -03:00
|
|
|
@ApiOperation({
|
|
|
|
|
summary: 'Remove template',
|
|
|
|
|
description: 'Exclui um template cadastrado.',
|
|
|
|
|
})
|
|
|
|
|
@ApiParam({ name: 'id', description: 'ID do template.' })
|
2026-05-22 10:51:44 -03:00
|
|
|
@Delete('templates/:id')
|
|
|
|
|
async deleteTemplate(@Param('id') id: string) {
|
|
|
|
|
return this.whatsappService.deleteTemplate(Number(id));
|
2026-05-18 13:28:17 -03:00
|
|
|
}
|
2026-05-18 11:14:17 -03:00
|
|
|
}
|