diff --git a/src/ozonetel/ozonetel-agent.controller.ts b/src/ozonetel/ozonetel-agent.controller.ts index bdc41ea..3a83f51 100644 --- a/src/ozonetel/ozonetel-agent.controller.ts +++ b/src/ozonetel/ozonetel-agent.controller.ts @@ -322,23 +322,27 @@ export class OzonetelAgentController { } @Get('performance') - async performance(@Query('date') date?: string) { + async performance(@Query('date') date?: string, @Query('agentId') agentId?: string) { + const agent = agentId ?? this.defaultAgentId; const targetDate = date ?? new Date().toISOString().split('T')[0]; - this.logger.log(`Performance: date=${targetDate} agent=${this.defaultAgentId}`); + this.logger.log(`Performance: date=${targetDate} agent=${agent}`); const [cdr, summary, aht] = await Promise.all([ this.ozonetelAgent.fetchCDR({ date: targetDate }), - this.ozonetelAgent.getAgentSummary(this.defaultAgentId, targetDate), - this.ozonetelAgent.getAHT(this.defaultAgentId), + this.ozonetelAgent.getAgentSummary(agent, targetDate), + this.ozonetelAgent.getAHT(agent), ]); - const totalCalls = cdr.length; - const inbound = cdr.filter((c: any) => c.Type === 'InBound').length; - const outbound = cdr.filter((c: any) => c.Type === 'Manual' || c.Type === 'Progressive').length; - const answered = cdr.filter((c: any) => c.Status === 'Answered').length; - const missed = cdr.filter((c: any) => c.Status === 'Unanswered' || c.Status === 'NotAnswered').length; + // Filter CDR to this agent only — fetchCDR returns all agents' calls + const agentCdr = cdr.filter((c: any) => c.AgentID === agent || c.AgentName === agent); - const talkTimes = cdr + const totalCalls = agentCdr.length; + const inbound = agentCdr.filter((c: any) => c.Type === 'InBound').length; + const outbound = agentCdr.filter((c: any) => c.Type === 'Manual' || c.Type === 'Progressive').length; + const answered = agentCdr.filter((c: any) => c.Status === 'Answered').length; + const missed = agentCdr.filter((c: any) => c.Status === 'NotAnswered').length; + + const talkTimes = agentCdr .filter((c: any) => c.TalkTime && c.TalkTime !== '00:00:00') .map((c: any) => { const parts = c.TalkTime.split(':').map(Number); @@ -349,12 +353,12 @@ export class OzonetelAgentController { : 0; const dispositions: Record = {}; - for (const c of cdr) { + for (const c of agentCdr) { const d = (c as any).Disposition || 'No Disposition'; dispositions[d] = (dispositions[d] ?? 0) + 1; } - const appointmentsBooked = cdr.filter((c: any) => + const appointmentsBooked = agentCdr.filter((c: any) => c.Disposition?.toLowerCase().includes('appointment'), ).length; diff --git a/src/supervisor/supervisor.service.ts b/src/supervisor/supervisor.service.ts index 85e2355..ccecc02 100644 --- a/src/supervisor/supervisor.service.ts +++ b/src/supervisor/supervisor.service.ts @@ -186,20 +186,52 @@ export class SupervisorService implements OnModuleInit { ); const agents = agentData?.agents?.edges?.map((e: any) => e.node) ?? []; - // Fetch Ozonetel time summary per agent + // Fetch CDR for the entire account for this date (one call, not per-agent) + let allCdr: any[] = []; + try { + allCdr = await this.ozonetel.fetchCDR({ date }); + } catch (err) { + this.logger.warn(`Failed to fetch CDR for ${date}: ${err}`); + } + + // Fetch Ozonetel time summary per agent + compute call metrics from CDR const summaries = await Promise.all( agents.map(async (agent: any) => { - if (!agent.ozonetelAgentId) return { ...agent, timeBreakdown: null }; + if (!agent.ozonetelAgentId) return { ...agent, timeBreakdown: null, calls: null }; try { const summary = await this.ozonetel.getAgentSummary(agent.ozonetelAgentId, date); - return { ...agent, timeBreakdown: summary }; + + // Filter CDR to this agent + const agentCdr = allCdr.filter( + (c: any) => c.AgentID === agent.ozonetelAgentId || c.AgentName === agent.ozonetelAgentId, + ); + const totalCalls = agentCdr.length; + const inbound = agentCdr.filter((c: any) => c.Type === 'InBound').length; + const outbound = agentCdr.filter((c: any) => c.Type === 'Manual' || c.Type === 'Progressive').length; + const answered = agentCdr.filter((c: any) => c.Status === 'Answered').length; + const missed = agentCdr.filter((c: any) => c.Status === 'NotAnswered').length; + + return { + ...agent, + timeBreakdown: summary, + calls: { total: totalCalls, inbound, outbound, answered, missed }, + }; } catch (err) { this.logger.warn(`Failed to get summary for ${agent.ozonetelAgentId}: ${err}`); - return { ...agent, timeBreakdown: null }; + return { ...agent, timeBreakdown: null, calls: null }; } }), ); - return { date, agents: summaries }; + // Aggregate team totals + const teamTotals = { + totalCalls: summaries.reduce((sum, a) => sum + (a.calls?.total ?? 0), 0), + inbound: summaries.reduce((sum, a) => sum + (a.calls?.inbound ?? 0), 0), + outbound: summaries.reduce((sum, a) => sum + (a.calls?.outbound ?? 0), 0), + answered: summaries.reduce((sum, a) => sum + (a.calls?.answered ?? 0), 0), + missed: summaries.reduce((sum, a) => sum + (a.calls?.missed ?? 0), 0), + }; + + return { date, agents: summaries, teamTotals }; } }