Voice Management Guide

The Voice Management user guide

Extension of connector behaviour

1 - Extension of connector behaviour

Since version 2.0 you can do your work before, after, or instead of the caller ID lookup.

You can create a contact upstream if you need it before searching for it using our method, for example.
It is also possible to create or search for a case and return this ID instead of our search.
Or if you want, you can redesign the entire search yourself to suit your own needs, e.g. different searches depending on the operator profile.

2 - Activate my redefinitions

Create a class that inherits from AxiaVM.Extension:

				
					global class MyAxialysExtension extends AxiaVM.Extension {
    // code d'extension ici
}



				
			

To use this code extension you must insert in the table AxiaVM__ConfigCTI__c an element with

Name = 'redefined_class'and AxiaVM__value__c = 'MyAxialysExtension'

or

AxiaVM__value__c = 'MyOrgNamespace.MyAxialysExtension'

if your class is in a Namespace

2 - Search method (incoming/outgoing call)

Version greater than 2.0

The search receives a serialised JSON as input and returns an ID list.
The json currently contains two values:

				
					

{
    "e164": "[numéro_appelant]" ,  // numéro en String de l'appelant, ex : 33123456789
    "numSVI": "[numéro_appelé]"    // numéro en String du SVI appelé
}


				
			

The return must be a List even if there is only one item in it.

Example of redefinition

In this example, we want to create a case and open it instead of the contact.
We will run the parent code, create the case, and return the ID for this case.

				
					global class MyAxialysExtension extends AxiaVM.Extension {

    /**
     * @description Retourne une liste d'Id d'objet à ouvrir.
     *
     * - un seul élément retourné sera ouvert
     * - plusieurs va déclencher une ouverture de la recherche avec le numéro de téléphone
     *
     * @param   params   Chaine en Json sérialisé qui contient les paramètres
     * @return  List<Id>
     */
    global override List<Id> getIdsByPhone(String params) {

        // si je veux récupérer les champs du paramètre js
        Map<String, Object> tmp = (Map<String, Object>) JSON.deserializeUntyped(params);
        String e164 = (String) tmp.get('e164');
        String numSVI = (String) tmp.get('numSVI');

        // On peut faire du travail en amont

        // On peut récupérer le résultat de la méthode parente
        List<Id> s = super.getIdsByPhone(params);

        // On peut aussi faire quelque chose après pour changer le retours ou ajouter un Objet
        Case c = new Case(
            Subject = 'Appel en cours',
            Origin = 'Phone',
            ContactId = s[0],
            Status = 'Working',
            SVI__c = numSVI
        );
        insert c;

        return new List<Id>{ c.Id };
    }

}

				
			

Version greater than 2.30

SeekPhone class overview

In the Voice-management connector, SeekPhone is used to search for the caller’s number in the different possible objects (Account , Contact, Lead) and return the result found to the Voice-management window.
Depending on the options, you can choose:

< ul dir="auto" data-sourcepos="91:1-94:0">

  • create form if not found
  • < li data-sourcepos="92:1-92:34">open a search window

  • do nothing at all.
  • 				
    					global class SeekPhone {
        /**
         * Recherche la fiche de l'appelant et permet plusieurs comportement différents en retour
         * @param js JSON Sérialisé, (voir plus bas)
         * @return JSON sérialisé. Il est conseillé d'utiliser le helper SeekPhone.response
         */
        public static String get(string js) {
            // travail de recherche
            return SeekPhone.response('OBJECT', (String) objectId);
        }
    
        /**
         * Helper qui fournit une réponse en JSON sérialisé pour retours de la méthode `get`
         * @params type Type d'ouverture que l'on va demander au voicemanagement pour l'appel
         *         valeurs possibles : 'OBJECT'|'SEARCH'|'URL'|'NOTHING'
         * @param value id ou numéro de téléphone à rechercher
         */
        global static String  response (String type, String value) {
            return json.serialize(new Map<String, Object> {
                'type' => type,
                'values' => new List<String>{value}
            });
        }
    }
    
    				
    			
    • The feedback conditions the output display of Voice-management:
    • OBJECT: Opens a Salesforce object regardless of its class. values is a single element array that contains the object id.
    • SEARCH : Opens the search regardless of the result. values is a one-element array that contains the search criteria (e.g. caller number).
    • URL: Opens a page on a Lightning component in your organization. values is a single element array that contains the url to open.
    • NOTHING: Does nothing at all.

    Redefining the Behavior of SeekPhone.get

    To do upstream, downstream work , or instead of SeekPhone.get, it is possible to use inheritance of this sort

    				
    					global class MyAxialysExtension extends AxiaVM.Extension {
        global override String seekPhone(String js) {
            // ici je peux faire du travail en amont
            [...]
    
            // execution du code parent (optionnel)
            String s = super.seekPhone(js);
    
            // ici je peux faire du travail en aval
            [...]
    
            // selon mon retour je peux changer complètement le comportement
            return AxiaVM.SeekPhone.response('SOBJECT|URL|SEARCH...', value);
        }
    }
    
    				
    			

    The elements present in the js variable are as follows:

    				
    					{
        "e164": "33123456789",
        "numSVI": "33800800800",
        "inOut": "in/out"
    }
    
    				
    			

    4 - Task addition method

    				
    					global class CaseExtensionExample extends AxiaVM.Extension{
    
        global override String seekPhone(String params) {
           
            // We need the results of parent call
            String previous = super.seekPhone(params);
    
            Map<String, Object> res = (Map<String, Object>) JSON.deserializeUntyped(previous);
            String result_type = (String) res.get('type');
            List<Object> result_values = (List<Object>) res.get('values');
    
           
            if( result_type == 'SOBJECT') {
                Id theId = (ID)result_values[0];
               
                // break if the object is neither an account or a contact
                if (!compare(theId.getSobjectType(), 'Account') && !compare(theId.getSobjectType(), 'Contact') ){
                    return previous;
                }
               
                // get lasts open cases
                List<Case> open_cases = [SELECT Id FROM Case WHERE
                    (AccountId = :theID OR ContactId = :theID)
                    AND isClosed=False AND isDeleted = False
                    ORDER BY CreatedDate DESC NULLS LAST
                ];
               
                // if I have one or more result, I open the most recent one
                if (open_cases.size() > 0) {
                    return AxiaVM.SeekPhone.response('SOBJECT', (String) open_cases[0].Id);
                }
                // if I want to create Case if no cases opened
                /*else {
                    Case new_one = Case(...);
                    insert new_one;
                    return AxiaVM.SeekPhone.response('SOBJECT', (String) new_one.Id);
                }*/
            }
    
           
            return previous;
        }
    
        /**
        * Helper that compare the type of the object
        */
        private boolean compare(Schema.SObjectType compare, String checkType){
            try{
                Schema.SObjectType targetType = Schema.getGlobalDescribe().get(checkType);
                if(targetType == null){
                    return false;
                }else if( compare == targetType){
                    return true;
                }else{
                    return false;
                }
            }catch(Exception e){
                //handle exception
                        return false;
            }
        }
    
    }
    
    
    				
    			

    4 - Method of adding Task

    Version greater than 2.0

    The search receives a serialised JSON as input and returns an ID list.
    The json currently contains two values:

    				
    					
    
    {
        "e164": "[numéro_appelant]" ,       // numéro en String de l'appelant, ex : 33123456789
        "dt": "[date_appel]",               // date de l'appel au format DateTime apex
        "id_appel": "[id_appel_voice]",     // id de l'appel du voice management
        "inOut": "[appel_entrant_sortant]", // "in" ou "out" sens de l'appel
        "op_email": "[email_operateur]",    // email de l'opérateur pour attribution
        "groupe": "[groupe]",               // nom du groupe qui à eu l'appel
        "duree": "[temps_de_l_appel]",      // temps de l'appel en secondes
        "rec": "[presence_enregistrement]"  // Booléen qui détermine la présence d'un enregistrement
    }
    
    
    				
    			

    The return must be the Id of the task.

    Example of redefinition

    In this example, we want to add the task to the call and not to the caller’s contact. We will run the caller search code, find the associated case and attach the task to it.

    				
    					
    
    global class MyNewExtension extends AxiaVM.Extension {
    
        global override Id recordTask(String params) {
    
            Map<String, Object> tmp = (Map<String, Object>) JSON.deserializeUntyped(params);
            String e164 = (String) tmp.get('e164');
            String op_email = (String) tmp.get('op_email');
            String op_email = (String) tmp.get('op_email');
            Datetime date = (Datetime) tmp.get('dt');
            String inout = (String) tmp.get('inOut');
    
            User[] operator = [SELECT Id FROM User WHERE FederationIdentifier =: op_email ];
            List<List<Contact>> tmp = [FIND :e164 IN PHONE FIELDS  RETURNING Contact(Id)];
            Id id_contact = tmp[0].Id;
            Case c = [SELECT Id FROM Case WHERE ContactId =: id_contact];
    
            Task task = new Task(
                OwnerId = operator.Id,
                CreatedDate = date,
                WhatId = c.Id,
                Subject = 'appel ' + inout
                //[...]
            );
            insert task;
    
            return new List<Id>{ task.Id };
        }
        
    }
    
    
    				
    			

    Version greater than 2.30

    Since this version there are two different JSONs depending on a call task or a sms sending task:< /p>

    Invoke Task JSON

    				
    					{
        "e164": "numero appelant",
        "id_call": "id de l'appel",
        "inOut": "in|out",
        "group_name": "Nom du groupe",
        "call_duration": 123, // en secondes
        "wait_duration": 0,   // en secondes
        "type": 'CALL',
        "numSVI": "numéro du SVI",
        "objectType": "Classe de l'objet dans lequel l'opérateur est ex : Account|Contact|Lead|Case...",
        "recordId": "Id de l'objectType",
        "url": "URL de l'objectType",
        "mode": "task|cron",
        "op_mail": "operator@email",
        "begin_ts":  159269489, // timestamp du début de l'appel
        "end_ts": 159269489,    // timestamp de fin de l'appel
        "transferred_to": "info de transfert"
    }
    
    				
    			

    SMS Task JSON

    				
    					{
        "e164": "numero appelant",
        "content": "contenu du sms (optionnel)",
        "success": true|false, // si tout c'est bien passé pendant l'envoie
        "objectType": "Classe de l'objet dans lequel l'opérateur est ex : Account|Contact|Lead|Case...",
        "recordId": "Id de l'objectType",
        "url": "URL de l'objectType",
        "type": "SMS",
        "mode": "task",
        "op_mail": "operator@email"
    }
    
    				
    			

    Example of redefining a Task

    In this example, we want to add the task to the call and not to the caller’s Contact. We will run the caller lookup code and find the associated Case and attach the task to it.

    				
    					global class MyNewExtension extends AxiaVM.Extension {
    
        global override Id recordTask(String params) {
    
            Map<String, Object> tmp = (Map<String, Object>) JSON.deserializeUntyped(params);
            String e164 = (String) tmp.get('e164');
            String op_mail = (String) tmp.get('op_mail');
            String inout = (String) tmp.get('inOut');
            String type = (String) tmp.get('type');
            //[...]
    
            if (type == 'CALL') {
                User[] operator = [SELECT Id FROM User WHERE FederationIdentifier =: op_mail ];
                List<List<Contact>> tmp = [FIND :e164 IN PHONE FIELDS  RETURNING Contact(Id)];
                Id id_contact = tmp[0].Id;
                Case c = [SELECT Id FROM Case WHERE ContactId =: id_contact];
    
                Task task = new Task(
                    OwnerId = operator.Id,
                    WhatId = c.Id,
                    Subject = 'appel ' + inout == 'in' ? 'entrant' : 'sortant'
                    //[...]
                );
                insert task;
    
                return task.Id ;
            }
            return super.recordTask(params)
        }
    
    }
    
    				
    			

    Example to manage the personalAccount

    In this example we do not want to add tasks, and create a personal account if we have no correspondence in the database.

    				
    					global with sharing class PersonnalAccountExtension extends AxiaVM.Extension {
        
        private static Pattern numPattern = Pattern.compile('^(([a-zA-Z]+:)?\\d{1,16})|([a-zA-Z]+)$');
        
        
        global override Id recordTask(String params) {
            // Don’t create task
            return null ;
        }
    
         
       global override String seekPhone(String params) {
            // We need the results of parent call
            // 
            Map<String, Object> tmp = (Map<String, Object>) JSON.deserializeUntyped(
          		params
        	);
            String e164 = (String) tmp.get('e164');
             
            String previous = super.seekPhone(params);
            Map<String, Object> res = (Map<String, Object>) JSON.deserializeUntyped(previous);
            String result_type = (String) res.get('type');
            List<Object> result_values = (List<Object>) res.get('values');
    
            if( result_type == 'SOBJECT') {
                return previous;
            } else {
    
                Id personAccountRecordTypeId =  Schema.SObjectType.Account.getRecordTypeInfosByDeveloperName().get('PersonAccount').getRecordTypeId();
                Account account = new Account();
                account.RecordTypeId = personAccountRecordTypeId;
                account.FirstName = 'Axialys';
                account.LastName = 'Axialys';
                account.PersonMailingStreet='Paris';    // fill with contact details
                account.PersonMailingPostalCode='75015';
                account.PersonMailingCity='Paris';
                account.PersonHomePhone=e164;
                account.PersonMobilePhone=e164;
    	    insert account;
                System.debug('CreationOK');
                return AxiaVM.SeekPhone.response('SOBJECT', account.ID);       
            }         
           
      }
    }
    
    
    
    				
    			

    6 - Declare my extension class

    This is a temporary procedure; in later versions a selector will be added to the Voice Management settings page.

    We will indicate to the VoiceManagement connector that there is an extension class

    To do this, a new item must be defined in the AxiaVM__ConfigCTI__c.
    .table.
    For example, for a class MyNewExtension which extends AxiaVM.Extension, you must create an item AxiaVM__ConfigCTI__c where Name = "redefined_class" et AxiaVM__value__c = "MyNewExtension".

    6 - Tips for analyzing a problem

    We advise you first to make sure that the class you want to use overrides the default one. If you create your own objects, set the object to create to “None”. Next, make sure you don’t have any checks in place that prevent the creation of the object you want to create.

    Make sure your your class overrides the original one
      < li data-sourcepos="63:2-63:351">Make sure that the name of the class created overrides the original method by adding a “System.debug”, or look in the logs of the “developer console”, loading, you should see lines like 12:15:18:373 VARIABLE_SCOPE_BEGIN [22]|this|PersonnalAccountExtension|true|false with the name of the overloaded class.
    • If you don’t see your class, please check via the workbench or via the console by running the following command that there is only ‘a “redefined_class” class and that it is well populated with the name you gave to your class.
    				
    					Select Id, Name,AxiaVM__value__c From AxiaVM__ConfigCTI__c Where Name = 'redefined_class'
    
    
    
    
    
    				
    			
    Make sure you don’t have blocking controls in place

    Checks that could prevent certain operations from being carried out, which would not be defined, if for example you made it mandatory to configure an address on a customer file.

    Finally, to help you debug, you can view the processing status of exchanges with salesforces by opening the development screen of your browser. You will be able to check the return status, and in case of failure the error that caused the problem.