add Steve Murphy's guide on setting up call queues using dynamic queue members,
authorRussell Bryant <russell@russellbryant.com>
Thu, 17 Aug 2006 18:09:59 +0000 (18:09 +0000)
committerRussell Bryant <russell@russellbryant.com>
Thu, 17 Aug 2006 18:09:59 +0000 (18:09 +0000)
including examples in AEL.

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@40254 65c4cc65-6c06-0410-ace0-fbb531ad65f3

doc/queues.txt [new file with mode: 0644]

diff --git a/doc/queues.txt b/doc/queues.txt
new file mode 100644 (file)
index 0000000..a070252
--- /dev/null
@@ -0,0 +1,403 @@
+
+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:
+
+   ============ queue.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 persistenmembers=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 
+detemine 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();
+                case O:
+                        Playback(agent-loggedoff);
+                        Hangup();
+                }
+        }
+}
+
+
+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});
+                }
+        case O:  // Logout
+                {
+                RemoveQueueMember(${queuename},Local/${MACRO_EXTEN:1}@agents);
+                }
+        case P:  // Pause
+                {
+                PauseQueueMember(${queuename},Local/${MACRO_EXTEN:1}@agents);
+                }
+        case U:  // Unpause
+                {
+                UnpauseQueueMember(${queuename},Local/${MACRO_EXTEN:1}@agents);
+                }
+        default: // Invalid
+                {
+                Playback(invalid);
+                }
+        }
+}
+
+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.
+
+
+================ Caveats 
+
+In the above examples, some of the possible error checking has been omitted,
+to reduce clutter and make the examples clearer.
+