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:
2026-03-23 09:17:33 +05:30
parent 4963a698d9
commit cec2526d37
8 changed files with 310 additions and 10 deletions

View File

@@ -1,6 +1,7 @@
import { Controller, Get, Headers, HttpException, Logger } from '@nestjs/common';
import { Controller, Get, Patch, Headers, Param, Body, HttpException, Logger } from '@nestjs/common';
import { PlatformGraphqlService } from '../platform/platform-graphql.service';
import { WorklistService } from './worklist.service';
import { MissedQueueService } from './missed-queue.service';
@Controller('api/worklist')
export class WorklistController {
@@ -8,6 +9,7 @@ export class WorklistController {
constructor(
private readonly worklist: WorklistService,
private readonly missedQueue: MissedQueueService,
private readonly platform: PlatformGraphqlService,
) {}
@@ -23,6 +25,24 @@ export class WorklistController {
return this.worklist.getWorklist(agentName, authHeader);
}
@Get('missed-queue')
async getMissedQueue(@Headers('authorization') authHeader: string) {
if (!authHeader) throw new HttpException('Authorization header required', 401);
const agentName = await this.resolveAgentName(authHeader);
return this.missedQueue.getMissedQueue(agentName, authHeader);
}
@Patch('missed-queue/:id/status')
async updateMissedCallStatus(
@Param('id') id: string,
@Headers('authorization') authHeader: string,
@Body() body: { status: string },
) {
if (!authHeader) throw new HttpException('Authorization header required', 401);
if (!body.status) throw new HttpException('status is required', 400);
return this.missedQueue.updateStatus(id, body.status, authHeader);
}
private async resolveAgentName(authHeader: string): Promise<string> {
try {
const data = await this.platform.queryWithAuth<any>(