FreePBX: Возвращаем абонента к оператору, с которым он уже разговаривал.

Обычная для коллцентра ситуация: Абонент звонит в компанию; трубку поднимает оператор; разговор по какой-то причине прерывается (возможно, абоненту необходимо что-то сделать, а затем перезвонить) и вот же беда! Абонент перезванивает, и попадает совсем к другому оператору, которому заново приходится всё объяснять. Казалось бы, столь типичная задача, сделать так, чтобы абонент попадал к одному и тому же оператору после перезвона. Но почему-же в стоковом комплекте и даже среди платных модулей нет решения этой проблемы?

“Не, ну ё-моё!” Подумал я, и решил запилить своеобразный протез, закрывающий этот недостаток.
Можете закидать меня тапками, но я буду использовать любимый php. Тем более, что на сервере с freepbx он всегда есть! Кстати, я нашёл практически готовое решение в инете, но я нихрена не понял как оно работает и не смог им воспользоваться. :)) Полагаю, эта статья будет полезна тем, кто наступил примерно на те же грабли.

Первое, что нам необходимо сделать, это найти директорию, где у asterisk располагаются AGI скрипты. Это может быть /var/lib/asterisk/agi-bin/ или, как у меня на домашнем сервере, /usr/share/asterisk/agi-bin/. Короче, искать надо agi-bin. Cоздаём в этой папке скрипт, например mymanager.php и пихаем в него следующее содержимое:

#!/usr/bin/php -q
<?php
$prio = true; // Приоритет исходящих от операторов звонков (true|false)
$interval = 24; // Период, после которого фиксация абонента за определённым менеджером будет снята (в часах)
$timeout = 15; // Сколько секунд абоненту ждать ответа оператора?
$queue = 199; // Номер очереди, куда нужно вернуть абонента, если звонок на его оператора не удался.

// Подготавливаем PDO и подключаемся к базе
	$user = 'freepbxuser';
	$pass = 'Пароль юзера базы'; // Настройки базы обычно лежат в /etc/freepbx.conf. Копируем их оттуда.
	
	$dsn = "mysql:host=localhost;dbname=asteriskcdrdb;charset=utf8";
	$opt = [
		PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
		PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
		PDO::ATTR_EMULATE_PREPARES   => false,
	];
	try {$pdo = new PDO($dsn, $user, $pass, $opt);} catch (PDOException $e) { die('DB connection failed: ' . $e->getMessage());}

require('phpagi.php'); // Цепляем класс работы с AGI. Он есть в папке agi-bin "из коробки"
$agi = new AGI();
$cid = $agi->request['agi_callerid'];
		
if(strlen($cid) > 8){ // на всякий случай, проверяем, что за звонок к нам прилетел. Внешние, по идее, не могут быть короче 9 символов.
	 $cid = $agi->request['agi_callerid']; // забираем CID звонящего
	 // Ищем в базе CDR, не звонил ли нам этот номер ранее, но не позднее указанного в настройках интервала. Нас интересуют состоявшиеся звонки, длительностью не короче 5 секунд (можно увеличить).
	 $stmt = $pdo->prepare('select dst from cdr where calldate > now() - interval ? hour and cnum = ? and disposition="ANSWERED" and duration > 5 and lastapp = "Dial" and dcontext = "ext-local" order by `calldate` desc limit 1');
	 $stmt->execute(Array($interval,$cid));
	 if($row = $stmt->fetch()){
		$operator = $row['dst'];
	 } else {
		 $prio = true;
	 }
	 // А теперь проверяем, не звонил ли сам оператор на этот номер? В настройке выше можно включить или выключить приоритет полученных из исходящих звонков данных
	 if($prio == true){
		 $stmt = $pdo->prepare('select cnum from cdr where calldate > now() - interval ? hour and dst = ?  and lastapp = "Dial" order by `calldate` desc limit 1;');
		 $stmt->execute(Array($interval,$cid));
		 if($row = $stmt->fetch()){
			if($row['cnum'] > 0){
				$operator = $row['cnum'];
			}
		 }
	 }
	// Если мы получили номер какого-либо оператора - пытаемся его вызвать.
	 if($operator > 0){
		$agi->exec('Dial',"Local/".$operator."@from-internal,".$timeout.",g");
	 }
	 // Если ничего не получилось, или если оператор сам сбросил звонок, либо по заранее указанному таймауту, передаём звонок в заранее указанную очередь к другим операторам. 
	 $dialstatus = $agi->get_variable('DIALSTATUS');
	if ( $dialstatus != 'ANSWERED' ) {
	   $agi->exec('Goto',"ext-queues,".$queue.",1");
	}
} 
// Говорим астериску, что всё норм и скрипт выполнился без ошибок.
exit(0);
?>

Передаём этот скрипт юзеру и группе asterisk с правами на чтение и исполнение. Теперь надо подключить его к нашей АТС.

Если у вас не установлен модуль Misc Destinations – установите его в разделе Module Admin.

Переходим в меню Applications > Misc Destinations и добавляем новое назначение. В имени указываем что угодно, в поле Dial указываем свободный номер назначения, который далее будет использован в контексте. Допустим, у меня это будет 96.

Теперь нам нужно открыть файл /etc/asterisk/extensions_custom.conf и добавить туда следующее:

[from-internal]
exten => 96,1,Noop(Running miscapp 1: mymanager)
exten => 96,n,AGI(mymanager.php, ${CALLERID(number)})
exten => 96,n,Hangup()

Сохраняем, ждём кнопку Apply Config, тестим, откупориваем бутылку пенного за здоровье админа. ;)