Files
helix-engage-server/src/worklist/worklist.service.ts
2026-03-23 17:21:00 +05:30

112 lines
3.7 KiB
TypeScript

import { Injectable, Logger } from '@nestjs/common';
import { PlatformGraphqlService } from '../platform/platform-graphql.service';
export type WorklistResponse = {
missedCalls: any[];
followUps: any[];
marketingLeads: any[];
totalPending: number;
};
@Injectable()
export class WorklistService {
private readonly logger = new Logger(WorklistService.name);
constructor(private readonly platform: PlatformGraphqlService) {}
async getWorklist(
agentName: string,
authHeader: string,
): Promise<WorklistResponse> {
const [missedCalls, followUps, marketingLeads] = await Promise.all([
this.getMissedCalls(agentName, authHeader),
this.getPendingFollowUps(agentName, authHeader),
this.getAssignedLeads(agentName, authHeader),
]);
return {
missedCalls,
followUps,
marketingLeads,
totalPending:
missedCalls.length + followUps.length + marketingLeads.length,
};
}
private async getAssignedLeads(
agentName: string,
authHeader: string,
): Promise<any[]> {
try {
const data = await this.platform.queryWithAuth<any>(
`{ leads(first: 20, filter: { assignedAgent: { eq: "${agentName}" } }, orderBy: [{ createdAt: AscNullsLast }]) { edges { node {
id createdAt
contactName { firstName lastName }
contactPhone { primaryPhoneNumber }
contactEmail { primaryEmail }
source status interestedService
assignedAgent campaignId
contactAttempts spamScore isSpam
aiSummary aiSuggestedAction
} } } }`,
undefined,
authHeader,
);
return data.leads.edges.map((e: any) => e.node);
} catch (err) {
this.logger.warn(`Failed to fetch assigned leads: ${err}`);
return [];
}
}
private async getPendingFollowUps(
agentName: string,
authHeader: string,
): Promise<any[]> {
try {
const data = await this.platform.queryWithAuth<any>(
`{ followUps(first: 20, filter: { assignedAgent: { eq: "${agentName}" } }) { edges { node {
id name createdAt
typeCustom status scheduledAt completedAt
priority assignedAgent
patientId
} } } }`,
undefined,
authHeader,
);
// Filter to PENDING/OVERDUE client-side since platform may not support in-filter on remapped fields
return data.followUps.edges
.map((e: any) => e.node)
.filter((f: any) => f.status === 'PENDING' || f.status === 'OVERDUE');
} catch (err) {
this.logger.warn(`Failed to fetch follow-ups: ${err}`);
return [];
}
}
private async getMissedCalls(
agentName: string,
authHeader: string,
): Promise<any[]> {
try {
// FIFO ordering (AscNullsLast) — oldest first. No agentName filter — missed calls are a shared queue.
const data = await this.platform.queryWithAuth<any>(
`{ calls(first: 20, filter: { callStatus: { eq: MISSED }, callbackstatus: { in: [PENDING_CALLBACK, CALLBACK_ATTEMPTED] } }, orderBy: [{ startedAt: AscNullsLast }]) { edges { node {
id name createdAt
direction callStatus agentName
callerNumber { primaryPhoneNumber }
startedAt endedAt durationSec
disposition leadId
callbackstatus callsourcenumber missedcallcount callbackattemptedat
} } } }`,
undefined,
authHeader,
);
return data.calls.edges.map((e: any) => e.node);
} catch (err) {
this.logger.warn(`Failed to fetch missed calls: ${err}`);
return [];
}
}
}