Como Criar um Script de Processamento de Chamadas

Introdução

Os scripts de processamento de chamadas, também chamados de Route Point, são um novo e poderoso recurso da V20. Eles permitem que você capture chamadas e as processe usando o código C# padrão, o que basicamente lhe dá possibilidades ilimitadas de analisar uma chamada e aplicar lógica personalizada. Aqui estão alguns exemplos:

  • Analisar a ID do autor da chamada e atribuir agentes específicos
  • Fazer pesquisas de clientes com base na ID do autor da chamada e encaminhar de acordo
  • Verificar a hora e a data e processar uma chamada de acordo.
  • Verificar a data e reproduzir um prompt com base na data.

Chamada de um Script de Processamento de Chamadas

Antes de escrever um script, você deve pensar onde e como deseja que esse script seja acionado. Você pode configurar um código de discagem para ele ou encaminhar chamadas para ele atribuindo-lhe um DID ou encaminhando-as para o script com base em uma determinada variável.

Na atualização 2, você poderá acionar a chamada em cada chamada de entrada que chegar a um tronco SIP!

Depois de decidir como o script será acionado, você precisará filtrar as chamadas e escrever sua lógica de processamento de chamadas.

Visão Geral da API de Processamento de Chamadas

A API consiste em três métodos principais que estão passando chamadas para o novo destino.

Grupo de método Task<CallControlResult> RouteToAsync(this ActiveConnection ac,<Destino>)

Esse método cria uma rota que está vinculada à conexão especificada ac

  • ac - a conexão ativa de propriedade da RoutePoint. Quando o novo destino responde, sua conexão substitui ac. (participação do RoutePoint).
  • O script deve lidar com falhas na tarefa. O script pode simplesmente chamar MyCall.Return para encerrar sua própria conexão com o chamador.
  • O script pode tentar criar quantas rotas forem necessárias, mas a primeira rota respondida cancelará todas as outras e substituirá a participação do RoutePoint na chamada (encerrará a chamada para o RoutePoint).
  • O RouteToAsync pode ser executado em qualquer estado da conexão com o RoutePoint. Portanto, o RoutePoint pode executar o roteamento em segundo plano ao se comunicar com o chamador (reproduzir prompts, manipular DTMFs etc.).
  • Quando a tarefa é bem-sucedida, a chamada é revogada do RoutePoint (MyCall é desconectada) e o novo destino continua a ser tratado.

Grupo de método Task<CallControlResult> DivertAsync(this ActiveConnection ac,<Destino>)

Esse método desvia a chamada para o novo destino sem estabelecer (atender) a conexão (toque no RoutePoint).

  • Se uma chamada já estiver estabelecida com o RoutePoint, o método falhará e o RouteToAsyc/ReplaceWithAsync deverá ser usado
  • A conexão ativa (em estado de toque) pertencente ao RoutePoint será substituída por um novo destino e o RoutePoint será desconectado da chamada.
  • Esse método é útil se o ponto de rota não precisar interagir com o chamador.
  • Se uma tarefa tiver falhado, o script poderá continuar a lidar com a conexão com o chamador.
  • Quando a tarefa é bem-sucedida, a chamada é revogada do RoutePoint (a conexão MyCall é encerrada) e o novo destino inicia o tratamento da chamada. O script do RoutePoint é alternado para o modo Wrap Up e precisa concluir seu próprio trabalho.

Grupo de método Task<CallControlResult> ReplaceWithAsync(this ActiveConnection ac,<Destino>)

Também conhecido como método de "transferência cega".

  • Permitido somente no modo conectado (o RoutePoint aceitou a chamada e interage com o usado).
  • O autor da chamada será colocado em espera.
  • A tarefa falhará se o destino não puder ser alcançado.
  • O script pode continuar processando a chamada após a falha da tarefa (se o chamador ainda estiver conectado ao RoutePoint).
  • Quando uma tarefa é bem-sucedida, a conexão do script (ICallHandler.MyCall) é encerrada e o script é separado do tratamento da chamada (o novo participante cuidará do chamador).

Exemplo de Script de Processamento de Chamadas

Este exemplo demonstra como criar um ponto de rota personalizado e como programá-lo:

  1. Estrutura básica do código C# fornecido à RoutePoint
  2. Uso básico dos métodos de extensão TCX.PBXAPI.CallControlAPI para a interface ActiveConnection (ICall)
  3. Trabalho básico com o objeto MyCall fornecido pelo CallFlowScriptingCore
  4. Trabalho básico com a configuração (parâmetros do PABX)
  5. Uso do método de extensão RouteToAsyc do objeto ActiveConnection

Um exemplo da funcionalidade do RoutePoint:

  • O ponto de rota está aceitando apenas chamadas enviadas por meio de transferência cega do telefone (Ramal). As chamadas diretas são rejeitadas.
  • Chamadas ilimitadas podem ser transferidas para esse RoutePoint por qualquer ramal simultaneamente (cada chamada é tratada separadamente)
  • A chamada é retornada após 15 segundos para o transferidor (ramal) diretamente (sem encaminhamento aplicado). Se a chamada retornada não for atendida em 15 segundos, a chamada será cancelada e repetida novamente em 15 segundos.
  • O chamador ouve música em espera, conforme configurado para Parking no PABX.

Como executá-lo

  • Crie um RoutingPoint com qualquer número - por exemplo, 101 - e defina a propriedade RoutePoint.ScriptCode como o texto abaixo (a interface do usuário ainda não permite criar um ponto de roteamento com código personalizado (escrito à mão); é necessário algum tipo de arquivo zip, que não é realmente necessário para um script simples)
  • O RoutePoint deve aparecer na lista correspondente ("aplicativos CFD" neste momento) com um ponto verde [a compilação não deve falhar para esse código])
  • Em seguida:
  • Se algum ramal transferir sua chamada para o número #101, a chamada será retornada em 15 segundos.
  • O chamador ouvirá música em espera, pois ela está configurada para estacionamento. (O script usa essa configuração, mas o código pode ser modificado para gerar outro conteúdo para o chamador).
  • Se a chamada retornada não for atendida, o RoutePoint tentará novamente (e novamente) 15 segundos após a tentativa anterior até que o chamador não abandone a chamada ou o transferidor original atenda (ou sua chamada seja capturada).

Comentários sobre o código

  • O código do objeto "com script" é baseado em (usa, implementa e/ou herda)
  • Espaço de Nome CallFlow
  • CallFlow.ICall
  • CallFlow.ICallHandler
  • CallFlow.ICallHandlerEx
  • CallFlow.ScriptBase<T>
  • A classe "Objeto de Script" deve herdar CallFlow.ScriptBase<T> e implementar todos os métodos abstratos necessários para sua instância.
  • O objeto está em execução quando o ScriptingHost executa o manipulador de chamadas usando o método ICallHandler.Start.
  • O script deve ser encerrado explicitamente com ICall.Return.
  • A implementação preferencial do método ICall.Start é "async void", que executa uma tarefa separada (deve capturar todas as exceções).
  • A implementação do script deve controlar apenas o objeto MyCall exposto pelo Scripting Host. Ele é o único objeto da sessão do script de chamada.
  • Quando o MyCall (participação do ponto de rota na chamada) for encerrado, a implementação do script deverá encerrar e terminar sua sessão.
  • O script de fluxo de chamadas não é uma forma de monitorar a configuração do sistema ou quaisquer recursos externos.
  • Não se trata de uma forma de monitorar todas as chamadas no sistema.
  • É apenas uma lógica do RoutePoint, que pode ser integrada a outros fluxos de chamadas.
  • Em outras palavras: o script lida com uma das chamadas que estão conectadas ao RoutePoint, mas nunca inicia uma nova chamada.
  • A API de encaminhamento é encapsulada na classe estática TCX.PBXAPI.CallControlAPI, que expõe métodos de extensão para
  • TCX.Configuration.ActiveCannection
  • TCX.Configuration.DN
  • TCX.Configuration.RegistrarRecord

Amostra de Código

#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);

                }

            });

        }

    }

}

Veja Também

Última Atualização

Este documento foi atualizado pela última vez em 5 de março de 2024

https://www.3cx.com.br/docs/manual/script-processamento-chamadas/