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