refactor: ticket service mehtods

This commit is contained in:
mahyargdz
2025-03-03 12:20:30 +03:30
parent 1f08514deb
commit 3278e07d95
3 changed files with 144 additions and 124 deletions
+2
View File
@@ -245,6 +245,8 @@ export const enum TicketMessageEnum {
TICKET_CLOSED = "تیکت قبلا بسته شده است", TICKET_CLOSED = "تیکت قبلا بسته شده است",
ERROR_IN_TICKET_MSG_CREATION = "خطا در ایجاد پیام تیکت", ERROR_IN_TICKET_MSG_CREATION = "خطا در ایجاد پیام تیکت",
SERVICE_NOT_FOUND = "سرویس مورد نظر یافت نشد", SERVICE_NOT_FOUND = "سرویس مورد نظر یافت نشد",
TICKET_NOT_ASSIGNED = "تیکت به کارشناسی اختصاص داده نشده است",
TICKET_NOT_ASSIGNED_TO_USER = "تیکت به شما اختصاص داده نشده است",
} }
export const enum WalletMessage { export const enum WalletMessage {
@@ -29,7 +29,7 @@ export class Ticket extends BaseEntity {
danakService: DanakService | null; danakService: DanakService | null;
@ManyToOne(() => User, (user) => user.tickets, { nullable: true }) @ManyToOne(() => User, (user) => user.tickets, { nullable: true })
assignedTo: User; assignedTo: User | null;
@ManyToOne(() => User, (user) => user.tickets, { onDelete: "CASCADE", nullable: false }) @ManyToOne(() => User, (user) => user.tickets, { onDelete: "CASCADE", nullable: false })
user: User; user: User;
+141 -123
View File
@@ -1,5 +1,5 @@
import { BadRequestException, HttpException, Injectable, InternalServerErrorException, Logger } from "@nestjs/common"; 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 { ParamDto } from "../../../common/DTO/param.dto";
import { CommonMessage, TicketMessageEnum, UserMessage } from "../../../common/enums/message.enum"; import { CommonMessage, TicketMessageEnum, UserMessage } from "../../../common/enums/message.enum";
@@ -117,33 +117,34 @@ export class TicketsService {
const queryRunner = this.dataSource.createQueryRunner(); const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect(); await queryRunner.connect();
await queryRunner.startTransaction(); await queryRunner.startTransaction();
try {
let service;
const user = await this.usersService.findOneByIdWithQueryRunner(userId, queryRunner);
const ticketCategory = await queryRunner.manager.findOne(TicketCategory, { try {
where: { id: createDto.categoryId }, const [user, ticketCategory] = await Promise.all([
relations: { group: { users: true } }, 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); if (!ticketCategory) throw new BadRequestException(TicketMessageEnum.CATEGORY_NOT_FOUND);
const { attachmentUrls } = createDto; let service = null;
//
if (createDto.danakServiceId) { if (createDto.danakServiceId) {
service = await queryRunner.manager.findOneBy(DanakService, { id: createDto.danakServiceId }); service = await queryRunner.manager.findOneBy(DanakService, { id: createDto.danakServiceId });
if (!service) throw new BadRequestException(TicketMessageEnum.SERVICE_NOT_FOUND); if (!service) throw new BadRequestException(TicketMessageEnum.SERVICE_NOT_FOUND);
} }
const ticketMessage: Partial<TicketMessage> = { content: createDto.message, author: user }; const ticketMessage: Partial<TicketMessage> = {
content: createDto.message,
author: user,
};
if (attachmentUrls?.length) { if (createDto.attachmentUrls?.length) {
ticketMessage.attachments = attachmentUrls.map((url) => { ticketMessage.attachments = createDto.attachmentUrls.map((url) => {
const attachment = new TicketMessageAttachment(); const attachment = new TicketMessageAttachment();
attachment.attachmentUrl = url; attachment.attachmentUrl = url;
return attachment; return attachment;
}); });
} }
const ticket = queryRunner.manager.create(Ticket, { const ticket = queryRunner.manager.create(Ticket, {
...createDto, ...createDto,
user, user,
@@ -154,36 +155,7 @@ export class TicketsService {
await queryRunner.manager.save(Ticket, ticket); await queryRunner.manager.save(Ticket, ticket);
const assignedUser = await this.autoAssignUser(ticketCategory); await this.assignTicketAndNotify(ticket, ticketCategory, user, queryRunner);
//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 queryRunner.commitTransaction(); await queryRunner.commitTransaction();
@@ -205,40 +177,35 @@ export class TicketsService {
async getTickets(queryDto: SearchTicketQueryDto, userId: string) { async getTickets(queryDto: SearchTicketQueryDto, userId: string) {
const { limit, skip } = PaginationUtils(queryDto); const { limit, skip } = PaginationUtils(queryDto);
const findOptions = { const queryBuilder = this.ticketsRepository
where: { .createQueryBuilder("ticket")
user: { id: userId }, .leftJoinAndSelect("ticket.category", "category")
...(queryDto.status ? { status: queryDto.status } : { status: Not(TicketStatus.CLOSED) }), .leftJoinAndSelect("ticket.danakService", "danakService")
}, .where("ticket.user.id = :userId", { userId })
relations: { .orderBy("ticket.createdAt", "DESC")
category: true, .select([
danakService: true, "ticket.id",
}, "ticket.subject",
select: { "ticket.status",
id: true, "ticket.priority",
subject: true, "ticket.numericId",
status: true, "ticket.createdAt",
priority: true, "ticket.updatedAt",
numericId: true, "category.id",
createdAt: true, "category.title",
updatedAt: true, "danakService.id",
category: { "danakService.name",
id: true, ])
title: true, .skip(skip)
}, .take(limit);
danakService: {
id: true,
name: true,
},
},
order: {
createdAt: "DESC" as const,
},
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 }; return { tickets, count, paginate: true };
} }
@@ -281,51 +248,31 @@ export class TicketsService {
await queryRunner.startTransaction(); await queryRunner.startTransaction();
try { try {
let ticket: null | Ticket = null; const ticket = await this.findTicket(queryRunner, ticketId, userId, isAdmin);
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);
if (ticket.status === TicketStatus.CLOSED) throw new BadRequestException(TicketMessageEnum.TICKET_CLOSED); 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 }); const user = await queryRunner.manager.findOneBy(User, { id: userId });
if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND); if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND);
const ticketMessage = queryRunner.manager.create(TicketMessage, { ...createDto, author: user, ticket }); const ticketMessage = queryRunner.manager.create(TicketMessage, {
content: createDto.content,
if (isAdmin && ticket.status === TicketStatus.PENDING) { author: user,
ticket.status = TicketStatus.ANSWERED; ticket,
} else if (!isAdmin && ticket.status === TicketStatus.ANSWERED) { });
ticket.status = TicketStatus.PENDING;
}
await queryRunner.manager.save(Ticket, ticket);
await queryRunner.manager.save(TicketMessage, ticketMessage); await queryRunner.manager.save(TicketMessage, ticketMessage);
if (createDto.attachmentUrls) { if (createDto.attachmentUrls?.length) {
const attachments = createDto.attachmentUrls.map((url) => await this.saveAttachments(queryRunner, createDto.attachmentUrls, ticketMessage);
queryRunner.manager.create(TicketMessageAttachment, { attachmentUrl: url, ticketMessage }),
);
await queryRunner.manager.save(TicketMessageAttachment, attachments);
} }
this.updateTicketStatus(ticket, isAdmin);
await queryRunner.manager.save(Ticket, ticket);
if (isAdmin) { if (isAdmin) {
await this.notificationsService.createAnswerTicketNotification( await this.sendNotificationForAdminResponse(queryRunner, ticket, user);
ticket.user.id,
{
ticketId: ticket.numericId.toString(),
subject: ticket.subject,
date: ticket.createdAt,
userPhone: user.phone,
userEmail: user.email,
},
queryRunner,
);
} }
await queryRunner.commitTransaction(); await queryRunner.commitTransaction();
@@ -385,22 +332,13 @@ export class TicketsService {
//******************************** */ //******************************** */
async getUnreadTickets() { async getUnreadTickets() {
const unreadTickets = await this.ticketsRepository.count({ const unreadTickets = await this.ticketsRepository.count({ where: { status: TicketStatus.PENDING } });
where: {
status: TicketStatus.PENDING,
},
});
return unreadTickets; return unreadTickets;
} }
//******************************** */ //******************************** */
async countUserTickets(userId: string) { async countUserTickets(userId: string) {
const ticketCount = await this.ticketsRepository.count({ const ticketCount = await this.ticketsRepository.count({ where: { user: { id: userId }, status: Not(TicketStatus.CLOSED) } });
where: {
user: { id: userId },
status: Not(TicketStatus.CLOSED),
},
});
return ticketCount; return ticketCount;
} }
@@ -416,4 +354,84 @@ export class TicketsService {
return users[0]; 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<Ticket> {
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<void> {
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<void> {
await this.notificationsService.createAnswerTicketNotification(
ticket.user.id,
{
ticketId: ticket.numericId.toString(),
subject: ticket.subject,
date: ticket.createdAt,
userPhone: admin.phone,
userEmail: admin.email,
},
queryRunner,
);
}
} }