FS-9609 - [mod_callcenter] BLF for Queues, callcenter_track app, EXIT_WITH_KEY reason and xml_curl improvements

This change implements a few new features, there's no behavior change at all.

1 - BLF for queues

You can now use BLF for knowing when there are callers waiting on a queue,
the pattern to be subscribed is callcenter+queuename@default or @domain name.

2 - callcenter_track app

Now every agent has an external_calls_count column that will be read upon
selecting agent to offer new calls.
If you start an external call (inbound or outbound) and don't wan't
mod_callcenter to offer new calls for this agent just call this app
passing the agent name as argument:

<action application="callcenter_track" data="agent1"/>

This will increment the external calls count, and to make mod_callcenter look to this variable you need to set this queue parameter:

<param name="skip-agents-with-external-calls" value="true"/>

3 - EXIT_WITH_KEY reason

If the member press a key to exit the queue the cc_cancel_reason will be this now.

4 - xml_curl config loading improvements

Prior to this mod_callcenter query your web server a LOT of times,
this will make it only query once per queue, just watch for CC-Queue
request param and build your xml with everything related to that queue,
including tiers (tiers wasn't loaded before this).
This commit is contained in:
Italo Rossi 2016-10-04 23:07:42 -03:00
parent 5957beafa2
commit 15a232b5bb
1 changed files with 272 additions and 80 deletions

View File

@ -49,8 +49,8 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_callcenter_load);
*/
SWITCH_MODULE_DEFINITION(mod_callcenter, mod_callcenter_load, mod_callcenter_shutdown, NULL);
static switch_status_t load_agent(const char *agent_name, switch_event_t *params);
static switch_status_t load_tiers(switch_bool_t load_all, const char *queue_name, const char *agent_name, switch_event_t *params);
static switch_status_t load_agent(const char *agent_name, switch_event_t *params, switch_xml_t x_agents_cfg);
static switch_status_t load_tiers(switch_bool_t load_all, const char *queue_name, const char *agent_name, switch_event_t *params, switch_xml_t x_tiers_cfg);
static const char *global_cf = "callcenter.conf";
struct cc_status_table {
@ -163,7 +163,8 @@ typedef enum {
CC_MEMBER_CANCEL_REASON_NONE,
CC_MEMBER_CANCEL_REASON_TIMEOUT,
CC_MEMBER_CANCEL_REASON_NO_AGENT_TIMEOUT,
CC_MEMBER_CANCEL_REASON_BREAK_OUT
CC_MEMBER_CANCEL_REASON_BREAK_OUT,
CC_MEMBER_CANCEL_REASON_EXIT_WITH_KEY
} cc_member_cancel_reason_t;
static struct cc_member_cancel_reason_table MEMBER_CANCEL_REASON_CHART[] = {
@ -171,6 +172,7 @@ static struct cc_member_cancel_reason_table MEMBER_CANCEL_REASON_CHART[] = {
{"TIMEOUT", CC_MEMBER_CANCEL_REASON_TIMEOUT},
{"NO_AGENT_TIMEOUT", CC_MEMBER_CANCEL_REASON_NO_AGENT_TIMEOUT},
{"BREAK_OUT", CC_MEMBER_CANCEL_REASON_BREAK_OUT},
{"EXIT_WITH_KEY", CC_MEMBER_CANCEL_REASON_EXIT_WITH_KEY},
{NULL, 0}
};
@ -229,7 +231,8 @@ static char agents_sql[] =
" no_answer_count INTEGER NOT NULL DEFAULT 0,\n"
" calls_answered INTEGER NOT NULL DEFAULT 0,\n"
" talk_time INTEGER NOT NULL DEFAULT 0,\n"
" ready_time INTEGER NOT NULL DEFAULT 0\n"
" ready_time INTEGER NOT NULL DEFAULT 0,\n"
" external_calls_count INTEGER NOT NULL DEFAULT 0\n"
");\n";
static char tiers_sql[] =
@ -423,6 +426,7 @@ static struct {
int32_t running;
switch_mutex_t *mutex;
switch_memory_pool_t *pool;
switch_event_node_t *node;
} globals;
#define CC_QUEUE_CONFIGITEM_COUNT 100
@ -449,6 +453,7 @@ struct cc_queue {
uint32_t max_wait_time;
uint32_t max_wait_time_with_no_agent;
uint32_t max_wait_time_with_no_agent_time_reached;
char *agent_no_answer_status;
uint32_t calls_answered;
uint32_t calls_abandoned;
@ -461,12 +466,16 @@ struct cc_queue {
switch_time_t last_agent_exist;
switch_time_t last_agent_exist_check;
switch_bool_t skip_agents_with_external_calls;
switch_xml_config_item_t config[CC_QUEUE_CONFIGITEM_COUNT];
switch_xml_config_string_options_t config_str_pool;
};
typedef struct cc_queue cc_queue_t;
static void cc_send_presence(cc_queue_t *queue);
static void free_queue(cc_queue_t *queue)
{
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Destroying Profile %s\n", queue->name);
@ -560,6 +569,10 @@ cc_queue_t *queue_set_config(cc_queue_t *queue)
SWITCH_CONFIG_SET_ITEM(queue->config[i++], "max-wait-time-with-no-agent", SWITCH_CONFIG_INT, 0, &queue->max_wait_time_with_no_agent, 0, &config_int_0_86400, NULL, NULL);
SWITCH_CONFIG_SET_ITEM(queue->config[i++], "max-wait-time-with-no-agent-time-reached", SWITCH_CONFIG_INT, 0, &queue->max_wait_time_with_no_agent_time_reached, 5, &config_int_0_86400, NULL, NULL);
SWITCH_CONFIG_SET_ITEM(queue->config[i++], "agent-no-answer-status", SWITCH_CONFIG_STRING, 0, &queue->agent_no_answer_status, cc_agent_status2str(CC_AGENT_STATUS_ON_BREAK), &queue->config_str_pool, NULL, NULL);
SWITCH_CONFIG_SET_ITEM(queue->config[i++], "skip-agents-with-external-calls", SWITCH_CONFIG_BOOL, 0, &queue->skip_agents_with_external_calls, SWITCH_TRUE, NULL, NULL, NULL);
switch_assert(i < CC_QUEUE_CONFIGITEM_COUNT);
return queue;
@ -678,10 +691,11 @@ end:
return ret;
}
static cc_queue_t *load_queue(const char *queue_name, switch_bool_t request_agents, switch_bool_t request_tiers)
static cc_queue_t *load_queue(const char *queue_name, switch_bool_t request_agents, switch_bool_t request_tiers, switch_xml_t x_queues_cfg)
{
cc_queue_t *queue = NULL;
switch_xml_t x_queues, x_queue, cfg, xml, x_agents, x_agent;
switch_xml_t x_queues, x_queue, cfg, x_agents, x_agent, x_tiers;
switch_xml_t xml = NULL;
switch_event_t *event = NULL;
switch_event_t *params = NULL;
@ -689,13 +703,16 @@ static cc_queue_t *load_queue(const char *queue_name, switch_bool_t request_agen
switch_assert(params);
switch_event_add_header_string(params, SWITCH_STACK_BOTTOM, "CC-Queue", queue_name);
if (!(xml = switch_xml_open_cfg(global_cf, &cfg, params))) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Open of %s failed\n", global_cf);
goto end;
}
if (!(x_queues = switch_xml_child(cfg, "queues"))) {
goto end;
if (x_queues_cfg) {
x_queues = x_queues_cfg;
} else {
if (!(xml = switch_xml_open_cfg(global_cf, &cfg, params))) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Open of %s failed\n", global_cf);
goto end;
}
if (!(x_queues = switch_xml_child(cfg, "queues"))) {
goto end;
}
}
if ((x_queue = switch_xml_find_child(x_queues, "queue", "name", queue_name))) {
@ -733,6 +750,11 @@ static cc_queue_t *load_queue(const char *queue_name, switch_bool_t request_agen
queue->calls_answered = 0;
queue->calls_abandoned = 0;
if (cc_agent_str2status(queue->agent_no_answer_status) == CC_AGENT_STATUS_UNKNOWN) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Queue %s has invalid agent-no-answer-status, setting to %s", queue->name, cc_agent_status2str(CC_AGENT_STATUS_ON_BREAK));
queue->agent_no_answer_status = switch_core_strdup(pool, cc_agent_status2str(CC_AGENT_STATUS_ON_BREAK));
}
switch_mutex_init(&queue->mutex, SWITCH_MUTEX_NESTED, queue->pool);
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Added queue %s\n", queue->name);
switch_core_hash_insert(globals.queue_hash, queue->name, queue);
@ -744,13 +766,13 @@ static cc_queue_t *load_queue(const char *queue_name, switch_bool_t request_agen
for (x_agent = switch_xml_child(x_agents, "agent"); x_agent; x_agent = x_agent->next) {
const char *agent = switch_xml_attr(x_agent, "name");
if (agent) {
load_agent(agent, params);
load_agent(agent, params, x_agents);
}
}
}
/* Importing from XML config Agent Tiers */
if (queue && request_tiers) {
load_tiers(SWITCH_TRUE, NULL, NULL, params);
if (queue && request_tiers && (x_tiers = switch_xml_child(cfg, "tiers"))) {
load_tiers(SWITCH_TRUE, queue_name, NULL, params, x_tiers);
}
end:
@ -773,7 +795,7 @@ static cc_queue_t *get_queue(const char *queue_name)
switch_mutex_lock(globals.mutex);
if (!(queue = switch_core_hash_find(globals.queue_hash, queue_name))) {
queue = load_queue(queue_name, SWITCH_FALSE, SWITCH_FALSE);
queue = load_queue(queue_name, SWITCH_FALSE, SWITCH_FALSE, NULL);
}
if (queue) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG10, "[%s] rwlock\n", queue->name);
@ -805,6 +827,7 @@ struct call_helper {
int reject_delay_time;
int busy_delay_time;
int no_answer_delay_time;
cc_agent_status_t agent_no_answer_status;
switch_memory_pool_t *pool;
};
@ -1273,15 +1296,21 @@ cc_status_t cc_tier_del(const char *queue_name, const char *agent)
return result;
}
static switch_status_t load_agent(const char *agent_name, switch_event_t *params)
static switch_status_t load_agent(const char *agent_name, switch_event_t *params, switch_xml_t x_agents_cfg)
{
switch_xml_t x_agents, x_agent, cfg, xml;
if (!(xml = switch_xml_open_cfg(global_cf, &cfg, params))) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Open of %s failed\n", global_cf);
return SWITCH_STATUS_FALSE;
}
if (!(x_agents = switch_xml_child(cfg, "agents"))) {
goto end;
switch_xml_t x_agents, x_agent, cfg;
switch_xml_t xml = NULL;
if (x_agents_cfg) {
x_agents = x_agents_cfg;
} else {
if (!(xml = switch_xml_open_cfg(global_cf, &cfg, params))) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Open of %s failed\n", global_cf);
return SWITCH_STATUS_FALSE;
}
if (!(x_agents = switch_xml_child(cfg, "agents"))) {
goto end;
}
}
if ((x_agent = switch_xml_find_child(x_agents, "agent", "name", agent_name))) {
@ -1365,18 +1394,23 @@ static switch_status_t load_tier(const char *queue, const char *agent, const cha
return SWITCH_STATUS_SUCCESS;
}
static switch_status_t load_tiers(switch_bool_t load_all, const char *queue_name, const char *agent_name, switch_event_t *params)
static switch_status_t load_tiers(switch_bool_t load_all, const char *queue_name, const char *agent_name, switch_event_t *params, switch_xml_t x_tiers_cfg)
{
switch_xml_t x_tiers, x_tier, cfg, xml;
switch_xml_t x_tiers, x_tier, cfg;
switch_xml_t xml = NULL;
switch_status_t result = SWITCH_STATUS_FALSE;
if (!(xml = switch_xml_open_cfg(global_cf, &cfg, params))) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Open of %s failed\n", global_cf);
return SWITCH_STATUS_FALSE;
}
if (x_tiers_cfg) {
x_tiers = x_tiers_cfg;
} else {
if (!(xml = switch_xml_open_cfg(global_cf, &cfg, params))) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Open of %s failed\n", global_cf);
return SWITCH_STATUS_FALSE;
}
if (!(x_tiers = switch_xml_child(cfg, "tiers"))) {
goto end;
if (!(x_tiers = switch_xml_child(cfg, "tiers"))) {
goto end;
}
}
/* Importing from XML config Agent Tiers */
@ -1408,7 +1442,7 @@ end:
static switch_status_t load_config(void)
{
switch_status_t status = SWITCH_STATUS_SUCCESS;
switch_xml_t cfg, xml, settings, param, x_queues, x_queue, x_agents, x_agent;
switch_xml_t cfg, xml, settings, param, x_queues, x_queue, x_agents, x_agent, x_tiers;
switch_cache_db_handle_t *dbh = NULL;
char *sql = NULL;
@ -1459,6 +1493,7 @@ static switch_status_t load_config(void)
"alter table agents add busy_delay_time integer not null default 0;");
switch_cache_db_test_reactive(dbh, "select count(no_answer_delay_time) from agents", NULL, "alter table agents add no_answer_delay_time integer not null default 0;");
switch_cache_db_test_reactive(dbh, "select count(ready_time) from agents", "drop table agents", agents_sql);
switch_cache_db_test_reactive(dbh, "select external_calls_count from agents", NULL, "alter table agents add external_calls_count integer not null default 0;");
switch_cache_db_test_reactive(dbh, "select count(queue) from tiers", "drop table tiers" , tiers_sql);
switch_cache_db_release_db_handle(&dbh);
@ -1466,7 +1501,8 @@ static switch_status_t load_config(void)
/* Reset a unclean shutdown */
sql = switch_mprintf("update agents set state = 'Waiting', uuid = '' where system = 'single_box';"
"update tiers set state = 'Ready' where agent IN (select name from agents where system = 'single_box');"
"update members set state = '%q', session_uuid = '' where system = 'single_box';",
"update members set state = '%q', session_uuid = '' where system = 'single_box';"
"update agents set external_calls_count = 0 where system = 'single_box';",
cc_member_state2str(CC_MEMBER_STATE_ABANDONED));
cc_execute_sql(NULL, sql, NULL);
switch_safe_free(sql);
@ -1488,7 +1524,8 @@ static switch_status_t load_config(void)
/* Loading queue into memory struct */
if ((x_queues = switch_xml_child(cfg, "queues"))) {
for (x_queue = switch_xml_child(x_queues, "queue"); x_queue; x_queue = x_queue->next) {
load_queue(switch_xml_attr_soft(x_queue, "name"), SWITCH_FALSE, SWITCH_FALSE);
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Loading queue %s\n", switch_xml_attr_soft(x_queue, "name"));
load_queue(switch_xml_attr_soft(x_queue, "name"), SWITCH_FALSE, SWITCH_FALSE, x_queues);
}
}
@ -1497,13 +1534,17 @@ static switch_status_t load_config(void)
for (x_agent = switch_xml_child(x_agents, "agent"); x_agent; x_agent = x_agent->next) {
const char *agent = switch_xml_attr(x_agent, "name");
if (agent) {
load_agent(agent, NULL);
load_agent(agent, NULL, x_agents);
}
}
}
/* Importing from XML config Agent Tiers */
load_tiers(SWITCH_TRUE, NULL, NULL, NULL);
if ((x_tiers = switch_xml_child(cfg, "tiers"))) {
load_tiers(SWITCH_TRUE, NULL, NULL, NULL, x_tiers);
} else {
load_tiers(SWITCH_TRUE, NULL, NULL, NULL, NULL);
}
end:
switch_mutex_unlock(globals.mutex);
@ -1970,11 +2011,11 @@ static void *SWITCH_THREAD_FUNC outbound_agent_thread_run(switch_thread_t *threa
cc_execute_sql(NULL, sql, NULL);
switch_safe_free(sql);
/* Put Agent on break because he didn't answer often */
/* Change Agent Status because he didn't answer often */
if (h->max_no_answer > 0 && (h->no_answer_count + 1) >= h->max_no_answer) {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(member_session), SWITCH_LOG_DEBUG, "Agent %s reach maximum no answer of %d, Putting agent on break\n",
h->agent_name, h->max_no_answer);
cc_agent_update("status", cc_agent_status2str(CC_AGENT_STATUS_ON_BREAK), h->agent_name);
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(member_session), SWITCH_LOG_DEBUG, "Agent %s reach maximum no answer of %d, setting agent status to %s\n",
h->agent_name, h->max_no_answer, cc_agent_status2str(h->agent_no_answer_status));
cc_agent_update("status", cc_agent_status2str(h->agent_no_answer_status), h->agent_name);
}
break;
}
@ -2055,6 +2096,8 @@ struct agent_callback {
switch_bool_t tier_rule_wait_multiply_level;
switch_bool_t tier_rule_no_agent_no_wait;
switch_bool_t agent_found;
switch_bool_t skip_agents_with_external_calls;
cc_agent_status_t agent_no_answer_status;
int tier;
int tier_agent_available;
@ -2084,6 +2127,7 @@ static int agents_callback(void *pArg, int argc, char **argv, char **columnNames
const char *agent_tier_level = argv[15];
const char *agent_type = argv[16];
const char *agent_uuid = argv[17];
const char *agent_external_calls_count = argv[18];
switch_bool_t contact_agent = SWITCH_TRUE;
@ -2126,7 +2170,10 @@ static int agents_callback(void *pArg, int argc, char **argv, char **columnNames
if (! (strcasecmp(agent_status, cc_agent_status2str(CC_AGENT_STATUS_ON_BREAK)))) {
contact_agent = SWITCH_FALSE;
}
/* XXX callcenter_track app can update this counter after we selected this agent on database */
if (cbt->skip_agents_with_external_calls && atoi(agent_external_calls_count) > 0) {
contact_agent = SWITCH_FALSE;
}
if (contact_agent == SWITCH_FALSE) {
return 0; /* Continue to next Agent */
}
@ -2207,6 +2254,7 @@ static int agents_callback(void *pArg, int argc, char **argv, char **columnNames
h->reject_delay_time = atoi(agent_reject_delay_time);
h->busy_delay_time = atoi(agent_busy_delay_time);
h->no_answer_delay_time = atoi(agent_no_answer_delay_time);
h->agent_no_answer_status = cbt->agent_no_answer_status;
if (!strcasecmp(cbt->strategy, "ring-progressively")) {
switch_core_session_t *member_session = switch_core_session_locate(cbt->member_session_uuid);
@ -2303,6 +2351,9 @@ static int members_callback(void *pArg, int argc, char **argv, char **columnName
queue_record_template = strdup(queue->record_template);
}
cbt.skip_agents_with_external_calls = queue->skip_agents_with_external_calls;
cbt.agent_no_answer_status = cc_agent_str2status(queue->agent_no_answer_status);
queue_rwunlock(queue);
}
@ -2406,13 +2457,13 @@ static int members_callback(void *pArg, int argc, char **argv, char **columnName
switch_core_session_rwunlock(member_session);
}
sql = switch_mprintf("SELECT system, name, status, contact, no_answer_count, max_no_answer, reject_delay_time, busy_delay_time, no_answer_delay_time, tiers.state, agents.last_bridge_end, agents.wrap_up_time, agents.state, agents.ready_time, tiers.position as tiers_position, tiers.level as tiers_level, agents.type, agents.uuid, agents.last_offered_call as agents_last_offered_call, 1 as dyn_order FROM agents LEFT JOIN tiers ON (agents.name = tiers.agent)"
sql = switch_mprintf("SELECT system, name, status, contact, no_answer_count, max_no_answer, reject_delay_time, busy_delay_time, no_answer_delay_time, tiers.state, agents.last_bridge_end, agents.wrap_up_time, agents.state, agents.ready_time, tiers.position as tiers_position, tiers.level as tiers_level, agents.type, agents.uuid, agents.last_offered_call as agents_last_offered_call, 1 as dyn_order, external_calls_count FROM agents LEFT JOIN tiers ON (agents.name = tiers.agent)"
" WHERE tiers.queue = '%q'"
" AND (agents.status = '%q' OR agents.status = '%q' OR agents.status = '%q')"
" AND tiers.position > %d"
" AND tiers.level = %d"
" UNION "
"SELECT system, name, status, contact, no_answer_count, max_no_answer, reject_delay_time, busy_delay_time, no_answer_delay_time, tiers.state, agents.last_bridge_end, agents.wrap_up_time, agents.state, agents.ready_time, tiers.position as tiers_position, tiers.level as tiers_level, agents.type, agents.uuid, agents.last_offered_call as agents_last_offered_call, 2 as dyn_order FROM agents LEFT JOIN tiers ON (agents.name = tiers.agent)"
"SELECT system, name, status, contact, no_answer_count, max_no_answer, reject_delay_time, busy_delay_time, no_answer_delay_time, tiers.state, agents.last_bridge_end, agents.wrap_up_time, agents.state, agents.ready_time, tiers.position as tiers_position, tiers.level as tiers_level, agents.type, agents.uuid, agents.last_offered_call as agents_last_offered_call, 2 as dyn_order, external_calls_count FROM agents LEFT JOIN tiers ON (agents.name = tiers.agent)"
" WHERE tiers.queue = '%q'"
" AND (agents.status = '%q' OR agents.status = '%q' OR agents.status = '%q')"
" ORDER BY dyn_order asc, tiers_level, tiers_position, agents_last_offered_call",
@ -2424,13 +2475,13 @@ static int members_callback(void *pArg, int argc, char **argv, char **columnName
cc_agent_status2str(CC_AGENT_STATUS_AVAILABLE), cc_agent_status2str(CC_AGENT_STATUS_ON_BREAK), cc_agent_status2str(CC_AGENT_STATUS_AVAILABLE_ON_DEMAND)
);
} else if (!strcasecmp(queue->strategy, "round-robin")) {
sql = switch_mprintf("SELECT system, name, status, contact, no_answer_count, max_no_answer, reject_delay_time, busy_delay_time, no_answer_delay_time, tiers.state, agents.last_bridge_end, agents.wrap_up_time, agents.state, agents.ready_time, tiers.position as tiers_position, tiers.level as tiers_level, agents.type, agents.uuid, agents.last_offered_call as agents_last_offered_call, 1 as dyn_order FROM agents LEFT JOIN tiers ON (agents.name = tiers.agent)"
sql = switch_mprintf("SELECT system, name, status, contact, no_answer_count, max_no_answer, reject_delay_time, busy_delay_time, no_answer_delay_time, tiers.state, agents.last_bridge_end, agents.wrap_up_time, agents.state, agents.ready_time, tiers.position as tiers_position, tiers.level as tiers_level, agents.type, agents.uuid, agents.last_offered_call as agents_last_offered_call, 1 as dyn_order, external_calls_count FROM agents LEFT JOIN tiers ON (agents.name = tiers.agent)"
" WHERE tiers.queue = '%q'"
" AND (agents.status = '%q' OR agents.status = '%q' OR agents.status = '%q')"
" AND tiers.position > (SELECT tiers.position FROM agents LEFT JOIN tiers ON (agents.name = tiers.agent) WHERE tiers.queue = '%q' AND agents.last_offered_call > 0 ORDER BY agents.last_offered_call DESC LIMIT 1)"
" AND tiers.level = (SELECT tiers.level FROM agents LEFT JOIN tiers ON (agents.name = tiers.agent) WHERE tiers.queue = '%q' AND agents.last_offered_call > 0 ORDER BY agents.last_offered_call DESC LIMIT 1)"
" UNION "
"SELECT system, name, status, contact, no_answer_count, max_no_answer, reject_delay_time, busy_delay_time, no_answer_delay_time, tiers.state, agents.last_bridge_end, agents.wrap_up_time, agents.state, agents.ready_time, tiers.position as tiers_position, tiers.level as tiers_level, agents.type, agents.uuid, agents.last_offered_call as agents_last_offered_call, 2 as dyn_order FROM agents LEFT JOIN tiers ON (agents.name = tiers.agent)"
"SELECT system, name, status, contact, no_answer_count, max_no_answer, reject_delay_time, busy_delay_time, no_answer_delay_time, tiers.state, agents.last_bridge_end, agents.wrap_up_time, agents.state, agents.ready_time, tiers.position as tiers_position, tiers.level as tiers_level, agents.type, agents.uuid, agents.last_offered_call as agents_last_offered_call, 2 as dyn_order, external_calls_count FROM agents LEFT JOIN tiers ON (agents.name = tiers.agent)"
" WHERE tiers.queue = '%q'"
" AND (agents.status = '%q' OR agents.status = '%q' OR agents.status = '%q')"
" ORDER BY dyn_order asc, tiers_level, tiers_position, agents_last_offered_call",
@ -2465,7 +2516,7 @@ static int members_callback(void *pArg, int argc, char **argv, char **columnName
sql_order_by = switch_mprintf("level, position, agents.last_offered_call");
}
sql = switch_mprintf("SELECT system, name, status, contact, no_answer_count, max_no_answer, reject_delay_time, busy_delay_time, no_answer_delay_time, tiers.state, agents.last_bridge_end, agents.wrap_up_time, agents.state, agents.ready_time, tiers.position, tiers.level, agents.type, agents.uuid FROM agents LEFT JOIN tiers ON (agents.name = tiers.agent)"
sql = switch_mprintf("SELECT system, name, status, contact, no_answer_count, max_no_answer, reject_delay_time, busy_delay_time, no_answer_delay_time, tiers.state, agents.last_bridge_end, agents.wrap_up_time, agents.state, agents.ready_time, tiers.position, tiers.level, agents.type, agents.uuid, external_calls_count FROM agents LEFT JOIN tiers ON (agents.name = tiers.agent)"
" WHERE tiers.queue = '%q'"
" AND (agents.status = '%q' OR agents.status = '%q' OR agents.status = '%q')"
" ORDER BY %q",
@ -2902,6 +2953,7 @@ SWITCH_STANDARD_APP(callcenter_function)
/* Send Event with queue count */
cc_queue_count(queue_name);
cc_send_presence(queue);
/* Start Thread that will playback different prompt to the channel */
switch_core_new_memory_pool(&pool);
@ -2961,6 +3013,7 @@ SWITCH_STANDARD_APP(callcenter_function)
} else if (status == SWITCH_STATUS_BREAK) {
char buf[2] = { ht.dtmf, 0 };
switch_channel_set_variable(member_channel, "cc_exit_key", buf);
h->member_cancel_reason = CC_MEMBER_CANCEL_REASON_EXIT_WITH_KEY;
break;
} else if (!SWITCH_READ_ACCEPTABLE(status)) {
break;
@ -2969,6 +3022,7 @@ SWITCH_STANDARD_APP(callcenter_function)
if ((switch_ivr_collect_digits_callback(member_session, &args, 0, 0)) == SWITCH_STATUS_BREAK) {
char buf[2] = { ht.dtmf, 0 };
switch_channel_set_variable(member_channel, "cc_exit_key", buf);
h->member_cancel_reason = CC_MEMBER_CANCEL_REASON_EXIT_WITH_KEY;
break;
}
}
@ -3052,12 +3106,139 @@ SWITCH_STANDARD_APP(callcenter_function)
/* Send Event with queue count */
cc_queue_count(queue_name);
cc_send_presence(queue);
end:
return;
}
switch_application_interface_t *app_interface = NULL;
static switch_status_t cc_hook_state_run(switch_core_session_t *session)
{
switch_channel_t *channel = switch_core_session_get_channel(session);
switch_channel_state_t state = switch_channel_get_state(channel);
const char *agent_name = NULL;
char *sql = NULL;
agent_name = switch_channel_get_variable(channel, "cc_tracked_agent");
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Called cc_hook_hanguphook channel %s with state %s", switch_channel_get_name(channel), switch_channel_state_name(state));
if (state == CS_HANGUP) {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Tracked call for agent %s ended, decreasing external_calls_count", agent_name);
sql = switch_mprintf("UPDATE agents SET external_calls_count = external_calls_count - 1 WHERE name = '%q'", agent_name);
cc_execute_sql(NULL, sql, NULL);
switch_safe_free(sql);
switch_core_event_hook_remove_state_run(session, cc_hook_state_run);
UNPROTECT_INTERFACE(app_interface);
}
return SWITCH_STATUS_SUCCESS;
}
SWITCH_STANDARD_APP(callcenter_track)
{
switch_channel_t *channel = switch_core_session_get_channel(session);
char agent_status[255];
char *agent_name = NULL;
char *sql = NULL;
if (zstr(data)) {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Missing agent name\n");
return;
}
if (cc_agent_get("status", data, agent_status, sizeof(agent_status)) != CC_STATUS_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "Invalid agent %s", data);
return;
}
agent_name = switch_safe_strdup(data);
switch_channel_set_variable(channel, "cc_tracked_agent", agent_name);
sql = switch_mprintf("UPDATE agents SET external_calls_count = external_calls_count + 1 WHERE name = '%q'", agent_name);
cc_execute_sql(NULL, sql, NULL);
switch_safe_free(sql);
switch_core_event_hook_add_state_run(session, cc_hook_state_run);
PROTECT_INTERFACE(app_interface);
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Tracking this call for agent %s", data);
switch_safe_free(agent_name);
return;
}
static void cc_send_presence(cc_queue_t *queue) {
char *sql;
char res[256] = "";
int count = 0;
switch_event_t *send_event;
sql = switch_mprintf("SELECT COUNT(*) FROM members WHERE queue = '%q' AND state = '%q'", queue->name, cc_member_state2str(CC_MEMBER_STATE_WAITING));
cc_execute_sql2str(NULL, NULL, sql, res, sizeof(res));
count = atoi(res);
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Queue has %d waiting calls.\n", count);
if (switch_event_create(&send_event, SWITCH_EVENT_PRESENCE_IN) == SWITCH_STATUS_SUCCESS) {
switch_event_add_header_string(send_event, SWITCH_STACK_BOTTOM, "proto", "callcenter");
switch_event_add_header(send_event, SWITCH_STACK_BOTTOM, "login", "%s", queue->name);
switch_event_add_header_string(send_event, SWITCH_STACK_BOTTOM, "from", queue->name);
if (count > 0) {
switch_event_add_header(send_event, SWITCH_STACK_BOTTOM, "force-status", "Active (%d waiting)", count);
} else {
switch_event_add_header_string(send_event, SWITCH_STACK_BOTTOM, "force-status", "Idle");
}
switch_event_add_header_string(send_event, SWITCH_STACK_BOTTOM, "rpid", "unknown");
switch_event_add_header_string(send_event, SWITCH_STACK_BOTTOM, "event_type", "presence");
switch_event_add_header_string(send_event, SWITCH_STACK_BOTTOM, "alt_event_type", "dialog");
switch_event_add_header(send_event, SWITCH_STACK_BOTTOM, "event_count", "%d", 0);
switch_event_add_header_string(send_event, SWITCH_STACK_BOTTOM, "channel-state", count > 0 ? "CS_ROUTING" : "CS_HANGUP");
switch_event_add_header_string(send_event, SWITCH_STACK_BOTTOM, "unique-id", queue->name);
switch_event_add_header_string(send_event, SWITCH_STACK_BOTTOM, "answer-state", count > 0 ? "confirmed" : "terminated");
switch_event_add_header_string(send_event, SWITCH_STACK_BOTTOM, "presence-call-direction", "inbound");
switch_event_fire(&send_event);
} else {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to create presence in event\n");
}
switch_safe_free(sql);
}
static void cc_presence_event_handler(switch_event_t *event) {
char *to = switch_event_get_header(event, "to");
char *dup_to = NULL, *queue_name;
cc_queue_t *queue;
if (!globals.running) {
return;
}
// DUMP_EVENT(event);
if (!to || strncasecmp(to, "callcenter+", 11) || !strchr(to, '@')) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Not interested!\n");
return;
}
dup_to = strdup(to);
queue_name = dup_to + 11;
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Searching queue %s\n", queue_name);
queue = get_queue(queue_name);
if (!queue) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Queue not found, exit!\n");
return;
}
cc_send_presence(queue);
queue_rwunlock(queue);
switch_safe_free(dup_to);
return;
}
struct list_result {
const char *name;
const char *format;
@ -3227,7 +3408,7 @@ SWITCH_STANDARD_API(cc_config_api_function)
goto done;
} else {
const char *agent = argv[0 + initial_argc];
switch (load_agent(agent, NULL)) {
switch (load_agent(agent, NULL, NULL)) {
case SWITCH_STATUS_SUCCESS:
stream->write_function(stream, "%s", "+OK\n");
break;
@ -3420,7 +3601,7 @@ SWITCH_STANDARD_API(cc_config_api_function)
if (!strcasecmp(queue, "all")) {
load_all = SWITCH_TRUE;
}
switch (load_tiers(load_all, queue, agent, NULL)) {
switch (load_tiers(load_all, queue, agent, NULL, NULL)) {
case SWITCH_STATUS_SUCCESS:
stream->write_function(stream, "%s", "+OK\n");
break;
@ -3448,7 +3629,7 @@ SWITCH_STANDARD_API(cc_config_api_function)
} else {
const char *queue_name = argv[0 + initial_argc];
cc_queue_t *queue = NULL;
if ((queue = load_queue(queue_name, SWITCH_TRUE, SWITCH_TRUE))) {
if ((queue = load_queue(queue_name, SWITCH_TRUE, SWITCH_TRUE, NULL))) {
stream->write_function(stream, "%s", "+OK\n");
} else {
stream->write_function(stream, "%s", "-ERR Invalid Queue not found!\n");
@ -3474,7 +3655,7 @@ SWITCH_STANDARD_API(cc_config_api_function)
const char *queue_name = argv[0 + initial_argc];
cc_queue_t *queue = NULL;
destroy_queue(queue_name);
if ((queue = load_queue(queue_name, SWITCH_TRUE, SWITCH_TRUE))) {
if ((queue = load_queue(queue_name, SWITCH_TRUE, SWITCH_TRUE, NULL))) {
stream->write_function(stream, "%s", "+OK\n");
} else {
stream->write_function(stream, "%s", "-ERR Invalid Queue not found!\n");
@ -3490,7 +3671,7 @@ SWITCH_STANDARD_API(cc_config_api_function)
"tier_rule_wait_second|tier_rule_wait_multiply_level|"\
"tier_rule_no_agent_no_wait|discard_abandoned_after|"\
"abandoned_resume_allowed|max_wait_time|max_wait_time_with_no_agent|"\
"max_wait_time_with_no_agent_time_reached|record_template|calls_answered|calls_abandoned|ring_progressively_delay\n");
"max_wait_time_with_no_agent_time_reached|record_template|calls_answered|calls_abandoned|ring_progressively_delay|skip_agents_with_external_calls|agent_no_answer_status\n");
switch_mutex_lock(globals.mutex);
for (hi = switch_core_hash_first(globals.queue_hash); hi; hi = switch_core_hash_next(&hi)) {
void *val = NULL;
@ -3499,7 +3680,7 @@ SWITCH_STANDARD_API(cc_config_api_function)
cc_queue_t *queue;
switch_core_hash_this(hi, &key, &keylen, &val);
queue = (cc_queue_t *) val;
stream->write_function(stream, "%s|%s|%s|%s|%s|%d|%s|%s|%d|%s|%d|%d|%d|%s|%d|%d|%d\n",
stream->write_function(stream, "%s|%s|%s|%s|%s|%d|%s|%s|%d|%s|%d|%d|%d|%s|%d|%d|%d|%s|%s\n",
queue->name,
queue->strategy,
queue->moh,
@ -3516,7 +3697,9 @@ SWITCH_STANDARD_API(cc_config_api_function)
queue->record_template,
queue->calls_answered,
queue->calls_abandoned,
queue->ring_progressively_delay);
queue->ring_progressively_delay,
(queue->skip_agents_with_external_calls?"true":"false"),
queue->agent_no_answer_status);
queue = NULL;
}
switch_mutex_unlock(globals.mutex);
@ -3657,29 +3840,31 @@ SWITCH_STANDARD_JSON_API(json_callcenter_config_function)
switch_mutex_lock(globals.mutex);
for (hi = switch_core_hash_first(globals.queue_hash); hi; hi = switch_core_hash_next(&hi)) {
cJSON *o = cJSON_CreateObject();
void *val = NULL;
const void *key;
switch_ssize_t keylen;
cc_queue_t *queue;
switch_core_hash_this(hi, &key, &keylen, &val);
queue = (cc_queue_t *) val;
cJSON_AddItemToObject(o, "name", cJSON_CreateString(queue->name));
cJSON_AddItemToObject(o, "strategy", cJSON_CreateString(queue->strategy));
cJSON_AddItemToObject(o, "moh_sound", cJSON_CreateString(queue->moh));
cJSON_AddItemToObject(o, "time_base_score", cJSON_CreateString(queue->time_base_score));
cJSON_AddItemToObject(o, "tier_rules_apply", cJSON_CreateString(queue->tier_rules_apply ? "true" : "false"));
cJSON_AddItemToObject(o, "tier_rule_wait_second", cJSON_CreateNumber(queue->tier_rule_wait_second));
cJSON_AddItemToObject(o, "tier_rule_wait_multiply_level", cJSON_CreateString(queue->tier_rule_wait_multiply_level ? "true": "false"));
cJSON_AddItemToObject(o, "tier_rule_no_agent_no_wait", cJSON_CreateString(queue->tier_rule_no_agent_no_wait ? "true": "false"));
cJSON_AddItemToObject(o, "discard_abandoned_after", cJSON_CreateNumber(queue->discard_abandoned_after));
cJSON_AddItemToObject(o, "abandoned_resume_allowed", cJSON_CreateString(queue->abandoned_resume_allowed ? "true": "false"));
cJSON_AddItemToObject(o, "max_wait_time", cJSON_CreateNumber(queue->max_wait_time));
cJSON_AddItemToObject(o, "max_wait_time_with_no_agent", cJSON_CreateNumber(queue->max_wait_time_with_no_agent));
cJSON_AddItemToObject(o, "max_wait_time_with_no_agent_time_reached", cJSON_CreateNumber(queue->max_wait_time_with_no_agent_time_reached));
cJSON_AddItemToObject(o, "record_template", cJSON_CreateString(queue->record_template));
cJSON_AddItemToArray(reply, o);
queue = NULL;
cJSON *o = cJSON_CreateObject();
void *val = NULL;
const void *key;
switch_ssize_t keylen;
cc_queue_t *queue;
switch_core_hash_this(hi, &key, &keylen, &val);
queue = (cc_queue_t *) val;
cJSON_AddItemToObject(o, "name", cJSON_CreateString(queue->name));
cJSON_AddItemToObject(o, "strategy", cJSON_CreateString(queue->strategy));
cJSON_AddItemToObject(o, "moh_sound", cJSON_CreateString(queue->moh));
cJSON_AddItemToObject(o, "time_base_score", cJSON_CreateString(queue->time_base_score));
cJSON_AddItemToObject(o, "tier_rules_apply", cJSON_CreateString(queue->tier_rules_apply ? "true" : "false"));
cJSON_AddItemToObject(o, "tier_rule_wait_second", cJSON_CreateNumber(queue->tier_rule_wait_second));
cJSON_AddItemToObject(o, "tier_rule_wait_multiply_level", cJSON_CreateString(queue->tier_rule_wait_multiply_level ? "true": "false"));
cJSON_AddItemToObject(o, "tier_rule_no_agent_no_wait", cJSON_CreateString(queue->tier_rule_no_agent_no_wait ? "true": "false"));
cJSON_AddItemToObject(o, "discard_abandoned_after", cJSON_CreateNumber(queue->discard_abandoned_after));
cJSON_AddItemToObject(o, "abandoned_resume_allowed", cJSON_CreateString(queue->abandoned_resume_allowed ? "true": "false"));
cJSON_AddItemToObject(o, "max_wait_time", cJSON_CreateNumber(queue->max_wait_time));
cJSON_AddItemToObject(o, "max_wait_time_with_no_agent", cJSON_CreateNumber(queue->max_wait_time_with_no_agent));
cJSON_AddItemToObject(o, "max_wait_time_with_no_agent_time_reached", cJSON_CreateNumber(queue->max_wait_time_with_no_agent_time_reached));
cJSON_AddItemToObject(o, "record_template", cJSON_CreateString(queue->record_template));
cJSON_AddItemToObject(o, "skip_agents_with_external_calls", cJSON_CreateString(queue->skip_agents_with_external_calls ? "true" : "false"));
cJSON_AddItemToObject(o, "agent_no_answer_status", cJSON_CreateString(queue->agent_no_answer_status));
cJSON_AddItemToArray(reply, o);
queue = NULL;
}
switch_mutex_unlock(globals.mutex);
*json_reply = reply;
@ -3786,7 +3971,6 @@ SWITCH_STANDARD_JSON_API(json_callcenter_config_function)
/* Macro expands to: switch_status_t mod_callcenter_load(switch_loadable_module_interface_t **module_interface, switch_memory_pool_t *pool) */
SWITCH_MODULE_LOAD_FUNCTION(mod_callcenter_load)
{
switch_application_interface_t *app_interface;
switch_api_interface_t *api_interface;
switch_json_api_interface_t *json_api_interface;
switch_status_t status;
@ -3807,6 +3991,12 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_callcenter_load)
return status;
}
if (switch_event_bind_removable(modname, SWITCH_EVENT_PRESENCE_PROBE, SWITCH_EVENT_SUBCLASS_ANY,
cc_presence_event_handler, NULL, &globals.node) != SWITCH_STATUS_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to subscribe for presence events!\n");
return SWITCH_STATUS_GENERR;
}
switch_mutex_lock(globals.mutex);
globals.running = 1;
switch_mutex_unlock(globals.mutex);
@ -3819,6 +4009,7 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_callcenter_load)
}
SWITCH_ADD_APP(app_interface, "callcenter", "CallCenter", CC_DESC, callcenter_function, CC_USAGE, SAF_NONE);
SWITCH_ADD_APP(app_interface, "callcenter_track", "CallCenter Track Call", "Track external mod_callcenter calls to avoid place new calls", callcenter_track, CC_USAGE, SAF_NONE);
SWITCH_ADD_API(api_interface, "callcenter_config", "Config of callcenter", cc_config_api_function, CC_CONFIG_API_SYNTAX);
SWITCH_ADD_JSON_API(json_api_interface, "callcenter_config", "JSON Callcenter API", json_callcenter_config_function, "");
@ -3875,7 +4066,8 @@ SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_callcenter_shutdown)
switch_event_free_subclass(CALLCENTER_EVENT);
switch_event_unbind(&globals.node);
switch_mutex_lock(globals.mutex);
if (globals.running == 1) {
globals.running = 0;