/* * Copyright (C) 2002-2003, Bill Heckel * * Written by Bill Heckel though many of the * bits used are recycled... * * This program is free software, distributed under the terms of * the GNU General Public License */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define __MORTCO__ "P" #include #define DATE_FORMAT "%Y-%m-%d %T" #define MYSQLUSR "acduser" #define MYSQLPASS "AcD" #define MYSQLACDDB "acd" static char *tdesc = "ACD"; static char *app = "ACD"; static char *synopsis = "ACD"; static char *descrip = "ACD(): evenly distributes calls to peoples or carteret agents \n" "call with ACD,P or ACD,C\n" "returns the results that the embedded Dial call returns\n"; static pthread_mutex_t inqueuelock = AST_MUTEX_INITIALIZER; static int inqueue=0; // how many people are waiting in the queue right now // These are obsolete per Kram STANDARD_LOCAL_USER; LOCAL_USER_DECL; // our local prototypes void QifyCallerID(struct ast_channel *chan,char *state); int MySqlConnectRunQuery(MYSQL *myConn,MYSQL_RES **resaddr,char *sql); int GetTrackingNumber(struct ast_channel *chan,int queueid); int acd_exec(struct ast_channel *chan, void *data); int acd_exec(struct ast_channel *chan, void *data) { struct localuser *u; MYSQL myConn ; // standard MySQL connection variables MYSQL_RES * res ; MYSQL_ROW row ; int rowcount; int areacode=0; // default no area code char callsettings[25]="|15|tm"; // if not defined... char sqlcmd[512]=""; char ss[32]="P"; char *s=ss; char *Mortco=s; char tmp[128]; int MaxSpins=3,MinSpinTime=30; // spin control strncpy(s,data,31); s[31]=0; memset(tmp,0,128); Mortco=strsep(&s,"|"); ast_verbose("ACD %s entered %d in queue\n",Mortco,inqueue); LOCAL_USER_ADD(u); // deprecated // Increase the number of people in queue, when waiting, they are 'In Queue' ast_pthread_mutex_lock(&inqueuelock); inqueue++; ast_pthread_mutex_unlock(&inqueuelock); if (chan->callerid) { char *num; char *name; strncpy(tmp, chan->callerid, 64); ast_callerid_parse(tmp, &name, &num); // set num to point to the number part of tmp if (!num) num=tmp; ast_shrink_phone_number(num); // eliminate any -() etc num[3]=0; // 3 dig area code areacode=atoi(num); ast_verbose(" -ACD: Caller ID number = (%d) [%s] {%s}\n",areacode, num,chan->callerid); } else ast_verbose( " =ACD: No caller ID number found [%s]\n", chan->callerid); mysql_init(&myConn); int queueid=1; // default queue char state[3]="--"; char timezone[8]="-5"; // Eastern char name[64]="no tzname"; int tracknum=GetTrackingNumber(chan,queueid); // get the tracking number ( or assign one ) ast_verbose( " =ACD: Tracking number= %d\n",tracknum); // Let's get the state and timezone for the inbound caller from our DB sprintf(sqlcmd,"select queueid,state,timezone,name from areacode where ac='%03d'",areacode); if (!MySqlConnectRunQuery(&myConn,&res,sqlcmd)) ast_log(LOG_ERROR,"Failed to run MySQL query. %s\n",sqlcmd); else { rowcount = mysql_num_rows( res ); if (rowcount) { row = mysql_fetch_row( res ); char tmp[4]="000"; strncpy(tmp,row[0],3); queueid=atoi(tmp); strncpy(state,row[1],2); // 0 is already there strncpy(timezone,row[2],8); timezone[7]=0; strncpy(name,row[3],64); name[63]=0; if ((Mortco[0]=='P')&&(strncmp(state,"PA",2))) { ast_verbose( " =ACD: Call diverted to carteret, Tracking number= %d\n",tracknum); Mortco[0]='C'; } } ast_verbose("==ACD %s: Got %d rows for (%d)\n Using: %d, %s, %s, %s\n",Mortco, rowcount,areacode,queueid,state,timezone,name); mysql_free_result(res); } sprintf(sqlcmd,"select callcontrol,spins,minspintime from settings where company='%s'",Mortco); if (mysql_query(&myConn,sqlcmd)) return 0; // open 1 res = mysql_store_result( &myConn ); rowcount = mysql_num_rows( res ) ; if (rowcount) { row = mysql_fetch_row( res ); strncpy(callsettings,row[0],24); callsettings[24]=0; strncpy(tmp,row[1],8); tmp[8]=0; MaxSpins=atoi(tmp); if (MaxSpins<1) MaxSpins=1; strncpy(tmp,row[2],8); tmp[8]=0; MinSpinTime=atoi(tmp); if (MinSpinTime<0) MinSpinTime=0; } mysql_free_result(res); // Now that we have a caller and a location, let's look who handles that queue and make a list // ordered by last call ended. We put some randomness in for good measure sprintf(sqlcmd,"select a.agentid,a.name,a.channelid from qagent qa,queue q,agent a " "where qa.company='%s' and qa.queueid=1 and q.queueid=qa.queueid " "and a.agentid=qa.agentid and a.active='Y' order by lastcall + rand()*30",Mortco); #define MAXQUEUE 16 char agentid[MAXQUEUE][8]; char agentname[MAXQUEUE][32]; char channel[MAXQUEUE][48]; int queuesize=0; // how many agents are in the queue if (mysql_query(&myConn,sqlcmd)) ast_log(LOG_ERROR,"Failed to run MySQL query.: %s\n",mysql_error(&myConn)); else { if (Mortco[0]=='C') QifyCallerID(chan,state); // carteret gets qST else QifyCallerID(chan,""); // peoples gets the small q but no state res = mysql_store_result( &myConn ) ; while ((row = mysql_fetch_row( res )) && (queuesize < MAXQUEUE) ) { ast_verbose( " =ACD %s %d: Agents: %s, %s, %s\n",Mortco,tracknum,row[0],row[1],row[2]); strncpy(agentid[queuesize],row[0],7); agentid[queuesize][7]=0; strncpy(agentname[queuesize],row[1],31); agentname[queuesize][31]=0; strncpy(channel[queuesize],row[2],47); channel[queuesize][47]=0; queuesize++; } mysql_free_result(res); } mysql_close(&myConn); // open 1 // Now we have a list of agents and the call to handle. Even without any agents // leave the caller in the queue for the minspin * minspintime then drop out int spins=MaxSpins; int done=0; int result=0; char callstr[32]; struct ast_app *app_dial, *app_wmoh; // ast_verbose( " Trying to find Dial!\n"); app_dial = pbx_findapp("Dial"); if (!app_dial) { ast_verbose( " WARNING! Where the hell is Dial!\n"); } app_wmoh = pbx_findapp("WaitMusicOnHold"); if (!app_wmoh) { ast_verbose( " WARNING! Where the hell is WaitMusicOnHold!\n"); } while (spins>0 && !done) // go through the list 4 times { time_t Tstart = time(0); int curagent; // Decrease the number of people in queue, when ringing or answered, they are not 'In Queue' ast_pthread_mutex_lock(&inqueuelock); inqueue--; ast_pthread_mutex_unlock(&inqueuelock); for (curagent=0;curagentexten here to trap tranfers char oldexten[AST_MAX_EXTENSION]=""; strncpy(oldexten,chan->exten,AST_MAX_EXTENSION); // ast_verbose( " =ACD %s: Entering dial as %s\n",Mortco,oldexten); time_t Tdial = time(0); // time we start ringing the agent if (!(result = pbx_exec(chan,app_dial,callstr, 1))) { // nobody answered if (strncmp(oldexten,chan->exten,AST_MAX_EXTENSION)) { // someone did answer but we want to transfer... ast_verbose( " =ACD %s %d: We got a transfer from %s to %s\n",Mortco,tracknum,oldexten,chan->exten); done=1; // result==0 break; // break so we return 0 to pretend it wasn't answered } ast_verbose( " =ACD %s %d: noanswer %d at %s\n",Mortco,tracknum,result,callstr); } else { // they answered int Toncall = time(0)-Tdial; // how long the person was on the phone ast_verbose( " =ACD %s %d: Answer at %s\n",Mortco,tracknum,callstr); // right here do our logging of answered call and set lastcall on this agent snprintf(sqlcmd,512,"update acd.agent set lastcall=now() where agentid='%s' and lastcallvarshead; int TrackingNumber=0; AST_LIST_TRAVERSE(headp,current,entries) // ( a for loop ) { if (strncasecmp(ast_var_name(current),"ACDTracking",16)==0) // then we found one { if (current->value) return atoi(current->value); } } // AST_LIST_TRAVERSE ( for loop ) // if we get here, the variable isn't in the channel yet MYSQL myConn; MYSQL_RES *resaddr; char sql[256]; mysql_init(&myConn); // don't forget this... snprintf(sql,256,"insert into acd.acdcall (dnis,callid,timein,queueid) values ('%s','%s',now(),%d)" ,(chan->dnid)?chan->dnid:"",(chan->callerid)?chan->callerid:"",queueid); MySqlConnectRunQuery(&myConn,&resaddr,sql); // open 3 TrackingNumber=mysql_insert_id(&myConn); mysql_close(&myConn); // close 3 snprintf(sql,8,"%d",TrackingNumber); current=ast_var_assign("ACDTracking",sql); // make a variable and put it's ptr in current AST_LIST_INSERT_HEAD(headp,current,entries); // stick it in the list at the head return TrackingNumber; } // Connects and runs a query. places result in address pointed to by resaddr // returns 1 for success, 0 for failure int MySqlConnectRunQuery(MYSQL *myConn,MYSQL_RES **resaddr,char *sql) { if (!sql) return 0; if (!mysql_real_connect(myConn,0,MYSQLUSR,MYSQLPASS,MYSQLACDDB, 0,0, 0)) { ast_log(LOG_ERROR," CRITICAL ERROR!!! Failed needed connection to MySQL\n"); return 0; } if (mysql_query(myConn,sql)) { ast_log(LOG_ERROR,"Failed to run MySQL query.: %s \n %s\n",sql,mysql_error(myConn)); return 0; } if ((void **)resaddr!=0) *resaddr = mysql_store_result( myConn ) ; return 1; } // if the caller ID data isn't in qXX name name format, add the Q and state static char DEFSTATE[3]="--"; void QifyCallerID(struct ast_channel *chan,char *state) { char *n=0,*l=0; char newcid[64]; char oldcid[32]; memset(newcid,0,64); memset(oldcid,0,32); if (!state) state=DEFSTATE; if (!chan) { ast_verbose( " Invalid channel passed to Qify in ACD"); return; // we really should have a valid channel but better to err on the side of caution } if (chan->callerid) { strncpy(oldcid, chan->callerid, sizeof(oldcid) - 1); ast_callerid_parse(oldcid, &n, &l); if (n && ( n[0]=='q' || n[1]=='q')) return; // passed in 2nd position if a " is present // ast_verbose( " =ACD : cidparse %s : %s : %s\n",oldcid,n,l); if (n && strlen(n)) // do we have a name { if (l && strlen(l)) // name and number { snprintf(newcid, sizeof(newcid)-1,"\"q%s %s\" <%s>",state,n, l); // ast_verbose( " =ACD We have name and number %s\n",newcid); } else // name but no number { snprintf(newcid,sizeof(newcid)-1,"\"q%s %s\" <>",state,n); // ast_verbose( " =ACD We have number %s\n",newcid); } } else if (l && strlen(l)) // no name, do we have a number { snprintf(newcid,sizeof(newcid)-1,"\"q%s Unavailable Name\" <%s>",state,l); } } else // neither snprintf(newcid,sizeof(newcid)-1,"\"q%s Unavailable Name\" <>",state); ast_set_callerid(chan,newcid, 1); } int unload_module(void) { STANDARD_HANGUP_LOCALUSERS; ast_verbose( " unload_module() called in ACD"); return ast_unregister_application(app); } char *description(void) { return tdesc; } int usecount(void) { int res=0; STANDARD_USECOUNT(res); return res; } char *key() { return ASTERISK_GPL_KEY; } // we connect to the database here just to test the connection and password // each time it's run, we make a new connection ( MYSQL object is not MT safe ) int load_module(void) { int res; MYSQL *mysql; mysql = mysql_init(NULL); mysql = mysql_real_connect(mysql,0,MYSQLUSR,MYSQLPASS,MYSQLACDDB, 0, NULL, 0); // open 4 if (mysql == NULL) { ast_log(LOG_ERROR, "Failed to connect to Mysql ACD database using: %s,%s,%s\n",MYSQLUSR,MYSQLPASS,MYSQLACDDB); return -1; } else { ast_log(LOG_DEBUG,"Successfully connected to MySQL ACD database.\n"); } mysql_close(mysql); // close 4 res = ast_register_application(app, acd_exec, synopsis, descrip); if (res) { ast_log(LOG_ERROR, "Unable to register ACD app\n"); } return res; }