diff --git a/backend/src/controllers/ReportController.ts b/backend/src/controllers/ReportController.ts index 01664d4..a2956f0 100644 --- a/backend/src/controllers/ReportController.ts +++ b/backend/src/controllers/ReportController.ts @@ -20,6 +20,7 @@ import ShowQueuesByUser from "../services/UserServices/ShowQueuesByUser"; import { getIO } from "../libs/socket"; import { Json } from "sequelize/types/lib/utils"; import ReportByNumberQueueService from "../services/ReportServices/ReportByNumberQueueService"; +import CountStatusChatEndService from "../services/StatusChatEndService/CountStatusChatEndService"; type IndexQuery = { userId: string; @@ -312,7 +313,7 @@ export const reportService = async ( const reportService = await ReportByNumberQueueService({ startDate, endDate - }); + }); return res.status(200).json({ reportService }); }; @@ -331,12 +332,37 @@ export const reportServiceByQueue = async ( const { startDate, endDate, queueId } = req.query as IndexQuery; - const reportService = await ReportByNumberQueueService({ startDate, endDate, queue: true - }); + }); return res.status(200).json({ reportService }); }; + +export const reportTicksCountByStatusChatEnds = 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 { startDate, endDate } = req.query as IndexQuery; + + const dateToday = splitDateTime( + new Date(format(new Date(), "yyyy-MM-dd HH:mm:ss", { locale: ptBR })) + ); + + const reportStatusChatEnd = await CountStatusChatEndService( + startDate || dateToday.fullDate, + endDate || dateToday.fullDate + ); + + return res.status(200).json({ reportStatusChatEnd }); +}; diff --git a/backend/src/controllers/TicketController.ts b/backend/src/controllers/TicketController.ts index 2f62ba3..e8f327c 100644 --- a/backend/src/controllers/TicketController.ts +++ b/backend/src/controllers/TicketController.ts @@ -76,6 +76,7 @@ import CreateContactService from "../services/ContactServices/CreateContactServi import { botSendMessage } from "../services/WbotServices/wbotMessageListener"; import WhatsappQueue from "../models/WhatsappQueue"; import { get } from "../helpers/RedisClient" +import CountStatusChatEndService from "../services/StatusChatEndService/CountStatusChatEndService" export const index = async (req: Request, res: Response): Promise => { const { @@ -109,8 +110,8 @@ export const index = async (req: Request, res: Response): Promise => { withUnreadMessages, unlimited, searchParamContent - }); - + }); + return res.status(200).json({ tickets, count, hasMore }); }; @@ -348,7 +349,8 @@ export const update = async ( ticketData: { status: status, userId: userId, - statusChatEnd: statusChatEndName.name + statusChatEnd: statusChatEndName.name, + statusChatEndId: statusChatEndName.id }, ticketId }); diff --git a/backend/src/database/migrations/20240325193138-change-column-reference-statusChatEndId-to-tickets.ts b/backend/src/database/migrations/20240325193138-change-column-reference-statusChatEndId-to-tickets.ts new file mode 100644 index 0000000..9043cea --- /dev/null +++ b/backend/src/database/migrations/20240325193138-change-column-reference-statusChatEndId-to-tickets.ts @@ -0,0 +1,17 @@ +import { QueryInterface, DataTypes } from "sequelize"; + +module.exports = { + up: (queryInterface: QueryInterface) => { + return queryInterface.addColumn("Tickets", "statusChatEndId", { + type: DataTypes.INTEGER, + references: { model: "StatusChatEnds", key: "id" }, + onUpdate: "CASCADE", + onDelete: "SET NULL", + allowNull: true + }); + }, + + down: (queryInterface: QueryInterface) => { + return queryInterface.removeColumn("Tickets", "statusChatEndId"); + } +}; diff --git a/backend/src/models/StatusChatEnd.ts b/backend/src/models/StatusChatEnd.ts index dde5775..fc4505f 100644 --- a/backend/src/models/StatusChatEnd.ts +++ b/backend/src/models/StatusChatEnd.ts @@ -10,25 +10,29 @@ import { } from "sequelize-typescript"; import SchedulingNotify from "./SchedulingNotify"; +import Ticket from "./Ticket" @Table class StatusChatEnd extends Model { @PrimaryKey @AutoIncrement @Column - id: number; - + id: number; + @Column name: string; - + @CreatedAt createdAt: Date; - + @UpdatedAt updatedAt: Date; @HasMany(() => SchedulingNotify) SchedulingNotifies: SchedulingNotify[]; + + @HasMany(() => Ticket) + tickets: Ticket[]; } export default StatusChatEnd; diff --git a/backend/src/models/Ticket.ts b/backend/src/models/Ticket.ts index d3b95c9..42175c8 100644 --- a/backend/src/models/Ticket.ts +++ b/backend/src/models/Ticket.ts @@ -21,6 +21,7 @@ import User from "./User"; import Whatsapp from "./Whatsapp"; import SchedulingNotify from "./SchedulingNotify"; +import StatusChatEnd from "./StatusChatEnd" @Table class Ticket extends Model { @@ -42,6 +43,10 @@ class Ticket extends Model { @Column isGroup: boolean; + @ForeignKey(() => StatusChatEnd) + @Column + statusChatEndId: number; + @Column statusChatEnd: string; diff --git a/backend/src/routes/reportRoutes.ts b/backend/src/routes/reportRoutes.ts index d6228ef..ecaf41a 100644 --- a/backend/src/routes/reportRoutes.ts +++ b/backend/src/routes/reportRoutes.ts @@ -1,26 +1,48 @@ //relatorio -import express from "express"; +import express from "express"; -import isAuth from "../middleware/isAuth"; - -import * as ReportController from "../controllers/ReportController"; +import isAuth from "../middleware/isAuth"; -const reportRoutes = express.Router(); +import * as ReportController from "../controllers/ReportController"; -reportRoutes.get("/reports", isAuth, ReportController.reportUserByDateStartDateEnd); +const reportRoutes = express.Router(); -reportRoutes.post("/reports/onqueue", ReportController.reportOnQueue); +reportRoutes.get( + "/reports", + isAuth, + ReportController.reportUserByDateStartDateEnd +); -reportRoutes.get("/reports/user/services", isAuth, ReportController.reportUserService); +reportRoutes.post("/reports/onqueue", ReportController.reportOnQueue); + +reportRoutes.get( + "/reports/user/services", + isAuth, + ReportController.reportUserService +); reportRoutes.get( "/reports/services/numbers", isAuth, ReportController.reportService -); +); -reportRoutes.get("/reports/services/queues", isAuth, ReportController.reportServiceByQueue); +reportRoutes.get( + "/reports/services/queues", + isAuth, + ReportController.reportServiceByQueue +); -reportRoutes.get("/reports/messages", isAuth, ReportController.reportMessagesUserByDateStartDateEnd); +reportRoutes.get( + "/reports/messages", + isAuth, + ReportController.reportMessagesUserByDateStartDateEnd +); + +reportRoutes.get( + "/reports/count/statusChatEnd", + isAuth, + ReportController.reportTicksCountByStatusChatEnds +); export default reportRoutes; diff --git a/backend/src/services/ReportServices/ReportByNumberQueueService.ts b/backend/src/services/ReportServices/ReportByNumberQueueService.ts index 32cd1f4..02c07dd 100644 --- a/backend/src/services/ReportServices/ReportByNumberQueueService.ts +++ b/backend/src/services/ReportServices/ReportByNumberQueueService.ts @@ -113,7 +113,8 @@ const ReportByNumberQueueService = async ({ AND m.fromMe = 0 -- AND q.id = 2 AND w.number = ${number} - AND (t.status = 'open' OR t.status = 'closed') + AND t.status IN ('open', 'closed') + HAVING WAITING_TIME IS NOT NULL ORDER BY WAITING_TIME;`, { type: QueryTypes.SELECT } @@ -237,7 +238,8 @@ const ReportByNumberQueueService = async ({ AND m.createdAt = (SELECT MIN(createdAt) FROM Messages WHERE ticketId = t.id) AND m.fromMe = 0 AND q.id = ${q.id} - AND (t.status = 'open' OR t.status = 'closed') + AND t.status IN ('open', 'closed') + HAVING WAITING_TIME IS NOT NULL ORDER BY WAITING_TIME;`, { type: QueryTypes.SELECT } diff --git a/backend/src/services/StatusChatEndService/CountStatusChatEndService.ts b/backend/src/services/StatusChatEndService/CountStatusChatEndService.ts new file mode 100644 index 0000000..bffbb5d --- /dev/null +++ b/backend/src/services/StatusChatEndService/CountStatusChatEndService.ts @@ -0,0 +1,27 @@ +import StatusChatEnd from "../../models/StatusChatEnd"; +import AppError from "../../errors/AppError"; + +import { Sequelize } from "sequelize"; +import { splitDateTime } from "../../helpers/SplitDateTime" +import ptBR from "date-fns/locale/pt-BR"; +import { format } from "date-fns" +const dbConfig = require("../../config/database"); +const sequelize = new Sequelize(dbConfig); +const { QueryTypes } = require("sequelize"); + +const CountStatusChatEndService = async ( + startDate: string, + endDate: string +) => { + + const countStatusChatEnd: any = await sequelize.query( + `select t.id, s.name, count(t.id) as count from Tickets t join StatusChatEnds s on +t.statusChatEndId = s.id and DATE(t.createdAt) BETWEEN '${startDate} 00:00:00.000000' AND '${endDate} 23:59:59.999999' +group by s.id;`, + { type: QueryTypes.SELECT } + ); + + return countStatusChatEnd; +}; + +export default CountStatusChatEndService; diff --git a/backend/src/services/TicketServices/ShowTicketReport.ts b/backend/src/services/TicketServices/ShowTicketReport.ts index 8e75349..f26919a 100644 --- a/backend/src/services/TicketServices/ShowTicketReport.ts +++ b/backend/src/services/TicketServices/ShowTicketReport.ts @@ -7,7 +7,7 @@ import Queue from "../../models/Queue"; import Message from "../../models/Message"; import { userInfo } from "os"; -import { Op, QueryTypes, where } from "sequelize"; +import { Op, QueryTypes, json, where } from "sequelize"; import { Sequelize } from "sequelize"; import moment from "moment"; @@ -17,7 +17,7 @@ const sequelize = new Sequelize(dbConfig); import { startOfDay, endOfDay, parseISO, getDate } from "date-fns"; import { string } from "yup/lib/locale"; import Whatsapp from "../../models/Whatsapp"; -import Query from "mysql2/typings/mysql/lib/protocol/sequences/Query" +import Query from "mysql2/typings/mysql/lib/protocol/sequences/Query"; interface Request { userId: string | number; @@ -73,9 +73,7 @@ const ShowTicketReport = async ({ { type: QueryTypes.SELECT } ); - console.log('QUERY: ', query) - - const { count, rows: tickets } = await Ticket.findAndCountAll({ + let { count, rows: tickets }: any = await Ticket.findAndCountAll({ where: { id: { [Op.in]: _ticketsId.map((t: any) => t.id) } }, @@ -153,6 +151,58 @@ const ShowTicketReport = async ({ throw new AppError("ERR_NO_TICKET_FOUND", 404); } + const ticketIds = tickets.map((t: any) => t.id); + + if (ticketIds.length > 0) { + const waiting_time: any = await sequelize.query( + `SELECT t.id as ticketId, t.status, TIME_FORMAT( + SEC_TO_TIME( + TIMESTAMPDIFF( + SECOND, + ( + SELECT createdAt + FROM Messages + WHERE ticketId = m.ticketId + AND fromMe = 0 + ORDER BY createdAt ASC + LIMIT 1 + ), + ( + SELECT createdAt + FROM Messages + WHERE ticketId = m.ticketId + AND fromAgent = 1 + ORDER BY createdAt ASC + LIMIT 1 + ) + ) + ), '%H:%i:%s') AS WAITING_TIME + FROM Tickets t + JOIN Messages m ON t.id = m.ticketId + JOIN Whatsapps w ON t.whatsappId = w.id + JOIN Queues q ON q.id = t.queueId + WHERE DATE(m.createdAt) BETWEEN '${startDate} 00:00:00.000000' AND '${endDate} 23:59:59.999999' + AND t.id IN (${ticketIds.join()}) + AND m.createdAt = (SELECT MIN(createdAt) FROM Messages WHERE ticketId = t.id) + AND m.fromMe = 0 + AND t.status IN ('open', 'closed') + HAVING WAITING_TIME IS NOT NULL + ORDER BY + WAITING_TIME;`, + { type: QueryTypes.SELECT } + ); + + for (let w of waiting_time) { + const { ticketId, status, WAITING_TIME } = w; + + const index = tickets.findIndex((t: any) => +t?.id == +ticketId); + + if (index != -1) { + tickets[index].dataValues.waiting_time = WAITING_TIME; + } + } + } + return { tickets, count, hasMore }; }; diff --git a/backend/src/services/TicketServices/UpdateTicketService.ts b/backend/src/services/TicketServices/UpdateTicketService.ts index 1c3b10a..943015b 100644 --- a/backend/src/services/TicketServices/UpdateTicketService.ts +++ b/backend/src/services/TicketServices/UpdateTicketService.ts @@ -10,7 +10,7 @@ import { createOrUpdateTicketCache } from "../../helpers/TicketCache"; import AppError from "../../errors/AppError"; import sendWhatsAppMessageSocket from "../../helpers/SendWhatsappMessageSocket"; import BotIsOnQueue from "../../helpers/BotIsOnQueue"; -import { deleteObject } from "../../helpers/RedisClient" +import { deleteObject } from "../../helpers/RedisClient"; var flatten = require("flat"); interface TicketData { @@ -18,6 +18,7 @@ interface TicketData { userId?: number; queueId?: number; statusChatEnd?: string; + statusChatEndId?: number; unreadMessages?: number; whatsappId?: string | number; } @@ -46,6 +47,7 @@ const UpdateTicketService = async ({ queueId, statusChatEnd, unreadMessages, + statusChatEndId, whatsappId } = ticketData; @@ -66,13 +68,14 @@ const UpdateTicketService = async ({ if (oldStatus === "closed") { await CheckContactOpenTickets(ticket.contact.id, ticket.whatsappId); } - + await ticket.update({ status, queueId, userId, unreadMessages, statusChatEnd, + statusChatEndId, whatsappId }); diff --git a/frontend/src/pages/Report/index.js b/frontend/src/pages/Report/index.js index 055f95e..d3b97fa 100644 --- a/frontend/src/pages/Report/index.js +++ b/frontend/src/pages/Report/index.js @@ -235,6 +235,7 @@ let columnsData = [ { title: `${i18n.t("reports.listColumns.column1_7")}`, field: 'createdAt' }, { title: `${i18n.t("reports.listColumns.column1_8")}`, field: 'updatedAt' }, { title: `${i18n.t("reports.listColumns.column1_9")}`, field: 'statusChatEnd' }, + { title: `Espera`, field: 'waiting_time' }, { title: `Mensagens`, field: 'messagesToFilter', searchable: true, hidden: true }, ] @@ -249,6 +250,7 @@ let columnsDataSuper = [ { title: `${i18n.t("reports.listColumns.column1_7")}`, field: 'createdAt' }, { title: `${i18n.t("reports.listColumns.column1_8")}`, field: 'updatedAt' }, { title: `${i18n.t("reports.listColumns.column1_9")}`, field: 'statusChatEnd' }, + { title: `Espera`, field: 'waiting_time' }, { title: `Mensagens`, field: 'messagesToFilter', searchable: true, hidden: true }, ]