Bitrix24 adalah pemanen besar yang menggabungkan CRM, aliran dokumen, akuntansi, dan banyak hal lain yang sangat disukai manajer dan staf TI tidak terlalu menyukainya. Portal ini digunakan oleh banyak perusahaan kecil dan menengah, termasuk klinik kecil, pekerja manufaktur, dan bahkan salon kecantikan. Fungsi utama yang "disukai" oleh manajer adalah integrasi telepon dan CRM, ketika panggilan apa pun segera direkam di CRM, kartu pelanggan dibuat, informasi tentang pelanggan ditampilkan saat masuk, dan Anda dapat segera melihat siapa dia, apa yang dapat dia jual dan berapa banyak hutangnya. Tetapi telepon dari Bitrix24 dan integrasinya dengan CRM membutuhkan biaya, terkadang banyak. Dalam artikel ini saya akan memberi tahu Anda pengalaman integrasi dengan alat terbuka dan IP PBX FreePBX yang populer , dan juga mempertimbangkan logika pekerjaan berbagai bagian
Saya bekerja sebagai perusahaan outsourcing di sebuah perusahaan yang menjual dan mengkonfigurasi, mengintegrasikan telepon IP. Ketika saya ditanya apakah kami dapat menawarkan ini dan perusahaan ini sesuatu untuk mengintegrasikan Bitrix24 dengan PBX yang dimiliki pelanggan, serta dengan PBX virtual di berbagai perusahaan VDS, saya pergi ke Google. Dan, tentu saja, dia memberi saya tautan ke artikel di Habr , di mana ada deskripsi, dan github, dan semuanya tampak berfungsi. Tetapi ketika mencoba menggunakan solusi ini, ternyata Bitrix24 tidak seperti dulu lagi, dan banyak yang perlu diulang. Selain itu, FreePBX bukanlah tanda bintang telanjang untuk Anda, di sini Anda perlu memikirkan tentang cara menggabungkan kegunaan dan dialplan hardcore dalam file konfigurasi.
Kami mempelajari logika kerja
Jadi pertama, bagaimana cara kerjanya. Ketika panggilan datang dari luar PBX (acara SIP INVITE dari penyedia), pemrosesan dialplan (dialplan) dimulai - aturan tentang apa yang harus dilakukan dengan panggilan tersebut dan dalam urutan apa. Banyak informasi dapat diperoleh dari paket pertama, yang kemudian dapat digunakan dalam aturan. Alat yang sangat baik untuk mempelajari bagian dalam SIP adalah sngrep analyzer ( tautan ), yang hanya dipasang di distribusi populer melalui apt install / yum install dan sejenisnya, tetapi Anda juga dapat membangunnya dari sumber. Mari kita lihat log panggilan di sngrep

Dalam bentuk yang disederhanakan, dialplan hanya berurusan dengan paket pertama, terkadang selama percakapan, panggilan ditransfer, tombol ditekan (DTMF), berbagai hal menarik seperti FollowMe, RingGroup, IVR dan lainnya.
Apa yang ada di dalam paket Undang

DID CallerID. DID - , CallerID - .
- (/ ) (Ring Group), IVR (, ... ...), (Phrases), (Time Conditions), (FollowMe, Forward). , .

"". Asterisk - , ( exten, exten=DID). - ( - Dial(), - Hangup()), (IF, ELSE, ExecIF ), (Goto, GotoIF), (Gosub, Macro). include _, . , include .
FreePBX include Gosub, Macro Handler. FreePBX

, (Macro), (Gosub) (Goto), , .
. DID, , - . 1 . hangupcall, , (hangup handler).

CRM, , CRM?
CRM? , . API, API HTTP REST. asterisk.
Asterisk :
AMI
Event: Newchannel Privilege: call,all Channel: PJSIP/VMS_pjsip-0000078b ChannelState: 4 ChannelStateDesc: Ring CallerIDNum: 111222 CallerIDName: 111222 ConnectedLineNum: ConnectedLineName: Language: en AccountCode: Context: from-pstn Exten: s Priority: 1 Uniqueid: 1599589046.5244 Linkedid: 1599589046.5244
ARI
{ "variable":"CallMeCallerIDName", "value":"111222", "type":"ChannelVarset", "timestamp":"2020-09-09T09:38:36.269+0000", "channel":{ "id":"1599644315.5334", "name":"PJSIP/VMSpjsip-000007b6", "state":"Ring", "caller":{ "name":"111222", "number":"111222" }, "connected":{ "name":"", "number":"" }, "accountcode":"", "dialplan":{ "context":"from-pstn", "exten":"s", "priority":2, "appname":"Stasis", "appdata":"hello-world" }, "creationtime":"2020-09-09T09:38:35.926+0000", "language":"ru" }, "asteriskid":"48:5b:aa:aa:aa:aa", "application":"hello-world" }
, API , . CRM :
, , CallerID, DID, , ( CRM)
, ,
( ), ,
: CRM, FollowME ( CRM)
AMI ARI, ARI , , , AMI ( , , ). , - AMI ( ). ( , ) - ( ) PAMI. * ARI, .
, FreePBX AMI , , , , , - . PAMI -.
(s - , DID)
[ext-did-custom]
exten => s,1,Set(CallStart=${STRFTIME(epoch,,%s)})AMI
Event: Newchannel
Privilege: call,all
Channel: PJSIP/VMS_pjsip-0000078b
ChannelState: 4
ChannelStateDesc: Ring
CallerIDNum: 111222
CallerIDName: 111222
ConnectedLineNum:
ConnectedLineName:
Language: en
AccountCode:
Context: from-pstn
Exten: s
Priority: 1
Uniqueid: 1599589046.5244
Linkedid: 1599589046.5244
Application: Set AppData:
CallStart=1599571046
FreePBX extention.conf extention_additional.conf, extention_custom.conf
extention_custom.conf
[globals]
;; - asterisk
;;
WAV=/var/www/html/callme/records/wav
MP3=/var/www/html/callme/records/mp3
;;
URLRECORDS=https://www.host.ru/callmeplus/records/mp3
;;
URLPHP=https://www.host.ru/callmeplus
;;
RECORDING=1
;; .
;; , -
;;
[recording]
exten => ~~s~~,1,Set(LOCAL(calling)=${ARG1})
exten => ~~s~~,2,Set(LOCAL(called)=${ARG2})
exten => ~~s~~,3,GotoIf($["${RECORDING}" = "1"]?4:14)
exten => ~~s~~,4,Set(fname=${UNIQUEID}-${STRFTIME(${EPOCH},,%Y-%m-%d-%H_%M)}-${calling}-${called})
exten => ~~s~~,5,Set(datedir=${STRFTIME(${EPOCH},,%Y/%m/%d)})
exten => ~~s~~,6,System(mkdir -p ${MP3}/${datedir})
exten => ~~s~~,7,System(mkdir -p ${WAV}/${datedir})
exten => ~~s~~,8,Set(monopt=nice -n 19 /usr/bin/lame -b 32 --silent "${WAV}/${datedir}/${fname}.wav" "${MP3}/${datedir}/${fname}.mp3" && rm -f "${WAV}/${fname}.wav" && chmod o+r "${MP3}/${datedir}/${fname}.mp3")
exten => ~~s~~,9,Set(FullFname=${URLRECORDS}/${datedir}/${fname}.mp3)
exten => ~~s~~,10,Set(CDR(filename)=${fname}.mp3)
exten => ~~s~~,11,Set(CDR(recordingfile)=${fname}.wav)
exten => ~~s~~,12,Set(CDR(realdst)=${called})
exten => ~~s~~,13,MixMonitor(${WAV}/${datedir}/${fname}.wav,b,${monopt})
exten => ~~s~~,14,NoOp(Finish if_recording_1)
exten => ~~s~~,15,Return()
;;
[ext-did-custom]
;; , , - '8'
exten => s,1,Set(CALLERID(num)=8${CALLERID(num)})
;;
exten => s,n,Gosub(recording,~~s~~,1(${CALLERID(number)},${EXTEN}))
exten => s,n,ExecIF(${CallMeCallerIDName}?Set(CALLERID(name)=${CallMeCallerIDName}):NoOp())
exten => s,n,Set(CallStart=${STRFTIME(epoch,,%s)})
exten => s,n,Set(CallMeDISPOSITION=${CDR(disposition)})
;; ! .
;; (exten=>h,1,) FreePBX - Macro(hangupcall,) .
;; Hangup_Handler
exten => s,n,Set(CHANNEL(hangup_handler_push)=sub-call-from-cid-ended,s,1(${CALLERID(num)},${EXTEN}))
;;
[sub-call-from-cid-ended]
;;
exten => s,1,Set(CDR_PROP(disable)=true)
exten => s,n,Set(CallStop=${STRFTIME(epoch,,%s)})
exten => s,n,Set(CallMeDURATION=${MATH(${CallStop}-${CallStart},int)})
;; - , ...
exten => s,n,Set(CallMeDISPOSITION=${CDR(disposition)})
exten => s,n,Return
;; -
[outbound-allroutes-custom]
;;
exten => _.,1,Gosub(recording,~~s~~,1(${CALLERID(number)},${EXTEN}))
;;
exten => _.,n,Set(__CallIntNum=${CALLERID(num)})
exten => _.,n,Set(CallExtNum=${EXTEN})
exten => _.,n,Set(CallStart=${STRFTIME(epoch,,%s)})
exten => _.,n,Set(CallmeCALLID=${SIPCALLID})
;; Hangup_Handler
exten => _.,n,Set(CHANNEL(hangup_handler_push)=sub-call-internal-ended,s,1(${CALLERID(num)},${EXTEN}))
;;
[sub-call-internal-ended]
;;
exten => s,1,Set(CDR_PROP(disable)=true)
exten => s,n,Set(CallStop=${STRFTIME(epoch,,%s)})
exten => s,n,Set(CallMeDURATION=${MATH(${CallStop}-${CallStart},int)})
exten => s,n,Set(CallMeDISPOSITION=${CDR(disposition)})
;; , CRM - ,
;;
exten => s,n,System(curl -s ${URLPHP}/CallMeOut.php --data action=sendcall2b24 --data ExtNum=${CallExtNum} --data call_id=${SIPCALLID} --data-urlencode FullFname='${FullFname}' --data CallIntNum=${CallIntNum} --data CallDuration=${CallMeDURATION} --data-urlencode CallDisposition='${CallMeDISPOSITION}')
exten => s,n,Return-
.conf, FreePBX ( .ael, )
exten=>h hangup_handler, FreePBX
, ExtNum
_custom FreePBX - [ext-did-custom], [outbound-allroutes-custom]
-
AMI - FreePBX _custom
manager_custom.conf
;;
[callmeplus]
;;
secret = trampampamturlala
deny = 0.0.0.0/0.0.0.0
;; - ,
permit = 127.0.0.1/255.255.255.255
read = system,call,log,verbose,agent,user,config,dtmf,reporting,cdr,dialplan
write = system,call,agent,log,verbose,user,config,command,reporting,originate/etc/asterisk, ( )
# astrisk -rv
Connected to Asterisk 16.6.2 currently running on freepbx (pid = 31629)
#freepbx*CLI> dialplan reload
Dialplan reloaded.
#freepbx*CLI> exitPHP
24, AMI , . AMI . , . , PAMI , , ..
, NewExten [from-pstn], . _custom CallMeCallerIDName CallStart
UserID, , . ? , ( ) ? Fisrt Available, , .
24, CallID, . UserID

, (, , ), mp3 ( ).
CallMeIn.php , SystemD callme.service, /etc/systemd/system/callme.service
[Unit]
Description=CallMe
[Service]
WorkingDirectory=/var/www/html/callmeplus
ExecStart=/usr/bin/php /var/www/html/callmeplus/CallMeIn.php 2>&1 >>/var/log/callmeplus.log
ExecStop=/bin/kill -WINCH ${MAINPID}
KillSignal=SIGKILL
Restart=on-failure
RestartSec=10s
# ,
#User=www-data #Ubuntu - debian
#User=nginx #Centos
[Install]
WantedBy=multi-user.targetsystemctl service
# systemctl enable callme
# systemctl start callme( ). , php ( FeePBX). ( https) .
. CallMeOut.php :
php ( "" ). , HTTP POST,
, . Asterisk [sub-call-internal-ended]

- ( HTTPS) CallMeOut.php. FreePBX, /var/www/html, .
(, , ). , FreeDomain( https://www.freenom.com/ru/index.html), IP ( 80, 443 , ). DNS , ( 15 48 ) . - 1 .
github , . - , , , . (
Docker
- Docker - , , ( LetsEncrypt , , FreePBX ( - 88), LetsEncrypt
( git clone), ( asterisk) URL
version: '3.3'
services:
nginx:
image: nginx:1.15-alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/ssl_docker.conf:/etc/nginx/conf.d/ssl_docker.conf
certbot:
image: certbot/certbot
freepbx:
image: flaviostutz/freepbx
ports:
- 88:80 #
- 5060:5060/udp
- 5160:5160/udp
- 127.0.0.1:5038:5038 # CallMeOut.php
# - 3306:3306
- 18000-18100:18000-18100/udp
restart: always
environment:
- ADMIN_PASSWORD=admin123
volumes:
- backup:/backup
- recordings:/var/spool/asterisk/monitor
- ./callme:/var/www/html/callme
- ./systemd/callme.service:/etc/systemd/system/callme.conf
- ./asterisk/manager_custom.conf:/etc/asterisk/manager_custom.conf
- ./asterisk/extensions_custom.conf:/etc/asterisk/extensions_custom.conf
# - ./conf/startup.sh:/startup.sh
volumes:
backup:
recordings:
docker-compose.yaml,
docker-compose up -d
nginx , nginx/ssl_docker.conf
CRM , . API CRM, - ShugarCRM Vtiger, ! , . , .
: ,