All files / src/domains/helpdesk/services helpdesk-routing.service.ts

0% Statements 0/54
0% Branches 0/41
0% Functions 0/8
0% Lines 0/45

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106                                                                                                                                                                                                                   
import { Injectable, BadRequestException } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model, Types } from 'mongoose';
import { AgentAvailability, SupportGroup, SupportQueue, Ticket } from '../schemas';
import { EventBusService } from '../../../platform/events/event-bus.service';
 
@Injectable()
export class HelpdeskRoutingService {
  constructor(
    @InjectModel(Ticket.name)
    private readonly ticketModel: Model<Ticket>,
    @InjectModel(SupportGroup.name)
    private readonly groupModel: Model<SupportGroup>,
    @InjectModel(SupportQueue.name)
    private readonly queueModel: Model<SupportQueue>,
    @InjectModel(AgentAvailability.name)
    private readonly availabilityModel: Model<AgentAvailability>,
    private readonly eventBus: EventBusService
  ) {}
 
  async registerAgent(tenantId: string, agentId: string, skills: string[], maxCapacity = 5): Promise<AgentAvailability> {
    let avail = await this.availabilityModel.findOne({ tenantId, agentId }).exec();
    if (!avail) {
      avail = new this.availabilityModel({
        tenantId,
        agentId,
        skills,
        maxCapacity,
        isOnline: true,
        activeTicketsCount: 0
      });
    } else {
      avail.skills = skills;
      avail.maxCapacity = maxCapacity;
    }
    return avail.save();
  }
 
  async getEligibleAgents(tenantId: string, queueId: string, requiredSkills: string[] = []): Promise<AgentAvailability[]> {
    const queue = await this.queueModel.findOne({ _id: queueId, tenantId }).exec();
    if (!queue || !queue.routingGroupId) return [];
 
    const group = await this.groupModel.findById(queue.routingGroupId).exec();
    if (!group) return [];
 
    // Find online members within capacity
    const agents = await this.availabilityModel.find({
      tenantId,
      agentId: { $in: group.memberAgentIds },
      isOnline: true,
      $expr: { $lt: ['$activeTicketsCount', '$maxCapacity'] }
    }).exec();
 
    // Filter by required skills
    if (requiredSkills.length > 0) {
      return agents.filter(agent =>
        requiredSkills.every(skill => agent.skills.includes(skill))
      );
    }
 
    return agents;
  }
 
  async assignTicket(tenantId: string, ticketId: string, requiredSkills: string[] = []): Promise<Ticket> {
    const ticket = await this.ticketModel.findOne({ _id: ticketId, tenantId }).exec();
    if (!ticket) throw new BadRequestException('Ticket not found');
    if (!ticket.queueId) throw new BadRequestException('Ticket has no queue assigned');
 
    const eligible = await this.getEligibleAgents(tenantId, ticket.queueId.toString(), requiredSkills);
    
    if (eligible.length === 0) {
      // Fallback: remains unassigned for manual dispatch
      return ticket;
    }
 
    // Least assigned logic
    eligible.sort((a, b) => a.activeTicketsCount - b.activeTicketsCount);
    const assignedAgent = eligible[0];
 
    ticket.assignedAgentId = assignedAgent.agentId;
    ticket.status = 'assigned';
    await ticket.save();
 
    // Increment agent active count
    assignedAgent.activeTicketsCount += 1;
    await assignedAgent.save();
 
    await this.eventBus.publish('helpdesk.ticket.assigned.v1', {
      ticketId: ticketId,
      ticketNumber: ticket.ticketNumber,
      tenantId,
      agentId: assignedAgent.agentId
    }, tenantId);
 
    return ticket;
  }
 
  async setAgentAvailability(tenantId: string, agentId: string, isOnline: boolean): Promise<AgentAvailability> {
    const avail = await this.availabilityModel.findOne({ tenantId, agentId }).exec();
    if (!avail) throw new BadRequestException('Agent profile not found');
 
    avail.isOnline = isOnline;
    return avail.save();
  }
}