Все началось с того, что мои друзья попросили подобрать CRM-систему для менеджеров оптовой компании. Мы потратили уйму времени на поиски, но ничего подходящего не нашли. В итоге я предложил написать написать свою CRM. Удивительно, но готовый продукт внедрили сразу, без долгой «раскачки». Система работает и выложена в открытом доступе. В этой статье я расскажу, как ее сделал, — возможно, кому-нибудь пригодится.
Видео с демонстрацией возможностей
Для начала посмотрите ролик, мы сняли его для менеджеров. Показали, как работает CRM и какие функции в ней реализованы.
Инициация исходящего звонка
Исходящий звонок клиенту инициировали с помощью Python AMI Client и
Asterisk Manager Interface (AMI)
Что сделали:
- На Астериске прописали логин и пароль для AMI в файле /etc/asterisk/manager.conf
- Перезапустили Астериск и открыли на нем порт 5038 для соединения с нашим CRM-сайтом
- На сервере с CRM поставили AMI Client командой:
pip install asterisk-ami
- Убедились, что все звонит с помощью этого кода:
from asterisk.ami import SimpleAction client = AMIClient(address='your-remote.freepbx.net',port=5038) client.login(username='username',secret='password') action = SimpleAction( 'Originate', Channel='SIP/974957777777', # Внешний городской номер, на который надо позвонить Exten='2010', # Инициатор вызова, внутренний номер на Астериске Priority=1, Context='default', CallerID='python', ) client.send_action(action)
Затем прикрутили результат к нашему сайту на Flask, в итоге с сайта можно звонить нажатием кнопки на мышке. Работает так: менеджер кликает и у него звонит SIP-телефон. Когда он берет трубку — начинается вызов клиенту.
Астерисков у ребят два: один основной, другой — резервный, на случай выхода из строя основного. Поэтому в файле aster.py я прописал проверку на доступность с помощью socket и try.
Прием входящего звонка
Когда у менеджера звонит SIP-телефон, CRM показывает ему окно, как на скриншоте. Пробовали разные варианты, но написали в итоге на Perl, с помощью модуля Asterisk::AMI и EV.
use EV;
use Asterisk::AMI;
#Create your connection
my $astman = Asterisk::AMI->new(
PeerAddr => '127.0.0.1',
PeerPort => '5038',
Username => 'admin',
Secret => 'supersecret',
Events => 'on',
Handlers => { default => &eventhandler }
);
#Alternatively you can set Blocking => 0, and set an on_error sub to catch connection errors
die "Unable to connect to asterisk" unless ($astman);
#Define the subroutines for events
sub eventhandler { my ( $ami, $event ) = @_; print 'Got Event: ', $event->{'Event'}, "rn"; }
#Define a subroutine for your action callback
sub actioncb { my ( $ami, $response ) = @_; print 'Got Action Reponse: ', $response->{'Response'}, "rn"; }
#Send an action
my $action = $astman->( { Action => 'Ping' }, &actioncb );
#Do all of you other eventy stuff here, or before all this stuff, whichever ..............
#Start our loop
EV::loop
Кому интересно, почитайте статью о событийно-ориентированном программировании — мне она помогла.
Идем дальше. На гитхабе лежит файл с названием amievent.pl. С его помощью происходит отлов входящих звонков, занесение в memcached и в mysql базу.
На сервере у нас стоит Centos 7.
Мы демонизировали этот процесс с помощью systemd:
[Unit]
Description=Crm dancer event incomming call
After=mysql.service
[Service]
Type=simple
User=crmdancer
Group=crmdancer
Restart=always
RestartSec=30
ExecStart=/home/crmdancer/crmdancer/amievent.pl
[Install]
WantedBy=multi-user.target
Работа с таблицей клиентов
Для таблицы с клиентами использовали DataTables.
Этот jquery-плагин делает из обычной html-таблицы таблицу с поиском, сортировкой, фильтрами, разбиением больших таблиц на страницы и другими полезными функциями.
Это работало с небольшим количеством клиентов. Как только импортировали большую базу, увеличилось время открытия страницы, а общий размер html-таблицы составил 12 000 строк.
Загуглили и нашли в документации следующее:
Обработка на стороне клиента с помощью DOM: ~5'000 строк
Обработка на стороне клиента с загрузкой данных через Ajax: ~50'000 строк
Обработка на стороне сервера — миллион строк
Переделали загрузку на Ajax. Загрузка страницы с 4-х секунд упала до >1 секунды.
Записи звонков
У нас не было технической возможности сжимать записи в mp3 на FreePBX.
Поэтому сделали rsync с FreePBX на сайт с CRM.
Принцип такой: файлы перебрасываются в wav формате, потом конвертируются в mp3. Конвертируем с помощью lame и perl-скрипта convertor.pl — он находит wav файлы, которые еще не сконвертированны. Файл length.pl создает csv-файлы в формате «название mp3-файла — время звучания». Для каждого дня свой csv-файл.
Календарь событий
Чтобы менеджерам было удобно вести план работ, записывать предстоящие дела и события, мы установили jquery-плагин fullcalendar. К нему дописали возможность хранения событий в mysql и цветовую идентификацию событий.
Как это реализовано можно посмотреть в коде на гитхабе.
Про темплейт
При верстке использовали замечательный темплейт админки:
https://github.com/puikinsh/gentelella
Очень красивый и удобный. Здесь демо-версия:
https://colorlib.com/polygon/gentelella/index.html
Единственный минус — меню слева по умолчанию развернуто. Чтобы получить дополнительное пространства, свернули вот так:
заменили на Других изменений не вносили, все работает из коробки.
CRMDANCER — установка и работа
- Ставим mysql и memcached
- Устанавливаем python 3.x
- Ставим виртуальное окружение
- Устанавливаем flask
- Копируем код с гитхаба
- Ставим все необходимые модули
- Создаем базу данных с готовой структурой, код sql на гитхабе
- Правим конфиги и прописываем свои логины и пароли
- Запускаем
P.S.
Бывает пишешь что-нибудь, используешь передовые технологии, а в итоге никто продуктом не пользуется. У нас были подобные истории, поэтому когда писали Crm Dancer, воспринимали его как прототип, не думали, что менеджерам понравится и они будут им пользоваться. Из-за этого использовали обычный ajax вместо websocket и признаем — в других местах можно было бы написать лучше.
В остальном все в порядке, скачивайте и пользуйтесь.
Автор: crmdancer