SWITCH_STANDARD_APP(callcenter_function)
{
char *argv[6] = { 0 };
char *mydata = NULL;
cc_queue_t *queue = NULL;
const char *queue_name = NULL;
switch_core_session_t *member_session = session;
switch_channel_t *member_channel = switch_core_session_get_channel(member_session);
char *sql = NULL;
char *member_session_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;
switch_channel_timetable_t *times = NULL;
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 *moh_expanded = NULL;
char start_epoch[64];
switch_event_t *event;
switch_time_t t_member_called = local_epoch_time_now(NULL);
long abandoned_epoch = 0;
switch_uuid_t smember_uuid;
char member_uuid[SWITCH_UUID_FORMATTED_LENGTH + 1] = "";
switch_bool_t agent_found = SWITCH_FALSE;
switch_bool_t moh_valid = SWITCH_TRUE;
const char *p;
if (!zstr(data)) {
mydata = switch_core_session_strdup(member_session, data);
switch_separate_string(mydata, ' ', argv, (sizeof(argv) / sizeof(argv[0])));
} else {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(member_session), SWITCH_LOG_WARNING, "No Queue name provided\n");
goto end;
}
if (argv[0]) {
queue_name = argv[0];
}
if (!queue_name || !(queue = get_queue(queue_name))) {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(member_session), SWITCH_LOG_WARNING, "Queue %s not found\n", queue_name);
goto end;
}
/* Make sure we answer the channel before getting the switch_channel_time_table_t answer time */
switch_channel_answer(member_channel);
/* Grab the start epoch of a channel */
times = switch_channel_get_timetable(member_channel);
switch_snprintf(start_epoch, sizeof(start_epoch), "%" SWITCH_TIME_T_FMT, times->answered / 1000000);
/* Check if we support and have a queued abandoned member we can resume from */
if (queue->abandoned_resume_allowed == SWITCH_TRUE) {
char res[256];
/* Check to see if agent already exist */
sql = switch_mprintf("SELECT uuid FROM members WHERE queue = '%q' AND cid_number = '%q' AND state = '%q' ORDER BY abandoned_epoch DESC",
queue_name, switch_str_nil(switch_channel_get_variable(member_channel, "caller_id_number")), cc_member_state2str(CC_MEMBER_STATE_ABANDONED));
cc_execute_sql2str(NULL, NULL, sql, res, sizeof(res));
switch_safe_free(sql);
strncpy(member_uuid, res, sizeof(member_uuid));
if (!zstr(member_uuid)) {
sql = switch_mprintf("SELECT abandoned_epoch FROM members WHERE uuid = '%q'", member_uuid);
cc_execute_sql2str(NULL, NULL, sql, res, sizeof(res));
switch_safe_free(sql);
abandoned_epoch = atol(res);
}
}
/* If no existing uuid is restored, let create a new one */
if (abandoned_epoch == 0) {
switch_uuid_get(&smember_uuid);
switch_uuid_format(member_uuid, &smember_uuid);
}
switch_channel_set_variable(member_channel, "cc_side", "member");
switch_channel_set_variable(member_channel, "cc_member_uuid", member_uuid);
/* Add manually imported score */
if (cc_base_score) {
cc_base_score_int += atoi(cc_base_score);
}
/* If system, will add the total time the session is up to the base score */
if (!switch_strlen_zero(start_epoch) && !strcasecmp("system", queue->time_base_score)) {
cc_base_score_int += ((long) local_epoch_time_now(NULL) - atol(start_epoch));
}
/* for xml_cdr needs */
switch_channel_set_variable_printf(member_channel, "cc_queue_joined_epoch", "%" SWITCH_TIME_T_FMT, local_epoch_time_now(NULL));
switch_channel_set_variable(member_channel, "cc_queue", queue_name);
/* We have a previous abandoned user, let's try to recover his place */
if (abandoned_epoch > 0) {
char res[256];
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(member_session), SWITCH_LOG_DEBUG, "Member %s <%s> restoring it previous position in queue %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);
/* Update abandoned member */
sql = switch_mprintf("UPDATE members SET session_uuid = '%q', state = '%q', rejoined_epoch = '%" SWITCH_TIME_T_FMT "' WHERE uuid = '%q' AND state = '%q'",
member_session_uuid, cc_member_state2str(CC_MEMBER_STATE_WAITING), local_epoch_time_now(NULL), member_uuid, cc_member_state2str(CC_MEMBER_STATE_ABANDONED));
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 session_uuid = '%q' AND state = '%q' AND queue = '%q'", member_uuid, member_session_uuid, cc_member_state2str(CC_MEMBER_STATE_WAITING), queue_name);
cc_execute_sql2str(NULL, NULL, sql, res, sizeof(res));
switch_safe_free(sql);
abandoned_epoch = atol(res);
if (abandoned_epoch == 0) {
/* Failed to get the member !!! */
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(member_session), SWITCH_LOG_ERROR, "Member %s <%s> restoring action failed in queue %s, joining again\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);
//queue_rwunlock(queue);
} else {
}
}
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-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-Member-UUID", member_uuid);
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Member-Session-UUID", member_session_uuid);
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Member-CID-Name", switch_str_nil(switch_channel_get_variable(member_channel, "caller_id_name")));
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Member-CID-Number", switch_str_nil(switch_channel_get_variable(member_channel, "caller_id_number")));
switch_event_fire(&event);
}
if (abandoned_epoch == 0) {
char *strategy_str = NULL;
/* Add the caller to the member queue */
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(member_session), SWITCH_LOG_DEBUG, "Member %s <%s> joining queue %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);
if (!strcasecmp(queue->strategy,"ring-all")) {
strategy_str = "ring-all";
} else if (!strcasecmp(queue->strategy,"ring-progressively")) {
strategy_str = "ring-progressively";
} else {
strategy_str = "";
}
sql = switch_mprintf("INSERT INTO members"
" (queue,system,uuid,session_uuid,system_epoch,joined_epoch,base_score,skill_score,cid_number,cid_name,serving_agent,serving_system,state)"
" VALUES('%q','single_box','%q','%q','%q','%" SWITCH_TIME_T_FMT "','%d','%d','%q','%q','%q','','%q')",
queue_name,
member_uuid,
member_session_uuid,
start_epoch,
local_epoch_time_now(NULL),
cc_base_score_int,
0 /*TODO SKILL score*/,
switch_str_nil(switch_channel_get_variable(member_channel, "caller_id_number")),
switch_str_nil(switch_channel_get_variable(member_channel, "caller_id_name")),
strategy_str,
cc_member_state2str(CC_MEMBER_STATE_WAITING));
cc_execute_sql(queue, sql, NULL);
switch_safe_free(sql);
}
/* Send Event with queue count */
cc_queue_count(queue_name);
/* 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->member_uuid = switch_core_strdup(h->pool, member_uuid);
h->member_session_uuid = switch_core_strdup(h->pool, member_session_uuid);
h->member_cid_name = switch_core_strdup(h->pool, switch_str_nil(switch_channel_get_variable(member_channel, "caller_id_name")));
h->member_cid_number = switch_core_strdup(h->pool, switch_str_nil(switch_channel_get_variable(member_channel, "caller_id_number")));
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);
switch_thread_create(&thread, thd_attr, cc_member_thread_run, h, h->pool);
/* Playback MOH */
if (cc_moh_override) {
cur_moh = switch_core_session_strdup(member_session, cc_moh_override);
} else {
cur_moh = switch_core_session_strdup(member_session, queue->moh);
}
queue_rwunlock(queue);
moh_expanded = switch_channel_expand_variables(member_channel, cur_moh);
while (switch_channel_ready(member_channel)) {
switch_input_args_t args = { 0 };
struct moh_dtmf_helper ht;
ht.exit_keys = switch_channel_get_variable(member_channel, "cc_exit_keys");
ht.dtmf = '\0';
args.input_callback = moh_on_dtmf;
args.buf = (void *) &ht;
args.buflen = sizeof(h);
/* An agent was found, time to exit and let the bridge do it job */
if ((p = switch_channel_get_variable(member_channel, "cc_agent_found")) && (agent_found = switch_true(p))) {
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 (moh_valid && moh_expanded) {
switch_status_t status = switch_ivr_play_file(member_session, NULL, moh_expanded, &args);
if (status == SWITCH_STATUS_FALSE /* Invalid Recording */ && SWITCH_READ_ACCEPTABLE(status)) {
/* Sadly, there doesn't seem to be a return to switch_ivr_play_file that tell you the file wasn't found. FALSE also mean that the channel got switch to BRAKE state, so we check for read acceptable */
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(member_session), SWITCH_LOG_WARNING, "Couldn't play file '%s', continuing wait with no audio\n", cur_moh);
moh_valid = SWITCH_FALSE;
} else if (status == SWITCH_STATUS_BREAK) {
char buf[2] = { ht.dtmf, 0 };
switch_channel_set_variable(member_channel, "cc_exit_key", buf);
break;
} else if (!SWITCH_READ_ACCEPTABLE(status)) {
break;
}
} else {
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);
break;
}
}
switch_yield(1000);
}
if (moh_expanded != cur_moh) {
switch_safe_free(moh_expanded);
}
/* Make sure an agent was found, as we might break above without setting it */
if (!agent_found && (p = switch_channel_get_variable(member_channel, "cc_agent_found"))) {
agent_found = switch_true(p);
}
/* Stop member thread */
if (h) {
h->running = 0;
}
/* Check if we were removed because FS Core(BREAK) asked us to */
if (h->member_cancel_reason == CC_MEMBER_CANCEL_REASON_NONE && !agent_found) {
h->member_cancel_reason = CC_MEMBER_CANCEL_REASON_BREAK_OUT;
}
switch_channel_set_variable(member_channel, "cc_agent_found", NULL);
/* Canceled for some reason */
if (!switch_channel_up(member_channel) || h->member_cancel_reason != CC_MEMBER_CANCEL_REASON_NONE) {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(member_session), SWITCH_LOG_DEBUG, "Member %s <%s> abandoned waiting in queue %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);
/* Update member state */
sql = switch_mprintf("UPDATE members SET state = '%q', session_uuid = '', abandoned_epoch = '%" SWITCH_TIME_T_FMT "' WHERE system = 'single_box' AND uuid = '%q'",
cc_member_state2str(CC_MEMBER_STATE_ABANDONED), local_epoch_time_now(NULL), member_uuid);
cc_execute_sql(NULL, sql, NULL);
switch_safe_free(sql);
/* Hangup any callback agents */
switch_core_session_hupall_matching_var("cc_member_pre_answer_uuid", member_uuid, SWITCH_CAUSE_ORIGINATOR_CANCEL);
/* Generate an 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-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-Member-Leaving-Time", "%" SWITCH_TIME_T_FMT, local_epoch_time_now(NULL));
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "CC-Member-Joined-Time", "%" SWITCH_TIME_T_FMT, t_member_called);
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Cause", "Cancel");
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Cancel-Reason", cc_member_cancel_reason2str(h->member_cancel_reason));
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Member-UUID", member_uuid);
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Member-Session-UUID", member_session_uuid);
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Member-CID-Name", switch_str_nil(switch_channel_get_variable(member_channel, "caller_id_name")));
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CC-Member-CID-Number", switch_str_nil(switch_channel_get_variable(member_channel, "caller_id_number")));
switch_event_fire(&event);
}
/* Update some channel variables for xml_cdr needs */
switch_channel_set_variable_printf(member_channel, "cc_queue_canceled_epoch", "%" SWITCH_TIME_T_FMT, local_epoch_time_now(NULL));
switch_channel_set_variable_printf(member_channel, "cc_cause", "%s", "cancel");
switch_channel_set_variable_printf(member_channel, "cc_cancel_reason", "%s", cc_member_cancel_reason2str(h->member_cancel_reason));
/* Print some debug log information */
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(member_session), 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));
if ((queue = get_queue(queue_name))) {
queue->calls_abandoned++;
queue_rwunlock(queue);
}
} else {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(member_session), SWITCH_LOG_DEBUG, "Member %s <%s> is answered by an agent in queue %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);
/* Update member state */
sql = switch_mprintf("UPDATE members SET state = '%q', bridge_epoch = '%" SWITCH_TIME_T_FMT "' WHERE system = 'single_box' AND uuid = '%q'",
cc_member_state2str(CC_MEMBER_STATE_ANSWERED), local_epoch_time_now(NULL), member_uuid);
cc_execute_sql(NULL, sql, NULL);
switch_safe_free(sql);
/* Update some channel variables for xml_cdr needs */
switch_channel_set_variable_printf(member_channel, "cc_cause", "%s", "answered");
if ((queue = get_queue(queue_name))) {
queue->calls_answered++;
queue_rwunlock(queue);
}
}
/* Send Event with queue count */
cc_queue_count(queue_name);
end:
return;
}
members 表
desc:
+-----------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------------+--------------+------+-----+---------+-------+
| queue | varchar(255) | YES | | NULL | |
| system | varchar(255) | YES | | NULL | |
| uuid | varchar(255) | NO | | | |
| session_uuid | varchar(255) | NO | | | |
| cid_number | varchar(255) | YES | | NULL | |
| cid_name | varchar(255) | YES | | NULL | |
| system_epoch | int(11) | NO | | 0 | |
| joined_epoch | int(11) | NO | | 0 | |
| rejoined_epoch | int(11) | NO | | 0 | |
| bridge_epoch | int(11) | NO | | 0 | |
| abandoned_epoch | int(11) | NO | | 0 | |
| base_score | int(11) | NO | | 0 | |
| skill_score | int(11) | NO | | 0 | |
| serving_agent | varchar(255) | YES | | NULL | |
| serving_system | varchar(255) | YES | | NULL | |
| state | varchar(255) | YES | | NULL | |
+-----------------+--------------+------+-----+---------+-------+
16 rows in set (0.00 sec)
> desc agents;
+----------------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------------------+--------------+------+-----+---------+-------+
| name | varchar(255) | YES | | NULL | |
| system | varchar(255) | YES | | NULL | |
| uuid | varchar(255) | YES | | NULL | |
| type | varchar(255) | YES | | NULL | |
| contact | text | YES | | NULL | |
| status | varchar(255) | YES | | NULL | |
| state | varchar(255) | YES | | NULL | |
| max_no_answer | int(11) | NO | | 0 | |
| wrap_up_time | int(11) | NO | | 0 | |
| reject_delay_time | int(11) | NO | | 0 | |
| busy_delay_time | int(11) | NO | | 0 | |
| no_answer_delay_time | int(11) | NO | | 0 | |
| last_bridge_start | int(11) | NO | | 0 | |
| last_bridge_end | int(11) | NO | | 0 | |
| last_offered_call | int(11) | NO | | 0 | |
| last_status_change | int(11) | NO | | 0 | |
| no_answer_count | int(11) | NO | | 0 | |
| calls_answered | int(11) | NO | | 0 | |
| talk_time | int(11) | NO | | 0 | |
| ready_time | int(11) | NO | | 0 | |
| external_calls_count | int(11) | NO | | 0 | |
+----------------------+--------------+------+-----+---------+-------+
21 rows in set (0.00 sec)
mysql> desc tiers;
+----------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+-------+
| queue | varchar(255) | YES | | NULL | |
| agent | varchar(255) | YES | | NULL | |
| state | varchar(255) | YES | | NULL | |
| level | int(11) | NO | | 1 | |
| position | int(11) | NO | | 1 | |
+----------+--------------+------+-----+---------+-------+
5 rows in set (0.00 sec)
mod_callcenter.c 此模块在我们进行 dialplan 进行配置时,及
当电话进入dialplan, answer后会生成一条members记录,
cid_name, cid_number 等。
此时进入SWITCH_STANDARD_APP(callcenter_function)
分配agent进行接通,同时修改members记录,为此记录 增加字段 serving_agent 比如:1004 等
详细实现逻辑请看 SWITCH_STANDARD_APP(callcenter_function) 代码位于 mod_callcenter.c