From c8ea53a4bcf299da25a638c0641f22a879e6acee Mon Sep 17 00:00:00 2001 From: adriano Date: Tue, 8 Aug 2023 12:09:03 -0300 Subject: [PATCH 1/8] =?UTF-8?q?finaliza=C3=A7=C3=A3o=20do=20recurso=20de?= =?UTF-8?q?=20encerramento=20de=20ticket=20automatico=20e=20expira=C3=A7?= =?UTF-8?q?=C3=A3o=20de=20ticket?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/controllers/SettingController.ts | 33 +- backend/src/database/index.ts | 4 +- .../20230807152412-setting-tickets.ts | 46 + .../20230807154146-add-setting-tickets.ts | 34 + backend/src/helpers/AutoCloseTickets.ts | 84 ++ backend/src/models/SettingTicket.ts | 40 + backend/src/routes/settingRoutes.ts | 10 +- backend/src/server.ts | 1 + .../SettingServices/UpdateSettingTicket.ts | 35 + .../TicketServices/ListTicketTimeLife.ts | 47 + .../TicketServices/UpdateTicketService.ts | 2 +- .../WbotServices/wbotMessageListener.ts | 1044 +++++++++-------- frontend/package.json | 1 + frontend/src/components/ConfigModal/index.js | 304 +++++ frontend/src/hooks/useAuth.js/index.js | 2 +- frontend/src/pages/Connections/index.js | 54 +- frontend/src/pages/Queues/index.js | 2 +- frontend/src/pages/Settings/index.js | 2 +- 18 files changed, 1257 insertions(+), 488 deletions(-) create mode 100644 backend/src/database/migrations/20230807152412-setting-tickets.ts create mode 100644 backend/src/database/seeds/20230807154146-add-setting-tickets.ts create mode 100644 backend/src/helpers/AutoCloseTickets.ts create mode 100644 backend/src/models/SettingTicket.ts create mode 100644 backend/src/services/SettingServices/UpdateSettingTicket.ts create mode 100644 backend/src/services/TicketServices/ListTicketTimeLife.ts create mode 100644 frontend/src/components/ConfigModal/index.js diff --git a/backend/src/controllers/SettingController.ts b/backend/src/controllers/SettingController.ts index 7144a89..c8cb940 100644 --- a/backend/src/controllers/SettingController.ts +++ b/backend/src/controllers/SettingController.ts @@ -5,6 +5,8 @@ import AppError from "../errors/AppError"; import UpdateSettingService from "../services/SettingServices/UpdateSettingService"; import ListSettingsService from "../services/SettingServices/ListSettingsService"; +import updateSettingTicket from "../services/SettingServices/UpdateSettingTicket"; +import SettingTicket from "../models/SettingTicket"; export const index = async (req: Request, res: Response): Promise => { // if (req.user.profile !== "master") { @@ -12,8 +14,37 @@ export const index = async (req: Request, res: Response): Promise => { // } const settings = await ListSettingsService(); + const outBusinessHours = await SettingTicket.findOne({ + where: { key: "outBusinessHours" } + }); + const ticketExpiration = await SettingTicket.findOne({ + where: { key: "ticketExpiration" } + }); - return res.status(200).json(settings); + return res.status(200).json({ settings, outBusinessHours, ticketExpiration }); +}; + +export const updateTicketSettings = async ( + req: Request, + res: Response +): Promise => { + const { outBusinessHours, ticketExpiration } = req.body; + + if (outBusinessHours && Object.keys(outBusinessHours).length > 0) { + await updateSettingTicket({ + ...outBusinessHours, + key: "outBusinessHours" + }); + } + + if (ticketExpiration && Object.keys(ticketExpiration).length > 0) { + await updateSettingTicket({ + ...ticketExpiration, + key: "ticketExpiration" + }); + } + + return res.status(200).json({ outBusinessHours, ticketExpiration }); }; export const update = async ( diff --git a/backend/src/database/index.ts b/backend/src/database/index.ts index 9749831..f5bf45d 100644 --- a/backend/src/database/index.ts +++ b/backend/src/database/index.ts @@ -14,6 +14,7 @@ import QuickAnswer from "../models/QuickAnswer"; import SchedulingNotify from "../models/SchedulingNotify"; import StatusChatEnd from "../models/StatusChatEnd"; import UserOnlineTime from "../models/UserOnlineTime"; +import SettingTicket from "../models/SettingTicket"; // eslint-disable-next-line const dbConfig = require("../config/database"); // import dbConfig from "../config/database"; @@ -35,7 +36,8 @@ const models = [ SchedulingNotify, StatusChatEnd, - UserOnlineTime, + UserOnlineTime, + SettingTicket ]; sequelize.addModels(models); diff --git a/backend/src/database/migrations/20230807152412-setting-tickets.ts b/backend/src/database/migrations/20230807152412-setting-tickets.ts new file mode 100644 index 0000000..b2581c8 --- /dev/null +++ b/backend/src/database/migrations/20230807152412-setting-tickets.ts @@ -0,0 +1,46 @@ +import { QueryInterface, DataTypes } from "sequelize" + +module.exports = { + up: (queryInterface: QueryInterface) => { + return queryInterface.createTable("SettingTickets", { + id: { + type: DataTypes.INTEGER, + autoIncrement: true, + primaryKey: true, + allowNull: false + }, + message: { + type: DataTypes.STRING, + allowNull: true + }, + startTime: { + type: DataTypes.DATE, + allowNull: true + }, + endTime: { + type: DataTypes.DATE, + allowNull: true + }, + value: { + type: DataTypes.STRING, + allowNull: false + }, + key: { + type: DataTypes.STRING, + allowNull: false + }, + createdAt: { + type: DataTypes.DATE, + allowNull: false + }, + updatedAt: { + type: DataTypes.DATE, + allowNull: false + } + }); + }, + + down: (queryInterface: QueryInterface) => { + return queryInterface.dropTable("SettingTickets"); + } +} diff --git a/backend/src/database/seeds/20230807154146-add-setting-tickets.ts b/backend/src/database/seeds/20230807154146-add-setting-tickets.ts new file mode 100644 index 0000000..9d40d31 --- /dev/null +++ b/backend/src/database/seeds/20230807154146-add-setting-tickets.ts @@ -0,0 +1,34 @@ +import { QueryInterface } from "sequelize"; + +module.exports = { + up: (queryInterface: QueryInterface) => { + return queryInterface.bulkInsert( + "SettingTickets", + [ + { + message: "", + startTime: new Date(), + endTime: new Date(), + value: "disabled", + key: "outBusinessHours", + createdAt: new Date(), + updatedAt: new Date() + }, + { + message: "", + startTime: new Date(), + endTime: new Date(), + value: "disabled", + key: "ticketExpiration", + createdAt: new Date(), + updatedAt: new Date() + } + ], + {} + ); + }, + + down: (queryInterface: QueryInterface) => { + return queryInterface.bulkDelete("SettingTickets", {}); + } +}; diff --git a/backend/src/helpers/AutoCloseTickets.ts b/backend/src/helpers/AutoCloseTickets.ts new file mode 100644 index 0000000..2948035 --- /dev/null +++ b/backend/src/helpers/AutoCloseTickets.ts @@ -0,0 +1,84 @@ +import SettingTicket from "../models/SettingTicket"; +import ListTicketTimeLife from "../services/TicketServices/ListTicketTimeLife"; +import UpdateTicketService from "../services/TicketServices/UpdateTicketService"; +import BotIsOnQueue from "./BotIsOnQueue"; + +import { + format as _format, + isWithinInterval, + parse, + subMinutes +} from "date-fns"; + +import ptBR from "date-fns/locale/pt-BR"; +import { splitDateTime } from "./SplitDateTime"; + +const fsPromises = require("fs/promises"); +const fs = require("fs"); + +let timer: any; + +const AutoCloseTickets = async () => { + try { + // const botInfo = await BotIsOnQueue('botqueue') + + // if (!botInfo.userIdBot) return + + const ticketExpiration = await SettingTicket.findOne({ + where: { key: "ticketExpiration" } + }); + + if (ticketExpiration && ticketExpiration.value == "enabled") { + const startTime = splitDateTime( + new Date( + _format(new Date(ticketExpiration.startTime), "yyyy-MM-dd HH:mm:ss", { + locale: ptBR + }) + ) + ); + + const seconds = timeStringToSeconds(startTime.fullTime); + + console.log("Ticket seconds: ", seconds); + + let tickets: any = await ListTicketTimeLife({ + timeseconds: seconds, + status: "open" + }); + + console.log("tickets: ", tickets); + + for (let i = 0; i < tickets.length; i++) { + + await UpdateTicketService({ + ticketData: { status: "closed", statusChatEnd: "FINALIZADO" }, + ticketId: tickets[i].ticket_id, + msg: ticketExpiration.message + }); + } + } + } catch (error) { + console.log("There was an error on try close the bot tickets: ", error); + } +}; + +function timeStringToSeconds(timeString: any) { + const [hours, minutes, seconds] = timeString.split(":").map(Number); + return hours * 3600 + minutes * 60 + seconds; +} + +const schedule = async () => { + try { + clearInterval(timer); + + await AutoCloseTickets(); + } catch (error) { + console.log("error on schedule: ", error); + } finally { + timer = setInterval(schedule, 60000); + } +}; + +timer = setInterval(schedule, 60000); + +export default schedule; diff --git a/backend/src/models/SettingTicket.ts b/backend/src/models/SettingTicket.ts new file mode 100644 index 0000000..10ef6c2 --- /dev/null +++ b/backend/src/models/SettingTicket.ts @@ -0,0 +1,40 @@ +import { + Table, + Column, + CreatedAt, + UpdatedAt, + Model, + PrimaryKey, + AutoIncrement +} from "sequelize-typescript"; + +@Table +class SettingTicket extends Model { + @PrimaryKey + @AutoIncrement + @Column + id: number; + + @Column + message: string; + + @Column + startTime: Date; + + @Column + endTime: Date; + + @Column + value: string; + + @Column + key: string; + + @CreatedAt + createdAt: Date; + + @UpdatedAt + updatedAt: Date; +} + +export default SettingTicket; diff --git a/backend/src/routes/settingRoutes.ts b/backend/src/routes/settingRoutes.ts index 7047a63..dc361fb 100644 --- a/backend/src/routes/settingRoutes.ts +++ b/backend/src/routes/settingRoutes.ts @@ -3,13 +3,21 @@ import isAuth from "../middleware/isAuth"; import * as SettingController from "../controllers/SettingController"; -const settingRoutes = Router(); +const settingRoutes = Router(); settingRoutes.get("/settings", SettingController.index); // routes.get("/settings/:settingKey", isAuth, SettingsController.show); +settingRoutes.put( + "/settings/ticket", + isAuth, + SettingController.updateTicketSettings +); + + // change setting key to key in future settingRoutes.put("/settings/:settingKey", isAuth, SettingController.update); + export default settingRoutes; diff --git a/backend/src/server.ts b/backend/src/server.ts index 2ab92c0..3e7b7ac 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -10,6 +10,7 @@ import { cacheSize, flushCache, loadTicketsCache } from "./helpers/TicketCache"; import { loadContactsCache } from "./helpers/ContactsCache"; import { loadSchedulesCache } from "./helpers/SchedulingNotifyCache"; import { delRestoreControllFile } from "./helpers/RestoreControll"; +import "./helpers/AutoCloseTickets"; import "./helpers/SchedulingNotifySendMessage" import axios from "axios"; diff --git a/backend/src/services/SettingServices/UpdateSettingTicket.ts b/backend/src/services/SettingServices/UpdateSettingTicket.ts new file mode 100644 index 0000000..5319c10 --- /dev/null +++ b/backend/src/services/SettingServices/UpdateSettingTicket.ts @@ -0,0 +1,35 @@ +import AppError from "../../errors/AppError"; +import SettingTicket from "../../models/SettingTicket"; + +interface Request { + key: string; + startTime: string; + endTime: string; + value: string; + message: string; +} + +const updateSettingTicket = async ({ + key, + startTime, + endTime, + value, + message +}: Request): Promise => { + try { + const businessHours = await SettingTicket.findOne({ where: { key } }); + + if (!businessHours) { + throw new AppError("ERR_NO_SETTING_FOUND", 404); + } + + await businessHours.update({ startTime, endTime, message, value }); + + return businessHours; + } catch (error: any) { + console.error("===> Error on UpdateSettingService.ts file: \n", error); + throw new AppError(error.message); + } +}; + +export default updateSettingTicket; diff --git a/backend/src/services/TicketServices/ListTicketTimeLife.ts b/backend/src/services/TicketServices/ListTicketTimeLife.ts new file mode 100644 index 0000000..bcaa0c8 --- /dev/null +++ b/backend/src/services/TicketServices/ListTicketTimeLife.ts @@ -0,0 +1,47 @@ + +import { Sequelize, } from "sequelize"; + +const dbConfig = require("../../config/database"); +const sequelize = new Sequelize(dbConfig); +const { QueryTypes } = require('sequelize'); + +import { splitDateTime } from "../../helpers/SplitDateTime"; +import format from 'date-fns/format'; +import ptBR from 'date-fns/locale/pt-BR'; + + +interface Request { + timeseconds: string | number; + status: string; + userId?: string | number; +} + +const ListTicketTimeLife = async ({timeseconds, status, userId }: Request): Promise => { + + let tickets = [] + + let currentDate = format(new Date(), 'yyyy-MM-dd HH:mm:ss', { locale: ptBR }) + + // console.log('------------------> currentDate: ', currentDate) + + if (userId) { + // CONSULTANDO FILAS PELO ID DO USUARIO + tickets = await sequelize.query(`select user.id as user_id, user.name as user_name, t.id as ticket_id from Tickets as t inner join Users as user on + t.userId = user.id and user.name = 'botqueue' and t.status='${status}' and (TIMESTAMPDIFF(SECOND, t.updatedAt, '${currentDate}')) >= ${timeseconds};`, { type: QueryTypes.SELECT }); + + } else { + + // CONSULTANDO FILAS PELO USUARIO + tickets = await sequelize.query(`select id as ticket_id from Tickets where status='${status}' and + (TIMESTAMPDIFF(SECOND, updatedAt, '${currentDate}')) >= ${timeseconds};`, { type: QueryTypes.SELECT }); + + } + + return tickets; +}; + +export default ListTicketTimeLife; + + + + diff --git a/backend/src/services/TicketServices/UpdateTicketService.ts b/backend/src/services/TicketServices/UpdateTicketService.ts index eed34da..b4181d5 100644 --- a/backend/src/services/TicketServices/UpdateTicketService.ts +++ b/backend/src/services/TicketServices/UpdateTicketService.ts @@ -63,7 +63,7 @@ const UpdateTicketService = async ({ await ticket.reload(); - if (msg.length > 0) { + if (msg?.trim().length > 0) { setTimeout(async () => { diff --git a/backend/src/services/WbotServices/wbotMessageListener.ts b/backend/src/services/WbotServices/wbotMessageListener.ts index 053b625..c85e2ab 100644 --- a/backend/src/services/WbotServices/wbotMessageListener.ts +++ b/backend/src/services/WbotServices/wbotMessageListener.ts @@ -5,12 +5,15 @@ import * as Sentry from "@sentry/node"; import { copyFolder } from "../../helpers/CopyFolder"; import { removeDir } from "../../helpers/DeleteDirectory"; -import path from 'path'; - -import { format } from "date-fns"; -import ptBR from 'date-fns/locale/pt-BR'; - +import path from "path"; +import { + format as _format, + isWithinInterval, + parse, + subMinutes +} from "date-fns"; +import ptBR from "date-fns/locale/pt-BR"; import { Contact as WbotContact, @@ -34,38 +37,42 @@ import UpdateTicketService from "../TicketServices/UpdateTicketService"; import { date } from "faker"; import ShowQueueService from "../QueueService/ShowQueueService"; -import ShowTicketMessage from "../TicketServices/ShowTicketMessage" -import BotIsOnQueue from "../../helpers/BotIsOnQueue" +import ShowTicketMessage from "../TicketServices/ShowTicketMessage"; +import BotIsOnQueue from "../../helpers/BotIsOnQueue"; import Queue from "../../models/Queue"; -import fs from 'fs'; +import fs from "fs"; import { StartWhatsAppSession } from "../../services/WbotServices/StartWhatsAppSession"; -import { removeWbot } from '../../libs/wbot' +import { removeWbot } from "../../libs/wbot"; import { restartWhatsSession } from "../../helpers/RestartWhatsSession"; -// test del -import data_ura from './ura' -import msg_client_transfer from './ura_msg_transfer' +// test del +import data_ura from "./ura"; +import msg_client_transfer from "./ura_msg_transfer"; import final_message from "./ura_final_message"; import SendWhatsAppMessage from "./SendWhatsAppMessage"; import Whatsapp from "../../models/Whatsapp"; import { splitDateTime } from "../../helpers/SplitDateTime"; -// +// -import { updateTicketCacheByTicketId } from '../../helpers/TicketCache' -import { insertMessageContactCache, getLastId } from '../../helpers/LastMessageIdByContactCache' +import { updateTicketCacheByTicketId } from "../../helpers/TicketCache"; +import { + insertMessageContactCache, + getLastId +} from "../../helpers/LastMessageIdByContactCache"; import autoRestore from "../../helpers/AutoRestore"; import { _restore } from "../../helpers/RestoreControll"; import sendWhatsAppMessageSocket from "../../helpers/SendWhatsappMessageSocket"; -import { getWhatsappIds, setWhatsappId } from "../../helpers/WhatsappIdMultiSessionControl"; +import { + getWhatsappIds, + setWhatsappId +} from "../../helpers/WhatsappIdMultiSessionControl"; import AppError from "../../errors/AppError"; import { setMessageAsRead } from "../../helpers/SetMessageAsRead"; +import SettingTicket from "../../models/SettingTicket"; - - -var lst: any[] = getWhatsappIds() - +var lst: any[] = getWhatsappIds(); interface Session extends Client { id?: number; @@ -113,7 +120,7 @@ const verifyMediaMessage = async ( ticket: Ticket, contact: Contact, media: any, - quotedMsg?: any, + quotedMsg?: any ): Promise => { // const quotedMsg = await verifyQuotedMessage(msg); @@ -123,12 +130,15 @@ const verifyMediaMessage = async ( throw new Error("ERR_WAPP_DOWNLOAD_MEDIA"); } - console.log('MEDIA.FILENAME: ', media.fileName, ' | msg.fromMe: ', msg.fromMe) + console.log( + "MEDIA.FILENAME: ", + media.fileName, + " | msg.fromMe: ", + msg.fromMe + ); if (!media.filename) { - - console.log('No file name -----------------------------------------') - + console.log("No file name -----------------------------------------"); const ext = media.mimetype.split("/")[1].split(";")[0]; media.filename = `${new Date().getTime()}.${ext}`; @@ -141,15 +151,13 @@ const verifyMediaMessage = async ( // "base64" // ); - console.log('FROM wbotMessageListener.ts media.filename: ', media.filename) - + console.log("FROM wbotMessageListener.ts media.filename: ", media.filename); await writeFileAsync( join(__dirname, "..", "..", "..", "..", "..", "public", media.filename), media.data, "base64" ); - } catch (err) { Sentry.captureException(err); logger.error(`There was an error: wbotMessageLitener.ts: ${err}`); @@ -178,11 +186,8 @@ const verifyMessage = async ( msg: WbotMessage, ticket: Ticket, contact: Contact, - quotedMsg?: any, + quotedMsg?: any ) => { - - - // const quotedMsg = await verifyQuotedMessage(msg); // const quotedMsg = await verifyQuotedMessage(msg); @@ -199,21 +204,17 @@ const verifyMessage = async ( // quotedMsgId: quotedMsg?.id }; - await ticket.update({ lastMessage: msg.body }); await CreateMessageService({ messageData }); }; - - const verifyQueue = async ( wbot: Session, msg: WbotMessage, ticket: Ticket, contact: Contact ) => { - const { queues, greetingMessage } = await ShowWhatsAppService(wbot.id!); /*if (queues.length === 1) { @@ -224,45 +225,32 @@ const verifyQueue = async ( return; }*/ - - let selectedOption = null; - let choosenQueue = null + let choosenQueue = null; //Habilitar esse caso queira usar o bot - // const botInfo = await BotIsOnQueue('botqueue') - const botInfo = { isOnQueue: false, botQueueId: 0, userIdBot: 0 } + // const botInfo = await BotIsOnQueue('botqueue') + const botInfo = { isOnQueue: false, botQueueId: 0, userIdBot: 0 }; if (botInfo.isOnQueue) { - choosenQueue = await ShowQueueService(botInfo.botQueueId); - - } - - else if (queues.length === 1) { + } else if (queues.length === 1) { selectedOption = 1; choosenQueue = queues[+selectedOption - 1]; - } - else { - + } else { selectedOption = msg.body; //////////////// EXTRAIR APENAS O NÚMERO /////////////////// - selectedOption = selectedOption.replace(/[^1-9]/g, '') + selectedOption = selectedOption.replace(/[^1-9]/g, ""); /////////////////////////////////// choosenQueue = queues[+selectedOption - 1]; } - - if (choosenQueue) { - // Atualizando o status do ticket para mostrar notificação para o atendente da fila escolhida pelo usuário. De queueChoice para pending if (queues.length > 1 && !botInfo.isOnQueue) { - await ticket.update({ status: "pending" }); - } // @@ -271,48 +259,42 @@ const verifyQueue = async ( ticketId: ticket.id }); - - let botOptions = '' + let botOptions = ""; // O bot abre a mensagem na fila para atender o usuario if (botInfo.isOnQueue) { - await UpdateTicketService({ - ticketData: { status: 'open', userId: botInfo.userIdBot }, + ticketData: { status: "open", userId: botInfo.userIdBot }, ticketId: ticket.id }); - data_ura.forEach((s, index) => { botOptions += `*${index + 1}* - ${s.option}\n` }); + data_ura.forEach((s, index) => { + botOptions += `*${index + 1}* - ${s.option}\n`; + }); } // - let body = '' + let body = ""; if (botOptions.length > 0) { body = `\u200e${choosenQueue.greetingMessage}\n\n${botOptions}\n${final_message.msg}`; - } - else { + } else { body = `\u200e${choosenQueue.greetingMessage}`; } - // const sentMessage = await wbot.sendMessage(`${contact.number}@c.us`, body); - // await verifyMessage(sentMessage, ticket, contact); + // const sentMessage = await wbot.sendMessage(`${contact.number}@c.us`, body); + // await verifyMessage(sentMessage, ticket, contact); - sendWhatsAppMessageSocket(ticket, body) - - } - else { - - - //test del transfere o atendimento se entrar na ura infinita + sendWhatsAppMessageSocket(ticket, body); + } else { + //test del transfere o atendimento se entrar na ura infinita let ticket_message = await ShowTicketMessage(ticket.id, false); if (ticket_message.length > 10) { - - await UpdateTicketService({ ticketData: { status: 'pending', queueId: queues[0].id }, ticketId: ticket.id }); - - } - else { - + await UpdateTicketService({ + ticketData: { status: "pending", queueId: queues[0].id }, + ticketId: ticket.id + }); + } else { let options = ""; queues.forEach((queue, index) => { @@ -323,24 +305,17 @@ const verifyQueue = async ( const debouncedSentMessage = debounce( async () => { - - // const sentMessage = await wbot.sendMessage(`${contact.number}@c.us`, body); + // const sentMessage = await wbot.sendMessage(`${contact.number}@c.us`, body); // verifyMessage(sentMessage, ticket, contact); - sendWhatsAppMessageSocket(ticket, body) - - + sendWhatsAppMessageSocket(ticket, body); }, 3000, ticket.id ); debouncedSentMessage(); - } - - - } }; @@ -360,106 +335,123 @@ const isValidMsg = (msg: WbotMessage): boolean => { return false; }; - const queuesOutBot = async (wbot: Session, botId: string | number) => { - const { queues, greetingMessage } = await ShowWhatsAppService(wbot.id!); - const indexQueue = queues.findIndex((q) => q.id == botId) + const indexQueue = queues.findIndex(q => q.id == botId); if (indexQueue != -1) { - queues.splice(indexQueue, 1) + queues.splice(indexQueue, 1); } - return { queues, greetingMessage } - -} - -const botTransferTicket = async (queues: Queue, ticket: Ticket, contact: Contact, wbot: Session) => { + return { queues, greetingMessage }; +}; +const botTransferTicket = async ( + queues: Queue, + ticket: Ticket, + contact: Contact, + wbot: Session +) => { await ticket.update({ userId: null }); - await UpdateTicketService({ ticketData: { status: 'pending', queueId: queues.id }, ticketId: ticket.id }); - -} - - -const botSendMessage = (ticket: Ticket, contact: Contact, wbot: Session, msg: string) => { + await UpdateTicketService({ + ticketData: { status: "pending", queueId: queues.id }, + ticketId: ticket.id + }); +}; +const botSendMessage = (ticket: Ticket, msg: string) => { const debouncedSentMessage = debounce( - async () => { - const sentMessage = await wbot.sendMessage(`${contact.number}@c.us`, `${msg}`); - verifyMessage(sentMessage, ticket, contact); + //OLD + // const sentMessage = await wbot.sendMessage(`${contact.number}@c.us`, `${msg}`); + // verifyMessage(sentMessage, ticket, contact); + + //NEW + await SendWhatsAppMessage({ body: msg, ticket }); }, 3000, ticket.id ); debouncedSentMessage(); +}; -} +// const botSendMessage = ( +// ticket: Ticket, +// contact: Contact, +// wbot: Session, +// msg: string +// ) => { +// const debouncedSentMessage = debounce( +// async () => { +// const sentMessage = await wbot.sendMessage( +// `${contact.number}@c.us`, +// `${msg}` +// ); +// verifyMessage(sentMessage, ticket, contact); +// }, +// 3000, +// ticket.id +// ); + +// debouncedSentMessage(); +// }; const _clear_lst = () => { + console.log("THE lst.length: ", lst.length); - console.log('THE lst.length: ', lst.length) + if (lst.length <= 199) return; - if (lst.length <= 199) return - - const chunk: any = Math.floor((lst.length / 2)) + const chunk: any = Math.floor(lst.length / 2); lst = lst.slice(chunk, chunk + lst.length); - let whatsappIdsSplited = lst.map((e) => `${e.id}`).toString() + let whatsappIdsSplited = lst.map(e => `${e.id}`).toString(); - setWhatsappId(whatsappIdsSplited, true) - -} - -const handleMessage = async ( - msg: any, - wbot: any -): Promise => { + setWhatsappId(whatsappIdsSplited, true); +}; +const handleMessage = async (msg: any, wbot: any): Promise => { if (!msg.fromMe) { + _clear_lst(); - _clear_lst() + let index = lst.findIndex((x: any) => x.id == msg.id.id); - let index = lst.findIndex((x: any) => x.id == msg.id.id) - - console.log('INDEX: ', index) + console.log("INDEX: ", index); if (index == -1) { - // console.log('-----------------> LST: ', lst):q - lst.push({ id: msg.id.id }) + lst.push({ id: msg.id.id }); - setWhatsappId(msg.id.id) + setWhatsappId(msg.id.id); + } else { + console.log("IGNORED ID: ", msg.id.id); - } - else { - console.log('IGNORED ID: ', msg.id.id) - - return + return; } // console.log('LIST OF ID MESSAGE lst: ', lst) - console.log('PASSOU.................................FROM: ', msg.from.split("@")[0], ' | ID: ', msg.id.id) + console.log( + "PASSOU.................................FROM: ", + msg.from.split("@")[0], + " | ID: ", + msg.id.id + ); } - if (!isValidMsg(msg)) { return; } try { - let msgContact: any = wbot.msgContact + let msgContact: any = wbot.msgContact; // let groupContact: Contact | undefined; if (msg.fromMe) { - // console.log('FROM ME: ', msg.fromMe, ' | /\u200e/.test(msg.body[0]: ', (/\u200e/.test(msg.body[0]))) // messages sent automatically by wbot have a special character in front of it @@ -479,14 +471,12 @@ const handleMessage = async ( // console.log('1 --------------> msgContat: ', JSON.parse(JSON.stringify(msgContact))) // console.log(' # msg.type: ', msg.type ) - } else { - // msgContact = await msg.getContact(); // console.log('2 --------------> msgContat: ', JSON.parse(JSON.stringify(msgContact))) - // + // console.log(`\n <<<<<<<<<< RECEIVING MESSAGE: Parcial msg and msgContact info: msgContact.name: ${msgContact.name} @@ -497,15 +487,13 @@ const handleMessage = async ( msg.body: ${msg.body} msg.type: ${msg.type} msg.from: ${msg.from} - msg.to: ${msg.to}\n`) - + msg.to: ${msg.to}\n`); } - // const chat = await msg.getChat(); - const chat = wbot.chat + const chat = wbot.chat; // if(chat.isGroup){ - + // console.log('This message is from a Group and will be ignored!') // return // } @@ -524,8 +512,6 @@ const handleMessage = async ( // groupContact = await verifyContact(msgGroupContact); // } - - const whatsapp = await ShowWhatsAppService(wbot.id!); // const whatsapp = await ShowWhatsAppService(46); @@ -536,47 +522,53 @@ const handleMessage = async ( // console.log('----------> contact: ', JSON.parse(JSON.stringify(contact))) - - - if (unreadMessages === 0 && whatsapp.farewellMessage && whatsapp.farewellMessage === msg.body) return; + if ( + unreadMessages === 0 && + whatsapp.farewellMessage && + whatsapp.farewellMessage === msg.body + ) + return; const ticket = await FindOrCreateTicketService( contact, wbot.id!, - unreadMessages, + unreadMessages // groupContact ); - // + // // await updateTicketCacheByTicketId(ticket.id, {'contact.profilePicUrl': ticket.contact.profilePicUrl}) // Para responder para o cliente pelo mesmo whatsapp que ele enviou a mensagen if (wbot.id != ticket.whatsappId) { - // console.log('PARA RESPONDER PELO MEMOS WHATSAPP wbot.id: ', wbot.id, ' | wbot.status: ', wbot.status) // console.log('WHATSAPP STATUS ticket.whatsappId: ', ticket.whatsappId) try { await ticket.update({ whatsappId: wbot.id }); } catch (error: any) { - console.error('===> Error on wbotMessageListener.ts into handleMessage fuction file: \n', error) + console.error( + "===> Error on wbotMessageListener.ts into handleMessage fuction file: \n", + error + ); throw new AppError(error.message); } - - - } - // + // if (msg.hasMedia) { - await verifyMediaMessage(msg, ticket, contact, wbot.media, wbot.quotedMsg); + await verifyMediaMessage( + msg, + ticket, + contact, + wbot.media, + wbot.quotedMsg + ); } else { - // console.log('>>>>>>> msg.fromMe: ',msg.fromMe ) await verifyMessage(msg, ticket, contact, wbot.quotedMsg); } - if ( !ticket.queue && !chat.isGroup && @@ -587,324 +579,442 @@ const handleMessage = async ( await verifyQueue(wbot, msg, ticket, contact); } + if (msg.fromMe) { + const ticketExpiration = await SettingTicket.findOne({ + where: { key: "ticketExpiration" } + }); + if ( + ticketExpiration && + ticketExpiration.value == "enabled" && + ticketExpiration?.message.trim() == msg.body.trim() + ) { + console.log("*********** TICKET EXPIRATION"); + return; + } + } // 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 //Habilitar esse caso queira usar o bot // const botInfo = await BotIsOnQueue('botqueue') - const botInfo = { isOnQueue: false, botQueueId: 0, userIdBot: 0 } - - if (botInfo.isOnQueue && !msg.fromMe && ticket.userId == botInfo.userIdBot) { - - - if (msg.body === '0') { - - const queue = await ShowQueueService(ticket.queue.id); - - const greetingMessage = `\u200e${queue.greetingMessage}`; - - let options = ""; - - data_ura.forEach((s, index) => { options += `*${index + 1}* - ${s.option}\n` }); - - botSendMessage(ticket, contact, wbot, `${greetingMessage}\n\n${options}\n${final_message.msg}`) - - } - else { - - - // Pega as ultimas 9 opções numericas digitadas pelo cliente em orde DESC - // Consulta apenas mensagens do usuári - - - let lastOption = '' - - let ura_length = data_ura.length - - let indexAttendant = data_ura.findIndex((u) => u.atendente) - - let opt_user_attendant = '-1' - - if (indexAttendant != -1) { - opt_user_attendant = data_ura[indexAttendant].id - } - - // - - let ticket_message = await ShowTicketMessage(ticket.id, true, ura_length, `^[0-${ura_length}}]$`); - - if (ticket_message.length > 1) { - - lastOption = ticket_message[1].body - - const queuesWhatsGreetingMessage = await queuesOutBot(wbot, botInfo.botQueueId) - - const queues = queuesWhatsGreetingMessage.queues - - if (queues.length > 1) { - - const index_opt_user_attendant = ticket_message.findIndex((q) => q.body == opt_user_attendant) - const index0 = ticket_message.findIndex((q) => q.body == '0') - - if (index_opt_user_attendant != -1) { - - if (index0 > -1 && index0 < index_opt_user_attendant) { - lastOption = '' - } - else { - lastOption = opt_user_attendant - } - } - - } - - } - - - - - // - - // - - // È numero - if (!Number.isNaN(Number(msg.body.trim())) && (+msg.body >= 0 && +msg.body <= data_ura.length)) { - - const indexUra = data_ura.findIndex((ura) => ura.id == msg.body.trim()) - - - - if (indexUra != -1) { - - if (data_ura[indexUra].id != opt_user_attendant && lastOption != opt_user_attendant) { - - - - - - // test del - let next = true - - let indexAux = ticket_message.findIndex((e) => e.body == '0') - - let listMessage = null - - if (indexAux != -1) { - - listMessage = ticket_message.slice(0, indexAux) - } - else { - - listMessage = ticket_message - - } - - let id = '' - let subUra = null - - if (listMessage.length > 1) { - - id = listMessage[listMessage.length - 1].body - subUra = data_ura.filter((e) => e.id == id)[0] - - if (subUra && (!subUra.subOptions || subUra.subOptions.length == 0)) { - - listMessage.pop() - - } - - } - - - if (listMessage.length > 1) { - - id = listMessage[listMessage.length - 1].body - subUra = data_ura.filter((e) => e.id == id)[0] - - if (subUra.subOptions && subUra.subOptions.length > 0) { - - if (!Number.isNaN(Number(msg.body.trim())) && (+msg.body >= 0 && +msg.body <= subUra.subOptions?.length) && subUra.subOptions) { - - - if (subUra.subOptions[+msg.body - 1].responseToClient) { - - botSendMessage(ticket, contact, wbot, `*${subUra.option}*\n\n${subUra.subOptions[+msg.body - 1].responseToClient}`) - - } - else { - botSendMessage(ticket, contact, wbot, `*${subUra.option}*\n\n${subUra.subOptions[+msg.body - 1].subOpt}`) - } - - const queuesWhatsGreetingMessage = await queuesOutBot(wbot, botInfo.botQueueId) - - const queues = queuesWhatsGreetingMessage.queues - - if (queues.length > 0) { - await botTransferTicket(queues[0], ticket, contact, wbot) - } - else { - console.log('NO QUEUE!') - } - - } - else { - - let options = ""; - let subOptions: any[] = subUra.subOptions - - subOptions?.forEach((s, index) => { options += `*${index + 1}* - ${s.subOpt}\n` }); - - botSendMessage(ticket, contact, wbot, `*${subUra.option}*\n\nDigite um número válido disponível no menu de opções de atendimento abaixo: \n${options}\n\n*0* - Voltar ao menu principal`) - - } - - next = false - - } - - } - - - // - if (next) { - if (data_ura[indexUra].subOptions && data_ura[indexUra].subOptions.length > 0) { - - let options = ""; - let option = data_ura[indexUra].option - let subOptions: any[] = data_ura[indexUra].subOptions - let description = data_ura[indexUra].description - - subOptions?.forEach((s, index) => { options += `*${index + 1}* - ${s.subOpt}\n` }); - - const body = `\u200e${description}:\n${options}` - - botSendMessage(ticket, contact, wbot, `*${option}*\n\n${body}\n\n *0* - Voltar ao menu principal`) - - } - else { - - //test del deletar isso (Usar somente na hit) - if (data_ura[indexUra].closeChat) { - - - const { ticket: res } = await UpdateTicketService({ - ticketData: { 'status': 'closed', 'userId': botInfo.userIdBot }, ticketId: ticket.id - }); - - /////////////////////////////// - const whatsapp = await ShowWhatsAppService(ticket.whatsappId); - - const { farewellMessage } = whatsapp; - - if (farewellMessage) { - await SendWhatsAppMessage({ body: farewellMessage, ticket: res }); - } - /////////////////////////////// - - } - else { - botSendMessage(ticket, contact, wbot, `${data_ura[indexUra].description}\n\n *0* - Voltar ao menu principal`) - } - // - - - // botSendMessage(ticket, contact, wbot, `${data_ura[indexUra].description}\n\n *0* - Voltar ao menu principal`) - - } - } - - } - else if (data_ura[indexUra].id == opt_user_attendant) { - - const queuesWhatsGreetingMessage = await queuesOutBot(wbot, botInfo.botQueueId) - - const queues = queuesWhatsGreetingMessage.queues - - // Se fila for maior que 1 exibi as opções fila para atendimento humano - if (queues.length > 1) { - - let options = ""; - - queues.forEach((queue, index) => { - - options += `*${index + 1}* - ${queue.name}\n`; - - }); - - const body = `\u200eSelecione uma das opções de atendimento abaixo:\n${options}`; - - botSendMessage(ticket, contact, wbot, body) - - } // Para situações onde há apenas uma fila com exclusão da fila do bot, já direciona o cliente para essa fila de atendimento humano - else if (queues.length == 1) { - - await botTransferTicket(queues[0], ticket, contact, wbot) - - botSendMessage(ticket, contact, wbot, `${msg_client_transfer.msg}`) - - } - } - else if (lastOption == opt_user_attendant) { - - const queuesWhatsGreetingMessage = await queuesOutBot(wbot, botInfo.botQueueId) - - const queues = queuesWhatsGreetingMessage.queues - - // É numero - if (!Number.isNaN(Number(msg.body.trim())) && (+msg.body >= 0 && +msg.body <= queues.length)) { - - await botTransferTicket(queues[+msg.body - 1], ticket, contact, wbot) - - botSendMessage(ticket, contact, wbot, `${msg_client_transfer.msg}`) - } - else { - - botSendMessage(ticket, contact, wbot, `Digite um número válido disponível no menu de opções de atendimento\n\n*0* - Voltar ao menu principal`) - - } - } - - - } - - } - else { - - // É numero - if (!Number.isNaN(Number(msg.body.trim()))) { - - botSendMessage(ticket, contact, wbot, `Opção numérica inválida!\nDigite um dos números mostrados no menu de opções\n\n*0* - Voltar ao menu principal`) - - } - else { - - botSendMessage(ticket, contact, wbot, `Digite um número válido disponível no menu de opções\n\n*0* - Voltar ao menu principal`) - - } - - } - - } - - - } - - - if (msg && !msg.fromMe && ticket.status == 'pending') { - - await setMessageAsRead(ticket) - + const botInfo = { isOnQueue: false, botQueueId: 0, userIdBot: 0 }; + + // if ( + // botInfo.isOnQueue && + // !msg.fromMe && + // ticket.userId == botInfo.userIdBot + // ) { + // if (msg.body === "0") { + // const queue = await ShowQueueService(ticket.queue.id); + + // const greetingMessage = `\u200e${queue.greetingMessage}`; + + // let options = ""; + + // data_ura.forEach((s, index) => { + // options += `*${index + 1}* - ${s.option}\n`; + // }); + + // botSendMessage( + // ticket, + // contact, + // wbot, + // `${greetingMessage}\n\n${options}\n${final_message.msg}` + // ); + // } else { + // // Pega as ultimas 9 opções numericas digitadas pelo cliente em orde DESC + // // Consulta apenas mensagens do usuári + + // let lastOption = ""; + + // let ura_length = data_ura.length; + + // let indexAttendant = data_ura.findIndex(u => u.atendente); + + // let opt_user_attendant = "-1"; + + // if (indexAttendant != -1) { + // opt_user_attendant = data_ura[indexAttendant].id; + // } + + // // + + // let ticket_message = await ShowTicketMessage( + // ticket.id, + // true, + // ura_length, + // `^[0-${ura_length}}]$` + // ); + + // if (ticket_message.length > 1) { + // lastOption = ticket_message[1].body; + + // const queuesWhatsGreetingMessage = await queuesOutBot( + // wbot, + // botInfo.botQueueId + // ); + + // const queues = queuesWhatsGreetingMessage.queues; + + // if (queues.length > 1) { + // const index_opt_user_attendant = ticket_message.findIndex( + // q => q.body == opt_user_attendant + // ); + // const index0 = ticket_message.findIndex(q => q.body == "0"); + + // if (index_opt_user_attendant != -1) { + // if (index0 > -1 && index0 < index_opt_user_attendant) { + // lastOption = ""; + // } else { + // lastOption = opt_user_attendant; + // } + // } + // } + // } + + // // + + // // + + // // È numero + // if ( + // !Number.isNaN(Number(msg.body.trim())) && + // +msg.body >= 0 && + // +msg.body <= data_ura.length + // ) { + // const indexUra = data_ura.findIndex(ura => ura.id == msg.body.trim()); + + // if (indexUra != -1) { + // if ( + // data_ura[indexUra].id != opt_user_attendant && + // lastOption != opt_user_attendant + // ) { + // // test del + // let next = true; + + // let indexAux = ticket_message.findIndex(e => e.body == "0"); + + // let listMessage = null; + + // if (indexAux != -1) { + // listMessage = ticket_message.slice(0, indexAux); + // } else { + // listMessage = ticket_message; + // } + + // let id = ""; + // let subUra = null; + + // if (listMessage.length > 1) { + // id = listMessage[listMessage.length - 1].body; + // subUra = data_ura.filter(e => e.id == id)[0]; + + // if ( + // subUra && + // (!subUra.subOptions || subUra.subOptions.length == 0) + // ) { + // listMessage.pop(); + // } + // } + + // if (listMessage.length > 1) { + // id = listMessage[listMessage.length - 1].body; + // subUra = data_ura.filter(e => e.id == id)[0]; + + // if (subUra.subOptions && subUra.subOptions.length > 0) { + // if ( + // !Number.isNaN(Number(msg.body.trim())) && + // +msg.body >= 0 && + // +msg.body <= subUra.subOptions?.length && + // subUra.subOptions + // ) { + // if (subUra.subOptions[+msg.body - 1].responseToClient) { + // botSendMessage( + // ticket, + // contact, + // wbot, + // `*${subUra.option}*\n\n${ + // subUra.subOptions[+msg.body - 1].responseToClient + // }` + // ); + // } else { + // botSendMessage( + // ticket, + // contact, + // wbot, + // `*${subUra.option}*\n\n${ + // subUra.subOptions[+msg.body - 1].subOpt + // }` + // ); + // } + + // const queuesWhatsGreetingMessage = await queuesOutBot( + // wbot, + // botInfo.botQueueId + // ); + + // const queues = queuesWhatsGreetingMessage.queues; + + // if (queues.length > 0) { + // await botTransferTicket(queues[0], ticket, contact, wbot); + // } else { + // console.log("NO QUEUE!"); + // } + // } else { + // let options = ""; + // let subOptions: any[] = subUra.subOptions; + + // subOptions?.forEach((s, index) => { + // options += `*${index + 1}* - ${s.subOpt}\n`; + // }); + + // botSendMessage( + // ticket, + // contact, + // wbot, + // `*${subUra.option}*\n\nDigite um número válido disponível no menu de opções de atendimento abaixo: \n${options}\n\n*0* - Voltar ao menu principal` + // ); + // } + + // next = false; + // } + // } + + // // + // if (next) { + // if ( + // data_ura[indexUra].subOptions && + // data_ura[indexUra].subOptions.length > 0 + // ) { + // let options = ""; + // let option = data_ura[indexUra].option; + // let subOptions: any[] = data_ura[indexUra].subOptions; + // let description = data_ura[indexUra].description; + + // subOptions?.forEach((s, index) => { + // options += `*${index + 1}* - ${s.subOpt}\n`; + // }); + + // const body = `\u200e${description}:\n${options}`; + + // botSendMessage( + // ticket, + // contact, + // wbot, + // `*${option}*\n\n${body}\n\n *0* - Voltar ao menu principal` + // ); + // } else { + // //test del deletar isso (Usar somente na hit) + // if (data_ura[indexUra].closeChat) { + // const { ticket: res } = await UpdateTicketService({ + // ticketData: { + // status: "closed", + // userId: botInfo.userIdBot + // }, + // ticketId: ticket.id + // }); + + // /////////////////////////////// + // const whatsapp = await ShowWhatsAppService( + // ticket.whatsappId + // ); + + // const { farewellMessage } = whatsapp; + + // if (farewellMessage) { + // await SendWhatsAppMessage({ + // body: farewellMessage, + // ticket: res + // }); + // } + // /////////////////////////////// + // } else { + // botSendMessage( + // ticket, + // contact, + // wbot, + // `${data_ura[indexUra].description}\n\n *0* - Voltar ao menu principal` + // ); + // } + // // + + // // botSendMessage(ticket, contact, wbot, `${data_ura[indexUra].description}\n\n *0* - Voltar ao menu principal`) + // } + // } + // } else if (data_ura[indexUra].id == opt_user_attendant) { + // const queuesWhatsGreetingMessage = await queuesOutBot( + // wbot, + // botInfo.botQueueId + // ); + + // const queues = queuesWhatsGreetingMessage.queues; + + // // Se fila for maior que 1 exibi as opções fila para atendimento humano + // if (queues.length > 1) { + // let options = ""; + + // queues.forEach((queue, index) => { + // options += `*${index + 1}* - ${queue.name}\n`; + // }); + + // const body = `\u200eSelecione uma das opções de atendimento abaixo:\n${options}`; + + // botSendMessage(ticket, contact, wbot, body); + // } // Para situações onde há apenas uma fila com exclusão da fila do bot, já direciona o cliente para essa fila de atendimento humano + // else if (queues.length == 1) { + // await botTransferTicket(queues[0], ticket, contact, wbot); + + // botSendMessage( + // ticket, + // contact, + // wbot, + // `${msg_client_transfer.msg}` + // ); + // } + // } else if (lastOption == opt_user_attendant) { + // const queuesWhatsGreetingMessage = await queuesOutBot( + // wbot, + // botInfo.botQueueId + // ); + + // const queues = queuesWhatsGreetingMessage.queues; + + // // É numero + // if ( + // !Number.isNaN(Number(msg.body.trim())) && + // +msg.body >= 0 && + // +msg.body <= queues.length + // ) { + // await botTransferTicket( + // queues[+msg.body - 1], + // ticket, + // contact, + // wbot + // ); + + // botSendMessage( + // ticket, + // contact, + // wbot, + // `${msg_client_transfer.msg}` + // ); + // } else { + // botSendMessage( + // ticket, + // contact, + // wbot, + // `Digite um número válido disponível no menu de opções de atendimento\n\n*0* - Voltar ao menu principal` + // ); + // } + // } + // } + // } else { + // // É numero + // if (!Number.isNaN(Number(msg.body.trim()))) { + // botSendMessage( + // ticket, + // contact, + // wbot, + // `Opção numérica inválida!\nDigite um dos números mostrados no menu de opções\n\n*0* - Voltar ao menu principal` + // ); + // } else { + // botSendMessage( + // ticket, + // contact, + // wbot, + // `Digite um número válido disponível no menu de opções\n\n*0* - Voltar ao menu principal` + // ); + // } + // } + // } + // } + + if (msg && !msg.fromMe && ticket.status == "pending") { + await setMessageAsRead(ticket); } + const businessTime: any = await BusinessTime(); + + if ( + msg.fromMe && + businessTime && + !businessTime.isWithinRange && + businessTime.message.trim() == msg.body.trim() + ) { + console.log("BUSINESS TIME OUT"); + return; + } + + if (!businessTime.isWithinRange && businessTime.message.trim().length > 0) { + botSendMessage(ticket, businessTime.message); + } } catch (err) { Sentry.captureException(err); - logger.error(`Error handling whatsapp message: Err: ${err}`); + console.log("Error handling whatsapp message: Err: ", err); + logger.error(`Error handling whatsapp message: Err: ${err}`); + } + + async function BusinessTime() { + const outBusinessHours = await SettingTicket.findOne({ + where: { key: "outBusinessHours" } + }); + + let isWithinRange = false; + + if (outBusinessHours && outBusinessHours.value == "enabled") { + const ticketDateTimeUpdate = splitDateTime( + new Date( + _format(new Date(), "yyyy-MM-dd HH:mm:ss", { + locale: ptBR + }) + ) + ); + + const startTime = splitDateTime( + new Date( + _format(new Date(outBusinessHours.startTime), "yyyy-MM-dd HH:mm:ss", { + locale: ptBR + }) + ) + ); + + const endTime = splitDateTime( + new Date( + _format(new Date(outBusinessHours.endTime), "yyyy-MM-dd HH:mm:ss", { + locale: ptBR + }) + ) + ); + + const format = "HH:mm:ss"; + const parsedStartTime = parse( + ticketDateTimeUpdate.fullTime, + format, + new Date() + ); + const parsedEndTime = parse(startTime.fullTime, format, new Date()); + const parsedTimeToCheck = parse(endTime.fullTime, format, new Date()); + + const timeInterval = { start: parsedStartTime, end: parsedEndTime }; + + // If the time range spans across different days, handle the date part + if (parsedEndTime < parsedStartTime) { + const nextDay = new Date(parsedStartTime); + nextDay.setDate(nextDay.getDate() + 1); + timeInterval.end = nextDay; + } + + isWithinRange = isWithinInterval(parsedTimeToCheck, timeInterval); + + console.log("Is the time within the range?", isWithinRange); + + return { isWithinRange, message: outBusinessHours.message }; + } } }; const handleMsgAck = async (msg_id: any, ack: any) => { - await new Promise(r => setTimeout(r, 4000)); const io = getIO(); @@ -921,16 +1031,17 @@ 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`) + console.log( + `Not found the MESSAGE ID ${msg_id}to change the ACK into the Message table` + ); return; } await messageToUpdate.update({ ack }); - + io.to(messageToUpdate.ticketId.toString()).emit("appMessage", { action: "update", message: messageToUpdate }); - } catch (err) { Sentry.captureException(err); logger.error(`Error handling message ack. Err: ${err}`); @@ -938,7 +1049,6 @@ const handleMsgAck = async (msg_id: any, ack: any) => { }; const wbotMessageListener = (wbot: Session): void => { - wbot.on("message_create", async msg => { handleMessage(msg, wbot); }); diff --git a/frontend/package.json b/frontend/package.json index a077e60..50b81dc 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -20,6 +20,7 @@ "dotenv": "^16.0.1", "emoji-mart": "^3.0.1", "formik": "^2.2.0", + "formik-material-ui-pickers": "^1.0.0-alpha.1", "i18next": "^19.8.2", "i18next-browser-languagedetector": "^6.0.1", "js-file-download": "^0.4.12", diff --git a/frontend/src/components/ConfigModal/index.js b/frontend/src/components/ConfigModal/index.js new file mode 100644 index 0000000..7c08118 --- /dev/null +++ b/frontend/src/components/ConfigModal/index.js @@ -0,0 +1,304 @@ +import React, { useState, useEffect, } from 'react' +// import * as Yup from 'yup' +import { Formik, Form, Field, } from 'formik' +import { toast } from 'react-toastify' + +import { makeStyles } from '@material-ui/core/styles' +import { green } from '@material-ui/core/colors' + +import { TimePicker } from 'formik-material-ui-pickers' + +import DateFnsUtils from '@date-io/date-fns' + +import ptBrLocale from "date-fns/locale/pt-BR" + + +import { + MuiPickersUtilsProvider, +} from '@material-ui/pickers' + +import { + Dialog, + DialogContent, + DialogTitle, + Button, + DialogActions, + CircularProgress, + TextField, + Switch, + FormControlLabel, +} from '@material-ui/core' + +import api from '../../services/api' +import { i18n } from '../../translate/i18n' +import toastError from '../../errors/toastError' + +const useStyles = makeStyles((theme) => ({ + root: { + display: 'flex', + flexWrap: 'wrap', + }, + + multFieldLine: { + display: 'flex', + '& > *:not(:last-child)': { + marginRight: theme.spacing(1), + }, + }, + + btnWrapper: { + position: 'relative', + }, + + buttonProgress: { + color: green[500], + position: 'absolute', + top: '50%', + left: '50%', + marginTop: -12, + marginLeft: -12, + }, +})) + +// const SessionSchema = Yup.object().shape({ +// name: Yup.string() +// .min(2, 'Too Short!') +// .max(100, 'Too Long!') +// .required('Required'), +// }) + +const ConfigModal = ({ open, onClose, change }) => { + const classes = useStyles() + const initialState = { + startTimeBus: new Date(), + endTimeBus: new Date(), + messageBus: '', + businessTimeEnalbe: false, + ticketTimeExpiration: new Date(), + ticketExpirationMsg: '', + ticketExpirationEnable: false, + } + + const [config, setConfig] = useState(initialState) + + useEffect(() => { + const fetchSession = async () => { + + try { + const { data } = await api.get('/settings') + + setConfig({ + startTimeBus: data.outBusinessHours.startTime, + endTimeBus: data.outBusinessHours.endTime, + messageBus: data.outBusinessHours.message, + businessTimeEnalbe: data.outBusinessHours.value === 'enabled' ? true : false, + ticketTimeExpiration: data.ticketExpiration.startTime, + ticketExpirationMsg: data.ticketExpiration.message, + ticketExpirationEnable: data.ticketExpiration.value === 'enabled' ? true : false + }) + + } catch (err) { + toastError(err) + } + } + fetchSession() + }, [change]) + + const handleSaveConfig = async (values) => { + + values = { + outBusinessHours: { + startTime: values.startTimeBus, + endTime: values.endTimeBus, + message: values.messageBus, + value: values.businessTimeEnalbe ? 'enabled' : 'disabled' + }, + ticketExpiration: { + startTime: values.ticketTimeExpiration, + message: values.ticketExpirationMsg, + value: values.ticketExpirationEnable ? 'enabled' : 'disabled' + } + } + + + try { + + await api.put(`/settings/ticket`, values) + + toast.success('Atualização realizada com sucesso!') + handleClose() + + } catch (err) { + toastError(err) + } + } + + const handleClose = () => { + onClose() + // setConfig(initialState) + } + + return ( +
+ + + Configurações + + + + { + + setTimeout(() => { + handleSaveConfig(values) + actions.setSubmitting(false) + }, 100) + }} + > + {({ values, touched, errors, isSubmitting }) => ( + +
+ + + +
+ + {' '} + + + + } + label={'Ativar/Desativar'} /> +
+ +
+ +
+ +
+
+ + + + } + label={'Ativar/Desativar'} + /> +
+
+ +
+ +
+ + + + +
+
+ )} +
+
+
+ ) +} + +export default React.memo(ConfigModal) diff --git a/frontend/src/hooks/useAuth.js/index.js b/frontend/src/hooks/useAuth.js/index.js index 70ffe21..6e26b05 100644 --- a/frontend/src/hooks/useAuth.js/index.js +++ b/frontend/src/hooks/useAuth.js/index.js @@ -76,7 +76,7 @@ const useAuth = () => { const fetchSession = async () => { try { const { data } = await api.get('/settings') - setSetting(data) + setSetting(data.settings) } catch (err) { toastError(err) } diff --git a/frontend/src/pages/Connections/index.js b/frontend/src/pages/Connections/index.js index a8c5fe4..3e188ee 100644 --- a/frontend/src/pages/Connections/index.js +++ b/frontend/src/pages/Connections/index.js @@ -6,6 +6,9 @@ import openSocket from 'socket.io-client' import { makeStyles } from '@material-ui/core/styles' import { green } from '@material-ui/core/colors' + +import Settings from "@material-ui/icons/Settings"; + import { Button, TableBody, @@ -47,6 +50,7 @@ import toastError from '../../errors/toastError' //-------- import { AuthContext } from '../../context/Auth/AuthContext' import { Can } from '../../components/Can' +import ConfigModal from '../../components/ConfigModal' const useStyles = makeStyles((theme) => ({ mainPaper: { @@ -107,6 +111,7 @@ const Connections = () => { const { whatsApps, loading } = useContext(WhatsAppsContext) const [whatsAppModalOpen, setWhatsAppModalOpen] = useState(false) + const [configModalOpen, setConfigModalOpen] = useState(false) const [qrModalOpen, setQrModalOpen] = useState(false) const [selectedWhatsApp, setSelectedWhatsApp] = useState(null) const [confirmModalOpen, setConfirmModalOpen] = useState(false) @@ -134,7 +139,7 @@ const Connections = () => { const fetchSession = async () => { try { const { data } = await api.get('/settings') - setSettings(data) + setSettings(data.settings) } catch (err) { toastError(err) } @@ -205,6 +210,13 @@ const Connections = () => { setWhatsAppModalOpen(true) } + const handleOpenConfigModal = () => { + setConfigModalOpen(true) + } + + const handleCloseConfigModal = () => { + setConfigModalOpen(false) + } const handleCloseWhatsAppModal = useCallback(() => { setWhatsAppModalOpen(false) setSelectedWhatsApp(null) @@ -307,17 +319,17 @@ const Connections = () => { {(whatsApp.status === 'CONNECTED' || whatsApp.status === 'PAIRING' || whatsApp.status === 'TIMEOUT') && ( - - )} + + )} {whatsApp.status === 'OPENING' && ( { settings.length > 0 && getSettingValue('editURA') && getSettingValue('editURA') === - 'enabled') | - (user.profile === 'master') ? ( + 'enabled') | + (user.profile === 'master') ? ( diff --git a/frontend/src/pages/Queues/index.js b/frontend/src/pages/Queues/index.js index 99b6eab..85144e2 100644 --- a/frontend/src/pages/Queues/index.js +++ b/frontend/src/pages/Queues/index.js @@ -121,7 +121,7 @@ const Queues = () => { const fetchSession = async () => { try { const { data } = await api.get('/settings') - setSettings(data) + setSettings(data.settings) } catch (err) { toastError(err) } diff --git a/frontend/src/pages/Settings/index.js b/frontend/src/pages/Settings/index.js index ab79873..2dc8891 100644 --- a/frontend/src/pages/Settings/index.js +++ b/frontend/src/pages/Settings/index.js @@ -52,7 +52,7 @@ const Settings = () => { const fetchSession = async () => { try { const { data } = await api.get('/settings') - setSettings(data) + setSettings(data.settings) } catch (err) { toastError(err) } From f778a928c815e499ccb2d32e9fc52b4370c415f6 Mon Sep 17 00:00:00 2001 From: adriano Date: Tue, 8 Aug 2023 12:31:25 -0300 Subject: [PATCH 2/8] =?UTF-8?q?Corre=C3=A7=C3=A3o=20para=20evitar=20que=20?= =?UTF-8?q?o=20ticket=20expiration=20gere=20um=20novo=20ticket?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../WbotServices/wbotMessageListener.ts | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/backend/src/services/WbotServices/wbotMessageListener.ts b/backend/src/services/WbotServices/wbotMessageListener.ts index c85e2ab..b6b0a8e 100644 --- a/backend/src/services/WbotServices/wbotMessageListener.ts +++ b/backend/src/services/WbotServices/wbotMessageListener.ts @@ -452,6 +452,19 @@ const handleMessage = async (msg: any, wbot: any): Promise => { // let groupContact: Contact | undefined; if (msg.fromMe) { + const ticketExpiration = await SettingTicket.findOne({ + where: { key: "ticketExpiration" } + }); + + if ( + ticketExpiration && + ticketExpiration.value == "enabled" && + ticketExpiration?.message.trim() == msg.body.trim() + ) { + console.log("*********** TICKET EXPIRATION"); + return; + } + // console.log('FROM ME: ', msg.fromMe, ' | /\u200e/.test(msg.body[0]: ', (/\u200e/.test(msg.body[0]))) // messages sent automatically by wbot have a special character in front of it @@ -579,21 +592,6 @@ const handleMessage = async (msg: any, wbot: any): Promise => { await verifyQueue(wbot, msg, ticket, contact); } - if (msg.fromMe) { - const ticketExpiration = await SettingTicket.findOne({ - where: { key: "ticketExpiration" } - }); - - if ( - ticketExpiration && - ticketExpiration.value == "enabled" && - ticketExpiration?.message.trim() == msg.body.trim() - ) { - console.log("*********** TICKET EXPIRATION"); - return; - } - } - // 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 //Habilitar esse caso queira usar o bot From 4ee662f4ff1004a1a841b45ec21f08ff23c58e0d Mon Sep 17 00:00:00 2001 From: adriano Date: Tue, 8 Aug 2023 14:01:02 -0300 Subject: [PATCH 3/8] =?UTF-8?q?remo=C3=A7=C3=A3o=20de=20codigo=20desativad?= =?UTF-8?q?o=20de=20bot?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../WbotServices/wbotMessageListener.ts | 333 +----------------- 1 file changed, 2 insertions(+), 331 deletions(-) diff --git a/backend/src/services/WbotServices/wbotMessageListener.ts b/backend/src/services/WbotServices/wbotMessageListener.ts index b6b0a8e..e6bd9de 100644 --- a/backend/src/services/WbotServices/wbotMessageListener.ts +++ b/backend/src/services/WbotServices/wbotMessageListener.ts @@ -596,337 +596,8 @@ const handleMessage = async (msg: any, wbot: any): Promise => { //Habilitar esse caso queira usar o bot // const botInfo = await BotIsOnQueue('botqueue') - const botInfo = { isOnQueue: false, botQueueId: 0, userIdBot: 0 }; - - // if ( - // botInfo.isOnQueue && - // !msg.fromMe && - // ticket.userId == botInfo.userIdBot - // ) { - // if (msg.body === "0") { - // const queue = await ShowQueueService(ticket.queue.id); - - // const greetingMessage = `\u200e${queue.greetingMessage}`; - - // let options = ""; - - // data_ura.forEach((s, index) => { - // options += `*${index + 1}* - ${s.option}\n`; - // }); - - // botSendMessage( - // ticket, - // contact, - // wbot, - // `${greetingMessage}\n\n${options}\n${final_message.msg}` - // ); - // } else { - // // Pega as ultimas 9 opções numericas digitadas pelo cliente em orde DESC - // // Consulta apenas mensagens do usuári - - // let lastOption = ""; - - // let ura_length = data_ura.length; - - // let indexAttendant = data_ura.findIndex(u => u.atendente); - - // let opt_user_attendant = "-1"; - - // if (indexAttendant != -1) { - // opt_user_attendant = data_ura[indexAttendant].id; - // } - - // // - - // let ticket_message = await ShowTicketMessage( - // ticket.id, - // true, - // ura_length, - // `^[0-${ura_length}}]$` - // ); - - // if (ticket_message.length > 1) { - // lastOption = ticket_message[1].body; - - // const queuesWhatsGreetingMessage = await queuesOutBot( - // wbot, - // botInfo.botQueueId - // ); - - // const queues = queuesWhatsGreetingMessage.queues; - - // if (queues.length > 1) { - // const index_opt_user_attendant = ticket_message.findIndex( - // q => q.body == opt_user_attendant - // ); - // const index0 = ticket_message.findIndex(q => q.body == "0"); - - // if (index_opt_user_attendant != -1) { - // if (index0 > -1 && index0 < index_opt_user_attendant) { - // lastOption = ""; - // } else { - // lastOption = opt_user_attendant; - // } - // } - // } - // } - - // // - - // // - - // // È numero - // if ( - // !Number.isNaN(Number(msg.body.trim())) && - // +msg.body >= 0 && - // +msg.body <= data_ura.length - // ) { - // const indexUra = data_ura.findIndex(ura => ura.id == msg.body.trim()); - - // if (indexUra != -1) { - // if ( - // data_ura[indexUra].id != opt_user_attendant && - // lastOption != opt_user_attendant - // ) { - // // test del - // let next = true; - - // let indexAux = ticket_message.findIndex(e => e.body == "0"); - - // let listMessage = null; - - // if (indexAux != -1) { - // listMessage = ticket_message.slice(0, indexAux); - // } else { - // listMessage = ticket_message; - // } - - // let id = ""; - // let subUra = null; - - // if (listMessage.length > 1) { - // id = listMessage[listMessage.length - 1].body; - // subUra = data_ura.filter(e => e.id == id)[0]; - - // if ( - // subUra && - // (!subUra.subOptions || subUra.subOptions.length == 0) - // ) { - // listMessage.pop(); - // } - // } - - // if (listMessage.length > 1) { - // id = listMessage[listMessage.length - 1].body; - // subUra = data_ura.filter(e => e.id == id)[0]; - - // if (subUra.subOptions && subUra.subOptions.length > 0) { - // if ( - // !Number.isNaN(Number(msg.body.trim())) && - // +msg.body >= 0 && - // +msg.body <= subUra.subOptions?.length && - // subUra.subOptions - // ) { - // if (subUra.subOptions[+msg.body - 1].responseToClient) { - // botSendMessage( - // ticket, - // contact, - // wbot, - // `*${subUra.option}*\n\n${ - // subUra.subOptions[+msg.body - 1].responseToClient - // }` - // ); - // } else { - // botSendMessage( - // ticket, - // contact, - // wbot, - // `*${subUra.option}*\n\n${ - // subUra.subOptions[+msg.body - 1].subOpt - // }` - // ); - // } - - // const queuesWhatsGreetingMessage = await queuesOutBot( - // wbot, - // botInfo.botQueueId - // ); - - // const queues = queuesWhatsGreetingMessage.queues; - - // if (queues.length > 0) { - // await botTransferTicket(queues[0], ticket, contact, wbot); - // } else { - // console.log("NO QUEUE!"); - // } - // } else { - // let options = ""; - // let subOptions: any[] = subUra.subOptions; - - // subOptions?.forEach((s, index) => { - // options += `*${index + 1}* - ${s.subOpt}\n`; - // }); - - // botSendMessage( - // ticket, - // contact, - // wbot, - // `*${subUra.option}*\n\nDigite um número válido disponível no menu de opções de atendimento abaixo: \n${options}\n\n*0* - Voltar ao menu principal` - // ); - // } - - // next = false; - // } - // } - - // // - // if (next) { - // if ( - // data_ura[indexUra].subOptions && - // data_ura[indexUra].subOptions.length > 0 - // ) { - // let options = ""; - // let option = data_ura[indexUra].option; - // let subOptions: any[] = data_ura[indexUra].subOptions; - // let description = data_ura[indexUra].description; - - // subOptions?.forEach((s, index) => { - // options += `*${index + 1}* - ${s.subOpt}\n`; - // }); - - // const body = `\u200e${description}:\n${options}`; - - // botSendMessage( - // ticket, - // contact, - // wbot, - // `*${option}*\n\n${body}\n\n *0* - Voltar ao menu principal` - // ); - // } else { - // //test del deletar isso (Usar somente na hit) - // if (data_ura[indexUra].closeChat) { - // const { ticket: res } = await UpdateTicketService({ - // ticketData: { - // status: "closed", - // userId: botInfo.userIdBot - // }, - // ticketId: ticket.id - // }); - - // /////////////////////////////// - // const whatsapp = await ShowWhatsAppService( - // ticket.whatsappId - // ); - - // const { farewellMessage } = whatsapp; - - // if (farewellMessage) { - // await SendWhatsAppMessage({ - // body: farewellMessage, - // ticket: res - // }); - // } - // /////////////////////////////// - // } else { - // botSendMessage( - // ticket, - // contact, - // wbot, - // `${data_ura[indexUra].description}\n\n *0* - Voltar ao menu principal` - // ); - // } - // // - - // // botSendMessage(ticket, contact, wbot, `${data_ura[indexUra].description}\n\n *0* - Voltar ao menu principal`) - // } - // } - // } else if (data_ura[indexUra].id == opt_user_attendant) { - // const queuesWhatsGreetingMessage = await queuesOutBot( - // wbot, - // botInfo.botQueueId - // ); - - // const queues = queuesWhatsGreetingMessage.queues; - - // // Se fila for maior que 1 exibi as opções fila para atendimento humano - // if (queues.length > 1) { - // let options = ""; - - // queues.forEach((queue, index) => { - // options += `*${index + 1}* - ${queue.name}\n`; - // }); - - // const body = `\u200eSelecione uma das opções de atendimento abaixo:\n${options}`; - - // botSendMessage(ticket, contact, wbot, body); - // } // Para situações onde há apenas uma fila com exclusão da fila do bot, já direciona o cliente para essa fila de atendimento humano - // else if (queues.length == 1) { - // await botTransferTicket(queues[0], ticket, contact, wbot); - - // botSendMessage( - // ticket, - // contact, - // wbot, - // `${msg_client_transfer.msg}` - // ); - // } - // } else if (lastOption == opt_user_attendant) { - // const queuesWhatsGreetingMessage = await queuesOutBot( - // wbot, - // botInfo.botQueueId - // ); - - // const queues = queuesWhatsGreetingMessage.queues; - - // // É numero - // if ( - // !Number.isNaN(Number(msg.body.trim())) && - // +msg.body >= 0 && - // +msg.body <= queues.length - // ) { - // await botTransferTicket( - // queues[+msg.body - 1], - // ticket, - // contact, - // wbot - // ); - - // botSendMessage( - // ticket, - // contact, - // wbot, - // `${msg_client_transfer.msg}` - // ); - // } else { - // botSendMessage( - // ticket, - // contact, - // wbot, - // `Digite um número válido disponível no menu de opções de atendimento\n\n*0* - Voltar ao menu principal` - // ); - // } - // } - // } - // } else { - // // É numero - // if (!Number.isNaN(Number(msg.body.trim()))) { - // botSendMessage( - // ticket, - // contact, - // wbot, - // `Opção numérica inválida!\nDigite um dos números mostrados no menu de opções\n\n*0* - Voltar ao menu principal` - // ); - // } else { - // botSendMessage( - // ticket, - // contact, - // wbot, - // `Digite um número válido disponível no menu de opções\n\n*0* - Voltar ao menu principal` - // ); - // } - // } - // } - // } + const botInfo = { isOnQueue: false, botQueueId: 0, userIdBot: 0 }; + if (msg && !msg.fromMe && ticket.status == "pending") { await setMessageAsRead(ticket); From 18db49c88cf3d726f7fa612ce62e06ceff544ab2 Mon Sep 17 00:00:00 2001 From: adriano Date: Tue, 8 Aug 2023 14:02:51 -0300 Subject: [PATCH 4/8] =?UTF-8?q?desativa=C3=A7ao=20de=20codigo=20de=20bot?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/services/WbotServices/wbotMessageListener.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/services/WbotServices/wbotMessageListener.ts b/backend/src/services/WbotServices/wbotMessageListener.ts index e6bd9de..426b119 100644 --- a/backend/src/services/WbotServices/wbotMessageListener.ts +++ b/backend/src/services/WbotServices/wbotMessageListener.ts @@ -596,7 +596,7 @@ const handleMessage = async (msg: any, wbot: any): Promise => { //Habilitar esse caso queira usar o bot // const botInfo = await BotIsOnQueue('botqueue') - const botInfo = { isOnQueue: false, botQueueId: 0, userIdBot: 0 }; + // const botInfo = { isOnQueue: false, botQueueId: 0, userIdBot: 0 }; if (msg && !msg.fromMe && ticket.status == "pending") { From 9125be43def8b9d26b6ec7f9915577c415b284a5 Mon Sep 17 00:00:00 2001 From: adriano Date: Wed, 9 Aug 2023 08:30:03 -0300 Subject: [PATCH 5/8] =?UTF-8?q?finaliza=C3=A7=C3=A3o=20do=20recurso=20para?= =?UTF-8?q?=20envio=20de=20mensagem=20em=20finais=20de=20semana=20e=20feri?= =?UTF-8?q?ados?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/package.json | 4 +- backend/src/controllers/SettingController.ts | 58 ++++- ...230808210411-add-setting-ticket-weekend.ts | 34 +++ ...230808210840-add-setting-ticket-holiday.ts | 25 +++ ...85716-add-setting-ticket-weekend-enable.ts | 25 +++ backend/src/helpers/AutoCloseTickets.ts | 4 +- .../WbotServices/wbotMessageListener.ts | 101 ++++++++- frontend/package.json | 7 +- frontend/src/components/ConfigModal/index.js | 210 ++++++++++++++++-- 9 files changed, 427 insertions(+), 41 deletions(-) create mode 100644 backend/src/database/seeds/20230808210411-add-setting-ticket-weekend.ts create mode 100644 backend/src/database/seeds/20230808210840-add-setting-ticket-holiday.ts create mode 100644 backend/src/database/seeds/20230809085716-add-setting-ticket-weekend-enable.ts diff --git a/backend/package.json b/backend/package.json index 1d55190..f9c9b6d 100644 --- a/backend/package.json +++ b/backend/package.json @@ -21,8 +21,8 @@ "bcryptjs": "^2.4.3", "cookie-parser": "^1.4.5", "cors": "^2.8.5", - "date-fns": "^2.16.1", - "date-fns-tz": "^1.3.4", + "date-fns": "^2.30.0", + "date-fns-tz": "^1.3.8", "dotenv": "^8.2.0", "express": "^4.17.1", "express-async-errors": "^3.1.1", diff --git a/backend/src/controllers/SettingController.ts b/backend/src/controllers/SettingController.ts index c8cb940..938acf8 100644 --- a/backend/src/controllers/SettingController.ts +++ b/backend/src/controllers/SettingController.ts @@ -14,21 +14,24 @@ export const index = async (req: Request, res: Response): Promise => { // } const settings = await ListSettingsService(); - const outBusinessHours = await SettingTicket.findOne({ - where: { key: "outBusinessHours" } - }); - const ticketExpiration = await SettingTicket.findOne({ - where: { key: "ticketExpiration" } - }); - return res.status(200).json({ settings, outBusinessHours, ticketExpiration }); + const config = await SettingTicket.findAll(); + + return res.status(200).json({ settings, config }); }; export const updateTicketSettings = async ( req: Request, res: Response ): Promise => { - const { outBusinessHours, ticketExpiration } = req.body; + const { + outBusinessHours, + ticketExpiration, + weekend, + saturday, + sunday, + holiday + } = req.body; if (outBusinessHours && Object.keys(outBusinessHours).length > 0) { await updateSettingTicket({ @@ -44,7 +47,44 @@ export const updateTicketSettings = async ( }); } - return res.status(200).json({ outBusinessHours, ticketExpiration }); + if (weekend && Object.keys(weekend).length > 0) { + await updateSettingTicket({ + ...weekend, + key: "weekend" + }); + } + + if (saturday && Object.keys(saturday).length > 0) { + await updateSettingTicket({ + ...saturday, + key: "saturday" + }); + } + + if (sunday && Object.keys(sunday).length > 0) { + await updateSettingTicket({ + ...sunday, + key: "sunday" + }); + } + + if (holiday && Object.keys(holiday).length > 0) { + await updateSettingTicket({ + ...holiday, + key: "holiday" + }); + } + + return res + .status(200) + .json({ + outBusinessHours, + ticketExpiration, + weekend, + saturday, + sunday, + holiday + }); }; export const update = async ( diff --git a/backend/src/database/seeds/20230808210411-add-setting-ticket-weekend.ts b/backend/src/database/seeds/20230808210411-add-setting-ticket-weekend.ts new file mode 100644 index 0000000..8ffa27d --- /dev/null +++ b/backend/src/database/seeds/20230808210411-add-setting-ticket-weekend.ts @@ -0,0 +1,34 @@ +import { QueryInterface } from "sequelize" + +module.exports = { + up: (queryInterface: QueryInterface) => { + return queryInterface.bulkInsert( + "SettingTickets", + [ + { + message: "", + startTime: new Date(), + endTime: new Date(), + value: "disabled", + key: "saturday", + createdAt: new Date(), + updatedAt: new Date() + }, + { + message: "", + startTime: new Date(), + endTime: new Date(), + value: "disabled", + key: "sunday", + createdAt: new Date(), + updatedAt: new Date() + } + ], + {} + ); + }, + + down: (queryInterface: QueryInterface) => { + return queryInterface.bulkDelete("SettingTickets", {}) + } +} diff --git a/backend/src/database/seeds/20230808210840-add-setting-ticket-holiday.ts b/backend/src/database/seeds/20230808210840-add-setting-ticket-holiday.ts new file mode 100644 index 0000000..efd73ce --- /dev/null +++ b/backend/src/database/seeds/20230808210840-add-setting-ticket-holiday.ts @@ -0,0 +1,25 @@ +import { QueryInterface } from "sequelize"; + +module.exports = { + up: (queryInterface: QueryInterface) => { + return queryInterface.bulkInsert( + "SettingTickets", + [ + { + message: "", + startTime: new Date(), + endTime: new Date(), + value: "disabled", + key: "holiday", + createdAt: new Date(), + updatedAt: new Date() + }, + ], + {} + ); + }, + + down: (queryInterface: QueryInterface) => { + return queryInterface.bulkDelete("SettingTickets", {}); + } +}; diff --git a/backend/src/database/seeds/20230809085716-add-setting-ticket-weekend-enable.ts b/backend/src/database/seeds/20230809085716-add-setting-ticket-weekend-enable.ts new file mode 100644 index 0000000..48fd2db --- /dev/null +++ b/backend/src/database/seeds/20230809085716-add-setting-ticket-weekend-enable.ts @@ -0,0 +1,25 @@ +import { QueryInterface } from "sequelize"; + +module.exports = { + up: (queryInterface: QueryInterface) => { + return queryInterface.bulkInsert( + "SettingTickets", + [ + { + message: "", + startTime: new Date(), + endTime: new Date(), + value: "disabled", + key: "weekend", + createdAt: new Date(), + updatedAt: new Date() + } + ], + {} + ); + }, + + down: (queryInterface: QueryInterface) => { + return queryInterface.bulkDelete("SettingTickets", {}); + } +}; diff --git a/backend/src/helpers/AutoCloseTickets.ts b/backend/src/helpers/AutoCloseTickets.ts index 2948035..4074c37 100644 --- a/backend/src/helpers/AutoCloseTickets.ts +++ b/backend/src/helpers/AutoCloseTickets.ts @@ -39,14 +39,14 @@ const AutoCloseTickets = async () => { const seconds = timeStringToSeconds(startTime.fullTime); - console.log("Ticket seconds: ", seconds); + // console.log("Ticket seconds: ", seconds); let tickets: any = await ListTicketTimeLife({ timeseconds: seconds, status: "open" }); - console.log("tickets: ", tickets); + // console.log("tickets: ", tickets); for (let i = 0; i < tickets.length; i++) { diff --git a/backend/src/services/WbotServices/wbotMessageListener.ts b/backend/src/services/WbotServices/wbotMessageListener.ts index 426b119..d5dfae9 100644 --- a/backend/src/services/WbotServices/wbotMessageListener.ts +++ b/backend/src/services/WbotServices/wbotMessageListener.ts @@ -11,10 +11,15 @@ import { format as _format, isWithinInterval, parse, - subMinutes + subMinutes, + isSaturday, + isSunday, + parseISO } from "date-fns"; import ptBR from "date-fns/locale/pt-BR"; +import { utcToZonedTime, zonedTimeToUtc } from "date-fns-tz"; + import { Contact as WbotContact, Message as WbotMessage, @@ -596,13 +601,103 @@ const handleMessage = async (msg: any, wbot: any): Promise => { //Habilitar esse caso queira usar o bot // const botInfo = await BotIsOnQueue('botqueue') - // const botInfo = { isOnQueue: false, botQueueId: 0, userIdBot: 0 }; - + // const botInfo = { isOnQueue: false, botQueueId: 0, userIdBot: 0 }; if (msg && !msg.fromMe && ticket.status == "pending") { await setMessageAsRead(ticket); } + // MESSAGE TO HOLIDAY + const holiday = await SettingTicket.findOne({ + where: { key: "holiday" } + }); + + if ( + holiday && + holiday.value == "enabled" && + holiday.message?.trim()?.length > 0 + ) { + const startTime = splitDateTime( + new Date( + _format(new Date(holiday.startTime), "yyyy-MM-dd HH:mm:ss", { + locale: ptBR + }) + ) + ); + + const currentDate = splitDateTime( + new Date( + _format(new Date(), "yyyy-MM-dd HH:mm:ss", { + locale: ptBR + }) + ) + ); + + if (msg.fromMe && holiday && holiday.message.trim() == msg.body.trim()) { + console.log("HOLIDAY DAY"); + return; + } + + if (currentDate.fullDate == startTime.fullDate) { + botSendMessage(ticket, holiday.message); + return; + } + } + + // MESSAGES TO SATURDAY OR SUNDAY + const weekend = await SettingTicket.findOne({ + where: { key: "weekend" } + }); + + if ( + weekend && + weekend.value == "enabled" && + weekend.message?.trim()?.length > 0 + ) { + if (msg.fromMe && weekend.message.trim() == msg.body.trim()) { + console.log("SATURDAY OR SUNDAY DATE"); + return; + } + + // Specify your desired timezone + const brazilTimeZone = "America/Sao_Paulo"; + + const currentDateUtc = new Date(); + + // Convert UTC date to Brazil time zone + const currentDate = utcToZonedTime(currentDateUtc, brazilTimeZone); + + // Format the date using the desired format + const formattedDate = _format(currentDate, "yyyy-MM-dd HH:mm:ssXXX"); + + const parsedDate = parseISO(formattedDate); + + // Convert parsed date to Brazil time zone + const localDate = utcToZonedTime(parsedDate, brazilTimeZone); + + // Check if it's Saturday or Sunday + if (isSaturday(localDate)) { + const saturday = await SettingTicket.findOne({ + where: { key: "saturday" } + }); + + if (saturday && saturday.value == "enabled") { + botSendMessage(ticket, weekend.message); + return; + } + } else if (isSunday(localDate)) { + const sunday = await SettingTicket.findOne({ + where: { key: "sunday" } + }); + + if (sunday && sunday.value == "enabled") { + botSendMessage(ticket, weekend.message); + return; + } + } + } + + // MESSAGE TO BUSINESS TIME const businessTime: any = await BusinessTime(); if ( diff --git a/frontend/package.json b/frontend/package.json index 50b81dc..681cd4b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -4,13 +4,13 @@ "private": true, "dependencies": { "@date-io/date-fns": "^1.3.13", - "@emotion/react": "^11.7.1", - "@emotion/styled": "^11.6.0", + "@emotion/react": "^11.11.1", + "@emotion/styled": "^11.11.0", "@material-ui/core": "^4.12.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "^4.0.0-alpha.56", "@material-ui/pickers": "^3.3.10", - "@mui/material": "^5.3.0", + "@mui/material": "^5.14.4", "@mui/x-data-grid": "^5.3.0", "@testing-library/jest-dom": "^5.11.4", "@testing-library/react": "^11.0.4", @@ -31,6 +31,7 @@ "react": "^17.0.2", "react-color": "^2.19.3", "react-csv": "^2.2.2", + "react-datepicker": "^4.16.0", "react-dom": "^17.0.2", "react-modal-image": "^2.5.0", "react-router-dom": "^5.2.0", diff --git a/frontend/src/components/ConfigModal/index.js b/frontend/src/components/ConfigModal/index.js index 7c08118..2187fdc 100644 --- a/frontend/src/components/ConfigModal/index.js +++ b/frontend/src/components/ConfigModal/index.js @@ -4,15 +4,16 @@ import { Formik, Form, Field, } from 'formik' import { toast } from 'react-toastify' import { makeStyles } from '@material-ui/core/styles' -import { green } from '@material-ui/core/colors' +import { green } from '@material-ui/core/colors' -import { TimePicker } from 'formik-material-ui-pickers' +import { TimePicker, DatePicker } from 'formik-material-ui-pickers' import DateFnsUtils from '@date-io/date-fns' import ptBrLocale from "date-fns/locale/pt-BR" + import { MuiPickersUtilsProvider, } from '@material-ui/pickers' @@ -73,10 +74,17 @@ const ConfigModal = ({ open, onClose, change }) => { startTimeBus: new Date(), endTimeBus: new Date(), messageBus: '', - businessTimeEnalbe: false, + businessTimeEnable: false, ticketTimeExpiration: new Date(), ticketExpirationMsg: '', ticketExpirationEnable: false, + holidayDate: new Date(), + holidayDateEnable: false, + holidayDateMessage: '', + checkboxSundayValue: false, + checkboxSaturdayValue: false, + weekendMessage: '', + enableWeekendMessage: false } const [config, setConfig] = useState(initialState) @@ -87,14 +95,35 @@ const ConfigModal = ({ open, onClose, change }) => { try { const { data } = await api.get('/settings') + console.log('data.config: ', data.config) + + const outBusinessHours = data.config.find((c) => c.key == "outBusinessHours") + const ticketExpiration = data.config.find((c) => c.key == "ticketExpiration") + const saturday = data.config.find((c) => c.key == "saturday") + const sunday = data.config.find((c) => c.key == "sunday") + const weekend = data.config.find((c) => c.key == "weekend") + + + const holiday = data.config.find((c) => c.key == "holiday") + setConfig({ - startTimeBus: data.outBusinessHours.startTime, - endTimeBus: data.outBusinessHours.endTime, - messageBus: data.outBusinessHours.message, - businessTimeEnalbe: data.outBusinessHours.value === 'enabled' ? true : false, - ticketTimeExpiration: data.ticketExpiration.startTime, - ticketExpirationMsg: data.ticketExpiration.message, - ticketExpirationEnable: data.ticketExpiration.value === 'enabled' ? true : false + startTimeBus: outBusinessHours.startTime, + endTimeBus: outBusinessHours.endTime, + messageBus: outBusinessHours.message, + businessTimeEnable: outBusinessHours.value === 'enabled' ? true : false, + + ticketTimeExpiration: ticketExpiration.startTime, + ticketExpirationMsg: ticketExpiration.message, + ticketExpirationEnable: ticketExpiration.value === 'enabled' ? true : false, + + checkboxSaturdayValue: saturday.value === 'enabled' ? true : false, + checkboxSundayValue: sunday.value === 'enabled' ? true : false, + weekendMessage: weekend.message, + enableWeekendMessage: weekend.value === 'enabled' ? true : false, + + holidayDate: holiday.startTime, + holidayDateMessage: holiday.message, + holidayDateEnable: holiday.value === 'enabled' ? true : false, }) } catch (err) { @@ -111,19 +140,35 @@ const ConfigModal = ({ open, onClose, change }) => { startTime: values.startTimeBus, endTime: values.endTimeBus, message: values.messageBus, - value: values.businessTimeEnalbe ? 'enabled' : 'disabled' + value: values.businessTimeEnable ? 'enabled' : 'disabled' }, ticketExpiration: { startTime: values.ticketTimeExpiration, message: values.ticketExpirationMsg, value: values.ticketExpirationEnable ? 'enabled' : 'disabled' + }, + weekend: { + message: values.weekendMessage, + value: values.enableWeekendMessage ? 'enabled' : 'disabled' + }, + saturday:{ + value: values.checkboxSaturdayValue ? 'enabled' : 'disabled' + }, + sunday: { + value: values.checkboxSundayValue ? 'enabled' : 'disabled' + }, + holiday: { + startTime: values.holidayDate, + message: values.holidayDateMessage, + value: values.holidayDateEnable ? 'enabled' : 'disabled' } + } - try { - - await api.put(`/settings/ticket`, values) + try { + + await api.put(`/settings/ticket`, values) toast.success('Atualização realizada com sucesso!') handleClose() @@ -157,10 +202,10 @@ const ConfigModal = ({ open, onClose, change }) => { enableReinitialize={true} // validationSchema={SessionSchema} onSubmit={(values, actions) => { - - setTimeout(() => { - handleSaveConfig(values) - actions.setSubmitting(false) + + setTimeout(() => { + handleSaveConfig(values) + actions.setSubmitting(false) }, 100) }} > @@ -196,8 +241,8 @@ const ConfigModal = ({ open, onClose, change }) => { } label={'Ativar/Desativar'} /> @@ -223,8 +268,108 @@ const ConfigModal = ({ open, onClose, change }) => { /> +
-
+ + + {/* Saturday and Sunday date */} +
+
+ + + +
+ + + } + label={'Ativar/Desativar'} + /> +
+
+ +
+ +
+ + {/* Holiday date */} +
+ + + + + } + label={'Ativar/Desativar'} + /> +
+
+ +
+ + + + + + +
+
{ openTo="hours" views={['hours', 'minutes',]} format="HH:mm" - /> + /> { ) } + + +const TimePickerField = ({ field, form, ...other }) => { + const { name, value } = field + + const handleChange = (time) => { + form.setFieldValue(name, time, true) + } + + return ( + + ) +} + + export default React.memo(ConfigModal) From b3ac76e340e032ac3983960adbfa3fc97f44511d Mon Sep 17 00:00:00 2001 From: adriano Date: Wed, 9 Aug 2023 10:24:11 -0300 Subject: [PATCH 6/8] =?UTF-8?q?Refatora=C3=A7=C3=A3o=20do=20codigo=20de=20?= =?UTF-8?q?configura=C3=A7=C3=A3o=20de=20tickets?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/helpers/TicketConfig.ts | 183 ++++++++++++++++ .../WbotServices/wbotMessageListener.ts | 195 ++++-------------- frontend/src/components/ConfigModal/index.js | 8 +- 3 files changed, 224 insertions(+), 162 deletions(-) create mode 100644 backend/src/helpers/TicketConfig.ts diff --git a/backend/src/helpers/TicketConfig.ts b/backend/src/helpers/TicketConfig.ts new file mode 100644 index 0000000..830646a --- /dev/null +++ b/backend/src/helpers/TicketConfig.ts @@ -0,0 +1,183 @@ +import SettingTicket from "../models/SettingTicket"; +import { splitDateTime } from "./SplitDateTime"; +import { utcToZonedTime, zonedTimeToUtc } from "date-fns-tz"; + +import { + format as _format, + isWithinInterval, + parse, + subMinutes, + isSaturday, + isSunday, + parseISO +} from "date-fns"; +import ptBR from "date-fns/locale/pt-BR"; + +const isHoliday = async () => { + let obj = { set: false, msg: "" }; + + const holiday = await SettingTicket.findOne({ + where: { key: "holiday" } + }); + + if ( + holiday && + holiday.value == "enabled" && + holiday.message?.trim()?.length > 0 + ) { + const startTime = splitDateTime( + new Date( + _format(new Date(holiday.startTime), "yyyy-MM-dd HH:mm:ss", { + locale: ptBR + }) + ) + ); + + const currentDate = splitDateTime( + new Date( + _format(new Date(), "yyyy-MM-dd HH:mm:ss", { + locale: ptBR + }) + ) + ); + + if (currentDate.fullDate == startTime.fullDate) { + obj.set = true; + obj.msg = holiday.message.trim(); + } + } + + return obj; +}; + +const isWeekend = async () => { + let obj = { set: false, msg: "" }; + + const weekend = await SettingTicket.findOne({ + where: { key: "weekend" } + }); + + if ( + weekend && + weekend.value == "enabled" && + weekend.message?.trim()?.length > 0 + ) { + + // Specify your desired timezone + const brazilTimeZone = "America/Sao_Paulo"; + + const currentDateUtc = new Date(); + + // Convert UTC date to Brazil time zone + const currentDate = utcToZonedTime(currentDateUtc, brazilTimeZone); + + // Format the date using the desired format + const formattedDate = _format(currentDate, "yyyy-MM-dd HH:mm:ssXXX"); + + const parsedDate = parseISO(formattedDate); + + // Convert parsed date to Brazil time zone + const localDate = utcToZonedTime(parsedDate, brazilTimeZone); + + // Check if it's Saturday or Sunday + if (isSaturday(localDate)) { + const saturday = await SettingTicket.findOne({ + where: { key: "saturday" } + }); + + if (saturday && saturday.value == "enabled") { + // botSendMessage(ticket, weekend.message); + obj.set = true; + obj.msg = weekend.message; + } + } else if (isSunday(localDate)) { + const sunday = await SettingTicket.findOne({ + where: { key: "sunday" } + }); + + if (sunday && sunday.value == "enabled") { + // botSendMessage(ticket, weekend.message); + obj.set = true; + obj.msg = weekend.message; + } + } + else{ + // obj.set = true; + // obj.msg = weekend.message; + } + + return obj; + } +}; + +async function isOutBusinessTime() { + let obj = { set: false, msg: "" }; + + const outBusinessHours = await SettingTicket.findOne({ + where: { key: "outBusinessHours" } + }); + + let isWithinRange = false; + + if ( + outBusinessHours && + outBusinessHours.value == "enabled" && + outBusinessHours?.message?.trim()?.length > 0 + ) { + const ticketDateTimeUpdate = splitDateTime( + new Date( + _format(new Date(), "yyyy-MM-dd HH:mm:ss", { + locale: ptBR + }) + ) + ); + + const startTime = splitDateTime( + new Date( + _format(new Date(outBusinessHours.startTime), "yyyy-MM-dd HH:mm:ss", { + locale: ptBR + }) + ) + ); + + const endTime = splitDateTime( + new Date( + _format(new Date(outBusinessHours.endTime), "yyyy-MM-dd HH:mm:ss", { + locale: ptBR + }) + ) + ); + + const format = "HH:mm:ss"; + const parsedStartTime = parse( + ticketDateTimeUpdate.fullTime, + format, + new Date() + ); + const parsedEndTime = parse(startTime.fullTime, format, new Date()); + const parsedTimeToCheck = parse(endTime.fullTime, format, new Date()); + const timeInterval = { start: parsedStartTime, end: parsedEndTime }; + + // If the time range spans across different days, handle the date part + if (parsedEndTime < parsedStartTime) { + const nextDay = new Date(parsedStartTime); + nextDay.setDate(nextDay.getDate() + 1); + timeInterval.end = nextDay; + } + + isWithinRange = isWithinInterval(parsedTimeToCheck, timeInterval); + + if (!isWithinRange) { + obj.set = true; + obj.msg = outBusinessHours.message; + } + } + + return obj; +} + +export { + isWeekend, + isHoliday, + isOutBusinessTime +}; diff --git a/backend/src/services/WbotServices/wbotMessageListener.ts b/backend/src/services/WbotServices/wbotMessageListener.ts index d5dfae9..7e812b6 100644 --- a/backend/src/services/WbotServices/wbotMessageListener.ts +++ b/backend/src/services/WbotServices/wbotMessageListener.ts @@ -7,6 +7,12 @@ import { copyFolder } from "../../helpers/CopyFolder"; import { removeDir } from "../../helpers/DeleteDirectory"; import path from "path"; +import { + isHoliday, + isOutBusinessTime, + isWeekend +} from "../../helpers/TicketConfig"; + import { format as _format, isWithinInterval, @@ -608,174 +614,51 @@ const handleMessage = async (msg: any, wbot: any): Promise => { } // MESSAGE TO HOLIDAY - const holiday = await SettingTicket.findOne({ - where: { key: "holiday" } - }); + const holiday: any = await isHoliday(); - if ( - holiday && - holiday.value == "enabled" && - holiday.message?.trim()?.length > 0 - ) { - const startTime = splitDateTime( - new Date( - _format(new Date(holiday.startTime), "yyyy-MM-dd HH:mm:ss", { - locale: ptBR - }) - ) - ); - - const currentDate = splitDateTime( - new Date( - _format(new Date(), "yyyy-MM-dd HH:mm:ss", { - locale: ptBR - }) - ) - ); - - if (msg.fromMe && holiday && holiday.message.trim() == msg.body.trim()) { - console.log("HOLIDAY DAY"); + if (holiday.set) { + if (msg.fromMe && holiday.msg == msg.body) { + console.log("HOLIDAY MESSAGE IGNORED"); return; } - if (currentDate.fullDate == startTime.fullDate) { - botSendMessage(ticket, holiday.message); - return; - } - } - - // MESSAGES TO SATURDAY OR SUNDAY - const weekend = await SettingTicket.findOne({ - where: { key: "weekend" } - }); - - if ( - weekend && - weekend.value == "enabled" && - weekend.message?.trim()?.length > 0 - ) { - if (msg.fromMe && weekend.message.trim() == msg.body.trim()) { - console.log("SATURDAY OR SUNDAY DATE"); - return; - } - - // Specify your desired timezone - const brazilTimeZone = "America/Sao_Paulo"; - - const currentDateUtc = new Date(); - - // Convert UTC date to Brazil time zone - const currentDate = utcToZonedTime(currentDateUtc, brazilTimeZone); - - // Format the date using the desired format - const formattedDate = _format(currentDate, "yyyy-MM-dd HH:mm:ssXXX"); - - const parsedDate = parseISO(formattedDate); - - // Convert parsed date to Brazil time zone - const localDate = utcToZonedTime(parsedDate, brazilTimeZone); - - // Check if it's Saturday or Sunday - if (isSaturday(localDate)) { - const saturday = await SettingTicket.findOne({ - where: { key: "saturday" } - }); - - if (saturday && saturday.value == "enabled") { - botSendMessage(ticket, weekend.message); - return; - } - } else if (isSunday(localDate)) { - const sunday = await SettingTicket.findOne({ - where: { key: "sunday" } - }); - - if (sunday && sunday.value == "enabled") { - botSendMessage(ticket, weekend.message); - return; - } - } - } - - // MESSAGE TO BUSINESS TIME - const businessTime: any = await BusinessTime(); - - if ( - msg.fromMe && - businessTime && - !businessTime.isWithinRange && - businessTime.message.trim() == msg.body.trim() - ) { - console.log("BUSINESS TIME OUT"); + botSendMessage(ticket, holiday.msg); return; } - if (!businessTime.isWithinRange && businessTime.message.trim().length > 0) { - botSendMessage(ticket, businessTime.message); + // MESSAGES TO SATURDAY OR SUNDAY + const weekend: any = await isWeekend(); + + if (weekend.set) { + if (msg.fromMe && weekend.msg == msg.body) { + console.log("WEEKEND MESSAGE IGNORED"); + return; + } + + botSendMessage(ticket, weekend.msg); + return; } + + // MESSAGE TO BUSINESS TIME + const businessTime = await isOutBusinessTime(); + + if (businessTime.set) { + if (msg.fromMe && businessTime.msg == msg.body) { + console.log("BUSINESS TIME MESSAGE IGNORED"); + return; + } + + botSendMessage(ticket, businessTime.msg); + return; + } + + } catch (err) { Sentry.captureException(err); console.log("Error handling whatsapp message: Err: ", err); logger.error(`Error handling whatsapp message: Err: ${err}`); - } - - async function BusinessTime() { - const outBusinessHours = await SettingTicket.findOne({ - where: { key: "outBusinessHours" } - }); - - let isWithinRange = false; - - if (outBusinessHours && outBusinessHours.value == "enabled") { - const ticketDateTimeUpdate = splitDateTime( - new Date( - _format(new Date(), "yyyy-MM-dd HH:mm:ss", { - locale: ptBR - }) - ) - ); - - const startTime = splitDateTime( - new Date( - _format(new Date(outBusinessHours.startTime), "yyyy-MM-dd HH:mm:ss", { - locale: ptBR - }) - ) - ); - - const endTime = splitDateTime( - new Date( - _format(new Date(outBusinessHours.endTime), "yyyy-MM-dd HH:mm:ss", { - locale: ptBR - }) - ) - ); - - const format = "HH:mm:ss"; - const parsedStartTime = parse( - ticketDateTimeUpdate.fullTime, - format, - new Date() - ); - const parsedEndTime = parse(startTime.fullTime, format, new Date()); - const parsedTimeToCheck = parse(endTime.fullTime, format, new Date()); - - const timeInterval = { start: parsedStartTime, end: parsedEndTime }; - - // If the time range spans across different days, handle the date part - if (parsedEndTime < parsedStartTime) { - const nextDay = new Date(parsedStartTime); - nextDay.setDate(nextDay.getDate() + 1); - timeInterval.end = nextDay; - } - - isWithinRange = isWithinInterval(parsedTimeToCheck, timeInterval); - - console.log("Is the time within the range?", isWithinRange); - - return { isWithinRange, message: outBusinessHours.message }; - } - } + } + }; const handleMsgAck = async (msg_id: any, ack: any) => { diff --git a/frontend/src/components/ConfigModal/index.js b/frontend/src/components/ConfigModal/index.js index 2187fdc..0f2ef88 100644 --- a/frontend/src/components/ConfigModal/index.js +++ b/frontend/src/components/ConfigModal/index.js @@ -93,17 +93,13 @@ const ConfigModal = ({ open, onClose, change }) => { const fetchSession = async () => { try { - const { data } = await api.get('/settings') - - console.log('data.config: ', data.config) + const { data } = await api.get('/settings') const outBusinessHours = data.config.find((c) => c.key == "outBusinessHours") const ticketExpiration = data.config.find((c) => c.key == "ticketExpiration") const saturday = data.config.find((c) => c.key == "saturday") const sunday = data.config.find((c) => c.key == "sunday") - const weekend = data.config.find((c) => c.key == "weekend") - - + const weekend = data.config.find((c) => c.key == "weekend") const holiday = data.config.find((c) => c.key == "holiday") setConfig({ From 7b1d2f2d2b4acce207a7d7b00ed4d025b82920ba Mon Sep 17 00:00:00 2001 From: adriano Date: Wed, 9 Aug 2023 10:26:54 -0300 Subject: [PATCH 7/8] =?UTF-8?q?refatora=C3=A7=C3=A3o=20de=20frontend?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/ConfigModal/index.js | 31 +++++--------------- 1 file changed, 7 insertions(+), 24 deletions(-) diff --git a/frontend/src/components/ConfigModal/index.js b/frontend/src/components/ConfigModal/index.js index 0f2ef88..c221455 100644 --- a/frontend/src/components/ConfigModal/index.js +++ b/frontend/src/components/ConfigModal/index.js @@ -95,12 +95,12 @@ const ConfigModal = ({ open, onClose, change }) => { try { const { data } = await api.get('/settings') - const outBusinessHours = data.config.find((c) => c.key == "outBusinessHours") - const ticketExpiration = data.config.find((c) => c.key == "ticketExpiration") - const saturday = data.config.find((c) => c.key == "saturday") - const sunday = data.config.find((c) => c.key == "sunday") - const weekend = data.config.find((c) => c.key == "weekend") - const holiday = data.config.find((c) => c.key == "holiday") + const outBusinessHours = data.config.find((c) => c.key === "outBusinessHours") + const ticketExpiration = data.config.find((c) => c.key === "ticketExpiration") + const saturday = data.config.find((c) => c.key === "saturday") + const sunday = data.config.find((c) => c.key === "sunday") + const weekend = data.config.find((c) => c.key === "weekend") + const holiday = data.config.find((c) => c.key === "holiday") setConfig({ startTimeBus: outBusinessHours.startTime, @@ -443,24 +443,7 @@ const ConfigModal = ({ open, onClose, change }) => { } - -const TimePickerField = ({ field, form, ...other }) => { - const { name, value } = field - - const handleChange = (time) => { - form.setFieldValue(name, time, true) - } - - return ( - - ) -} + export default React.memo(ConfigModal) From ee11b977ccb243edd30b1c6dd738410a8f1ea3a6 Mon Sep 17 00:00:00 2001 From: adriano Date: Thu, 10 Aug 2023 09:32:38 -0300 Subject: [PATCH 8/8] =?UTF-8?q?Adi=C3=A7=C3=A3o=20do=20recurso=20para=20tr?= =?UTF-8?q?ansferir=20ticket=20quando=20entrar=20em=20loop=20infinito=20de?= =?UTF-8?q?=20ura?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/helpers/MostRepeatedPhrase.ts | 56 ++++++++++++++ .../WbotServices/wbotMessageListener.ts | 74 +++++++++++-------- 2 files changed, 100 insertions(+), 30 deletions(-) create mode 100644 backend/src/helpers/MostRepeatedPhrase.ts diff --git a/backend/src/helpers/MostRepeatedPhrase.ts b/backend/src/helpers/MostRepeatedPhrase.ts new file mode 100644 index 0000000..71ced82 --- /dev/null +++ b/backend/src/helpers/MostRepeatedPhrase.ts @@ -0,0 +1,56 @@ +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; diff --git a/backend/src/services/WbotServices/wbotMessageListener.ts b/backend/src/services/WbotServices/wbotMessageListener.ts index 7e812b6..8bef346 100644 --- a/backend/src/services/WbotServices/wbotMessageListener.ts +++ b/backend/src/services/WbotServices/wbotMessageListener.ts @@ -82,6 +82,7 @@ import { import AppError from "../../errors/AppError"; import { setMessageAsRead } from "../../helpers/SetMessageAsRead"; import SettingTicket from "../../models/SettingTicket"; +import mostRepeatedPhrase from "../../helpers/MostRepeatedPhrase"; var lst: any[] = getWhatsappIds(); @@ -299,12 +300,20 @@ const verifyQueue = async ( sendWhatsAppMessageSocket(ticket, body); } else { //test del transfere o atendimento se entrar na ura infinita - let ticket_message = await ShowTicketMessage(ticket.id, false); - if (ticket_message.length > 10) { + const repet: any = await mostRepeatedPhrase(ticket.id); + + if (repet.occurrences > 4) { await UpdateTicketService({ ticketData: { status: "pending", queueId: queues[0].id }, ticketId: ticket.id }); + + await SendWhatsAppMessage({ + body: `Seu atendimento foi transferido para um agente! + `, + ticket, + number: `${contact.number}@c.us` + }); } else { let options = ""; @@ -613,52 +622,57 @@ const handleMessage = async (msg: any, wbot: any): Promise => { await setMessageAsRead(ticket); } - // MESSAGE TO HOLIDAY - const holiday: any = await isHoliday(); + let ticketHasQueue = false; - if (holiday.set) { - if (msg.fromMe && holiday.msg == msg.body) { - console.log("HOLIDAY MESSAGE IGNORED"); + if (ticket?.queueId) { + ticketHasQueue = true; + } + + if (ticketHasQueue) { + // MESSAGE TO HOLIDAY + const holiday: any = await isHoliday(); + + if (holiday.set) { + if (msg.fromMe && holiday.msg == msg.body) { + console.log("HOLIDAY MESSAGE IGNORED"); + return; + } + + botSendMessage(ticket, holiday.msg); return; } - botSendMessage(ticket, holiday.msg); - return; - } + // MESSAGES TO SATURDAY OR SUNDAY + const weekend: any = await isWeekend(); - // MESSAGES TO SATURDAY OR SUNDAY - const weekend: any = await isWeekend(); + if (weekend.set) { + if (msg.fromMe && weekend.msg == msg.body) { + console.log("WEEKEND MESSAGE IGNORED"); + return; + } - if (weekend.set) { - if (msg.fromMe && weekend.msg == msg.body) { - console.log("WEEKEND MESSAGE IGNORED"); + botSendMessage(ticket, weekend.msg); return; } - botSendMessage(ticket, weekend.msg); - return; - } + // MESSAGE TO BUSINESS TIME + const businessTime = await isOutBusinessTime(); - // MESSAGE TO BUSINESS TIME - const businessTime = await isOutBusinessTime(); + if (businessTime.set) { + if (msg.fromMe && businessTime.msg == msg.body) { + console.log("BUSINESS TIME MESSAGE IGNORED"); + return; + } - if (businessTime.set) { - if (msg.fromMe && businessTime.msg == msg.body) { - console.log("BUSINESS TIME MESSAGE IGNORED"); + botSendMessage(ticket, businessTime.msg); return; } - - botSendMessage(ticket, businessTime.msg); - return; } - - } catch (err) { Sentry.captureException(err); console.log("Error handling whatsapp message: Err: ", err); logger.error(`Error handling whatsapp message: Err: ${err}`); - } - + } }; const handleMsgAck = async (msg_id: any, ack: any) => {