From 03d8c431fae3a673f7bb84b2580caadbfd0cdb61 Mon Sep 17 00:00:00 2001 From: mahyargdz Date: Mon, 21 Apr 2025 16:17:48 +0330 Subject: [PATCH] chore: add manaual refer for ticket of super admin --- src/app.module.ts | 6 +- src/common/enums/message.enum.ts | 4 ++ src/configs/typeorm.config.ts | 2 +- .../invoices/entities/invoice.entity.ts | 4 +- .../invoices/providers/invoices.service.ts | 8 +-- src/modules/tickets/DTO/refer-ticket.dto.ts | 11 ++++ .../tickets/providers/tickets.service.ts | 55 ++++++++++++++++--- src/modules/tickets/tickets.controller.ts | 30 +++++----- 8 files changed, 86 insertions(+), 34 deletions(-) create mode 100644 src/modules/tickets/DTO/refer-ticket.dto.ts diff --git a/src/app.module.ts b/src/app.module.ts index 69c859e..1281a70 100755 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -7,13 +7,13 @@ import { ConfigModule } from "@nestjs/config"; import { ThrottlerModule } from "@nestjs/throttler"; import { TypeOrmModule } from "@nestjs/typeorm"; import { MailerModule } from "@nestjs-modules/mailer"; -import { TelegrafModule } from "nestjs-telegraf"; +// import { TelegrafModule } from "nestjs-telegraf"; import { bullMqConfig } from "./configs/bullmq.config"; import { cacheConfig } from "./configs/cache.config"; import { mailerConfig } from "./configs/mailer.config"; import { rateLimitConfig } from "./configs/rateLimit.config"; -import { telegrafConfig } from "./configs/telegraf.config"; +// import { telegrafConfig } from "./configs/telegraf.config"; import { databaseConfigs } from "./configs/typeorm.config"; import { HTTPLogger } from "./core/middlewares/logger.middleware"; import { AddressModule } from "./modules/address/address.module"; @@ -42,7 +42,7 @@ import { WalletsModule } from "./modules/wallets/wallets.module"; @Module({ imports: [ - TelegrafModule.forRootAsync(telegrafConfig()), + // TelegrafModule.forRootAsync(telegrafConfig()), MailerModule.forRootAsync(mailerConfig()), BullModule.forRootAsync(bullMqConfig()), ThrottlerModule.forRootAsync(rateLimitConfig()), diff --git a/src/common/enums/message.enum.ts b/src/common/enums/message.enum.ts index 1f03f70..2123227 100755 --- a/src/common/enums/message.enum.ts +++ b/src/common/enums/message.enum.ts @@ -102,6 +102,9 @@ export const enum UserMessage { REAL_DATA_NOT_FOUND = "اطلاعات حقیقی یافت نشد", REGISTRATION_NAME_EXIST = "نام ثبت قبلا ثبت شده است", ADDRESS_NOT_FOUND = "آدرس یافت نشد", + ADMIN_ID_REQUIRED = "شناسه ادمین مورد نیاز است", + INVALID_ADMIN_ID = "شناسه ادمین نامعتبر است", + ADMIN_ID_SHOULD_BE_UUID = "شناسه ادمین باید یک UUID باشد", } export const enum CommonMessage { @@ -273,6 +276,7 @@ export const enum TicketMessageEnum { SERVICE_NOT_FOUND = "سرویس مورد نظر یافت نشد", TICKET_NOT_ASSIGNED = "تیکت به کارشناسی اختصاص داده نشده است", TICKET_NOT_ASSIGNED_TO_USER = "تیکت به شما اختصاص داده نشده است", + REFERRED = "REFERRED", } export const enum WalletMessage { diff --git a/src/configs/typeorm.config.ts b/src/configs/typeorm.config.ts index 8d8378f..9391ab2 100755 --- a/src/configs/typeorm.config.ts +++ b/src/configs/typeorm.config.ts @@ -13,7 +13,7 @@ export function databaseConfigs(): TypeOrmModuleAsyncOptions { username: configService.getOrThrow("DB_USER"), password: configService.getOrThrow("DB_PASS"), autoLoadEntities: true, - synchronize: configService.getOrThrow("NODE_ENV") == "production" ? false : true, + synchronize: configService.getOrThrow("NODE_ENV") == "production" ? false : false, logging: configService.getOrThrow("NODE_ENV") == "production" ? false : true, migrationsTableName: "typeorm_migrations", migrationsRun: false, diff --git a/src/modules/invoices/entities/invoice.entity.ts b/src/modules/invoices/entities/invoice.entity.ts index 4a02794..e86738c 100755 --- a/src/modules/invoices/entities/invoice.entity.ts +++ b/src/modules/invoices/entities/invoice.entity.ts @@ -36,7 +36,7 @@ export class Invoice extends BaseEntity { tax: Decimal; @Column({ type: "decimal", precision: 16, scale: 2, nullable: true, transformer: new DecimalTransformer() }) - lateFee?: Decimal; + lateFee: Decimal | null; //--------------------------------------------- @@ -44,7 +44,7 @@ export class Invoice extends BaseEntity { items: InvoiceItem[]; @ManyToOne(() => Discount, (discount) => discount.invoices, { nullable: true, onDelete: "RESTRICT" }) - discount?: Discount; + discount: Discount | null; get isOverdue(): boolean { return this.status === InvoiceStatus.PENDING && new Date() > this.dueDate; diff --git a/src/modules/invoices/providers/invoices.service.ts b/src/modules/invoices/providers/invoices.service.ts index 06b2361..2b16655 100755 --- a/src/modules/invoices/providers/invoices.service.ts +++ b/src/modules/invoices/providers/invoices.service.ts @@ -619,12 +619,12 @@ export class InvoicesService { let discountAmount: Decimal; if (discount.type === DiscountType.PERCENTAGE) { - discountAmount = invoice.originalPrice.mul(discount.value).div(100); + discountAmount = new Decimal(invoice.originalPrice).mul(discount.value).div(100); } else { discountAmount = new Decimal(discount.value); } - invoice.totalPrice = invoice.originalPrice.sub(discountAmount); + invoice.totalPrice = new Decimal(invoice.originalPrice).sub(discountAmount); invoice.discount = discount; await queryRunner.manager.save(invoice); @@ -655,9 +655,9 @@ export class InvoicesService { if (!invoice.originalPrice) throw new BadRequestException(InvoiceMessage.ORIGINAL_PRICE_NOT_FOUND); invoice.totalPrice = invoice.originalPrice; - invoice.discount = undefined; + invoice.discount = null; - await queryRunner.manager.save(invoice); + await queryRunner.manager.save(this.invoiceRepository.target, invoice); await queryRunner.commitTransaction(); return { diff --git a/src/modules/tickets/DTO/refer-ticket.dto.ts b/src/modules/tickets/DTO/refer-ticket.dto.ts new file mode 100644 index 0000000..08e03be --- /dev/null +++ b/src/modules/tickets/DTO/refer-ticket.dto.ts @@ -0,0 +1,11 @@ +import { ApiProperty } from "@nestjs/swagger"; +import { IsNotEmpty, IsUUID } from "class-validator"; + +import { UserMessage } from "../../../common/enums/message.enum"; + +export class ReferTicketDto { + @IsNotEmpty({ message: UserMessage.ADMIN_ID_REQUIRED }) + @IsUUID("4", { message: UserMessage.ADMIN_ID_SHOULD_BE_UUID }) + @ApiProperty({ description: "admin id", example: "123e4567-e89b-12d3-a456-426614174000" }) + adminId: string; +} diff --git a/src/modules/tickets/providers/tickets.service.ts b/src/modules/tickets/providers/tickets.service.ts index d7a0141..b482809 100755 --- a/src/modules/tickets/providers/tickets.service.ts +++ b/src/modules/tickets/providers/tickets.service.ts @@ -12,6 +12,7 @@ import { PaginationUtils } from "../../utils/providers/pagination.utils"; import { CreateTicketCategoryDto } from "../DTO/create-ticket-category.dto"; import { CreateTicketMessageDto } from "../DTO/create-ticket-message.dto"; import { CreateTicketDto } from "../DTO/create-ticket.dto"; +import { ReferTicketDto } from "../DTO/refer-ticket.dto"; import { SearchTicketCategoryDto } from "../DTO/search-ticket-category.dto"; import { SearchTicketQueryDto } from "../DTO/search-ticket-query.dto"; import { UpdateTicketCategoryDto } from "../DTO/update-ticket-category.dto"; @@ -23,7 +24,6 @@ import { TicketStatus } from "../enums/ticket-status.enum"; import { TicketCategoryRepository } from "../repositories/tickets-category.repository"; import { TicketMessagesRepository } from "../repositories/tickets-message.repository"; import { TicketsRepository } from "../repositories/tickets.repository"; - @Injectable() export class TicketsService { private readonly logger = new Logger(TicketsService.name); @@ -58,13 +58,11 @@ export class TicketsService { .loadRelationCountAndMap("category.usersCount", "group.users"); if (queryDto.isActive !== undefined) { - queryBuilder.andWhere("category.isActive = :isActive", { - isActive: queryDto.isActive === 1, - }); + queryBuilder.andWhere("category.isActive = :isActive", { isActive: queryDto.isActive === 1 }); } if (queryDto.q) { - queryBuilder.where("category.title LIKE :q", { q: `%${queryDto.q}%` }); + queryBuilder.where("category.title ILIKE :q", { q: `%${queryDto.q}%` }); } const [categories, count] = await queryBuilder.skip(skip).take(limit).getManyAndCount(); @@ -276,8 +274,6 @@ export class TicketsService { if (ticket.status === TicketStatus.CLOSED) throw new BadRequestException(TicketMessageEnum.TICKET_CLOSED); const isSuperAdmin = await this.usersService.isSuperAdmin(userId); - console.log(isSuperAdmin); - console.log(ticket.assignedTo); if (isAdmin && !isSuperAdmin && ticket.assignedTo?.id !== userId) throw new BadRequestException(TicketMessageEnum.TICKET_NOT_ASSIGNED_TO_USER); @@ -355,6 +351,50 @@ export class TicketsService { message: TicketMessageEnum.CLOSED, }; } + //******************************** */ + async referTicket(ticketId: string, userId: string, referDto: ReferTicketDto) { + const queryRunner = this.dataSource.createQueryRunner(); + + try { + await queryRunner.connect(); + await queryRunner.startTransaction(); + + const isSuperAdmin = await this.usersService.isSuperAdmin(userId); + if (!isSuperAdmin) throw new BadRequestException(TicketMessageEnum.TICKET_NOT_ASSIGNED_TO_USER); + + const ticket = await this.ticketsRepository.findTicketById(ticketId); + if (!ticket) throw new BadRequestException(TicketMessageEnum.TICKET_NOT_FOUND); + + const admin = await this.usersService.findOneByIdWithQueryRunner(referDto.adminId, queryRunner); + + ticket.assignedTo = admin; + await queryRunner.manager.save(this.ticketsRepository.target, ticket); + + await this.notificationsService.createAssignTicketNotificationForAdmin( + admin.id, + { + ticketId: ticket.numericId.toString(), + subject: ticket.subject, + date: ticket.createdAt, + userPhone: ticket.user.phone, + userEmail: ticket.user.email, + user: `${ticket.user.firstName} ${ticket.user.lastName}`, + }, + queryRunner, + ); + + await queryRunner.commitTransaction(); + return { + message: TicketMessageEnum.REFERRED, + ticket, + }; + } catch (error) { + await queryRunner.rollbackTransaction(); + throw error; + } finally { + await queryRunner.release(); + } + } //******************************** */ @@ -375,7 +415,6 @@ export class TicketsService { //***************************** */ private async assignTicketAndNotify(ticket: Ticket, ticketCategory: TicketCategory, user: User, queryRunner: QueryRunner) { - // const assignedUser = await this.autoAssignUser(ticketCategory); const assignedUser = await this.referralService.assignAdmin(ticketCategory.group); // ticket.assignedTo = assignedUser; diff --git a/src/modules/tickets/tickets.controller.ts b/src/modules/tickets/tickets.controller.ts index d5b11f7..60912f8 100755 --- a/src/modules/tickets/tickets.controller.ts +++ b/src/modules/tickets/tickets.controller.ts @@ -4,26 +4,26 @@ import { ApiOperation, ApiTags } from "@nestjs/swagger"; import { CreateTicketCategoryDto } from "./DTO/create-ticket-category.dto"; import { CreateTicketMessageDto } from "./DTO/create-ticket-message.dto"; import { CreateTicketDto } from "./DTO/create-ticket.dto"; +import { ReferTicketDto } from "./DTO/refer-ticket.dto"; import { SearchTicketCategoryDto } from "./DTO/search-ticket-category.dto"; import { SearchTicketQueryDto } from "./DTO/search-ticket-query.dto"; import { UpdateTicketCategoryDto } from "./DTO/update-ticket-category.dto"; import { TicketsService } from "./providers/tickets.service"; +import { AdminRoute } from "../../common/decorators/admin.decorator"; import { AuthGuards } from "../../common/decorators/auth-guard.decorator"; -import { Pagination } from "../../common/decorators/pagination.decorator"; import { PermissionsDec } from "../../common/decorators/permission.decorator"; import { UserDec } from "../../common/decorators/user.decorator"; import { ParamDto } from "../../common/DTO/param.dto"; import { PermissionEnum } from "../users/enums/permission.enum"; - @Controller("tickets") @ApiTags("Tickets") +@AuthGuards() export class TicketsController { constructor(private readonly ticketsService: TicketsService) {} //----------------- ticket categories ------------------- @ApiOperation({ summary: "Create ticket category => admin route" }) - @AuthGuards() @PermissionsDec(PermissionEnum.TICKETS) @Post("category") createTicketCategory(@Body() createDto: CreateTicketCategoryDto) { @@ -31,7 +31,6 @@ export class TicketsController { } @ApiOperation({ summary: "Update ticket category => admin route" }) - @AuthGuards() @PermissionsDec(PermissionEnum.TICKETS) @Patch("category/:id") updateCategory(@Param() paramDto: ParamDto, @Body() updateCategoryDto: UpdateTicketCategoryDto) { @@ -39,7 +38,6 @@ export class TicketsController { } @ApiOperation({ summary: "Get ticket categories " }) - @AuthGuards() @Get("categories") // @PermissionsDec(PermissionEnum.TICKETS) getTicketCategories() { @@ -47,8 +45,7 @@ export class TicketsController { } @ApiOperation({ summary: "Get ticket categories list => admin route" }) - @Pagination() - @AuthGuards() + @AdminRoute() @PermissionsDec(PermissionEnum.TICKETS) @Get("categories-list") getCategoriesList(@Query() queryDto: SearchTicketCategoryDto) { @@ -56,7 +53,7 @@ export class TicketsController { } @ApiOperation({ summary: "toggle status of categories => admin route" }) - @AuthGuards() + @AdminRoute() @HttpCode(HttpStatus.OK) @PermissionsDec(PermissionEnum.TICKETS) @Post("category/toggle-status/:id") @@ -67,31 +64,26 @@ export class TicketsController { //----------------- ticket and ticket messages ------------------- @ApiOperation({ summary: "create ticket ==> user route" }) - @AuthGuards() @Post() createTicket(@Body() createDto: CreateTicketDto, @UserDec("id") userId: string) { return this.ticketsService.createTicket(createDto, userId); } @ApiOperation({ summary: "Get all tickets of user ==> user route" }) - @AuthGuards() - @Pagination() @Get() getTickets(@Query() queryDto: SearchTicketQueryDto, @UserDec("id") userId: string) { return this.ticketsService.getTickets(queryDto, userId); } @ApiOperation({ summary: "Get all tickets ==> admin route" }) - @AuthGuards() + @AdminRoute() @PermissionsDec(PermissionEnum.TICKETS) - @Pagination() @Get("admin") getAllTickets(@Query() queryDto: SearchTicketQueryDto, @UserDec("id") adminId: string) { return this.ticketsService.getTicketForAdmin(queryDto, adminId); } @ApiOperation({ summary: "create ticket messages" }) - @AuthGuards() @Post(":id/messages") createTicketMessage( @Param() paramDto: ParamDto, @@ -103,17 +95,23 @@ export class TicketsController { } @ApiOperation({ summary: "Get all ticket messages of user" }) - @AuthGuards() @Get(":id/messages") getTicketMessages(@Param() paramDto: ParamDto, @UserDec("id") userId: string, @UserDec("isAdmin") isAdmin: boolean) { return this.ticketsService.getTicketMessages(paramDto.id, userId, isAdmin); } @ApiOperation({ summary: "close ticket by user or admin" }) - @AuthGuards() @HttpCode(HttpStatus.OK) @Post(":id/close") closedTicketByUser(@Param() paramDto: ParamDto, @UserDec("id") userId: string, @UserDec("isAdmin") isAdmin: boolean) { return this.ticketsService.closeTicketByUser(paramDto.id, userId, isAdmin); } + + @ApiOperation({ summary: "referer ticket ==> admin route" }) + @AdminRoute() + @PermissionsDec(PermissionEnum.TICKETS) + @Post(":id/refer") + referTicket(@Param() paramDto: ParamDto, @UserDec("id") userId: string, @Body() referDto: ReferTicketDto) { + return this.ticketsService.referTicket(paramDto.id, userId, referDto); + } }