import { BadRequestException, HttpException, Injectable, InternalServerErrorException } from "@nestjs/common"; import { DataSource, Not } from "typeorm"; import { ParamDto } from "../../../common/DTO/param.dto"; import { CommonMessage, TicketMessageEnum, UserMessage } from "../../../common/enums/message.enum"; import { User } from "../../users/entities/user.entity"; import { RoleEnum } from "../../users/enums/role.enum"; import { UsersService } from "../../users/providers/users.service"; import { PaginationUtils } from "../../utils/providers/pagination.utils"; import { CreateTicketCategoryDto } from "../DTO/create-ticket-category.dto"; import { CreateTicketMessageDto } from "../DTO/create-ticket-message.dto"; import { CreateTicketDto } from "../DTO/create-ticket.dto"; import { SearchTicketCategoryDto } from "../DTO/search-ticket-category.dto"; import { SearchTicketQueryDto } from "../DTO/search-ticket-query.dto"; import { UpdateTicketCategoryDto } from "../DTO/update-ticket-category.dto"; import { TicketMessageAttachment } from "../entities/ticket-message-attachment"; import { TicketMessage } from "../entities/ticket-message.entity"; import { Ticket } from "../entities/ticket.entity"; import { TicketStatus } from "../enums/ticket-status.enum"; import { TicketCategoryRepository } from "../repositories/tickets-category.repository"; import { TicketMessagesRepository } from "../repositories/tickets-message.repository"; import { TicketsRepository } from "../repositories/tickets.repository"; @Injectable() export class TicketsService { constructor( private readonly ticketsCategoryRepository: TicketCategoryRepository, private readonly ticketsRepository: TicketsRepository, private readonly ticketMessagesRepository: TicketMessagesRepository, private readonly usersService: UsersService, private dataSource: DataSource, ) {} //******************************** */ async getTicketCategories() { const ticketCategories = await this.ticketsCategoryRepository.find({}); return { ticketCategories, }; } //******************************** */ async getCategoriesList(queryDto: SearchTicketCategoryDto) { const { limit, skip } = PaginationUtils(queryDto); const queryBuilder = this.ticketsCategoryRepository .createQueryBuilder("category") // .leftJoinAndSelect("category.group", "group") // .addSelect(["group.id", "group.name"]) .leftJoin("category.group", "group") .addSelect(["group.id", "group.name"]) // .leftJoinAndSelect("group.users", "users") .loadRelationCountAndMap("category.ticketsCount", "category.tickets") .loadRelationCountAndMap("category.usersCount", "group.users"); if (queryDto.isActive !== undefined) { queryBuilder.andWhere("category.isActive = :isActive", { isActive: queryDto.isActive === 1 }); } if (queryDto.q) { queryBuilder.where("category.title LIKE :q", { q: `%${queryDto.q}%` }); } const [categories, count] = await queryBuilder.skip(skip).take(limit).getManyAndCount(); return { categories, count, paginate: true }; } //******************************** */ async createTicketCategory(createDto: CreateTicketCategoryDto) { const { userGroup } = await this.usersService.getUserGroupById(createDto.userGroupId); const existCategory = await this.ticketsCategoryRepository.findCategoryByTitle(createDto.title); if (existCategory) throw new BadRequestException(TicketMessageEnum.CATEGORY_EXIST); const ticketCategory = this.ticketsCategoryRepository.create({ ...createDto, group: userGroup }); await this.ticketsCategoryRepository.save(ticketCategory); return { message: TicketMessageEnum.CATEGORY_CREATED, }; } //******************************** */ async updateCategory(paramDto: ParamDto, updateCategoryDto: UpdateTicketCategoryDto) { const category = await this.ticketsCategoryRepository.findCategoryById(paramDto.id); if (!category) throw new BadRequestException(TicketMessageEnum.CATEGORY_NOT_FOUND); if (updateCategoryDto.title) { const { title } = updateCategoryDto; const existCategory = await this.ticketsCategoryRepository.findOneBy({ title, id: Not(paramDto.id) }); if (existCategory) throw new BadRequestException(TicketMessageEnum.CATEGORY_EXIST); } await this.ticketsCategoryRepository.save({ ...category, ...updateCategoryDto }); return { message: CommonMessage.UPDATE_SUCCESS, }; } //******************************** */ async toggleCategoryStatus(paramDto: ParamDto) { const category = await this.ticketsCategoryRepository.findCategoryById(paramDto.id); if (!category) throw new BadRequestException(TicketMessageEnum.CATEGORY_NOT_FOUND); category.isActive = !category.isActive; await this.ticketsCategoryRepository.save(category); return { message: CommonMessage.UPDATE_SUCCESS, isActive: category.isActive, }; } //******************************** */ async createTicket(createDto: CreateTicketDto, userId: string) { const { user } = await this.usersService.findOneById(userId); const ticketCategory = await this.ticketsCategoryRepository.findCategoryById(createDto.categoryId); if (!ticketCategory) throw new BadRequestException(TicketMessageEnum.CATEGORY_NOT_FOUND); const { attachmentUrls } = createDto; // const ticketMessage: Partial = { content: createDto.message, author: user }; if (attachmentUrls) { ticketMessage.attachments = attachmentUrls.map((url) => { const attachment = new TicketMessageAttachment(); attachment.attachmentUrl = url; return attachment; }); } const ticket = this.ticketsRepository.create({ ...createDto, user, category: ticketCategory, messages: [ticketMessage] }); await this.ticketsRepository.save(ticket); return { message: TicketMessageEnum.CREATED, ticketId: ticket.id, }; } //******************************** */ async getTickets(queryDto: SearchTicketQueryDto, userId: string) { const { limit, skip } = PaginationUtils(queryDto); const findOptions = { where: { user: { id: userId }, ...(queryDto.status && { status: queryDto.status }), }, relations: { category: true, // user: true, }, order: { createdAt: "DESC" as const, }, skip, take: limit, }; const [tickets, count] = await this.ticketsRepository.findAndCount(findOptions); return { tickets, count, paginate: true }; } //******************************** */ //TODO: get for admin with group of category async getAllTickets(queryDto: SearchTicketQueryDto) { const { limit, skip } = PaginationUtils(queryDto); const findOptions = { where: { ...(queryDto.status && { status: queryDto.status }), }, relations: ["category", "user"], order: { createdAt: "DESC" as const }, skip, take: limit, }; const [tickets, count] = await this.ticketsRepository.findAndCount(findOptions); return { tickets, count, paginate: true }; } //******************************** */ async createTicketMessage(ticketId: string, createDto: CreateTicketMessageDto, userId: string, roles: RoleEnum[]) { const queryRunner = this.dataSource.createQueryRunner(); await queryRunner.connect(); await queryRunner.startTransaction(); try { let ticket: null | Ticket = null; if (roles.includes(RoleEnum.ADMIN)) { ticket = await queryRunner.manager.findOneBy(Ticket, { id: ticketId }); } else { ticket = await queryRunner.manager.findOneBy(Ticket, { id: ticketId, user: { id: userId } }); } if (!ticket) throw new BadRequestException(TicketMessageEnum.TICKET_NOT_FOUND); if (ticket.status === TicketStatus.CLOSED) throw new BadRequestException(TicketMessageEnum.TICKET_CLOSED); 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 (roles.includes(RoleEnum.ADMIN) && ticket.status === TicketStatus.PENDING) { ticket.status = TicketStatus.ANSWERED; } await queryRunner.manager.save(Ticket, 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); } await queryRunner.commitTransaction(); return { message: TicketMessageEnum.MESSAGE_CREATED, ticketMessage, }; } catch (error) { await queryRunner.rollbackTransaction(); if (error instanceof HttpException) throw error; throw new InternalServerErrorException(TicketMessageEnum.ERROR_IN_TICKET_MSG_CREATION); } finally { await queryRunner.release(); } } //******************************** */ async getTicketMessages(ticketId: string, userId: string, roles: RoleEnum[]) { let ticket: null | Ticket = null; // if (roles.includes(RoleEnum.ADMIN)) { ticket = await this.ticketsRepository.findTicketById(ticketId); } else { ticket = await this.ticketsRepository.findTicketById(ticketId, userId); } if (!ticket) throw new BadRequestException(TicketMessageEnum.TICKET_NOT_FOUND); const messages = await this.ticketMessagesRepository.findMessagesByTicketId(ticketId); return { ticket, messages, }; } //******************************** */ async closeTicketByUser(ticketId: string, userId: string, roles: RoleEnum[]) { let ticket: null | Ticket = null; if (roles.includes(RoleEnum.ADMIN)) { ticket = await this.ticketsRepository.findTicketById(ticketId); } else { ticket = await this.ticketsRepository.findTicketById(ticketId, userId); } if (!ticket) throw new BadRequestException(TicketMessageEnum.TICKET_NOT_FOUND); ticket.status = TicketStatus.CLOSED; await this.ticketsRepository.save(ticket); return { message: TicketMessageEnum.CLOSED, }; } //******************************** */ } // async getTickets(queryDto: SearchTicketQueryDto, userId: string) { // const { limit, skip } = PaginationUtils(queryDto); // //TODO:add danak-service to query // const queryBuilder = this.ticketsRepository // .createQueryBuilder("ticket") // .leftJoinAndSelect("ticket.category", "category") // .leftJoinAndSelect("ticket.user", "user") // .where("user.id = :userId", { userId }) // .orderBy("ticket.createdAt", "DESC"); // if (queryDto.status) { // queryBuilder.andWhere("ticket.status = :status", { status: queryDto.status }); // } // const [tickets, count] = await queryBuilder.skip(skip).take(limit).getManyAndCount(); // return { // tickets, // count, // }; // }