Pull to refresh

Пишем примитивную биллинг-статистику для Asterisk

Reading time5 min
Views17K
Как известно, Asterisk может хранить детализацию звонков, она же CDR (Call Detail Records). CDR может храниться разными способами, это и обычный csv-файл и практически любая база данных. У нас, например это обычная база на MySQL, с одной единственной табличкой — cdr. Была поставлена цель: написать скрипт который бы считал стоимость звонков.

Этап 1: изучаем структуру таблицы cdr и что в ней хранится


image
Нам интересны поля:
  • src- источник;
  • dst – назначение;
  • billsec – тарифицируемые секунды (секунды после снятия трубки);
  • channel — используемый канал;
  • dstchannel – канал направления;
  • calldate – дата и время;
  • uniqueid – уникальный идентификатор;
  • disposition — что случилось с вызовом: ANSWERED, NO ANSWER, BUSY, FAILED.
  • userfield – свободное поле.

Этап 2: Создаём свои таблицы.

price_russia – таблица с кодами регионов россии и ценами.
CREATE TABLE IF NOT EXISTS `price_russia ` (
  `code` int(10) NOT NULL COMMENT 'Код области',
  `cost` varchar(10) NOT NULL COMMENT 'Цена',
  `region` varchar(100) NOT NULL COMMENT 'Регион',
  UNIQUE KEY `code` (`code`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;


price_international – таблица с кодами стран и ценами.
CREATE TABLE IF NOT EXISTS ` price_international` (
  `code` int(10) NOT NULL COMMENT 'Код страны',
  `price` varchar(10) NOT NULL COMMENT 'Цена',
  `country` varchar(100) NOT NULL COMMENT 'Страна',
  UNIQUE KEY `code` (`code`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;


clients — таблица с клиентами.
CREATE TABLE IF NOT EXISTS `clients` (
  `login` varchar(32) NOT NULL COMMENT 'Логин',
  `password` varchar(32) NOT NULL COMMENT 'Пароль',
  `email` varchar(40) NOT NULL COMMENT 'Email',
  `rate` smallint(4) NOT NULL COMMENT 'Множитель',
  UNIQUE KEY `login` (`login`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

* поле rate позволяет давать скидку или ставить наценку для клиента (пишем 90 – даём скидку в 10 процентов, ставим 110 – получаем наценку в 10 процентов).

clients_ext – сопоставление клиентов и экстеншенов.
CREATE TABLE IF NOT EXISTS `clients_ext` (
  `login` varchar(32) NOT NULL,
  `ext` int(6) NOT NULL,
  UNIQUE KEY `ext` (`ext`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;


сalls – таблица с обработанными звонками.
CREATE TABLE IF NOT EXISTS `calls` (
  `uniqueid` varchar(32) NOT NULL,
  `date` datetime NOT NULL,
  `login` varchar(32) NOT NULL,
  `rate` bigint(10) NOT NULL,
  `ext` bigint(10) NOT NULL,
  `dst` bigint(20) NOT NULL,
  `src` bigint(20) NOT NULL,
  `type` varchar(20) NOT NULL,
  `minutes` int(10) NOT NULL,
  `seconds` int(100) NOT NULL,
  `cost` int(10) NOT NULL,
  `description` varchar(100) NOT NULL,
  UNIQUE KEY `uniqueid` (`uniqueid`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

*type – тип звонка (входящий/исходящий).

Этап 3: Обработка данных.



Предполагается что данный скрипт будет запускаться по расписанию, и что бы ничего не пропустить, в начале выполнения скрипта мы помечаем необработанные строки, для того что бы обрабатывать только их, и не трогать те которые могут появится пока выполняется скрипт:
mysql_query("UPDATE cdr SET userfield=`step1` WHERE userfield=``;");


Первое что мы делаем – это загружаем в массив информацию о:
1. Клиентах, их экстеншенах, группах и множителе.

function get_clients(){ // На выходе получаем массив с экстеншенами, кому они принадлежат и какой там множитель.
	$query=mysql_query("SELECT rate,ext,login FROM clients, clients_ext WHERE clients_ext.login = clients.login");
	while($row=mysql_fetch_assoc($query)){
		$clients[$row['ext']]['rate']=$row['rate']; 
		$clients[$row['ext']]['user']=$row['login'];
	}
	return $clients;	
}


2. Коды регионов, описание и цены по России.

function get_price_russia(){ // Получаем массив с ценами по России
	$query=mysql_query("SELECT * FROM price_russia");
	while($row=mysql_fetch_assoc($query)){
		$price[$row['code']]['cost']=$row['price'];
		$price[$row['code']]['description']=$row['region'];	
	}	
	return $price;
}


3. Коды стран, описание и цены по миру.
function get_price_international(){ //Получаем массив с ценами по миру.
	$query=mysql_query("SELECT * FROM price_international");
	while($row=mysql_fetch_assoc($query)){
		$price[$row['code']]['cost']=$row['price'];
		$price[$row['code']]['description']=$row['country'];	
	}	
	return $price;
}


Соответственно делаем мы вот что:

$clients=get_clients();
$price['russia']=get_price_russia();
$price['international']=get_price_international();


Теперь можем выбрать из базы, отмеченные строки и начать обработку.
$query=mysql_query("SELECT * FROM cdr WHERE userfield=`step1`");
while($row=mysql_fetch_assoc($row)){
//обработка
}


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

Определяем направление звонка:
function get_call_type($dst,$src){
	$dst=strlen($dst);
	$src=strlen($src);
	if($scr<7 && $dst<7)$type='internal'; //Внутренний вызов.
	if($src<7 && $dst>=7)$type='outcoming'; // Исходящий
	if($src>=7 && $dst<7)$type='incoming'; //Входящий
	return $type;
}
$type=get_call_type($row['dst'],$row['src']);


Теперь нам нужно определить экстеншен, по которому мы сможем определить клиента. Экстеншен можно извлечь из канала, в таблице cdr — это поля «channel» при исходящем вызове и «dstchannel» при входящим.

function get_ext_from_channel($channel){ // Достаём ext из channel
	$channel=split("/",$channel);
	$channel=split("-",$channel[1]);
	return($channel[0]);	
}

switch ($type) {
    case 'internal':
        $ext=$row['src'];
        break;
    case 'incoming':
        $ext=get_ext_from_channel($row['dstchannel']);
        break;
    case 'outcoming':
        $ext=get_ext_from_channel($row['channel']);
        break;
}

* знаю что тут лучше использовать регулярные выражения, но…

Теперь определяем какому клиенту принадлежит экстеншен.
$login=$clients[$ext]['login']

и множитель
$rate=$clients[$ext]['rate']

Округляем секунды до минут:
$minutes=ceil($row['billsec']/60);


Если звонок внутренний или входящий, цену можно установить в ноль, а описание оставить пустым. А в противном случае нужно определить куда ушёл звонок.

if($type=='outcoming'){
	//Определени звонка.
}else{
	$cost=0;
	$description=0;
}

Определяем цену звонка:

//Дописываем 8495 если в номере только 7 цифр. 
function check_for_moscow($num){
	if(strlen($num)==7)$num='8495'.$num;
	return $num;
}
$dst=check_for_moscow($row['dst']);

//Теперь определим какой применять прайс, российский или международный. Мы применили в настройках атс стандартную схему: что вход на международку через 10. Поэтому номера в которых 11 и меньше цифр - российские, а больше - международные.
function get_country_type($number){
	if(strlen($number)<=11){
		$return='russia';	
	}else{
		$return='international';	
	}
	return $return;
}
$country=get_country_type($dst);

$cost='';
$description='';
$i=1;

//Если Россия - то код региона начинается со второга символа (8xxx), если международный то с 4 (810xxx)
if($country=='international'){$s=3;}else{$s=1;}

//В коде города от трёх цифр, кроме тех у которых код начинается с 9 - это сотовые. Международные коды 2-3 цифры.
while($cost==''){
	$code=substr($dst,$s,$i);
	$cost=$price[$country][$code]['cost'];
	$description=$price[$country][$code]['description'];
	$i++;
}


Теперь мы имеем всё что бы, записать информацию о звонке в таблицу.

mysql_query("INSERT INTO calls(uniqueid, date, login, rate, ext, dst, src, type, minutes, seconds, cost, description) 
		VALUES
		('$row[uniqueid]', '$row[calldate]', '$login', '$rate', '$ext', '$dst', '$row[src]', '$type', '$minutes', '$row[billsec]', '$cost', '$description')");


И в самом конце, пометить строки как полностью в обработанные:

mysql_query("UPDATE cdr SET userfield=`done` WHERE userfield=`step1`");


Вышенаписанное писалось под Москву, но легко может быть переделано под другой город или страну.
Ссылка на исходники: 77.108.85.102/habr/import.php.txt, 77.108.85.102/habr/functions.php.txt,
Tags:
Hubs:
Total votes 25: ↑21 and ↓4+17
Comments14

Articles