chore: add new structure for the ticket assinge to admins

This commit is contained in:
mahyargdz
2025-04-04 14:22:35 +03:30
parent 0b62341908
commit 0bc001c66b
10 changed files with 185 additions and 34 deletions
-1
View File
@@ -68,7 +68,6 @@ import { WalletsModule } from "./modules/wallets/wallets.module";
LoggerModule,
],
controllers: [],
providers: [],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
+1
View File
@@ -0,0 +1 @@
export const REFERRAL_STRATEGY = "referralStrategy";
@@ -0,0 +1,6 @@
import { UserGroup } from "../../users/entities/user-group.entity";
import { User } from "../../users/entities/user.entity";
export interface IReferralStrategy {
assignAdmin(group: UserGroup): Promise<User>;
}
@@ -0,0 +1,29 @@
import { Injectable } from "@nestjs/common";
import { IReferralStrategy } from "../interfaces/referral-strategy";
import { GroupStrategy } from "../strategies/group.strategy";
import { RoundRobinStrategy } from "../strategies/round-robin.strategy";
export enum ReferralStrategyType {
GROUP = "GROUP",
ROUND_ROBIN = "ROUND_ROBIN",
}
@Injectable()
export class ReferralStrategyFactory {
constructor(
private readonly groupStrategy: GroupStrategy,
private readonly roundRobinStrategy: RoundRobinStrategy,
) {}
getStrategy(type: ReferralStrategyType): IReferralStrategy {
switch (type) {
case ReferralStrategyType.GROUP:
return this.groupStrategy;
case ReferralStrategyType.ROUND_ROBIN:
return this.roundRobinStrategy;
default:
return this.groupStrategy; // Default to group strategy
}
}
}
@@ -0,0 +1,23 @@
import { Injectable } from "@nestjs/common";
import { ReferralStrategyFactory, ReferralStrategyType } from "./referral-strategy.factory";
import { UserGroup } from "../../users/entities/user-group.entity";
import { User } from "../../users/entities/user.entity";
import { IReferralStrategy } from "../interfaces/referral-strategy";
@Injectable()
export class ReferralService {
private currentStrategy: IReferralStrategy;
constructor(private readonly strategyFactory: ReferralStrategyFactory) {
this.currentStrategy = this.strategyFactory.getStrategy(ReferralStrategyType.GROUP);
}
assignAdmin(group: UserGroup): Promise<User> {
return this.currentStrategy.assignAdmin(group);
}
setStrategy(type: ReferralStrategyType) {
this.currentStrategy = this.strategyFactory.getStrategy(type);
}
}
@@ -1,6 +1,7 @@
import { BadRequestException, HttpException, Injectable, InternalServerErrorException, Logger } from "@nestjs/common";
import { DataSource, 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";
@@ -32,6 +33,7 @@ export class TicketsService {
private readonly ticketsRepository: TicketsRepository,
private readonly ticketMessagesRepository: TicketMessagesRepository,
private readonly usersService: UsersService,
private readonly referralService: ReferralService,
private dataSource: DataSource,
) {}
//******************************** */
@@ -371,41 +373,26 @@ export class TicketsService {
return ticketCount;
}
//******************************** */
async autoAssignUser(category: TicketCategory): Promise<User | null> {
if (!category.group) return null;
const { users } = await this.usersService.getUsersByGroup(category.group.id);
if (users.length === 0) return null;
users.sort((a, b) => (a.tickets.length || 0) - (b.tickets.length || 0));
console.log(users);
return users[0];
}
//***************************** */
private async assignTicketAndNotify(ticket: Ticket, ticketCategory: TicketCategory, user: User, queryRunner: QueryRunner) {
const assignedUser = await this.autoAssignUser(ticketCategory);
// const assignedUser = await this.autoAssignUser(ticketCategory);
const assignedUser = await this.referralService.assignAdmin(ticketCategory.group);
//
ticket.assignedTo = assignedUser;
await queryRunner.manager.save(Ticket, ticket);
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.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,
@@ -0,0 +1,39 @@
import { Injectable, InternalServerErrorException, Logger } from "@nestjs/common";
import { UserGroup } from "../../users/entities/user-group.entity";
import { User } from "../../users/entities/user.entity";
import { UsersService } from "../../users/providers/users.service";
import { IReferralStrategy } from "../interfaces/referral-strategy";
@Injectable()
export class GroupStrategy implements IReferralStrategy {
private readonly logger = new Logger(GroupStrategy.name);
constructor(private readonly usersService: UsersService) {}
async assignAdmin(group: UserGroup): Promise<User> {
try {
const { users: admins } = await this.usersService.getUsersByGroup(group.id);
if (!admins.length) {
this.logger.warn(`No admins found in Group: ${group.name}, falling back to super admin`);
//fall back
const superAdmin = await this.usersService.getSuperAdmin();
if (!superAdmin) throw new InternalServerErrorException("No super admin found as fallback");
return superAdmin;
}
// return the one with the fewest tickets
return admins.reduce((prev, curr) => ((prev.tickets?.length || 0) < (curr.tickets?.length || 0) ? prev : curr));
//
} catch (error: unknown) {
this.logger.error(`Error assigning admin for group ${group.name}: ${error instanceof Error ? error.message : "Unknown error"}`);
// If any error occurs, try to get a super admin as last resort
const superAdmin = await this.usersService.getSuperAdmin();
if (!superAdmin) throw new InternalServerErrorException("No super admin found as fallback");
return superAdmin;
}
}
}
@@ -0,0 +1,44 @@
import { Injectable, InternalServerErrorException, Logger } from "@nestjs/common";
import { UserGroup } from "../../users/entities/user-group.entity";
import { User } from "../../users/entities/user.entity";
import { UsersService } from "../../users/providers/users.service";
import { IReferralStrategy } from "../interfaces/referral-strategy";
@Injectable()
export class RoundRobinStrategy implements IReferralStrategy {
private readonly logger = new Logger(RoundRobinStrategy.name);
private currentIndex: Map<string, number> = new Map();
constructor(private readonly usersService: UsersService) {}
async assignAdmin(group: UserGroup): Promise<User> {
try {
const { users: admins } = await this.usersService.getUsersByGroup(group.id);
if (!admins.length) {
this.logger.warn(`No admins found in Group: ${group.name}, falling back to super admin`);
const superAdmin = await this.usersService.getSuperAdmin();
if (!superAdmin) throw new InternalServerErrorException("No super admin found as fallback");
return superAdmin;
}
// Get or initialize the current index for this group
const currentIndex = this.currentIndex.get(group.id) || 0;
// Calculate the next admin index using modulo to wrap around
const nextIndex = (currentIndex + 1) % admins.length;
// Update the current index for this group
this.currentIndex.set(group.id, nextIndex);
// Return the admin at the calculated index
return admins[nextIndex];
} catch (error: unknown) {
this.logger.error(`Error assigning admin for group ${group.name}: ${error instanceof Error ? error.message : "Unknown error"}`);
const superAdmin = await this.usersService.getSuperAdmin();
if (!superAdmin) throw new InternalServerErrorException("No super admin found as fallback");
return superAdmin;
}
}
}
+19 -1
View File
@@ -4,6 +4,7 @@ import { TypeOrmModule } from "@nestjs/typeorm";
import { TicketCategory } from "./entities/ticket-category.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 { TicketCategoryRepository } from "./repositories/tickets-category.repository";
import { TicketMessagesRepository } from "./repositories/tickets-message.repository";
@@ -14,6 +15,9 @@ import { TicketMessageAttachment } from "./entities/ticket-message-attachment";
import { TicketMessageAttachmentsRepository } from "./repositories/ticket-message-attachment.repository";
import { DanakServicesModule } from "../danak-services/danak-services.module";
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";
@Module({
imports: [
@@ -22,7 +26,21 @@ import { NotificationModule } from "../notifications/notifications.module";
DanakServicesModule,
NotificationModule,
],
providers: [TicketsService, TicketsRepository, TicketCategoryRepository, TicketMessagesRepository, TicketMessageAttachmentsRepository],
providers: [
GroupStrategy,
RoundRobinStrategy,
ReferralStrategyFactory,
{
provide: ReferralService,
useFactory: (strategyFactory: ReferralStrategyFactory) => new ReferralService(strategyFactory),
inject: [ReferralStrategyFactory],
},
TicketsService,
TicketsRepository,
TicketCategoryRepository,
TicketMessagesRepository,
TicketMessageAttachmentsRepository,
],
controllers: [TicketsController],
exports: [TicketsService],
})
@@ -337,6 +337,11 @@ export class UsersService {
}
/************************************************************ */
async getSuperAdmin() {
const superAdmin = await this.userRepository.findOne({ where: { roles: { name: RoleEnum.SUPER_ADMIN } }, relations: { roles: true } });
return superAdmin;
}
async isSuperAdmin(adminId: string) {
const user = await this.userRepository.findOne({ where: { id: adminId }, relations: { roles: true } });
if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND);