chore: add new structure for the ticket assinge to admins
This commit is contained in:
@@ -68,7 +68,6 @@ import { WalletsModule } from "./modules/wallets/wallets.module";
|
||||
LoggerModule,
|
||||
],
|
||||
controllers: [],
|
||||
providers: [],
|
||||
})
|
||||
export class AppModule implements NestModule {
|
||||
configure(consumer: MiddlewareConsumer) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user