diff --git a/src/common/enums/message.enum.ts b/src/common/enums/message.enum.ts index b47053a..156a1fb 100755 --- a/src/common/enums/message.enum.ts +++ b/src/common/enums/message.enum.ts @@ -286,6 +286,10 @@ export const enum TicketMessageEnum { TICKET_NOT_ASSIGNED = "تیکت به کارشناسی اختصاص داده نشده است", TICKET_NOT_ASSIGNED_TO_USER = "تیکت به شما اختصاص داده نشده است", REFERRED = "REFERRED", + NO_ACTIVE_SUPPORT_PLAN = "شما پلن پشتیبانی فعال ندارید", + TICKET_LIMIT_NOT_FOUND = "محدودیت تعداد تیکت در پلن پشتیبانی شما یافت نشد", + INVALID_TICKET_LIMIT = "محدودیت تعداد تیکت در پلن پشتیبانی شما نامعتبر است", + TICKET_LIMIT_EXCEEDED = "شما به محدودیت تعداد تیکت در پلن پشتیبانی خود رسیده‌اید", } export const enum WalletMessage { diff --git a/src/modules/support-plans/providers/support-plans.service.ts b/src/modules/support-plans/providers/support-plans.service.ts index 9d2055f..db5ea79 100644 --- a/src/modules/support-plans/providers/support-plans.service.ts +++ b/src/modules/support-plans/providers/support-plans.service.ts @@ -173,6 +173,15 @@ export class SupportPlansService { return { userSupportPlan }; } + + //***************************************** */ + async getUsersOfSupportPlan(supportPlanId: string) { + const users = await this.userSupportPlanRepo.find({ + where: { supportPlan: { id: supportPlanId }, status: UserSupportPlanStatus.ACTIVE }, + relations: { user: true, supportPlan: true }, + }); + return users; + } //***************************************** */ async upgradeSupportPlan(newPlanId: string, userId: string) { const queryRunner = this.dataSource.createQueryRunner(); diff --git a/src/modules/support-plans/repositories/support-plan.repository.ts b/src/modules/support-plans/repositories/support-plan.repository.ts index 0d92fc7..6070145 100644 --- a/src/modules/support-plans/repositories/support-plan.repository.ts +++ b/src/modules/support-plans/repositories/support-plan.repository.ts @@ -30,7 +30,13 @@ export class SupportPlanRepository extends Repository { } async getSupportPlansListUser() { - return this.find({ where: { isActive: true, deletedAt: IsNull() }, order: { price: "ASC" }, relations: { features: true } }); + return this.createQueryBuilder("supportPlan") + .leftJoinAndSelect("supportPlan.features", "features") + .where("supportPlan.isActive = :isActive", { isActive: true }) + .andWhere("supportPlan.deletedAt IS NULL") + .orderBy("supportPlan.price", "ASC") + .addOrderBy("features.featureKey", "ASC") + .getMany(); } //***************************************** */ async getSupportPlanById(id: string) { diff --git a/src/modules/tickets/providers/tickets.service.ts b/src/modules/tickets/providers/tickets.service.ts index ac25158..bd3009d 100755 --- a/src/modules/tickets/providers/tickets.service.ts +++ b/src/modules/tickets/providers/tickets.service.ts @@ -1,11 +1,14 @@ import { BadRequestException, HttpException, Injectable, InternalServerErrorException, Logger } from "@nestjs/common"; -import { DataSource, Not, QueryRunner } from "typeorm"; +import dayjs from "dayjs"; +import { DataSource, MoreThanOrEqual, Not, QueryRunner } from "typeorm"; import { ReferralService } from "./referral.service"; import { ParamDto } from "../../../common/DTO/param.dto"; import { CommonMessage, TicketMessageEnum, UserMessage } from "../../../common/enums/message.enum"; import { DanakService } from "../../danak-services/entities/danak-service.entity"; import { NotificationQueue } from "../../notifications/queue/notification.queue"; +import { SupportPlanFeatureKey } from "../../support-plans/enums/support-plan-feature-key.enum"; +import { SupportPlansService } from "../../support-plans/providers/support-plans.service"; import { User } from "../../users/entities/user.entity"; import { AdminsService } from "../../users/providers/admins.service"; import { UsersService } from "../../users/providers/users.service"; @@ -24,19 +27,23 @@ 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); + constructor( - private readonly notificationQueue: NotificationQueue, - private readonly ticketsCategoryRepository: TicketCategoryRepository, private readonly ticketsRepository: TicketsRepository, + private readonly ticketsCategoryRepository: TicketCategoryRepository, private readonly ticketMessagesRepository: TicketMessagesRepository, private readonly usersService: UsersService, private readonly adminsService: AdminsService, + private readonly notificationQueue: NotificationQueue, private readonly referralService: ReferralService, - private dataSource: DataSource, + private readonly dataSource: DataSource, + private readonly supportPlansService: SupportPlansService, ) {} + //******************************** */ async getTicketCategories() { const ticketCategories = await this.ticketsCategoryRepository.find({}); @@ -106,6 +113,36 @@ export class TicketsService { //******************************** */ + private async checkTicketLimit(userId: string, queryRunner: QueryRunner): Promise { + const { userSupportPlan } = await this.supportPlansService.getUserSupportPlanOfUser(userId); + + if (!userSupportPlan) throw new BadRequestException(TicketMessageEnum.NO_ACTIVE_SUPPORT_PLAN); + + const ticketLimitFeature = userSupportPlan.supportPlan.features.find( + (feature) => feature.featureKey === SupportPlanFeatureKey.TICKET_LIMIT, + ); + + if (!ticketLimitFeature) throw new BadRequestException(TicketMessageEnum.TICKET_LIMIT_NOT_FOUND); + + const ticketLimit = parseInt(ticketLimitFeature.featureValue); + if (isNaN(ticketLimit)) return; + + if (isNaN(ticketLimit)) throw new BadRequestException(TicketMessageEnum.INVALID_TICKET_LIMIT); + + const today = dayjs().startOf("day").toDate(); + + const ticketCount = await queryRunner.manager.count(this.ticketsRepository.target, { + where: { + user: { id: userId }, + createdAt: MoreThanOrEqual(today), + }, + }); + + if (ticketCount >= ticketLimit) throw new BadRequestException(TicketMessageEnum.TICKET_LIMIT_EXCEEDED); + } + + //******************************** */ + async createTicket(createDto: CreateTicketDto, userId: string) { const queryRunner = this.dataSource.createQueryRunner(); await queryRunner.connect(); @@ -122,6 +159,8 @@ export class TicketsService { if (!ticketCategory) throw new BadRequestException(TicketMessageEnum.CATEGORY_NOT_FOUND); + await this.checkTicketLimit(userId, queryRunner); + let service = null; if (createDto.danakServiceId) { service = await queryRunner.manager.findOneBy(DanakService, { diff --git a/src/modules/tickets/tickets.module.ts b/src/modules/tickets/tickets.module.ts index 58aa53a..c157004 100755 --- a/src/modules/tickets/tickets.module.ts +++ b/src/modules/tickets/tickets.module.ts @@ -18,6 +18,7 @@ import { NotificationModule } from "../notifications/notifications.module"; import { ReferralService } from "./providers/referral.service"; import { GroupStrategy } from "./strategies/group.strategy"; import { RoundRobinStrategy } from "./strategies/round-robin.strategy"; +import { SupportPlansModule } from "../support-plans/support-plans.module"; @Module({ imports: [ @@ -25,6 +26,7 @@ import { RoundRobinStrategy } from "./strategies/round-robin.strategy"; UsersModule, DanakServicesModule, NotificationModule, + SupportPlansModule, ], providers: [ GroupStrategy,