Возврат звонка после «слепого» перевода

Возврат звонка после безусловного / «слепого» перевода
Callback after unattended / blind transfer

В случае сопровождаемого перевода звонка (attended transfer) всё довольно просто: оператор переводит абонента А на абонента Б, общается какое-то время с абонентом Б (или ждет, пока тот ответит или не ответит) и когда оператор кладет трубку, абоненты А и Б начинают общаться между собой. В случае, если абонент Б занят или не отвечает, оператор снова соединяется с абонентом А и сообщает ему о невозможности соединиться с абонентом Б.

В случае «слепого» перевода звонка (unattended transfer или blind transfer) всё сложнее: оператор переводит абонента А на абонента Б, и сразу после такого перевода оператор отключается. При этом абонент А остается предоставлен судьбе: если абонент Б ему отвечает, то всё хорошо. Если же нет (например, абонент Б занят или не отвечает), то у абонента А в трубке раздаются короткие гудки, после чего ему остается только повесить трубку и попытаться еще раз дозвониться до оператора. Однако для фирм, которым важен каждый звонок, крайне нежелательны такие обрывы связи. Что же делать?

Есть несколько вариантов решения проблемы (подход только один: чтобы звонки не «терялись», т.е. в случае неответа/занятости абонента звонок переходил куда-либо, пока на него не ответят):

  1. Несколько команд Dial
  2. С использованием attended transfer вместо blind transfer
  3. С использованием TRANSFER_CONTEXT

Несколько команд Dial

Для всех номеров в диалплане после команды Dial выполняется проверка результатов звонка, и если переменная DIALSTATUS не равна ANSWER (т.е. абонент занят, номер недоступен, на звонок не ответили и т.п.), то выполненяется следующая команда Dial, соединяющая абонента например, с секретарем. В случае неответа секретаря (что тоже возможно) выполняется третья команда Dial, ну и так далее.

;файл extensions.conf:
[local-phones]
exten => _XXXX,1,Answer()
exten => _XXXX,n(begin),Dial(SIP/${EXTEN},240,tTg)		  ; g - после окончания звонка независимо от результата продолжить выполнение сценария
; если DialStatus = Answer (звонок отвечен), то кладем трубку:
exten => _XXXX,n,GotoIf($["${DIALSTATUS}" = "ANSWER"]?end:)	
exten => _XXXX,n,Dial(SIP/100,240,tTg)			; 100 - номер секретаря
exten => _XXXX,n,GotoIf($["${DIALSTATUS}" = "ANSWER"]?end:)	
exten => _XXXX,n,Dial(SIP/101,240,tTg)			; 101 - номер второго секретаря
exten => _XXXX,n,GotoIf($["${DIALSTATUS}" = "ANSWER"]?end:)	
exten => _XXXX,n,Dial(SIP/102,240,tTg)			; 102 - номер третьего секретаря
; если никуда не дозвонились, кладем трубку (или можем сделать goto на begin и продолжать по кругу до бесконечности)
exten => _XXXX,n(end),Hangup()

С использованием attended transfer вместо blind transfer

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

;atxfernoanswertimeout = 15     ; Timeout for answer on attended transfer default is 15 seconds.
;atxferdropcall = no            ; If someone does an attended transfer, then hangs up before the transferred
                                ; caller is connected, then by default, the system will try to call back the
                                ; person that did the transfer.  If this is set to "yes", the callback will
                                ; not be attempted and the transfer will just fail.
                                ; For atxferdropcall=no to work properly, you also need to
                                ; define ATXFER_NULL_TECH in main/features.c.  The reason the
                                ; code is not enabled by default is spelled out in the comment
                                ; block near the top of main/features.c describing ATXFER_NULL_TECH.
;atxferloopdelay = 10           ; Number of seconds to sleep between retries (if atxferdropcall = no)
;atxfercallbackretries = 2      ; Number of times to attempt to send the call back to the transferer.
                                ; By default, this is 2.

Рассмотрим ситуацию: абонент А звонит абоненту Б и пообщавшись с ним, просит перевести его на абонента С. Если абонент Б (делая attended transfer) дожидается ответа абонента С, возможно общается с ним, и отключается — то абоненты А и С связываются между собой и общаются. Это стандартный сопровождаемый перевод.
Если же абонент Б делает attended transfer на абонента С и сразу (не дожидаясь ответа) кладет трубку, то получается примерно та же ситуация, что и в случае blind transfer. Однако, всё хорошо только в случае, если абонент С снял трубку (или абонент А не дождавшись ответа, положил трубку сам).

atxferdropcall = yes — после неответа абонента С (или если этот абонент занят / сбросил вызов) происходит разрыв соединения, и абоненту А надо снова совершать звонок.

atxferdrpcall = no — теоретически, после неответа (или занятости/сброса звонка) абонента С, звонок абонента А должен вернуться обратно. Однако:

  1. пока абонент С не ответит (или абонент А не положит трубку, или пока не истечет время ответа абонента С и связь прервется сама) абонент Б не может разговаривать по телефону, его телефон в состоянии hold (абонент Б не может совершать и принимать вызовы);
  2. вернуться назад звонок (к абоненту Б) тоже не может (поскольку абонент Б положил трубку и теоретически разговор прервался, но его телефон по-прежнему в состоянии hold, т.е. занят), это чётко видно в случае, если стоит лимит на одновременные звонки, call-limit=1

Эту проблему призвана пофиксить (насколько я понимаю, это временное решение!) переменная ATXFER_NULL_TECH в файле features.c, который находится в каталоге main с исходными кодами астериска. Достаточно сделать поиск по этому файлу, и раскомментировать (убрать символы «//») строку:
//#define ATXFER_NULL_TECH 1
после чего пересобрать (и перезапустить разумеется) астериск.

Только в случае включения этой переменной, пересборки астериска и выставления atxferdrpcall = no, другие переменные имеют смысл:
atxfercallbackretries — количество попыток вернуть звонок абоненту Б (если он уже снова занят)
atxferloopdelay — задержка между попытками вернуть звонок абоненту Б.

 

С использованием TRANSFER_CONTEXT

Это, вероятно, самый интересный вариант с возвратом звонков после «слепого» перевода. Зачастую необходимость перевести звонок на другого абонента возникает не только у секретаря. Представьте себе фирму (к примеру, тот же call-центр), в котором осуществляются консалтинговые услуги. При этом множестов «секретарей» переводят звонки на специалистов, специалисты могут переводить звонки друг на друга, и зачастую нет времени (и желания) переведя звонок, дожидаться ответа того, на кого этот звонок переводится — а хочется перенаправить звонок на нужного сотрудника, чтобы в случае, если человека нет на месте (или он занят) звонок вернулся обратно автоматически. Таким образом, звонок должен возвращаться не «кому-нибудь», а именно тому человеку, который его перевел.

В этом случае для всех переводимых звонков настраивается отдельный контекст, в котором они будут обрабатываться (задав значение переменной TRANSFER_CONTEXT). При этом контролируется, был ли ответ на этот переведенный звонок, и при необходимости (используя переменную BLINDTRANSFER) можно вернуть звонок тому абоненту, который осуществлял перевод вызова:

...
[globals]
TRANSFER_CONTEXT=blind_transfer_mikhed_ringback
...
[local_phones]		; контекст локальных звонков
exten => _XXXX,1,Answer()
exten => _XXXX,n,Dial(SIP/${EXTEN},45,tT)
exten => _XXXX,n,Hangup()
...
; в этот контекст попадают все переводимые звонки:
[blind_transfer_mikhed_ringback]
exten => _X.,1,NoOp("Blindtransfer: " ${BLINDTRANSFER})
exten => _X.,n,Set(ExtLength=${LEN(${EXTEN})}).
; ${BLINDTRANSFER} - это не оригинальный номер, а оригинальный канал, например: SIP/1111-433242424242
exten => _X.,n,Set(OrigNumber=${BLINDTRANSFER:4:${ExtLength}})
; делаем основной звонок:
exten => _X.,n,Dial(SIP/${EXTEN},45,tTg)
; если он удался, то вешаем трубку, если нет - возвращаем тому, кто переводил звонок:
exten => _X.,n,GotoIf($["${DIALSTATUS}" = "ANSWER"]?hangup:callback)
exten => _X.,n(callback),Dial(local/${OrigNumber}@local_phones,45,tT)
; Обратите внимание: здесь делается только одна попытка вернуть звонок. 
; Однако на самом деле к моменту возврата звонка сотрудник может быть уже занят.
; Так что имеет смысл делать несколько попыток, или перенаправлять звонки куда-либо еще.
exten => _X.,n(hangup),Hangup().
...

Обратите внимание: в данном примере возможны переводы звонков только на «реальных» людей (т.е. только на SIP-абонентов). В общем же случае Вы можете захотеть переводить звонки на группы номеров, либо на номера с обработкой (в extensions.conf) до или после вызова. В этом случае Вы можете обратиться за консультацией к автору сайта, на нашем предприятии это реализовано (и работает).

Внимание: в описанном выше способе возврата звонка с помощью transfer_context обнаружен небольшой баг, ну или особенность реализации: номер того, кто переводит звонок и номер, куда переводят звонок, должны совпадать по длине. Если (например) у Вас в организации 4-значные номера и Вы переводите звонок на мобильный, то в случае, когда человек не дозвонился, звонок не вернется обратно (будет неправильный номер в OrigNumber). А если (вдруг) у Вас в организации используется много номеров и они могут различаться по длине (например: в одной и той же организации номера 11…19 и 300..399), то описанный выше вариант (с transfer_context) Вам однозначно придется переделывать.

P.S. Обратите также внимание на то, что с точки зрения безопасности asterisk, в контексте перевода звонков использован неправильный шаблон.

Оригинал статьи: http://linux.mixed-spb.ru/asterisk/callback_after_blind_transfer.php