mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage-server
synced 2026-04-11 18:08:16 +00:00
feat: webhook field fixes, Force Ready endpoint, improved error logging
- Fix Call record field names (recording, callerNumber, durationSec) - Add POST /api/ozonetel/agent-ready using logout+login for Force Ready - Add callerNumber to kookoo callback - Better error logging with response body Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -25,12 +25,10 @@ export class KookooIvrController {
|
|||||||
// New outbound call — customer answered, put them in a conference room
|
// New outbound call — customer answered, put them in a conference room
|
||||||
// The room ID is based on the call SID so we can join from the browser
|
// The room ID is based on the call SID so we can join from the browser
|
||||||
if (event === 'NewCall') {
|
if (event === 'NewCall') {
|
||||||
// Try dialing the SIP extension with 0 prefix (internal routing)
|
this.logger.log(`Customer ${cid} answered — dialing DID ${this.callerId} to route to agent`);
|
||||||
const ext = query.ext ?? `0${this.sipId}`;
|
|
||||||
this.logger.log(`Customer ${cid} answered — dialing agent at ${ext}`);
|
|
||||||
return `<?xml version="1.0" encoding="UTF-8"?>
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<response>
|
<response>
|
||||||
<dial record="true" timeout="30" moh="ring">${ext}</dial>
|
<dial record="true" timeout="30" moh="ring">${this.callerId}</dial>
|
||||||
</response>`;
|
</response>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -53,6 +53,29 @@ export class OzonetelAgentController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Post('agent-ready')
|
||||||
|
async agentReady() {
|
||||||
|
this.logger.log(`Force ready: logging out and back in agent ${this.defaultAgentId}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.ozonetelAgent.logoutAgent({
|
||||||
|
agentId: this.defaultAgentId,
|
||||||
|
password: this.defaultAgentPassword,
|
||||||
|
});
|
||||||
|
const result = await this.ozonetelAgent.loginAgent({
|
||||||
|
agentId: this.defaultAgentId,
|
||||||
|
password: this.defaultAgentPassword,
|
||||||
|
phoneNumber: this.defaultSipId,
|
||||||
|
mode: 'blended',
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
} catch (error: any) {
|
||||||
|
const message = error.response?.data?.message ?? error.message ?? 'Force ready failed';
|
||||||
|
this.logger.error(`Force ready failed: ${message}`);
|
||||||
|
throw new HttpException(message, error.response?.status ?? 502);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Post('dispose')
|
@Post('dispose')
|
||||||
async dispose(
|
async dispose(
|
||||||
@Body() body: {
|
@Body() body: {
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ export class KookooCallbackController {
|
|||||||
name: `Outbound — ${phoneNumber}`,
|
name: `Outbound — ${phoneNumber}`,
|
||||||
direction: 'OUTBOUND',
|
direction: 'OUTBOUND',
|
||||||
callStatus,
|
callStatus,
|
||||||
|
callerNumber: { primaryPhoneNumber: `+91${phoneNumber}` },
|
||||||
startedAt: startTime ? new Date(startTime).toISOString() : new Date().toISOString(),
|
startedAt: startTime ? new Date(startTime).toISOString() : new Date().toISOString(),
|
||||||
endedAt: endTime ? new Date(endTime).toISOString() : null,
|
endedAt: endTime ? new Date(endTime).toISOString() : null,
|
||||||
durationSec: duration,
|
durationSec: duration,
|
||||||
@@ -82,8 +83,9 @@ export class KookooCallbackController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return { received: true, processed: true, callId: callResult.createCall.id };
|
return { received: true, processed: true, callId: callResult.createCall.id };
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
this.logger.error(`Kookoo callback processing failed: ${err}`);
|
const responseData = err?.response?.data ? JSON.stringify(err.response.data) : '';
|
||||||
|
this.logger.error(`Kookoo callback processing failed: ${err.message} ${responseData}`);
|
||||||
return { received: true, processed: false };
|
return { received: true, processed: false };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,8 +105,9 @@ export class MissedCallWebhookController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return { received: true, processed: true, callId, leadId: lead?.id ?? null };
|
return { received: true, processed: true, callId, leadId: lead?.id ?? null };
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
this.logger.error(`Webhook processing failed: ${err}`);
|
const responseData = err?.response?.data ? JSON.stringify(err.response.data) : '';
|
||||||
|
this.logger.error(`Webhook processing failed: ${err.message} ${responseData}`);
|
||||||
return { received: true, processed: false, error: String(err) };
|
return { received: true, processed: false, error: String(err) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -123,21 +124,24 @@ export class MissedCallWebhookController {
|
|||||||
disposition: string | null;
|
disposition: string | null;
|
||||||
ucid: string | null;
|
ucid: string | null;
|
||||||
}, authHeader: string): Promise<string> {
|
}, authHeader: string): Promise<string> {
|
||||||
const result = await this.platform.queryWithAuth<any>(
|
const callData: Record<string, any> = {
|
||||||
`mutation($data: CallCreateInput!) { createCall(data: $data) { id } }`,
|
|
||||||
{
|
|
||||||
data: {
|
|
||||||
name: `${data.direction === 'INBOUND' ? 'Inbound' : 'Outbound'} — ${data.callerPhone}`,
|
name: `${data.direction === 'INBOUND' ? 'Inbound' : 'Outbound'} — ${data.callerPhone}`,
|
||||||
direction: data.direction,
|
direction: data.direction,
|
||||||
callStatus: data.callStatus,
|
callStatus: data.callStatus,
|
||||||
|
callerNumber: { primaryPhoneNumber: `+91${data.callerPhone}` },
|
||||||
agentName: data.agentName,
|
agentName: data.agentName,
|
||||||
startedAt: data.startTime ? new Date(data.startTime).toISOString() : null,
|
startedAt: data.startTime ? new Date(data.startTime).toISOString() : null,
|
||||||
endedAt: data.endTime ? new Date(data.endTime).toISOString() : null,
|
endedAt: data.endTime ? new Date(data.endTime).toISOString() : null,
|
||||||
durationSec: data.duration,
|
durationSec: data.duration,
|
||||||
disposition: this.mapDisposition(data.disposition),
|
disposition: this.mapDisposition(data.disposition),
|
||||||
recordingUrl: data.recordingUrl ? { primaryLinkUrl: data.recordingUrl, primaryLinkLabel: 'Recording' } : undefined,
|
};
|
||||||
},
|
if (data.recordingUrl) {
|
||||||
},
|
callData.recording = { primaryLinkUrl: data.recordingUrl, primaryLinkLabel: 'Recording' };
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await this.platform.queryWithAuth<any>(
|
||||||
|
`mutation($data: CallCreateInput!) { createCall(data: $data) { id } }`,
|
||||||
|
{ data: callData },
|
||||||
authHeader,
|
authHeader,
|
||||||
);
|
);
|
||||||
return result.createCall.id;
|
return result.createCall.id;
|
||||||
@@ -185,7 +189,7 @@ export class MissedCallWebhookController {
|
|||||||
occurredAt: new Date().toISOString(),
|
occurredAt: new Date().toISOString(),
|
||||||
performedBy: data.performedBy,
|
performedBy: data.performedBy,
|
||||||
channel: data.channel,
|
channel: data.channel,
|
||||||
durationSeconds: data.durationSeconds,
|
durationSec: data.durationSeconds,
|
||||||
outcome: data.outcome,
|
outcome: data.outcome,
|
||||||
leadId: data.leadId,
|
leadId: data.leadId,
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user