fix: skip outbound calls in webhook + filter abandon polls by campaign

Webhook controller now skips outbound calls (type=Manual/OutBound).
An unanswered outbound dial is NOT a missed inbound call — it was
being incorrectly created as MISSED with PENDING_CALLBACK status.

MissedQueueService now filters the Ozonetel abandonCalls API response
by campaign name (read from TelephonyConfigService). Prevents
cross-tenant ingestion when multiple sidecars share the same
Ozonetel account.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-10 16:03:48 +05:30
parent 9665500b63
commit 96d0c32000
4 changed files with 40 additions and 3 deletions

View File

@@ -2,6 +2,7 @@ import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { PlatformGraphqlService } from '../platform/platform-graphql.service';
import { OzonetelAgentService } from '../ozonetel/ozonetel-agent.service';
import { TelephonyConfigService } from '../config/telephony-config.service';
// Ozonetel sends all timestamps in IST — convert to UTC for storage
export function istToUtc(istDateStr: string | null): string | null {
@@ -33,10 +34,16 @@ export class MissedQueueService implements OnModuleInit {
private readonly config: ConfigService,
private readonly platform: PlatformGraphqlService,
private readonly ozonetel: OzonetelAgentService,
private readonly telephony: TelephonyConfigService,
) {
this.pollIntervalMs = this.config.get<number>('missedQueue.pollIntervalMs', 30000);
}
// Read-through so admin config changes take effect without restart
private get ownCampaign(): string {
return this.telephony.getConfig().ozonetel.campaignName ?? '';
}
onModuleInit() {
this.logger.log(`Starting missed call ingestion polling every ${this.pollIntervalMs}ms`);
setInterval(() => this.ingest().catch(err => this.logger.error('Ingestion failed', err)), this.pollIntervalMs);
@@ -61,7 +68,17 @@ export class MissedQueueService implements OnModuleInit {
if (!abandonCalls?.length) return { created: 0, updated: 0 };
for (const call of abandonCalls) {
// Filter to this sidecar's campaign only — the Ozonetel API
// returns ALL abandoned calls across the account.
const filtered = this.ownCampaign
? abandonCalls.filter((c: any) => c.campaign === this.ownCampaign)
: abandonCalls;
if (filtered.length < abandonCalls.length) {
this.logger.log(`Filtered ${abandonCalls.length - filtered.length} calls from other campaigns (own=${this.ownCampaign})`);
}
for (const call of filtered) {
const ucid = call.monitorUCID;
if (!ucid || this.processedUcids.has(ucid)) continue;
this.processedUcids.add(ucid);