mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage-server
synced 2026-04-11 10:07:22 +00:00
fix: camelCase field names + dial uses per-agent config
Defect 5: Worklist, missed-call-webhook, missed-queue, ai-chat, and rules-engine all used legacy lowercase field names (callbackstatus, callsourcenumber, missedcallcount, callbackattemptedat) from the old VPS schema. Fixed to camelCase (callbackStatus, callSourceNumber, missedCallCount, callbackAttemptedAt) matching the current SDK sync. Defect 6: Dial endpoint used global defaults (OZONETEL_AGENT_ID env var) instead of the logged-in agent's config. Now accepts agentId and campaignName from the frontend request body. Falls back to telephony config → DID-derived campaign name → explicit error. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -271,7 +271,7 @@ export class AiChatController {
|
||||
inputSchema: z.object({}),
|
||||
execute: async () => {
|
||||
const data = await platformService.queryWithAuth<any>(
|
||||
`{ calls(first: 100, filter: { callStatus: { eq: MISSED }, callbackstatus: { eq: PENDING_CALLBACK } }) { edges { node { id callerNumber { primaryPhoneNumber } startedAt agentName sla } } } }`,
|
||||
`{ calls(first: 100, filter: { callStatus: { eq: MISSED }, callbackStatus: { eq: PENDING_CALLBACK } }) { edges { node { id callerNumber { primaryPhoneNumber } startedAt agentName sla } } } }`,
|
||||
undefined, auth,
|
||||
);
|
||||
const breached = data.calls.edges
|
||||
|
||||
@@ -157,7 +157,7 @@ export class OzonetelAgentController {
|
||||
if (newStatus) {
|
||||
try {
|
||||
await this.platform.query<any>(
|
||||
`mutation { updateCall(id: "${body.missedCallId}", data: { callbackstatus: ${newStatus} }) { id } }`,
|
||||
`mutation { updateCall(id: "${body.missedCallId}", data: { callbackStatus: ${newStatus} }) { id } }`,
|
||||
);
|
||||
} catch (err) {
|
||||
this.logger.warn(`Failed to update missed call status: ${err}`);
|
||||
@@ -191,19 +191,28 @@ export class OzonetelAgentController {
|
||||
|
||||
@Post('dial')
|
||||
async dial(
|
||||
@Body() body: { phoneNumber: string; campaignName?: string; leadId?: string },
|
||||
@Body() body: { phoneNumber: string; agentId?: string; campaignName?: string; leadId?: string },
|
||||
) {
|
||||
if (!body.phoneNumber) {
|
||||
throw new HttpException('phoneNumber required', 400);
|
||||
}
|
||||
|
||||
const campaignName = body.campaignName ?? this.telephony.getConfig().ozonetel.campaignName ?? 'Inbound_918041763265';
|
||||
const agentId = body.agentId ?? this.defaultAgentId;
|
||||
const campaignName = body.campaignName
|
||||
?? this.telephony.getConfig().ozonetel.campaignName
|
||||
?? this.telephony.getConfig().ozonetel.did
|
||||
? `Inbound_${this.telephony.getConfig().ozonetel.did}`
|
||||
: '';
|
||||
|
||||
this.logger.log(`[DIAL] phone=${body.phoneNumber} campaign=${campaignName} agentId=${this.defaultAgentId} lead=${body.leadId ?? 'none'}`);
|
||||
if (!campaignName) {
|
||||
throw new HttpException('Campaign name not configured — set in Telephony settings or pass campaignName', 400);
|
||||
}
|
||||
|
||||
this.logger.log(`[DIAL] phone=${body.phoneNumber} campaign=${campaignName} agentId=${agentId} lead=${body.leadId ?? 'none'}`);
|
||||
|
||||
try {
|
||||
const result = await this.ozonetelAgent.manualDial({
|
||||
agentId: this.defaultAgentId,
|
||||
agentId,
|
||||
campaignName,
|
||||
customerNumber: body.phoneNumber,
|
||||
});
|
||||
|
||||
@@ -18,10 +18,10 @@ export class CallFactsProvider implements FactProvider {
|
||||
'call.status': call.callStatus ?? null,
|
||||
'call.disposition': call.disposition ?? null,
|
||||
'call.durationSeconds': call.durationSeconds ?? call.durationSec ?? 0,
|
||||
'call.callbackStatus': call.callbackstatus ?? call.callbackStatus ?? null,
|
||||
'call.callbackStatus': call.callbackStatus ?? call.callbackStatus ?? null,
|
||||
'call.slaElapsedPercent': slaElapsedPercent,
|
||||
'call.slaBreached': slaElapsedPercent > 100,
|
||||
'call.missedCount': call.missedcallcount ?? call.missedCount ?? 0,
|
||||
'call.missedCount': call.missedCallCount ?? call.missedCount ?? 0,
|
||||
'call.taskType': taskType,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -147,8 +147,8 @@ export class MissedCallWebhookController {
|
||||
};
|
||||
// Set callback tracking fields for missed calls so they appear in the worklist
|
||||
if (data.callStatus === 'MISSED') {
|
||||
callData.callbackstatus = 'PENDING_CALLBACK';
|
||||
callData.missedcallcount = 1;
|
||||
callData.callbackStatus = 'PENDING_CALLBACK';
|
||||
callData.missedCallCount = 1;
|
||||
}
|
||||
if (data.recordingUrl) {
|
||||
callData.recording = { primaryLinkUrl: data.recordingUrl, primaryLinkLabel: 'Recording' };
|
||||
|
||||
@@ -97,19 +97,19 @@ export class MissedQueueService implements OnModuleInit {
|
||||
|
||||
const existing = await this.platform.query<any>(
|
||||
`{ calls(first: 1, filter: {
|
||||
callbackstatus: { eq: PENDING_CALLBACK },
|
||||
callbackStatus: { eq: PENDING_CALLBACK },
|
||||
callerNumber: { primaryPhoneNumber: { eq: "${phone}" } }
|
||||
}) { edges { node { id missedcallcount } } } }`,
|
||||
}) { edges { node { id missedCallCount } } } }`,
|
||||
);
|
||||
|
||||
const existingNode = existing?.calls?.edges?.[0]?.node;
|
||||
|
||||
if (existingNode) {
|
||||
const newCount = (existingNode.missedcallcount || 1) + 1;
|
||||
const newCount = (existingNode.missedCallCount || 1) + 1;
|
||||
const updateParts = [
|
||||
`missedcallcount: ${newCount}`,
|
||||
`missedCallCount: ${newCount}`,
|
||||
`startedAt: "${callTime}"`,
|
||||
`callsourcenumber: "${did}"`,
|
||||
`callSourceNumber: "${did}"`,
|
||||
];
|
||||
if (leadId) updateParts.push(`leadId: "${leadId}"`);
|
||||
if (leadName) updateParts.push(`leadName: "${leadName}"`);
|
||||
@@ -123,9 +123,9 @@ export class MissedQueueService implements OnModuleInit {
|
||||
`callStatus: MISSED`,
|
||||
`direction: INBOUND`,
|
||||
`callerNumber: { primaryPhoneNumber: "${phone}", primaryPhoneCallingCode: "+91" }`,
|
||||
`callsourcenumber: "${did}"`,
|
||||
`callbackstatus: PENDING_CALLBACK`,
|
||||
`missedcallcount: 1`,
|
||||
`callSourceNumber: "${did}"`,
|
||||
`callbackStatus: PENDING_CALLBACK`,
|
||||
`missedCallCount: 1`,
|
||||
`startedAt: "${callTime}"`,
|
||||
];
|
||||
if (leadId) dataParts.push(`leadId: "${leadId}"`);
|
||||
@@ -160,12 +160,12 @@ export class MissedQueueService implements OnModuleInit {
|
||||
// Find oldest unassigned PENDING_CALLBACK call (empty agentName)
|
||||
let result = await this.platform.query<any>(
|
||||
`{ calls(first: 1, filter: {
|
||||
callbackstatus: { eq: PENDING_CALLBACK },
|
||||
callbackStatus: { eq: PENDING_CALLBACK },
|
||||
agentName: { eq: "" }
|
||||
}, orderBy: [{ startedAt: AscNullsLast }]) {
|
||||
edges { node {
|
||||
id callerNumber { primaryPhoneNumber }
|
||||
startedAt callsourcenumber missedcallcount
|
||||
startedAt callSourceNumber missedCallCount
|
||||
} }
|
||||
} }`,
|
||||
);
|
||||
@@ -176,12 +176,12 @@ export class MissedQueueService implements OnModuleInit {
|
||||
if (!call) {
|
||||
result = await this.platform.query<any>(
|
||||
`{ calls(first: 1, filter: {
|
||||
callbackstatus: { eq: PENDING_CALLBACK },
|
||||
callbackStatus: { eq: PENDING_CALLBACK },
|
||||
agentName: { is: NULL }
|
||||
}, orderBy: [{ startedAt: AscNullsLast }]) {
|
||||
edges { node {
|
||||
id callerNumber { primaryPhoneNumber }
|
||||
startedAt callsourcenumber missedcallcount
|
||||
startedAt callSourceNumber missedCallCount
|
||||
} }
|
||||
} }`,
|
||||
);
|
||||
@@ -209,13 +209,13 @@ export class MissedQueueService implements OnModuleInit {
|
||||
throw new Error(`Invalid status: ${status}. Must be one of: ${validStatuses.join(', ')}`);
|
||||
}
|
||||
|
||||
const dataParts: string[] = [`callbackstatus: ${status}`];
|
||||
const dataParts: string[] = [`callbackStatus: ${status}`];
|
||||
if (status === 'CALLBACK_ATTEMPTED') {
|
||||
dataParts.push(`callbackattemptedat: "${new Date().toISOString()}"`);
|
||||
dataParts.push(`callbackAttemptedAt: "${new Date().toISOString()}"`);
|
||||
}
|
||||
|
||||
return this.platform.queryWithAuth<any>(
|
||||
`mutation { updateCall(id: "${callId}", data: { ${dataParts.join(', ')} }) { id callbackstatus callbackattemptedat } }`,
|
||||
`mutation { updateCall(id: "${callId}", data: { ${dataParts.join(', ')} }) { id callbackStatus callbackAttemptedAt } }`,
|
||||
undefined,
|
||||
authHeader,
|
||||
);
|
||||
@@ -230,12 +230,12 @@ export class MissedQueueService implements OnModuleInit {
|
||||
const fields = `id name createdAt direction callStatus agentName
|
||||
callerNumber { primaryPhoneNumber }
|
||||
startedAt endedAt durationSec disposition leadId
|
||||
callbackstatus callsourcenumber missedcallcount callbackattemptedat`;
|
||||
callbackStatus callSourceNumber missedCallCount callbackAttemptedAt`;
|
||||
|
||||
const buildQuery = (status: string) => `{ calls(first: 50, filter: {
|
||||
agentName: { eq: "${agentName}" },
|
||||
callStatus: { eq: MISSED },
|
||||
callbackstatus: { eq: ${status} }
|
||||
callbackStatus: { eq: ${status} }
|
||||
}, orderBy: [{ startedAt: AscNullsLast }]) { edges { node { ${fields} } } } }`;
|
||||
|
||||
try {
|
||||
|
||||
@@ -97,13 +97,13 @@ export class WorklistService {
|
||||
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 {
|
||||
`{ 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
|
||||
callbackStatus callSourceNumber missedCallCount callbackAttemptedAt
|
||||
} } } }`,
|
||||
undefined,
|
||||
authHeader,
|
||||
|
||||
Reference in New Issue
Block a user