Алина Леонова
25.06.2020
5608

Динамическая IVR на основе базы данных и диалплана

Всё чаще и чаще в компаниях можно встретить умную маршрутизацию входящих вызовов – клиент связывается со своим личным менеджером в обход колл-центра, различных голосовых меню. Помимо существенной экономии времени звонка умная маршрутизация ещё является отличным бонусом для постоянного клиента – всякий раз говорить, кто клиент, и с кем нужно соединить, начинает надоедать. Однако, иногда клиенту […]

Всё чаще и чаще в компаниях можно встретить умную маршрутизацию входящих вызовов – клиент связывается со своим личным менеджером в обход колл-центра, различных голосовых меню. Помимо существенной экономии времени звонка умная маршрутизация ещё является отличным бонусом для постоянного клиента – всякий раз говорить, кто клиент, и с кем нужно соединить, начинает надоедать.

Однако, иногда клиенту нужно поговорить не только со своим менеджером, а и с другими закреплёнными за ним сотрудниками, если таковые имеются. В данной статье рассмотрим возможность соединения клиента с другими сотрудниками без набора внутреннего номера в IVR, а также без переводов.

Задача: при входящем звонке предлагать клиенту соединить его с людьми, с которыми он говорил до этого. Разберем данную ситуацию на примерах.

Приблизительная схема необходимой IVR

Также нужно учитывать, что в этом месяце клиент мог вообще не звонить в компанию. В таком случае, стандартная IVR для нас будет такого типа: «Здравствуйте! Наша компания рада приветствовать Вас. Для связи с колл-центром нажмите 1 или оставайтесь на линии».

Реализация: данную задачу будем производить с помощью диалплана и обращениям к нужным таблицам.

Шаг 1. Импорт из csv-файла таблицы сотрудников компании

Не всем удобно работать напрямую с базой. Ещё неудобнее её заполнять (особенно когда количество сотрудников в компании достаточно большое). Даже если выделить часть сотрудников для данного голосового меню, их всё равно может оказаться не так уж мало, как может показаться на первый взгляд. Имея FreePBX, конечно, можно выгрузить csv-файл с номерами с помощью модуля Bulk Handler, а уже в выгруженной таблице удалить все поля, кроме имени и номера. Однако в большинстве случаев (особенно если компания большая) вместо имён мы получим тот же продублированный внутренний номер. Поэтому на этом шаге рассмотрим импорт csv-файла в таблицу нужной базы.

Для начала создадим таблицу, в которую будем импортировать значения. Для этого перейдём в нужную базу и выполним команду:

CREATE TABLE companys_employees (number varchar(30) NOT NULL PRIMARY KEY, name varchar(255), position varchar(255) NOT NULL);

Затем приступим к созданию csv-файла.

Все поля файла, а также формат импортируемых данных должны совпадать с указанными в таблице. Все текстовые значения должны быть заключены в кавычки.

Также стоит обратить внимание на то, что при заполнении файла должность пишем в творительном падеже. Это делаем для удобства динамического озвучивания должности.

После создания файла загрузим его на сервер и произведём импорт данных. Для импорта воспользуемся командой:

LOAD DATA INFILE '/tmp/employees.csv' INTO TABLE companys_employees FIELDS TERMINATED BY ',' ENCLOSED BY '"' LINES TERMINATED BY '\n' IGNORE 1 ROWS;

После этого проверим нашу таблицу. В примере после импорта она выглядит следующим образом:

Таблица сотрудников компании

Шаг 2. Создание основного запроса к базе

Данный функционал актуален для дистрибутивов FreePBX. Для других дистрибутивов могут потребоваться доработки.

Для начала сформируем необходимый запрос к cdr. Здесь необходимо учесть следующее:

  1. Вызов должен быть отвеченным;
  2. В качестве ответившего должен выступать исключительно внутренний номер, а не номер очереди и другие назначения (например, в качестве dst можно встретить буквенные обозначения).

В примере запрос будет выглядеть следующим образом:

SELECT DISTINCT cdr.dst FROM cdr JOIN companys_employees WHERE src = ‘${ARG1}’ AND calldate >= DATE_SUB(CURDATE(),INTERVAL 1 MONTH) AND disposition='ANSWERED' AND cdr.dst=companys_employees.number ORDER BY calldate DESC LIMIT 3

В данном запросе мы получаем последних трёх ответивших за последний месяц от текущего дня. Также проверяем, что вызов отвечен, а также, что номер ответившего присутствует в ранее созданной таблице company_employees – таким образом мы автоматически убираем все ненужные нам значения (например, номера очередей, нажатия в IVR).

Для проверки запроса подставим в src номер звонящего на место переменной ARG1. Данный запрос вернёт все уникальные назначения для указанного номера звонящего за последний месяц.

Также, для динамической озвучки, потребуется интеграция с синтезатором речи. В примере будет использоваться синтезатор речи RHVoice.

Обратите внимание, что в примере вся озвучка происходит с помощью RHVoice. Скрипт конвертации в нужный формат, а также подробнее об интеграции с RHVoice можно посмотреть в данной статье. Также стоит учесть, что IVR будет содержать необходимый для работы RHVoice диалплан.

Однако, если в дальнейшем не планируется озвучивать чьи-то имена, можно обойтись заранее записанными звуковыми файлами и проигрывать всё через стандартный Playback.

Далее перейдём к маршрутизации вызовов.

Шаг 3. Создание IVR. Маршрутизация

Реализуем основной замысел нашей IVR – маршрутизацию вызовов. Здесь для начала определимся с статической частью голосового меню. В примере этой статической частью станут возможные направления в IVR – у нас их будет всего 4 (нажатие 1, 2, 3 и отправка вызова на колл-центр по таймауту).

Далее, с помощью ODBC, пошлём в базу основной запрос. Подробнее узнать о том, как работать из Asterisk с базой с помощью ODBC можно из этой статьи.

Для этого перейдём в конфигурационный файл func_odbc.conf и пропишем в нём:

[VIPIVR]
dsn=asteriskcdrdb
mode=multirow
readsql=SELECT DISTINCT cdr.dst FROM cdr JOIN companys_employees WHERE src = ${ARG1} AND calldate >= DATE_SUB(CURDATE(),INTERVAL 1 MONTH) AND disposition='ANSWERED' AND cdr.dst=companys_employees.number ORDER BY calldate DESC LIMIT 3

dsn – база, к которой подлючаемся;

mode=multirow – отвечает за то, что в Asterisk будем передавать несколько значений.

Перейдём в диалплан и сначала пропишем контекст call-center. В него будем направлять клиента, назначения в cdr не оказалось.

[call-center]
exten => s,1,NoOp("Call-center")
same  => n,Set(TEXT='Пожалуйста, дождитесь соединения с колл-центром. Вам ответит первый свободный оператор')
same  => n,Set(RHV_FILE_CALL="call_center")
same  => n,System(/usr/local/bin/rhvoice_synthesis.sh ${RHV_FILE_CALL} "${TEXT}")
same  => n,Playback(custom/rhv_playback/${RHV_FILE_CALL})
same  => n,System(rm -f /var/lib/asterisk/sounds/ru/custom/rhv_playback/${RHV_FILE_CALL}.wav)
same  => n,Goto(ext-queues,1010,1)

В этом контексте происходит озвучивание необходимого текста клиенту, после чего происходит соединение с очередью. В примере это очередь 1010.

Затем, чтобы не упустить никакие переменные, сформируем нашу IVR на основе тех, что создаёт FreePBX – можно посмотреть в extension_additional.conf. Сначала пропишем основное тело IVR.

[ivr-vip]
exten => s,1,NoOp("Dynamic IVR")
same  => n,Set(_IVR_CONTEXT_${CONTEXT}=${IVR_CONTEXT})
same  => n,Set(_IVR_CONTEXT=${CONTEXT})
same  => n,Set(__IVR_RETVM=)
same  => n,GotoIf($["${CHANNEL(state)}" = "Up"]?skip)
same  => n,Answer()
same  => n,Wait(1)
same  => n(skip),Set(IVR_MSG='Здравствуйте! Наша компания рада приветствовать Вас! С целью улучшения качества обслуживания все разговоры записываются.')
same  => n(start),Set(TIMEOUT(digit)=3)
same  => n,Set(TEXT=${IVR_MSG})
same  => n,Set(RHV_FILE="hello_ivr")
same  => n,System(/usr/local/bin/rhvoice_synthesis.sh ${RHV_FILE} "${TEXT}")
same  => n,ExecIf($["${IVR_MSG}" != ""]?Playback(custom/rhv_playback/${RHV_FILE})
same  => n,System(rm -f /var/lib/asterisk/sounds/ru/custom/rhv_playback/${RHV_FILE}.wav)
same  => n,Set(ID=${ODBC_VIPIVR(${CALLERID(num)})})
same  => n,Set(VAR1=${ODBC_FETCH(${ID})})
same  => n,Set(VAR2=${ODBC_FETCH(${ID})})
same  => n,Set(VAR3=${ODBC_FETCH(${ID})})
same  => n,ODBCFINISH()
same  => n,Set(POS=${ODBC_TYPE(${VAR1})})
same  => n,Set(TEXT_POS='Если Вы хотите поговорить с Вашим ${POS} - нажмите 1')
same  => n,ExecIf($[${LEN(${VAR1})} < 2]?Set(TEXT_POS='Для перевода на колл-центр нажмите 1'))
same  => n,Set(RHV_FILE_POS="type_ivr")
same  => n,System(/usr/local/bin/rhvoice_synthesis.sh ${RHV_FILE_POS} "${TEXT_POS}")
same  => n,Playback(custom/rhv_playback/${RHV_FILE_POS})
same  => n,System(rm -f /var/lib/asterisk/sounds/ru/custom/rhv_playback/${RHV_FILE_POS}.wav)
same  => n,GotoIf($[${LEN(${VAR1})} < 2]?wait)
same  => n,Set(POS2=${ODBC_TYPE(${VAR2})})
same  => n,Set(TEXT_POS='Если Вы хотите поговорить с Вашим ${POS2} - нажмите 2')
same  => n,NoOp(${LEN(${VAR2})})
same  => n,ExecIf($[${LEN(${VAR2})} < 2]?Set(TEXT_POS='Для перевода на колл-центр нажмите 2'))
same  => n,NoOp(${VAR2})
same  => n,Set(RHV_FILE_POS="type_ivr")
same  => n,System(/usr/local/bin/rhvoice_synthesis.sh ${RHV_FILE_POS} "${TEXT_POS}")
same  => n,Playback(custom/rhv_playback/${RHV_FILE_POS})
same  => n,System(rm -f /var/lib/asterisk/sounds/ru/custom/rhv_playback/${RHV_FILE_POS}.wav)
same  => n,GotoIf($[${LEN(${VAR2})} < 2]?wait)
same  => n,Set(POS3=${ODBC_TYPE(${VAR3})})
same  => n,Set(TEXT_POS='Если Вы хотите поговорить с Вашим ${POS3} - нажмите 3')
same  => n,ExecIf($[${LEN(${VAR3})} < 2]?Set(TEXT_POS='Для перевода на колл-центр нажмите 3'))
same  => n,NoOp(${VAR3})
same  => n,Set(RHV_FILE_POS="type_ivr")
same  => n,System(/usr/local/bin/rhvoice_synthesis.sh ${RHV_FILE_POS} "${TEXT_POS}")
same  => n,Playback(custom/rhv_playback/${RHV_FILE_POS})
same  => n,System(rm -f /var/lib/asterisk/sounds/ru/custom/rhv_playback/${RHV_FILE_POS}.wav)
same  => n(wait),WaitExten(10,)

Остановимся подробнее на том, что здесь происходит.

Для начала, в переменную TEXT заносим текст для приветствия и озвучиваем его. После этого обращаемся к базе с помощью драйвера ODBC и получаем результат в переменную ID.

Чтобы вывести все назначения, которые получаем из базы, используем функцию ODBC_FETCH(). Она получает нужные значения по id Её нужно будет прописать в диалплане столько раз, сколько значений собираемся вывести в Asterisk. Использование после неё ODBCFINISH() обязательно.

После этого озвучиваем текст для нужных направлений из IVR. Здесь нам понадобится таблица companys_employees.

Для начала нужно учесть тот факт, что из базы можно получить ничего. Поэтому каждое полученное назначение будем проверять на длину. Если его длина меньше 2, озвучиваем фразу: «Для перевода на колл-центр нажмите N», где N – одно из возможных направлений.

Если же длина полученного номера назначения больше 2, то переходим к запросу должности сотрудника.

Для этого в func_odbc.conf нужно будет прописать:

[TYPE]
dsn=asteriskcdrdb
readsql=SELECT position FROM companys_employees WHERE number='${ARG1}'

Вернёмся к диалплану. Итак, по введённому запросу, получаем должность сотрудника в творительном падеже (переменная ${POS}). На основе этого формируем озвучку: «Если Вы хотите поговорить с Вашим ${POS} – нажмите N».

Описанные выше переходы для соединения с определённым сотрудником или с колл-центром прописываем для всех возможных направлений IVR.

Теперь переходим к описанию направлений. Так как все направления (кроме тайм-аута) однотипны, в статье опишем направления 1 и t.

exten => 1,1,NoOp("Type 1")
same  => n,GotoIf($[${LEN(${VAR1})} < 2]?call-center,s,1)
same  => n,Set(NAME=${ODBC_NAME(${VAR1})})
same  => n,Set(LEN_POS=$[${LEN(${POS})} - 4])
same  => n,Set(TEXT_NAME='Ваш ${POS:0:${LEN_POS}} - ${NAME}. Пожалуйста, дождитесь соединения с сотрудником')
same  => n,Set(RHV_FILE_NAME="name_dst")
same  => n,System(/usr/local/bin/rhvoice_synthesis.sh ${RHV_FILE_NAME} "${TEXT_NAME}")
same  => n,Playback(custom/rhv_playback/${RHV_FILE_NAME})
same  => n,System(rm -f /var/lib/asterisk/sounds/ru/custom/rhv_playback/${RHV_FILE_NAME}.wav)
same  => n,Goto(ext-local,${VAR1},1)

Начинаем описывание направления с проверки длины полученного номера назначения. Сперва снова проверяем, длина меньше двух или нет. Если меньше, перенаправляем вызов на контекст call-center. Если больше, идём по диалплану дальше. Следующим шагом нам нужно получить из базы имя сотрудника, с которым будем соединять клиента. Для этого снова перейдём в func_odbc и пропишем в нём:

[NAME]
dsn=asteriskcdrdb
readsql=SELECT name FROM companys_employees WHERE number='${ARG1}'

Вернёмся к диалплану. В переменную NAME получим имя сотрудника. Далее из творительного падежа должности нужно получить именительный. Для этого вычисляем длину должности, отнимаем от неё 4 – вычисленное значение поместим в переменную LEN_POS. Затем из переменной POS берём символы с 0 по LEN_POS. По итогу озвучен должен быть, к примеру, такой текст: «Ваш менеджер — Name». После этого производим соединение с необходимым сотрудником.

Обратите внимание, чтобы не потерять вызов, при отсутствии сотрудника на работе, обязательно должна быть выставлена переадресация либо на другого сотрудника, находящегося на работе, либо на очередь.

Для тайм-аута же просто пропишем переход на контекст call-center.

exten => t,1,NoOp("Timeout")
same  => n,Goto(call-center,s,1)

На данном шаге ранее заявленный функционал для динамической IVR готов. Мы уже можем прозвониться на IVR. В качестве примера рассмотрим следующий случай – прозвонимся с номера 201 на IVR. На этом этапе мы уже знаем, что за последний месяц общение производилось с двумя разными номерами – это 0008 и 0009. Также нам известно, что 0008 – это менеджер по имени Петрив Дарим, а 0009 – юрист по имени Антонова Раиса. Посмотрим, как поведёт себя при этом IVR.

Вызов с номера 201

Как видно из лога, всё отработало правильно – получили номера в следующем порядке VAR1 = 0008, VAR2 = 0009. В VAR3 не получили ничего. Далее нам предложили по нажатию 1 соединиться с менеджером, по нажатию 2 – с юристом, а по нажатию 3 мы должны были уйти на колл-центр. Но, так как было нажатие 1, нас отправили на менеджера, предварительно озвучив, что наш менеджер – Петрив Дарим.

Теперь, для проверки, создадим новый номер и прозвонимся с него. В данном случае до этого он не мог ни с кем разговаривать. Пусть это будет номер 953. По логу видно, что здесь всё тоже отработало верно – не получив ничего, было предложено связаться с колл-центром. Далее вызов ушёл на ожидание ввода.

Вызов с номера 953

Теперь немного усложним задачу: если в базе имеется имя клиента, вместо стандартного приветствия мы должны услышать: «Здравствуйте, Name! Мы рады снова приветствовать Вас. Для улучшения качества обслуживания сотрудников все разговоры записываются».

Реализация:

Для начала создадим таблицу vip_clients_name. В ней нужны будут такие поля: cid, client.

CREATE TABLE vip_clients_name (cid varchar(30) NOT NULL PRIMARY KEY, client varchar(255)); 

Далее импортируем необходимые данные.

Таблица vip_clients_name

Теперь перейдём в func_odbc.conf и пропишем:

[CLIENTS]
dsn=asteriskcdrdb
readsql=SELECT client FROM vip_clients_name WHERE cid='${ARG1}'
Далее перейдём в нашу IVR. После того, как в переменную IVR_MSG определили стандартное приветствие, пропишем:
same  => n,Set(CLIENT=${ODBC_CLIENTS(${CALLERID(num)})})
same  => n,ExecIf($[${LEN(${CLIENT})} > 2]?Set(IVR_MSG='Здравствуйте, ${CLIENT}! Мы рады снова приветствовать Вас. Для улучшения качества обслуживания сотрудников все разговоры записываются'))

Теперь проверим. Позвоним с номера 201 на IVR. Как видно из лога, отработало всё корректно.

Определение имени клиента

Если же позвоним с номера, для которого имени нет, услышим стандартное приветствие.

Однако, если звонок будет совершён с номера, которого вообще нет в базе клиентов, он тоже попадёт на эту IVR. И нас это не устраивает, т.к. для новых клиентов у нас другая IVR с другим приветствием и другими направлениями. Отсюда, для входящей маршрутизации до IVR, появляется следующая задача: отсортировать новых клиентов от уже имеющихся в базе. Новых клиентов необходимо направить на другую IVR.

Реализация: воспользуемся той же таблицей клиентов, что и на предыдущем шаге. Перейдём в func_odbc.conf и пропишем:

[EXISTENCE]
dsn=asteriskcdrdb
readsql=SELECT cid FROM vip_clients_name WHERE cid='${ARG1}'

Затем перейдём в диалплан. Здесь пропишем контекст existence_clients.

[existence-clients]
exten => s,1,Set(CID=${ODBC_EXISTENCE(${CALLERID(num)})})
same  => n,GotoIf($[${LEN(${CID})} > 2]?ivr-vip,s,1:ivr-1,s,1)

Далее с помощью модуля Custom Destinations направим входящие вызовы с маршрута на этот контекст. Подробнее о работе с модулем Custom Destinations можно прочитать в этой статье.

Новый Custom Destinations

Приступим к проверке. Мы уже знаем, что в базе клиентов существует номер 201 и не существует номер 953. Проверять будем вызовами именно с этих номеров.

Номер существует в клиентской базе
Номера не существует в клиентской базе

Отработало всё правильно: номер 201 ушён на VIP-IVR, а номер 953 – на стандартную IVR. Теперь функционал можно считать полностью завершённым.

На этом статья по созданию динамической IVR на основе диалплана и базы завершена.

Телефонная книга CallMetrix
Корпоративный телефонный справочник для удобной связи между сотрудниками организации
Скачать описание CallMetrix
Подписаться
Уведомить о
guest
0 комментариев
Межтекстовые Отзывы
Посмотреть все комментарии

Остались вопросы?

Я - Кондрашин Игорь, менеджер компании Voxlink. Хотите уточнить детали или готовы оставить заявку? Укажите номер телефона, я перезвоню в течение 3-х секунд.

VoIP оборудование


ближайшие курсы

10 доводов в пользу Asterisk

Распространяется бесплатно.

Asterisk – программное обеспечение с открытым исходным кодом, распространяется по лицензии GPL. Следовательно, установив один раз Asterisk вам не придется дополнительно платить за новых абонентов, подключение новых транков, расширение функционала и прочие лицензии. Это приближает стоимость владения станцией к нулю.

Безопасен в использовании.

Любое программное обеспечение может стать объектом интереса злоумышленников, в том числе телефонная станция. Однако, сам Asterisk, а также операционная система, на которой он работает, дают множество инструментов защиты от любых атак. При грамотной настройке безопасности у злоумышленников нет никаких шансов попасть на станцию.

Надежен в эксплуатации.

Время работы серверов некоторых наших клиентов исчисляется годами. Это значит, что Asterisk работает несколько лет, ему не требуются никакие перезагрузки или принудительные отключения. А еще это говорит о том, что в районе отличная ситуация с электроэнергией, но это уже не заслуга Asterisk.

Гибкий в настройке.

Зачастую возможности Asterisk ограничивает только фантазия пользователя. Ни один конструктор шаблонов не сравнится с Asterisk по гибкости настройки. Это позволяет решать с помощью Asterisk любые бизнес задачи, даже те, в которых выбор в его пользу не кажется изначально очевидным.

Имеет огромный функционал.

Во многом именно Asterisk показал какой должна быть современная телефонная станция. За многие годы развития функциональность Asterisk расширилась, а все основные возможности по-прежнему доступны бесплатно сразу после установки.

Интегрируется с любыми системами.

То, что Asterisk не умеет сам, он позволяет реализовать за счет интеграции. Это могут быть интеграции с коммерческими телефонными станциями, CRM, ERP системами, биллингом, сервисами колл-трекинга, колл-бэка и модулями статистики и аналитики.

Позволяет телефонизировать офис за считанные часы.

В нашей практике были проекты, реализованные за один рабочий день. Это значит, что утром к нам обращался клиент, а уже через несколько часов он пользовался новой IP-АТС. Безусловно, такая скорость редкость, ведь АТС – инструмент зарабатывания денег для многих компаний и спешка во внедрении не уместна. Но в случае острой необходимости Asterisk готов к быстрому старту.

Отличная масштабируемость.

Очень утомительно постоянно возвращаться к одному и тому же вопросу. Такое часто бывает в случае некачественного исполнения работ или выбора заведомо неподходящего бизнес-решения. С Asterisk точно не будет такой проблемы! Телефонная станция, построенная на Asterisk может быть масштабируема до немыслимых размеров. Главное – правильно подобрать оборудование.

Повышает управляемость бизнеса.

Asterisk дает не просто набор полезных функций, он повышает управляемость организации, качества и комфортности управления, а также увеличивает прозрачность бизнеса для руководства. Достичь этого можно, например, за счет автоматизации отчетов, подключения бота в Telegram, санкционированного доступа к станции из любой точки мира.

Снижает расходы на связь.

Связь между внутренними абонентами IP-АТС бесплатна всегда, независимо от их географического расположения. Также к Asterisk можно подключить любых операторов телефонии, в том числе GSM сим-карты и настроить маршрутизацию вызовов по наиболее выгодному тарифу. Всё это позволяет экономить с первых минут пользования станцией.