mirror of https://github.com/sipwise/asterisk.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
522 lines
17 KiB
522 lines
17 KiB
|
|
Setting up Call Queues -- A Tutorial
|
|
|
|
Pardon, but the dialplan in this tutorial will be expressed
|
|
in AEL, the new Asterisk Extension Language. If you are
|
|
not used to its syntax, we hope you will find it to some
|
|
degree intuitive. If not, there are documents explaining
|
|
its syntax and constructs.
|
|
|
|
|
|
====== Configuring Call Queues
|
|
|
|
First of all, set up call queues in queue.conf
|
|
|
|
Here is an example:
|
|
|
|
=========== queues.conf ===========
|
|
| ; Cool Digium Queues |
|
|
| [general] |
|
|
| persistentmembers = yes |
|
|
| |
|
|
| ; General sales queue |
|
|
| [sales-general] |
|
|
| music=default |
|
|
| context=sales |
|
|
| strategy=ringall |
|
|
| joinempty=strict |
|
|
| leavewhenempty=strict |
|
|
| |
|
|
| ; Customer service queue |
|
|
| [customerservice] |
|
|
| music=default |
|
|
| context=customerservice |
|
|
| strategy=ringall |
|
|
| joinempty=strict |
|
|
| leavewhenempty=strict |
|
|
| |
|
|
| ; Support dispatch queue |
|
|
| [support-dispatch] |
|
|
| music=default |
|
|
| context=dispatch |
|
|
| strategy=ringall |
|
|
| joinempty=strict |
|
|
| leavewhenempty=strict |
|
|
===================================
|
|
|
|
In the above, we have defined 3 separate calling queues:
|
|
sales-general, customerservice, and support-dispatch.
|
|
|
|
Please note that the sales-general queue specifies a
|
|
context of "sales", and that customerservice specifies the
|
|
context of "customerservice", and the support-dispatch
|
|
queue specifies the context "dispatch". These three
|
|
contexts must be defined somewhere in your dialplan.
|
|
We will show them after the main menu below.
|
|
|
|
<verbage explaining options above>
|
|
In the [general] section, specifying the persistentmembers=yes,
|
|
will cause the agent lists to be stored in astdb, and
|
|
recalled on startup.
|
|
|
|
The strategy=ringall will cause all agents to be dialed
|
|
together, the first to answer is then assigned the incoming
|
|
call.
|
|
|
|
"joinempty" set to "strict" will keep incoming callers from
|
|
being placed in queues where there are no agents to take calls.
|
|
The Queue() application will return, and the dial plan can
|
|
determine what to do next.
|
|
|
|
If there are calls queued, and the last agent logs out, the
|
|
remaining incoming callers will immediately be removed from
|
|
the queue, and the Queue() call will return, IF the "leavewhenempty" is
|
|
set to "strict".
|
|
|
|
|
|
=====================================
|
|
| Routing incoming Calls to Queues |
|
|
=====================================
|
|
|
|
|
|
Then in extensions.ael, you can do these things:
|
|
|
|
================ The Main Menu
|
|
|
|
At Digium, incoming callers are sent to the "mainmenu" context, where they
|
|
are greeted, and directed to the numbers they choose...
|
|
|
|
context mainmenu {
|
|
|
|
includes {
|
|
digium;
|
|
queues-loginout;
|
|
}
|
|
|
|
0 => goto dispatch|s|1;
|
|
2 => goto sales|s|1;
|
|
3 => goto customerservice|s|1;
|
|
4 => goto dispatch|s|1;
|
|
|
|
s => {
|
|
Ringing();
|
|
Wait(1);
|
|
Set(attempts=0);
|
|
Answer();
|
|
Wait(1);
|
|
Background(digium/ThankYouForCallingDigium);
|
|
Background(digium/YourOpenSourceTelecommunicationsSupplier);
|
|
WaitExten(0.3);
|
|
repeat:
|
|
Set(attempts=$[${attempts} + 1]);
|
|
Background(digium/IfYouKnowYourPartysExtensionYouMayDialItAtAnyTime);
|
|
WaitExten(0.1);
|
|
Background(digium/Otherwise);
|
|
WaitExten(0.1);
|
|
Background(digium/ForSalesPleasePress2);
|
|
WaitExten(0.2);
|
|
Background(digium/ForCustomerServicePleasePress3);
|
|
WaitExten(0.2);
|
|
Background(digium/ForAllOtherDepartmentsPleasePress4);
|
|
WaitExten(0.2);
|
|
Background(digium/ToSpeakWithAnOperatorPleasePress0AtAnyTime);
|
|
if( ${attempts} < 2 ) {
|
|
WaitExten(0.3);
|
|
Background(digium/ToHearTheseOptionsRepeatedPleaseHold);
|
|
}
|
|
WaitExten(5);
|
|
if( ${attempts} < 2 ) goto repeat;
|
|
Background(digium/YouHaveMadeNoSelection);
|
|
Background(digium/ThisCallWillBeEnded);
|
|
Background(goodbye);
|
|
Hangup();
|
|
}
|
|
}
|
|
|
|
|
|
============= The Contexts referenced from the queues.conf file
|
|
|
|
|
|
|
|
context sales {
|
|
|
|
0 => goto dispatch|s|1;
|
|
8 => Voicemail(${SALESVM});
|
|
|
|
s => {
|
|
Ringing();
|
|
Wait(2);
|
|
Background(digium/ThankYouForContactingTheDigiumSalesDepartment);
|
|
WaitExten(0.3);
|
|
Background(digium/PleaseHoldAndYourCallWillBeAnsweredByOurNextAvailableSalesRepresentative);
|
|
WaitExten(0.3);
|
|
Background(digium/AtAnyTimeYouMayPress0ToSpeakWithAnOperatorOr8ToLeaveAMessage);
|
|
Set(CALLERID(name)=Sales);
|
|
Queue(sales-general|t);
|
|
Set(CALLERID(name)=EmptySalQ);
|
|
goto dispatch|s|1;
|
|
Playback(goodbye);
|
|
Hangup();
|
|
}
|
|
}
|
|
|
|
Please note that there is only one attempt to queue a call in the sales queue. All sales agents that
|
|
are logged in will be rung.
|
|
|
|
|
|
context customerservice {
|
|
|
|
0 => {
|
|
SetCIDName(CSVTrans);
|
|
goto dispatch|s|1;
|
|
}
|
|
8 => Voicemail(${CUSTSERVVM});
|
|
|
|
s => {
|
|
Ringing();
|
|
Wait(2);
|
|
Background(digium/ThankYouForCallingDigiumCustomerService);
|
|
WaitExten(0.3);
|
|
notracking:
|
|
Background(digium/PleaseWaitForTheNextAvailableCustomerServiceRepresentative);
|
|
WaitExten(0.3);
|
|
Background(digium/AtAnyTimeYouMayPress0ToSpeakWithAnOperatorOr8ToLeaveAMessage);
|
|
Set(CALLERID(name)=Cust Svc);
|
|
Set(QUEUE_MAX_PENALTY=10);
|
|
Queue(customerservice|t);
|
|
Set(QUEUE_MAX_PENALTY=0);
|
|
Queue(customerservice|t);
|
|
Set(CALLERID(name)=EmptyCSVQ);
|
|
goto dispatch|s|1;
|
|
Background(digium/NoCustomerServiceRepresentativesAreAvailableAtThisTime);
|
|
Background(digium/PleaseLeaveAMessageInTheCustomerServiceVoiceMailBox);
|
|
Voicemail(${CUSTSERVVM});
|
|
Playback(goodbye);
|
|
Hangup();
|
|
}
|
|
}
|
|
|
|
Note that calls coming into customerservice will first be try to queue
|
|
calls to those agents with a QUEUE_MAX_PENALTY of 10, and if none are available,
|
|
then all agents are rung.
|
|
|
|
|
|
context dispatch
|
|
{
|
|
|
|
s => {
|
|
Ringing();
|
|
Wait(2);
|
|
Background(digium/ThankYouForCallingDigium);
|
|
WaitExten(0.3);
|
|
Background(digium/YourCallWillBeAnsweredByOurNextAvailableOperator);
|
|
Background(digium/PleaseHold);
|
|
Set(QUEUE_MAX_PENALTY=10);
|
|
Queue(dispatch|t);
|
|
Set(QUEUE_MAX_PENALTY=20);
|
|
Queue(dispatch|t);
|
|
Set(QUEUE_MAX_PENALTY=0);
|
|
Queue(dispatch|t);
|
|
Background(digium/NoOneIsAvailableToTakeYourCall);
|
|
Background(digium/PleaseLeaveAMessageInOurGeneralVoiceMailBox);
|
|
Voicemail(${DISPATCHVM});
|
|
Playback(goodbye);
|
|
Hangup();
|
|
}
|
|
}
|
|
|
|
And in the dispatch context, first agents of priority 10 are tried, then
|
|
20, and if none are available, all agents are tried.
|
|
|
|
Notice that a common pattern is followed in each of the three queue contexts:
|
|
|
|
First, you set QUEUE_MAX_PENALTY to a value, then you call
|
|
Queue(<queue-name>,option,... (see the documentation for the Queue application));
|
|
|
|
In the above, note that the "t" option is specified, and this allows the
|
|
agent picking up the incoming call the luxury of transferring the call to
|
|
other parties.
|
|
|
|
The purpose of specifying the QUEUE_MAX_PENALTY is to develop a set of priorities
|
|
amongst agents. By the above usage, agents with lower number priorities will
|
|
be given the calls first, and then, if no-one picks up the call, the QUEUE_MAX_PENALTY
|
|
will be incremented, and the queue tried again. Hopefully, along the line, someone
|
|
will pick up the call, and the Queue application will end with a hangup.
|
|
|
|
The final attempt to queue in most of our examples sets the QUEUE_MAX_PENALTY
|
|
to zero, which means to try all available agents.
|
|
|
|
|
|
=========================================
|
|
| Assigning agents to Queues |
|
|
=========================================
|
|
|
|
In this example dialplan, we want to be able to add and remove agents to
|
|
handle incoming calls, as they feel they are available. As they log in,
|
|
they are added to the queue's agent list, and as they log out, they are
|
|
removed. If no agents are available, the queue command will terminate, and
|
|
it is the duty of the dialplan to do something appropriate, be it sending
|
|
the incoming caller to voicemail, or trying the queue again with a higher
|
|
QUEUE_MAX_PENALTY.
|
|
|
|
Because a single agent can make themselves available to more than one queue,
|
|
the process of joining multiple queues can be handled automatically by the
|
|
dialplan.
|
|
|
|
|
|
================= Agents Log In and Out
|
|
|
|
|
|
context queues-loginout
|
|
{
|
|
6092 => {
|
|
Answer();
|
|
Read(AGENT_NUMBER,agent-enternum);
|
|
VMAuthenticate(${AGENT_NUMBER}@default,s);
|
|
Set(queue-announce-success=1);
|
|
goto queues-manip,I${AGENT_NUMBER},1;
|
|
}
|
|
|
|
6093 => {
|
|
Answer();
|
|
Read(AGENT_NUMBER,agent-enternum);
|
|
Set(queue-announce-success=1);
|
|
goto queues-manip,O${AGENT_NUMBER},1;
|
|
}
|
|
}
|
|
|
|
|
|
In the above contexts, the agents dial 6092 to log into their queues,
|
|
and they dial 6093 to log out of their queues. The agent is prompted
|
|
for their agent number, and if they are logging in, their passcode,
|
|
and then they are transferred to the proper extension in the
|
|
queues-manip context. The queues-manip context does all the
|
|
actual work:
|
|
|
|
|
|
context queues-manip {
|
|
|
|
// Raquel Squelch
|
|
_[IO]6121 => {
|
|
&queue-addremove(dispatch,10);
|
|
&queue-success();
|
|
}
|
|
|
|
// Brittanica Spears
|
|
_[IO]6165 => {
|
|
&queue-addremove(dispatch,20);
|
|
&queue-success();
|
|
}
|
|
|
|
// Rock Hudson
|
|
_[IO]6170 => {
|
|
&queue-addremove(sales-general,10);
|
|
&queue-addremove(customerservice,20);
|
|
&queue-addremove(dispatch,30);
|
|
&queue-success();
|
|
}
|
|
|
|
// Saline Dye-on
|
|
_[IO]6070 => {
|
|
&queue-addremove(sales-general,20);
|
|
&queue-addremove(customerservice,30);
|
|
&queue-addremove(dispatch,30);
|
|
&queue-success();
|
|
}
|
|
}
|
|
|
|
In the above extensions, note that the queue-addremove macro is used
|
|
to actually add or remove the agent from the applicable queue,
|
|
with the applicable priority level. Note that agents with a
|
|
priority level of 10 will be called before agents with levels
|
|
of 20 or 30.
|
|
|
|
In the above example, Raquel will be dialed first in the dispatch
|
|
queue, if she has logged in. If she is not, then the second call of
|
|
Queue() with priority of 20 will dial Brittanica if she is present,
|
|
otherwise the third call of Queue() with MAX_PENALTY of 0 will
|
|
dial Rock and Saline simultaneously.
|
|
|
|
Also note that Rock will be among the first to be called in the sales-general
|
|
queue, and among the last in the dispatch queue. As you can see in
|
|
main menu, the callerID is set in the main menu so they can tell
|
|
which queue incoming calls are coming from.
|
|
|
|
The call to queue-success() gives some feedback to the agent
|
|
as they log in and out, that the process has completed.
|
|
|
|
macro queue-success()
|
|
{
|
|
if( ${queue-announce-success} > 0 )
|
|
{
|
|
switch(${MACRO_EXTEN:0:1})
|
|
{
|
|
case I:
|
|
Playback(agent-loginok);
|
|
Hangup();
|
|
break;
|
|
case O:
|
|
Playback(agent-loggedoff);
|
|
Hangup();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
The queue-addremove macro is defined in this manner:
|
|
|
|
macro queue-addremove(queuename,penalty)
|
|
{
|
|
switch(${MACRO_EXTEN:0:1})
|
|
{
|
|
case I: // Login
|
|
AddQueueMember(${queuename},Local/${MACRO_EXTEN:1}@agents,${penalty});
|
|
break;
|
|
case O: // Logout
|
|
RemoveQueueMember(${queuename},Local/${MACRO_EXTEN:1}@agents);
|
|
break;
|
|
case P: // Pause
|
|
PauseQueueMember(${queuename},Local/${MACRO_EXTEN:1}@agents);
|
|
break;
|
|
case U: // Unpause
|
|
UnpauseQueueMember(${queuename},Local/${MACRO_EXTEN:1}@agents);
|
|
break;
|
|
default: // Invalid
|
|
Playback(invalid);
|
|
break;
|
|
}
|
|
}
|
|
|
|
Basically, it uses the first character of the MACRO_EXTEN variable, to determine the
|
|
proper actions to take. In the above dial plan code, only the cases I or O are used,
|
|
which correspond to the Login and Logout actions.
|
|
|
|
|
|
=======================================================
|
|
| Controlling The Way Queues Call the Agents |
|
|
=======================================================
|
|
|
|
Notice in the above, that the commands to manipulate agents in queues have
|
|
"@agents" in their arguments. This is a reference to the agents context:
|
|
|
|
context agents
|
|
{
|
|
// General sales queue
|
|
8010 =>
|
|
{
|
|
Set(QUEUE_MAX_PENALTY=10);
|
|
Queue(sales-general|t);
|
|
Set(QUEUE_MAX_PENALTY=0);
|
|
Queue(sales-general|t);
|
|
Set(CALLERID(name)=EmptySalQ);
|
|
goto dispatch|s|1;
|
|
}
|
|
// Customer Service queue
|
|
8011 =>
|
|
{
|
|
Set(QUEUE_MAX_PENALTY=10);
|
|
Queue(customerservice|t);
|
|
Set(QUEUE_MAX_PENALTY=0);
|
|
Queue(customerservice|t);
|
|
Set(CALLERID(name)=EMptyCSVQ);
|
|
goto dispatch|s|1;
|
|
}
|
|
8013 =>
|
|
{
|
|
Dial(iax2/sweatshop/9456@from-ecstacy);
|
|
|
|
Set(CALLERID(name)=EmptySupQ);
|
|
Set(QUEUE_MAX_PENALTY=10);
|
|
Queue(support-dispatch,t);
|
|
Set(QUEUE_MAX_PENALTY=20);
|
|
Queue(support-dispatch,t);
|
|
Set(QUEUE_MAX_PENALTY=0); // means no max
|
|
Queue(support-dispatch,t);
|
|
goto dispatch|s|1;
|
|
}
|
|
6121 => &callagent(${RAQUEL});
|
|
6165 => &callagent(${SPEARS});
|
|
6170 => &callagent(${ROCK});
|
|
6070 => &callagent(${SALINE});
|
|
}
|
|
|
|
In the above, the variables ${RAQUEL}, etc stand for
|
|
actual devices to ring that person's
|
|
phone (like Zap/37).
|
|
|
|
The 8010, 8011, and 8013 extensions are purely for transferring
|
|
incoming callers to queues. For instance, a customer service
|
|
agent might want to transfer the caller to talk to sales. The
|
|
agent only has to transfer to extension 8010, in this case.
|
|
|
|
Here is the callagent macro, note that if a person in the
|
|
queue is called, but does not answer, then they are automatically
|
|
removed from the queue.
|
|
|
|
macro callagent(device)
|
|
{
|
|
if( ${GROUP_COUNT(${MACRO_EXTEN}@agents)}=0 )
|
|
{
|
|
Set(OUTBOUND_GROUP=${MACRO_EXTEN}@agents);
|
|
Dial(${device}|300|t);
|
|
switch(${DIALSTATUS})
|
|
{
|
|
case BUSY:
|
|
Busy();
|
|
break;
|
|
case NOANSWER:
|
|
Set(queue-announce-success=0);
|
|
goto queues-manip|O${MACRO_EXTEN}|1;
|
|
default:
|
|
Hangup();
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Busy();
|
|
}
|
|
}
|
|
|
|
In the callagent macro above, the ${MACRO_EXTEN} will
|
|
be 6121, or 6165, etc, which is the extension of the agent.
|
|
|
|
The use of the GROUP_COUNT, and OUTBOUND_GROUP follow this line
|
|
of thinking. Incoming calls can be queued to ring all agents in the
|
|
current priority. If some of those agents are already talking, they
|
|
would get bothersome call-waiting tones. To avoid this inconvenience,
|
|
when an agent gets a call, the OUTBOUND_GROUP assigns that
|
|
conversation to the group specified, for instance 6171@agents.
|
|
The ${GROUP_COUNT()} variable on a subsequent call should return
|
|
"1" for that group. If GROUP_COUNT returns 1, then the busy()
|
|
is returned without actually trying to dial the agent.
|
|
|
|
================ Pre Acknowledgement Message
|
|
|
|
If you would like to have a pre acknowledge message with option to reject the message
|
|
you can use the following dialplan Macro as a base with the 'M' dial argument.
|
|
|
|
[macro-screen]
|
|
exten=>s,1,Wait(.25)
|
|
exten=>s,2,Read(ACCEPT|screen-callee-options|1)
|
|
exten=>s,3,Gotoif($[${ACCEPT} = 1] ?50)
|
|
exten=>s,4,Gotoif($[${ACCEPT} = 2] ?30)
|
|
exten=>s,5,Gotoif($[${ACCEPT} = 3] ?40)
|
|
exten=>s,6,Gotoif($[${ACCEPT} = 4] ?30:30)
|
|
exten=>s,30,Set(MACRO_RESULT=CONTINUE)
|
|
exten=>s,40,Read(TEXTEN|custom/screen-exten|)
|
|
exten=>s,41,Gotoif($[${LEN(${TEXTEN})} = 3]?42:45)
|
|
exten=>s,42,Set(MACRO_RESULT=GOTO:from-internal^${TEXTEN}^1)
|
|
exten=>s,45,Gotoif($[${TEXTEN} = 0] ?46:4)
|
|
exten=>s,46,Set(MACRO_RESULT=CONTINUE)
|
|
exten=>s,50,Playback(after-the-tone)
|
|
exten=>s,51,Playback(connected)
|
|
exten=>s,52,Playback(beep)
|
|
|
|
================ Caveats
|
|
|
|
In the above examples, some of the possible error checking has been omitted,
|
|
to reduce clutter and make the examples clearer.
|
|
|