diff --git a/TEST_SERVER1.7z b/TEST_SERVER1.7z new file mode 100644 index 0000000..e69de29 diff --git a/TEST_SERVER1/api/app.js b/TEST_SERVER1/api/app.js index 7d7c048..c84b124 100644 --- a/TEST_SERVER1/api/app.js +++ b/TEST_SERVER1/api/app.js @@ -211,7 +211,8 @@ app.post('/api/session', async function (req, res) { session_num = numbered_sessions.map((e) => parseInt((e.name.split('->')[e.name.split('->').length - 1]).trim().match(/\d+/)[0])) - console.log('session_num', session_num) + console.log('session_num', session_num) + } let index = 1; diff --git a/TEST_SERVER1/whats/app.js b/TEST_SERVER1/whats/app.js index ae61750..4ccffe4 100644 --- a/TEST_SERVER1/whats/app.js +++ b/TEST_SERVER1/whats/app.js @@ -19,8 +19,8 @@ const express = require('express'); const FormData = require('form-data'); // const { MessageMedia } = require('./node_modules/whatsapp-web.js/src/structures'); -let whatsappWebGlobalPath = path.join(process.env.NODE_PATH, 'whatsapp-web.js', '/src/structures'); -whatsappWebGlobalPath = whatsappWebGlobalPath.replace(':','') +let whatsappWebGlobalPath = path.join(process.env.NODE_PATH, 'whatsapp-web.js', '/src/structures'); +whatsappWebGlobalPath = whatsappWebGlobalPath.replace(':', '') console.log('whatsappWebGlobalPath: ', whatsappWebGlobalPath) console.log('process.env.NODE_PATH: ', process.env.NODE_PATH) @@ -178,17 +178,22 @@ client.on("qr", async qr => { asking_qrcode = true + await new Promise((resolve, reject) => { - dbcc.query("UPDATE Whatsapps SET qrcode = ?, status = ?, retries = ? where id = ?", [qr, 'qrcode', 0, process.env.WHATSAPP_ID], + dbcc.query("UPDATE Whatsapps SET qrcode = ?, status = ?, retries = ? where id = ?", [qr, 'qrcode', 0, process.env.WHATSAPP_ID], - function (err, result) { - if (err) - console.log("ERROR: " + err); + function (err, result) { - // else - // console.log('myslq result: ', result); - }); + if (err) { + console.log("ERROR: " + err); + reject(err) + } + else { + resolve(result) + } + }); + }) let url = process.env.CLIENT_URL + '/whatsapp/connection/qrcode' @@ -271,15 +276,25 @@ client.on("ready", async () => { - dbcc.query("UPDATE Whatsapps SET qrcode = ?, status = ?, retries = ?, number = ? where id = ?", ["", 'CONNECTED', 0, client.info["wid"]["user"], process.env.WHATSAPP_ID], + await new Promise((resolve, reject) => { + + dbcc.query("UPDATE Whatsapps SET qrcode = ?, status = ?, retries = ?, number = ? where id = ?", ["", 'CONNECTED', 0, client.info["wid"]["user"], process.env.WHATSAPP_ID], + + function (err, result) { + + if (err) { + console.log("ERROR: " + err); + reject(err) + } + else { + resolve(result) + } + + }); + + }) - function (err, result) { - if (err) - console.log("ERROR: " + err); - // else - // console.log('myslq result: ', result); - }); let url = process.env.CLIENT_URL + '/whatsapp/connection/qrcode' @@ -384,7 +399,7 @@ client.on("message_ack", async (msg, ack) => { }); socketIo.on('send_message', async data => { - + console.log('#') console.log('--------------> send_message from number: ', mobileuid); console.log('--------------> send_message to number: ', data.msg.number); @@ -400,7 +415,7 @@ socketIo.on('send_message', async data => { socketIo.on('send_media', async data => { - + console.log('#') console.log('--------------> send_message from number: ', mobileuid); console.log('--------------> send_message to number: ', data.msg.number); @@ -414,7 +429,7 @@ socketIo.on('send_media', async data => { if (media && !media.filename) media.filename = data.msg.media.filename - + const sentMessage = await client.sendMessage(data.msg.number, media, { sendAudioAsVoice: data.msg.sendAudioAsVoice }); // const fullFilename = process.cwd() + process.env.MEDIA_DOWNLOAD_IN + data.msg.media.filename; @@ -590,6 +605,37 @@ app.post('/api/restore', async (req, res) => { res.status(200).json({ message: "ok" }); }) +app.post('/api/sendSeen', async (req, res) => { + + let stat + + const { number } = req.body + + try { + + stat = await client.getState(); + + // await syncUnreadMessages(client) + + const wbotChat = await client.getChatById(number); + + wbotChat.sendSeen(); + + // const chatMessages = await wbotChat.fetchMessages({ limit: 100 }); + + // console.log('=============> wbotChat: ', chatMessages) + + } catch (err) { + + let terr = err.message; + + stat = (terr.search('Session closed') > -1 ? 'SESSIONCLOSED' : 'UNKNOWN') + + } + + res.status(200).json({ message: "ok" }); +}) + app.get('/api/connection/status', async (req, res) => { let stat @@ -621,6 +667,8 @@ const syncUnreadMessages = async (wbot) => { /* eslint-disable no-await-in-loop */ for (const chat of chats) { + // console.log('chat: ', chat) + if (chat.unreadCount > 0) { const unreadMessages = await chat.fetchMessages({ @@ -685,14 +733,13 @@ const getWbotMessage = async (messageId, number, limit,) => { async function whatsappMonitor(newState, omnihit_url, data) { - dbcc.query("UPDATE Whatsapps SET status = ? where id = ?", [newState, process.env.WHATSAPP_ID], - function (err, result) { - if (err) - console.log("ERROR: " + err); + const whatsapp = await whatsappUpdateStatus(newState) - // else - // console.log('myslq result: ', result); - }); + if (whatsapp && whatsapp.affectedRows) { + console.log('whatsapp status update affectedRows: ', whatsapp.affectedRows) + } + + // console.log(' whatsappwhatsappwhatsappwhatsapp: ', whatsapp) try { @@ -702,6 +749,23 @@ async function whatsappMonitor(newState, omnihit_url, data) { } } +async function whatsappUpdateStatus(newState) { + return await new Promise((resolve, reject) => { + + dbcc.query("UPDATE Whatsapps SET status = ? where id = ?", [newState, process.env.WHATSAPP_ID], + function (err, result) { + if (err) { + console.log("ERROR: " + err); + reject(err); + } + else { + resolve(result); + } + }); + + }); +} + async function handleMessage(msg) { console.log('Entrou no message_create'); @@ -709,7 +773,7 @@ async function handleMessage(msg) { let msgContact = null; let media = null; - if (msg.fromMe) { + if (msg.fromMe) { msgContact = await client.getContactById(msg.to); @@ -740,7 +804,7 @@ async function handleMessage(msg) { quotedMsg: quotedMsg ? quotedMsg.id.id : null, media: media }; - + socketIo.emit("message_create", data); @@ -868,6 +932,14 @@ async function monitor() { console.log(`WHATSAPP_ID: ${process.env.WHATSAPP_ID} | CLIENT MOBILEUID: ${mobileuid} | NAME: ${process.env.MOBILENAME} | ENV MOBILEUID: ${process.env.MOBILEUID} | STATUS: ${stat}`) + if (stat && stat === 'CONNECTED') { + + const result = await whatsappUpdateStatus('CONNECTED') + + if (result) + console.log(`Update status to CONNECTED WHATSAPP_ID: ${process.env.WHATSAPP_ID} => result.affectedRows: ${result.affectedRows}`) + + } } catch (error) { //new Date(new Date() + 'UTC') diff --git a/backend/src/controllers/ContactController.ts b/backend/src/controllers/ContactController.ts index 5f53abb..ce4363f 100644 --- a/backend/src/controllers/ContactController.ts +++ b/backend/src/controllers/ContactController.ts @@ -107,6 +107,7 @@ export const store = async (req: Request, res: Response): Promise => { number: Yup.string() .required() .matches(/^\d+$/, "Invalid number format. Only numbers is allowed.") + .matches(/^55\d+$/, "The number must start with 55.") }); try { @@ -173,10 +174,9 @@ export const update = async ( const schema = Yup.object().shape({ name: Yup.string(), - number: Yup.string().matches( - /^\d+$/, - "Invalid number format. Only numbers is allowed." - ) + number: Yup.string() + .matches(/^\d+$/,"Invalid number format. Only numbers is allowed.") + .matches(/^55\d+$/, "The number must start with 55.") }); try { diff --git a/backend/src/controllers/HitController.ts b/backend/src/controllers/HitController.ts index f2e9c7b..53faacb 100644 --- a/backend/src/controllers/HitController.ts +++ b/backend/src/controllers/HitController.ts @@ -14,217 +14,139 @@ import SendWhatsAppMessage from "../services/WbotServices/SendWhatsAppMessage"; import { Op, where, Sequelize } from "sequelize"; import ShowTicketServiceByContactId from "../services/TicketServices/ShowTicketServiceByContactId"; import hitPortalMonitoring from "../helpers/HitPortalMonitoring"; +import { sendDialogflowAwswer, verifyContact } from "../services/WbotServices/wbotMessageListener"; +import CheckIsValidContact from "../services/WbotServices/CheckIsValidContact"; +import Contact from "../models/Contact"; +import GetProfilePicUrl from "../services/WbotServices/GetProfilePicUrl"; +import CreateContactService from "../services/ContactServices/CreateContactService"; +import { resourceUsage } from "process"; +import FindOrCreateTicketServiceBot from "../services/TicketServices/FindOrCreateTicketServiceBot"; + // type IndexQuery = { -// centro_custo: string; +// cod_web: string; // }; export const hit = async (req: Request, res: Response): Promise => { // const { - // centro_custo, + // cod_web, // } = req.body as IndexQuery; console.log('req.boy: ', req.body) - console.log('req.boy: ', req.body['centro_custo']) - console.log('ACTION: ', req.body['action']) + console.log("req.body['cod_web']: ", req.body['cod_web']) + console.log("req.body['action']: ", req.body['action']) + console.log("req.body['phone']: ", req.body['phone']) + console.log("req.body['name']: ", req.body['name']) + if (req.headers["auth"] === '0424bd59b807674191e7d77572075f33') { - let contact = null + let phone = req.body['phone'] + let name = req.body['name'] - try { + if (!phone) return res.status(200).json({ "message": "Ok" }); - contact = await ContactByCustomField(req.body['centro_custo']) + const regex = /^55/; - } catch (error) { + phone = phone.match(/\d+/g).join(''); - console.log('There was an error on try get centro_custo info: ', error) + if (!regex.test(phone)) { + phone = '55' + phone; + } + if (phone.length < 7) return res.status(200).json({ "message": "Ok" }); + + let validNumber = await CheckIsValidContact(phone); + + if (!validNumber) { + return res.status(200).json({ "message": "Ok" }); + } + + validNumber = req.body['cod_web']==='0001' ? validNumber : '5517988325936' + + let contact = await Contact.findOne({ where: { number: validNumber } }); + // let contact = await Contact.findOne({ where: { number: '5517988325936' } }); + + if (!contact) { + + const profilePicUrl = await GetProfilePicUrl(validNumber); + + contact = await CreateContactService({ + name: name || phone, + number: validNumber, + profilePicUrl: profilePicUrl, + }); + + const io = getIO(); + io.emit("contact", { + action: "create", + contact + }); + + } + + + let response: any = await hitPortalMonitoring({ + 'params[n_chamado_web]': req.body['n_chamado_web'], + 'method': 'omnihit.consultachamado', + }) + + if (!response || response.length == 0 || !contact) { + console.log('Empty result from hit portal monitoring to n_chamado_web: ', req.body['n_chamado_web']) + return res.status(200).json({ "message": "Ok" }); + } + + const botInfo = await BotIsOnQueue('botqueue') + + let ticket: any = await ShowTicketServiceByContactId(contact.id) + + if (!ticket.dataValues.id) { + + const defaultWhatsapp = await GetDefaultWhatsApp(); + + let ticket_obj: any = await FindOrCreateTicketServiceBot( + contact, + defaultWhatsapp.id!, + 0, + ); + + ticket = ticket_obj.ticket } - if (req.body['action'] === 'atdfechou') { + if (ticket.id && ticket.status == 'pending') { - console.log('FECHOU') - - try { - - console.log('atdfechou ----------> THE CONTACT: ', contact) - - if (contact) { - - let data = req.body - let str = '' - let str2 = '' - - // const exclude = ["action", "centro_custo", "n_chamado_web", "historico"]; - - // for (const key in data) { - - // if (exclude.includes(`${key}`)) { - // continue - // } - - // str += `${key.replace('hostname', 'cliente')}: ${data[key]}\n` - - // } - - str = `*Cliente*: ${contact['contact.name']}` - - let historico = data['historico'] - - for (const key in historico) { - - const hist = Object.keys(historico[key]); - - hist.forEach((keys, index) => { - - str2 += `*${keys}*: ${historico[key][keys]}\n` - - }); - - str2 += '\n' - - } - - console.log('--------------> str: ', str) - console.log('--------------> str2: ', str2) - - const botInfo = await BotIsOnQueue('botqueue') - - let ticket = await ShowTicketServiceByContactId(contact['contact.id']) - - if (ticket.id && ticket.status == 'pending') { - - await sendMessageHitMonitoring(`*Olá. Somos a TI Espaçolaser.*\nO chamado da sua loja ${contact['contact.name']} foi fechado pela operadora. Abaixo seguem informações sobre o incidente.\n\n*Situação do chamado na Operadora*\n\n*Incidente:*\n\n ${str}\n\n*Atualizações*:\n\n${str2}`, ticket); - - } - else if (!ticket.id) { - - ticket = await CreateTicketService({ contactId: contact['contact.id'], status: 'open', userId: botInfo.userIdBot }); - - console.log('botInfo.botQueueId: ', botInfo.botQueueId) - - await UpdateTicketService({ ticketData: { queueId: botInfo.botQueueId }, ticketId: ticket.id }); - - await sendMessageHitMonitoring(`*Olá. Somos a TI Espaçolaser.*\nO chamado da sua loja ${contact['contact.name']} foi fechado pela operadora. Abaixo seguem informações sobre o incidente.\n\n*Situação do chamado na Operadora*\n\n*Incidente:*\n\n ${str}\n\n*Atualizações*:\n\n${str2}`, ticket); - - - } - - } - - } catch (error) { - - console.log(`Error on try sending the monitor message closed: `, error) - - } - } - else if (req.body['action'] === 'atdatualizou') { - - console.log('status: atdatualizou --------------> contact: ', contact) - - if (contact) { - - try { - - let response = await hitPortalMonitoring(req.body['centro_custo']) - - if (!response || response.length == 0) { - console.log('Empty result from hit portal monitoring. Centro_de_custo: ', req.body['centro_custo']) - return res.status(200).json({ "message": "Ok" }); - } - - const botInfo = await BotIsOnQueue('botqueue') - - let ticket = await ShowTicketServiceByContactId(contact['contact.id']) - - if (ticket.id && ticket.status == 'pending') { - - await sendMessageHitMonitoring(`*Olá. Somos a TI Espaçolaser.*\nAtualização do chamado para sua loja ${contact['contact.name']}. Abaixo seguem informações sobre o incidente para que possam acompanhar.\n\n*Situação do chamado na Operadora*\n\n*Incidente*:\n\n ${response[0].header}\n${response[0].body}`, ticket); - - } - else if (!ticket.id) { - - ticket = await CreateTicketService({ contactId: contact['contact.id'], status: 'open', userId: botInfo.userIdBot }); - - console.log('botInfo.botQueueId: ', botInfo.botQueueId) - - await UpdateTicketService({ ticketData: { queueId: botInfo.botQueueId }, ticketId: ticket.id }); - - await sendMessageHitMonitoring(`*Olá. Somos a TI Espaçolaser.*\nAtualização do chamado para sua loja ${contact['contact.name']}. Abaixo seguem informações sobre o incidente para que possam acompanhar.\n\n*Situação do chamado na Operadora*\n\n*Incidente*:\n\n ${response[0].header}\n${response[0].body}`, ticket); - - } - - - } catch (error) { - - console.log(`Error on try sending the message monitor: `, error) - - } - - } + await sendMessageHitMonitoring(response, ticket); } - else { + else if (ticket.id && ticket.userId == botInfo.userIdBot) { - console.log('status: atdatabriu --------------> contact: ', contact) + let queue = await ShowQueueService(botInfo.botQueueId); - if (contact) { + await UpdateTicketService({ ticketData: { queueId: queue.id }, ticketId: ticket.id }); - try { + ticket = await ShowTicketService(ticket.id); - let response = await hitPortalMonitoring(req.body['centro_custo']) + let msg: any = { type: 'chat', from: `${contact.number}@c.us`, body: '0' } - if (!response || response.length == 0) { - console.log('Empty result from hit portal monitoring. Centro_de_custo: ', req.body['centro_custo']) - return res.status(200).json({ "message": "Ok" }); - } + await sendDialogflowAwswer(ticket.whatsappId, ticket, msg, contact, false); - const botInfo = await BotIsOnQueue('botqueue') - - let ticket = await ShowTicketServiceByContactId(contact['contact.id']) - - if (ticket.id && ticket.status == 'pending') { - - await sendMessageHitMonitoring(`*Olá. Somos a TI Espaçolaser.*\nIdentificamos em nossos monitoramentos que há um problema na internet da sua loja ${contact['contact.name']} e já estamos resolvendo. Abaixo seguem informações sobre o incidente para que possam acompanhar.\n\n*Situação do chamado na Operadora*\n\n*Incidente*:\n\n ${response[0].header}\n${response[0].body}`, ticket); - - } - else if (!ticket.id) { - - ticket = await CreateTicketService({ contactId: contact['contact.id'], status: 'open', userId: botInfo.userIdBot }); - - console.log('botInfo.botQueueId: ', botInfo.botQueueId) - - await UpdateTicketService({ ticketData: { queueId: botInfo.botQueueId }, ticketId: ticket.id }); - - await sendMessageHitMonitoring(`*Olá. Somos a TI Espaçolaser.*\nIdentificamos em nossos monitoramentos que há um problema na internet da sua loja ${contact['contact.name']} e já estamos resolvendo. Abaixo seguem informações sobre o incidente para que possam acompanhar.\n\n*Situação do chamado na Operadora*\n\n*Incidente*:\n\n ${response[0].header}\n${response[0].body}`, ticket); - - } - - - } catch (error) { - - console.log(`Error on try sending the message monitor: `, error) - - } - - } + await sendMessageHitMonitoring(response, ticket); } - } else { - res.status(401).json({ "message": "Token Inválido!" }); + return res.status(401).json({ "message": "Token Inválido!" }); } return res.status(200).json({ "message": "Ok" }); + }; -async function sendMessageHitMonitoring(msg: string, ticket: Ticket) { +async function sendMessageHitMonitoring(msg: any, ticket: Ticket) { if (msg && msg.length > 0) { diff --git a/backend/src/controllers/MessageController.ts b/backend/src/controllers/MessageController.ts index bff0869..9717c27 100644 --- a/backend/src/controllers/MessageController.ts +++ b/backend/src/controllers/MessageController.ts @@ -9,6 +9,7 @@ import ShowTicketService from "../services/TicketServices/ShowTicketService"; import DeleteWhatsAppMessage from "../services/WbotServices/DeleteWhatsAppMessage"; import SendWhatsAppMedia from "../services/WbotServices/SendWhatsAppMedia"; import SendWhatsAppMessage from "../services/WbotServices/SendWhatsAppMessage"; + type IndexQuery = { pageNumber: string; @@ -19,12 +20,15 @@ type MessageData = { fromMe: boolean; read: boolean; quotedMsg?: Message; + id?: string; }; export const index = async (req: Request, res: Response): Promise => { const { ticketId } = req.params; const { pageNumber } = req.query as IndexQuery; + // console.log(':::::::::::::> TICKET ID: ', ticketId) + const { count, messages, ticket, hasMore } = await ListMessagesService({ pageNumber, ticketId @@ -34,24 +38,23 @@ export const index = async (req: Request, res: Response): Promise => { return res.json({ count, messages, ticket, hasMore }); }; - -export const store = async (req: Request, res: Response): Promise => { - + +export const store = async (req: Request, res: Response): Promise => { const { ticketId } = req.params; const { body, quotedMsg }: MessageData = req.body; const medias = req.files as Express.Multer.File[]; - + const ticket = await ShowTicketService(ticketId); - console.log('TICKET ID: ', ticketId) + console.log("TICKET ID: ", ticketId); // SetTicketMessagesAsRead(ticket); - + if (medias) { await Promise.all( - medias.map(async (media: Express.Multer.File) => { - - console.log(`\n >>>>>>>>>> SENDING MESSAGE MEDIA + medias.map(async (media: Express.Multer.File) => { + console.log( + `\n >>>>>>>>>> SENDING MESSAGE MEDIA Parcial ticket info and media: ticket.id: ${ticket.id} ticket.status: ${ticket.status} @@ -61,13 +64,15 @@ export const store = async (req: Request, res: Response): Promise => { ticket.contact.profilePicUrl: ${ticket.contact.profilePicUrl} ticket.user.id: ${ticket.user.id} ticket.user.name: ${ticket.user.name} - media:`, media,'\n') + media:`, + media, + "\n" + ); await SendWhatsAppMedia({ media, ticket }); }) ); } else { - console.log(`\n >>>>>>>>>> SENDING MESSAGE Parcial ticket info: ticket.id: ${ticket.id} @@ -78,8 +83,7 @@ export const store = async (req: Request, res: Response): Promise => { ticket.contact.name: ${ticket.contact.name} ticket.contact.profilePicUrl: ${ticket.contact.profilePicUrl} ticket.user.id: ${ticket.user.id} - ticket.user.name: ${ticket.user.name}\n`) - + ticket.user.name: ${ticket.user.name}\n`); await SendWhatsAppMessage({ body, ticket, quotedMsg }); } @@ -102,4 +106,4 @@ export const remove = async ( }); return res.send(); -}; +}; \ No newline at end of file diff --git a/backend/src/controllers/TicketController.ts b/backend/src/controllers/TicketController.ts index ecc339f..f19751d 100644 --- a/backend/src/controllers/TicketController.ts +++ b/backend/src/controllers/TicketController.ts @@ -19,9 +19,10 @@ import ptBR from 'date-fns/locale/pt-BR'; import { splitDateTime } from "../helpers/SplitDateTime"; import format from 'date-fns/format'; -import ListTicketsServiceCache from "../services/TicketServices/ListTicketServiceCache"; +import ListTicketsServiceCache from "../services/TicketServices/ListTicketServiceCache"; import { searchTicketCache, loadTicketsCache, } from '../helpers/TicketCache' +import { Op } from "sequelize"; @@ -34,6 +35,7 @@ type IndexQuery = { withUnreadMessages: string; queueIds: string; unlimited?: string; + searchParamContent?: string }; interface TicketData { @@ -41,6 +43,10 @@ interface TicketData { status: string; queueId: number; userId: number; + whatsappId?: string | number + msg?: string, + transfer?: boolean | undefined, + fromMe?: boolean } @@ -51,6 +57,16 @@ import TicketEmiterSumOpenClosedByUser from "../helpers/OnlineReporEmiterInfoByU import CountTicketService from "../services/TicketServices/CountTicketService"; import CountTicketsByUserQueue from "../services/UserServices/CountTicketsByUserQueue"; import ShowUserService from "../services/UserServices/ShowUserService"; +import axios from "axios"; +import User from "../models/User"; +import CheckContactOpenTickets from "../helpers/CheckContactOpenTickets"; +import QueuesByUser from "../services/UserServices/ShowQueuesByUser"; +import GetDefaultWhatsApp from "../helpers/GetDefaultWhatsApp"; +import { getWbot } from "../libs/wbot"; +import endPointQuery from "../helpers/old_EndPointQuery"; +import Contact from "../models/Contact"; +import BotIsOnQueue from "../helpers/BotIsOnQueue"; +import { setMessageAsRead } from "../helpers/SetMessageAsRead"; export const index = async (req: Request, res: Response): Promise => { @@ -62,7 +78,8 @@ export const index = async (req: Request, res: Response): Promise => { showAll, queueIds: queueIdsStringified, withUnreadMessages, - unlimited + unlimited, + searchParamContent } = req.query as IndexQuery; @@ -83,32 +100,41 @@ export const index = async (req: Request, res: Response): Promise => { userId, queueIds, withUnreadMessages, - unlimited + unlimited, + searchParamContent }); return res.status(200).json({ tickets, count, hasMore }); }; export const store = async (req: Request, res: Response): Promise => { - const { contactId, status, userId }: TicketData = req.body; - + const { contactId, status, userId, msg, queueId }: TicketData = req.body; - // test del - let ticket = await Ticket.findOne({ where: { contactId, status: 'queueChoice' } }); + const botInfo = await BotIsOnQueue('botqueue') + + let ticket = await Ticket.findOne({ + where: { + [Op.or]: [ + { contactId, status: 'queueChoice' }, + { contactId, status: 'open', userId: botInfo.userIdBot } + ] + } + }); if (ticket) { - await UpdateTicketService({ ticketData: { status: 'open', userId: userId, }, ticketId: ticket.id }); - + await UpdateTicketService({ ticketData: { status: 'open', userId: userId, queueId }, ticketId: ticket.id }); + } else { - ticket = await CreateTicketService({ contactId, status, userId }); + ticket = await CreateTicketService({ contactId, status, userId, queueId }); } const io = getIO(); io.to(ticket.status).emit("ticket", { action: "update", ticket - }); + }); + // // const ticket = await CreateTicketService({ contactId, status, userId }); @@ -138,7 +164,6 @@ export const show = async (req: Request, res: Response): Promise => { return res.status(200).json({ contact, statusChatEnd, schedulesContact }); }; - export const count = async (req: Request, res: Response): Promise => { // type indexQ = { status: string; date?: string; }; @@ -149,11 +174,11 @@ export const count = async (req: Request, res: Response): Promise => { return res.status(200).json(ticketCount); }; - - export const update = async (req: Request, res: Response): Promise => { + console.log('ENTROU NO UPDATE TICKET CONTROLLER') + const { ticketId } = req.params; const userOldInfo = await Ticket.findByPk(ticketId) @@ -175,10 +200,6 @@ export const update = async (req: Request, res: Response): Promise => }); - /////////////////////////////// - - // - if (scheduleData.farewellMessage) { const whatsapp = await ShowWhatsAppService(ticket.whatsappId); @@ -188,11 +209,11 @@ export const update = async (req: Request, res: Response): Promise => await SendWhatsAppMessage({ body: farewellMessage, ticket }); } } - + // lembrete // agendamento if (scheduleData.statusChatEndId === '2' || scheduleData.statusChatEndId === '3') { - + if (isScheduling(scheduleData.schedulingDate, scheduleData.schedulingTime)) { console.log('*** É AGENDAMENTO!') @@ -217,15 +238,52 @@ export const update = async (req: Request, res: Response): Promise => } else { - const ticketData: TicketData = req.body; + // Para aparecer pendente para todos usuarios que estao na fila + if (req.body.transfer) { + req.body.userId = null + } + + let ticketData: TicketData = req.body; + + // console.log('ticketData: ', ticketData) + // console.log('ticketData.transfer', ticketData.transfer) + + // return res.send() + + + // if (ticketData.transfer) { + + // const defaultWhatsapp: any = await GetDefaultWhatsApp(ticketData.userId); + + // const _ticket: any = await Ticket.findByPk(ticketId) + + // if (defaultWhatsapp && ticketData.status != 'open') { + + // await CheckContactOpenTickets(_ticket.dataValues.contactId, defaultWhatsapp.dataValues.id) + + // } + + // ticketData.whatsappId = defaultWhatsapp.dataValues.id + + // } + + console.log('--------> ticketData.status: ', ticketData.status, ' | ticketData.fromMe: ', ticketData.fromMe) - //ticketData: { status: 'open', userId: 4 } , ticketId const { ticket } = await UpdateTicketService({ ticketData, - ticketId + ticketId, }); + + if (ticketData.status == 'open' && !ticketData.fromMe) { + + await setMessageAsRead(ticket); + + } + + console.log('ticket.unreadMessages: ', ticket.unreadMessages) + if (ticketData.userId) { const dateToday = splitDateTime(new Date(format(new Date(), 'yyyy-MM-dd HH:mm:ss', { locale: ptBR }))) @@ -244,7 +302,7 @@ export const update = async (req: Request, res: Response): Promise => const dateToday = splitDateTime(new Date(format(new Date(), 'yyyy-MM-dd HH:mm:ss', { locale: ptBR }))) - if (userOldInfo.userId) { + if (userOldInfo.userId) { TicketEmiterSumOpenClosedByUser(userOldInfo.userId.toString(), dateToday.fullDate, dateToday.fullDate) @@ -258,7 +316,8 @@ export const update = async (req: Request, res: Response): Promise => }; - + + // export const update = async ( // req: Request, @@ -305,3 +364,11 @@ export const remove = async ( return res.status(200).json({ message: "ticket deleted" }); }; +// export async function setMessageAsRead(ticket: Ticket) { +// const wbot_url = await getWbot(ticket.whatsappId); + +// console.log('wbot_url: ', wbot_url, ' | ticket.contact.number: ', ticket.contact.number); + +// await endPointQuery(`${wbot_url}/api/sendSeen`, { number: `${ticket.contact.number}@${ticket.isGroup ? "g" : "c"}.us` }); +// } + diff --git a/backend/src/controllers/WbotMonitorController.ts b/backend/src/controllers/WbotMonitorController.ts index b0e990e..a7906d1 100644 --- a/backend/src/controllers/WbotMonitorController.ts +++ b/backend/src/controllers/WbotMonitorController.ts @@ -14,9 +14,7 @@ export const wbotMonitorRemote = async (req: Request, res: Response): Promise ACTION: ', req.body['action']) - - // let whatsapp = await ShowWhatsAppService(whatsappId) + console.log('-----------> ACTION: ', req.body['action']) const whatsapp: any = await Whatsapp.findByPk(whatsappId, { raw: true }) diff --git a/backend/src/helpers/EndpointQuery.ts b/backend/src/helpers/EndpointQuery.ts index e39caa3..8b4e473 100644 --- a/backend/src/helpers/EndpointQuery.ts +++ b/backend/src/helpers/EndpointQuery.ts @@ -3,7 +3,10 @@ const fs = require('fs') import axios from 'axios'; import * as https from "https"; -const endPointQuery = async (url: string, method: string, param: string = '') => { +const endPointQuery = async ( + url: string, + method: string, + params: object) => { let response: any = null @@ -26,14 +29,18 @@ const endPointQuery = async (url: string, method: string, param: string = '') => // const url = 'http://177.107.193.124:8095/labs/zabbix-frontend/api/api.php' - - response = await axios.post(url, { + let payload = { 'auth': '0424bd59b807674191e7d77572075f33', 'jsonrpc': '2.0', - 'method': 'chamado.ematendimento', - 'params[ccusto]': param, + // 'method': 'omnihit.consultachamado', id: '101' - }, { + } + + payload = { ...payload, ...params } + + console.log('xxxxxx payload: ', payload) + + response = await axios.post(url, payload, { httpsAgent, headers: { 'Content-Type': 'multipart/form-data' }, }); @@ -44,6 +51,8 @@ const endPointQuery = async (url: string, method: string, param: string = '') => console.error(`Erro ao consultar endpoint ${url}: ${error}`); } + // console.log('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> response: ', response) + return response } diff --git a/backend/src/helpers/HitPortalMonitoring.ts b/backend/src/helpers/HitPortalMonitoring.ts index 2dacf3f..1138ab0 100644 --- a/backend/src/helpers/HitPortalMonitoring.ts +++ b/backend/src/helpers/HitPortalMonitoring.ts @@ -6,62 +6,19 @@ import endPointQuery from "./EndpointQuery"; import WhatsQueueIndex from "./WhatsQueueIndex"; -const hitPortalMonitoring = async (centro_de_custo: string) => { - - let msg_endpoint: any = [] - - let response2 = await endPointQuery('http://177.107.193.124:8095/labs/zabbix-frontend/api/api.php', 'post', centro_de_custo.trim()) +const hitPortalMonitoring = async (params: object) => { + + let response = await endPointQuery('http://177.107.192.247:8095/labs/monitoramentohit/api/api.php', 'post', params) - if (response2 && response2.data.result) { - - response2 = response2.data.result; - - for (let i = 0; i < response2.length; i++) { - - let data = '' - let sub_data = '*Atualizações:*\n\n' - - const properties: any = Object.entries(response2[i]); - - for (let x = 0; x < properties.length; x++) { - - if (typeof (properties[x][1]) != 'object') { - - data += `*${properties[x][0]}*: ${properties[x][1].replace(/(\r\n|\n|\r)/gm, "")}\n` - - } - else if (typeof (properties[x][1]) == 'object') { - - const sub_properties = properties[x][1]; - - for (let k = 0; k < sub_properties.length; k++) { - - const inner_properties: any = Object.entries(sub_properties[k]); - - for (let y = 0; y < inner_properties.length; y++) { - - sub_data += `*${inner_properties[y][0]}*: ${inner_properties[y][1].replace(/(\r\n|\n|\r)/gm, "")}\n` - - } - - sub_data += '\n' - - } - - } - - } - - msg_endpoint.push({ header: data, body: sub_data }) - - } - - } - else { - msg_endpoint = null - } - - return msg_endpoint + if (response && response.data.result.trim().length > 0) { + return response.data.result + } + else if (response && response.data.result.trim().length === 0) { + return '' + } + else { + return null + } } diff --git a/backend/src/helpers/MostRepeatedPhrase.ts b/backend/src/helpers/MostRepeatedPhrase.ts new file mode 100644 index 0000000..b1e0bc0 --- /dev/null +++ b/backend/src/helpers/MostRepeatedPhrase.ts @@ -0,0 +1,58 @@ +import { subSeconds } from "date-fns"; +import Message from "../models/Message"; + +import { Op, Sequelize } from "sequelize"; + +const mostRepeatedPhrase = async (ticketId: number | string, fromMe: boolean = false) => { + + let res: any = { body: '', occurrences: 0 } + + try { + const mostRepeatedPhrase: any = await Message.findOne({ + where: { + ticketId: ticketId, + fromMe: fromMe ? fromMe : 0, + body: { + [Op.notRegexp]: '^[0-9]+$', + }, + updatedAt: { + [Op.between]: [+subSeconds(new Date(), 150), +new Date()], + }, + }, + attributes: [ + 'body', + [Sequelize.fn('COUNT', Sequelize.col('body')), 'occurrences'], + ], + + group: ['body'], + order: [[Sequelize.literal('occurrences'), 'DESC']], + limit: 1, + }); + + if (mostRepeatedPhrase) { + + const { body, occurrences } = mostRepeatedPhrase.get(); + + console.log(`The most repeated phrase is "${body}" with ${occurrences} occurrences.`); + + const isNumber = /^\d+$/.test(body.trim()); + + if (!isNumber) { + + return { body, occurrences } + } + + + } else { + console.log('No phrases found.'); + } + } catch (error) { + console.log('error on MostRepeatedPhrase: ', error) + } + + + + return { body: '', occurrences: 0 } +} + +export default mostRepeatedPhrase; \ No newline at end of file diff --git a/backend/src/helpers/SchedulingNotifySendMessage.ts b/backend/src/helpers/SchedulingNotifySendMessage.ts index 405cc13..93326a2 100644 --- a/backend/src/helpers/SchedulingNotifySendMessage.ts +++ b/backend/src/helpers/SchedulingNotifySendMessage.ts @@ -10,6 +10,9 @@ import path from "path"; import { convertBytes } from "./ConvertBytes"; import { deleteScheduleByTicketIdCache } from "./SchedulingNotifyCache"; import SchedulingNotify from "../models/SchedulingNotify"; +import Ticket from "../models/Ticket"; +import { Sequelize, Op } from "sequelize"; + const fastFolderSize = require('fast-folder-size') const { promisify } = require('util') @@ -41,33 +44,39 @@ const monitor = async () => { try { const { schedulingNotifies, count, hasMore } = await ListSchedulingNotifyService({ searchParam: dateParm, pageNumber: "1" }); - - if (schedulingNotifies && schedulingNotifies.length > 0) { - for (let i = 0; i < schedulingNotifies.length; i++) { + if (schedulingNotifies && schedulingNotifies.length > 0) { + for (let i = 0; i < schedulingNotifies.length; i++) { + + const ticket: any = await ShowTicketService(+schedulingNotifies[i].ticketId); + + let _ticket = await Ticket.findOne({ + where: { + contactId: ticket.contactId, + status: { [Op.in]: ['open', 'pending'] } + } + }) + + await deleteScheduleByTicketIdCache(schedulingNotifies[i].ticketId) + + await DeleteSchedulingNotifyService(schedulingNotifies[i].id) + + if (_ticket) continue + + if (ticket.dataValues.status == 'closed') { + await ticket.update({ status: 'pending' }) + } - const ticket = await ShowTicketService(+schedulingNotifies[i].ticketId); - await new Promise(f => setTimeout(f, 3000)); - - if(!ticket.queue){ - await ticket.update({status: 'open'}) - } - - // SetTicketMessagesAsRead(ticket); - await SendWhatsAppMessage({ body: schedulingNotifies[i].message, ticket }); - await deleteScheduleByTicketIdCache(schedulingNotifies[i].ticketId) - - await DeleteSchedulingNotifyService(schedulingNotifies[i].id) - } - + } + } @@ -173,7 +182,7 @@ _fifo = setInterval(SchedulingNotifySendMessage, 5000); module.exports = SchedulingNotifySendMessage - + diff --git a/backend/src/helpers/SendWhatsappMessageSocket.ts b/backend/src/helpers/SendWhatsappMessageSocket.ts index 073384e..c9485f3 100644 --- a/backend/src/helpers/SendWhatsappMessageSocket.ts +++ b/backend/src/helpers/SendWhatsappMessageSocket.ts @@ -14,19 +14,7 @@ function sendWhatsAppMessageSocket(ticket: Ticket, body: string, quotedMsgSerial quotedMessageId: quotedMsgSerializedId, linkPreview: false } - }); - - - // io.emit("send_message", { - // action: "create", - // msg: { - // number: `${ticket.contact.number}@${ticket.isGroup ? "g" : "c"}.us`, - // body: body, - // quotedMessageId: quotedMsgSerializedId, - // linkPreview: false - // } - // }); - + }); } diff --git a/backend/src/helpers/SetMessageAsRead.ts b/backend/src/helpers/SetMessageAsRead.ts new file mode 100644 index 0000000..3f073fa --- /dev/null +++ b/backend/src/helpers/SetMessageAsRead.ts @@ -0,0 +1,12 @@ +import { getWbot } from "../libs/wbot"; +import Ticket from "../models/Ticket"; +import endPointQuery from "./old_EndPointQuery"; + +export async function setMessageAsRead(ticket: Ticket) { + + const wbot_url = await getWbot(ticket.whatsappId); + + console.log('from wbotMessagelistener wbot_url: ', wbot_url, ' | ticket.contact.number: ', ticket.contact.number); + + await endPointQuery(`${wbot_url}/api/sendSeen`, { number: `${ticket.contact.number}@${ticket.isGroup ? "g" : "c"}.us` }); +} \ No newline at end of file diff --git a/backend/src/libs/socket.ts b/backend/src/libs/socket.ts index 8eb1464..24c79c6 100644 --- a/backend/src/libs/socket.ts +++ b/backend/src/libs/socket.ts @@ -45,13 +45,7 @@ export const initIO = (httpServer: Server): SocketIO => { io.on("connection", socket => { - logger.info("Client Connected"); - - - - - - + logger.info("Client Connected"); socket.on("joinWhatsSession", (whatsappId: string) => { logger.info(`A client joined a joinWhatsSession channel: ${whatsappId}`); @@ -65,6 +59,8 @@ export const initIO = (httpServer: Server): SocketIO => { socket.on("message_create", async (data: any) => { + // console.log('data: ', data) + handleMessage(data.msg, data); }); diff --git a/backend/src/routes/hitRoutes.ts b/backend/src/routes/hitRoutes.ts index 1c47bfb..b4e649b 100644 --- a/backend/src/routes/hitRoutes.ts +++ b/backend/src/routes/hitRoutes.ts @@ -4,6 +4,6 @@ import * as HitController from "../controllers/HitController"; const hitRoutes = express.Router(); -hitRoutes.post("/espacolaser/incidente", HitController.hit); +hitRoutes.post("/omnihit/incidente", HitController.hit); export default hitRoutes; diff --git a/backend/src/routes/messageRoutes.ts b/backend/src/routes/messageRoutes.ts index 31141c4..b029f9a 100644 --- a/backend/src/routes/messageRoutes.ts +++ b/backend/src/routes/messageRoutes.ts @@ -13,6 +13,8 @@ messageRoutes.get("/messages/:ticketId", isAuth, MessageController.index); messageRoutes.post("/messages/:ticketId", isAuth, upload.array("medias"), MessageController.store); +// messageRoutes.put("/messages/:ticketId", isAuth, MessageController.update) + messageRoutes.delete("/messages/:messageId", isAuth, MessageController.remove); -export default messageRoutes; +export default messageRoutes; \ No newline at end of file diff --git a/backend/src/services/TicketServices/CreateTicketService.ts b/backend/src/services/TicketServices/CreateTicketService.ts index f3bd64a..376a90c 100644 --- a/backend/src/services/TicketServices/CreateTicketService.ts +++ b/backend/src/services/TicketServices/CreateTicketService.ts @@ -21,18 +21,23 @@ let flatten = require('flat') interface Request { contactId: number; status: string; - userId: number; + userId: number; + queueId?: number | undefined; } const CreateTicketService = async ({ contactId, status, - userId + userId, + queueId = undefined }: Request): Promise => { + + console.log('========> queueId: ', queueId) + try { - const defaultWhatsapp = await GetDefaultWhatsApp(userId); + const defaultWhatsapp = await GetDefaultWhatsApp(userId); await CheckContactOpenTickets(contactId); @@ -42,7 +47,8 @@ const CreateTicketService = async ({ contactId, status, isGroup, - userId + userId, + queueId }); const ticket = await Ticket.findByPk(id, { include: ["contact"] }); diff --git a/backend/src/services/TicketServices/FindOrCreateTicketServiceBot.ts b/backend/src/services/TicketServices/FindOrCreateTicketServiceBot.ts new file mode 100644 index 0000000..57593c2 --- /dev/null +++ b/backend/src/services/TicketServices/FindOrCreateTicketServiceBot.ts @@ -0,0 +1,147 @@ +import { subHours, subMinutes, subSeconds } from "date-fns"; +import { Op } from "sequelize"; +import BotIsOnQueue from "../../helpers/BotIsOnQueue"; +import Contact from "../../models/Contact"; +import Ticket from "../../models/Ticket"; +import ShowWhatsAppService from "../WhatsappService/ShowWhatsAppService"; +import ShowTicketService from "./ShowTicketService"; +import AppError from "../../errors/AppError"; +import { userInfo } from "os"; +import { sendDialogflowAwswer } from "../WbotServices/wbotMessageListener"; +import ShowQueueService from "../QueueService/ShowQueueService"; +import UpdateTicketService from "./UpdateTicketService"; + + +const FindOrCreateTicketServiceBot = async ( + contact: Contact, + whatsappId: number, + unreadMessages: number, + groupContact?: Contact +): Promise => { + + try { + + let ticket = await Ticket.findOne({ + where: { + status: { + [Op.or]: ["open", "pending", "queueChoice"] + }, + contactId: groupContact ? groupContact.id : contact.id + } + }); + + const { queues, greetingMessage } = await ShowWhatsAppService(whatsappId); + + + //Habilitar esse caso queira usar o bot + const botInfo = await BotIsOnQueue('botqueue') + // const botInfo = { isOnQueue: false } + + + + if (ticket) { + await ticket.update({ unreadMessages }); + } + + // if (!ticket && groupContact) { + // ticket = await Ticket.findOne({ + // where: { + // contactId: groupContact.id + // }, + // order: [["updatedAt", "DESC"]] + // }); + + + + // if (ticket) { + + // await ticket.update({ + // status: "pending", + // userId: null, + // unreadMessages + // }); + // } + // } + + if (!ticket && !groupContact) { + + console.log('BOT CREATING OR REOPENING THE TICKET') + + ticket = await Ticket.findOne({ + where: { + contactId: contact.id, + userId: botInfo.userIdBot + }, + order: [["updatedAt", "DESC"]] + }); + + if (ticket) { + + await ticket.update({ + status: "open", + userId: botInfo.userIdBot, + unreadMessages + }); + + console.log('lxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx') + + await dialogFlowStartContext(contact, ticket, botInfo); + + } + } + + let created = false + + if (!ticket) { + + created = true + + let status = "open" + + if (queues.length > 1 && !botInfo.isOnQueue) { + status = "queueChoice" + } + + ticket = await Ticket.create({ + contactId: groupContact ? groupContact.id : contact.id, + status: status, + userId: botInfo.userIdBot, + isGroup: !!groupContact, + unreadMessages, + whatsappId + }); + + console.log('yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy') + + await dialogFlowStartContext(contact, ticket, botInfo); + + } + + ticket = await ShowTicketService(ticket.id); + + return { ticket, created }; + + } catch (error: any) { + console.error('===> Error on FindOrCreateTicketServiceBot.ts file: \n', error) + throw new AppError(error.message); + } +}; + +export default FindOrCreateTicketServiceBot; + +async function dialogFlowStartContext(contact: Contact, ticket: Ticket, botInfo: any) { + + let msg: any = { type: 'chat', from: `${contact.number}@c.us`, body: '0' }; + + let queue = await ShowQueueService(botInfo.botQueueId); + + await UpdateTicketService({ + ticketData: { queueId: queue.id }, + ticketId: ticket.id + }); + + ticket = await ShowTicketService(ticket.id); + + await sendDialogflowAwswer(ticket.whatsappId, ticket, msg, contact, false); +} + diff --git a/backend/src/services/TicketServices/ListTicketsService.ts b/backend/src/services/TicketServices/ListTicketsService.ts index 190bc7e..76c655b 100644 --- a/backend/src/services/TicketServices/ListTicketsService.ts +++ b/backend/src/services/TicketServices/ListTicketsService.ts @@ -15,8 +15,11 @@ const dateToday = splitDateTime(new Date(format(new Date(), 'yyyy-MM-dd HH:mm:ss import ListTicketServiceCache from "./ListTicketServiceCache" -import { searchTicketCache, loadTicketsCache } from '../../helpers/TicketCache' +import { searchTicketCache, loadTicketsCache } from '../../helpers/TicketCache' import { getWbot } from "../../libs/wbot"; +import User from "../../models/User"; + + interface Request { @@ -29,6 +32,7 @@ interface Request { withUnreadMessages?: string; queueIds: number[]; unlimited?: string; + searchParamContent?: string; } interface Response { @@ -47,9 +51,12 @@ const ListTicketsService = async ({ showAll, userId, withUnreadMessages, - unlimited = 'false' + unlimited = 'false', + searchParamContent = "" }: Request): Promise => { + console.log('----------> searchParamContent: ', searchParamContent) + let whereCondition: Filterable["where"] = { [Op.or]: [{ userId }, { status: "pending" }], queueId: { [Op.or]: [queueIds, null] } }; console.log('PAGE NUMBER TICKET: ', pageNumber) @@ -64,9 +71,6 @@ const ListTicketsService = async ({ } - - - if (searchParam && searchParam.trim().length > 0 && process.env.CACHE) { try { @@ -147,20 +151,33 @@ const ListTicketsService = async ({ if (searchParam) { const sanitizedSearchParam = searchParam.toLocaleLowerCase().trim(); + const sanitizedSearchParamContent = searchParamContent.toLocaleLowerCase().trim(); + + + if (searchParamContent.length > 0) { + + includeCondition = [ + ...includeCondition, + { + model: Message, + as: "messages", + attributes: ["id", "body"], + where: { + body: where(fn("LOWER", col("body")), "LIKE", `%${sanitizedSearchParamContent}%`) + }, + required: false, + duplicating: false + } + ]; + + whereCondition = { + ...whereCondition, + "$message.body$": where(fn("LOWER", col("body")), "LIKE", `%${sanitizedSearchParamContent}%`) + }; + + } + - // includeCondition = [ - // ...includeCondition, - // { - // model: Message, - // as: "messages", - // attributes: ["id", "body"], - // where: { - // body: where(fn("LOWER", col("body")), "LIKE", `%${sanitizedSearchParam}%`) - // }, - // required: false, - // duplicating: false - // } - // ]; whereCondition = { ...whereCondition, @@ -171,11 +188,17 @@ const ListTicketsService = async ({ { "$contact.number$": { [Op.like]: `%${sanitizedSearchParam}%` } }, - // { - // "$message.body$": where(fn("LOWER", col("body")), "LIKE", `%${sanitizedSearchParam}%`) - // } - ] + ], }; + + + const userProfile: any = await User.findByPk(userId) + + if (userProfile.dataValues.profile != 'admin' && userProfile.dataValues.profile != 'master') { + whereCondition = { ...whereCondition, userId } + } + + } if (date) { @@ -201,8 +224,6 @@ const ListTicketsService = async ({ const offset = limit * (+pageNumber - 1); - - const { count, rows: tickets } = await Ticket.findAndCountAll({ where: whereCondition, include: includeCondition, diff --git a/backend/src/services/TicketServices/ShowTicketMessage.ts b/backend/src/services/TicketServices/ShowTicketMessage.ts index 34a087d..a9fe737 100644 --- a/backend/src/services/TicketServices/ShowTicketMessage.ts +++ b/backend/src/services/TicketServices/ShowTicketMessage.ts @@ -9,53 +9,82 @@ import { userInfo } from "os"; import { Op, where } from "sequelize"; -import { Sequelize } from "sequelize"; -import moment from 'moment'; - -import { startOfDay, endOfDay, parseISO, getDate} from "date-fns"; +import { Sequelize } from "sequelize"; +import moment from 'moment'; + +import { startOfDay, endOfDay, parseISO, getDate, subSeconds } from "date-fns"; import { string } from "yup/lib/locale"; + +function paramsQuery(params: string[]) { + + let like = [] + + for (let i = 0; i < params.length; i++) { + like.push({ [Op.like]: `%${params[i]}%` }) + } + return like +} + //Report by user, startDate, endDate -const ShowTicketMessage = async (ticketId: string | number, onlyNumber: boolean = false, fromMe?:boolean, limit?: number, regexp?: string): Promise => { - +const ShowTicketMessage = async ( + ticketId: string | number, + limit: number, + fromMe: boolean, + params?: string[], + onlyNumber?: boolean, + regexp?: string): Promise => { + let where_clause = {} - if(onlyNumber){ - where_clause = { - ticketId: ticketId, - fromMe: fromMe ? fromMe : 0, - //body: {[Op.regexp]: '^[0-9]*$'}, - // body: {[Op.regexp]: '^[0-3]$'}, - body: {[Op.regexp]: regexp}, - } + if (onlyNumber) { + where_clause = { + ticketId: ticketId, + fromMe: fromMe ? fromMe : 0, + body: { [Op.regexp]: regexp }, + } } - else{ - where_clause = { - ticketId: ticketId, - fromMe: fromMe ? fromMe : 0, - } + else if (params && params.length > 0) { + where_clause = { + ticketId: ticketId, + fromMe: fromMe ? fromMe : 0, + updatedAt: { + [Op.between]: [+subSeconds(new Date(), 50), +new Date()], + }, + body: { + [Op.or]: paramsQuery(params) + } + } + } + else { + + where_clause = { + ticketId: ticketId, + fromMe: fromMe ? fromMe : 0, + } + } - - const ticket = await Message.findAll({ - where: where_clause , - limit: limit ? limit : 10000, - raw:true, - attributes: ['body', 'read', 'mediaType','fromMe', 'mediaUrl', [Sequelize.fn("DATE_FORMAT",Sequelize.col("createdAt"),"%d/%m/%Y %H:%i:%s"),"createdAt"]], + const ticket = await Message.findAll({ + + where: where_clause, + limit: limit, + raw: true, + attributes: ['body', 'read', 'mediaType', 'fromMe', 'mediaUrl', [Sequelize.fn("DATE_FORMAT", Sequelize.col("createdAt"), "%d/%m/%Y %H:%i:%s"), "createdAt"]], order: [ ['createdAt', 'DESC'] - ] - - }); - - + ] + + }); + + if (!ticket) { throw new AppError("ERR_NO_TICKET_FOUND", 404); } return ticket; -}; +}; export default ShowTicketMessage; diff --git a/backend/src/services/TicketServices/ShowTicketService.ts b/backend/src/services/TicketServices/ShowTicketService.ts index 2b283bd..13f8e28 100644 --- a/backend/src/services/TicketServices/ShowTicketService.ts +++ b/backend/src/services/TicketServices/ShowTicketService.ts @@ -27,7 +27,7 @@ const ShowTicketService = async (id: string | number): Promise => { ] }); - console.log('>>>>>>>>>>>>>>>>>>>>>>>> ShowTicketService: ',ticket?.whatsappId) + // console.log('>>>>>>>>>>>>>>>>>>>>>>>> ShowTicketService: ',ticket?.whatsappId) if (!ticket) { throw new AppError("ERR_NO_TICKET_FOUND", 404); diff --git a/backend/src/services/TicketServices/UpdateTicketService.ts b/backend/src/services/TicketServices/UpdateTicketService.ts index 3cafa11..eed34da 100644 --- a/backend/src/services/TicketServices/UpdateTicketService.ts +++ b/backend/src/services/TicketServices/UpdateTicketService.ts @@ -8,20 +8,23 @@ import ShowTicketService from "./ShowTicketService"; import { createOrUpdateTicketCache } from '../../helpers/TicketCache' import AppError from "../../errors/AppError"; +import sendWhatsAppMessageSocket from "../../helpers/SendWhatsappMessageSocket"; var flatten = require('flat') - + interface TicketData { status?: string; userId?: number; queueId?: number; - statusChatEnd?: string + statusChatEnd?: string; + unreadMessages?: number; } interface Request { ticketData: TicketData; - ticketId: string | number; + ticketId: string | number; + msg?: string } interface Response { @@ -32,83 +35,94 @@ interface Response { const UpdateTicketService = async ({ ticketData, - ticketId + ticketId, + msg='' }: Request): Promise => { try { - const { status, userId, queueId, statusChatEnd } = ticketData; + const { status, userId, queueId, statusChatEnd, unreadMessages } = ticketData; - const ticket = await ShowTicketService(ticketId); - // await SetTicketMessagesAsRead(ticket); + const ticket = await ShowTicketService(ticketId); + // await SetTicketMessagesAsRead(ticket); - const oldStatus = ticket.status; - const oldUserId = ticket.user?.id; + const oldStatus = ticket.status; + const oldUserId = ticket.user?.id; - if (oldStatus === "closed") { - await CheckContactOpenTickets(ticket.contact.id); - } - - await ticket.update({ - status, - queueId, - userId, - statusChatEnd - }); + if (oldStatus === "closed") { + await CheckContactOpenTickets(ticket.contact.id); + } - await ticket.reload(); - - // TEST DEL - try { - - // const { name, number } = await ShowContactService(ticket.contactId) - - let jsonString = JSON.stringify(ticket); //convert to string to remove the sequelize specific meta data - let ticket_obj = JSON.parse(jsonString); //to make plain json - delete ticket_obj['contact']['extraInfo'] - delete ticket_obj['user'] - - ticket_obj = flatten(ticket_obj) - - await createOrUpdateTicketCache(`ticket:${ticket.id}`, ticket_obj) - - } catch (error) { - console.log('There was an error on UpdateTicketService.ts on createTicketCache: ', error) - } - // - - let io = getIO(); - - if (ticket.status !== oldStatus || ticket.user?.id !== oldUserId) { - io.to(oldStatus).emit("ticket", { - action: "delete", - ticketId: ticket.id + await ticket.update({ + status, + queueId, + userId, + unreadMessages, + statusChatEnd }); - } + + await ticket.reload(); + + if (msg.length > 0) { + + setTimeout(async () => { + + sendWhatsAppMessageSocket(ticket, msg) + + }, 2000) + } + + // TEST DEL + try { + + // const { name, number } = await ShowContactService(ticket.contactId) + + let jsonString = JSON.stringify(ticket); //convert to string to remove the sequelize specific meta data + let ticket_obj = JSON.parse(jsonString); //to make plain json + delete ticket_obj['contact']['extraInfo'] + delete ticket_obj['user'] + + ticket_obj = flatten(ticket_obj) + + await createOrUpdateTicketCache(`ticket:${ticket.id}`, ticket_obj) + + } catch (error) { + console.log('There was an error on UpdateTicketService.ts on createTicketCache: ', error) + } + // + + let io = getIO(); + + if (ticket.status !== oldStatus || ticket.user?.id !== oldUserId) { + io.to(oldStatus).emit("ticket", { + action: "delete", + ticketId: ticket.id + }); + } - io.to(ticket.status) - .to("notification") - .to(ticketId.toString()) - .emit("ticket", { + io.to(ticket.status) + .to("notification") + .to(ticketId.toString()) + .emit("ticket", { + action: "update", + ticket + }); + + + io.emit("ticketStatus", { action: "update", - ticket + ticketStatus: { ticketId: ticket.id, status: ticket.status } }); - io.emit("ticketStatus", { - action: "update", - ticketStatus: { ticketId: ticket.id, status: ticket.status } - }); + return { ticket, oldStatus, oldUserId }; - - return { ticket, oldStatus, oldUserId }; - } catch (error: any) { console.error('===> Error on UpdateTicketService.ts file: \n', error) throw new AppError(error.message); } - + }; -export default UpdateTicketService; +export default UpdateTicketService; \ No newline at end of file diff --git a/backend/src/services/WbotServices/SendWhatsAppMessage.ts b/backend/src/services/WbotServices/SendWhatsAppMessage.ts index 9890cf3..183aaf3 100644 --- a/backend/src/services/WbotServices/SendWhatsAppMessage.ts +++ b/backend/src/services/WbotServices/SendWhatsAppMessage.ts @@ -45,10 +45,11 @@ const SendWhatsAppMessage = async ({ try { - let timestamp = Math.floor(Date.now() / 1000) + // let timestamp = Math.floor(Date.now() / 1000) + let timestamp = Date.now() + String(Math.floor(Math.random() * 1000)) var timetaken = `########################################${timestamp}| TicketId: ${ticket.id} => Time taken to send the message`; - console.time(timetaken) + console.time(timetaken) let quotedMsgSerializedId: string | undefined; @@ -67,9 +68,15 @@ const SendWhatsAppMessage = async ({ let listWhatsapp = null - // listWhatsapp = await searchWhatsappCache(`${ticket.whatsappId}`, 'CONNECTED') + // listWhatsapp = await searchWhatsappCache(`${ticket.whatsappId}`, 'CONNECTED') - console.log('ticket.whatsappIdticket.whatsappIdticket.whatsappIdticket: ', ticket.whatsappId) + if (!ticket.whatsappId) { + + const defaultWhatsapp: any = await GetDefaultWhatsApp(ticket.userId); + + await ticket.update({ whatsappId: +defaultWhatsapp.id }); + + } if (!listWhatsapp) { listWhatsapp = await ListWhatsAppsNumber(ticket.whatsappId, 'CONNECTED') @@ -77,12 +84,8 @@ const SendWhatsAppMessage = async ({ if (listWhatsapp.whatsapp && listWhatsapp.whatsapp.status != 'CONNECTED' && listWhatsapp.whatsapps.length > 0) { - // console.log('kkkkkkkkkkkkkkkkkkkkkkkkkkkk: ', listWhatsapp.whatsapps[0].id) - await ticket.update({ whatsappId: + listWhatsapp.whatsapps[0].id }); - let _ticket = await Ticket.findByPk(listWhatsapp.whatsapps[0].id) - } @@ -123,9 +126,9 @@ const SendWhatsAppMessage = async ({ try { - sendWhatsAppMessageSocket(ticket, body, quotedMsgSerializedId, number); + sendWhatsAppMessageSocket(ticket, body, quotedMsgSerializedId, number); - await ticket.update({ lastMessage: body }); + await ticket.update({ lastMessage: body }); await updateTicketCacheByTicketId(ticket.id, { lastMessage: body, updatedAt: new Date(ticket.updatedAt).toISOString() }) diff --git a/backend/src/services/WbotServices/wbotMessageListener.ts b/backend/src/services/WbotServices/wbotMessageListener.ts index 1f0616b..c6912dd 100644 --- a/backend/src/services/WbotServices/wbotMessageListener.ts +++ b/backend/src/services/WbotServices/wbotMessageListener.ts @@ -71,6 +71,10 @@ import sendWhatsAppMessageSocket from "../../helpers/SendWhatsappMessageSocket"; import { getWhatsappIds, setWhatsappId } from "../../helpers/WhatsappIdMultiSessionControl"; import SendWhatsAppMedia from "./SendWhatsAppMedia"; import AppError from "../../errors/AppError"; +import { tr } from "date-fns/locale"; +import mostRepeatedPhrase from "../../helpers/MostRepeatedPhrase"; +import FindOrCreateTicketServiceBot from "../TicketServices/FindOrCreateTicketServiceBot"; +import { setMessageAsRead } from "../../helpers/SetMessageAsRead"; @@ -223,23 +227,44 @@ const queryEndPointHit = async (centro_de_custo: string) => { let msg_endpoint: any = [] - let response2 = await endPointQuery('http://177.107.193.124:8095/labs/zabbix-frontend/api/api.php', 'post', centro_de_custo.trim()) + let response2 = await endPointQuery('http://177.107.193.124:8095/labs/zabbix-frontend/api/api.php', 'post', { 'params[cod_web]': centro_de_custo.trim(), }) if (response2 && response2.data.result) { response2 = response2.data.result; + for (let i = 0; i < response2.length; i++) { let data = '' let sub_data = '*Atualizações:*\n\n' - const properties: any = Object.entries(response2[i]); + let properties: any = Object.entries(response2[i]); for (let x = 0; x < properties.length; x++) { if (typeof (properties[x][1]) != 'object') { + if (properties[x][0] === 'n_chamado_web') { + properties[x][0] = 'Protocolo' + } + else if (properties[x][0] === 'nome_cliente') { + properties[x][0] = 'Nome do cliente' + } + else if (properties[x][0] === 'quando_inicio') { + properties[x][0] = 'Data de abertura' + } + else if (properties[x][0] === 'nome_filial') { + properties[x][0] = 'Nome da filial' + } + else if (properties[x][0] === 'cod_web') { + properties[x][0] = 'Codigo do cliente' + } + else if (properties[x][0] === 'id' || properties[x][0] === 'cliente_id') { + continue + } + + data += `*${properties[x][0]}*: ${properties[x][1].replace(/(\r\n|\n|\r)/gm, "")}\n` } @@ -249,10 +274,20 @@ const queryEndPointHit = async (centro_de_custo: string) => { for (let k = 0; k < sub_properties.length; k++) { - const inner_properties: any = Object.entries(sub_properties[k]); + let inner_properties: any = Object.entries(sub_properties[k]); for (let y = 0; y < inner_properties.length; y++) { + if (inner_properties[y][0] === 'texto') { + inner_properties[y][0] = 'Informação' + } + else if (inner_properties[y][0] === 'quando') { + inner_properties[y][0] = 'Data da Informação' + } + else if (inner_properties[y][0] === 'login') { + continue + } + sub_data += `*${inner_properties[y][0]}*: ${inner_properties[y][1].replace(/(\r\n|\n|\r)/gm, "")}\n` } @@ -324,7 +359,7 @@ const monitoramento_response2 = async (response: any | null, wbot: any, contact: -async function sendDelayedMessages(wbot: Session, ticket: Ticket, contact: Contact, message: string, _msg?: WbotMessage) { +async function sendDelayedMessages(wbot: any, ticket: Ticket, contact: Contact, message: string, _msg?: WbotMessage) { const body = message.replace(/\\n/g, '\n'); if (body.search('dialog_actions') != -1) { @@ -335,204 +370,132 @@ async function sendDelayedMessages(wbot: Session, ticket: Ticket, contact: Conta if (msgAction.actions[0] == 'request_endpoint') { - // OLD - // const sentMessage = await wbot.sendMessage(`${contact.number}@c.us`, msgAction.msgBody); - // await verifyMessage(sentMessage, ticket, contact); - // await new Promise(f => setTimeout(f, 1000)); // NEW await SendWhatsAppMessage({ body: msgAction.msgBody, ticket, number: `${contact.number}@c.us` }) + // const url = 'http://177.107.193.124:8095/labs/zabbix-frontend/api/api.php/99383' - // const url = 'https://sos.espacolaser.com.br/api/whatsapps/ticket/R32656' - let endPointResponse = await endPointQuery(msgAction.actions[1], 'get') + let aux = msgAction.actions[1].split('/') - console.log('Object.entries(endPointResponse.data).length: ', Object.entries(endPointResponse.data).length) - // console.log('endPointResonse.status: ',typeof(endPointResponse.status)) + let cod_web - if (endPointResponse && endPointResponse.status == 200 && Object.entries(endPointResponse.data).length > 0) { + if (aux && aux.length > 0) { + cod_web = aux[aux.length - 1] + } - // endPointResponse.data.categoria = 'ELOS' - // endPointResponse.data.categoria = 'INFRAESTRUTURA' - // endPointResponse.data.subcategoria = 'INTERNET' - // endPointResponse.data.terceiro_nivel = 'QUEDA TOTAL' + let params = msgAction.actions[1].split('api.php')[1].slice(1).split('/') - console.log('-------------> endPointResponse.data.categoria: ', endPointResponse.data.categoria) + let url = msgAction.actions[1].replace(/\.php.*/, '.php') - if (endPointResponse.data.categoria != 'INFRAESTRUTURA' && endPointResponse.data.categoria != 'ELOS') { - botSendMessage(ticket, contact, wbot, `A categorização desse chamado não se enquadra no atendimento deste canal!\n\n _Digite *0* para voltar ao menu principal._`) - return - } + let response: any = '' - const response = Object.entries(endPointResponse.data); - let msg_endpoint_response = '' - let msg_endpoint2: any = [] - let centro_de_custo = '' + if (params[0] === 'validate_n_chamado_web') { + + let valid = await endPointQuery( + 'http://177.107.192.247:8095/labs/monitoramentohit/api/api.php', + 'post', + { + 'params[n_chamado_web]': params[1], + 'method': 'omnihit.consultachamadostatus' + }) - for (let i = 0; i < response.length; i++) { + if (valid && valid.data.result == 'open') { - if (['id_solicitante', 'centro_custo_departamento', 'departamento_do_formulario', 'centro_de_custo_do_departamento_do_formulario'].includes(response[i][0])) - continue - - msg_endpoint_response += `*${response[i][0]}*: ${String(response[i][1]).replace(/(<([^>]+)>)/gi, "")}\n` + botSendMessage(ticket, `Protocolo validado, por favor, pode digitar o texto a ser adicionado no histórico do protocolo *${params[1]}*`) } + else if (valid && valid.data.result == 'notfound') { - - - if (endPointResponse.data.centro_custo_departamento && endPointResponse.data.centro_custo_departamento.trim().length > 0 && (endPointResponse.data.categoria == 'INFRAESTRUTURA' && endPointResponse.data.subcategoria == 'INTERNET')) { - - centro_de_custo = endPointResponse.data.centro_custo_departamento - - msg_endpoint2 = await queryEndPointHit(centro_de_custo) - } - - - const itsm_response = async (message1: string = '', message2: string = '') => { - - // OLD - // const msg = await wbot.sendMessage(`${contact.number}@c.us`, `*Situação do chamado ${extractCallCode(msgAction.msgBody)}:*${message1}${msg_endpoint_response}${message2}\n_Digite *0* para voltar ao menu principal._`); - // await verifyMessage(msg, ticket, contact); - // await new Promise(f => setTimeout(f, 1000)); - - // NEW - await SendWhatsAppMessage({ body: `*Situação do chamado ${extractCallCode(msgAction.msgBody)}:*${message1}${msg_endpoint_response}${message2}\n_Digite *0* para voltar ao menu principal._`, ticket, number: `${contact.number}@c.us` }) + botSendMessage(ticket, `Protocolo *${params[1]}* não encontrado!\n_Digite *0* para falar com a HIT._`) } + else if (valid && valid.data.result == 'close') { + botSendMessage( + ticket, + `O protocolo *${params[1]}* foi encerrado. Não é mais possível adicionar informação. Se desejar consultar o historico digite *1*`) - const sendMessageBot = async (message1: string = '', message2: string = '') => { - - // OLD - // const msg = await wbot.sendMessage(`${contact.number}@c.us`, `${message1}${message2}`); - // await verifyMessage(msg, ticket, contact); - // await new Promise(f => setTimeout(f, 1000)); - - // NEW - await SendWhatsAppMessage({ body: `${message1}${message2}`, ticket, number: `${contact.number}@c.us` }) - - } - - - - - if (body.search('dialog_options') != -1) { - - let msgAction = botMsgActions(body) - - let index = msgAction.actions.findIndex((i) => i == 'dialog_options') - - if (index != -1) { - - let dialog_options = msgAction.actions[index + 1] - - if (dialog_options == '3' && endPointResponse.data.categoria == 'ELOS') { - - index = msgAction.actions.findIndex((i) => i == 'queue_transfer') - - if (index != -1) { - - await itsm_response('\n\n') - - await monitoramento_response2(msg_endpoint2, wbot, contact, ticket, centro_de_custo, '\n\n', false) - - botSendMessage(ticket, contact, wbot, `Estamos direcionando seu atendimento para o Suporte. Em breve você será atendido por um de nossos atendentes!\n\nPara voltar ao atendimento *automatizado* e sair da fila de atendimento *humano* digite *0*`) - - await transferTicket(+msgAction.actions[index + 1], wbot, ticket, contact) - } - - } - } - - } - else if ( - - (endPointResponse.data.categoria == 'INFRAESTRUTURA' && endPointResponse.data.subcategoria == 'INTERNET' && - endPointResponse.data.terceiro_nivel == 'QUEDA TOTAL') || - - (endPointResponse.data.categoria == 'INFRAESTRUTURA' && endPointResponse.data.subcategoria == 'INTERNET' && - endPointResponse.data.terceiro_nivel == 'PROBLEMA DE LENTIDÃO') || - - (endPointResponse.data.categoria == 'ELOS' && endPointResponse.data.subcategoria == 'VENDAS') || - - (endPointResponse.data.categoria == 'ELOS' && endPointResponse.data.subcategoria == 'INDISPONIBILIDADE') - - ) { - - await itsm_response('\n\n') - - if (endPointResponse.data.categoria == 'INFRAESTRUTURA' && endPointResponse.data.subcategoria == 'INTERNET' && centro_de_custo) { - await monitoramento_response2(msg_endpoint2, wbot, contact, ticket, centro_de_custo, '\n\n _Digite *0* para voltar ao menu principal._', true) - } - - await sendMessageBot('Se deseja solicitar atendimento de urgência, digite *1*!', '\n\n _Digite *0* para voltar ao menu principal._') - - return + // } else { - //await sendMessageBot('A categorização desse chamado não se enquadra no atendimento deste canal!', '\n\n _Digite *0* para voltar ao menu principal._') - await itsm_response('\n\n') - if (endPointResponse.data.categoria == 'INFRAESTRUTURA' && endPointResponse.data.subcategoria == 'INTERNET' && centro_de_custo) { - await monitoramento_response2(msg_endpoint2, wbot, contact, ticket, centro_de_custo, '\n\n _Digite *0* para voltar ao menu principal._', true) - } + botSendMessage(ticket, `Ops! Não foi possível validar seu protocolo devido a um erro na comunicação com o servidor.Tente novamente mais tarde.\n_Digite *0* para falar com a HIT._`) - await sendMessageBot('Acompanhe a evolução do atendimento através do SOS') - - - return } - // else if ((endPointResponse.data.categoria == 'INFRAESTRUTURA' || endPointResponse.data.subcategoria == 'INTERNET' || - // endPointResponse.data.terceiro_nivel == 'QUEDA TOTAL' || endPointResponse.data.terceiro_nivel == 'PROBLEMA DE LENTIDÃO') || - // (endPointResponse.data.terceiro_nivel == 'PROBLEMA DE LENTIDÃO' || endPointResponse.data.terceiro_nivel == 'ABERTO')) { + } - // await itsm_response('', `\n Estamos direcionando seu atendimento para o Suporte. Em breve você será atendido por um de nossos atendentes!`) + if (params[0] === 'validate_n_chamado_web') return - // await monitoramento_response(msg_endpoint2) + if (params[0] === 'cod_web') { - // await transferTicket(0, wbot, ticket, contact) + response = await endPointQuery('http://177.107.192.247:8095/labs/monitoramentohit/api/api.php', + 'post', { + 'params[cod_web]': params[1], + 'method': 'omnihit.consultachamado' + }) - // } - // else { - - // await itsm_response('', `\n Por favor, aguarde atendimento e acompanhe sua solicitação no SOS.`) - - // await monitoramento_response(msg_endpoint2) - - // } } - else if (endPointResponse && endPointResponse.status == 200 && Object.entries(endPointResponse.data).length == 0) { - botSendMessage(ticket, contact, wbot, `Não existe nenhum chamado para consulta com esse número!\n _Digite *0* para voltar ao menu principal._`) - return + else if (params[0] === 'n_chamado_web') { + + response = await endPointQuery('http://177.107.192.247:8095/labs/monitoramentohit/api/api.php', + 'post', + { + 'params[n_chamado_web]': params[1], + 'method': 'omnihit.consultachamado', + }) + + } - else if (endPointResponse && endPointResponse.status == 500) { - botSendMessage(ticket, contact, wbot, `Houve um erro ao realizar a consulta no sos espacolaser!\n _Digite *0* para voltar ao menu principal._`) - return - } - else { - botSendMessage(ticket, contact, wbot, `Desculpe, nao foi possível realizar a consulta!\n _Digite *0* para voltar ao menu principal._`) - return + + try { + + if (response && response.data.result.trim().length > 0) { + response = response.data.result + } + else if (response && response.data.result.trim().length === 0) { + response = '' + } + else { + response = null + } + + // response = response.data.result + + if (response.trim().length == 0) { + + botSendMessage(ticket, `Não existe nenhum chamado para essa operação!\n _Digite *0* para falar com a HIT._`) + + } + else if (response.trim().length > 0) { + + await SendWhatsAppMessage({ body: response, ticket }); + + } + + } catch (error) { + botSendMessage(ticket, `Houve um erro ao realizar a consulta!\n _Digite *0* para falar com a HIT._`) } } else if (msgAction.actions[0] == 'queue_transfer') { console.log('>>>>>>>>>>>>>>> msgAction: ', msgAction, ' | msgAction.actions[1]: ', msgAction.actions[1]) - // const contact_custom_field = await ShowContactCustomFieldService(contact.id) + const contact_custom_field = await ShowContactCustomFieldService(contact.id) - // if (contact_custom_field && contact_custom_field.length > 0) { + if (contact_custom_field && contact_custom_field.length > 0) { - // const msg_endpoint = await queryEndPointHit(contact_custom_field[0].value) + const msg_endpoint = await queryEndPointHit(contact_custom_field[0].value) - // await monitoramento_response2(msg_endpoint, wbot, contact, ticket, contact_custom_field[0].value) + await monitoramento_response2(msg_endpoint, wbot, contact, ticket, contact_custom_field[0].value) - // } + } - // console.log('************* contact_custom_field: ', contact_custom_field) + console.log('************* contact_custom_field: ', contact_custom_field) // OLD // const msg = await wbot.sendMessage(`${contact.number}@c.us`, msgAction.msgBody); @@ -592,20 +555,22 @@ const extractCallCode = (str: string) => { return '' } -const sendDialogflowAwswer = async ( - wbot: Session, +const sendDialogflowAnswer = async ( + wbot: any, ticket: Ticket, msg: any, contact: Contact, chat: Chat, - startDialog: boolean = false + startDialog: boolean = false, + send: boolean = true + // chat: Chat ) => { - if (startDialog){ + if (startDialog) { msg.body = 'menu' console.log('<--------------------- VOLTOU AO MENU ---------------->') } - + console.log('uuuuuuuuuuuuuuuuuuuuuuuuuuu 04') @@ -620,14 +585,14 @@ const sendDialogflowAwswer = async ( console.log('uuuuuuuuuuuuuuuuuuuuuuuuuuu 4') if (msg.type != 'chat') { - botSendMessage(ticket, contact, wbot, `Desculpe, nao compreendi!\nEnvie apenas texto quando estiver interagindo com o bot!\n _Digite *0* para voltar ao menu principal._`) + botSendMessage(ticket, `Desculpe, nao compreendi!\nEnvie apenas texto quando estiver interagindo com o bot!\n _Digite *0* para falar com a HIT._`) return } console.log('uuuuuuuuuuuuuuuuuuuuuuuuuuu 5') if (msg.type == 'chat' && String(msg.body).length > 120) { - botSendMessage(ticket, contact, wbot, `Desculpe, nao compreendi!\nTexto acima de 120 caracteres!\n _Digite *0* para voltar ao menu principal._`) + botSendMessage(ticket, `Desculpe, nao compreendi!\nTexto acima de 120 caracteres!\n _Digite *0* para falar com a HIT._`) return } @@ -644,8 +609,10 @@ const sendDialogflowAwswer = async ( return; } + if (!send) return + // Make disponible later from session out - // chat.sendStateTyping(); + // chat.sendStateTyping(); await new Promise(f => setTimeout(f, 1000)); @@ -772,12 +739,11 @@ const verifyQueue = async ( console.log('uuuuuuuuuuuuuuuuuuuuuuuuuuu 3') const chat = wbot.chat - await sendDialogflowAwswer(wbot, _ticket, msg, contact, chat, aceppt); + await sendDialogflowAnswer(wbot, _ticket, msg, contact, chat, aceppt); console.log('uuuuuuuuuuuuuuuuuuuuuuuuuuu 03') return - } // @@ -792,9 +758,6 @@ const verifyQueue = async ( body = `\u200e${choosenQueue.greetingMessage}`; } - // const sentMessage = await wbot.sendMessage(`${contact.number}@c.us`, body); - // await verifyMessage(sentMessage, ticket, contact); - sendWhatsAppMessageSocket(ticket, body) // console.log('uuuuuuuuuuuuuuuuuuuuuuuuuuu 3 body: ',body) @@ -802,10 +765,9 @@ const verifyQueue = async ( } else { + const repet: any = await mostRepeatedPhrase(ticket.id) - //test del transfere o atendimento se entrar na ura infinita - let ticket_message = await ShowTicketMessage(ticket.id, false); - if (ticket_message.length > 20) { + if (repet.occurrences > 4) { await UpdateTicketService({ ticketData: { status: 'pending', queueId: queues[0].id }, ticketId: ticket.id }); @@ -835,8 +797,8 @@ const verifyQueue = async ( bodySplited.forEach(e => { - if(e && e.trim().length>0){ - console.log('ELEMENT e: ',e) + if (e && e.trim().length > 0) { + console.log('ELEMENT e: ', e) sendWhatsAppMessageSocket(ticket, e) } @@ -858,7 +820,7 @@ const verifyQueue = async ( } }; -const transferTicket = async (queueIndex: number, wbot: Session, ticket: Ticket, contact: Contact) => { +const transferTicket = async (queueIndex: number, wbot: any, ticket: Ticket, contact: Contact) => { const botInfo = await BotIsOnQueue('botqueue', contact.accept) @@ -868,10 +830,13 @@ const transferTicket = async (queueIndex: number, wbot: Session, ticket: Ticket, // console.log('queues ---> ', queues) - await botTransferTicket(queues[queueIndex], ticket, contact, wbot) + await botTransferTicket(queues[queueIndex], ticket) } + + + // const botMsgActions = (params: string) => { // let lstActions = params.split('dialog_actions=') @@ -914,7 +879,7 @@ const isValidMsg = (msg: WbotMessage): boolean => { }; -const queuesOutBot = async (wbot: Session, botId: string | number) => { +const queuesOutBot = async (wbot: any, botId: string | number) => { const { queues, greetingMessage } = await ShowWhatsAppService(wbot.id!); @@ -928,7 +893,9 @@ const queuesOutBot = async (wbot: Session, botId: string | number) => { } -const botTransferTicket = async (queues: Queue, ticket: Ticket, contact: Contact, wbot: Session) => { +const botTransferTicket = async (queues: Queue, ticket: Ticket) => { + + console.log('>>>>>>>>>>>>>>>>> queues.id: ', queues.id) await ticket.update({ userId: null }); @@ -936,7 +903,7 @@ const botTransferTicket = async (queues: Queue, ticket: Ticket, contact: Contact } -const botSendMedia = async (ticket: Ticket, contact: Contact, wbot: Session, mediaPath: string, fileNameExtension: string) => { +const botSendMedia = async (ticket: Ticket, contact: Contact, wbot: any, mediaPath: string, fileNameExtension: string) => { const debouncedSentMessage = debounce( @@ -965,7 +932,7 @@ const botSendMedia = async (ticket: Ticket, contact: Contact, wbot: Session, med } -const botSendMessage = (ticket: Ticket, contact: Contact, wbot: Session, msg: string) => { +const botSendMessage = (ticket: Ticket, msg: string) => { const debouncedSentMessage = debounce( @@ -1016,7 +983,7 @@ const handleMessage = async ( if (index == -1) { - // console.log('-----------------> LST: ', lst) + // console.log('-----------------> LST: ', lst):q lst.push({ id: msg.id.id }) @@ -1107,6 +1074,12 @@ const handleMessage = async ( // const chat = await msg.getChat(); const chat = wbot.chat + // if(chat.isGroup){ + + // console.log('This message is from a Group and will be ignored!') + // return + // } + // console.log('----------> chat: ', JSON.parse(JSON.stringify(chat))) // if (chat.isGroup) { @@ -1129,7 +1102,7 @@ const handleMessage = async ( const unreadMessages = msg.fromMe ? 0 : chat.unreadCount; - const contact = await verifyContact(msgContact); + const contact: any = await verifyContact(msgContact); // console.log('----------> contact: ', JSON.parse(JSON.stringify(contact))) @@ -1145,15 +1118,53 @@ const handleMessage = async ( // } + let ticket - if (unreadMessages === 0 && whatsapp.farewellMessage && whatsapp.farewellMessage === msg.body) return; + const _botInfo = await BotIsOnQueue('botqueue') + + if (_botInfo.isOnQueue) { + + let ticket_obj: any = await FindOrCreateTicketServiceBot( + contact, + wbot.id!, + unreadMessages, + // groupContact + ); + + ticket = ticket_obj.ticket + + if (ticket_obj.created) { + + let queue = await ShowQueueService(_botInfo.botQueueId); + + await UpdateTicketService({ + ticketData: { queueId: queue.id }, + ticketId: ticket.id + }); + + ticket = await ShowTicketService(ticket.id); + } + + + } + else { + ticket = await FindOrCreateTicketService( + contact, + wbot.id!, + unreadMessages, + // groupContact + ); + } + + + + // const ticket = await FindOrCreateTicketService( + // contact, + // wbot.id!, + // unreadMessages, + // // groupContact + // ); - const ticket = await FindOrCreateTicketService( - contact, - wbot.id!, - unreadMessages, - // groupContact - ); // // await updateTicketCacheByTicketId(ticket.id, {'contact.profilePicUrl': ticket.contact.profilePicUrl}) @@ -1190,9 +1201,7 @@ const handleMessage = async ( !ticket.userId && whatsapp.queues.length >= 1 ) { - await verifyQueue(wbot, msg, ticket, contact); - } // O bot interage com o cliente e encaminha o atendimento para fila de atendende quando o usuário escolhe a opção falar com atendente @@ -1202,40 +1211,75 @@ const handleMessage = async ( // const botInfo = { isOnQueue: false, botQueueId: 0, userIdBot: 0 } if (botInfo.isOnQueue && !msg.fromMe && ticket.userId == botInfo.userIdBot) { - // TEST DEL - let test: any = await ShowTicketMessage(ticket.id, false, true, 5); + const repet: any = await mostRepeatedPhrase(ticket.id) - if (test && test.length > 0 && test[0].body.includes('Se deseja solicitar atendimento de urgência, digite *1*') && msg.body == '1') { + console.log('repet.occurrences: ', repet.occurrences) - console.log('===================================> ENDPOINT REQUEST') + if (repet.occurrences > 4) { - for (let i = 0; i < test.length; i++) { + await transferTicket(0, wbot, ticket, contact.accept) - if (test[i].body.includes('*categoria*: INFRAESTRUTURA')) { + await SendWhatsAppMessage({ + body: `Seu atendimento foi transferido para um agente!\n\nPara voltar ao menu principal digite *0* + `, ticket, number: `${contact.number}@c.us` + }) - botSendMessage(ticket, contact, wbot, `Estamos direcionando seu atendimento para o Suporte. Em breve você será atendido por um de nossos atendentes!\n\nPara voltar ao atendimento *automatizado* e sair da fila de atendimento *humano* digite *0*`) + } + else { - await transferTicket(0, wbot, ticket, contact) - break + let last_messages = await ShowTicketMessage(ticket.id, 2, true) + + if (last_messages.length > 0 && last_messages[0].body.includes('validado') && msg.body.trim() != '0') { + + // botSendMessage(ticket,`Aguarde, inserindo informação...\n_Digite *0* para falar com a HIT._`) + + await SendWhatsAppMessage({ body: `Aguarde inserindo informação, em breve te atualizaremos`, ticket }) + + + let aux_msg = last_messages[0].body + + aux_msg = aux_msg.split('\n')[0] + + let index = aux_msg.indexOf('do protocolo ') + + aux_msg = aux_msg.substring(index, aux_msg.length) + + let regex = /[0-9-]+/g; + + let matches: any = aux_msg.match(regex); + + console.log("~~~~~~~~~~~~~~~~~~~~~~> matches.join(''): ", matches.join('')); + + let response = await endPointQuery( + 'http://177.107.192.247:8095/labs/monitoramentohit/api/api.php', + 'post', + { + 'params[n_chamado_web]': matches.join(''), + 'method': 'omnihit.chamadoaddobs', + 'params[obs]': msg.body + }) + + if (response && response.data.result) { + + botSendMessage(ticket, `${response.data.result}`) } - else if (test[i].body.includes('*categoria*: ELOS')) { + else { - botSendMessage(ticket, contact, wbot, `Estamos direcionando seu atendimento para o Suporte. Em breve você será atendido por um de nossos atendentes!\n\nPara voltar ao atendimento *automatizado* e sair da fila de atendimento *humano* digite *0*`) + botSendMessage(ticket, `Ops! Houve um erro ao tentar inserir sua informação devido a um erro na comunicação com o servidor.Tente novamente mais tarde.\n_Digite *0* para falar com a HIT._`) - await transferTicket(1, wbot, ticket, contact) - break } + + } + else { + + await sendDialogflowAnswer(wbot, ticket, msg, contact, chat, contact.accept); + } - return } - // - - await sendDialogflowAwswer(wbot, ticket, msg, contact, chat); - } else if (botInfo.isOnQueue && !msg.fromMe && msg.body == '0' && ticket.status == 'pending' && ticket.queueId) { @@ -1251,54 +1295,28 @@ const handleMessage = async ( // const chat = await msg.getChat(); const chat = wbot.chat - await sendDialogflowAwswer(wbot, _ticket, msg, contact, chat); + await sendDialogflowAnswer(wbot, _ticket, msg, contact, chat, contact.aceppt); return } - // if (msg.body.trim() == 'broken') { - // throw new Error('Throw makes it go boom!') - // } - // + if (msg && !msg.fromMe && ticket.status == 'pending') { + + await setMessageAsRead(ticket) + + } + } catch (err) { Sentry.captureException(err); + console.log('xxxxxxxxxxxxx err: ', err) logger.error(`Error handling whatsapp message: Err: ${err}`); - - const whatsapp = await ShowWhatsAppService(wbot.id!); - - //Solução para contornar erro de sessão - if ((`${err}`).includes("Evaluation failed: r")) { - - const sourcePath = path.join(__dirname, `../../../.wwebjs_auth/sessions/log`) - - let log = new Date(new Date() + 'UTC'); - const dateToday = splitDateTime(new Date(format(new Date(), 'yyyy-MM-dd HH:mm:ss', { locale: ptBR }))) - - if (whatsapp.status == 'CONNECTED') { - - - - let timestamp = Math.floor(Date.now() / 1000) - - fs.writeFile(`${sourcePath}/${timestamp}_wbotMessageListener.txt`, `Whatsapp id: ${whatsapp.id} \nDate: ${dateToday.fullDate} ${dateToday.fullTime} \nFile: wbotMessageListener.ts \nError: ${err}`, (error) => { }); - - // await restartWhatsSession(whatsapp) - - } - - } - else if (`${err}`.includes('[object Object]')) { - - await _restore(whatsapp, 'auto_object_error') - - } - } }; const handleMsgAck = async (msg_id: any, ack: any) => { - await new Promise(r => setTimeout(r, 500)); + + await new Promise(r => setTimeout(r, 4000)); const io = getIO(); @@ -1314,6 +1332,7 @@ const handleMsgAck = async (msg_id: any, ack: any) => { ] }); if (!messageToUpdate) { + console.log(`Not found the MESSAGE ID ${msg_id}to change the ACK into the Message table`) return; } await messageToUpdate.update({ ack }); @@ -1331,19 +1350,19 @@ const handleMsgAck = async (msg_id: any, ack: any) => { } }; -const wbotMessageListener = (wbot: Session): void => { +const wbotMessageListener = (wbot: any): void => { - wbot.on("message_create", async msg => { + wbot.on("message_create", async (msg: any) => { handleMessage(msg, wbot); }); - wbot.on("media_uploaded", async msg => { + wbot.on("media_uploaded", async (msg: any) => { handleMessage(msg, wbot); }); - wbot.on("message_ack", async (msg, ack) => { + wbot.on("message_ack", async (msg: any, ack: any) => { handleMsgAck(msg, ack); }); }; -export { wbotMessageListener, handleMessage, handleMsgAck, lst }; +export { wbotMessageListener, handleMessage, handleMsgAck, lst, verifyContact, sendDialogflowAnswer }; diff --git a/frontend/src.7z b/frontend/src.7z new file mode 100644 index 0000000..665cdbc Binary files /dev/null and b/frontend/src.7z differ diff --git a/frontend/src/components/ContactCreateTicketModal/index.js b/frontend/src/components/ContactCreateTicketModal/index.js new file mode 100644 index 0000000..55fa06b --- /dev/null +++ b/frontend/src/components/ContactCreateTicketModal/index.js @@ -0,0 +1,156 @@ +import React, { useState, useEffect, useContext, useRef, useCallback } from "react"; +import { useHistory } from "react-router-dom"; +import { toast } from "react-toastify"; + +import Button from "@material-ui/core/Button"; +import Dialog from "@material-ui/core/Dialog"; +import Select from "@material-ui/core/Select"; +import FormControl from "@material-ui/core/FormControl"; +import InputLabel from "@material-ui/core/InputLabel"; +import MenuItem from "@material-ui/core/MenuItem"; +import LinearProgress from "@material-ui/core/LinearProgress"; +import { makeStyles } from "@material-ui/core"; + +import DialogActions from "@material-ui/core/DialogActions"; +import DialogContent from "@material-ui/core/DialogContent"; +import DialogTitle from "@material-ui/core/DialogTitle"; + +import { i18n } from "../../translate/i18n"; +import ButtonWithSpinner from "../ButtonWithSpinner"; +import { AuthContext } from "../../context/Auth/AuthContext"; + +import toastError from "../../errors/toastError"; + +import api from "../../services/api"; + +const useStyles = makeStyles((theme) => ({ + maxWidth: { + width: "100%", + }, + paper: { + minWidth: "300px" + }, + linearProgress: { + marginTop: "5px" + } +})); + +const ContactCreateTicketModal = ({ modalOpen, onClose, contactId }) => { + const { user } = useContext(AuthContext); + let isMounted = useRef(true) + + const history = useHistory(); + const [queues, setQueues] = useState([]); + const [loading, setLoading] = useState(false); + const [selectedQueue, setSelectedQueue] = useState(''); + const [itemHover, setItemHover] = useState(-1) + const classes = useStyles(); + + useEffect(() => { + const userQueues = user.queues.map(({ id, name, color }) => { return { id, name, color } }) + if (userQueues.length === 1) setSelectedQueue(userQueues[0].id) + setQueues(userQueues) + }, [user]); + + const handleClose = () => { + onClose(); + }; + + const handleSaveTicket = useCallback(async (contactId, userId, queueId) => { + if (!contactId || !userId) { + console.log("Missing contactId or userId") + return + }; + if (!queueId) { + toast.warning("Nenhuma Fila Selecionada") + return + } + if (isMounted.current) setLoading(true); + + + const delayDebounceFn = setTimeout(() => { + const ticketCreate = async () => { + try { + const { data: ticket } = await api.post("/tickets", { + contactId: contactId, + userId: userId, + queueId: queueId, + status: "open", + }); + history.push(`/tickets/${ticket.id}`); + } catch (err) { + toastError(err); + } + if (isMounted.current) setLoading(false); + + }; + ticketCreate(); + }, 300); + return () => clearTimeout(delayDebounceFn); + + }, [history]) + + useEffect(() => { + if (modalOpen && queues.length <= 1) { + handleSaveTicket(contactId, user.id, selectedQueue) + } + return () => { + isMounted.current = false; + }; + }, [modalOpen, contactId, user.id, selectedQueue, handleSaveTicket, queues.length]); + + if (modalOpen && queues.length <= 1) { + return + } + + return ( + + + {i18n.t("newTicketModal.title")} + + + + {i18n.t("Selecionar Fila")} + + + + + + handleSaveTicket(contactId, user.id, selectedQueue)} + variant="contained" + color="primary" + loading={loading} + > + {i18n.t("newTicketModal.buttons.ok")} + + + + ); +}; + +export default ContactCreateTicketModal; \ No newline at end of file diff --git a/frontend/src/components/MessagesList/index.js b/frontend/src/components/MessagesList/index.js index 5c02bf0..236601a 100644 --- a/frontend/src/components/MessagesList/index.js +++ b/frontend/src/components/MessagesList/index.js @@ -1,8 +1,9 @@ -import React, { useState, useEffect, useReducer, useRef } from "react"; +import React, { useContext, useState, useEffect, useReducer, useRef } from "react"; import { isSameDay, parseISO, format } from "date-fns"; import openSocket from "socket.io-client"; import clsx from "clsx"; +import { AuthContext } from "../../context/Auth/AuthContext"; import { green } from "@material-ui/core/colors"; import { @@ -318,6 +319,9 @@ const MessagesList = ({ ticketId, isGroup }) => { const [anchorEl, setAnchorEl] = useState(null); const messageOptionsMenuOpen = Boolean(anchorEl); const currentTicketId = useRef(ticketId); + const [sendSeen, setSendSeen] = useState(false) + + const { user } = useContext(AuthContext); useEffect(() => { dispatch({ type: "RESET" }); @@ -327,13 +331,77 @@ const MessagesList = ({ ticketId, isGroup }) => { }, [ticketId]); useEffect(() => { + + let url_split + let url_ticketId + + try { + + url_split = window.location.href.split('tickets') + + url_ticketId = url_split[url_split.length - 1].match(/\d+/)[0] + + } catch (error) { + console.log('error on try do the send seen: ', error) + } + + if (!url_ticketId) return + + if (!sendSeen) return + + const delayDebounceFn = setTimeout(() => { + const sendSeenMessage = async () => { + try { + const { data } = await api.get("/messages/" + ticketId, { + params: { pageNumber }, + }); + + setSendSeen(false) + + if (!data) return + + if (data.ticket.status === "open" && /*data.ticket.unreadMessages > 0 &&*/ + data.ticket.userId === user.id) { + + let fromMe = false + + if (data.messages.length > 0) { + fromMe = data.messages[data.messages.length - 1].fromMe + } + + // Atualiza Unread messages para 0 + // Atualizei função no back-end para receber o novo parametro unreadMessages + const ticketUpdate = { ...data.ticket, unreadMessages: 0, fromMe } + await api.put("/tickets/" + ticketId, ticketUpdate) + + } + + } catch (err) { + setLoading(false); + toastError(err); + } + }; + sendSeenMessage(); + }, 500); + + + return () => { + clearTimeout(delayDebounceFn); + }; + + + + }, [sendSeen, pageNumber, ticketId, user.id]); + + useEffect(() => { + setLoading(true); const delayDebounceFn = setTimeout(() => { const fetchMessages = async () => { try { const { data } = await api.get("/messages/" + ticketId, { params: { pageNumber }, - }); + }); if (currentTicketId.current === ticketId) { dispatch({ type: "LOAD_MESSAGES", payload: data.messages }); @@ -365,17 +433,12 @@ const MessagesList = ({ ticketId, isGroup }) => { if (data.action === "create") { - console.log('ADD_MESSAGE: ', data.message) - dispatch({ type: "ADD_MESSAGE", payload: data.message }); scrollToBottom(); } if (data.action === "update") { - - console.log('joinChatBox update: ',data.action) - dispatch({ type: "UPDATE_MESSAGE", payload: data.message }); } }); @@ -391,7 +454,11 @@ const MessagesList = ({ ticketId, isGroup }) => { const scrollToBottom = () => { if (lastMessageRef.current) { + + setSendSeen(true) + lastMessageRef.current.scrollIntoView({}); + } }; @@ -513,7 +580,7 @@ const MessagesList = ({ ticketId, isGroup }) => { if (index === messagesList.length - 1) { let messageDay = parseISO(messagesList[index].createdAt); - let previousMessageDay = parseISO(messagesList[index - 1].createdAt); + let previousMessageDay = parseISO(messagesList[index - 1].createdAt); return ( <> @@ -682,4 +749,4 @@ const MessagesList = ({ ticketId, isGroup }) => { ); }; -export default MessagesList; +export default MessagesList; \ No newline at end of file diff --git a/frontend/src/components/NotificationsPopOver/index.js b/frontend/src/components/NotificationsPopOver/index.js index 86b8363..1386087 100644 --- a/frontend/src/components/NotificationsPopOver/index.js +++ b/frontend/src/components/NotificationsPopOver/index.js @@ -116,17 +116,13 @@ const NotificationsPopOver = () => { const socket = openSocket(process.env.REACT_APP_BACKEND_URL); - socket.on("reload_page", (data) => { - - console.log('UPDATING THE PAGE: ', data.userId, ' | user.id: ', user.id) + socket.on("reload_page", (data) => { if (user.id === data.userId) { window.location.reload(true); - } - - + } }) @@ -167,8 +163,7 @@ const NotificationsPopOver = () => { clearInterval(_fifo); } - _fifo = setInterval(() => { - console.log('user.id: ', user.id) + _fifo = setInterval(() => { socket.emit("online", user.id) }, 3000); diff --git a/frontend/src/components/Report/DatePicker/index.js b/frontend/src/components/Report/DatePicker/index.js index 7a28cb7..64e9b80 100644 --- a/frontend/src/components/Report/DatePicker/index.js +++ b/frontend/src/components/Report/DatePicker/index.js @@ -11,7 +11,7 @@ import { -import { i18n } from "../../../translate/i18n"; +// import { i18n } from "../../../translate/i18n"; import ptBrLocale from "date-fns/locale/pt-BR"; diff --git a/frontend/src/components/Ticket/index.js b/frontend/src/components/Ticket/index.js index 6b4a1cb..a88507b 100644 --- a/frontend/src/components/Ticket/index.js +++ b/frontend/src/components/Ticket/index.js @@ -83,14 +83,14 @@ const Ticket = () => { const [contact, setContact] = useState({}); const [ticket, setTicket] = useState({}); - const [statusChatEnd, setStatusChatEnd] = useState({}) + const [statusChatEnd, setStatusChatEnd] = useState({}) useEffect(() => { setLoading(true); const delayDebounceFn = setTimeout(() => { const fetchTicket = async () => { try { - + // maria julia const { data } = await api.get("/tickets/" + ticketId); @@ -101,7 +101,7 @@ const Ticket = () => { setContact(data.contact.contact); setTicket(data.contact); - setStatusChatEnd(data.statusChatEnd) + setStatusChatEnd(data.statusChatEnd) setLoading(false); } catch (err) { @@ -171,8 +171,8 @@ const Ticket = () => { onClick={handleDrawerOpen} /> -
- +
+
diff --git a/frontend/src/components/TicketActionButtons/index.js b/frontend/src/components/TicketActionButtons/index.js index 880d60a..8382add 100644 --- a/frontend/src/components/TicketActionButtons/index.js +++ b/frontend/src/components/TicketActionButtons/index.js @@ -29,12 +29,24 @@ const useStyles = makeStyles(theme => ({ }, })); -const TicketActionButtons = ({ ticket, statusChatEnd }) => { +const TicketActionButtons = ({ ticket, statusChatEnd }) => { const classes = useStyles(); - const history = useHistory(); + const history = useHistory(); + const [anchorEl, setAnchorEl] = useState(null); const [loading, setLoading] = useState(false); - const [useDialogflow, setUseDialogflow] = useState(ticket.contact.useDialogflow); + + // const [useDialogflow, setUseDialogflow] = useState(ticket.contact.useDialogflow); + + // const [/*useDialogflow*/, setUseDialogflow] = useState(() => { + // if (Object.keys(ticket).length !== 0) { + // return ticket.contact.useDialogflow; + // } else { + // // Set a default value if `ticket.contact.useDialogflow` is null + // return true + // } + // }); + const ticketOptionsMenuOpen = Boolean(anchorEl); const { user } = useContext(AuthContext); @@ -53,9 +65,7 @@ const TicketActionButtons = ({ ticket, statusChatEnd }) => { if (data) { - data = { ...data, 'ticketId': ticket.id } - - + data = { ...data, 'ticketId': ticket.id } handleUpdateTicketStatus(null, "closed", user?.id, data) @@ -122,17 +132,17 @@ const TicketActionButtons = ({ ticket, statusChatEnd }) => { }; - const handleContactToggleUseDialogflow = async () => { - setLoading(true); - try { - const contact = await api.put(`/contacts/toggleUseDialogflow/${ticket.contact.id}`); - setUseDialogflow(contact.data.useDialogflow); - setLoading(false); - } catch (err) { - setLoading(false); - toastError(err); - } - }; + // const handleContactToggleUseDialogflow = async () => { + // setLoading(true); + // try { + // const contact = await api.put(`/contacts/toggleUseDialogflow/${ticket.contact.id}`); + // setUseDialogflow(contact.data.useDialogflow); + // setLoading(false); + // } catch (err) { + // setLoading(false); + // toastError(err); + // } + // }; return ( diff --git a/frontend/src/components/TicketsList/index.js b/frontend/src/components/TicketsList/index.js index fb2ba3c..6e5bf4b 100644 --- a/frontend/src/components/TicketsList/index.js +++ b/frontend/src/components/TicketsList/index.js @@ -1,4 +1,6 @@ import React, { useState, useEffect, useReducer, useContext } from "react"; + + import openSocket from "socket.io-client"; import { makeStyles } from "@material-ui/core/styles"; @@ -175,7 +177,7 @@ const reducer = (state, action) => { }; const TicketsList = (props) => { - const { status, searchParam, showAll, selectedQueueIds, updateCount, style, tab } = props; + const { status, searchParam, searchParamContent, showAll, selectedQueueIds, updateCount, style, tab } = props; const classes = useStyles(); const [pageNumber, setPageNumber] = useState(1); const [ticketsList, dispatch] = useReducer(reducer, []); @@ -184,16 +186,16 @@ const TicketsList = (props) => { const { searchTicket } = useContext(SearchTicketContext) useEffect(() => { + dispatch({ type: "RESET" }); setPageNumber(1); - }, [status, searchParam, showAll, selectedQueueIds, searchTicket]); - - + }, [status, searchParam, searchParamContent, showAll, selectedQueueIds, searchTicket]); const { tickets, hasMore, loading } = useTickets({ pageNumber, searchParam, + searchParamContent, status, showAll, queueIds: JSON.stringify(selectedQueueIds), @@ -201,6 +203,7 @@ const TicketsList = (props) => { }); useEffect(() => { + if (!status && !searchParam) return; // if (searchParam) { diff --git a/frontend/src/components/TicketsManager/index.js b/frontend/src/components/TicketsManager/index.js index 038601e..a6f80d5 100644 --- a/frontend/src/components/TicketsManager/index.js +++ b/frontend/src/components/TicketsManager/index.js @@ -1,14 +1,21 @@ import React, { useContext, useEffect, useRef, useState } from "react"; import { makeStyles } from "@material-ui/core/styles"; +import { IconButton } from "@mui/material"; import Paper from "@material-ui/core/Paper"; -import SearchIcon from "@material-ui/icons/Search"; import InputBase from "@material-ui/core/InputBase"; import Tabs from "@material-ui/core/Tabs"; import Tab from "@material-ui/core/Tab"; import Badge from "@material-ui/core/Badge"; + +import Tooltip from "@material-ui/core/Tooltip"; + + +import SearchIcon from "@material-ui/icons/Search"; import MoveToInboxIcon from "@material-ui/icons/MoveToInbox"; import CheckBoxIcon from "@material-ui/icons/CheckBox"; +import MenuIcon from "@material-ui/icons/Menu"; +import FindInPageIcon from '@material-ui/icons/FindInPage'; import FormControlLabel from "@material-ui/core/FormControlLabel"; import Switch from "@material-ui/core/Switch"; @@ -63,6 +70,25 @@ const useStyles = makeStyles((theme) => ({ }, serachInputWrapper: { + flex: 1, + display: "flex", + flexDirection: "column", + gap: "10px", + borderRadius: 40, + padding: 4, + marginRight: theme.spacing(1), + }, + + searchInputHeader: { + flex: 1, + background: "#fff", + display: "flex", + borderRadius: 40, + padding: 4, + marginRight: theme.spacing(1), + }, + + searchContentInput: { flex: 1, background: "#fff", display: "flex", @@ -78,6 +104,11 @@ const useStyles = makeStyles((theme) => ({ alignSelf: "center", }, + menuSearch: { + color: "grey", + alignSelf: "center", + }, + searchInput: { flex: 1, border: "none", @@ -95,20 +126,21 @@ const useStyles = makeStyles((theme) => ({ }, })); +const DEFAULT_SEARCH_PARAM = { searchParam: "", searchParamContent: "" } + const TicketsManager = () => { const { tabOption, setTabOption } = useContext(TabTicketContext); - const {setSearchTicket} = useContext(SearchTicketContext) + const { setSearchTicket } = useContext(SearchTicketContext) const classes = useStyles(); - const [searchParam, setSearchParam] = useState(""); + const [searchParam, setSearchParam] = useState(DEFAULT_SEARCH_PARAM); const [tab, setTab] = useState("open"); const [tabOpen, setTabOpen] = useState("open"); const [newTicketModalOpen, setNewTicketModalOpen] = useState(false); const [showAllTickets, setShowAllTickets] = useState(false); - const searchInputRef = useRef(); const { user } = useContext(AuthContext); const [openCount, setOpenCount] = useState(0); @@ -117,7 +149,16 @@ const TicketsManager = () => { const userQueueIds = user.queues.map((q) => q.id); const [selectedQueueIds, setSelectedQueueIds] = useState(userQueueIds || []); + const [showContentSearch, setShowContentSearch] = useState(false) + const searchInputRef = useRef(); + const searchContentInputRef = useRef(); const [inputSearch, setInputSearch] = useState(''); + const [inputContentSearch, setInputContentSearch] = useState("") + + const [openTooltipSearch, setOpenTooltipSearch] = useState(false) + + let searchTimeout; + let searchContentTimeout; useEffect(() => { if (user.profile.toUpperCase() === "ADMIN") { @@ -135,22 +176,45 @@ const TicketsManager = () => { }, [tab, setTabOption]); + useEffect(() => { + + // clearTimeout(searchContentTimeout); + + // setSearchParam(prev => ({ ...prev, searchParamContent: "" })) + + if (!inputContentSearch) return + + if (!searchContentTimeout) return + + // searchContentTimeout = setTimeout(() => { + + // setSearchParam(prev => ({ ...prev, searchParamContent: inputContentSearch })); + + // }, 500); + + clearTimeout(searchContentTimeout); + + setSearchParam(prev => ({ ...prev, searchParamContent: "" })) + + + }, [inputContentSearch, searchContentTimeout]); + useEffect(() => { if (tabOption === 'open') { setTabOption('') - setSearchParam(''); + setSearchParam(DEFAULT_SEARCH_PARAM); setInputSearch(''); + setInputContentSearch('') setTab("open"); return; } }, [tabOption, setTabOption]) - let searchTimeout; - + const removeExtraSpace = (str) => { str = str.replace(/^\s+/g, '') @@ -163,24 +227,58 @@ const TicketsManager = () => { setInputSearch(removeExtraSpace(searchedTerm)) - setSearchTicket(searchParam) + setSearchTicket(searchParam.searchParam) clearTimeout(searchTimeout); if (searchedTerm === "") { - setSearchParam(searchedTerm); + setSearchParam(prev => ({ ...prev, searchParam: searchedTerm })) setInputSearch(searchedTerm) + setShowContentSearch(false) setTab("open"); return; } + if (searchedTerm.length < 4) { + setSearchParam(prev => ({ ...prev, searchParamContent: "" })) + setInputContentSearch('') + } + + searchTimeout = setTimeout(() => { - setSearchParam(searchedTerm); + setSearchParam(prev => ({ ...prev, searchParam: searchedTerm })); }, 500); }; + const handleContentSearch = e => { + + let searchedContentText = removeExtraSpace(e.target.value.toLowerCase()) + + setInputContentSearch(searchedContentText) + + searchContentTimeout = setTimeout(() => { + + setSearchParam(prev => ({ ...prev, searchParamContent: searchedContentText })); + + }, 500); + + } + + const handleOpenTooltipSearch = () => { + if (searchParam.searchParam.length < 4) { + setOpenTooltipSearch(true) + } + } + + const handleCloseTooltipSearch = () => { + setOpenTooltipSearch(false) + if (searchParam.searchParam.length < 4) { + searchInputRef.current.focus() + } + } + const handleChangeTab = (e, newValue) => { setTab(newValue); }; @@ -233,15 +331,50 @@ const TicketsManager = () => { {tab === "search" ? (
- - +
+ + + {/* setShowContentSearch(prev => !prev)}> + + */} + handleOpenTooltipSearch()} + onClose={() => handleCloseTooltipSearch()} + title="Digite pelo menos 4 caracteres" + arrow> + + setShowContentSearch(prev => !prev)} + > + + + + +
+ { + // showContentSearch ? + (showContentSearch && searchParam.searchParam.length >= 4) ? + (
+ + handleContentSearch(e)} + /> +
) : null + }
) : ( <> @@ -340,17 +473,18 @@ const TicketsManager = () => { - - - + + +
); }; -export default TicketsManager; +export default TicketsManager; \ No newline at end of file diff --git a/frontend/src/components/TransferTicketModal/index.js b/frontend/src/components/TransferTicketModal/index.js index 2707969..8680578 100644 --- a/frontend/src/components/TransferTicketModal/index.js +++ b/frontend/src/components/TransferTicketModal/index.js @@ -1,8 +1,7 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useContext, useMemo } from "react"; import { useHistory } from "react-router-dom"; import Button from "@material-ui/core/Button"; -import TextField from "@material-ui/core/TextField"; import Dialog from "@material-ui/core/Dialog"; import Select from "@material-ui/core/Select"; import FormControl from "@material-ui/core/FormControl"; @@ -13,102 +12,72 @@ import { makeStyles } from "@material-ui/core"; import DialogActions from "@material-ui/core/DialogActions"; import DialogContent from "@material-ui/core/DialogContent"; import DialogTitle from "@material-ui/core/DialogTitle"; -import Autocomplete, { - createFilterOptions, -} from "@material-ui/lab/Autocomplete"; -import CircularProgress from "@material-ui/core/CircularProgress"; import { i18n } from "../../translate/i18n"; import api from "../../services/api"; import ButtonWithSpinner from "../ButtonWithSpinner"; import toastError from "../../errors/toastError"; -import useQueues from "../../hooks/useQueues"; + +import { WhatsAppsContext } from "../../context/WhatsApp/WhatsAppsContext"; const useStyles = makeStyles((theme) => ({ - maxWidth: { - width: "100%", - }, + maxWidth: { + width: "100%", + }, })); -const filterOptions = createFilterOptions({ - trim: true, -}); +// Receive array of queues arrays +// Return a new array with unique queues from all arrays has passed by the parameter +const queueArraysToOneArray = (array) => { + if (!array) return [] + const map = {} + const uniqueQueuesAvailable = [] + array.forEach((queues) => { + queues.forEach(({ id, name, color }) => { + if (!map[id]) { + map[id] = true + uniqueQueuesAvailable.push({ id, name, color }) + } + }) + }) + return uniqueQueuesAvailable +} + const TransferTicketModal = ({ modalOpen, onClose, ticketid }) => { const history = useHistory(); - const [options, setOptions] = useState([]); - const [queues, setQueues] = useState([]); - const [allQueues, setAllQueues] = useState([]); + const { whatsApps } = useContext(WhatsAppsContext); const [loading, setLoading] = useState(false); - const [searchParam, setSearchParam] = useState(""); - const [selectedUser, setSelectedUser] = useState(null); const [selectedQueue, setSelectedQueue] = useState(''); const classes = useStyles(); - const { findAll: findAllQueues } = useQueues(); - - useEffect(() => { - const loadQueues = async () => { - const list = await findAllQueues(); - setAllQueues(list); - setQueues(list); - } - loadQueues(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - useEffect(() => { - if (!modalOpen || searchParam.length < 3) { - setLoading(false); - return; - } - setLoading(true); - const delayDebounceFn = setTimeout(() => { - const fetchUsers = async () => { - try { - const { data } = await api.get("/users/", { - params: { searchParam }, - }); - setOptions(data.users); - setLoading(false); - } catch (err) { - setLoading(false); - toastError(err); - } - }; - - fetchUsers(); - }, 500); - return () => clearTimeout(delayDebounceFn); - }, [searchParam, modalOpen]); + const queues = useMemo(() => { + if (!whatsApps) return [] + const whatsAppsQueues = whatsApps.map(({ queues }) => queues) + //const whatsAppsQueues = whatsApps.filter(({ status }) => status === "CONNECTED" ).map(({ queues }) => queues) + const uniqueQueuesAvailable = queueArraysToOneArray(whatsAppsQueues) + return uniqueQueuesAvailable + }, [whatsApps]) + const [itemHover, setItemHover] = useState(-1) const handleClose = () => { onClose(); - setSearchParam(""); - setSelectedUser(null); }; const handleSaveTicket = async e => { e.preventDefault(); if (!ticketid) return; + if (!selectedQueue) return; setLoading(true); try { let data = {}; - if (selectedUser) { - data.userId = selectedUser.id - } - if (selectedQueue && selectedQueue !== null) { data.queueId = selectedQueue - - if (!selectedUser) { - data.status = 'pending'; - data.userId = null; - } } // test del PARA APARECER NA FILA DE OUTRO ATENDENTE E O MESMO CLICAR EM ACEITAR AO INVES DE ENVIAR PARA ATENDENDO data.status = 'pending' + data.transfer = true await api.put(`/tickets/${ticketid}`, data); @@ -127,56 +96,26 @@ const TransferTicketModal = ({ modalOpen, onClose, ticketid }) => { {i18n.t("transferTicketModal.title")} - `${option.name}`} - onChange={(e, newValue) => { - setSelectedUser(newValue); - if (newValue != null && Array.isArray(newValue.queues)) { - setQueues(newValue.queues); - } else { - setQueues(allQueues); - setSelectedQueue(''); - } - }} - options={options} - filterOptions={filterOptions} - freeSolo - autoHighlight - noOptionsText={i18n.t("transferTicketModal.noOptions")} - loading={loading} - renderInput={params => ( - setSearchParam(e.target.value)} - InputProps={{ - ...params.InputProps, - endAdornment: ( - - {loading ? ( - - ) : null} - {params.InputProps.endAdornment} - - ), - }} - /> - )} - /> {i18n.t("transferTicketModal.fieldQueueLabel")} @@ -200,8 +139,8 @@ const TransferTicketModal = ({ modalOpen, onClose, ticketid }) => { - + ); }; -export default TransferTicketModal; +export default TransferTicketModal; \ No newline at end of file diff --git a/frontend/src/hooks/useTickets/index.js b/frontend/src/hooks/useTickets/index.js index e02baa9..7dfe366 100644 --- a/frontend/src/hooks/useTickets/index.js +++ b/frontend/src/hooks/useTickets/index.js @@ -5,6 +5,7 @@ import api from "../../services/api"; const useTickets = ({ searchParam, + searchParamContent, pageNumber, status, date, @@ -35,6 +36,7 @@ const useTickets = ({ const { data } = await api.get("/tickets", { params: { searchParam, + searchParamContent, pageNumber, status, date, @@ -63,6 +65,7 @@ const useTickets = ({ return () => clearTimeout(delayDebounceFn); }, [ searchParam, + searchParamContent, pageNumber, status, date, diff --git a/frontend/src/pages/Contacts/index.js b/frontend/src/pages/Contacts/index.js index 44f1265..d67298b 100644 --- a/frontend/src/pages/Contacts/index.js +++ b/frontend/src/pages/Contacts/index.js @@ -37,8 +37,9 @@ import { Can } from "../../components/Can"; import apiBroker from "../../services/apiBroker"; import fileDownload from 'js-file-download' +import ContactCreateTicketModal from "../../components/ContactCreateTicketModal"; + - const reducer = (state, action) => { @@ -111,6 +112,7 @@ const Contacts = () => { const [contacts, dispatch] = useReducer(reducer, []); const [selectedContactId, setSelectedContactId] = useState(null); const [contactModalOpen, setContactModalOpen] = useState(false); + const [isCreateTicketModalOpen, setIsCreateTicketModalOpen] = useState(false) const [deletingContact, setDeletingContact] = useState(null); const [confirmOpen, setConfirmOpen] = useState(false); const [hasMore, setHasMore] = useState(false); @@ -118,17 +120,17 @@ const Contacts = () => { const [onQueueStatus, setOnQueueProcessStatus] = useState(undefined) - const [zipfile, setZipFile] = useState() - + const [zipfile, setZipFile] = useState() + async function handleChange(event) { try { - if (event.target.files[0].size > 1024 * 1024 * 4){ + if (event.target.files[0].size > 1024 * 1024 * 4) { alert('Arquivo não pode ser maior que 4 MB!') return - } + } const formData = new FormData(); formData.append("adminId", user.id); @@ -302,21 +304,30 @@ const Contacts = () => { setContactModalOpen(false); }; - const handleSaveTicket = async (contactId) => { - if (!contactId) return; - setLoading(true); - try { - const { data: ticket } = await api.post("/tickets", { - contactId: contactId, - userId: user?.id, - status: "open", - }); - history.push(`/tickets/${ticket.id}`); - } catch (err) { - toastError(err); - } - setLoading(false); - }; + const handleOpenCreateTicketModal = (contactId) => { + setSelectedContactId(contactId) + setIsCreateTicketModalOpen(true) + } + + const handleCloseCreateTicketModal = () => { + setIsCreateTicketModalOpen(false) + } + + // const handleSaveTicket = async (contactId) => { + // if (!contactId) return; + // setLoading(true); + // try { + // const { data: ticket } = await api.post("/tickets", { + // contactId: contactId, + // userId: user?.id, + // status: "open", + // }); + // history.push(`/tickets/${ticket.id}`); + // } catch (err) { + // toastError(err); + // } + // setLoading(false); + // }; const hadleEditContact = (contactId) => { setSelectedContactId(contactId); @@ -415,21 +426,21 @@ const Contacts = () => { switch (param) { case 'empty': return ( - <> + <> - + - + {/*