Сегодня поговорим об интеграции Asterisk'a с CRM системой. Со стороны CRM вопрос рассматривать не будем, достаточно знать, что CRM хочет знать о всех звонках (как входящих, исходящих, так и переведенных)
Что мы хотим получить:
Для каждого звонка должны отправляться в CRM 2 события: start и stop. Естественно с кучей аргументов.
Для этого будем вызывать sh скрипт:
System(/etc/asterisk/scripts/crm.sh start ${GROUP} ${DSTNUM} ${CIDNUM} ${ID}); Начало звонка
System(/etc/asterisk/scripts/crm.sh stop ${GROUP} ${DSTNUM} ${CIDNUM} ${ID} ${CDUR}); Окончание
Содержимое самого скрипта нам неинтересно, смысл лишь в том, что он передает эти аргументы уже самой CRM, а иногда даже что-то возвращает обратно.
${GROUP} - Имя отдела/организации
${DSTNUM} - Номер, на который осуществляется звонок (используется для вывода popup окна у менеджера)
${CIDNUM} - Номер звонящего
${ID} - ID звонка (будет нас сопровождать на протяжении всего времени, пока вызывающий не повесит трубку). В качестве ID будем использовать ${UNIQUEID} самого первого вызова
${CDUR} - Время разговора
Эти два скрипта мы запишем в макрос (кстати макросы в ael работают/вызываются немного иначе чем в extensions.conf, для этого используется команда Gosub. Об этом нужно знать, если хочешь вызвать макрос, например, в команде Dial в момент поднятия трубки абонентом B. Т.е. использовать не M(x) как в extensions.conf, а U(x))
macro crm (GROUP, DSTNUM, CIDNUM, ID) {
System(/etc/asterisk/scripts/crm.sh start ${GROUP} ${DSTNUM} ${CIDNUM} ${ID});
return;
}
macro crm-end (GROUP, DSTNUM, CIDNUM, ID) {
MSet(CDUR=${CDR(billsec)});
System(/etc/asterisk/scripts/crm.sh stop ${GROUP} ${DSTNUM} ${CIDNUM} ${ID} ${CDUR});
return;
}
Для организации исходящих звонков задача оказалось элементарной, приведу лишь листинг, останавливаться тут не будем
context outgoing {
_X. => {
Set(GROUP=office1);
Set(DSTNUM=${EXTEN});
Set(CIDNUM=${CALLERID(num)});
Set(ID=${UNIQUEID});
&record-crm(${STRFTIME(,,%G/%m/%d/)}${ID});
&crm(${GROUP},${DSTNUM},${CIDNUM},${ID});
Dial(SIP/trunk/${DSTNUM});
};
}
Правила обработки входящих звонков немного интересней. Встречает нас приветствие с предложением набрать добавочный номер, если номер не набран, то отправляем звонок в CRM:
context inc {
2758725 => {
MSet(DSTNUM=${EXTEN});
MSet(GLOBAL(GROUP)=office1);
Gosub(inc-crm,s,1(${EXTEN},${CALLERID(num)},${UNIQUEID})) ;
};
}
context inc-crm {
s => {
MSET(DSTNUM=${ARG1});
MSET(GLOBAL(CIDNUM)=${ARG2});
MSet(GLOBAL(ID)=${ARG3});
Background(welcome_message);
&crm(${GROUP},${DSTNUM},${CIDNUM},${ID});
Gosub(redirect,${DB(crm/key)},1);
};
}
Контекст inc нам нужен на случай, если у нас больше, чем один номер для входящих звонков, часть переменных назначаем глобально, так как они нас будут сопровождать в разных частях диалплана.
&crm(${GROUP},${DSTNUM},${CIDNUM},${ID});
Отправляем первый запрос в CRM. Если клиент постоянный, то CRM вернет номер ответственного менеджера, и наш скрипт запишет его в AstDB (asterisk -rx «database put crm key XXX»). Если клиент новый или ему не назначен ответственный менеджер, то CRM вернет номер группы/очереди (это и доставило немного хлопот в плане передачи start/stop запросов)
Получив заветную комбинацию цифр из CRM, мы отправляемся дальше.
context redirect {
1000 => {
&record-crm(${STRFTIME(,,%G/%m/%d/)}${ID});
&crm-group(${QUEUE_MEMBER_LIST(first)},,);
Queue(first,tT,,,15,,,set-arg);
};
_1XX => {
&record-crm(${STRFTIME(,,%G/%m/%d/)}${ID});
&crm(${GROUP},${DSTNUM},${CIDNUM},${ID});
Dial(SIP/${EXTEN});
};
h => {
NoOP(${MEMBERINTERFACE});
if ( ${EXISTS(${MEMBERINTERFACE})} ) {
MSet(DSTNUM=${MEMBERINTERFACE:4});
&crm-end(${GROUP},${DSTNUM},${CIDNUM},${ID});
Hangup;
};
&crm-group-end(${QUEUE_MEMBER_LIST(first)},,);
};
}
1000 — номер группы, тут будет вызываться очередь. В нашем примере очередь будет ограничена 3 агентами. При желании увеличить число агентов очереди, нужно добавить запятые после ${QUEUE_MEMBER_LIST(first)} и несколько строк в macro crm-group/crm-group-end. Запятые нужны для вызова макроса, т.е. нужно передавать столько аргументов, сколько ждет макрос, точнее передать можно и больше аргументов, но если об этом упомянуть в диалплане, то он ругнется и работать не будет. ${QUEUE_MEMBER_LIST(first)} Содержит агентов в очереди, в итоге получится, что мы передаем в макрос более 3 аргументов (агенты + два пустых аргумента), но это уже не проблема. Тут главное, чтобы агентов не было больше, чем принимаемых аргументов в макросе, иначе не все получат popup окно.
macro crm-group ( member1, member2, member3 ) {
if (${EXISTS(${member1:4})) {
MSet(DSTNUM=${member1:4}));
&crm(${GROUP},${DSTNUM},${CIDNUM},${ID});
};
if (${EXISTS(${member2:4})) {
MSet(DSTNUM=${member2:4});
&crm(${GROUP},${DSTNUM},${CIDNUM},${ID});
};
if (${EXISTS(${member3:4})) {
MSet(DSTNUM=${member3:4});
&crm(${GROUP},${DSTNUM},${CIDNUM},${ID});
};
return;
}
Проверяем есть ли агент, назначаем его как принимающего вызов и отправляем запрос на вывод всплывающего окна.
После чего у нас уходит звонок в очередь first
Queue(first,tT,,,15,,,set-arg);
Тут главное не запутаться в запятых. В правилах хабра вроде написано, что не следует использовать смайлы/скобки, но тут я не удержался =)
Макрос set-arg вызываем командой Gosub, если бы макрос был записан в extensions.conf, то вызов писали бы на запятую раньше.
macro set-arg () {
&crm-group-end(${QUEUE_MEMBER_LIST(first)},,);
return;
}
Передать аргументы из команды Queue у меня не получилось, потому и пришлось прибегнуть к этому небольшому макросу.
macro crm-group-end ( member1, member2, member3 ) {
if ( ${EXISTS(${member1:4}) ) {
if (${member1:4}!=${CHANNEL(peername)}){
MSet(DSTNUM=${member1:4});
&crm-end(${GROUP},${DSTNUM},${CIDNUM},${ID});
};
};
if ( ${EXISTS(${member2:4}) ) {
if (${member2:4}!=${CHANNEL(peername)}){
MSet(DSTNUM=${member2:4});
&crm-end(${GROUP},${DSTNUM},${CIDNUM},${ID});
};
};
if ( ${EXISTS(${member3:4}) ) {
if (${member3:4}!=${CHANNEL(peername)}){
MSet(DSTNUM=${member3:4});
&crm-end(${GROUP},${DSTNUM},${CIDNUM},${ID});
};
};
return;
}
Опять же, как и при запросе start, проверяем наличие агентов, сравниваем агента очереди с тем, кто снял трубку (${CHANNEL}) и, если нет совпадения, то отправляем stop (больше всплывающее окно нам не нужно).
Менеджер поговорил с клиентом, разговор закончен, и нам нужно об этом сообщить нашей CRM. Возвращаемся к контексту redirect
h => {
if ( ${EXISTS(${MEMBERINTERFACE})} ) {
MSet(DSTNUM=${MEMBERINTERFACE:4});
&crm-end(${GROUP},${DSTNUM},${CIDNUM},${ID});
Hangup;
};
&crm-group-end(${QUEUE_MEMBER_LIST(first)},,);
};
Тут мы проверяем наличие ${MEMBERINTERFACE}, другими словами, был ли звонок обработан одним из агентов, если да, то отправляем в CRM stop для этого агента, если звонок сорвался, то отправляем stop для всех агентов очереди.
Осталось нам рассмотреть вариант с переводом звонка. Чтобы не терять клиентов при переводе, используем attended transfer (в features.conf я назначил для него #). Чтобы дополнить трансфер функцией отправки запросов в CRM, переназначим для него контекст. В моем случае это делается для всех вызовов, потому я просто в секции globals добавил TRANSFER_CONTEXT=transfer;
Перейдем к самому контексту:
context transfer {
_1XX => {
MSET(DSTNUM=${EXTEN});
&crm(${GROUP},${DSTNUM},${CIDNUM},${ID});
Dial(SIP/${DSTNUM});
};
h => {
&crm-end(${GROUP},${DSTNUM},${CIDNUM},${ID});
};
}
Получаем входящий вызов, жмем #100, в CRM уходит start c номером 100 (на кого переводим звонок) и номером клиента. Менеджер с номером 100 получает popup окно, его коллега может добавить пару слов о настроении клиента и положить трубку. Трансфер совершен. На этом этапе отправится запрос stop для вызова одного менеджера другому.
По завершению разговора менеджера с клиентом отправится два запроса stop — для первого и второго менеджера, ведь они оба поговорили с клиентом.
Автор: системный администратор компании Centos-admin.ru artzcom.