Resultado da chamada

Cartão de resultado da chamada

Após o término da conversa, os gestores podem receber a opção de fornecer um resultado da chamada. A janela modal de resultado da chamada exibe informações sobre o chamador e inclui opções para selecionar a entidade (lead, contato ou empresa) à qual a chamada será associada, anexar a gravação da chamada, adicionar uma tarefa com horário específico e deixar anotações sobre a conversa, caso existam. Se o gestor não atender a chamada, um registro com duração zero pode ser salvo. A aparência da janela de resultado da chamada é controlada pela parte JS do widget, enquanto a atualização real da chamada é feita pelo backend da integração.

Exemplo da janela de resultado da chamada:

A janela modal pode consistir nos seguintes elementos:

  • Nome da janela: Resumo da chamada
  • Entidade vinculada (lead/contato/empresa)
  • Título do lead, que é um campo com propriedade de busca para localizar o lead/cliente adequado
  • Painel da gravação da chamada (indicando a duração da chamada e um botão de reprodução)
  • Horário e anotação para uma tarefa
  • Botões Salvar ou Cancelar

Template para renderizar sua janela modal de Resultado da chamada:

import Modal from "lib/components/base/modal";
import markup from "./markup.js";

const appendCallResultModal = () => {
  /**
   * Você pode usar a classe Modal para abrir uma janela modal.
   */
  const modal = new Modal({
    class_name: "modal-window",
    init: function ($modal_body) {
      $modal_body
        /**
         * Aciona a exibição da janela modal.
         */
        .trigger("modal:loaded")
        /**
         * Exibe seu markup.
         */
        .html(markup)
        /**
         * Configura a janela modal.
         */
        .trigger("modal:centrify");
    },
    destroy: () => {},
  });

  /**
   *  Aqui entra a lógica da sua janela modal.
   */
};

Após selecionar a entidade e preencher os campos, você deve salvar o resultado da chamada, atualizando a chamada ao alterar o vínculo para a entidade escolhida. Para isso, uma tarefa é criada e adicionada à fila.

Vamos considerar o seguinte cenário de exemplo:

  1. Uma chamada de um contato existente é realizada.

  2. Um webhook sobre o término da chamada é disparado pelo serviço VoIP. Em seguida, o backend da integração precisa encontrar a entidade apropriada para vincular a chamada ou criar um Lead de Entrada caso nenhuma entidade conectada ao número exista em sua conta da Kommo.

  3. O gestor preenche o modal de resultado da chamada e seleciona uma entidade diferente daquela sugerida pelo algoritmo. A anotação deve ser vinculada à entidade selecionada, e o vínculo com a entidade sugerida anteriormente deve ser removido pela integração.

  4. O backend da integração VoIP então envia uma Nota de Chamada para vincular a nota ao cartão do contato selecionado.

Como mencionado no artigo Registro de chamadas, todas as chamadas devem ser recuperadas do serviço VoIP e armazenadas no repositório de chamadas dentro do banco de dados da integração. Também implementamos uma função para recuperar uma chamada com base em seus identificadores:

public static function getByCallIdAndKommoAccountId(string $callId, int $kommoAccountId): VoipCalls {
    return VoipCalls::query() 
        ->where('call_id', '=', $callId)
        ->where('kommo_account_id', '=', $kommoAccountId)
        ->first();
}

Uma tarefa para atualizar a chamada de acordo com as informações vindas da janela modal de resultado da chamada:

public function __construct(
    private int $kommoAccountId,
    private string $callId,
    private int $entityId,
    private string $entityType
);

Um exemplo do caso de uso responsável por atualizar a chamada conforme as informações vindas da janela modal de resultado da chamada é explicado aqui:

public function handle(UpdateFromModalTask $task): void
    {
        $widgetSettings = WidgetSettingsRepository::getByKommoAccountId($task->getKommoAccountId());
        $voipCall = VoipCalls::getByCallIdAndKommoAccountId(
            $task->getCallId(),
            $widgetSettings->getKommoAccountId()
        );
        $call = Call::fromModel($voipCall);
        if ($record = $voipCall->getRecording() ?? '') {
            $record = sprintf(
                '%s/voip/%s/get_call_record/%s',
                $this->appConfig->getBaseUrl(),
                $widgetSettings->getKommoAccountId(),
                $voipCall->getCallId()
            );
        }
        $call->setRecordLink($record);
        $unsorted = null;
        /** Se a chamada vem de leads de entrada:
        * a) Se o gerente escolhe a entidade Lead, você precisa vincular um contato a ele;
        * b) Se o gerente escolhe a entidade de Contato/Empresa, você precisa pegar à partir do contato no lead de entrada.
        */
        if ($voipCall->getUnsortedUid() !== null) {
                $unsorted = $this->unsortedService
                ->findUnsortedByUid($voipCall->getUnsortedUid());
                $unsortedContact = $unsorted->getContacts()?->first();

                $entityType = KommoEntityType::tryFrom($task->getEntityType());
                switch ($entityType) {
                    case KommoEntityType::LEADS:
                        if ($unsortedContact !== null) {
                            $this->contactService
                            ->linkContact($unsortedContact, $entityType, $task->getEntityId());
                        }
                        break;
                    case KommoEntityType::CONTACTS:
                    case KommoEntityType::COMPANIES:
                        // in the incoming lead only the contact id. We'll get all the information.
                        if ($unsortedContact !== null) {
                            $unsortedContact = $this
                            ->contactService>getContactById($unsortedContact->getId());
                            $phones = $unsortedContact
                            ->getCustomFieldsValues()
                            ?->getBy('fieldCode', 'PHONE');
                            $newPhone = $phones?->getValues()?->first()->getValue();
                            if (!empty($newPhone)) {
                                $this->contactService->updatePhones($task->getEntityId(),                                           $entityType, (string)$newPhone);
                            }
                        }
                        break;
                    default:
                        throw UnsupportedEntityException::create();
                }
        }
        $this->contactService->updateCallEvent(
            $call,
            $voipCall->getResponsibleUserId() ?? self::BOT_USER_ID,
            $voipCall->getEntityId(),
            $voipCall->getEntityType(),
            $task->getEntityId(),
            $task->getEntityType()
        );

        // Rejeite leads de entrada após recusar a nota de chamada
        if ($unsorted) {
            $this->unsortedService->declineUnsorted($unsorted);
            $voipCall->setUnsortedUid(null);
        }
        $voipCall->getEntityType())->save();
        $task->setSuccess($success);
}

A tarefa é criada dentro do worker, que obtém a tarefa da fila e a envia para o handler:

public function run(array $data, LoggerInterface $logger): void 
{
    $taskData = $data['data'] ?? [];
    $task = new UpdateFromModalTask(
        $taskData['account_id'],
        $taskData['call_id'],
        $taskData['entity_id'],
        $taskData['entity_type']
    );
    $this->updateFromModalUseCase->handle($task);
    if (!$task->isSuccess()) {
        throw BusinessLogicException::create('Atualize o evento de erro de chamada');
    }
}

Nota de chamada

O registro de chamadas é realizado criando eventos dentro da entidade correspondente, categorizados sob tipos específicos de eventos para chamadas realizadas e recebidas.

Se o sistema VoIP oferecer suporte à gravação de chamadas, a interface pode exibir uma URL e um player de áudio para reproduzir a gravação. Para que o player funcione corretamente, o servidor que hospeda o arquivo deve incluir o cabeçalho HTTP Accept-Ranges: bytes na resposta. Se esse cabeçalho estiver ausente, funcionalidades como a de avançar e retroceder na gravação podem não funcionar corretamente.

Uma nota de chamada pode ser adicionada usando o endpoint POST /api/v4/calls. O endpoint faz uma busca automática por entidades usando o número de telefone fornecido e anexa o registro da chamada à entidade apropriada conforme o algoritmo definido. A documentação detalhada desse algoritmo está disponível na especificação do método. Se nenhuma entidade corresponder ao número informado, a chamada não será vinculada a nenhuma entidade.

O sistema oferece diferentes maneiras de exibir informações de chamadas, dependendo da direção da chamada (recebida ou realizada).