Come creare uno script di elaborazione delle chiamate

Introduzione

Gli script di elaborazione delle chiamate sono una nuova e potente funzionalità di V20. Consentono di catturare le chiamate e di elaborarle utilizzando codice C# standard, offrendo in sostanza possibilità illimitate di analizzare una chiamata e di applicare una logica personalizzata. Ecco un paio di esempi:

  • Analizzare l'ID del chiamante e assegnare agenti specifici
  • Effettuare ricerche sui clienti in base all'ID del chiamante e instradare di conseguenza le chiamate
  • Controllare l'ora e la data ed elaborare una chiamata di conseguenza.
  • Controllare la data e riprodurre un messaggio in base alla data.

Panoramica dell'API di elaborazione delle chiamate

L'API è composta da tre metodi principali che passano le chiamate alla nuova destinazione.

Metodo gruppo Task<CallControlResult> RouteToAsync(this ActiveConnection ac,<Destinazione>)

Questo metodo crea una rotta legata alla connessione specificata ac

  • ac - la connessione attiva di proprietà di RoutePoint. Quando la nuova destinazione risponde, la sua connessione sostituisce ac (partecipazione di RoutePoint).
  • Lo script deve gestire i fallimenti delle attività. Lo script può semplicemente chiamare MyCall.Return per terminare la propria connessione con il chiamante.
  • Lo script può provare a costruire tutte le rotte che desidera, ma la prima rotta che risponde annullerà tutte le altre e sostituirà la partecipazione di RoutePoint alla chiamata (terminerà la chiamata per RoutePoint).
  • RouteToAsync può essere eseguito in qualsiasi stato della connessione a RoutePoint. Pertanto, il route point può eseguire il routing in background durante la comunicazione con il chiamante (riprodurre prompt, gestire DTMF, ecc.).
  • Quando l'operazione ha successo, la chiamata viene revocata da RoutePoint (MyCall viene scollegata) e la nuova destinazione continua la gestione.

Gruppo di metodi Task<CallControlResult> DivertAsync(this ActiveConnection ac,<Destination>)

Questo metodo devia la chiamata verso la nuova destinazione senza stabilire (rispondendo) la connessione (squillo su RoutePoint).

  • Se è già stata stabilita una chiamata con RoutePoint, il metodo fallirà e si dovrà utilizzare RouteToAsyc/ReplaceWithAsync.
  • La connessione attiva (in stato di squillo) di proprietà del RoutePoint sarà sostituita con una nuova destinazione e il RoutePoint sarà disconnesso dalla chiamata.
  • Questo metodo è utile se il route point non ha bisogno di interagire con il chiamante.
  • Se un'attività è fallita, lo script può continuare a gestire la connessione con il chiamante.
  • Quando il compito ha successo, la chiamata viene revocata da RoutePoint (la connessione MyCall viene terminata) e la nuova destinazione inizia a gestire la chiamata. Lo script RoutePoint passa alla modalità Wrap Up e deve terminare il proprio lavoro.

Gruppo di metodi Task<CallControlResult> ReplaceWithAsync(this ActiveConnection ac,<Destination>)

Conosciuto anche come metodo di "trasferimento cieco".

  • Consentito solo in modalità connessa (RoutePoint ha accettato la chiamata e interagisce con l'utente).
  • Il chiamante viene messo in attesa.
  • L'attività fallirà se la destinazione non è raggiungibile.
  • Lo script può continuare a elaborare la chiamata dopo il fallimento dell'attività (se il chiamante è ancora connesso con RoutePoint).
  • Quando un'attività ha successo, la connessione dello script (ICallHandler.MyCall) viene terminata e lo script si distacca dalla gestione della chiamata (un nuovo partecipante si occuperà del chiamante).

Esempio di script di elaborazione delle chiamate

Questo esempio mostra come creare un punto di percorso personalizzato e come programmarlo:

  1. Struttura di base del codice C# fornito a RoutePoint
  2. L'uso di base dei metodi di estensione TCX.PBXAPI.CallControlAPI per l'interfaccia ActiveConnection (ICall)
  3. Lavoro di base con l'oggetto MyCall fornito da CallFlowScriptingCore
  4. Lavoro di base con la configurazione (parametri PBX)
  5. Utilizzo del metodo di estensione RouteToAsyc dell'oggetto ActiveConnection

Un esempio di funzionalità di RoutePoint:

  • Il punto di instradamento accetta solo le chiamate inviate tramite trasferimento cieco dal telefono (interno). Le chiamate dirette vengono rifiutate.
  • Un numero illimitato di chiamate può essere trasferito a questo RoutePoint da qualsiasi interno contemporaneamente (ogni chiamata viene gestita separatamente).
  • La chiamata viene restituita dopo 15 secondi al trasferente (interno) direttamente (senza inoltro). Se la chiamata restituita non riceve risposta entro 15 secondi, la chiamata viene annullata e ripetuta entro 15 secondi.
  • Il chiamante sente la musica in attesa come è configurato per il parcheggio sul PBX.

Come si esegue

  • Creare un punto di instradamento con un numero qualsiasi, ad esempio 101, e impostare la proprietà RoutePoint.ScriptCode sul testo seguente (l'interfaccia utente non consente ancora di creare punti di instradamento con codice personalizzato (scritto a mano), ma richiede una sorta di file zip, che non è necessario per un semplice script).
  • RoutePoint dovrebbe apparire nell'elenco corrispondente ("Applicazioni CFD" in questo momento) con un punto verde [la compilazione non dovrebbe fallire per questo codice]).
  • Quindi:
  • Se un interno trasferisce la propria chiamata al numero #101, la chiamata verrà restituita entro 15 secondi.
  • Il chiamante sentirà la musica in attesa come è configurato per il parcheggio. (Lo script utilizza questa impostazione, ma il codice può essere modificato per generare altri contenuti per il chiamante).
  • Se la chiamata restituita non riceve risposta, il RoutePoint riprova (e ancora) 15 secondi dopo il tentativo precedente, finché il chiamante non abbandona la chiamata o il trasferente originale risponde (o la sua chiamata viene recuperata).

Commenti per il codice

  • Il codice dell'oggetto "scripted" si basa su (usa, implementa e/o eredita)
  • Spazio dei nomi CallFlow
  • CallFlow.ICall
  • CallFlow.ICallHandler
  • CallFlow.ICallHandlerEx
  • CallFlow.ScriptBase<T>
  • La classe "oggetto script" deve ereditare CallFlow.ScriptBase<T> e implementare tutti i metodi astratti richiesti per la sua istanza.
  • L'oggetto è in esecuzione quando lo ScriptingHost esegue il gestore di chiamate con il metodo ICallHandler.Start.
  • Lo script deve essere terminato esplicitamente con ICall.Return.
  • L'implementazione preferita del metodo ICall.Start è "async void" che esegue un'attività distaccata (deve catturare tutte le eccezioni).
  • L'implementazione dello script deve controllare solo l'oggetto MyCall esposto dall'host di scripting. È l'unico oggetto della sessione di script di chiamata.
  • Quando MyCall (la partecipazione del punto di passaggio alla chiamata) termina, l'implementazione dello script deve concludere la sessione.
  • Lo script del flusso di chiamata non è un modo per monitorare la configurazione del sistema o qualsiasi risorsa esterna.
  • Non è un modo per monitorare tutte le chiamate nel sistema.
  • È solo una logica di RoutePoint, che può essere integrata con altri flussi di chiamate.
  • In altre parole, lo script gestisce una delle chiamate collegate a RoutePoint, ma non avvia mai una nuova chiamata.
  • L'API di routing è incapsulata nella classe statica TCX.PBXAPI.CallControlAPI che espone metodi di estensione per
  • TCX.Configuration.ActiveCannection
  • TCX.Configuration.DN
  • TCX.Configuration.RegistrarRecord

Esempio del codice

#nullable disable

using CallFlow;

using System;

using System.Threading;

using System.Threading.Tasks;

using TCX.Configuration;

using TCX.PBXAPI;

namespace dummy

{

    public class ParkingRoutePointSample : ScriptBase<ParkingRoutePointSample>

    {

        async Task<CallControlResult> ProcessAutoPickup(RoutePoint sp, DestinationStruct returnTo, CancellationToken token)

        {

            while (true)

                try

                {

                    return await Task.Delay(TimeSpan.FromSeconds(15), token).ContinueWith(x =>

                    {

                        MyCall.Trace("{0} - automatic redirection of the call from {1}.{2} to '{3}'", MyCall.DN, MyCall.Caller?.CallerID, MyCall.Caller?.DN, returnTo);

                        return MyCall.RouteToAsync(new RouteRequest

                        {

                            RouteTarget = returnTo,

                            TimeOut = TimeSpan.FromSeconds(15) //will ring until failure

                        }

                        );

                    }

                    , TaskContinuationOptions.NotOnCanceled).Unwrap();

                }

                catch (OperationFailed ex)

                {

                    MyCall.Trace("Automatic redirection failed: {0}", ex.TheResult);

                    MyCall.Trace("Continue hold call from {0}({1}) on {2}", MyCall.Caller?.CallerID, MyCall.Caller?.DN, MyCall.DN);

                    continue;

                }

        }

        PhoneSystem ps = null;  

        /// <summary>

        ///

        /// </summary>

        public override async void Start()

        {

            await Task.Run(async () =>

            {

                try

                {

                    MyCall.Debug($"Script start delay: {DateTime.UtcNow - MyCall.LastChangeStatus}");

                    MyCall.Debug($"Incoming connection {MyCall}");

                    ps = MyCall.PS as PhoneSystem;

                    CallControlResult lastresult = null;

                    DN referredBy = null;

                    RoutePoint thisPark = null;

                    string callerID = "";

                    DN callerDN = null;

                    bool scriptCompleted = true;

                    try

                    {

                        referredBy = MyCall.ReferredByDN?.GetFullSnapshot() as Extension;

                        thisPark = MyCall.DN?.Clone() as RoutePoint;

                        callerID = MyCall.Caller?.CallerID;

                        callerDN = MyCall.Caller?.DN?.Clone() as DN;

                        MyCall.Trace(

                            "Parked call from {0}({1}) on {2}", callerID, callerDN, thisPark

                        );

                        if (referredBy == null)

                        {

                            MyCall.Trace("{0} rejects call from {1}. Reason: No referrer specified", thisPark, callerDN);

                            return;

                        }

                        var cancelationToken = new CancellationTokenSource();

                        MyCall.OnTerminated += () =>

                        {

                            cancelationToken.Cancel();

                        };

                        lastresult = await MyCall.AssureMedia().ContinueWith(

                            x =>

                            {

                                if(!string.IsNullOrWhiteSpace(ps.GetParameterValue("PARK_MOH_SOURCE")))

                                    MyCall.SetBackgroundAudio(true, new string[] { ps.GetParameterValue("PARK_MOH_SOURCE") });

                                else

                                    MyCall.SetBackgroundAudio(true, new string[] { ps.GetParameterValue("MUSICONHOLDFILE") });

                                return ProcessAutoPickup(thisPark, new DestinationStruct(referredBy), cancelationToken.Token);

                            }, TaskContinuationOptions.OnlyOnRanToCompletion).Unwrap();

                    }

                    catch (PBXIsNotConnected ex)

                    {

                        MyCall.Error($"Call control API is not available:\n{ex}");

                        scriptCompleted = false;

                    }

                    catch (TaskCanceledException)

                    {

                        MyCall.Trace($"Call was disconnected from parking place");

                    }

                    catch (Exception ex)

                    {

                        MyCall.Error($"Parking failure:\n{ex}");

                        scriptCompleted = false;

                    }

                    finally

                    {

                        try

                        {

                            MyCall.Info("Call from {0}({1}) parked by {2} on {3} finished with result={4}", callerID, callerDN, referredBy, thisPark, lastresult?.ToString() ?? "terminated");

                        }

                        catch (Exception ex)

                        {

                            MyCall.Error($"SharedParkingFlow finalize exception {ex}");

                        }

                        MyCall.Return(scriptCompleted);

                    }

                }

                catch

                {

                    MyCall.Return(false);

                }

            });

        }

    }

}

Per saperne di più

Ultimo aggiornamento

Questo documento è stato aggiornato il 5 marzo 2024.

https://www.3cx.it/doc/manuale/script-elaborazione-chiamate/