Задача:

1) Есть группа операторов Call-центра, нужно сделать возможность после разговора, оценивать звонок.
2) Максимальная интеграция с FreePBX.

Решение:

В Интернете много вариантов, все они ориентированы на чистый Asterisk.
Примеры:
https://wiki.merionet.ru/ip-telephoniya/55/ocenka-raboty-operatora-posle-zvonka-na-asterisk/
https://habr.com/ru/post/147122/
http://asterisk-service.com/blog/asterisk-1/post/2-0-53
Моя же реализация заключается в том, что все настройки можно делать в Web-интерфейсе FreePBX.
Реализация тестировалась и работает на:
— версии Asterisk 1.8.10.1
— FreePBX версии 12.0.76.6
— PHP версии 5.3.10-1ubuntu3.26
— библиотека PHPAGI версии 2.20 2010/09/30 02:21:00 (качаем тут: phpagi.zip)

Первым делом нам нужно определить как генерируется постановка вызова в очередь. Выполняем на сервере Asterisk:
nano /etc/asterisk/extensions_additional.conf
Ищем контекст с названием [ext-queues]. Внутри должны быть строки вида

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
[ext-queues]
include => ext-queues-custom
exten => 11,1,Macro(user-callerid,)
exten => 11,n,Answer
exten => 11,n,Macro(blkvm-set,reset)
exten => 11,n,ExecIf($["${REGEX("(M[(]auto-blkvm[)])" ${DIAL_OPTIONS})}" != "1"]?Set(_DIAL_OPTIONS=${DIAL_OPTIONS}M(auto-blkvm)))
exten => 11,n,Set(__NODEST=${EXTEN})
exten => 11,n,Set(QCIDPP=${IF($[${LEN(${VQ_CIDPP})}>0]?${VQ_CIDPP}: )})
exten => 11,n,Set(VQ_CIDPP=)
exten => 11,n,ExecIf($["${QCIDPP}"!=""]?Macro(prepend-cid,${QCIDPP}))
exten => 11,n,Set(QAINFO=${IF($[${LEN(${VQ_AINFO})}>0]?${VQ_AINFO}: )})
exten => 11,n,Set(VQ_AINFO=)
exten => 11,n,ExecIf($["${QAINFO}"!=""]?Set(__ALERT_INFO=${QAINFO}))
exten => 11,n,Set(QJOINMSG=${IF($[${LEN(${VQ_JOINMSG})}>0]?${IF($["${VQ_JOINMSG}"!="0"]?${VQ_JOINMSG}: )}:custom/zednannja-vstanovleno)})
exten => 11,n,Set(VQ_JOINMSG=)
exten => 11,n,Set(QRINGOPTS=r)
exten => 11,n,Set(QRETRY=${IF($[${LEN(${VQ_RETRY})}>0]?${VQ_RETRY}:n)})
exten => 11,n,Set(VQ_RETRY=)
exten => 11,n(qoptions),Set(QOPTIONS=${IF($[${LEN(${VQ_OPTIONS})}>0]?${VQ_OPTIONS}:t)}${QCANCELMISSED}${QRINGOPTS}${QRETRY})
exten => 11,n,Set(VQ_OPTIONS=)
exten => 11,n(qgosub),Set(QGOSUB=${IF($[${LEN(${VQ_GOSUB})}>0]?${VQ_GOSUB}:${QGOSUB})})
exten => 11,n,Set(VQ_GOSUB=)
exten => 11,n(qagi),Set(QAGI=${IF($[${LEN(${VQ_AGI})}>0]?${VQ_AGI}:${QAGI})})
exten => 11,n,Set(VQ_AGI=)
exten => 11,n(qrule),Set(QRULE=${IF($[${LEN(${VQ_RULE})}>0]?${IF($["${VQ_RULE}"!="0"]?${VQ_RULE}: )}:${QRULE})})
exten => 11,n,Set(VQ_RULE=)
exten => 11,n(qposition),Set(QPOSITION=${IF($[${LEN(${VQ_POSITION})}>0]?${VQ_POSITION}:${QPOSITION})})
exten => 11,n,Set(VQ_POSITION=)
exten => 11,n,Gosub(sub-record-check,s,1(q,11,dontcare))
exten => 11,n,ExecIf($["${QJOINMSG}"!=""]?Playback(${QJOINMSG}, ))
exten => 11,n,QueueLog(11,${UNIQUEID},NONE,DID,${FROM_DID})
exten => 11,n,Set(QAANNOUNCE=${IF($[${LEN(${VQ_AANNOUNCE})}>0]?${IF($["${VQ_AANNOUNCE}"!="0"]?${VQ_AANNOUNCE}: )}: )})
exten => 11,n,Set(VQ_AANNOUNCE=)
exten => 11,n,Set(QMOH=${IF($["${VQ_MOH}"!=""]?${VQ_MOH}: )})
exten => 11,n,Set(VQ_MOH=)
exten => 11,n,ExecIf($["${QMOH}"!=""]?Set(__MOHCLASS=${QMOH}))
exten => 11,n,ExecIf($["${MOHCLASS}"!=""]?Set(CHANNEL(musicclass)=${MOHCLASS}))
exten => 11,n,Set(QMAXWAIT=${IF($[${LEN(${VQ_MAXWAIT})}>0]?${VQ_MAXWAIT}: )})
exten => 11,n,Set(VQ_MAXWAIT=)
exten => 11,n,Set(QUEUENUM=11)
exten => 11,n,Set(QUEUEJOINTIME=${EPOCH})
exten => 11,n(qcall),Queue(11,${QOPTIONS},,${QAANNOUNCE},${QMAXWAIT},${QAGI},,${QGOSUB},${QRULE},${QPOSITION})
exten => 11,n,Macro(blkvm-clr,)
exten => 11,n,Gosub(sub-record-cancel,s,1())
exten => 11,n,Set(__NODEST=)
exten => 11,n,Set(_QUEUE_PRIO=0)
exten => 11,n,Set(QRINGOPTS=)
exten => 11,n,Set(QDEST=${VQ_DEST})
exten => 11,n,Set(VQ_DEST=)
exten => 11,n(gotodest),GotoIf($["${QDEST}"=""]?ext-group,505,1:${CUT(QDEST,^,1)},${CUT(QDEST,^,2)},${CUT(QDEST,^,3)})

здесь нас интересует строка: exten => 11,n(qcall),Queue(11,${QOPTIONS},,${QAANNOUNCE},${QMAXWAIT},${QAGI},,${QGOSUB},${QRULE},${QPOSITION})
Приложение Queue() генерирует постановку вызова в очередь. Как видим с примера — приложению передаются параметры, которые были сгенерированные перед выполнением приложения Queue(). Смотрим в документацию: https://asterisk-pbx.ru/wiki/asterisk/app/queue
Как видим по документации, если при выполнении приложения Queue(), добавить в аргумент «options» параметр «c», то диаплан продолжит свое выполнения, когда вызываемый (оператор) положил трубку. В нашем случае — диаплан перейдет на пункт «Fail Over Destination» в FreePBX.
По контексту определяем, что если перед поступлением звонка в очередь, мы установим значение «c» переменной VQ_OPTIONS, то приложение Queue() згенерирует постановку вызова в очередь с этим параметом.

Приступаем к выполнению

nano /etc/asterisk/extensions_custom.conf
Создаем собственный контекст с установкой значения переменной VQ_OPTIONS:

1
2
3
4
[queue-call-begin]
exten => s,1,Set(VQ_OPTIONS=tc) ;на первом приоритете, мы устанавливаем значение переменной. Параметром "t" - я разрешаю оператору переводить звонки
exten => s,n,System(php /var/www/call/call.php incall ${UNIQUEID} ${CALLERID(num)} ${CHANNEL} &) ;запуск фонового php скрипта на выполнения
exten => s,n,Return ;возвращаемся на предыдущий контекст, откуда был вызван

Также создаем контексты с оценками

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[assessment-1]
exten => s,1,Set(__OcenkaOper=1) ;выполняем необходимое действие
exten => s,n,Return ;возвращаемся на предыдущий контекст, откуда был вызван
 
[assessment-2]
exten => s,1,Set(__OcenkaOper=2) ;выполняем необходимое действие
exten => s,n,Return ;возвращаемся на предыдущий контекст, откуда был вызван
 
[assessment-3]
exten => s,1,Set(__OcenkaOper=3) ;выполняем необходимое действие
exten => s,n,Return ;возвращаемся на предыдущий контекст, откуда был вызван
 
[assessment-4]
exten => s,1,Set(__OcenkaOper=4) ;выполняем необходимое действие
exten => s,n,Return ;возвращаемся на предыдущий контекст, откуда был вызван
 
[assessment-5]
exten => s,1,Set(__OcenkaOper=5) ;выполняем необходимое действие
exten => s,n,Return ;возвращаемся на предыдущий контекст, откуда был вызван


После exten => s,1,Set(__OcenkaOper=X) можем выполнять еще команды. Например отправить сообщение в телеграмм:
exten => s,n,System(curl -s -X POST "https://api.telegram.org/bot545454443:AAAAAAAAAAAAAAAABBBBBBBBBBBCCC/sendmessage" -F chat_id="20202020" -F text="Оставлена оценка оператору: 5" &)
Или можно запустить php скрипт, который возвращает значение. Это значение присвоим переменной:
exten => s,n,Set(CallId=${SHELL(php /var/www/call/call.php inCallRegister ${CALLERID(num)})})

Логично, что если звонок не состоялся (оператор не взял трубку), то предлагать оценивать вызов не нужно. Для этого, создадим еще один контекст:

1
2
3
4
5
[queue-call-end]
exten => s,1,GotoIf($[${QUEUESTATUS}=TIMEOUT]?timeout:answered); очередь по завершению получает переменную QUEUESTATUS, от значения которого мы можем определить был ли принят звонок
exten => s,n(timeout),Goto(app-announcement-18,s,1) ; если звонок не был принят, направим его на app-announcement-18,s,1 - это приветствие, в котором говорим, что сейчас все операторы заняты и мы ему перезвоним. После, терминируем звонок (hangup) - обязательно. 
exten => s,n(answered),Noop(return to queue-call-end) ; если статус звонка отличается от "TIMEOUT", сделаем вывод, что звонок состоялся, и переходим на следующий приоритет
exten => s,n,Return ;возвращаемся на предыдущий контекст, откуда был вызван

Сохраняем изменения в файле /etc/asterisk/extensions_custom.conf и выходим с редактора nano — Ctrl+o, Ctrl+x.
Следующим этапом нам нужно зафиксировать созданные контексты в FreePBX.
Заходим в FreePBX → АДМИНИСТРАТОР (Admin) → Дополнительные назначения (Custom Destination)
Добавляем созданные ранее назначения:
Для примера опишу добавление контекста [queue-call-begin]
— в поле Спецназначение: указываем «queue-call-begin,s,1»
— в поле Описание: указываем «queue-call-begin» — название понятное Вам
— в поле Примечания: указываем описание для назначения понятное Вам
— Обязательно ставим галочку «Return», после чего появится выпадающий список дальнейших назначений — направляем в очередь (можно в другое место).

По аналогии, добавляем остальные назначения:
[assessment-1]
Спецназначение: assessment-1,s,1
Return: Приветствие → «Спасибо за оценку» → Терминовать звонок (Положить трубку).

[assessment-2]
Спецназначение: assessment-2,s,1
Return: Приветствие → «Спасибо за оценку» → Терминовать звонок (Положить трубку).

[assessment-3]
Спецназначение: assessment-3,s,1
Return: Приветствие → «Спасибо за оценку» → Терминовать звонок (Положить трубку).

[assessment-4]
Спецназначение: assessment-4,s,1
Return: Приветствие → «Спасибо за оценку» → Терминовать звонок (Положить трубку).

[assessment-5]
Спецназначение: assessment-5,s,1
Return: Приветствие → «Спасибо за оценку» → Терминовать звонок (Положить трубку).

[queue-call-end]
Спецназначение: queue-call-end,s,1
Return: IVR → «Оцените работу оператора».