Forstå FreePBX og integrere den med Bitrix24 og mer

Bitrix24 er en enorm kombinasjon som kombinerer CRM, arbeidsflyt, regnskap og mange andre ting som ledere virkelig liker og IT-ansatte egentlig ikke liker. Portalen brukes av mange små og mellomstore bedrifter, inkludert små klinikker, produsenter og til og med skjønnhetssalonger. Hovedfunksjonen som ledere "elsker" er integrasjonen av telefoni og CRM, når en samtale umiddelbart registreres i CRM, opprettes klientkort, ved innkommende vises informasjon om klienten, og du kan umiddelbart se hvem han er, hva han kan selge og hvor mye han skylder. Men telefoni fra Bitrix24 og dets integrasjon med CRM koster penger, noen ganger mye. I artikkelen vil jeg fortelle deg opplevelsen av å integrere med åpne verktøy og den populære IP PBX GratisPBX, og også vurdere logikken i arbeidet til ulike deler

Jeg jobber som outsourcer i et firma som selger og konfigurerer, integrerer IP-telefoni. Da jeg ble spurt om vi kunne tilby noe til dette og dette selskapet for å integrere Bitrix24 med PBX-er som kundene har, samt med virtuelle PBX-er på ulike VDS-selskaper, gikk jeg til Google. Og selvfølgelig ga han meg en link til artikkel i habr, hvor det er en beskrivelse, og github, og alt ser ut til å fungere. Men ved forsøk på å bruke denne løsningen viste det seg at Bitrix24 ikke lenger er det samme som før, og mye må gjøres om. I tillegg er FreePBX ikke en bare stjerne for deg, her må du tenke på hvordan du kan kombinere brukervennlighet og en hardcore dialplan i konfigurasjonsfiler.

Vi studerer logikken i arbeidet

Så for det første, hvordan det hele skal fungere. Når et anrop mottas fra utsiden av PBX (SIP INVITE-hendelse fra leverandøren), starter behandlingen av oppringingsplanen (oppringingsplan, oppringningsplan) - reglene for hva og i hvilken rekkefølge man skal gjøre med samtalen. Fra den første pakken kan du få mye informasjon, som deretter kan brukes i reglene. Et utmerket verktøy for å studere det indre av SIP er analysatoren sngrep (link) som ganske enkelt installeres i populære distribusjoner via apt install/yum install og lignende, men den kan også bygges fra kilden. La oss se på anropsloggen i sngrep

Forstå FreePBX og integrere den med Bitrix24 og mer

I en forenklet form omhandler telefonplanen kun den første pakken, noen ganger også under samtalen, samtaler overføres, knappetrykk (DTMF), forskjellige interessante ting som FollowMe, RingGroup, IVR og andre.

Hva er inne i invitasjonspakken

Forstå FreePBX og integrere den med Bitrix24 og mer

Faktisk fungerer de fleste enkle oppringningsplaner med de to første feltene, og hele logikken dreier seg om DID og CallerID. DID - hvor vi ringer, CallerID - hvem ringer.

Men vi har tross alt et selskap og ikke én telefon - noe som betyr at hussentralen mest sannsynlig har anropsgrupper (samtidig / fortløpende ringing av flere enheter) på bynummer (Ringgruppe), IVR (Hei, du ringte ... Trykk en for ...), telefonsvarer (setninger), tidsbetingelser, videresending til andre numre eller til en celle (FollowMe, Forward). Dette betyr at det er svært vanskelig å entydig fastslå hvem som faktisk skal motta en samtale og hvem som skal ha en samtale med når en samtale kommer. Her er et eksempel på begynnelsen av en typisk samtale i PBX-en til våre kunder

Forstå FreePBX og integrere den med Bitrix24 og mer

Etter at samtalen har kommet inn i PBX-en, går den gjennom oppringingsplanen i forskjellige "kontekster". Konteksten fra Asterisks synspunkt er et nummerert sett med kommandoer, som hver inneholder et filter etter det oppringte nummeret (det kalles exten, for en ekstern samtale i det innledende stadiet exten=DID). Kommandoene i oppringingslinjen kan være hva som helst - interne funksjoner (for eksempel ringe en intern abonnent - Dial(), legg telefonen fra seg - Hangup()), betingede operatører (IF, ELSE, ExecIF og lignende), overganger til andre regler i denne sammenhengen (Goto, GotoIF), overgang til andre kontekster i form av et funksjonskall (Gosub, Makro). Et eget direktiv include имя_контекста, som legger til kommandoer fra en annen kontekst til slutten av gjeldende kontekst. Kommandoer inkludert via include blir alltid utført etter kommandoer for gjeldende kontekst.

Hele logikken til FreePBX er bygget på inkludering av ulike kontekster i hverandre gjennom inkludere og ringe gjennom Gosub, Macro og Handler-behandlere. Vurder konteksten for innkommende FreePBX-anrop

Forstå FreePBX og integrere den med Bitrix24 og mer

Anropet går gjennom alle kontekster fra topp til bunn etter tur, i hver kontekst kan det være anrop til andre kontekster som makroer (Macro), funksjoner (Gosub) eller bare overganger (Goto), så det virkelige treet til det som kalles kan bare spores i loggene.

Et typisk oppsettdiagram for en typisk PBX er vist nedenfor. Ved oppringing søkes DID i innkommende ruter, midlertidige forhold sjekkes for det, hvis alt er i orden, startes talemenyen. Fra den, ved å trykke på knapp 1 eller timeout, gå ut til gruppen med oppringingsoperatører. Etter at samtalen er avsluttet, kalles hangupcall-makroen, hvoretter ingenting kan gjøres i oppringingsplanen, bortsett fra spesielle behandlere (hangup-handler).

Forstå FreePBX og integrere den med Bitrix24 og mer

Hvor i denne samtalealgoritmen skal vi gi informasjon om begynnelsen av samtalen til CRM, hvor vi skal starte opptaket, hvor vi skal avslutte opptaket og sende det sammen med informasjon om samtalen til CRM?

Integrasjon med eksterne systemer

Hva er PBX og CRM-integrasjon? Dette er innstillinger og programmer som konverterer data og hendelser mellom disse to plattformene og sender dem til hverandre. Den vanligste måten for uavhengige systemer å kommunisere på er gjennom APIer, og den mest populære måten å få tilgang til APIer på er HTTP REST. Men ikke for stjerne.

Inne i stjernen er:

  • AGI - synkront anrop av eksterne programmer/komponenter, brukes hovedsakelig i oppringingsplanen, det er biblioteker som phpagi, PAGI

  • AMI - en tekst TCP-kontakt som fungerer etter prinsippet om å abonnere på hendelser og skrive inn tekstkommandoer, ligner SMTP fra innsiden, kan spore hendelser og administrere samtaler, det er et bibliotek PAMI - den mest populære for å opprette en forbindelse med Asterisk

Eksempel på AMI-utgang

Arrangement: Ny kanal
Privilegium: ring, alle
Kanal: PJSIP/VMS_pjsip-0000078b
Kanalstatus: 4
ChannelStateDesc: Ring
Oppringer-ID: 111222
Anrops-ID: 111222
ConnectedLineNum:
tilkoblet linjenavn:
Språk: en
kontokode:
Kontekst: fra-pstn
Utvidelse: s
Prioritet: 1
Uniqueid: 1599589046.5244
Linkedid: 1599589046.5244

  • ARI er en blanding av begge, alt via REST, WebSocket, i JSON-format - men med ferske biblioteker og innpakninger, ikke veldig bra, umiddelbart funnet (phparia, phpari) som ble i deres utvikling for ca 3 år siden.

Eksempel på ARI-utgang når et anrop startes

{ "variable":"CallMeCallerIDName", "value":"111222", "type":"ChannelVarset", "timestamp":"2020-09-09T09:38:36.269+0000", "channel":{ "id" »:»1599644315.5334″, «navn»:»PJSIP/VMSpjsip-000007b6″, "state":"Ring", "ringer":{ "navn":"111222", "nummer":"111222" }, "tilkoblet":{ "navn":"", "nummer" :"" }, "accountcode":"", "dialplan":{ "context":"fra-pstn", "exten":"s", "priority":2, "appname":"Stasis", "appdata":"hello-world" }, "creationtime":"2020-09-09T09:38:35.926+0000", "language":"no" }, "stjerneid":"48:5b:aa:aa:aa:aa", "application":"hello-world" }

Bekvemmelighet eller ulempe, muligheten eller umuligheten av å jobbe med et bestemt API bestemmes av oppgavene som må løses. Oppgavene for integrasjon med CRM er som følger:

  • Spor begynnelsen av samtalen, hvor den ble overført, trekk ut CallerID, DID, start- og sluttider, kanskje data fra telefonboken (for å søke etter en forbindelse mellom telefonen og CRM-brukeren)

  • Start og avslutt opptaket av samtalen, lagre det i ønsket format, informer på slutten av opptaket hvor filen ligger

  • Start en samtale på en ekstern hendelse (fra programmet), ring et internt nummer, et eksternt nummer og koble dem til

  • valgfritt: integrer med CRM, oppringingsgrupper og FollowME for automatisk overføring av samtaler i fravær av et sted (i henhold til CRM)

Alle disse oppgavene kan løses gjennom AMI eller ARI, men ARI gir mye mindre informasjon, det er ikke mange hendelser, mange variabler som AMI fortsatt har (for eksempel makrokall, innstilling av variabler inne i makroer, inkludert samtaleopptak) spores ikke. Derfor, for korrekt og nøyaktig sporing, la oss velge AMI for nå (men ikke helt). I tillegg (vel, hvor ville det vært uten dette, vi er late mennesker) - i originalverket (artikkel i habr) bruk PAMI. *Da må du prøve å skrive om til ARI, men ikke det faktum at det vil fungere.

Gjenoppfinne integrering

For at vår FreePBX skal kunne rapportere til AMI på enkle måter om begynnelsen av samtalen, sluttid, tall, navn på innspilte filer, er det enklest å beregne varigheten av samtalen ved å bruke samme triks som de opprinnelige forfatterne - skriv inn variablene dine og analyser utdataene for deres tilstedeværelse. PAMI foreslår å gjøre dette ganske enkelt gjennom en filterfunksjon.

Her er et eksempel på innstilling av din egen variabel for starttidspunktet for samtalen (s er et spesialnummer i oppringingsplanen som utføres FØR du starter DID-søket)

[ext-did-custom]

exten => s,1,Set(CallStart=${STRFTIME(epoch,,%s)})

Et eksempel på AMI-hendelse for denne linjen

Arrangement: Ny kanal

Privilegium: ring, alle

Kanal: PJSIP/VMS_pjsip-0000078b

Kanalstatus: 4

ChannelStateDesc: Ring

Oppringer-ID: 111222

Anrops-ID: 111222

ConnectedLineNum:

tilkoblet linjenavn:

Språk: en

kontokode:

Kontekst: fra-pstn

Utvidelse: s

Prioritet: 1

Uniqueid: 1599589046.5244

Linkedid: 1599589046.5244

Applikasjon: Angi AppData:

CallStart=1599571046

Fordi FreePBX overskriver filene extension.conf og extension_additional.conf, vil vi bruke filen utvidelse_skikk.konf

Full kode for extension_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

Funksjon og forskjell fra den originale telefonplanen til forfatterne av den originale artikkelen -

  • Dialplan i .conf-format, slik FreePBX vil ha det (ja, det kan .ael, men ikke alle versjoner og det er ikke alltid praktisk)

  • I stedet for å behandle slutten gjennom exten=>h, ble behandlingen introdusert gjennom hangup_handler, fordi FreePBX-telefonplanen bare fungerte med den

  • Fast skriptanropsstreng, lagt til tilbud og eksternt anropsnummer ExtNum

  • Behandlingen flyttes til _egendefinerte kontekster og lar deg ikke berøre eller redigere FreePBX-konfigurasjoner - innkommende via [ext-di-tilpasset], utgående gjennom [utgående-allroutes-tilpasset]

  • Ingen binding til tall - filen er universell og må bare konfigureres for banen og koblingen til serveren

For å komme i gang må du også kjøre skript i AMI ved pålogging og passord - for dette har FreePBX også en _custom-fil

manager_custom.conf-fil

;;  это логин
[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

Begge disse filene må plasseres i /etc/asterisk, og les deretter konfigurasjonene på nytt (eller start stjernen på nytt)

# astrisk -rv
  Connected to Asterisk 16.6.2 currently running on freepbx (pid = 31629)
#freepbx*CLI> dialplan reload
     Dialplan reloaded.
#freepbx*CLI> exit

La oss nå gå videre til PHP

Initialisere skript og lage en tjeneste

Siden opplegget for å jobbe med Bitrix 24, en tjeneste for AMI, ikke er helt enkelt og oversiktlig, må det diskuteres separat. Asterisk, når AMI er aktivert, åpner du bare porten og det er det. Når en klient blir med, ber den om autorisasjon, deretter abonnerer klienten på de nødvendige hendelsene. Hendelser kommer i ren tekst, som PAMI konverterer til strukturerte objekter og gir muligheten til å angi filtreringsfunksjonen kun for hendelser av interesse, felt, tall, etc.

Så snart anropet kommer inn, utløses NewExten-hendelsen fra den overordnede [from-pstn]-konteksten, så går alle hendelsene i rekkefølgen til linjene i kontekstene. Når informasjon mottas fra CallMeCallerIDName- og CallStart-variablene spesifisert i _custom dialplan,

  1. Funksjonen for å be om bruker-ID som tilsvarer internnummeret der anropet kom. Hva om det er en oppringt gruppe? Spørsmålet er politisk, må du opprette en samtale til alle på en gang (når alle ringer på en gang) eller opprette som de kaller når du ringer etter tur? De fleste klienter har Fisrt Available-strategien, så det er ikke noe problem med dette, kun én ringer. Men problemet må løses.

  2. Anropsregistreringsfunksjonen i Bitrix24, som returnerer CallID, som deretter kreves for å rapportere anropsparametrene og en lenke til opptaket. Krever enten internnummer eller UserID

Forstå FreePBX og integrere den med Bitrix24 og mer

Etter avsluttet samtale kalles nedlastingsfunksjonen for post, som samtidig rapporterer status for fullført samtale (Opptatt, Ikke svar, Suksess), og laster også ned en lenke til mp3-filen med posten (hvis noen).

Fordi CallMeIn.php-modulen må kjøre kontinuerlig, er det opprettet en SystemD-oppstartsfil for den callme.service, som må legges inn i /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

initialisering og lansering av skriptet skjer gjennom systemctl eller tjeneste

# systemctl enable callme
# systemctl start callme

Tjenesten vil starte seg selv på nytt etter behov (i tilfelle krasj). Innbokssporingstjenesten krever ikke at en webserver er installert, kun php er nødvendig (som definitivt er på FeePBX-serveren). Men i mangel av tilgang til anropsposter gjennom webserveren (også med https), vil det ikke være mulig å lytte til anropsposter.

La oss nå snakke om utgående samtaler. CallMeOut.php-skriptet har to funksjoner:

  • Initiering av en samtale når en forespørsel mottas for et php-skript (inkludert bruk av "Ring"-knappen i selve Bitrix). Det fungerer ikke uten en webserver, forespørselen mottas via HTTP POST, forespørselen inneholder et token

  • Melding om samtalen, dens parametere og poster i Bitrix. Avfyrt av stjerne i [sub-call-internal-ended] dialplan når en samtale avsluttes

Forstå FreePBX og integrere den med Bitrix24 og mer

Nettserveren er bare nødvendig for to ting - nedlasting av Bitrix-postfiler (via HTTPS) og kalle CallMeOut.php-skriptet. Du kan bruke den innebygde FreePBX-serveren, filene som er /var/www/html, kan installere en annen server eller spesifisere en annen bane.

Internett server

La oss forlate webserveroppsettet for uavhengig undersøkelse (tyts, tyts, tyts). Hvis du ikke har et domene, kan du prøve FreeDomain( https://www.freenom.com/ru/index.html), som vil gi deg et gratis navn for din hvite IP (ikke glem å videresende porter 80, 443 gjennom ruteren hvis den eksterne adressen bare er på den). Hvis du nettopp opprettet et DNS-domene, må du vente (fra 15 minutter til 48 timer) til alle servere er lastet. I henhold til erfaringen med å jobbe med innenlandske leverandører - fra 1 time til en dag.

Installasjonsautomatisering

Et installasjonsprogram er utviklet på github for å gjøre installasjonen enda enklere. Men det var glatt på papiret - mens vi installerer alt manuelt, siden etter å ha trikset med alt dette ble det krystallklart hva som er venner med hvem, hvem som går hvor og hvordan man feilsøker det. Det er ingen installasjonsprogram ennå

Docker

Hvis du raskt vil prøve løsningen - det er et alternativ med Docker - lag raskt en beholder, gi den porter til utsiden, slip innstillingsfilene og prøv (dette er alternativet med LetsEncrypt-beholderen, hvis du allerede har et sertifikat , du trenger bare å omdirigere den omvendte proxyen til FreePBX-nettserveren (vi ga den en annen port er 88), LetsEncrypt i docker basert på denne artikkelen

Du må kjøre filen i den nedlastede prosjektmappen (etter git clone), men først gå inn i stjernekonfigurasjonene (stjernemappen) og skriv stiene til postene og URL-en til nettstedet ditt der

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:

Denne docker-compose.yaml-filen kjøres via

docker-compose up -d

Hvis nginx ikke starter, er det noe galt med konfigurasjonen i mappen nginx/ssl_docker.conf

Andre integrasjoner

Og hvorfor ikke legge inn litt CRM i skript samtidig, tenkte vi. Vi studerte flere andre CRM APIer, spesielt den gratis innebygde PBX - ShugarCRM og Vtiger, og ja! ja, prinsippet er det samme. Men dette er en annen historie, som vi senere vil laste opp til github separat.

referanser

Ansvarsfraskrivelse: Enhver likhet med virkeligheten er fiktiv, og det var ikke meg.

Kilde: www.habr.com

Legg til en kommentar