mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage-server
synced 2026-04-11 18:08:16 +00:00
feat: Ozonetel V3 dial endpoint, worklist query fixes
- Switch outbound dial to /CAServicesV3/mdlConnection.php (tbManualDial) - Use both Apikey + Basic Auth headers matching Ozonetel toolbar pattern - Auto-login agent before dial attempt - Dial controller returns 502 (not 401) for Ozonetel errors to prevent session logout - Fix worklist: remove non-existent callId from followUp query - Fix worklist: use unquoted MISSED enum, remove callerNumber subfield issue - Worklist controller resolves agent name from platform currentUser API Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,11 +1,23 @@
|
|||||||
import { Controller, Post, Body, Logger, HttpException } from '@nestjs/common';
|
import { Controller, Post, Body, Logger, HttpException } from '@nestjs/common';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { OzonetelAgentService } from './ozonetel-agent.service';
|
import { OzonetelAgentService } from './ozonetel-agent.service';
|
||||||
|
|
||||||
@Controller('api/ozonetel')
|
@Controller('api/ozonetel')
|
||||||
export class OzonetelAgentController {
|
export class OzonetelAgentController {
|
||||||
private readonly logger = new Logger(OzonetelAgentController.name);
|
private readonly logger = new Logger(OzonetelAgentController.name);
|
||||||
|
private readonly defaultAgentId: string;
|
||||||
|
private readonly defaultAgentPassword: string;
|
||||||
|
|
||||||
constructor(private readonly ozonetelAgent: OzonetelAgentService) {}
|
private readonly defaultSipId: string;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly ozonetelAgent: OzonetelAgentService,
|
||||||
|
private readonly config: ConfigService,
|
||||||
|
) {
|
||||||
|
this.defaultAgentId = config.get<string>('OZONETEL_AGENT_ID') ?? 'agent3';
|
||||||
|
this.defaultAgentPassword = config.get<string>('OZONETEL_AGENT_PASSWORD') ?? '';
|
||||||
|
this.defaultSipId = config.get<string>('OZONETEL_SIP_ID') ?? '521814';
|
||||||
|
}
|
||||||
|
|
||||||
@Post('agent-login')
|
@Post('agent-login')
|
||||||
async agentLogin(
|
async agentLogin(
|
||||||
@@ -40,4 +52,34 @@ export class OzonetelAgentController {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Post('dial')
|
||||||
|
async dial(
|
||||||
|
@Body() body: { phoneNumber: string; leadId?: string },
|
||||||
|
) {
|
||||||
|
if (!body.phoneNumber) {
|
||||||
|
throw new HttpException('phoneNumber required', 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.log(`Dial request: ${body.phoneNumber} (lead: ${body.leadId ?? 'none'})`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Ensure agent is logged in before dialing
|
||||||
|
await this.ozonetelAgent.loginAgent({
|
||||||
|
agentId: this.defaultAgentId,
|
||||||
|
password: this.defaultAgentPassword,
|
||||||
|
phoneNumber: this.defaultSipId,
|
||||||
|
mode: 'blended',
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await this.ozonetelAgent.dialCustomer({
|
||||||
|
agentId: this.defaultAgentId,
|
||||||
|
customerNumber: body.phoneNumber,
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
} catch (error: any) {
|
||||||
|
const message = error.response?.data?.message ?? error.message ?? 'Dial failed';
|
||||||
|
throw new HttpException(message, 502);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,6 +63,45 @@ export class OzonetelAgentService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async dialCustomer(params: {
|
||||||
|
agentId: string;
|
||||||
|
customerNumber: string;
|
||||||
|
campaignName?: string;
|
||||||
|
}): Promise<{ type: string; agentId: string; user: string }> {
|
||||||
|
const url = `https://${this.apiDomain}/CAServicesV3/mdlConnection.php`;
|
||||||
|
const basicAuth = Buffer.from(`${this.accountId}:${this.apiKey}`).toString('base64');
|
||||||
|
|
||||||
|
this.logger.log(`Dialing ${params.customerNumber} for agent ${params.agentId}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.post(
|
||||||
|
url,
|
||||||
|
{
|
||||||
|
type: 'tbManualDial',
|
||||||
|
ns: 'ozonetel.cloudagent',
|
||||||
|
customer: this.accountId,
|
||||||
|
agentId: params.agentId,
|
||||||
|
custNumber: params.customerNumber,
|
||||||
|
campaignName: params.campaignName ?? 'Inbound_918062873123',
|
||||||
|
timestamp: Date.now(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'Apikey': this.apiKey,
|
||||||
|
'Authorization': `Basic ${basicAuth}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
this.logger.log(`Dial response: ${JSON.stringify(response.data)}`);
|
||||||
|
return response.data;
|
||||||
|
} catch (error: any) {
|
||||||
|
this.logger.error(`Dial failed: ${error.response?.data?.message ?? error.message}`);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async logoutAgent(params: {
|
async logoutAgent(params: {
|
||||||
agentId: string;
|
agentId: string;
|
||||||
password: string;
|
password: string;
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ export class WorklistService {
|
|||||||
id name createdAt
|
id name createdAt
|
||||||
typeCustom status scheduledAt completedAt
|
typeCustom status scheduledAt completedAt
|
||||||
priority assignedAgent
|
priority assignedAgent
|
||||||
patientId callId
|
patientId
|
||||||
} } } }`,
|
} } } }`,
|
||||||
undefined,
|
undefined,
|
||||||
authHeader,
|
authHeader,
|
||||||
@@ -77,9 +77,9 @@ export class WorklistService {
|
|||||||
private async getMissedCalls(agentName: string, authHeader: string): Promise<any[]> {
|
private async getMissedCalls(agentName: string, authHeader: string): Promise<any[]> {
|
||||||
try {
|
try {
|
||||||
const data = await this.platform.queryWithAuth<any>(
|
const data = await this.platform.queryWithAuth<any>(
|
||||||
`{ calls(first: 20, filter: { agentName: { eq: "${agentName}" }, callStatus: { eq: "MISSED" } }, orderBy: [{ startedAt: DescNullsLast }]) { edges { node {
|
`{ calls(first: 20, filter: { agentName: { eq: "${agentName}" }, callStatus: { eq: MISSED } }, orderBy: [{ startedAt: DescNullsLast }]) { edges { node {
|
||||||
id name createdAt
|
id name createdAt
|
||||||
direction callStatus callerNumber agentName
|
direction callStatus agentName
|
||||||
startedAt endedAt durationSec
|
startedAt endedAt durationSec
|
||||||
disposition leadId
|
disposition leadId
|
||||||
} } } }`,
|
} } } }`,
|
||||||
|
|||||||
Reference in New Issue
Block a user