diff --git a/src/common/enums/message.enum.ts b/src/common/enums/message.enum.ts index c8e6c63..d21c8c7 100755 --- a/src/common/enums/message.enum.ts +++ b/src/common/enums/message.enum.ts @@ -245,6 +245,8 @@ export const enum TicketMessageEnum { TICKET_CLOSED = "تیکت قبلا بسته شده است", ERROR_IN_TICKET_MSG_CREATION = "خطا در ایجاد پیام تیکت", SERVICE_NOT_FOUND = "سرویس مورد نظر یافت نشد", + TICKET_NOT_ASSIGNED = "تیکت به کارشناسی اختصاص داده نشده است", + TICKET_NOT_ASSIGNED_TO_USER = "تیکت به شما اختصاص داده نشده است", } export const enum WalletMessage { diff --git a/src/modules/tickets/entities/ticket.entity.ts b/src/modules/tickets/entities/ticket.entity.ts index c9e05cc..880b4e2 100755 --- a/src/modules/tickets/entities/ticket.entity.ts +++ b/src/modules/tickets/entities/ticket.entity.ts @@ -29,7 +29,7 @@ export class Ticket extends BaseEntity { danakService: DanakService | null; @ManyToOne(() => User, (user) => user.tickets, { nullable: true }) - assignedTo: User; + assignedTo: User | null; @ManyToOne(() => User, (user) => user.tickets, { onDelete: "CASCADE", nullable: false }) user: User; diff --git a/src/modules/tickets/providers/tickets.service.ts b/src/modules/tickets/providers/tickets.service.ts index c223803..0117698 100755 --- a/src/modules/tickets/providers/tickets.service.ts +++ b/src/modules/tickets/providers/tickets.service.ts @@ -1,5 +1,5 @@ import { BadRequestException, HttpException, Injectable, InternalServerErrorException, Logger } from "@nestjs/common"; -import { DataSource, Not } from "typeorm"; +import { DataSource, Not, QueryRunner } from "typeorm"; import { ParamDto } from "../../../common/DTO/param.dto"; import { CommonMessage, TicketMessageEnum, UserMessage } from "../../../common/enums/message.enum"; @@ -117,33 +117,34 @@ export class TicketsService { const queryRunner = this.dataSource.createQueryRunner(); await queryRunner.connect(); await queryRunner.startTransaction(); - try { - let service; - const user = await this.usersService.findOneByIdWithQueryRunner(userId, queryRunner); - const ticketCategory = await queryRunner.manager.findOne(TicketCategory, { - where: { id: createDto.categoryId }, - relations: { group: { users: true } }, - }); + try { + const [user, ticketCategory] = await Promise.all([ + this.usersService.findOneByIdWithQueryRunner(userId, queryRunner), + queryRunner.manager.findOne(TicketCategory, { where: { id: createDto.categoryId }, relations: { group: { users: true } } }), + ]); + if (!ticketCategory) throw new BadRequestException(TicketMessageEnum.CATEGORY_NOT_FOUND); - const { attachmentUrls } = createDto; - - // + let service = null; if (createDto.danakServiceId) { service = await queryRunner.manager.findOneBy(DanakService, { id: createDto.danakServiceId }); if (!service) throw new BadRequestException(TicketMessageEnum.SERVICE_NOT_FOUND); } - const ticketMessage: Partial = { content: createDto.message, author: user }; + const ticketMessage: Partial = { + content: createDto.message, + author: user, + }; - if (attachmentUrls?.length) { - ticketMessage.attachments = attachmentUrls.map((url) => { + if (createDto.attachmentUrls?.length) { + ticketMessage.attachments = createDto.attachmentUrls.map((url) => { const attachment = new TicketMessageAttachment(); attachment.attachmentUrl = url; return attachment; }); } + const ticket = queryRunner.manager.create(Ticket, { ...createDto, user, @@ -154,36 +155,7 @@ export class TicketsService { await queryRunner.manager.save(Ticket, ticket); - const assignedUser = await this.autoAssignUser(ticketCategory); - //send notification to assignedUser - if (assignedUser) { - ticket.assignedTo = assignedUser; - await queryRunner.manager.save(Ticket, ticket); - await this.notificationsService.createAssignTicketNotificationForAdmin( - assignedUser.id, - { - ticketId: ticket.numericId.toString(), - subject: ticket.subject, - date: ticket.createdAt, - userPhone: user.phone, - userEmail: user.email, - user: `${user.firstName} ${user.lastName}`, - }, - queryRunner, - ); - } - - await this.notificationsService.createTicketNotification( - userId, - { - ticketId: ticket.numericId.toString(), - subject: ticket.subject, - date: ticket.createdAt, - userPhone: user.phone, - userEmail: user.email, - }, - queryRunner, - ); + await this.assignTicketAndNotify(ticket, ticketCategory, user, queryRunner); await queryRunner.commitTransaction(); @@ -205,40 +177,35 @@ export class TicketsService { async getTickets(queryDto: SearchTicketQueryDto, userId: string) { const { limit, skip } = PaginationUtils(queryDto); - const findOptions = { - where: { - user: { id: userId }, - ...(queryDto.status ? { status: queryDto.status } : { status: Not(TicketStatus.CLOSED) }), - }, - relations: { - category: true, - danakService: true, - }, - select: { - id: true, - subject: true, - status: true, - priority: true, - numericId: true, - createdAt: true, - updatedAt: true, - category: { - id: true, - title: true, - }, - danakService: { - id: true, - name: true, - }, - }, - order: { - createdAt: "DESC" as const, - }, - skip, - take: limit, - }; + const queryBuilder = this.ticketsRepository + .createQueryBuilder("ticket") + .leftJoinAndSelect("ticket.category", "category") + .leftJoinAndSelect("ticket.danakService", "danakService") + .where("ticket.user.id = :userId", { userId }) + .orderBy("ticket.createdAt", "DESC") + .select([ + "ticket.id", + "ticket.subject", + "ticket.status", + "ticket.priority", + "ticket.numericId", + "ticket.createdAt", + "ticket.updatedAt", + "category.id", + "category.title", + "danakService.id", + "danakService.name", + ]) + .skip(skip) + .take(limit); - const [tickets, count] = await this.ticketsRepository.findAndCount(findOptions); + if (queryDto.status) { + queryBuilder.andWhere("ticket.status = :status", { status: queryDto.status }); + } else { + queryBuilder.andWhere("ticket.status != :closedStatus", { closedStatus: TicketStatus.CLOSED }); + } + + const [tickets, count] = await queryBuilder.getManyAndCount(); return { tickets, count, paginate: true }; } @@ -281,51 +248,31 @@ export class TicketsService { await queryRunner.startTransaction(); try { - let ticket: null | Ticket = null; - - if (isAdmin) { - ticket = await queryRunner.manager.findOne(Ticket, { where: { id: ticketId }, relations: { user: true } }); - } else { - ticket = await queryRunner.manager.findOne(Ticket, { where: { id: ticketId, user: { id: userId } }, relations: { user: true } }); - } - - if (!ticket) throw new BadRequestException(TicketMessageEnum.TICKET_NOT_FOUND); - + const ticket = await this.findTicket(queryRunner, ticketId, userId, isAdmin); if (ticket.status === TicketStatus.CLOSED) throw new BadRequestException(TicketMessageEnum.TICKET_CLOSED); + const isSuperAdmin = await this.usersService.isSuperAdmin(userId); + if (!isSuperAdmin && ticket.assignedTo?.id !== userId) throw new BadRequestException(TicketMessageEnum.TICKET_NOT_ASSIGNED_TO_USER); + const user = await queryRunner.manager.findOneBy(User, { id: userId }); if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND); - const ticketMessage = queryRunner.manager.create(TicketMessage, { ...createDto, author: user, ticket }); - - if (isAdmin && ticket.status === TicketStatus.PENDING) { - ticket.status = TicketStatus.ANSWERED; - } else if (!isAdmin && ticket.status === TicketStatus.ANSWERED) { - ticket.status = TicketStatus.PENDING; - } - - await queryRunner.manager.save(Ticket, ticket); + const ticketMessage = queryRunner.manager.create(TicketMessage, { + content: createDto.content, + author: user, + ticket, + }); await queryRunner.manager.save(TicketMessage, ticketMessage); - if (createDto.attachmentUrls) { - const attachments = createDto.attachmentUrls.map((url) => - queryRunner.manager.create(TicketMessageAttachment, { attachmentUrl: url, ticketMessage }), - ); - await queryRunner.manager.save(TicketMessageAttachment, attachments); + if (createDto.attachmentUrls?.length) { + await this.saveAttachments(queryRunner, createDto.attachmentUrls, ticketMessage); } + this.updateTicketStatus(ticket, isAdmin); + await queryRunner.manager.save(Ticket, ticket); + if (isAdmin) { - await this.notificationsService.createAnswerTicketNotification( - ticket.user.id, - { - ticketId: ticket.numericId.toString(), - subject: ticket.subject, - date: ticket.createdAt, - userPhone: user.phone, - userEmail: user.email, - }, - queryRunner, - ); + await this.sendNotificationForAdminResponse(queryRunner, ticket, user); } await queryRunner.commitTransaction(); @@ -385,22 +332,13 @@ export class TicketsService { //******************************** */ async getUnreadTickets() { - const unreadTickets = await this.ticketsRepository.count({ - where: { - status: TicketStatus.PENDING, - }, - }); + const unreadTickets = await this.ticketsRepository.count({ where: { status: TicketStatus.PENDING } }); return unreadTickets; } //******************************** */ async countUserTickets(userId: string) { - const ticketCount = await this.ticketsRepository.count({ - where: { - user: { id: userId }, - status: Not(TicketStatus.CLOSED), - }, - }); + const ticketCount = await this.ticketsRepository.count({ where: { user: { id: userId }, status: Not(TicketStatus.CLOSED) } }); return ticketCount; } @@ -416,4 +354,84 @@ export class TicketsService { return users[0]; } + + //***************************** */ + private async assignTicketAndNotify(ticket: Ticket, ticketCategory: TicketCategory, user: User, queryRunner: QueryRunner) { + const assignedUser = await this.autoAssignUser(ticketCategory); + + if (assignedUser) { + ticket.assignedTo = assignedUser; + await queryRunner.manager.save(Ticket, ticket); + + await this.notificationsService.createAssignTicketNotificationForAdmin( + assignedUser.id, + { + ticketId: ticket.numericId.toString(), + subject: ticket.subject, + date: ticket.createdAt, + userPhone: user.phone, + userEmail: user.email, + user: `${user.firstName} ${user.lastName}`, + }, + queryRunner, + ); + } + + await this.notificationsService.createTicketNotification( + user.id, + { + ticketId: ticket.numericId.toString(), + subject: ticket.subject, + date: ticket.createdAt, + userPhone: user.phone, + userEmail: user.email, + }, + queryRunner, + ); + } + //***************************** */ + + private async findTicket(queryRunner: QueryRunner, ticketId: string, userId: string, isAdmin: boolean): Promise { + const ticket = isAdmin + ? await queryRunner.manager.findOne(Ticket, { where: { id: ticketId }, relations: { user: true } }) + : await queryRunner.manager.findOne(Ticket, { where: { id: ticketId, user: { id: userId } }, relations: { user: true } }); + + if (!ticket) throw new BadRequestException(TicketMessageEnum.TICKET_NOT_FOUND); + return ticket; + } + //***************************** */ + + private updateTicketStatus(ticket: Ticket, isAdmin: boolean): void { + if (isAdmin && ticket.status === TicketStatus.PENDING) { + ticket.status = TicketStatus.ANSWERED; + } else if (!isAdmin && ticket.status === TicketStatus.ANSWERED) { + ticket.status = TicketStatus.PENDING; + } + } + //***************************** */ + + private async saveAttachments(queryRunner: QueryRunner, attachmentUrls: string[], ticketMessage: TicketMessage): Promise { + const attachments = attachmentUrls.map((url) => + queryRunner.manager.create(TicketMessageAttachment, { + attachmentUrl: url, + ticketMessage, + }), + ); + await queryRunner.manager.save(TicketMessageAttachment, attachments); + } + //***************************** */ + + private async sendNotificationForAdminResponse(queryRunner: QueryRunner, ticket: Ticket, admin: User): Promise { + await this.notificationsService.createAnswerTicketNotification( + ticket.user.id, + { + ticketId: ticket.numericId.toString(), + subject: ticket.subject, + date: ticket.createdAt, + userPhone: admin.phone, + userEmail: admin.email, + }, + queryRunner, + ); + } }