ticket master
This commit is contained in:
@@ -330,6 +330,12 @@ export const enum TicketMessageEnum {
|
||||
INVALID_TICKET_LIMIT = "محدودیت تعداد تیکت در پلن پشتیبانی شما نامعتبر است",
|
||||
TICKET_LIMIT_EXCEEDED = "شما به محدودیت تعداد تیکت در پلن پشتیبانی خود رسیدهاید",
|
||||
OPENED = "تیکت با موفقیت باز شد",
|
||||
MASTER_USER_ID_REQUIRED = "شناسه کاربر مستر تیکت اجباری است",
|
||||
MASTER_USER_ID_SHOULD_BE_UUID = "شناسه کاربر مستر تیکت باید یک UUID معتبر باشد",
|
||||
MASTER_CREATED = "مستر تیکت با موفقیت ایجاد شد",
|
||||
MASTER_NOT_FOUND = "مستر تیکت یافت نشد",
|
||||
MASTER_EXIST = "این کاربر قبلاً به عنوان مستر تیکت ثبت شده است",
|
||||
MASTER_DELETED = "مستر تیکت با موفقیت حذف شد",
|
||||
}
|
||||
|
||||
export const enum WalletMessage {
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { IsNotEmpty, IsUUID } from "class-validator";
|
||||
|
||||
import { TicketMessageEnum } from "../../../common/enums/message.enum";
|
||||
|
||||
export class CreateTicketMasterDto {
|
||||
@IsNotEmpty({ message: TicketMessageEnum.MASTER_USER_ID_REQUIRED })
|
||||
@IsUUID("4", { message: TicketMessageEnum.MASTER_USER_ID_SHOULD_BE_UUID })
|
||||
@ApiProperty({ description: "User ID of the ticket master", example: "123e4567-e89b-12d3-a456-426614174000" })
|
||||
userId: string;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { IsNotEmpty, IsUUID } from "class-validator";
|
||||
|
||||
import { TicketMessageEnum } from "../../../common/enums/message.enum";
|
||||
|
||||
export class UpdateTicketMasterDto {
|
||||
@IsNotEmpty({ message: TicketMessageEnum.MASTER_USER_ID_REQUIRED })
|
||||
@IsUUID("4", { message: TicketMessageEnum.MASTER_USER_ID_SHOULD_BE_UUID })
|
||||
@ApiProperty({ description: "User ID of the ticket master", example: "123e4567-e89b-12d3-a456-426614174000" })
|
||||
userId: string;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Entity, JoinColumn, ManyToOne, Unique } from "typeorm";
|
||||
|
||||
import { BaseEntity } from "../../../common/entities/base.entity";
|
||||
import { User } from "../../users/entities/user.entity";
|
||||
|
||||
@Entity("ticket_master")
|
||||
@Unique(["user"])
|
||||
export class TicketMaster extends BaseEntity {
|
||||
@ManyToOne(() => User, { nullable: false, onDelete: "CASCADE" })
|
||||
@JoinColumn()
|
||||
user: User;
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import { User } from "../../users/entities/user.entity";
|
||||
import { AdminsService } from "../../users/providers/admins.service";
|
||||
import { UsersService } from "../../users/providers/users.service";
|
||||
import { CreateTicketCategoryDto } from "../DTO/create-ticket-category.dto";
|
||||
import { CreateTicketMasterDto } from "../DTO/create-ticket-master.dto";
|
||||
import { CreateTicketMessageDto } from "../DTO/create-ticket-message.dto";
|
||||
import { CreateTicketByAdminDto, CreateTicketDto } from "../DTO/create-ticket.dto";
|
||||
import { ReferTicketDto } from "../DTO/refer-ticket.dto";
|
||||
@@ -30,6 +31,7 @@ import { TicketMessageAttachment } from "../entities/ticket-message-attachment";
|
||||
import { TicketMessage } from "../entities/ticket-message.entity";
|
||||
import { Ticket } from "../entities/ticket.entity";
|
||||
import { TicketStatus } from "../enums/ticket-status.enum";
|
||||
import { TicketMasterRepository } from "../repositories/ticket-master.repository";
|
||||
import { TicketCategoryRepository } from "../repositories/tickets-category.repository";
|
||||
import { TicketMessagesRepository } from "../repositories/tickets-message.repository";
|
||||
import { TicketsRepository } from "../repositories/tickets.repository";
|
||||
@@ -43,6 +45,7 @@ export class TicketsService implements OnModuleInit {
|
||||
@InjectQueue(TICKET.QUEUE_NAME) private readonly ticketQueue: Queue,
|
||||
private readonly ticketsRepository: TicketsRepository,
|
||||
private readonly ticketsCategoryRepository: TicketCategoryRepository,
|
||||
private readonly ticketMasterRepository: TicketMasterRepository,
|
||||
private readonly ticketMessagesRepository: TicketMessagesRepository,
|
||||
private readonly usersService: UsersService,
|
||||
private readonly adminsService: AdminsService,
|
||||
@@ -167,6 +170,79 @@ export class TicketsService implements OnModuleInit {
|
||||
};
|
||||
}
|
||||
|
||||
//******************************** */
|
||||
// ticket masters
|
||||
//******************************** */
|
||||
|
||||
async createTicketMaster(createDto: CreateTicketMasterDto, userIpAndHeaders: IUserIpAndHeaders) {
|
||||
const { user } = await this.usersService.findOneById(createDto.userId);
|
||||
|
||||
const existingMaster = await this.ticketMasterRepository.findByUserId(createDto.userId);
|
||||
if (existingMaster) throw new BadRequestException(TicketMessageEnum.MASTER_EXIST);
|
||||
|
||||
const ticketMaster = this.ticketMasterRepository.create({ user });
|
||||
await this.ticketMasterRepository.save(ticketMaster);
|
||||
|
||||
await this.accessLogService.logCreate("TicketMaster", UserType.ADMIN, {
|
||||
user: { id: userIpAndHeaders.userId },
|
||||
endpoint: "/tickets/masters",
|
||||
requestId: userIpAndHeaders.headers["x-request-id"],
|
||||
ipAddress: userIpAndHeaders.ip,
|
||||
userAgent: userIpAndHeaders.headers["user-agent"],
|
||||
actionDescription: `Admin created ticket master for user: ${user.id}`,
|
||||
metadata: {
|
||||
ticketMasterId: ticketMaster.id,
|
||||
masterUserId: user.id,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
message: TicketMessageEnum.MASTER_CREATED,
|
||||
ticketMaster: await this.ticketMasterRepository.findByIdWithUser(ticketMaster.id),
|
||||
};
|
||||
}
|
||||
|
||||
//******************************** */
|
||||
|
||||
async getTicketMasters() {
|
||||
const ticketMasters = await this.ticketMasterRepository.findAllWithUser();
|
||||
return { ticketMasters };
|
||||
}
|
||||
|
||||
//******************************** */
|
||||
|
||||
async getTicketMaster(paramDto: ParamDto) {
|
||||
const ticketMaster = await this.ticketMasterRepository.findByIdWithUser(paramDto.id);
|
||||
if (!ticketMaster) throw new BadRequestException(TicketMessageEnum.MASTER_NOT_FOUND);
|
||||
return { ticketMaster };
|
||||
}
|
||||
|
||||
//******************************** */
|
||||
|
||||
async deleteTicketMaster(paramDto: ParamDto, userIpAndHeaders: IUserIpAndHeaders) {
|
||||
const ticketMaster = await this.ticketMasterRepository.findByIdWithUser(paramDto.id);
|
||||
if (!ticketMaster) throw new BadRequestException(TicketMessageEnum.MASTER_NOT_FOUND);
|
||||
|
||||
await this.ticketMasterRepository.delete(paramDto.id);
|
||||
|
||||
await this.accessLogService.logDelete("TicketMaster", paramDto.id, UserType.ADMIN, {
|
||||
user: { id: userIpAndHeaders.userId },
|
||||
endpoint: "/tickets/masters",
|
||||
requestId: userIpAndHeaders.headers["x-request-id"],
|
||||
ipAddress: userIpAndHeaders.ip,
|
||||
userAgent: userIpAndHeaders.headers["user-agent"],
|
||||
actionDescription: `Admin deleted ticket master: ${paramDto.id}`,
|
||||
metadata: {
|
||||
ticketMasterId: ticketMaster.id,
|
||||
masterUserId: ticketMaster.user.id,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
message: TicketMessageEnum.MASTER_DELETED,
|
||||
};
|
||||
}
|
||||
|
||||
//******************************** */
|
||||
|
||||
private async checkTicketLimit(userId: string, queryRunner: QueryRunner): Promise<void> {
|
||||
@@ -539,7 +615,7 @@ export class TicketsService implements OnModuleInit {
|
||||
|
||||
//***************************** */
|
||||
private async assignTicketAndNotify(ticket: Ticket, ticketCategory: TicketCategory, user: User, queryRunner: QueryRunner) {
|
||||
const assignedUser = await this.referralService.assignAdmin(ticketCategory.group);
|
||||
const assignedUser = (await this.assignFromTicketMasters()) ?? (await this.referralService.assignAdmin(ticketCategory.group));
|
||||
//
|
||||
ticket.assignedTo = assignedUser;
|
||||
await queryRunner.manager.save(Ticket, ticket);
|
||||
@@ -562,6 +638,22 @@ export class TicketsService implements OnModuleInit {
|
||||
});
|
||||
await this.notifySuperAdminsForTicket(queryRunner, ticket);
|
||||
}
|
||||
|
||||
//***************************** */
|
||||
|
||||
private async assignFromTicketMasters(): Promise<User | null> {
|
||||
const masters = await this.ticketMasterRepository.findAllWithUser();
|
||||
if (!masters.length) return null;
|
||||
|
||||
const mastersWithLoad = await Promise.all(
|
||||
masters.map(async (master) => ({
|
||||
user: master.user,
|
||||
assignedCount: await this.ticketsRepository.count({ where: { assignedTo: { id: master.user.id } } }),
|
||||
})),
|
||||
);
|
||||
|
||||
return mastersWithLoad.reduce((prev, curr) => (prev.assignedCount < curr.assignedCount ? prev : curr)).user;
|
||||
}
|
||||
//***************************** */
|
||||
|
||||
private async findTicket(queryRunner: QueryRunner, ticketId: string, userId: string, isAdmin: boolean): Promise<Ticket> {
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { InjectRepository } from "@nestjs/typeorm";
|
||||
import { Repository } from "typeorm";
|
||||
|
||||
import { TicketMaster } from "../entities/ticket-master.entity";
|
||||
|
||||
@Injectable()
|
||||
export class TicketMasterRepository extends Repository<TicketMaster> {
|
||||
constructor(@InjectRepository(TicketMaster) ticketMasterRepository: Repository<TicketMaster>) {
|
||||
super(ticketMasterRepository.target, ticketMasterRepository.manager, ticketMasterRepository.queryRunner);
|
||||
}
|
||||
|
||||
findAllWithUser(): Promise<TicketMaster[]> {
|
||||
return this.find({
|
||||
relations: { user: true },
|
||||
order: { createdAt: "ASC" },
|
||||
});
|
||||
}
|
||||
|
||||
findByIdWithUser(id: string): Promise<TicketMaster | null> {
|
||||
return this.findOne({
|
||||
where: { id },
|
||||
relations: { user: true },
|
||||
});
|
||||
}
|
||||
|
||||
findByUserId(userId: string): Promise<TicketMaster | null> {
|
||||
return this.findOne({
|
||||
where: { user: { id: userId } },
|
||||
relations: { user: true },
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Body, Controller, Get, HttpCode, HttpStatus, Param, Patch, Post, Query } from "@nestjs/common";
|
||||
import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Patch, Post, Query } from "@nestjs/common";
|
||||
import { ApiOperation, ApiTags } from "@nestjs/swagger";
|
||||
|
||||
import { CreateTicketCategoryDto } from "./DTO/create-ticket-category.dto";
|
||||
import { CreateTicketMasterDto } from "./DTO/create-ticket-master.dto";
|
||||
import { CreateTicketMessageDto } from "./DTO/create-ticket-message.dto";
|
||||
import { CreateTicketByAdminDto, CreateTicketDto } from "./DTO/create-ticket.dto";
|
||||
import { ReferTicketDto } from "./DTO/refer-ticket.dto";
|
||||
@@ -16,6 +17,7 @@ import { IUserIpAndHeaders, UserDec, UserIpAndHeaders } from "../../common/decor
|
||||
import { ParamDto } from "../../common/DTO/param.dto";
|
||||
import { PermissionEnum } from "../users/enums/permission.enum";
|
||||
import { ITokenPayload } from "../auth/interfaces/IToken-payload";
|
||||
|
||||
@Controller("tickets")
|
||||
@ApiTags("Tickets")
|
||||
@AuthGuards()
|
||||
@@ -65,6 +67,40 @@ export class TicketsController {
|
||||
return this.ticketsService.toggleCategoryStatus(paramDto, userIpAndHeaders);
|
||||
}
|
||||
|
||||
//----------------- ticket masters -------------------
|
||||
|
||||
@ApiOperation({ summary: "Create ticket master => admin route" })
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.TICKET_MASTERS_CREATE)
|
||||
@Post("masters")
|
||||
createTicketMaster(@Body() createDto: CreateTicketMasterDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) {
|
||||
return this.ticketsService.createTicketMaster(createDto, userIpAndHeaders);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "Get all ticket masters => admin route" })
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.TICKET_MASTERS_READ)
|
||||
@Get("masters")
|
||||
getTicketMasters() {
|
||||
return this.ticketsService.getTicketMasters();
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "Get ticket master by id => admin route" })
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.TICKET_MASTERS_READ)
|
||||
@Get("masters/:id")
|
||||
getTicketMaster(@Param() paramDto: ParamDto) {
|
||||
return this.ticketsService.getTicketMaster(paramDto);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "Delete ticket master => admin route" })
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.TICKET_MASTERS_DELETE)
|
||||
@Delete("masters/:id")
|
||||
deleteTicketMaster(@Param() paramDto: ParamDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) {
|
||||
return this.ticketsService.deleteTicketMaster(paramDto, userIpAndHeaders);
|
||||
}
|
||||
|
||||
//----------------- ticket and ticket messages -------------------
|
||||
|
||||
@ApiOperation({ summary: "create ticket ==> user route" })
|
||||
|
||||
@@ -4,10 +4,12 @@ import { TypeOrmModule } from "@nestjs/typeorm";
|
||||
|
||||
import { TICKET } from "./constant";
|
||||
import { TicketCategory } from "./entities/ticket-category.entity";
|
||||
import { TicketMaster } from "./entities/ticket-master.entity";
|
||||
import { TicketMessage } from "./entities/ticket-message.entity";
|
||||
import { Ticket } from "./entities/ticket.entity";
|
||||
import { ReferralStrategyFactory } from "./providers/referral-strategy.factory";
|
||||
import { TicketsService } from "./providers/tickets.service";
|
||||
import { TicketMasterRepository } from "./repositories/ticket-master.repository";
|
||||
import { TicketCategoryRepository } from "./repositories/tickets-category.repository";
|
||||
import { TicketMessagesRepository } from "./repositories/tickets-message.repository";
|
||||
import { TicketsRepository } from "./repositories/tickets.repository";
|
||||
@@ -27,7 +29,7 @@ import { LoggerModule } from "../logger/logger.module";
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([Ticket, TicketCategory, TicketMessageAttachment, TicketMessage]),
|
||||
TypeOrmModule.forFeature([Ticket, TicketCategory, TicketMaster, TicketMessageAttachment, TicketMessage]),
|
||||
AccessLogsModule,
|
||||
BullModule.registerQueue({ name: TICKET.QUEUE_NAME }),
|
||||
UsersModule,
|
||||
@@ -48,6 +50,7 @@ import { LoggerModule } from "../logger/logger.module";
|
||||
TicketsService,
|
||||
TicketsRepository,
|
||||
TicketCategoryRepository,
|
||||
TicketMasterRepository,
|
||||
TicketMessagesRepository,
|
||||
TicketMessageAttachmentsRepository,
|
||||
TicketProcessor,
|
||||
|
||||
@@ -57,6 +57,11 @@ export const permissionMeta: Record<PermissionEnum, PermissionMeta> = {
|
||||
[PermissionEnum.TICKET_CATEGORIES_UPDATE]: { title: "ویرایش دستهبندی تیکت", group: PermissionGroupEnum.SUPPORT },
|
||||
[PermissionEnum.TICKET_CATEGORIES_DELETE]: { title: "حذف دستهبندی تیکت", group: PermissionGroupEnum.SUPPORT },
|
||||
|
||||
[PermissionEnum.TICKET_MASTERS_CREATE]: { title: "ایجاد مستر تیکت", group: PermissionGroupEnum.SUPPORT },
|
||||
[PermissionEnum.TICKET_MASTERS_READ]: { title: "مشاهده مستر تیکت", group: PermissionGroupEnum.SUPPORT },
|
||||
[PermissionEnum.TICKET_MASTERS_UPDATE]: { title: "ویرایش مستر تیکت", group: PermissionGroupEnum.SUPPORT },
|
||||
[PermissionEnum.TICKET_MASTERS_DELETE]: { title: "حذف مستر تیکت", group: PermissionGroupEnum.SUPPORT },
|
||||
|
||||
[PermissionEnum.CRITICISMS_CREATE]: { title: "ایجاد انتقادات", group: PermissionGroupEnum.OTHER },
|
||||
[PermissionEnum.CRITICISMS_READ]: { title: "مشاهده انتقادات", group: PermissionGroupEnum.OTHER },
|
||||
[PermissionEnum.CRITICISMS_UPDATE]: { title: "ویرایش انتقادات", group: PermissionGroupEnum.OTHER },
|
||||
|
||||
@@ -59,6 +59,12 @@ export enum PermissionEnum {
|
||||
TICKET_CATEGORIES_UPDATE = "ticket_categories_update",
|
||||
TICKET_CATEGORIES_DELETE = "ticket_categories_delete",
|
||||
|
||||
// ticket_masters
|
||||
TICKET_MASTERS_CREATE = "ticket_masters_create",
|
||||
TICKET_MASTERS_READ = "ticket_masters_read",
|
||||
TICKET_MASTERS_UPDATE = "ticket_masters_update",
|
||||
TICKET_MASTERS_DELETE = "ticket_masters_delete",
|
||||
|
||||
// criticisms
|
||||
CRITICISMS_CREATE = "criticisms_create",
|
||||
CRITICISMS_READ = "criticisms_read",
|
||||
|
||||
@@ -277,7 +277,11 @@ export class AdminsService {
|
||||
paginate: true,
|
||||
};
|
||||
}
|
||||
/************************************************************ */
|
||||
|
||||
async getAllAdmins() {
|
||||
return this.userRepository.find({ where: { roles: { isAdmin: true } } });
|
||||
}
|
||||
/************************************************************ */
|
||||
|
||||
async getAdminById(id: string) {
|
||||
|
||||
@@ -171,12 +171,18 @@ export class UsersController {
|
||||
|
||||
@ApiOperation({ summary: "get all admins ==> admin route" })
|
||||
@AdminRoute()
|
||||
// @PermissionsDec(PermissionEnum.ADMINS_READ, PermissionEnum.CUSTOMERS_READ)
|
||||
@Get("admins")
|
||||
getAdmins(@Query() queryDto: SearchAdminQueryDto) {
|
||||
return this.adminsService.getAdmins(queryDto);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "get all admins without pagination==> admin route" })
|
||||
@AdminRoute()
|
||||
@Get("admins-list/all")
|
||||
getAllAdmins() {
|
||||
return this.adminsService.getAllAdmins();
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "get admin by id ==> admin route" })
|
||||
@PermissionsDec(PermissionEnum.ADMINS_READ, PermissionEnum.CUSTOMERS_READ)
|
||||
@Get("admins/:id")
|
||||
|
||||
Reference in New Issue
Block a user