mod_callcenter: add options for not-registered-delay-time, max rejected, max busy and max_not_registered (PR #153)

This commit is contained in:
Antonio Silva 2023-03-30 10:47:01 +01:00
parent f5f7f76cd4
commit f47be763b1
1 changed files with 276 additions and 78 deletions

View File

@ -118,6 +118,15 @@ static struct cc_status_table AGENT_STATUS_CHART[] = {
};
static switch_xml_config_enum_item_t config_cc_agent_status[] = {
{"Unknown", CC_AGENT_STATUS_UNKNOWN},
{"Logged Out", CC_AGENT_STATUS_LOGGED_OUT},
{"Available", CC_AGENT_STATUS_AVAILABLE},
{"Available (On Demand)", CC_AGENT_STATUS_AVAILABLE_ON_DEMAND},
{"On Break", CC_AGENT_STATUS_ON_BREAK},
{NULL, 0}
};
typedef enum {
CC_AGENT_STATE_UNKNOWN = 0,
CC_AGENT_STATE_WAITING = 1,
@ -225,17 +234,23 @@ static char agents_sql[] =
Receiving
In a queue call
*/
" max_no_answer INTEGER NOT NULL DEFAULT 0,\n"
" wrap_up_time INTEGER NOT NULL DEFAULT 0,\n"
" reject_delay_time INTEGER NOT NULL DEFAULT 0,\n"
" busy_delay_time INTEGER NOT NULL DEFAULT 0,\n"
" max_no_answer INTEGER NOT NULL DEFAULT 0,\n"
" no_answer_count INTEGER NOT NULL DEFAULT 0,\n"
" no_answer_delay_time INTEGER NOT NULL DEFAULT 0,\n"
" max_rejected INTEGER NOT NULL DEFAULT 0,\n"
" rejected_count INTEGER NOT NULL DEFAULT 0,\n"
" reject_delay_time INTEGER NOT NULL DEFAULT 0,\n"
" max_busy INTEGER NOT NULL DEFAULT 0,\n"
" busy_count INTEGER NOT NULL DEFAULT 0,\n"
" busy_delay_time INTEGER NOT NULL DEFAULT 0,\n"
" max_not_registered INTEGER NOT NULL DEFAULT 0,\n"
" not_registered_count INTEGER NOT NULL DEFAULT 0,\n"
" not_registered_delay_time INTEGER NOT NULL DEFAULT 5,\n"
" last_bridge_start INTEGER NOT NULL DEFAULT 0,\n"
" last_bridge_end INTEGER NOT NULL DEFAULT 0,\n"
" last_offered_call INTEGER NOT NULL DEFAULT 0,\n"
" last_status_change INTEGER NOT NULL DEFAULT 0,\n"
" 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"
@ -259,6 +274,52 @@ static char tiers_sql[] =
" level INTEGER NOT NULL DEFAULT 1,\n"
" position INTEGER NOT NULL DEFAULT 1\n" ");\n";
struct agent_info_callback {
switch_bool_t found;
int no_answer_count;
int max_no_answer;
int no_answer_delay_time;
int rejected_count;
int max_rejected;
int reject_delay_time;
int busy_count;
int max_busy;
int busy_delay_time;
int not_registered_count;
int max_not_registered;
int not_registered_delay_time;
};
typedef struct agent_info_callback agent_info_callback_t;
static int agent_info_callback(void *pArg, int argc, char **argv, char **columnNames)
{
agent_info_callback_t *cbt = (agent_info_callback_t *) pArg;
cbt->found = SWITCH_TRUE;
cbt->no_answer_count = atoi(argv[0]);
cbt->max_no_answer = atoi(argv[1]);
cbt->no_answer_delay_time = atoi(argv[2]);
cbt->rejected_count = atoi(argv[3]);
cbt->max_rejected = atoi(argv[4]);
cbt->reject_delay_time = atoi(argv[5]);
cbt->busy_count = atoi(argv[6]);
cbt->max_busy = atoi(argv[7]);
cbt->busy_delay_time = atoi(argv[8]);
cbt->not_registered_count = atoi(argv[9]);
cbt->max_not_registered = atoi(argv[10]);
cbt->not_registered_delay_time = atoi(argv[11]);
return 1; /* Single Row Result */
}
static switch_xml_config_int_options_t config_int_0_86400 = { SWITCH_TRUE, 0, SWITCH_TRUE, 86400 };
/* TODO This is temporary until we either move it to the core, or use it differently in the module */
@ -463,9 +524,12 @@ 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;
cc_agent_status_t agent_no_answer_status;
cc_agent_status_t agent_rejected_status;
cc_agent_status_t agent_busy_status;
cc_agent_status_t agent_not_registered_status;
switch_mutex_t *mutex;
@ -560,6 +624,7 @@ cc_queue_t *queue_set_config(cc_queue_t *queue)
SWITCH _CONFIG_SET_ITEM(item, "key", type, flags,
pointer, default, options, help_syntax, help_description)
*/
SWITCH_CONFIG_SET_ITEM(queue->config[i++], "strategy", SWITCH_CONFIG_STRING, 0, &queue->strategy, "longest-idle-agent", &queue->config_str_pool, NULL, NULL);
SWITCH_CONFIG_SET_ITEM(queue->config[i++], "moh-sound", SWITCH_CONFIG_STRING, 0, &queue->moh, NULL, &queue->config_str_pool, NULL, NULL);
SWITCH_CONFIG_SET_ITEM(queue->config[i++], "announce-sound", SWITCH_CONFIG_STRING, 0, &queue->announce, NULL, &queue->config_str_pool, NULL, NULL);
@ -579,7 +644,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++], "agent-no-answer-status", SWITCH_CONFIG_ENUM, 0, &queue->agent_no_answer_status, CC_AGENT_STATUS_ON_BREAK, &config_cc_agent_status, NULL, NULL);
SWITCH_CONFIG_SET_ITEM(queue->config[i++], "agent-rejected-status", SWITCH_CONFIG_ENUM, 0, &queue->agent_rejected_status, CC_AGENT_STATUS_ON_BREAK, &config_cc_agent_status, NULL, NULL);
SWITCH_CONFIG_SET_ITEM(queue->config[i++], "agent-busy-status", SWITCH_CONFIG_ENUM, 0, &queue->agent_busy_status, CC_AGENT_STATUS_ON_BREAK, &config_cc_agent_status, NULL, NULL);
SWITCH_CONFIG_SET_ITEM(queue->config[i++], "agent-not-registered-status", SWITCH_CONFIG_ENUM, 0, &queue->agent_not_registered_status, CC_AGENT_STATUS_ON_BREAK, &config_cc_agent_status, 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);
@ -772,9 +840,24 @@ 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) {
if (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));
queue->agent_no_answer_status = CC_AGENT_STATUS_ON_BREAK;
}
if (queue->agent_rejected_status == CC_AGENT_STATUS_UNKNOWN) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Queue %s has invalid agent-rejected-status, setting to %s", queue->name, cc_agent_status2str(CC_AGENT_STATUS_ON_BREAK));
queue->agent_rejected_status = CC_AGENT_STATUS_ON_BREAK;
}
if (queue->agent_busy_status == CC_AGENT_STATUS_UNKNOWN) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Queue %s has invalid agent-busy-status, setting to %s", queue->name, cc_agent_status2str(CC_AGENT_STATUS_ON_BREAK));
queue->agent_busy_status = CC_AGENT_STATUS_ON_BREAK;
}
if (queue->agent_not_registered_status == CC_AGENT_STATUS_UNKNOWN) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Queue %s has invalid agent-not-registered-status, setting to %s", queue->name, cc_agent_status2str(CC_AGENT_STATUS_ON_BREAK));
queue->agent_not_registered_status = CC_AGENT_STATUS_ON_BREAK;
}
switch_mutex_init(&queue->mutex, SWITCH_MUTEX_NESTED, queue->pool);
@ -844,13 +927,7 @@ struct call_helper {
const char *agent_uuid;
const char *originate_string;
const char *record_template;
int no_answer_count;
int max_no_answer;
int reject_delay_time;
int busy_delay_time;
int no_answer_delay_time;
cc_agent_status_t agent_no_answer_status;
int delay_next_agent_call;
switch_memory_pool_t *pool;
};
@ -1015,7 +1092,8 @@ cc_status_t cc_agent_update(const char *key, const char *value, const char *agen
if (cc_agent_str2status(value) != CC_AGENT_STATUS_UNKNOWN) {
/* Reset values on available only */
if (cc_agent_str2status(value) == CC_AGENT_STATUS_AVAILABLE) {
sql = switch_mprintf("UPDATE agents SET status = '%q', last_status_change = '%" SWITCH_TIME_T_FMT "', talk_time = 0, calls_answered = 0, no_answer_count = 0"
sql = switch_mprintf("UPDATE agents SET status = '%q', last_status_change = '%" SWITCH_TIME_T_FMT "', talk_time = 0, calls_answered = 0, no_answer_count = 0,"
" rejected_count = 0, busy_count = 0, not_registered_count = 0"
" WHERE name = '%q' AND NOT status = '%q'",
value, local_epoch_time_now(NULL),
agent, value);
@ -1168,7 +1246,27 @@ cc_status_t cc_agent_update(const char *key, const char *value, const char *agen
}
switch_safe_free(sql);
}
} else if (!strcasecmp(key, "not_registered_delay_time")) {
sql = switch_mprintf("UPDATE agents SET not_registered_delay_time = '%ld', instance_id = 'single_box' WHERE name = '%q'", atol(value), agent);
cc_execute_sql(NULL, sql, NULL);
switch_safe_free(sql);
result = CC_STATUS_SUCCESS;
} else if (!strcasecmp(key, "max_rejected")) {
sql = switch_mprintf("UPDATE agents SET max_rejected = '%d', instance_id = 'single_box' WHERE name = '%q'", atoi(value), agent);
cc_execute_sql(NULL, sql, NULL);
switch_safe_free(sql);
result = CC_STATUS_SUCCESS;
} else if (!strcasecmp(key, "max_busy")) {
sql = switch_mprintf("UPDATE agents SET max_busy = '%d', instance_id = 'single_box' WHERE name = '%q'", atoi(value), agent);
cc_execute_sql(NULL, sql, NULL);
switch_safe_free(sql);
result = CC_STATUS_SUCCESS;
} else if (!strcasecmp(key, "max_not_registered")) {
sql = switch_mprintf("UPDATE agents SET max_not_registered = '%d', instance_id = 'single_box' WHERE name = '%q'", atoi(value), agent);
cc_execute_sql(NULL, sql, NULL);
switch_safe_free(sql);
result = CC_STATUS_SUCCESS;
} else {
result = CC_STATUS_INVALID_KEY;
goto done;
@ -1339,11 +1437,15 @@ static switch_status_t load_agent(const char *agent_name, switch_event_t *params
const char *type = switch_xml_attr(x_agent, "type");
const char *contact = switch_xml_attr(x_agent, "contact");
const char *status = switch_xml_attr(x_agent, "status");
const char *max_no_answer = switch_xml_attr(x_agent, "max-no-answer");
const char *wrap_up_time = switch_xml_attr(x_agent, "wrap-up-time");
const char *reject_delay_time = switch_xml_attr(x_agent, "reject-delay-time");
const char *busy_delay_time = switch_xml_attr(x_agent, "busy-delay-time");
const char *max_no_answer = switch_xml_attr(x_agent, "max-no-answer");
const char *no_answer_delay_time = switch_xml_attr(x_agent, "no-answer-delay-time");
const char *max_rejected = switch_xml_attr(x_agent, "max-rejected");
const char *reject_delay_time = switch_xml_attr(x_agent, "reject-delay-time");
const char *max_busy = switch_xml_attr(x_agent, "max-busy");
const char *busy_delay_time = switch_xml_attr(x_agent, "busy-delay-time");
const char *max_not_registered = switch_xml_attr(x_agent, "max-not-registered");
const char *not_registered_delay_time = switch_xml_attr(x_agent, "not-registered-delay-time");
if (type) {
cc_status_t res = cc_agent_add(agent_name, type);
@ -1360,14 +1462,26 @@ static switch_status_t load_agent(const char *agent_name, switch_event_t *params
if (max_no_answer) {
cc_agent_update("max_no_answer", max_no_answer, agent_name);
}
if (no_answer_delay_time) {
cc_agent_update("no_answer_delay_time", no_answer_delay_time, agent_name);
}
if (max_rejected) {
cc_agent_update("max_rejected", max_rejected, agent_name);
}
if (reject_delay_time) {
cc_agent_update("reject_delay_time", reject_delay_time, agent_name);
}
if (max_busy) {
cc_agent_update("max_busy", max_busy, agent_name);
}
if (busy_delay_time) {
cc_agent_update("busy_delay_time", busy_delay_time, agent_name);
}
if (no_answer_delay_time) {
cc_agent_update("no_answer_delay_time", no_answer_delay_time, agent_name);
if (max_not_registered) {
cc_agent_update("max_not_registered", max_not_registered, agent_name);
}
if (not_registered_delay_time) {
cc_agent_update("not_registered_delay_time", not_registered_delay_time, agent_name);
}
if (type && res == CC_STATUS_AGENT_ALREADY_EXIST) {
@ -1574,6 +1688,14 @@ static switch_status_t load_config(switch_memory_pool_t *pool)
switch_cache_db_test_reactive(dbh, "select count(ready_time) from agents", "drop table agents", agents_sql);
switch_cache_db_test_reactive(dbh, "select count(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_test_reactive(dbh, "select count(max_rejected) from agents", NULL,
"alter table agents add max_rejected integer not null default 0;"
"alter table agents add max_busy integer not null default 0;"
"alter table agents add max_not_registered integer not null default 0;"
"alter table agents add not_registered_delay_time integer not null default 5;"
"alter table agents add rejected_count integer not null default 0;"
"alter table agents add busy_count integer not null default 0;"
"alter table agents add not_registered_count integer not null default 0;");
/* This will rename column system for SQLite */
if (switch_cache_db_get_type(dbh) == SCDB_TYPE_CORE_DB) {
char *errmsg = NULL;
@ -1679,6 +1801,109 @@ static switch_status_t playback_array(switch_core_session_t *session, const char
return status;
}
static void cc_agent_failed_count_status_update(struct call_helper *h, switch_call_cause_t hangupcause)
{
cc_queue_t *queue = NULL;
char *column_count = NULL;
char *event_cc_action = NULL;
char *event_cc_header_count = NULL;
char *event_cc_header_status = NULL;
switch_event_t *event = NULL;
char *sql = NULL;
int max_attempt = 0;
int current_attempt_count = 0;
cc_agent_status_t max_reached_status = CC_AGENT_STATUS_ON_BREAK;
agent_info_callback_t cbt;
cbt.found = SWITCH_TRUE;
sql = switch_mprintf("SELECT no_answer_count, max_no_answer, no_answer_delay_time, rejected_count, max_rejected, reject_delay_time, busy_count, max_busy, busy_delay_time, not_registered_count, max_not_registered, not_registered_delay_time FROM agents WHERE name = '%q'", h->agent_name);
cc_execute_sql_callback(NULL /* queue */, NULL /* mutex */, sql, agent_info_callback, &cbt /* Call back variables */);
switch_safe_free(sql);
if (cbt.found != SWITCH_TRUE) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Queue %s not found locally, skip\n", h->queue_name);
return;
}
if (!h->queue_name || !(queue = get_queue(h->queue_name))) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Queue %s not found locally, skip\n", h->queue_name);
return;
}
/* set db column to use and header events */
switch (hangupcause) {
/* Busy: Do Not Disturb, Circuit congestion */
case SWITCH_CAUSE_NORMAL_CIRCUIT_CONGESTION:
case SWITCH_CAUSE_USER_BUSY:
h->delay_next_agent_call = (cbt.busy_delay_time > h->delay_next_agent_call ? cbt.busy_delay_time : h->delay_next_agent_call);
column_count = "busy_count";
event_cc_action = "agent-max-busy";
event_cc_header_count = "CC-Agent-Busy-Count";
event_cc_header_status = "CC-Agent-Busy-Status";
max_reached_status = queue->agent_busy_status;
max_attempt = cbt.max_busy;
current_attempt_count = cbt.busy_count;
break;
/* Reject: User rejected the call */
case SWITCH_CAUSE_CALL_REJECTED:
h->delay_next_agent_call = (cbt.reject_delay_time > h->delay_next_agent_call ? cbt.reject_delay_time : h->delay_next_agent_call);
column_count = "rejected_count";
event_cc_action = "agent-max-rejected";
event_cc_header_count = "CC-Agent-Rejected-Count";
event_cc_header_status = "CC-Agent-Rejected-Status";
max_reached_status = queue->agent_rejected_status;
max_attempt = cbt.max_rejected;
current_attempt_count = cbt.rejected_count;
break;
/* Protection againts super fast loop due to unregistrer */
case SWITCH_CAUSE_USER_NOT_REGISTERED:
h->delay_next_agent_call = (cbt.not_registered_delay_time > h->delay_next_agent_call ? cbt.not_registered_delay_time : h->delay_next_agent_call);
column_count = "not_registered_count";
event_cc_action = "agent-max-not-registered";
event_cc_header_count = "CC-Agent-Not-Registered-Count";
event_cc_header_status = "CC-Agent-Not-Registered-Status";
max_reached_status = queue->agent_not_registered_status;
max_attempt = cbt.max_not_registered;
current_attempt_count = cbt.not_registered_count;
break;
/* No answer: Destination does not answer for some other reason */
default:
h->delay_next_agent_call = (cbt.no_answer_delay_time > h->delay_next_agent_call ? cbt.no_answer_delay_time : h->delay_next_agent_call);
column_count = "no_answer_count";
event_cc_action = "agent-max-no-answer";
event_cc_header_count = "CC-Agent-No-Answer-Count";
event_cc_header_status = "CC-Agent-No-Answer-Status";
max_reached_status = queue->agent_no_answer_status;
max_attempt = cbt.max_no_answer;
current_attempt_count = cbt.no_answer_count;
break;
}
/* Update Agent column count */
sql = switch_mprintf("UPDATE agents SET %s = %s + 1 WHERE name = '%q';", column_count, column_count, h->agent_name);
cc_execute_sql(NULL, sql, NULL);
switch_safe_free(sql);
/* Change Agent Status because he didn't answer often */
if (max_attempt > 0 && (current_attempt_count + 1) >= max_attempt) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Agent %s reach %s of %d, setting agent status to %s\n", h->agent_name, column_count, max_attempt, cc_agent_status2str(max_reached_status));
if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CALLCENTER_EVENT) == SWITCH_STATUS_SUCCESS) {
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Agent", h->agent_name);
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Action", event_cc_action);
switch_event_add_header(event, SWITCH_STACK_BOTTOM, event_cc_header_count, "%d", max_attempt);
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, event_cc_header_status, cc_agent_status2str(max_reached_status));
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Member-UUID", h->member_uuid);
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Member-Session-UUID", h->member_session_uuid);
switch_event_fire(&event);
}
cc_agent_update("status", cc_agent_status2str(max_reached_status), h->agent_name);
}
queue_rwunlock(queue);
}
static void *SWITCH_THREAD_FUNC outbound_agent_thread_run(switch_thread_t *thread, void *obj)
{
struct call_helper *h = (struct call_helper *) obj;
@ -2032,6 +2257,7 @@ static void *SWITCH_THREAD_FUNC outbound_agent_thread_run(switch_thread_t *threa
switch_channel_set_variable_printf(member_channel, "cc_queue_answered_epoch", "%" SWITCH_TIME_T_FMT, local_epoch_time_now(NULL));
/* Set UUID of the Agent channel */
sql = switch_mprintf("UPDATE agents SET uuid = '%q', last_bridge_start = '%" SWITCH_TIME_T_FMT "', calls_answered = calls_answered + 1, no_answer_count = 0"
", rejected_count = 0, busy_count = 0, not_registered_count = 0"
" WHERE name = '%q' AND instance_id = '%q'",
agent_uuid, local_epoch_time_now(NULL),
h->agent_name, h->agent_system);
@ -2125,7 +2351,6 @@ static void *SWITCH_THREAD_FUNC outbound_agent_thread_run(switch_thread_t *threa
} else {
/* Agent didn't answer or originate/bridge failed */
int delay_next_agent_call = 0;
switch_channel_t *member_channel = switch_core_session_get_channel(member_session);
switch_channel_clear_app_flag_key(CC_APP_KEY, member_channel, CC_APP_AGENT_CONNECTING);
sql = switch_mprintf("UPDATE members SET state = case state when '%q' then '%q' else state end, serving_agent = '', serving_system = ''"
@ -2138,6 +2363,9 @@ static void *SWITCH_THREAD_FUNC outbound_agent_thread_run(switch_thread_t *threa
bridged = 0;
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(member_session), SWITCH_LOG_DEBUG, "Agent %s Origination Canceled : %s\n", h->agent_name, switch_channel_cause2str(cause));
h->delay_next_agent_call = 0;
cc_agent_failed_count_status_update(h, cause);
switch (cause) {
/* When we hang-up agents that did not answer in ring-all strategy */
case SWITCH_CAUSE_LOSE_RACE:
@ -2146,54 +2374,25 @@ static void *SWITCH_THREAD_FUNC outbound_agent_thread_run(switch_thread_t *threa
/* Busy: Do Not Disturb, Circuit congestion */
case SWITCH_CAUSE_NORMAL_CIRCUIT_CONGESTION:
case SWITCH_CAUSE_USER_BUSY:
delay_next_agent_call = (h->busy_delay_time > delay_next_agent_call? h->busy_delay_time : delay_next_agent_call);
break;
/* Reject: User rejected the call */
case SWITCH_CAUSE_CALL_REJECTED:
delay_next_agent_call = (h->reject_delay_time > delay_next_agent_call? h->reject_delay_time : delay_next_agent_call);
break;
/* Protection againts super fast loop due to unregistrer */
case SWITCH_CAUSE_USER_NOT_REGISTERED:
delay_next_agent_call = 5;
break;
/* No answer: Destination does not answer for some other reason */
default:
delay_next_agent_call = (h->no_answer_delay_time > delay_next_agent_call? h->no_answer_delay_time : delay_next_agent_call);
tiers_state = CC_TIER_STATE_NO_ANSWER;
/* Update Agent NO Answer count */
sql = switch_mprintf("UPDATE agents SET no_answer_count = no_answer_count + 1 WHERE name = '%q' AND instance_id = '%q';",
h->agent_name, h->agent_system);
cc_execute_sql(NULL, sql, NULL);
switch_safe_free(sql);
/* 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, setting agent status to %s\n",
h->agent_name, h->max_no_answer, cc_agent_status2str(h->agent_no_answer_status));
if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CALLCENTER_EVENT) == SWITCH_STATUS_SUCCESS) {
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Agent", h->agent_name);
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Action", "agent-max-no-answer");
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "CC-Agent-No-Answer-Count", "%d", h->max_no_answer);
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Agent-No-Answer-Status", cc_agent_status2str(h->agent_no_answer_status));
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Member-UUID", h->member_uuid);
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Member-Session-UUID", h->member_session_uuid);
switch_event_fire(&event);
}
cc_agent_update("status", cc_agent_status2str(h->agent_no_answer_status), h->agent_name);
}
break;
}
/* Put agent to sleep for some time if necessary */
if (delay_next_agent_call > 0) {
if (h->delay_next_agent_call > 0) {
char ready_epoch[64];
switch_snprintf(ready_epoch, sizeof(ready_epoch), "%" SWITCH_TIME_T_FMT, local_epoch_time_now(NULL) + delay_next_agent_call);
switch_snprintf(ready_epoch, sizeof(ready_epoch), "%" SWITCH_TIME_T_FMT, local_epoch_time_now(NULL) + h->delay_next_agent_call);
cc_agent_update("ready_time", ready_epoch , h->agent_name);
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(member_session), SWITCH_LOG_DEBUG, "Agent %s sleeping for %d seconds\n", h->agent_name, delay_next_agent_call);
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(member_session), SWITCH_LOG_DEBUG, "Agent %s sleeping for %d seconds\n", h->agent_name, h->delay_next_agent_call);
}
/* Fire up event when contact agent fails */
@ -2265,7 +2464,6 @@ struct agent_callback {
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;
@ -2281,11 +2479,6 @@ static int agents_callback(void *pArg, int argc, char **argv, char **columnNames
const char *agent_name = argv[1];
const char *agent_status = argv[2];
const char *agent_originate_string = argv[3];
const char *agent_no_answer_count = argv[4];
const char *agent_max_no_answer = argv[5];
const char *agent_reject_delay_time = argv[6];
const char *agent_busy_delay_time = argv[7];
const char *agent_no_answer_delay_time = argv[8];
const char *agent_tier_state = argv[9];
const char *agent_last_bridge_end = argv[10];
const char *agent_wrap_up_time = argv[11];
@ -2417,12 +2610,6 @@ static int agents_callback(void *pArg, int argc, char **argv, char **columnNames
h->member_cid_number = switch_core_strdup(h->pool, cbt->member_cid_number);
h->queue_name = switch_core_strdup(h->pool, cbt->queue_name);
h->record_template = switch_core_strdup(h->pool, cbt->record_template);
h->no_answer_count = atoi(agent_no_answer_count);
h->max_no_answer = atoi(agent_max_no_answer);
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);
@ -2524,7 +2711,6 @@ static int members_callback(void *pArg, int argc, char **argv, char **columnName
}
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);
}
@ -2629,13 +2815,14 @@ static int members_callback(void *pArg, int argc, char **argv, char **columnName
switch_core_session_rwunlock(member_session);
}
sql = switch_mprintf("SELECT instance_id, 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, external_calls_count, 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 instance_id, 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, external_calls_count, rejected_count, busy_count, not_registered_count,max_rejected, max_busy, max_not_registered, not_registered_delay_time, agents.last_offered_call as agents_last_offered_call, 1 as dyn_order"
" 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 instance_id, 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, external_calls_count, agents.last_offered_call as agents_last_offered_call, 2 as dyn_order FROM agents LEFT JOIN tiers ON (agents.name = tiers.agent)"
"SELECT instance_id, 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, external_calls_count, rejected_count, busy_count, not_registered_count,max_rejected, max_busy, max_not_registered, not_registered_delay_time, agents.last_offered_call as agents_last_offered_call, 2 as dyn_order 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.level > %d"
@ -2649,13 +2836,15 @@ static int members_callback(void *pArg, int argc, char **argv, char **columnName
level
);
} else if (!strcasecmp(queue->strategy, "round-robin")) {
sql = switch_mprintf("SELECT instance_id, 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, external_calls_count, 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 instance_id, 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, external_calls_count, rejected_count, busy_count, not_registered_count,max_rejected, max_busy, max_not_registered, not_registered_delay_time, agents.last_offered_call as agents_last_offered_call, 1 as dyn_order"
" 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 instance_id, 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, external_calls_count, agents.last_offered_call as agents_last_offered_call, 2 as dyn_order FROM agents LEFT JOIN tiers ON (agents.name = tiers.agent)"
"SELECT instance_id, 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, external_calls_count, rejected_count, busy_count, not_registered_count,max_rejected, max_busy, max_not_registered, not_registered_delay_time, agents.last_offered_call as agents_last_offered_call, 2 as dyn_order"
" 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",
@ -2690,7 +2879,8 @@ 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 instance_id, 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)"
sql = switch_mprintf("SELECT instance_id, 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, rejected_count, busy_count, not_registered_count,max_rejected, max_busy, max_not_registered, not_registered_delay_time"
" 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",
@ -3514,6 +3704,7 @@ static int list_result_json_callback(void *pArg, int argc, char **argv, char **c
"\tcallcenter_config agent set reject_delay_time [agent_name] [wait second] | \n"\
"\tcallcenter_config agent set busy_delay_time [agent_name] [wait second] | \n"\
"\tcallcenter_config agent set no_answer_delay_time [agent_name] [wait second] | \n"\
"\tcallcenter_config agent set not_registered_delay_time [agent_name] [wait second] | \n"\
"\tcallcenter_config agent get status [agent_name] | \n" \
"\tcallcenter_config agent get state [agent_name] | \n" \
"\tcallcenter_config agent get uuid [agent_name] | \n" \
@ -3876,7 +4067,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|skip_agents_with_external_calls|agent_no_answer_status\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|agent_rejected_status|agent_busy_status|agent_not_registered_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;
@ -3885,7 +4076,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|%s|%s\n",
stream->write_function(stream, "%s|%s|%s|%s|%s|%d|%s|%s|%d|%s|%d|%d|%d|%s|%d|%d|%d|%s|%s|%s|%s|%s\n",
queue->name,
queue->strategy,
queue->moh,
@ -3904,7 +4095,10 @@ SWITCH_STANDARD_API(cc_config_api_function)
queue->calls_abandoned,
queue->ring_progressively_delay,
(queue->skip_agents_with_external_calls?"true":"false"),
queue->agent_no_answer_status);
cc_agent_status2str(queue->agent_no_answer_status),
cc_agent_status2str(queue->agent_rejected_status),
cc_agent_status2str(queue->agent_busy_status),
cc_agent_status2str(queue->agent_not_registered_status));
queue = NULL;
}
switch_mutex_unlock(globals.mutex);
@ -4108,7 +4302,10 @@ SWITCH_STANDARD_JSON_API(json_callcenter_config_function)
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_AddItemToObject(o, "agent_no_answer_status", cJSON_CreateString(cc_agent_status2str(queue->agent_no_answer_status)));
cJSON_AddItemToObject(o, "agent_rejected_status", cJSON_CreateString(cc_agent_status2str(queue->agent_rejected_status)));
cJSON_AddItemToObject(o, "agent_busy_status", cJSON_CreateString(cc_agent_status2str(queue->agent_busy_status)));
cJSON_AddItemToObject(o, "agent_not_registered_status", cJSON_CreateString(cc_agent_status2str(queue->agent_not_registered_status)));
cJSON_AddItemToArray(reply, o);
queue = NULL;
}
@ -4277,6 +4474,7 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_callcenter_load)
switch_console_set_complete("add callcenter_config agent set reject_delay_time");
switch_console_set_complete("add callcenter_config agent set busy_delay_time");
switch_console_set_complete("add callcenter_config agent set no_answer_delay_time");
switch_console_set_complete("add callcenter_config agent set not_registered_delay_time");
switch_console_set_complete("add callcenter_config agent get status");
switch_console_set_complete("add callcenter_config agent list");