mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage-server
synced 2026-04-11 18:08:16 +00:00
feat: Phase 2 — missed call queue ingestion, auto-assignment, endpoints
- MissedQueueService: polls Ozonetel abandonCalls every 30s, dedup by phone - Auto-assigns oldest PENDING_CALLBACK call on agent Ready (dispose + state change) - GET /api/worklist/missed-queue, PATCH /api/worklist/missed-queue/:id/status - Worklist query updated with callback fields and FIFO ordering - PlatformGraphqlService.query() made public for server-to-server ops - forwardRef circular dependency resolution between WorklistModule and OzonetelAgentModule Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import { Controller, Post, Get, Body, Query, Logger, HttpException } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { OzonetelAgentService } from './ozonetel-agent.service';
|
||||
import { MissedQueueService } from '../worklist/missed-queue.service';
|
||||
import { PlatformGraphqlService } from '../platform/platform-graphql.service';
|
||||
|
||||
@Controller('api/ozonetel')
|
||||
export class OzonetelAgentController {
|
||||
@@ -13,6 +15,8 @@ export class OzonetelAgentController {
|
||||
constructor(
|
||||
private readonly ozonetelAgent: OzonetelAgentService,
|
||||
private readonly config: ConfigService,
|
||||
private readonly missedQueue: MissedQueueService,
|
||||
private readonly platform: PlatformGraphqlService,
|
||||
) {
|
||||
this.defaultAgentId = config.get<string>('OZONETEL_AGENT_ID') ?? 'agent3';
|
||||
this.defaultAgentPassword = config.get<string>('OZONETEL_AGENT_PASSWORD') ?? '';
|
||||
@@ -74,6 +78,18 @@ export class OzonetelAgentController {
|
||||
const message = error.response?.data?.message ?? error.message ?? 'State change failed';
|
||||
return { status: 'error', message };
|
||||
}
|
||||
|
||||
// Auto-assign missed call when agent goes Ready
|
||||
if (body.state === 'Ready') {
|
||||
try {
|
||||
const assigned = await this.missedQueue.assignNext(this.defaultAgentId);
|
||||
if (assigned) {
|
||||
return { status: 'ok', message: `State changed to Ready. Assigned missed call ${assigned.id}`, assignedCall: assigned };
|
||||
}
|
||||
} catch (err) {
|
||||
this.logger.warn(`Auto-assignment on Ready failed: ${err}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Post('agent-ready')
|
||||
@@ -109,6 +125,7 @@ export class OzonetelAgentController {
|
||||
durationSec?: number;
|
||||
leadId?: string;
|
||||
notes?: string;
|
||||
missedCallId?: string;
|
||||
},
|
||||
) {
|
||||
if (!body.ucid || !body.disposition) {
|
||||
@@ -125,13 +142,40 @@ export class OzonetelAgentController {
|
||||
ucid: body.ucid,
|
||||
disposition: ozonetelDisposition,
|
||||
});
|
||||
return result;
|
||||
} catch (error: any) {
|
||||
const message = error.response?.data?.message ?? error.message ?? 'Disposition failed';
|
||||
this.logger.error(`Dispose failed: ${message}`);
|
||||
// Don't throw — disposition failure shouldn't block the UI
|
||||
return { status: 'error', message };
|
||||
}
|
||||
|
||||
// Handle missed call callback status update
|
||||
if (body.missedCallId) {
|
||||
const statusMap: Record<string, string> = {
|
||||
APPOINTMENT_BOOKED: 'CALLBACK_COMPLETED',
|
||||
INFO_PROVIDED: 'CALLBACK_COMPLETED',
|
||||
FOLLOW_UP_SCHEDULED: 'CALLBACK_COMPLETED',
|
||||
CALLBACK_REQUESTED: 'CALLBACK_COMPLETED',
|
||||
WRONG_NUMBER: 'WRONG_NUMBER',
|
||||
};
|
||||
const newStatus = statusMap[body.disposition];
|
||||
if (newStatus) {
|
||||
try {
|
||||
await this.platform.query<any>(
|
||||
`mutation { updateCall(id: "${body.missedCallId}", data: { callbackstatus: ${newStatus} }) { id } }`,
|
||||
);
|
||||
} catch (err) {
|
||||
this.logger.warn(`Failed to update missed call status: ${err}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-assign next missed call to this agent
|
||||
try {
|
||||
await this.missedQueue.assignNext(this.defaultAgentId);
|
||||
} catch (err) {
|
||||
this.logger.warn(`Auto-assignment after dispose failed: ${err}`);
|
||||
}
|
||||
|
||||
return { status: 'ok' };
|
||||
}
|
||||
|
||||
@Post('dial')
|
||||
|
||||
Reference in New Issue
Block a user