mirror of https://github.com/asterisk/asterisk
				
				
				
			
			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.
		
		
		
		
		
			
		
			
				
					
					
						
							525 lines
						
					
					
						
							17 KiB
						
					
					
				
			
		
		
	
	
							525 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();
 | |
|                 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.
 | |
| 
 | |
| 
 | |
| =======================================================
 | |
| |    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.
 | |
| 
 |