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 = "تیکت قبلا بسته شده است",
ERROR_IN_TICKET_MSG_CREATION = "خطا در ایجاد پیام تیکت",
SERVICE_NOT_FOUND = "سرویس مورد نظر یافت نشد",
TICKET_NOT_ASSIGNED = "تیکت به کارشناسی اختصاص داده نشده است",
TICKET_NOT_ASSIGNED_TO_USER = "تیکت به شما اختصاص داده نشده است",
}
export const enum WalletMessage {
@@ -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;
+141 -123
View File
@@ -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<TicketMessage> = { content: createDto.message, author: user };
const ticketMessage: Partial<TicketMessage> = {
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<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,
);
}
}