diff --git a/conf/autoload_configs/callcenter.conf.xml b/conf/autoload_configs/callcenter.conf.xml index 156ed374ca..e764d17f15 100644 --- a/conf/autoload_configs/callcenter.conf.xml +++ b/conf/autoload_configs/callcenter.conf.xml @@ -10,6 +10,8 @@ + + diff --git a/src/mod/applications/mod_callcenter/mod_callcenter.c b/src/mod/applications/mod_callcenter/mod_callcenter.c index e56309d005..e08757ba38 100644 --- a/src/mod/applications/mod_callcenter/mod_callcenter.c +++ b/src/mod/applications/mod_callcenter/mod_callcenter.c @@ -37,6 +37,7 @@ #define CC_AGENT_TYPE_UUID_STANDBY "uuid-standby" #define CC_SQLITE_DB_NAME "callcenter" +#define CC_MAX_TIME_DIFF_CHECK 5 /* TODO drop caller if no agent login dont allow new caller @@ -53,6 +54,16 @@ SWITCH_MODULE_DEFINITION(mod_callcenter, mod_callcenter_load, mod_callcenter_shu static const char *global_cf = "callcenter.conf"; +struct cc_status_table { + const char *name; + int status; +}; + +struct cc_state_table { + const char *name; + int state; +}; + typedef enum { CC_STATUS_SUCCESS, CC_STATUS_FALSE, @@ -77,12 +88,7 @@ typedef enum { CC_TIER_STATE_STANDBY = 5 } cc_tier_state_t; -struct cc_tier_state_table { - const char *name; - cc_tier_state_t state; -}; - -static struct cc_tier_state_table STATE_CHART[] = { +static struct cc_state_table STATE_CHART[] = { {"Unknown", CC_TIER_STATE_UNKNOWN}, {"No Answer", CC_TIER_STATE_NO_ANSWER}, {"Ready", CC_TIER_STATE_READY}, @@ -101,12 +107,7 @@ typedef enum { CC_AGENT_STATUS_ON_BREAK = 4 } cc_agent_status_t; -struct cc_agent_status_table { - const char *name; - cc_agent_status_t status; -}; - -static struct cc_agent_status_table AGENT_STATUS_CHART[] = { +static struct cc_status_table AGENT_STATUS_CHART[] = { {"Unknown", CC_AGENT_STATUS_UNKNOWN}, {"Logged Out", CC_AGENT_STATUS_LOGGED_OUT}, {"Available", CC_AGENT_STATUS_AVAILABLE}, @@ -124,12 +125,7 @@ typedef enum { CC_AGENT_STATE_IDLE = 4 } cc_agent_state_t; -struct cc_agent_state_table { - const char *name; - cc_agent_state_t state; -}; - -static struct cc_agent_state_table AGENT_STATE_CHART[] = { +static struct cc_state_table AGENT_STATE_CHART[] = { {"Unknown", CC_AGENT_STATE_UNKNOWN}, {"Waiting", CC_AGENT_STATE_WAITING}, {"Receiving", CC_AGENT_STATE_RECEIVING}, @@ -147,12 +143,7 @@ typedef enum { CC_MEMBER_STATE_ABANDONED = 4 } cc_member_state_t; -struct cc_member_state_table { - const char *name; - cc_member_state_t state; -}; - -static struct cc_member_state_table MEMBER_STATE_CHART[] = { +static struct cc_state_table MEMBER_STATE_CHART[] = { {"Unknown", CC_MEMBER_STATE_UNKNOWN}, {"Waiting", CC_MEMBER_STATE_WAITING}, {"Trying", CC_MEMBER_STATE_TRYING}, @@ -162,10 +153,24 @@ static struct cc_member_state_table MEMBER_STATE_CHART[] = { }; -/*static char queues_sql[] = - "CREATE TABLE queues (\n" - " name VARCHAR(255)\n" ");\n"; - */ +struct cc_member_cancel_reason_table { + const char *name; + int reason; +}; + +typedef enum { + CC_MEMBER_CANCEL_REASON_NONE, + CC_MEMBER_CANCEL_REASON_TIMEOUT, + CC_MEMBER_CANCEL_REASON_NO_AGENT_TIMEOUT +} cc_member_cancel_reason_t; + +static struct cc_member_cancel_reason_table MEMBER_CANCEL_REASON_CHART[] = { + {"NONE", CC_MEMBER_CANCEL_REASON_NONE}, + {"TIMEOUT", CC_MEMBER_CANCEL_REASON_TIMEOUT}, + {"NO_AGENT_TIMEOUT", CC_MEMBER_CANCEL_REASON_NO_AGENT_TIMEOUT}, + {NULL, 0} +}; + static char members_sql[] = "CREATE TABLE members (\n" " queue VARCHAR(255),\n" @@ -222,7 +227,6 @@ static char agents_sql[] = " ready_time INTEGER NOT NULL DEFAULT 0\n" ");\n"; - static char tiers_sql[] = "CREATE TABLE tiers (\n" " queue VARCHAR(255),\n" @@ -247,7 +251,7 @@ const char * cc_tier_state2str(cc_tier_state_t state) uint8_t x; const char *str = "Unknown"; - for (x = 0; x < (sizeof(STATE_CHART) / sizeof(struct cc_tier_state_table)) - 1; x++) { + for (x = 0; x < (sizeof(STATE_CHART) / sizeof(struct cc_state_table)) - 1; x++) { if (STATE_CHART[x].state == state) { str = STATE_CHART[x].name; break; @@ -262,7 +266,7 @@ cc_tier_state_t cc_tier_str2state(const char *str) uint8_t x; cc_tier_state_t state = CC_TIER_STATE_UNKNOWN; - for (x = 0; x < (sizeof(STATE_CHART) / sizeof(struct cc_tier_state_table)) - 1 && STATE_CHART[x].name; x++) { + for (x = 0; x < (sizeof(STATE_CHART) / sizeof(struct cc_state_table)) - 1 && STATE_CHART[x].name; x++) { if (!strcasecmp(STATE_CHART[x].name, str)) { state = STATE_CHART[x].state; break; @@ -271,12 +275,41 @@ cc_tier_state_t cc_tier_str2state(const char *str) return state; } +const char * cc_member_cancel_reason2str(cc_member_cancel_reason_t reason) +{ + uint8_t x; + const char *str = "NONE"; + + for (x = 0; x < (sizeof(MEMBER_CANCEL_REASON_CHART) / sizeof(struct cc_member_cancel_reason_table)) - 1; x++) { + if (MEMBER_CANCEL_REASON_CHART[x].reason == reason) { + str = MEMBER_CANCEL_REASON_CHART[x].name; + break; + } + } + + return str; +} + +cc_member_cancel_reason_t cc_member_cancel_str2reason(const char *str) +{ + uint8_t x; + cc_member_cancel_reason_t reason = CC_MEMBER_CANCEL_REASON_NONE; + + for (x = 0; x < (sizeof(MEMBER_CANCEL_REASON_CHART) / sizeof(struct cc_member_cancel_reason_table)) - 1 && MEMBER_CANCEL_REASON_CHART[x].name; x++) { + if (!strcasecmp(MEMBER_CANCEL_REASON_CHART[x].name, str)) { + reason = MEMBER_CANCEL_REASON_CHART[x].reason; + break; + } + } + return reason; +} + const char * cc_agent_status2str(cc_agent_status_t status) { uint8_t x; const char *str = "Unknown"; - for (x = 0; x < (sizeof(AGENT_STATUS_CHART) / sizeof(struct cc_agent_status_table)) - 1; x++) { + for (x = 0; x < (sizeof(AGENT_STATUS_CHART) / sizeof(struct cc_status_table)) - 1; x++) { if (AGENT_STATUS_CHART[x].status == status) { str = AGENT_STATUS_CHART[x].name; break; @@ -291,7 +324,7 @@ cc_agent_status_t cc_agent_str2status(const char *str) uint8_t x; cc_agent_status_t status = CC_AGENT_STATUS_UNKNOWN; - for (x = 0; x < (sizeof(AGENT_STATUS_CHART) / sizeof(struct cc_agent_status_table)) - 1 && AGENT_STATUS_CHART[x].name; x++) { + for (x = 0; x < (sizeof(AGENT_STATUS_CHART) / sizeof(struct cc_status_table)) - 1 && AGENT_STATUS_CHART[x].name; x++) { if (!strcasecmp(AGENT_STATUS_CHART[x].name, str)) { status = AGENT_STATUS_CHART[x].status; break; @@ -305,7 +338,7 @@ const char * cc_agent_state2str(cc_agent_state_t state) uint8_t x; const char *str = "Unknown"; - for (x = 0; x < (sizeof(AGENT_STATE_CHART) / sizeof(struct cc_agent_state_table)) - 1; x++) { + for (x = 0; x < (sizeof(AGENT_STATE_CHART) / sizeof(struct cc_state_table)) - 1; x++) { if (AGENT_STATE_CHART[x].state == state) { str = AGENT_STATE_CHART[x].name; break; @@ -320,7 +353,7 @@ cc_agent_state_t cc_agent_str2state(const char *str) uint8_t x; cc_agent_state_t state = CC_AGENT_STATE_UNKNOWN; - for (x = 0; x < (sizeof(AGENT_STATE_CHART) / sizeof(struct cc_agent_state_table)) - 1 && AGENT_STATE_CHART[x].name; x++) { + for (x = 0; x < (sizeof(AGENT_STATE_CHART) / sizeof(struct cc_state_table)) - 1 && AGENT_STATE_CHART[x].name; x++) { if (!strcasecmp(AGENT_STATE_CHART[x].name, str)) { state = AGENT_STATE_CHART[x].state; break; @@ -334,7 +367,7 @@ const char * cc_member_state2str(cc_member_state_t state) uint8_t x; const char *str = "Unknown"; - for (x = 0; x < (sizeof(MEMBER_STATE_CHART) / sizeof(struct cc_member_state_table)) - 1; x++) { + for (x = 0; x < (sizeof(MEMBER_STATE_CHART) / sizeof(struct cc_state_table)) - 1; x++) { if (MEMBER_STATE_CHART[x].state == state) { str = MEMBER_STATE_CHART[x].name; break; @@ -349,7 +382,7 @@ cc_member_state_t cc_member_str2state(const char *str) uint8_t x; cc_member_state_t state = CC_MEMBER_STATE_UNKNOWN; - for (x = 0; x < (sizeof(MEMBER_STATE_CHART) / sizeof(struct cc_member_state_table)) - 1 && MEMBER_STATE_CHART[x].name; x++) { + for (x = 0; x < (sizeof(MEMBER_STATE_CHART) / sizeof(struct cc_state_table)) - 1 && MEMBER_STATE_CHART[x].name; x++) { if (!strcasecmp(MEMBER_STATE_CHART[x].name, str)) { state = MEMBER_STATE_CHART[x].state; break; @@ -384,19 +417,27 @@ struct cc_queue { char *moh; char *record_template; char *time_base_score; + switch_bool_t tier_rules_apply; uint32_t tier_rule_wait_second; switch_bool_t tier_rule_wait_multiply_level; switch_bool_t tier_rule_no_agent_no_wait; + uint32_t discard_abandoned_after; switch_bool_t abandoned_resume_allowed; + uint32_t max_wait_time; + uint32_t max_wait_time_with_no_agent; + switch_mutex_t *mutex; switch_thread_rwlock_t *rwlock; switch_memory_pool_t *pool; uint32_t flags; + switch_time_t last_agent_exist; + switch_time_t last_agent_exist_check; + switch_xml_config_item_t config[CC_QUEUE_CONFIGITEM_COUNT]; switch_xml_config_string_options_t config_str_pool; @@ -488,6 +529,7 @@ cc_queue_t *queue_set_config(cc_queue_t *queue) 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++], "record-template", SWITCH_CONFIG_STRING, 0, &queue->record_template, NULL, &queue->config_str_pool, NULL, NULL); SWITCH_CONFIG_SET_ITEM(queue->config[i++], "time-base-score", SWITCH_CONFIG_STRING, 0, &queue->time_base_score, "queue", &queue->config_str_pool, NULL, NULL); + SWITCH_CONFIG_SET_ITEM(queue->config[i++], "tier-rules-apply", SWITCH_CONFIG_BOOL, 0, &queue->tier_rules_apply, SWITCH_FALSE, NULL, NULL, NULL); SWITCH_CONFIG_SET_ITEM(queue->config[i++], "tier-rule-wait-second", SWITCH_CONFIG_INT, 0, &queue->tier_rule_wait_second, 0, &config_int_0_86400, NULL, NULL); SWITCH_CONFIG_SET_ITEM(queue->config[i++], "tier-rule-wait-multiply-level", SWITCH_CONFIG_BOOL, 0, &queue->tier_rule_wait_multiply_level, SWITCH_FALSE, NULL, NULL, NULL); @@ -495,6 +537,9 @@ cc_queue_t *queue_set_config(cc_queue_t *queue) SWITCH_CONFIG_SET_ITEM(queue->config[i++], "discard-abandoned-after", SWITCH_CONFIG_INT, 0, &queue->discard_abandoned_after, 60, &config_int_0_86400, NULL, NULL); SWITCH_CONFIG_SET_ITEM(queue->config[i++], "abandoned-resume-allowed", SWITCH_CONFIG_BOOL, 0, &queue->abandoned_resume_allowed, SWITCH_FALSE, NULL, NULL, NULL); + SWITCH_CONFIG_SET_ITEM(queue->config[i++], "max-wait-time", SWITCH_CONFIG_INT, 0, &queue->max_wait_time, 0, &config_int_0_86400, NULL, NULL); + 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_assert(i < CC_QUEUE_CONFIGITEM_COUNT); return queue; @@ -646,6 +691,9 @@ static cc_queue_t *load_queue(const char *queue_name) switch_thread_rwlock_create(&queue->rwlock, pool); queue->name = switch_core_strdup(pool, queue_name); + queue->last_agent_exist = 0; + queue->last_agent_exist_check = 0; + if (!(dbh = cc_get_db_handle())) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Cannot open DB!\n"); goto end; @@ -702,7 +750,7 @@ static cc_queue_t *get_queue(const char *queue_name) struct call_helper { const char *member_uuid; - const char *queue; + const char *queue_name; const char *queue_strategy; const char *member_joined_epoch; const char *member_caller_name; @@ -744,7 +792,7 @@ int cc_queue_count(const char *queue) count = atoi(res); if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CALLCENTER_EVENT) == SWITCH_STATUS_SUCCESS) { - switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Name", queue); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Queue", queue); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Action", "members-count"); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Count", res); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Selection", event_name); @@ -827,7 +875,7 @@ cc_agent_status_t cc_agent_get(const char *key, const char *agent, char *ret_res result = CC_STATUS_SUCCESS; if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CALLCENTER_EVENT) == SWITCH_STATUS_SUCCESS) { - switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Name", agent); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Agent", agent); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Action", "agent-status-get"); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Agent-Status", res); switch_event_fire(&event); @@ -894,7 +942,7 @@ cc_status_t cc_agent_update(const char *key, const char *value, const char *agen result = CC_STATUS_SUCCESS; if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CALLCENTER_EVENT) == SWITCH_STATUS_SUCCESS) { - switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Name", agent); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Agent", agent); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Action", "agent-status-change"); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Agent-Status", value); switch_event_fire(&event); @@ -918,7 +966,7 @@ cc_status_t cc_agent_update(const char *key, const char *value, const char *agen result = CC_STATUS_SUCCESS; if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CALLCENTER_EVENT) == SWITCH_STATUS_SUCCESS) { - switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Name", agent); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Agent", agent); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Action", "agent-state-change"); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Agent-State", value); switch_event_fire(&event); @@ -1118,13 +1166,13 @@ done: return result; } -cc_status_t cc_tier_del(const char *queue, const char *agent) +cc_status_t cc_tier_del(const char *queue_name, const char *agent) { cc_status_t result = CC_STATUS_SUCCESS; char *sql; - switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Deleted tier Agent %s in Queue %s\n", agent, queue); - sql = switch_mprintf("DELETE FROM tiers WHERE queue = '%q' AND agent = '%q';", queue, agent); + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Deleted tier Agent %s in Queue %s\n", agent, queue_name); + sql = switch_mprintf("DELETE FROM tiers WHERE queue = '%q' AND agent = '%q';", queue_name, agent); cc_execute_sql(NULL, sql, NULL); switch_safe_free(sql); @@ -1307,7 +1355,7 @@ static void *SWITCH_THREAD_FUNC outbound_agent_thread_run(switch_thread_t *threa } if (!strcasecmp(h->agent_type, CC_AGENT_TYPE_CALLBACK)) { switch_event_create(&ovars, SWITCH_EVENT_REQUEST_PARAMS); - switch_event_add_header(ovars, SWITCH_STACK_BOTTOM, "cc_queue", "%s", h->queue); + switch_event_add_header(ovars, SWITCH_STACK_BOTTOM, "cc_queue", "%s", h->queue_name); switch_event_add_header(ovars, SWITCH_STACK_BOTTOM, "cc_member_uuid", "%s", h->member_uuid); switch_event_add_header(ovars, SWITCH_STACK_BOTTOM, "cc_member_pre_answer_uuid", "%s", h->member_uuid); switch_event_add_header(ovars, SWITCH_STACK_BOTTOM, "cc_agent", "%s", h->agent_name); @@ -1327,7 +1375,7 @@ static void *SWITCH_THREAD_FUNC outbound_agent_thread_run(switch_thread_t *threa switch_event_t *event; const char *cc_warning_tone = switch_channel_get_variable(agent_channel, "cc_warning_tone"); - switch_channel_set_variable(agent_channel, "cc_queue", h->queue); + switch_channel_set_variable(agent_channel, "cc_queue", h->queue_name); switch_channel_set_variable(agent_channel, "cc_agent", h->agent_name); switch_channel_set_variable(agent_channel, "cc_agent_type", h->agent_type); switch_channel_set_variable(agent_channel, "cc_member_uuid", h->member_uuid); @@ -1384,7 +1432,7 @@ static void *SWITCH_THREAD_FUNC outbound_agent_thread_run(switch_thread_t *threa if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CALLCENTER_EVENT) == SWITCH_STATUS_SUCCESS) { switch_channel_event_set_data(agent_channel, event); - switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Name", h->queue); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Queue", h->queue_name); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Action", "bridge-agent-start"); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Agent", h->agent_name); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Agent-System", h->agent_system); @@ -1407,11 +1455,7 @@ static void *SWITCH_THREAD_FUNC outbound_agent_thread_run(switch_thread_t *threa switch_safe_free(sql); /* Change the agents Status in the tiers */ - sql = switch_mprintf("UPDATE tiers SET state = '%q' WHERE agent = '%q' AND queue = '%q'", - cc_tier_state2str(CC_TIER_STATE_ACTIVE_INBOUND), h->agent_name, h->queue); - cc_execute_sql(NULL, sql, NULL); - switch_safe_free(sql); - + cc_tier_update("state", cc_tier_state2str(CC_TIER_STATE_ACTIVE_INBOUND), h->queue_name, h->agent_name); cc_agent_update("state", cc_agent_state2str(CC_AGENT_STATE_IN_A_QUEUE_CALL), h->agent_name); /* Record session if record-template is provided */ @@ -1424,10 +1468,13 @@ static void *SWITCH_THREAD_FUNC outbound_agent_thread_run(switch_thread_t *threa } } - switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Agent %s answered \"%s\" (%s) from queue %s%s\n", - h->agent_name, h->member_caller_name, h->member_caller_number, h->queue, (h->record_template?" (Recorded)":"")); + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Agent %s answered \"%s\" <%s> from queue %s%s\n", + h->agent_name, h->member_caller_name, h->member_caller_number, h->queue_name, (h->record_template?" (Recorded)":"")); switch_ivr_uuid_bridge(h->member_uuid, switch_core_session_get_uuid(agent_session)); + /* This is used for the waiting caller to quit waiting for a agent */ + switch_channel_set_variable(member_channel, "cc_agent_uuid", agent_uuid); + /* Wait until the member hangup or the agent hangup. This will quit also if the agent transfer the call */ while(switch_channel_up(member_channel) && switch_channel_up(agent_channel) && globals.running) { switch_yield(100000); @@ -1436,7 +1483,7 @@ static void *SWITCH_THREAD_FUNC outbound_agent_thread_run(switch_thread_t *threa if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CALLCENTER_EVENT) == SWITCH_STATUS_SUCCESS) { switch_channel_event_set_data(agent_channel, event); - switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Name", h->queue); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Queue", h->queue_name); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Action", "bridge-agent-end"); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Agent", h->agent_name); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Agent-System", h->agent_system); @@ -1452,7 +1499,7 @@ static void *SWITCH_THREAD_FUNC outbound_agent_thread_run(switch_thread_t *threa /* Update Agents Items */ /* Do not remove uuid of the agent if we are a standby agent */ sql = switch_mprintf("UPDATE agents SET %q last_bridge_end = %ld, talk_time = talk_time + (%ld-last_bridge_start) WHERE name = '%q' AND system = '%q';" - , (strcasecmp(h->agent_type, "uuid-standby")?"uuid = '',":""), (long) switch_epoch_time_now(NULL), (long) switch_epoch_time_now(NULL), h->agent_name, h->agent_system); + , (strcasecmp(h->agent_type, CC_AGENT_TYPE_UUID_STANDBY)?"uuid = '',":""), (long) switch_epoch_time_now(NULL), (long) switch_epoch_time_now(NULL), h->agent_name, h->agent_system); cc_execute_sql(NULL, sql, NULL); switch_safe_free(sql); @@ -1464,7 +1511,7 @@ static void *SWITCH_THREAD_FUNC outbound_agent_thread_run(switch_thread_t *threa /* Caller off event */ if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CALLCENTER_EVENT) == SWITCH_STATUS_SUCCESS) { switch_channel_event_set_data(member_channel, event); - switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Name", h->queue); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Queue", h->queue_name); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Action", "member-queue-end"); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Cause", "Terminated"); switch_event_add_header(event, SWITCH_STACK_BOTTOM, "CC-Agent-Answer-Time", "%ld", (long) (t_agent_answered - t_agent_called)); @@ -1472,8 +1519,10 @@ static void *SWITCH_THREAD_FUNC outbound_agent_thread_run(switch_thread_t *threa switch_event_add_header(event, SWITCH_STACK_BOTTOM, "CC-Talk-Time", "%ld", (long) (switch_epoch_time_now(NULL) - t_agent_answered)); switch_event_add_header(event, SWITCH_STACK_BOTTOM, "CC-Total-Time", "%ld", (long) (switch_epoch_time_now(NULL) - t_member_called)); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Caller-UUID", h->member_uuid); - switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Caller-CID-Name", switch_str_nil(switch_channel_get_variable(member_channel, "caller_id_name"))); - switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Caller-CID-Number", switch_str_nil(switch_channel_get_variable(member_channel, "caller_id_number"))); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Caller-CID-Name", + switch_str_nil(switch_channel_get_variable(member_channel, "caller_id_name"))); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Caller-CID-Number", + switch_str_nil(switch_channel_get_variable(member_channel, "caller_id_number"))); switch_event_fire(&event); } @@ -1538,8 +1587,8 @@ done: sql = switch_mprintf( "UPDATE tiers SET state = '%q' WHERE agent = '%q' AND queue = '%q' AND (state = '%q' OR state = '%q' OR state = '%q');" "UPDATE tiers SET state = '%q' WHERE agent = '%q' AND NOT queue = '%q' AND state = '%q'" - , cc_tier_state2str(tiers_state), h->agent_name, h->queue, cc_tier_state2str(CC_TIER_STATE_ACTIVE_INBOUND), cc_tier_state2str(CC_TIER_STATE_STANDBY), cc_tier_state2str(CC_TIER_STATE_OFFERING), - cc_tier_state2str(CC_TIER_STATE_READY), h->agent_name, h->queue, cc_tier_state2str(CC_TIER_STATE_STANDBY)); + , cc_tier_state2str(tiers_state), h->agent_name, h->queue_name, cc_tier_state2str(CC_TIER_STATE_ACTIVE_INBOUND), cc_tier_state2str(CC_TIER_STATE_STANDBY), cc_tier_state2str(CC_TIER_STATE_OFFERING), + cc_tier_state2str(CC_TIER_STATE_READY), h->agent_name, h->queue_name, cc_tier_state2str(CC_TIER_STATE_STANDBY)); cc_execute_sql(NULL, sql, NULL); switch_safe_free(sql); @@ -1567,7 +1616,7 @@ done: } struct agent_callback { - const char *queue; + const char *queue_name; const char *system; const char *uuid; const char *caller_number; @@ -1579,6 +1628,7 @@ struct agent_callback { uint32_t tier_rule_wait_second; switch_bool_t tier_rule_wait_multiply_level; switch_bool_t tier_rule_no_agent_no_wait; + switch_bool_t agent_found; int tier; int tier_agent_available; @@ -1602,6 +1652,8 @@ static int agents_callback(void *pArg, int argc, char **argv, char **columnNames switch_bool_t contact_agent = SWITCH_TRUE; + cbt->agent_found = SWITCH_TRUE; + /* Check if we switch to a different tier, if so, check if we should continue further for that member */ if (cbt->tier_rules_apply == SWITCH_TRUE && atoi(agent_tier_level) > cbt->tier) { @@ -1700,7 +1752,7 @@ static int agents_callback(void *pArg, int argc, char **argv, char **columnNames h->member_joined_epoch = switch_core_strdup(h->pool, cbt->joined_epoch); h->member_caller_name = switch_core_strdup(h->pool, cbt->caller_name); h->member_caller_number = switch_core_strdup(h->pool, cbt->caller_number); - h->queue = switch_core_strdup(h->pool, cbt->queue); + 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(argv[4]); h->max_no_answer = atoi(argv[5]); @@ -1713,8 +1765,8 @@ static int agents_callback(void *pArg, int argc, char **argv, char **columnNames sql = switch_mprintf( "UPDATE tiers SET state = '%q' WHERE agent = '%q' AND queue = '%q';" "UPDATE tiers SET state = '%q' WHERE agent = '%q' AND NOT queue = '%q' AND state = '%q';", - cc_tier_state2str(CC_TIER_STATE_OFFERING), h->agent_name, h->queue, - cc_tier_state2str(CC_TIER_STATE_STANDBY), h->agent_name, h->queue, cc_tier_state2str(CC_TIER_STATE_READY)); + cc_tier_state2str(CC_TIER_STATE_OFFERING), h->agent_name, h->queue_name, + cc_tier_state2str(CC_TIER_STATE_STANDBY), h->agent_name, h->queue_name, cc_tier_state2str(CC_TIER_STATE_READY)); cc_execute_sql(NULL, sql, NULL); switch_safe_free(sql); @@ -1796,9 +1848,10 @@ static int members_callback(void *pArg, int argc, char **argv, char **columnName cbt.caller_number = argv[2]; cbt.caller_name = argv[3]; cbt.joined_epoch = argv[4]; - cbt.queue = argv[0]; + cbt.queue_name = argv[0]; cbt.strategy = queue_strategy; cbt.record_template = queue_record_template; + cbt.agent_found = SWITCH_FALSE; if (!strcasecmp(queue->strategy, "longest-idle-agent")) { sql_order_by = switch_mprintf("level, agents.last_offered_call, position"); @@ -1832,6 +1885,18 @@ static int members_callback(void *pArg, int argc, char **argv, char **columnName switch_safe_free(sql); switch_safe_free(sql_order_by); + /* We update a field in the queue struct so we can kick caller out if waiting for too long with no agent */ + if (!argv[0] || !(queue = get_queue(argv[0]))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Queue %s not found locally, skip this member\n", argv[0]); + goto end; + } else { + queue->last_agent_exist_check = switch_epoch_time_now(NULL); + if (cbt.agent_found) { + queue->last_agent_exist = queue->last_agent_exist_check; + } + queue_rwunlock(queue); + } + end: switch_safe_free(queue_name); switch_safe_free(queue_strategy); @@ -1911,30 +1976,53 @@ void cc_agent_dispatch_thread_start(void) switch_thread_create(&thread, thd_attr, cc_agent_dispatch_thread_run, NULL, globals.pool); } -struct member_helper { - const char *uuid; +struct member_thread_helper { + const char *member_uuid; + const char *queue_name; + switch_time_t t_member_called; + cc_member_cancel_reason_t member_cancel_reason; + int running; switch_memory_pool_t *pool; }; void *SWITCH_THREAD_FUNC cc_member_thread_run(switch_thread_t *thread, void *obj) { - struct member_helper *m = (struct member_helper *) obj; - switch_core_session_t *member_session = switch_core_session_locate(m->uuid); - switch_channel_t *channel = NULL; + struct member_thread_helper *m = (struct member_thread_helper *) obj; + switch_core_session_t *member_session = switch_core_session_locate(m->member_uuid); + switch_channel_t *member_channel = NULL; switch_mutex_lock(globals.mutex); globals.threads++; switch_mutex_unlock(globals.mutex); if (member_session) { - channel = switch_core_session_get_channel(member_session); + member_channel = switch_core_session_get_channel(member_session); } else { switch_core_destroy_memory_pool(&m->pool); return NULL; } - while(switch_channel_ready(channel) && m->running && globals.running) { + while(switch_channel_ready(member_channel) && m->running && globals.running) { + cc_queue_t *queue = NULL; + + if (!m->queue_name || !(queue = get_queue(m->queue_name))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Queue %s not found\n", m->queue_name); + break; + } + /* Make the Caller Leave if he went over his max wait time */ + if (queue->max_wait_time > 0 && queue->max_wait_time <= switch_epoch_time_now(NULL) - m->t_member_called) { + m->member_cancel_reason = CC_MEMBER_CANCEL_REASON_TIMEOUT; + switch_channel_set_flag_value(member_channel, CF_BREAK, 2); + } + + /* Will drop the caller if no agent was found for more than X secondes */ + if (queue->max_wait_time_with_no_agent > 0 && m->t_member_called < queue->last_agent_exist_check - CC_MAX_TIME_DIFF_CHECK && + queue->last_agent_exist_check - queue->last_agent_exist >= queue->max_wait_time_with_no_agent) { + m->member_cancel_reason = CC_MEMBER_CANCEL_REASON_NO_AGENT_TIMEOUT; + switch_channel_set_flag_value(member_channel, CF_BREAK, 2); + } + /* TODO Go thought the list of phrases */ /* SAMPLE CODE to playback something over the MOH @@ -1949,8 +2037,12 @@ void *SWITCH_THREAD_FUNC cc_member_thread_run(switch_thread_t *thread, void *obj /* If Agent Logoff, we might need to recalculare score based on skill */ /* Play Announcement in order */ - switch_yield(100000); + + queue_rwunlock(queue); + + switch_yield(500000); } + switch_core_session_rwunlock(member_session); switch_core_destroy_memory_pool(&m->pool); @@ -1971,26 +2063,27 @@ SWITCH_STANDARD_APP(callcenter_function) char *mydata = NULL; cc_queue_t *queue = NULL; const char *queue_name = NULL; - switch_channel_t *member_channel = switch_core_session_get_channel(session); + switch_core_session_t *member_session = session; + switch_channel_t *member_channel = switch_core_session_get_channel(member_session); char *sql = NULL; - char *uuid = switch_core_session_get_uuid(session); - switch_input_args_t args = { 0 }; - struct member_helper *h = NULL; + char *member_uuid = switch_core_session_get_uuid(member_session); + struct member_thread_helper *h = NULL; switch_thread_t *thread; switch_threadattr_t *thd_attr = NULL; switch_memory_pool_t *pool; - int cc_base_score_int = 0; switch_channel_timetable_t *times = NULL; - const char *cc_base_score = switch_channel_get_variable(member_channel, "cc_base_score"); const char *cc_moh_override = switch_channel_get_variable(member_channel, "cc_moh_override"); + const char *cc_base_score = switch_channel_get_variable(member_channel, "cc_base_score"); + int cc_base_score_int = 0; const char *cur_moh = NULL; char start_epoch[64]; switch_event_t *event; switch_time_t t_member_called = switch_epoch_time_now(NULL); long abandoned_epoch = 0; + const char *agent_uuid = NULL; if (!zstr(data)) { - mydata = switch_core_session_strdup(session, data); + mydata = switch_core_session_strdup(member_session, data); argc = switch_separate_string(mydata, ' ', argv, (sizeof(argv) / sizeof(argv[0]))); } else { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "No Queue name provided\n"); @@ -2013,7 +2106,7 @@ SWITCH_STANDARD_APP(callcenter_function) times = switch_channel_get_timetable(member_channel); switch_snprintf(start_epoch, sizeof(start_epoch), "%" SWITCH_TIME_T_FMT, times->answered / 1000000); - /* Check of we have a queued abandoned member we can resume from */ + /* Check if we support and have a queued abandoned member we can resume from */ if (queue->abandoned_resume_allowed == SWITCH_TRUE) { char res[256]; @@ -2037,9 +2130,9 @@ SWITCH_STANDARD_APP(callcenter_function) if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CALLCENTER_EVENT) == SWITCH_STATUS_SUCCESS) { switch_channel_event_set_data(member_channel, event); - switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Name", queue_name); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Queue", queue_name); switch_event_add_header(event, SWITCH_STACK_BOTTOM, "CC-Action", "member-queue-%s", (abandoned_epoch==0?"start":"resume")); - switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Caller-UUID", uuid); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Caller-UUID", member_uuid); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Caller-CID-Name", switch_str_nil(switch_channel_get_variable(member_channel, "caller_id_name"))); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Caller-CID-Number", switch_str_nil(switch_channel_get_variable(member_channel, "caller_id_number"))); switch_event_fire(&event); @@ -2054,7 +2147,7 @@ SWITCH_STANDARD_APP(callcenter_function) " (queue,system,uuid,system_epoch,joined_epoch,base_score,skill_score,caller_number,caller_name,serving_agent,serving_system,state)" " VALUES('%q','single_box','%q','%q','%ld','%d','%d','%q','%q','%q','','%q')", queue_name, - uuid, + member_uuid, start_epoch, (long) switch_epoch_time_now(NULL), cc_base_score_int, @@ -2069,12 +2162,12 @@ SWITCH_STANDARD_APP(callcenter_function) char res[256]; /* Update abandoned member */ sql = switch_mprintf("UPDATE members SET uuid = '%q', state = '%q', rejoined_epoch = '%ld' WHERE caller_number = '%q' AND abandoned_epoch = '%ld' AND state = '%q' AND queue = '%q'", - uuid, cc_member_state2str(CC_MEMBER_STATE_WAITING), (long) switch_epoch_time_now(NULL), switch_str_nil(switch_channel_get_variable(member_channel, "caller_id_number")), abandoned_epoch, cc_member_state2str(CC_MEMBER_STATE_ABANDONED), queue_name); + member_uuid, cc_member_state2str(CC_MEMBER_STATE_WAITING), (long) switch_epoch_time_now(NULL), switch_str_nil(switch_channel_get_variable(member_channel, "caller_id_number")), abandoned_epoch, cc_member_state2str(CC_MEMBER_STATE_ABANDONED), queue_name); cc_execute_sql(queue, sql, NULL); switch_safe_free(sql); /* Confirm we took that member in */ - sql = switch_mprintf("SELECT abandoned_epoch FROM members WHERE uuid = '%q' AND state = '%q' AND queue = '%q'", uuid, cc_member_state2str(CC_MEMBER_STATE_WAITING), queue_name); + sql = switch_mprintf("SELECT abandoned_epoch FROM members WHERE uuid = '%q' AND state = '%q' AND queue = '%q'", member_uuid, cc_member_state2str(CC_MEMBER_STATE_WAITING), queue_name); cc_execute_sql2str(NULL, NULL, sql, res, sizeof(res)); switch_safe_free(sql); @@ -2092,9 +2185,14 @@ SWITCH_STANDARD_APP(callcenter_function) /* Start Thread that will playback different prompt to the channel */ switch_core_new_memory_pool(&pool); h = switch_core_alloc(pool, sizeof(*h)); + h->pool = pool; - h->uuid = switch_core_strdup(h->pool, uuid); + h->member_uuid = switch_core_strdup(h->pool, member_uuid); + h->queue_name = switch_core_strdup(h->pool, queue_name); + h->t_member_called = t_member_called; + h->member_cancel_reason = CC_MEMBER_CANCEL_REASON_NONE; h->running = 1; + switch_threadattr_create(&thd_attr, h->pool); switch_threadattr_detach_set(thd_attr, 1); switch_threadattr_stacksize_set(thd_attr, SWITCH_THREAD_STACKSIZE); @@ -2104,16 +2202,37 @@ SWITCH_STANDARD_APP(callcenter_function) /* TODO Add DTMF callback support */ /* TODO add MOH infitite loop */ if (cc_moh_override) { - cur_moh = switch_core_session_strdup(session, cc_moh_override); + cur_moh = switch_core_session_strdup(member_session, cc_moh_override); } else { - cur_moh = switch_core_session_strdup(session, queue->moh); + cur_moh = switch_core_session_strdup(member_session, queue->moh); } queue_rwunlock(queue); - if (cur_moh) { - switch_ivr_play_file(session, NULL, cur_moh, &args); - } else { - switch_ivr_collect_digits_callback(session, &args, 0, 0); + while (switch_channel_ready(member_channel)) { + switch_input_args_t args = { 0 }; + + /* An agent was found, time to exit and let the bridge do it job */ + if ((agent_uuid = switch_channel_get_variable(member_channel, "cc_agent_uuid"))) { + break; + } + /* If the member thread set a different reason, we monitor it so we can quit the wait */ + if (h->member_cancel_reason != CC_MEMBER_CANCEL_REASON_NONE) { + break; + } + + switch_core_session_flush_private_events(member_session); + + if (cur_moh) { + switch_status_t status = switch_ivr_play_file(member_session, NULL, cur_moh, &args); + + if (!SWITCH_READ_ACCEPTABLE(status)) { + break; + } + + } else { + switch_ivr_collect_digits_callback(session, &args, 0, 0); + } + } /* Stop Member Thread */ @@ -2123,33 +2242,42 @@ SWITCH_STANDARD_APP(callcenter_function) /* Hangup any agents been callback */ if (!switch_channel_up(member_channel)) { /* If channel is still up, it mean that the member didn't hangup, so we should leave the agent alone */ - switch_core_session_hupall_matching_var("cc_member_uuid", uuid, SWITCH_CAUSE_ORIGINATOR_CANCEL); + switch_core_session_hupall_matching_var("cc_member_uuid", member_uuid, SWITCH_CAUSE_ORIGINATOR_CANCEL); sql = switch_mprintf("UPDATE members SET state = '%q', uuid = '', abandoned_epoch = '%ld' WHERE system = 'single_box' AND uuid = '%q'", - cc_member_state2str(CC_MEMBER_STATE_ABANDONED), (long) switch_epoch_time_now(NULL), uuid); + cc_member_state2str(CC_MEMBER_STATE_ABANDONED), (long) switch_epoch_time_now(NULL), member_uuid); cc_execute_sql(NULL, sql, NULL); switch_safe_free(sql); + /* Generate an Event and update some channel variable */ if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CALLCENTER_EVENT) == SWITCH_STATUS_SUCCESS) { switch_channel_event_set_data(member_channel, event); - switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Name", queue_name); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Queue", queue_name); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Action", "member-queue-end"); switch_event_add_header(event, SWITCH_STACK_BOTTOM, "CC-Wait-Time", "%ld", (long) (switch_epoch_time_now(NULL) - t_member_called)); - switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Cause", "Abort"); - switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Caller-UUID", uuid); + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Member \"%s\" <%s> exit queue %s due to %s\n", + switch_str_nil(switch_channel_get_variable(member_channel, "caller_id_name")), + switch_str_nil(switch_channel_get_variable(member_channel, "caller_id_number")), + queue_name, cc_member_cancel_reason2str(h->member_cancel_reason)); + + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Cause", cc_member_cancel_reason2str(h->member_cancel_reason)); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Caller-UUID", member_uuid); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Caller-CID-Name", switch_str_nil(switch_channel_get_variable(member_channel, "caller_id_name"))); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Caller-CID-Number", switch_str_nil(switch_channel_get_variable(member_channel, "caller_id_number"))); switch_event_fire(&event); } + /* for xml_cdr needs */ switch_channel_set_variable_printf(member_channel, "cc_queue_canceled_epoch", "%ld", (long) switch_epoch_time_now(NULL)); + switch_channel_set_variable_printf(member_channel, "cc_cause", "%s", cc_member_cancel_reason2str(h->member_cancel_reason)); /* Send Event with queue count */ cc_queue_count(queue_name); } else { + switch_channel_set_variable_printf(member_channel, "cc_cause", "%s", "answered"); sql = switch_mprintf("UPDATE members SET state = '%q', bridge_epoch = '%ld' WHERE system = 'single_box' AND uuid = '%q'", - cc_member_state2str(CC_MEMBER_STATE_ANSWERED), (long) switch_epoch_time_now(NULL), uuid); + cc_member_state2str(CC_MEMBER_STATE_ANSWERED), (long) switch_epoch_time_now(NULL), member_uuid); cc_execute_sql(NULL, sql, NULL); switch_safe_free(sql); @@ -2525,7 +2653,7 @@ SWITCH_STANDARD_API(cc_config_api_function) stream->write_function(stream, "%d\n", atoi(res)); if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CALLCENTER_EVENT) == SWITCH_STATUS_SUCCESS) { - switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Name", queue_name); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Queue", queue_name); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Action", "members-count"); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Count", res); switch_event_fire(&event);