425 lines
15 KiB
TypeScript
Executable File
425 lines
15 KiB
TypeScript
Executable File
import { TicketRepository } from "../repositories/tickets.repository";
|
|
import { BadRequestException, Injectable } from "@nestjs/common";
|
|
import { CreateTicketDto } from "../DTO/create-ticket.dto";
|
|
import { OrderRepository } from "src/modules/order/repositories/order.repository";
|
|
import { User } from "src/modules/user/entities/user.entity";
|
|
import { UserRepository } from "src/modules/user/repositories/user.repository";
|
|
import { AdminRepository } from "src/modules/admin/repositories/admin.repository";
|
|
import { Admin } from "src/modules/admin/entities/admin.entity";
|
|
import { FindTicketQueryDto } from "../DTO/search-ticket-query.dto";
|
|
import { EntityManager } from "@mikro-orm/postgresql";
|
|
import { UpdateTicketDto } from "../DTO/update-ticket.dto";
|
|
|
|
|
|
@Injectable()
|
|
export class TicketService {
|
|
|
|
constructor(
|
|
private readonly ticketsRepository: TicketRepository,
|
|
private readonly orderRepository: OrderRepository,
|
|
private readonly userRepository: UserRepository,
|
|
private readonly adminRepository: AdminRepository,
|
|
private readonly em: EntityManager,
|
|
) { }
|
|
|
|
async createTicket(orderId: string, dto: CreateTicketDto, userId?: string, adminId?: string) {
|
|
const { content, attachments } = dto
|
|
if (!userId && !adminId) {
|
|
throw new BadRequestException("One of userId ord adminId is required")
|
|
}
|
|
const order = await this.orderRepository.findOne({ id: orderId })
|
|
if (!order) {
|
|
throw new BadRequestException("Order not found")
|
|
}
|
|
|
|
let user: null | User = null
|
|
let admin: null | Admin = null
|
|
|
|
if (userId) {
|
|
user = await this.userRepository.findOne({ id: userId })
|
|
if (!user) {
|
|
throw new BadRequestException("User not found")
|
|
}
|
|
}
|
|
if (adminId) {
|
|
admin = await this.adminRepository.findOne({ id: adminId })
|
|
if (!admin) {
|
|
throw new BadRequestException("Admin not found")
|
|
}
|
|
}
|
|
|
|
if (!user && !admin) {
|
|
throw new BadRequestException("One of user or admin is required")
|
|
}
|
|
|
|
const ticket = this.ticketsRepository.create({
|
|
content,
|
|
attachments,
|
|
admin,
|
|
user,
|
|
order,
|
|
})
|
|
|
|
await this.ticketsRepository.em.persistAndFlush(ticket)
|
|
|
|
return ticket
|
|
|
|
}
|
|
|
|
// //******************************** */
|
|
|
|
async getTickets(orderId: string, queryDto: FindTicketQueryDto, userId?: string) {
|
|
|
|
const order = await this.orderRepository.findOne({ id: orderId })
|
|
if (!order) {
|
|
throw new BadRequestException("order not found!")
|
|
}
|
|
if (userId) {
|
|
if (order.user.id !== userId) {
|
|
throw new BadRequestException("This ticket doenst belongs to You !")
|
|
}
|
|
}
|
|
const data = await this.ticketsRepository.findAllPaginated({ ...queryDto, orderId });
|
|
|
|
return data
|
|
}
|
|
|
|
//******************************** */
|
|
|
|
async deleteTicket(ticketId: string, userId?: string) {
|
|
const ticket = await this.ticketsRepository.findOne({ id: ticketId }, { populate: ['order', 'order.user'] })
|
|
if (!ticket) {
|
|
throw new BadRequestException('ticket not found!')
|
|
}
|
|
if (userId && ticket.order.user.id !== userId) {
|
|
throw new BadRequestException("You can not Delete this Ticket")
|
|
}
|
|
|
|
await this.em.removeAndFlush(ticket)
|
|
|
|
return true
|
|
}
|
|
|
|
// //******************************** */
|
|
|
|
async updateTicket(ticketId: string, dto: UpdateTicketDto, userId?: string) {
|
|
const ticket = await this.ticketsRepository.findOne({ id: ticketId }, { populate: ['order', 'order.user'] })
|
|
if (!ticket) {
|
|
throw new BadRequestException('ticket not found!')
|
|
}
|
|
if (userId && ticket.order.user.id !== userId) {
|
|
throw new BadRequestException("You can not Update this Ticket")
|
|
}
|
|
this.ticketsRepository.assign(ticket, dto, {
|
|
onlyProperties: true,
|
|
})
|
|
|
|
await this.em.flush()
|
|
|
|
return true
|
|
}
|
|
|
|
// //******************************** */
|
|
|
|
// async createTicketMessage(ticketId: string, createDto: CreateTicketMessageDto, userId: string, isAdmin: boolean) {
|
|
// const queryRunner = this.dataSource.createQueryRunner();
|
|
// await queryRunner.connect();
|
|
// await queryRunner.startTransaction();
|
|
|
|
// try {
|
|
// 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 (isAdmin && !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, {
|
|
// content: createDto.content,
|
|
// author: user,
|
|
// ticket,
|
|
// });
|
|
// await queryRunner.manager.save(TicketMessage, ticketMessage);
|
|
|
|
// 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.sendNotificationForAdminResponse(ticket, user);
|
|
// }
|
|
|
|
// 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, isAdmin: boolean) {
|
|
// let ticket: null | Ticket = null;
|
|
// //
|
|
// if (isAdmin) {
|
|
// 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);
|
|
// const userSupportPlan = await this.supportPlansService.getUserSupportPlanOfUser(ticket.user.id);
|
|
// return {
|
|
// ticket,
|
|
// messages,
|
|
// supportPlan: userSupportPlan,
|
|
// };
|
|
// }
|
|
|
|
// //******************************** */
|
|
|
|
// async closeTicketByUser(ticketId: string, isAdmin: boolean, userIpAndHeaders: IUserIpAndHeaders) {
|
|
// let ticket: null | Ticket = null;
|
|
|
|
// if (isAdmin) {
|
|
// ticket = await this.ticketsRepository.findTicketById(ticketId);
|
|
// } else {
|
|
// ticket = await this.ticketsRepository.findTicketById(ticketId, userIpAndHeaders.userId);
|
|
// }
|
|
|
|
// if (!ticket) throw new BadRequestException(TicketMessageEnum.TICKET_NOT_FOUND);
|
|
|
|
// ticket.status = TicketStatus.CLOSED;
|
|
// await this.ticketsRepository.save(ticket);
|
|
|
|
// await this.accessLogService.logUpdate("Ticket", ticketId, UserType.ADMIN, {
|
|
// user: { id: userIpAndHeaders.userId },
|
|
// endpoint: "/tickets",
|
|
// requestId: userIpAndHeaders.headers["x-request-id"],
|
|
// ipAddress: userIpAndHeaders.ip,
|
|
// userAgent: userIpAndHeaders.headers["user-agent"],
|
|
// actionDescription: `Admin closed ticket: ${ticket.subject}`,
|
|
// metadata: {
|
|
// ticketId: ticket.id,
|
|
// ticketSubject: ticket.subject,
|
|
// },
|
|
// });
|
|
|
|
// return {
|
|
// message: TicketMessageEnum.CLOSED,
|
|
// };
|
|
// }
|
|
// //******************************** * /
|
|
// async openTicketByAdmin(ticketId: string, userIpAndHeaders: IUserIpAndHeaders) {
|
|
// const ticket = await this.ticketsRepository.findTicketById(ticketId);
|
|
// if (!ticket) throw new BadRequestException(TicketMessageEnum.TICKET_NOT_FOUND);
|
|
|
|
// ticket.status = TicketStatus.PENDING;
|
|
// await this.ticketsRepository.save(ticket);
|
|
|
|
// await this.accessLogService.logUpdate("Ticket", ticketId, UserType.ADMIN, {
|
|
// user: { id: userIpAndHeaders.userId },
|
|
// endpoint: "/tickets",
|
|
// requestId: userIpAndHeaders.headers["x-request-id"],
|
|
// ipAddress: userIpAndHeaders.ip,
|
|
// userAgent: userIpAndHeaders.headers["user-agent"],
|
|
// actionDescription: `Admin opened ticket: ${ticket.subject}`,
|
|
// metadata: {
|
|
// ticketId: ticket.id,
|
|
// ticketSubject: ticket.subject,
|
|
// },
|
|
// });
|
|
|
|
// return {
|
|
// message: TicketMessageEnum.OPENED,
|
|
// };
|
|
// }
|
|
|
|
// //******************************** */
|
|
// async referTicket(ticketId: string, referDto: ReferTicketDto, userIpAndHeaders: IUserIpAndHeaders) {
|
|
// const queryRunner = this.dataSource.createQueryRunner();
|
|
|
|
// try {
|
|
// await queryRunner.connect();
|
|
// await queryRunner.startTransaction();
|
|
|
|
// const isSuperAdmin = await this.usersService.isSuperAdmin(userIpAndHeaders.userId);
|
|
// // if (!isSuperAdmin) throw new BadRequestException(TicketMessageEnum.TICKET_NOT_ASSIGNED_TO_USER);
|
|
// console.log("isSuperAdmin", isSuperAdmin);
|
|
|
|
// const ticket = await this.ticketsRepository.findTicketById(ticketId);
|
|
// if (!ticket) throw new BadRequestException(TicketMessageEnum.TICKET_NOT_FOUND);
|
|
|
|
// const admin = await this.usersService.findOneByIdWithQueryRunner(referDto.adminId, queryRunner);
|
|
|
|
// ticket.assignedTo = admin;
|
|
// await queryRunner.manager.save(this.ticketsRepository.target, ticket);
|
|
|
|
// await this.notificationQueue.addAssignTicketNotificationForAdmin(admin.id, {
|
|
// ticketId: ticket.numericId.toString(),
|
|
// subject: ticket.subject,
|
|
// date: ticket.createdAt,
|
|
// userPhone: admin.phone,
|
|
// userEmail: admin.email,
|
|
// user: `${ticket.user.firstName} ${ticket.user.lastName}`,
|
|
// });
|
|
|
|
// await queryRunner.commitTransaction();
|
|
|
|
// await this.accessLogService.logUpdate("Ticket", ticketId, UserType.ADMIN, {
|
|
// user: { id: userIpAndHeaders.userId },
|
|
// endpoint: "/tickets",
|
|
// requestId: userIpAndHeaders.headers["x-request-id"],
|
|
// ipAddress: userIpAndHeaders.ip,
|
|
// userAgent: userIpAndHeaders.headers["user-agent"],
|
|
// actionDescription: `Admin referred ticket: ${ticket.subject}`,
|
|
// metadata: {
|
|
// ticketId: ticket.id,
|
|
// ticketSubject: ticket.subject,
|
|
// },
|
|
// });
|
|
// return {
|
|
// message: TicketMessageEnum.REFERRED,
|
|
// ticket,
|
|
// };
|
|
// } catch (error) {
|
|
// await queryRunner.rollbackTransaction();
|
|
// throw error;
|
|
// } finally {
|
|
// await queryRunner.release();
|
|
// }
|
|
// }
|
|
|
|
// //******************************** */
|
|
|
|
// async getUnreadTickets() {
|
|
// 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) },
|
|
// });
|
|
// return ticketCount;
|
|
// }
|
|
|
|
// //***************************** */
|
|
// private async assignTicketAndNotify(ticket: Ticket, ticketCategory: TicketCategory, user: User, queryRunner: QueryRunner) {
|
|
// const assignedUser = await this.referralService.assignAdmin(ticketCategory.group);
|
|
// //
|
|
// ticket.assignedTo = assignedUser;
|
|
// await queryRunner.manager.save(Ticket, ticket);
|
|
|
|
// await this.notificationQueue.addAssignTicketNotificationForAdmin(assignedUser.id, {
|
|
// ticketId: ticket.numericId.toString(),
|
|
// subject: ticket.subject,
|
|
// date: ticket.createdAt,
|
|
// userPhone: user.phone,
|
|
// userEmail: user.email,
|
|
// user: `${user.firstName} ${user.lastName}`,
|
|
// });
|
|
|
|
// await this.notificationQueue.addTicketNotification(user.id, {
|
|
// ticketId: ticket.numericId.toString(),
|
|
// subject: ticket.subject,
|
|
// date: ticket.createdAt,
|
|
// userPhone: user.phone,
|
|
// userEmail: user.email,
|
|
// });
|
|
// await this.notifySuperAdminsForTicket(queryRunner, ticket);
|
|
// }
|
|
// //***************************** */
|
|
|
|
// private async findTicket(queryRunner: QueryRunner, ticketId: string, userId: string, isAdmin: boolean): Promise<Ticket> {
|
|
// const ticket = isAdmin
|
|
// ? await queryRunner.manager.findOne(Ticket, {
|
|
// where: { id: ticketId },
|
|
// relations: { user: true, assignedTo: true },
|
|
// })
|
|
// : await queryRunner.manager.findOne(Ticket, {
|
|
// where: { id: ticketId, user: { id: userId } },
|
|
// relations: { user: true, assignedTo: 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) {
|
|
// const attachments = attachmentUrls.map((url) =>
|
|
// queryRunner.manager.create(TicketMessageAttachment, {
|
|
// attachmentUrl: url,
|
|
// ticketMessage,
|
|
// }),
|
|
// );
|
|
// await queryRunner.manager.save(TicketMessageAttachment, attachments);
|
|
// }
|
|
// //***************************** */
|
|
|
|
// private async sendNotificationForAdminResponse(ticket: Ticket, admin: User) {
|
|
// await this.notificationQueue.addAnswerTicketNotification(ticket.user.id, {
|
|
// ticketId: ticket.numericId.toString(),
|
|
// subject: ticket.subject,
|
|
// date: ticket.createdAt,
|
|
// userPhone: admin.phone,
|
|
// userEmail: admin.email,
|
|
// });
|
|
// }
|
|
// //***************************** */
|
|
|
|
// private async notifySuperAdminsForTicket(queryRunner: QueryRunner, ticket: Ticket) {
|
|
// const superAdmins = await this.adminsService.getSuperAdmins(queryRunner);
|
|
|
|
// for (const admin of superAdmins) {
|
|
// await this.notificationQueue.addNewTicketGlobalNotification(admin.id, {
|
|
// ticketId: ticket.numericId.toString(),
|
|
// ticketSubject: ticket.subject,
|
|
// userPhone: admin.phone,
|
|
// userEmail: admin.email,
|
|
// fullName: `${ticket.user.firstName} ${ticket.user.lastName}`,
|
|
// });
|
|
// }
|
|
// }
|
|
// //***************************** */
|
|
|
|
// async scheduleAutoCloseJob() {
|
|
// await this.ticketQueue.add(
|
|
// TICKET.AUTO_CLOSE_JOB_NAME,
|
|
// {},
|
|
// {
|
|
// repeat: { pattern: TICKET.AUTO_CLOSE_REPEAT_PATTERN },
|
|
// priority: TICKET.AUTO_CLOSE_PRIORITY,
|
|
// },
|
|
// );
|
|
// this.logger.log("Scheduled auto-close-inactive job for tickets");
|
|
// }
|
|
}
|