diff --git a/backend/src/controllers/ContactController.ts b/backend/src/controllers/ContactController.ts index 7ab9363..1b194a1 100644 --- a/backend/src/controllers/ContactController.ts +++ b/backend/src/controllers/ContactController.ts @@ -20,12 +20,18 @@ import { } from "../helpers/ContactsCache"; import { off } from "process"; +import GetContactService from "../services/ContactServices/GetContactService" type IndexQuery = { searchParam: string; pageNumber: string; }; +type IndexGetContactQuery = { + name: string; + number: string; +}; + interface ExtraInfo { name: string; value: string; @@ -84,6 +90,20 @@ export const index = async (req: Request, res: Response): Promise => { return res.json({ contacts, count, hasMore }); }; +export const getContact = async ( + req: Request, + res: Response +): Promise => { + const { name, number } = req.body as IndexGetContactQuery; + + const contact = await GetContactService({ + name, + number + }); + + return res.status(200).json(contact); +}; + export const store = async (req: Request, res: Response): Promise => { const newContact: ContactData = req.body; newContact.number = newContact.number.replace("-", "").replace(" ", ""); diff --git a/backend/src/controllers/MessageController.ts b/backend/src/controllers/MessageController.ts index 234d654..2016bca 100644 --- a/backend/src/controllers/MessageController.ts +++ b/backend/src/controllers/MessageController.ts @@ -76,7 +76,7 @@ export const store = async (req: Request, res: Response): Promise => { let payloadComponents = []; - try { + try { for (let i in params) { const { parameters, language, type } = params[i]; if (type == "BODY") { @@ -97,7 +97,7 @@ export const store = async (req: Request, res: Response): Promise => { } const name = params.find((p: any) => p?.template_name); - const { language }: any = params.find((p: any) => p?.language); + const { language }: any = params?.find((p: any) => p?.language) || 'pt_BR' const { template_name } = name; @@ -110,9 +110,10 @@ export const store = async (req: Request, res: Response): Promise => { } }; + console.log("TEMPLATE: ", template); + sendWhatsAppMessageOfficialAPI(ticket, body, null, template); - console.log("TEMPLATE: ", template); return res.send(); } } catch (error: any) { diff --git a/backend/src/controllers/ReportController.ts b/backend/src/controllers/ReportController.ts index bced8df..211c9bc 100644 --- a/backend/src/controllers/ReportController.ts +++ b/backend/src/controllers/ReportController.ts @@ -4,28 +4,19 @@ import { Request, Response } from "express"; import AppError from "../errors/AppError"; import ShowTicketReport from "../services/TicketServices/ShowTicketReport"; import ShowMessageReport from "../services/MessageServices/ShowMessageReport"; - import onlineUserService from "../services/UserServices/CreateOrUpdateOnlineUserService"; import User from "../models/User"; import Queue from "../models/Queue"; import UserOnlineTime from "../models/UserOnlineTime"; - import { Op, Sequelize, literal } from "sequelize"; -import format from 'date-fns/format'; -import ptBR from 'date-fns/locale/pt-BR'; +import format from "date-fns/format"; +import ptBR from "date-fns/locale/pt-BR"; import { splitDateTime } from "../helpers/SplitDateTime"; import ListUserOnlineOffline from "../services/UserServices/ListUsersOnlineOfflineService"; import ListUserParamiterService from "../services/UserServices/ListUserParamiterService"; - import ShowUserServiceReport from "../services/UserServices/ShowUserServiceReport"; - import CountTicketsByUserQueue from "../services/UserServices/CountTicketsByUserQueue"; - import ShowQueuesByUser from "../services/UserServices/ShowQueuesByUser"; - - -// import { filter } from "bluebird"; - import { getIO } from "../libs/socket"; import { Json } from "sequelize/types/lib/utils"; @@ -33,6 +24,8 @@ type IndexQuery = { userId: string; startDate: string; endDate: string; + createdOrUpdated: string; + queueId: string; pageNumber: string; userQueues: []; }; @@ -40,11 +33,12 @@ type IndexQuery = { type ReportOnQueue = { userId: string; identifier: string; -} - - -export const reportUserByDateStartDateEnd = async (req: Request, res: Response): Promise => { +}; +export const reportUserByDateStartDateEnd = async ( + req: Request, + res: Response +): Promise => { if ( req.user.profile !== "master" && req.user.profile !== "admin" && @@ -53,79 +47,145 @@ export const reportUserByDateStartDateEnd = async (req: Request, res: Response): throw new AppError("ERR_NO_PERMISSION", 403); } - const { userId, startDate, endDate, pageNumber, userQueues } = req.query as IndexQuery + const { + userId, + startDate, + endDate, + pageNumber, + userQueues, + createdOrUpdated, + queueId + } = req.query as IndexQuery; - console.log("userId, startDate, endDate, pageNumber: ", userId, startDate, endDate, pageNumber); + console.log( + "userId, startDate, endDate, pageNumber, userQueues, createdOrUpdated, queueId: ", + userId, + startDate, + endDate, + pageNumber, + userQueues, + createdOrUpdated, + queueId + ); - const { tickets, count, hasMore } = await ShowTicketReport({ userId, startDate, endDate, pageNumber }); - // console.log('kkkkkkkkkkkkkkkkkk tickets: ', JSON.stringify(tickets, null, 6)) - - return res.status(200).json({ tickets, count, hasMore }); + // return res.status(200).json({ tickets:[], count:0, hasMore:false, queues:[] }); + + const { tickets, count, hasMore } = await ShowTicketReport({ + userId, + startDate, + endDate, + pageNumber, + createdOrUpdated, + queueId + }); + + const queues = await Queue.findAll({ attributes: ["id", "name"] }); + + return res.status(200).json({ tickets, count, hasMore, queues }); }; - -export const reportUserService = async (req: Request, res: Response): Promise => { - - if (req.user.profile !== "master" && req.user.profile !== "admin" && req.user.profile !=="supervisor") { +export const reportUserService = async ( + req: Request, + res: Response +): Promise => { + if ( + req.user.profile !== "master" && + req.user.profile !== "admin" && + req.user.profile !== "supervisor" + ) { throw new AppError("ERR_NO_PERMISSION", 403); } - const { userId, startDate, endDate } = req.query as IndexQuery - + const { userId, startDate, endDate } = req.query as IndexQuery; + // let usersProfile = await ListUserParamiterService({ profile: 'user' }) let usersProfile = await ListUserParamiterService({ profiles: ["user", "supervisor"], raw: true }); - const sumUserOlineTime = await ShowUserServiceReport({ startDate, endDate, userId }); - const closedByUser = await ShowUserServiceReport({ startDate, endDate, ticketStatus: 'closed', userId }); - const openByUser = await ShowUserServiceReport({ startDate, endDate, ticketStatus: 'open', userId }); + const sumUserOlineTime = await ShowUserServiceReport({ + startDate, + endDate, + userId + }); + const closedByUser = await ShowUserServiceReport({ + startDate, + endDate, + ticketStatus: "closed", + userId + }); + const openByUser = await ShowUserServiceReport({ + startDate, + endDate, + ticketStatus: "open", + userId + }); - let dateTime = splitDateTime(new Date(format(new Date(), 'yyyy-MM-dd HH:mm:ss', { locale: ptBR }))) - const onlineUsers = await ListUserOnlineOffline({ date: dateTime.fullDate }) + let dateTime = splitDateTime( + new Date(format(new Date(), "yyyy-MM-dd HH:mm:ss", { locale: ptBR })) + ); + const onlineUsers = await ListUserOnlineOffline({ date: dateTime.fullDate }); - const openByUserOnQueue = await CountTicketsByUserQueue({ startDate: startDate, endDate: endDate, status: 'open', clientChatStart: true }) - const openByUserOutQueue = await CountTicketsByUserQueue({ startDate: startDate, endDate: endDate, status: 'open', clientChatStart: false }) + const openByUserOnQueue = await CountTicketsByUserQueue({ + startDate: startDate, + endDate: endDate, + status: "open", + clientChatStart: true + }); + const openByUserOutQueue = await CountTicketsByUserQueue({ + startDate: startDate, + endDate: endDate, + status: "open", + clientChatStart: false + }); - const closedByUserOnQueue = await CountTicketsByUserQueue({ startDate: startDate, endDate: endDate, status: 'closed', clientChatStart: true }) - const closedUserOutQueue = await CountTicketsByUserQueue({ startDate: startDate, endDate: endDate, status: 'closed', clientChatStart: false }) + const closedByUserOnQueue = await CountTicketsByUserQueue({ + startDate: startDate, + endDate: endDate, + status: "closed", + clientChatStart: true + }); + const closedUserOutQueue = await CountTicketsByUserQueue({ + startDate: startDate, + endDate: endDate, + status: "closed", + clientChatStart: false + }); // let openQueueInOut = openByUserOnQueue.concat(openByUserOutQueue) // let closedQueueInOut = closedByUserOnQueue.concat(closedUserOutQueue) + const queuesByUser = await ShowQueuesByUser({ profile: "user" }); - const queuesByUser = await ShowQueuesByUser({ profile: 'user' }) - - let openCloseOnQueue = openByUserOnQueue.concat(closedByUserOnQueue) - let openCloseOutQueue = openByUserOutQueue.concat(closedUserOutQueue) - + let openCloseOnQueue = openByUserOnQueue.concat(closedByUserOnQueue); + let openCloseOutQueue = openByUserOutQueue.concat(closedUserOutQueue); // console.log('onlineUsers: ',JSON.parse(JSON.stringify(onlineUsers))) // console.log('sumUserOlineTime: ', JSON.parse(JSON.stringify(sumUserOlineTime))) for (let i = 0; i < queuesByUser.length; i++) { - - queuesByUser[i].countOpen = 0 - queuesByUser[i].countClosed = 0 + queuesByUser[i].countOpen = 0; + queuesByUser[i].countClosed = 0; for (let x = 0; x < openCloseOnQueue.length; x++) { - if ((queuesByUser[i].userId == openCloseOnQueue[x].userId) && - (queuesByUser[i].queueId == openCloseOnQueue[x].queueId && openCloseOnQueue[x].status == 'open')) { - queuesByUser[i].countOpen = openCloseOnQueue[x].totAttendance + if ( + queuesByUser[i].userId == openCloseOnQueue[x].userId && + queuesByUser[i].queueId == openCloseOnQueue[x].queueId && + openCloseOnQueue[x].status == "open" + ) { + queuesByUser[i].countOpen = openCloseOnQueue[x].totAttendance; + } else if ( + queuesByUser[i].userId == openCloseOnQueue[x].userId && + queuesByUser[i].queueId == openCloseOnQueue[x].queueId && + openCloseOnQueue[x].status == "closed" + ) { + queuesByUser[i].countClosed = openCloseOnQueue[x].totAttendance; } - else if ((queuesByUser[i].userId == openCloseOnQueue[x].userId) && - (queuesByUser[i].queueId == openCloseOnQueue[x].queueId && openCloseOnQueue[x].status == 'closed')) { - queuesByUser[i].countClosed = openCloseOnQueue[x].totAttendance - } - } } - - usersProfile.map((user: any) => { - - let index = sumUserOlineTime.findIndex((e: any) => e.userId == user.id) + let index = sumUserOlineTime.findIndex((e: any) => e.userId == user.id); if (index != -1) { user.sumOnlineTime = sumUserOlineTime[index]; @@ -133,68 +193,67 @@ export const reportUserService = async (req: Request, res: Response): Promise e.userId == user.id) + index = closedByUser.findIndex((e: any) => e.userId == user.id); if (index != -1) { user.sumClosed = closedByUser[index]; } - index = openByUser.findIndex((e: any) => e.userId == user.id) + index = openByUser.findIndex((e: any) => e.userId == user.id); if (index != -1) { - user.sumOpen = openByUser[index] + user.sumOpen = openByUser[index]; } - // OPEN, CLOSED TICKETS STARTED BY USERS - let openClosedOutQueue = {} - let open = openCloseOutQueue.filter((e) => e.userId == user.id && e.status == 'open') - let closed = openCloseOutQueue.filter((e) => e.userId == user.id && e.status == 'closed') + let openClosedOutQueue = {}; + let open = openCloseOutQueue.filter( + e => e.userId == user.id && e.status == "open" + ); + let closed = openCloseOutQueue.filter( + e => e.userId == user.id && e.status == "closed" + ); openClosedOutQueue = { ...openClosedOutQueue, userId: user.id, countOpen: open && open.length > 0 ? open[0].totAttendance : 0, countClosed: closed && closed.length > 0 ? closed[0].totAttendance : 0 - } - - user.openClosedOutQueue = openClosedOutQueue + }; + user.openClosedOutQueue = openClosedOutQueue; // OPEN, CLOSED TICKETS STARTED BY CLIENTS - let openClosedInQueue = queuesByUser.filter((e) => e.userId == user.id) + let openClosedInQueue = queuesByUser.filter(e => e.userId == user.id); if (openClosedInQueue && openClosedInQueue.length > 0) { - user.openClosedInQueue = openClosedInQueue + user.openClosedInQueue = openClosedInQueue; } - - index = onlineUsers.findIndex((e: any) => e.userId == user.id) + index = onlineUsers.findIndex((e: any) => e.userId == user.id); if (index != -1) { - user.statusOnline = onlineUsers[index] + user.statusOnline = onlineUsers[index]; } - if (startDate.length > 0 && startDate.split('-').length == 3) { - let date = startDate.split('-') - user.startDate = `${date[2]}/${date[1]}/${date[0]}` + if (startDate.length > 0 && startDate.split("-").length == 3) { + let date = startDate.split("-"); + user.startDate = `${date[2]}/${date[1]}/${date[0]}`; } - if (endDate.length > 0 && endDate.split('-').length == 3) { - let date = endDate.split('-') - user.endDate = `${date[2]}/${date[1]}/${date[0]}` + if (endDate.length > 0 && endDate.split("-").length == 3) { + let date = endDate.split("-"); + user.endDate = `${date[2]}/${date[1]}/${date[0]}`; } + }); - }) - - return res.status(200).json({usersProfile: usersProfile}); - + return res.status(200).json({ usersProfile: usersProfile }); }; - - -export const reportMessagesUserByDateStartDateEnd = async (req: Request, res: Response): Promise => { - +export const reportMessagesUserByDateStartDateEnd = async ( + req: Request, + res: Response +): Promise => { if ( req.user.profile !== "master" && req.user.profile !== "admin" && @@ -203,35 +262,32 @@ export const reportMessagesUserByDateStartDateEnd = async (req: Request, res: Re throw new AppError("ERR_NO_PERMISSION", 403); } - const { userId, startDate, endDate } = req.query as IndexQuery + const { userId, startDate, endDate } = req.query as IndexQuery; let data_query_messages = await ShowMessageReport(userId, startDate, endDate); for (var i = 0; i < data_query_messages.length; i++) { - if (data_query_messages[i].fromMe) { - data_query_messages[i].fromMe = 'Atendente' - } - else { - data_query_messages[i].fromMe = 'Cliente' + data_query_messages[i].fromMe = "Atendente"; + } else { + data_query_messages[i].fromMe = "Cliente"; } - data_query_messages[i].id = (i + 1) + data_query_messages[i].id = i + 1; - console.log('data_query_messages: ', data_query_messages[i]) + console.log("data_query_messages: ", data_query_messages[i]); } return res.status(200).json(data_query_messages); - }; - - -export const reportOnQueue = async (req: Request, res: Response): Promise => { - +export const reportOnQueue = async ( + req: Request, + res: Response +): Promise => { // console.log(req.body) - const { adminId, identifier, queueStatus, file } = req.body + const { adminId, identifier, queueStatus, file } = req.body; const io = getIO(); io.emit("queryOnQueueStatus", { @@ -244,10 +300,5 @@ export const reportOnQueue = async (req: Request, res: Response): Promise => { if (queueIdsStringified) { queueIds = JSON.parse(queueIdsStringified); - } + } const { tickets, count, hasMore } = await ListTicketsService({ searchParam, @@ -109,7 +109,7 @@ export const index = async (req: Request, res: Response): Promise => { unlimited, searchParamContent }); - + return res.status(200).json({ tickets, count, hasMore }); }; @@ -120,12 +120,21 @@ export const remoteTicketCreation = async ( const { contact_from, contact_to, msg, contact_name }: any = req.body; const validate = ["contact_from", "contact_to", "msg"]; + const validateOnlyNumber = ["contact_from", "contact_to"]; for (let prop of validate) { if (!req.body[prop]) return res .status(400) .json({ error: `Property '${prop}' is undefined.` }); + + if (validateOnlyNumber.includes(prop)) { + if (!/^\d+$/.test(req.body[prop])) { + return res + .status(400) + .json({ error: `The property '${prop}' must be a number` }); + } + } } const whatsapp = await Whatsapp.findOne({ @@ -192,7 +201,7 @@ export const remoteTicketCreation = async ( ticketId: ticket.id }); } - } + } if (!ticket) { ticket = await FindOrCreateTicketService( @@ -226,11 +235,11 @@ export const remoteTicketCreation = async ( } console.log( - `REMOTE TICKET CREATION FROM ENDPOINT | STATUS: 500 | MSG: Whatsapp number ${contact_from} disconnected` + `REMOTE TICKET CREATION FROM ENDPOINT | STATUS: 500 | MSG: Whatsapp number ${contact_from} disconnected or it doesn't exist in omnihit` ); - return res - .status(500) - .json({ msg: `Whatsapp number ${contact_from} disconnected` }); + return res.status(500).json({ + msg: `Whatsapp number ${contact_from} disconnected or it doesn't exist in omnihit` + }); }; export const store = async (req: Request, res: Response): Promise => { diff --git a/backend/src/controllers/UserController.ts b/backend/src/controllers/UserController.ts index d36b7ff..88d463a 100644 --- a/backend/src/controllers/UserController.ts +++ b/backend/src/controllers/UserController.ts @@ -99,7 +99,7 @@ export const index = async (req: Request, res: Response): Promise => { // }; export const all = async (req: Request, res: Response): Promise => { - const { userId, profile } = req.query as IndexQuery; + let { userId, profile }: any = req.query as IndexQuery; console.log( "userId: ", @@ -110,7 +110,10 @@ export const all = async (req: Request, res: Response): Promise => { getSettingValue("queueTransferByWhatsappScope")?.value ); - if (getSettingValue("queueTransferByWhatsappScope")?.value == "enabled") { + if (getSettingValue("queueTransferByWhatsappScope")?.value == "enabled") { + + if (!userId) return res.json({ users: [], queues: [] }); + const obj = await ListUserByWhatsappQueuesService( userId, '"admin", "user", "supervisor"' @@ -119,7 +122,7 @@ export const all = async (req: Request, res: Response): Promise => { const usersByWhatsqueue = obj.users; const queues = obj.queues; - let userIds = usersByWhatsqueue.map((w: any) => w.userId); + let userIds = usersByWhatsqueue.map((w: any) => w.userId); const users = await ListUser({ userIds diff --git a/backend/src/controllers/WhatsAppController.ts b/backend/src/controllers/WhatsAppController.ts index 9b0e532..c71d883 100644 --- a/backend/src/controllers/WhatsAppController.ts +++ b/backend/src/controllers/WhatsAppController.ts @@ -54,6 +54,7 @@ interface WhatsappData { isDefault?: boolean; isOfficial?: boolean; phoneNumberId?: string; + number?: string; wabaId?: string; } @@ -322,7 +323,8 @@ export const store = async (req: Request, res: Response): Promise => { urlApi, phoneNumberId, wabaId, - isOfficial + isOfficial, + number }: WhatsappData = req.body; if (req.user.profile !== "master") { @@ -333,7 +335,8 @@ export const store = async (req: Request, res: Response): Promise => { urlApi, isOfficial, phoneNumberId, - wabaId + wabaId, + number }); if (invalid) { @@ -346,6 +349,7 @@ export const store = async (req: Request, res: Response): Promise => { } else if (!isOfficial) { phoneNumberId = ""; wabaId = ""; + number = ""; } let invalidPhoneName = validatePhoneName(name); @@ -365,7 +369,8 @@ export const store = async (req: Request, res: Response): Promise => { queueIds, phoneNumberId, wabaId, - isOfficial + isOfficial, + number }); console.log("whatsapp.id: ", whatsapp.id); @@ -408,7 +413,7 @@ export const update = async ( res: Response ): Promise => { const { whatsappId } = req.params; - const whatsappData = req.body; + const whatsappData = req.body; let invalidPhoneName = validatePhoneName(whatsappData.name); @@ -549,19 +554,23 @@ interface WhatsappDataValidate { isOfficial?: boolean; phoneNumberId?: string; wabaId?: string; + number?: string; } const checkWhatsAppData = ({ urlApi, isOfficial, phoneNumberId, - wabaId + wabaId, + number }: WhatsappDataValidate) => { if (isOfficial && (!phoneNumberId || phoneNumberId.trim() == "")) { return { message: "Phone number Id is required!" }; } else if (isOfficial && (!wabaId || wabaId.trim() == "")) { return { message: "WABA ID is required!" }; + } else if (isOfficial && (!number || number.trim() == "")) { + return { message: "Phone number is required!" }; } else if (!isOfficial && (!urlApi || urlApi.trim() == "")) { return { message: "urlApi is required!" }; - } + } }; diff --git a/backend/src/helpers/CheckContactOpenTickets.ts b/backend/src/helpers/CheckContactOpenTickets.ts index 6cfc24d..303f4a7 100644 --- a/backend/src/helpers/CheckContactOpenTickets.ts +++ b/backend/src/helpers/CheckContactOpenTickets.ts @@ -15,9 +15,9 @@ const CheckContactOpenTickets = async ( if (getSettingValue("oneContactChatWithManyWhats")?.value == "enabled") { let whats = await ListWhatsAppsNumber(whatsappId); - console.log("contactId: ", contactId, " | whatsappId: ", whatsappId); + // console.log("contactId: ", contactId, " | whatsappId: ", whatsappId); - console.log("WHATS: ", JSON.stringify(whats, null, 6)); + // console.log("WHATS: ", JSON.stringify(whats, null, 6)); ticket = await Ticket.findOne({ where: { diff --git a/backend/src/helpers/RedisClient.ts b/backend/src/helpers/RedisClient.ts index aadc8c8..93c9e0a 100644 --- a/backend/src/helpers/RedisClient.ts +++ b/backend/src/helpers/RedisClient.ts @@ -17,6 +17,18 @@ export async function get(key: string) { return JSON.parse(value); } +export async function clearAllKeys() { + // Retrieve all keys matching the pattern '*' + const keys = await redis.keys("user:*"); + + // If there are keys, delete them + if (keys.length > 0) { + console.log('keys: ', keys) + await redis.del(...keys); + } +} + + export async function findByContain( key: string, keyName: string, diff --git a/backend/src/routes/contactRoutes.ts b/backend/src/routes/contactRoutes.ts index 158159c..8c77791 100644 --- a/backend/src/routes/contactRoutes.ts +++ b/backend/src/routes/contactRoutes.ts @@ -16,6 +16,8 @@ contactRoutes.get("/contacts/:contactId", isAuth, ContactController.show); contactRoutes.post("/contacts", isAuth, ContactController.store); +contactRoutes.post("/contact", isAuth, ContactController.getContact); + contactRoutes.put("/contacts/:contactId", isAuth, ContactController.update); contactRoutes.delete("/contacts/:contactId", isAuth, ContactController.remove); diff --git a/backend/src/routes/ticketRoutes.ts b/backend/src/routes/ticketRoutes.ts index 2c17f9e..80fa9d3 100644 --- a/backend/src/routes/ticketRoutes.ts +++ b/backend/src/routes/ticketRoutes.ts @@ -5,7 +5,6 @@ import * as TicketController from "../controllers/TicketController"; const ticketRoutes = express.Router(); - // ticketRoutes.get("/tickets/cache", isAuth, TicketController.ticketsCache); ticketRoutes.get("/tickets/count", isAuth, TicketController.count); diff --git a/backend/src/server.ts b/backend/src/server.ts index f02362b..6aa0b13 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -23,7 +23,7 @@ import fs from "fs"; import dir from "path"; import { getSettingValue } from "./helpers/WhaticketSettings"; import loadSettings from "./helpers/LoadSettings"; -import { set } from "./helpers/RedisClient"; +import { clearAllKeys, set } from "./helpers/RedisClient"; const server = app.listen(process.env.PORT, () => { logger.info(`Server started on port: ${process.env.PORT}`); @@ -39,11 +39,13 @@ const server = app.listen(process.env.PORT, () => { initIO(server); // StartAllWhatsAppsSessions(); -gracefulShutdown(server); +gracefulShutdown(server); (async () => { console.log("os.tmpdir(): ", os.tmpdir()); + await clearAllKeys(); + const users = await User.findAll(); for (const user of users) { diff --git a/backend/src/services/ContactServices/GetContactService.ts b/backend/src/services/ContactServices/GetContactService.ts new file mode 100644 index 0000000..3865bc5 --- /dev/null +++ b/backend/src/services/ContactServices/GetContactService.ts @@ -0,0 +1,38 @@ +import AppError from "../../errors/AppError"; +import Contact from "../../models/Contact"; +import CreateContactService from "./CreateContactService"; + +interface ExtraInfo { + name: string; + value: string; +} + +interface Request { + name: string; + number: string; + email?: string; + profilePicUrl?: string; + extraInfo?: ExtraInfo[]; +} + +const GetContactService = async ({ name, number }: Request): Promise => { + const numberExists = await Contact.findOne({ + where: { number } + }); + + if (!numberExists) { + const contact = await CreateContactService({ + name, + number, + }) + + if (contact == null) + throw new AppError("CONTACT_NOT_FIND") + else + return contact + } + + return numberExists +}; + +export default GetContactService; \ No newline at end of file diff --git a/backend/src/services/TicketServices/ListTicketsService.ts b/backend/src/services/TicketServices/ListTicketsService.ts index ac2e2fb..1ea2cc6 100644 --- a/backend/src/services/TicketServices/ListTicketsService.ts +++ b/backend/src/services/TicketServices/ListTicketsService.ts @@ -59,13 +59,8 @@ const ListTicketsService = async ({ let whereCondition: Filterable["where"] = { [Op.or]: [{ userId }, { status: "pending" }], queueId: { [Op.or]: [queueIds, null] } }; - console.log('PAGE NUMBER TICKET: ', pageNumber) - - //TEST DEL - // const url = await getWbot(46) - // console.log('---------> URL: ', url) - // - + console.log('PAGE NUMBER TICKET: ', pageNumber) + if (pageNumber.trim().length == 0) { pageNumber = '1' } @@ -136,15 +131,14 @@ const ListTicketsService = async ({ whereCondition = { ...whereCondition, status }; - if (unlimited === 'true' && status !== 'pending') { - + if (unlimited === "current" && status !== "pending") { whereCondition = { ...whereCondition, createdAt: { - [Op.gte]: dateToday.fullDate + ' 00:00:00.000000', - [Op.lte]: dateToday.fullDate + ' 23:59:59.999999' + [Op.gte]: dateToday.fullDate + " 00:00:00.000000", + [Op.lte]: dateToday.fullDate + " 23:59:59.999999" } - } + }; } } @@ -196,8 +190,8 @@ const ListTicketsService = async ({ if ( userProfile.dataValues.profile != "admin" && - userProfile.dataValues.profile != "master" - // userProfile.dataValues.profile != "supervisor" + userProfile.dataValues.profile != "master" && + userProfile.dataValues.profile != "supervisor" ) { whereCondition = { ...whereCondition, userId }; } @@ -224,9 +218,10 @@ const ListTicketsService = async ({ }; } - const limit = unlimited === 'true' ? 100000 : 40; - const offset = limit * (+pageNumber - 1); + const limit = unlimited === "current" || unlimited === "all" ? 100000 : 40; + const offset = limit * (+pageNumber - 1); + console.log("kkkkkkkkk limit: ", limit); const { count, rows: tickets } = await Ticket.findAndCountAll({ where: whereCondition, diff --git a/backend/src/services/TicketServices/ShowTicketReport.ts b/backend/src/services/TicketServices/ShowTicketReport.ts index 6fe5bf9..c60156d 100644 --- a/backend/src/services/TicketServices/ShowTicketReport.ts +++ b/backend/src/services/TicketServices/ShowTicketReport.ts @@ -9,10 +9,10 @@ import { userInfo } from "os"; import { Op, where } from "sequelize"; -import { Sequelize } from "sequelize"; -import moment from 'moment'; - -import { startOfDay, endOfDay, parseISO, getDate} from "date-fns"; +import { Sequelize } from "sequelize"; +import moment from "moment"; + +import { startOfDay, endOfDay, parseISO, getDate } from "date-fns"; import { string } from "yup/lib/locale"; import Whatsapp from "../../models/Whatsapp"; @@ -20,10 +20,11 @@ interface Request { userId: string | number; startDate: string; endDate: string; + createdOrUpdated?: string; + queueId?: string; pageNumber?: string; } - interface Response { tickets: Ticket[]; count: number; @@ -35,89 +36,124 @@ const ShowTicketReport = async ({ userId, startDate, endDate, - pageNumber = "1" -}: Request): Promise => { - - let where_clause = {} + pageNumber = "1", + createdOrUpdated = "created", + queueId +}: Request): Promise => { + let where_clause: any = {}; + // let where_clause_msg: any = {}; - if(userId=='0'){ - where_clause = { - updatedAt: { - [Op.gte]: startDate+' 00:00:00.000000', - [Op.lte]: endDate +' 23:59:59.999999' - }, - } - } - else{ - where_clause = { - userid: userId, - updatedAt: { - [Op.gte]: startDate+' 00:00:00.000000', - [Op.lte]: endDate +' 23:59:59.999999' - }, - } + if (userId !== "0") { + where_clause.userid = userId; + } + + if (createdOrUpdated === "updated") { + where_clause = { + ...where_clause, + updatedAt: { + [Op.gte]: startDate + " 00:00:00.000000", + [Op.lte]: endDate + " 23:59:59.999999" + } + }; + } + + if (createdOrUpdated === "created") { + where_clause = { + ...where_clause, + createdAt: { + [Op.gte]: startDate + " 00:00:00.000000", + [Op.lte]: endDate + " 23:59:59.999999" + } + }; + } + + let { userid, ...where_clause_msg } = where_clause; + + if (queueId) { + where_clause.queueId = queueId; } - const limit = 40; const offset = limit * (+pageNumber - 1); - - const {count, rows: tickets} = await Ticket.findAndCountAll({ - where: where_clause , + const { count, rows: tickets } = await Ticket.findAndCountAll({ + where: where_clause, limit, offset, - //attributes: ['id', 'status', 'createdAt', 'updatedAt'], - attributes: ['id', 'status', 'statusChatEnd', [Sequelize.fn("DATE_FORMAT",Sequelize.col("Ticket.createdAt"),"%d/%m/%Y %H:%i:%s"),"createdAt"], - [Sequelize.fn("DATE_FORMAT",Sequelize.col("Ticket.updatedAt"),"%d/%m/%Y %H:%i:%s"),"updatedAt"]], + attributes: [ + "id", + "status", + "statusChatEnd", + [ + Sequelize.fn( + "DATE_FORMAT", + Sequelize.col("Ticket.createdAt"), + "%d/%m/%Y %H:%i:%s" + ), + "createdAt" + ], + [ + Sequelize.fn( + "DATE_FORMAT", + Sequelize.col("Ticket.updatedAt"), + "%d/%m/%Y %H:%i:%s" + ), + "updatedAt" + ] + ], include: [ { model: Message, - required:true, + required: true, separate: true, - - // attributes: ['body', 'read', 'mediaType','fromMe', 'mediaUrl','createdAt'], + where: where_clause_msg , - attributes: ['body', 'read', 'mediaType','fromMe', 'mediaUrl', [Sequelize.fn("DATE_FORMAT",Sequelize.col("createdAt"),"%d/%m/%Y %H:%i:%s"),"createdAt"]], - - order: [ - ['createdAt', 'ASC'] - ] - }, - { - model: Contact, - attributes: ['name', 'number'] + attributes: [ + "body", + "read", + "mediaType", + "fromMe", + "mediaUrl", + [ + Sequelize.fn( + "DATE_FORMAT", + Sequelize.col("createdAt"), + "%d/%m/%Y %H:%i:%s" + ), + "createdAt" + ] + ], + order: [["createdAt", "ASC"]] }, { - model: User, - attributes: ['name', 'email'] + model: Contact, + attributes: ["name", "number"] }, { - model: Queue, - attributes: ['name'] + model: User, + attributes: ["name", "email"] }, { - model: Whatsapp, - attributes: ['name'] + model: Queue, + attributes: ["name"] }, + { + model: Whatsapp, + attributes: ["name"] + } ], - - order: [ - ['id', 'ASC'] - ] - - }); - - const hasMore = count > offset + tickets.length; - + order: [["updatedAt", "DESC"]] + }); + const hasMore = count > offset + tickets.length; + if (!tickets) { throw new AppError("ERR_NO_TICKET_FOUND", 404); } - return {tickets, count, hasMore}; -}; + return { tickets, count, hasMore }; +}; export default ShowTicketReport; diff --git a/backend/src/services/UserServices/ListUserParamiterService.ts b/backend/src/services/UserServices/ListUserParamiterService.ts index ebb610b..2d92498 100644 --- a/backend/src/services/UserServices/ListUserParamiterService.ts +++ b/backend/src/services/UserServices/ListUserParamiterService.ts @@ -2,7 +2,7 @@ import { Op, Sequelize } from "sequelize"; import Queue from "../../models/Queue"; import User from "../../models/User"; import UserQueue from "../../models/UserQueue"; -import { List } from "whatsapp-web.js" +import { List } from "whatsapp-web.js"; interface Request { userId?: string | number; @@ -12,7 +12,13 @@ interface Request { userIds?: string | number; } -const ListUser = async ({ profile, userId, raw, userIds, profiles }: Request): Promise => { +const ListUser = async ({ + profile, + userId, + raw, + userIds, + profiles +}: Request): Promise => { let where_clause = {}; if (userId && profile) { @@ -47,7 +53,7 @@ const ListUser = async ({ profile, userId, raw, userIds, profiles }: Request): P ], order: [["id", "ASC"]], - group: ["User.id"] + group: userIds ? undefined : ["User.id"] }); return users; diff --git a/backend/src/services/WbotServices/wbotMessageListener.ts b/backend/src/services/WbotServices/wbotMessageListener.ts index d0b1a8f..bb1c57e 100644 --- a/backend/src/services/WbotServices/wbotMessageListener.ts +++ b/backend/src/services/WbotServices/wbotMessageListener.ts @@ -98,6 +98,7 @@ import FindOrCreateTicketServiceBot from "../TicketServices/FindOrCreateTicketSe import ShowTicketService from "../TicketServices/ShowTicketService"; import ShowQueuesByUser from "../UserServices/ShowQueuesByUser"; import ListWhatsappQueuesByUserQueue from "../UserServices/ListWhatsappQueuesByUserQueue"; +import CreateContactService from "../ContactServices/CreateContactService" var lst: any[] = getWhatsappIds(); @@ -479,6 +480,7 @@ const isValidMsg = (msg: any): boolean => { msg.type === "image" || msg.type === "document" || msg.type === "vcard" || + // msg.type === "multi_vcard" || msg.type === "sticker" ) return true; @@ -497,7 +499,12 @@ const queuesOutBot = async (wbot: Session, botId: string | number) => { return { queues, greetingMessage }; }; -const transferTicket = async (queueName: any, wbot: any, ticket: Ticket) => { +const transferTicket = async ( + queueName: any, + wbot: any, + ticket: Ticket, + sendGreetingMessage?: boolean +) => { const botInfo = await BotIsOnQueue("botqueue"); console.log("kkkkkkkkkkkkkkkkkkkkk queueName: ", queueName); @@ -519,16 +526,24 @@ const transferTicket = async (queueName: any, wbot: any, ticket: Ticket) => { queue = queues[queueName]; } - if (queue) await botTransferTicket(queue, ticket); + if (queue) await botTransferTicket(queue, ticket, sendGreetingMessage); }; -const botTransferTicket = async (queues: Queue, ticket: Ticket) => { +const botTransferTicket = async ( + queues: Queue, + ticket: Ticket, + sendGreetingMessage?: boolean +) => { await ticket.update({ userId: null }); await UpdateTicketService({ ticketData: { status: "pending", queueId: queues.id }, ticketId: ticket.id }); + + if (sendGreetingMessage && queues?.greetingMessage?.length > 0) { + botSendMessage(ticket, queues.greetingMessage); + } }; const botTransferTicketToUser = async ( @@ -595,7 +610,7 @@ const handleMessage = async ( return; } - } + } if (!isValidMsg(msg)) { return; @@ -630,7 +645,13 @@ const handleMessage = async ( // media messages sent from me from cell phone, first comes with "hasMedia = false" and type = "image/ptt/etc" // in this case, return and let this message be handled by "media_uploaded" event, when it will have "hasMedia = true" - if (!msg.hasMedia && msg.type !== "chat" && msg.type !== "vcard") return; + if ( + !msg.hasMedia && + msg.type !== "chat" && + msg.type !== "vcard" && + msg.type !== "multi_vcard" + ) + return; } else { console.log(`\n <<<<<<<<<< RECEIVING MESSAGE: Parcial msg and msgContact info: @@ -759,12 +780,54 @@ const handleMessage = async ( await verifyQueue(wbot, msg, ticket, contact); } + if (msg.type === "vcard") { + try { + const array = msg.body.split("\n"); + const obj = []; + let contact = ""; + for (let index = 0; index < array.length; index++) { + const v = array[index]; + const values = v.split(":"); + for (let ind = 0; ind < values.length; ind++) { + if (values[ind].indexOf("+") !== -1) { + obj.push({ number: values[ind] }); + } + if (values[ind].indexOf("FN") !== -1) { + contact = values[ind + 1]; + } + } + } + for await (const ob of obj) { + const cont = await CreateContactService({ + name: contact, + number: ob.number.replace(/\D/g, "") + }); + } + } catch (error) { + console.log(error); + } + } + + const botInfo = await BotIsOnQueue("botqueue"); + // Transfer to agent - if (!msg.fromMe) { - const filteredUsers = await findByContain("user:*", "name", msg?.body); - + // 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 + if ( + !msg.fromMe && + ((ticket.status == "open" && + botInfo && + ticket.userId == +botInfo.userIdBot) || + ticket.status == "pending" || + ticket.status == "queueChoice") + ) { + const filteredUsers = await findByContain("user:*", "name", msg?.body); if (filteredUsers && filteredUsers.length > 0) { + if (botInfo.isOnQueue) { + transferTicket(filteredUsers[0].name, wbot, ticket, true); + return; + } + const whatsappQueues = await ListWhatsappQueuesByUserQueue( +filteredUsers[0].id ); @@ -791,12 +854,6 @@ const handleMessage = async ( } // - // 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 && @@ -844,11 +901,6 @@ const handleMessage = async ( menuMsg?.transferToQueue && menuMsg.transferToQueue.trim().length > 0 ) { - console.log( - "YYYYYYYYYYYYYYYYYYYY menuMsg.transferToQueue: ", - menuMsg.transferToQueue - ); - transferTicket(menuMsg.transferToQueue.trim(), wbot, ticket); } } diff --git a/backend/src/services/WhatsappService/CreateWhatsAppService.ts b/backend/src/services/WhatsappService/CreateWhatsAppService.ts index 2693709..879bde4 100644 --- a/backend/src/services/WhatsappService/CreateWhatsAppService.ts +++ b/backend/src/services/WhatsappService/CreateWhatsAppService.ts @@ -16,6 +16,7 @@ interface Request { phoneNumberId?: string; wabaId?: string; isOfficial?: boolean; + number?: string; } interface Response { @@ -34,7 +35,8 @@ const CreateWhatsAppService = async ({ isDefault = false, isOfficial = false, phoneNumberId, - wabaId + wabaId, + number }: Request): Promise => { try { const schema = Yup.object().shape({ @@ -98,6 +100,7 @@ const CreateWhatsAppService = async ({ phoneNumberId, wabaId, isOfficial, + number, classification }, { include: ["queues"] } diff --git a/frontend/src/components/Audio/index.jsx b/frontend/src/components/Audio/index.jsx new file mode 100644 index 0000000..cc0c7cc --- /dev/null +++ b/frontend/src/components/Audio/index.jsx @@ -0,0 +1,62 @@ +import { Button } from "@material-ui/core"; +import React, { useRef } from "react"; +import { useEffect } from "react"; +import { useState } from "react"; + +const LS_NAME = 'audioMessageRate'; + +export default function({url}) { + const audioRef = useRef(null); + const [audioRate, setAudioRate] = useState( parseFloat(localStorage.getItem(LS_NAME) || "1") ); + const [showButtonRate, setShowButtonRate] = useState(false); + + useEffect(() => { + audioRef.current.playbackRate = audioRate; + localStorage.setItem(LS_NAME, audioRate); + }, [audioRate]); + + useEffect(() => { + audioRef.current.onplaying = () => { + setShowButtonRate(true); + }; + audioRef.current.onpause = () => { + setShowButtonRate(false); + }; + audioRef.current.onended = () => { + setShowButtonRate(false); + }; + }, []); + + const toogleRate = () => { + let newRate = null; + + switch(audioRate) { + case 0.5: + newRate = 1; + break; + case 1: + newRate = 1.5; + break; + case 1.5: + newRate = 2; + break; + case 2: + newRate = 0.5; + break; + default: + newRate = 1; + break; + } + + setAudioRate(newRate); + }; + + return ( + <> + + {showButtonRate && } + + ); +} \ No newline at end of file diff --git a/frontend/src/components/LocationPreview/index.js b/frontend/src/components/LocationPreview/index.js new file mode 100644 index 0000000..9187faf --- /dev/null +++ b/frontend/src/components/LocationPreview/index.js @@ -0,0 +1,53 @@ +import React, { useEffect } from 'react'; +import toastError from "../../errors/toastError"; + +import Typography from "@material-ui/core/Typography"; +import Grid from "@material-ui/core/Grid"; + +import { Button, Divider, } from "@material-ui/core"; + +const LocationPreview = ({ image, link, description }) => { + useEffect(() => {}, [image, link, description]); + + const handleLocation = async() => { + try { + window.open(link); + } catch (err) { + toastError(err); + } + } + + return ( + <> +
+
+
+ +
+ { description && ( +
+ +
') }}>
+
+
+ )} +
+
+ + +
+
+
+ + ); + +}; + +export default LocationPreview; \ No newline at end of file diff --git a/frontend/src/components/MarkdownWrapper/index.js b/frontend/src/components/MarkdownWrapper/index.js index 64764b2..3d2c01b 100644 --- a/frontend/src/components/MarkdownWrapper/index.js +++ b/frontend/src/components/MarkdownWrapper/index.js @@ -1,5 +1,5 @@ -import React from "react"; -import Markdown from "markdown-to-jsx"; +import React from "react" +import Markdown from "markdown-to-jsx" const elements = [ "a", @@ -139,25 +139,32 @@ const elements = [ "svg", "text", "tspan", -]; +] -const allowedElements = ["a", "b", "strong", "em", "u", "code", "del"]; +const allowedElements = ["a", "b", "strong", "em", "u", "code", "del"] const CustomLink = ({ children, ...props }) => ( {children} -); +) const MarkdownWrapper = ({ children }) => { - const boldRegex = /\*(.*?)\*/g; - const tildaRegex = /~(.*?)~/g; + const boldRegex = /\*(.*?)\*/g + const tildaRegex = /~(.*?)~/g + + if (children && children.includes('BEGIN:VCARD')) + //children = "Diga olá ao seu novo contato clicando em *conversar*!"; + children = null + + if (children && children.includes('data:image/')) + children = null if (children && boldRegex.test(children)) { - children = children.replace(boldRegex, "**$1**"); + children = children.replace(boldRegex, "**$1**") } if (children && tildaRegex.test(children)) { - children = children.replace(tildaRegex, "~~$1~~"); + children = children.replace(tildaRegex, "~~$1~~") } const options = React.useMemo(() => { @@ -167,20 +174,20 @@ const MarkdownWrapper = ({ children }) => { overrides: { a: { component: CustomLink }, }, - }; + } elements.forEach(element => { if (!allowedElements.includes(element)) { - markdownOptions.overrides[element] = el => el.children || null; + markdownOptions.overrides[element] = el => el.children || null } - }); + }) - return markdownOptions; - }, []); + return markdownOptions + }, []) - if (!children) return null; + if (!children) return null - return {children}; -}; + return {children} +} -export default MarkdownWrapper; +export default MarkdownWrapper \ No newline at end of file diff --git a/frontend/src/components/MessageInput/index.js b/frontend/src/components/MessageInput/index.js index f760ff5..9773517 100644 --- a/frontend/src/components/MessageInput/index.js +++ b/frontend/src/components/MessageInput/index.js @@ -350,6 +350,8 @@ const MessageInput = ({ ticketStatus }) => { try { + console.log('kkkkkkkkkkkkkkkkkkk message: ', message) + const { data } = await api.post(`/messages/${ticketId}`, message) setParams(null) if (data && data?.data && Array.isArray(data.data)) { @@ -371,7 +373,11 @@ const MessageInput = ({ ticketStatus }) => { if (!params) return - const body_params = params.find(p => p?.type === 'BODY') + const body_params = params?.find(p => p?.type === 'BODY') + + console.log('------------> body_params: ', body_params) + + if(!body_params) return let { text } = body_params diff --git a/frontend/src/components/MessagesList/index.js b/frontend/src/components/MessagesList/index.js index 236601a..9d25471 100644 --- a/frontend/src/components/MessagesList/index.js +++ b/frontend/src/components/MessagesList/index.js @@ -23,6 +23,11 @@ import { } from "@material-ui/icons"; import MarkdownWrapper from "../MarkdownWrapper"; +import VcardPreview from "../VcardPreview"; +import LocationPreview from "../LocationPreview"; +import Audio from "../Audio"; + + import ModalImageCors from "../ModalImageCors"; import MessageOptionsMenu from "../MessageOptionsMenu"; import whatsBackground from "../../assets/wa-background.png"; @@ -488,27 +493,109 @@ const MessagesList = ({ ticketId, isGroup }) => { setAnchorEl(null); }; + // const checkMessageMedia = (message) => { + // if (message.mediaType === "image") { + // return ; + // } + // if (message.mediaType === "audio") { + + // return ( + // + // ); + // } + + // if (message.mediaType === "video") { + // return ( + //