refactor: ticket service mehtods
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user