Bitrix24 est un énorme moissonneur qui combine le CRM, le flux de documents, la comptabilité et bien d'autres choses que les gestionnaires aiment vraiment et que le personnel informatique n'aime pas vraiment. Le portail est utilisé par de nombreuses petites et moyennes entreprises, y compris de petites cliniques, des travailleurs de la fabrication et même des salons de beauté. La fonction principale que les managers «adorent» est l'intégration de la téléphonie et du CRM, lorsqu'un appel est immédiatement enregistré dans le CRM, des cartes client sont créées, des informations sur le client sont affichées à l'entrée, et vous pouvez voir immédiatement qui il est, ce qu'il peut vendre et combien il doit. Mais la téléphonie de Bitrix24 et son intégration avec le CRM coûte de l'argent, parfois beaucoup. Dans l'article, je vais vous raconter l'expérience de l'intégration avec des outils ouverts et le populaire IP PBX FreePBX , et également considérer la logique du travail de diverses parties
Je travaille comme entreprise d'externalisation dans une entreprise qui vend et configure, intègre la téléphonie IP. Quand on m'a demandé si nous pouvions offrir ceci et cette société quelque chose pour intégrer Bitrix24 avec les PBX que les clients ont, ainsi qu'avec les PBX virtuels sur diverses sociétés VDS, je suis allé chez Google. Et il, bien sûr, m'a donné un lien vers un article dans Habr , où il y a une description, et github, et tout semble fonctionner. Mais en essayant d'utiliser cette solution, il s'est avéré que Bitrix24 n'est plus ce qu'il était et que beaucoup de choses doivent être refaites. De plus, FreePBX n'est pas un astérisque nu pour vous, vous devez ici réfléchir à la façon de combiner la convivialité et un plan de numérotation hardcore dans les fichiers de configuration.
Nous étudions la logique du travail
Alors d'abord, comment est-il censé fonctionner. Lorsqu'un appel arrive de l'extérieur vers le PBX (événement SIP INVITE du fournisseur), le traitement du plan de numérotation (plan de numérotation, plan de numérotation) commence - les règles de ce qu'il faut faire avec l'appel et dans quel ordre. De nombreuses informations peuvent être obtenues à partir du premier package, qui peuvent ensuite être utilisées dans les règles. Un excellent outil pour étudier l'intérieur de SIP est l'analyseur sngrep ( lien ), qui est simplement installé dans les distributions populaires via apt install / yum install et autres, mais vous pouvez également le construire à partir des sources. Voyons le journal des appels dans sngrep
Dans une forme simplifiée, le plan de numérotation ne traite que du premier paquet, parfois pendant la conversation, les appels sont transférés, les boutons enfoncés (DTMF), diverses choses intéressantes comme FollowMe, RingGroup, IVR et autres.
Contenu du package Invite
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> exit
PHP
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.target
systemctl 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, ! , . , .
: ,