mirror of
https://github.com/ACSPRI/queXS
synced 2024-04-02 12:12:16 +00:00
Added a "system sort" process which sorts cases and the sample file from enabled questionnaires periodically (based on SYSTEM_SORT_MINUTES config directive). Enabling this may improve performance where there is a large number of interviewers, a large sample or complex quotas as the sort doesn't need to be done for each interviewer at each case.
Allow for multiple background processes to run concurrently
This commit is contained in:
@@ -87,7 +87,8 @@ print "<li><h3>" . T_("System settings") . "</h3>";
|
|||||||
print "<ul><li><a href=\"?page=timezonetemplate.php\">" . T_("Set default timezone list") . "</a></li>";
|
print "<ul><li><a href=\"?page=timezonetemplate.php\">" . T_("Set default timezone list") . "</a></li>";
|
||||||
print "<li><a href=\"?page=shifttemplate.php\">" . T_("Set default shift times") . "</a></li>";
|
print "<li><a href=\"?page=shifttemplate.php\">" . T_("Set default shift times") . "</a></li>";
|
||||||
print "<li><a href=\"?page=callrestrict.php\">" . T_("Set call restriction times") . "</a></li>";
|
print "<li><a href=\"?page=callrestrict.php\">" . T_("Set call restriction times") . "</a></li>";
|
||||||
print "<li><a href=\"?page=centreinfo.php\">" . T_("Set centre information") . "</a></li></ul></li>";
|
print "<li><a href=\"?page=centreinfo.php\">" . T_("Set centre information") . "</a></li>";
|
||||||
|
print "<li><a href=\"?page=systemsort.php\">" . T_("Start and monitor system wide case sorting") . "</a></li></ul></li>";
|
||||||
|
|
||||||
if (VOIP_ENABLED)
|
if (VOIP_ENABLED)
|
||||||
{
|
{
|
||||||
|
|||||||
100
admin/systemsort.php
Normal file
100
admin/systemsort.php
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
<?
|
||||||
|
/**
|
||||||
|
* Run the system wide case sorting process and monitor it's progress
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* This file is part of queXS
|
||||||
|
*
|
||||||
|
* queXS is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* queXS is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with queXS; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author Adam Zammit <adam.zammit@acspri.org.au>
|
||||||
|
* @copyright Australian Consortium for Social and Political Research Incorporated (ACSPRI) 2011
|
||||||
|
* @package queXS
|
||||||
|
* @subpackage admin
|
||||||
|
* @link http://www.acspri.org.au/ queXS was writen for ACSPRI
|
||||||
|
* @license http://opensource.org/licenses/gpl-2.0.php The GNU General Public License (GPL) Version 2
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration file
|
||||||
|
*/
|
||||||
|
include ("../config.inc.php");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Database file
|
||||||
|
*/
|
||||||
|
include ("../db.inc.php");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process
|
||||||
|
*/
|
||||||
|
include ("../functions/functions.process.php");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* XHTML functions
|
||||||
|
*/
|
||||||
|
include("../functions/functions.xhtml.php");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (isset($_GET['watch']))
|
||||||
|
{
|
||||||
|
//start watching process
|
||||||
|
start_process(realpath(dirname(__FILE__) . "/systemsortprocess.php"),2);
|
||||||
|
}
|
||||||
|
|
||||||
|
$p = is_process_running(2);
|
||||||
|
|
||||||
|
|
||||||
|
if ($p)
|
||||||
|
{
|
||||||
|
if (isset($_GET['kill']))
|
||||||
|
{
|
||||||
|
if ($_GET['kill'] == "force")
|
||||||
|
end_process($p);
|
||||||
|
else
|
||||||
|
kill_process($p);
|
||||||
|
}
|
||||||
|
|
||||||
|
xhtml_head(T_("Monitor system wide case sorting"),true,false,false,false,false,true);
|
||||||
|
|
||||||
|
print "<h1>" . T_("Running process:") . " $p</h1>";
|
||||||
|
|
||||||
|
if (is_process_killed($p))
|
||||||
|
{
|
||||||
|
print "<h3>" . T_("Kill signal sent: Please wait...") . "</h3>";
|
||||||
|
print "<p><a href='?kill=force'>" . T_("Process is already closed (eg. server was rebooted) - click here to confirm") . "</a></p>";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
print "<p><a href='?kill=kill'>" . T_("Kill the running process") . "</a></p>";
|
||||||
|
}
|
||||||
|
|
||||||
|
print process_get_data($p);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
xhtml_head(T_("Monitor system wide case sorting"));
|
||||||
|
print "<h2>" . T_("Monitor system wide case sorting") . "</h2>";
|
||||||
|
print "<p><a href='?watch=watch'>" . T_("Click here to enable and begin system wide case sorting") . "</a></p>";
|
||||||
|
print "<p>" . T_("System wide case sorting is periodically (via SYSTEM_SORT_MINUTES configuration directive) sorting cases on a system wide basis instead of finding the most appropriate case each time an operator requests a new case. This may increase performance where there are a large number of cases or complex quotas in place. If you are not experiencing any performance problems, it is not recommended to use this feature.") . "</p>";
|
||||||
|
print "<h2>" . T_("Outcome of last process run (if any)") . "</h2>";
|
||||||
|
print process_get_last_data(2);
|
||||||
|
}
|
||||||
|
xhtml_foot();
|
||||||
|
|
||||||
|
?>
|
||||||
194
admin/systemsortprocess.php
Normal file
194
admin/systemsortprocess.php
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
<?
|
||||||
|
/**
|
||||||
|
* Run the system wide case sorting process
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* This file is part of queXS
|
||||||
|
*
|
||||||
|
* queXS is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* queXS is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with queXS; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author Adam Zammit <adam.zammit@acspri.org.au>
|
||||||
|
* @copyright Australian Consortium for Social and Political Research Incorporated (ACSPRI) 2011
|
||||||
|
* @package queXS
|
||||||
|
* @subpackage admin
|
||||||
|
* @link http://www.acspri.org.au/ queXS was written for ACSPRI
|
||||||
|
* @license http://opensource.org/licenses/gpl-2.0.php The GNU General Public License (GPL) Version 2
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration file
|
||||||
|
*/
|
||||||
|
include (dirname(__FILE__) . "/../config.inc.php");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Database file
|
||||||
|
*/
|
||||||
|
include (dirname(__FILE__) . "/../db.inc.php");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process
|
||||||
|
*/
|
||||||
|
include (dirname(__FILE__) . "/../functions/functions.process.php");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the database with the new data from the running script
|
||||||
|
*
|
||||||
|
* @param string $buffer The data to append to the database
|
||||||
|
* @return string Return a blank string to empty the buffer
|
||||||
|
*/
|
||||||
|
function update_callback($buffer)
|
||||||
|
{
|
||||||
|
global $process_id;
|
||||||
|
|
||||||
|
process_append_data($process_id,"<p>" . $buffer . "</p>");
|
||||||
|
|
||||||
|
return ""; //empty buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disable system sort on shutdown
|
||||||
|
*
|
||||||
|
* @author Adam Zammit <adam.zammit@acspri.org.au>
|
||||||
|
* @since 2011-01-31
|
||||||
|
*/
|
||||||
|
function disable_systemsort()
|
||||||
|
{
|
||||||
|
set_setting('systemsort',false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//get the arguments from the command line (this process_id)
|
||||||
|
if ($argc != 2) exit();
|
||||||
|
|
||||||
|
$process_id = $argv[1];
|
||||||
|
|
||||||
|
//register an exit function which will tell the database we have ended
|
||||||
|
register_shutdown_function('end_process',$process_id);
|
||||||
|
register_shutdown_function('disable_systemsort');
|
||||||
|
|
||||||
|
//all output send to database instead of stdout
|
||||||
|
ob_start('update_callback',2);
|
||||||
|
|
||||||
|
print T_("Sorting cases process starting");
|
||||||
|
|
||||||
|
$sleepinterval = 10; // in seconds so we can monitor if the process has been killed
|
||||||
|
|
||||||
|
while (!is_process_killed($process_id)) //check if process killed every $sleepinterval
|
||||||
|
{
|
||||||
|
//Make sure that the system knows we are system sorting
|
||||||
|
set_setting('systemsort',true);
|
||||||
|
|
||||||
|
print date("Y-m-d H:i") . " : " . T_("Sorting cases");
|
||||||
|
|
||||||
|
$time_start = microtime(true);
|
||||||
|
|
||||||
|
$db->StartTrans();
|
||||||
|
|
||||||
|
//Sort current cases for all enabled questionnaires
|
||||||
|
|
||||||
|
|
||||||
|
$sql = "SELECT c.case_id
|
||||||
|
FROM `case` as c
|
||||||
|
LEFT JOIN `call` as a on (a.call_id = c.last_call_id)
|
||||||
|
JOIN (sample as s, sample_import as si) on (s.sample_id = c.sample_id and si.sample_import_id = s.import_id)
|
||||||
|
JOIN (questionnaire_sample as qs, questionnaire as q, outcome as ou) on (c.questionnaire_id = q.questionnaire_id and qs.sample_import_id = s.import_id and ou.outcome_id = c.current_outcome_id and q.enabled = 1)
|
||||||
|
LEFT JOIN shift as sh on (sh.questionnaire_id = q.questionnaire_id and (CONVERT_TZ(NOW(),'System','UTC') >= sh.start) AND (CONVERT_TZ(NOW(),'System','UTC') <= sh.end))
|
||||||
|
LEFT JOIN appointment as ap on (ap.case_id = c.case_id AND ap.completed_call_id is NULL AND (ap.start > CONVERT_TZ(NOW(),'System','UTC')))
|
||||||
|
LEFT JOIN appointment as apn on (apn.case_id = c.case_id AND apn.completed_call_id is NULL AND (CONVERT_TZ(NOW(),'System','UTC') >= apn.start) AND (CONVERT_TZ(NOW(),'System','UTC') <= apn.end))
|
||||||
|
LEFT JOIN call_restrict as cr on (cr.day_of_week = DAYOFWEEK(CONVERT_TZ(NOW(), 'System' , s.Time_zone_name)) and TIME(CONVERT_TZ(NOW(), 'System' , s.Time_zone_name)) >= cr.start and TIME(CONVERT_TZ(NOW(), 'System' , s.Time_zone_name)) <= cr.end)
|
||||||
|
LEFT JOIN questionnaire_sample_exclude_priority AS qsep ON (qsep.questionnaire_id = c.questionnaire_id AND qsep.sample_id = c.sample_id)
|
||||||
|
WHERE c.current_operator_id IS NULL
|
||||||
|
AND (a.call_id is NULL or (a.end < CONVERT_TZ(DATE_SUB(NOW(), INTERVAL ou.default_delay_minutes MINUTE),'System','UTC')))
|
||||||
|
AND ap.case_id is NULL
|
||||||
|
AND ((qsep.questionnaire_id is NULL) or qsep.exclude = 0)
|
||||||
|
AND !(q.restrict_work_shifts = 1 AND sh.shift_id IS NULL)
|
||||||
|
AND !(si.call_restrict = 1 AND cr.day_of_week IS NULL)
|
||||||
|
AND ((apn.appointment_id IS NOT NULL) or qs.call_attempt_max = 0 or ((SELECT count(*) FROM call_attempt WHERE case_id = c.case_id) < qs.call_attempt_max))
|
||||||
|
AND ((apn.appointment_id IS NOT NULL) or qs.call_max = 0 or ((SELECT count(*) FROM `call` WHERE case_id = c.case_id) < qs.call_max))
|
||||||
|
AND (SELECT count(*) FROM `questionnaire_sample_quota` WHERE questionnaire_id = c.questionnaire_id AND sample_import_id = s.import_id AND quota_reached = 1) = 0
|
||||||
|
GROUP BY c.case_id
|
||||||
|
ORDER BY apn.start DESC, a.start ASC, qsep.priority DESC";
|
||||||
|
|
||||||
|
$rs = $db->GetAll($sql);
|
||||||
|
|
||||||
|
$i = 1;
|
||||||
|
foreach ($rs as $r)
|
||||||
|
{
|
||||||
|
$sql = "UPDATE `case`
|
||||||
|
SET sortorder = '$i'
|
||||||
|
WHERE case_id = '{$r['case_id']}'";
|
||||||
|
|
||||||
|
$db->Execute($sql);
|
||||||
|
$i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Sort sample list where attached to an enabled questionnaire
|
||||||
|
|
||||||
|
$sql = "SELECT s.sample_id as sample_id,qs.questionnaire_id as questionnaire_id
|
||||||
|
FROM sample as s
|
||||||
|
JOIN (questionnaire_sample as qs, questionnaire as q, sample_import as si) on (qs.sample_import_id = s.import_id and si.sample_import_id = s.import_id and q.questionnaire_id = qs.questionnaire_id AND q.enabled = 1)
|
||||||
|
LEFT JOIN `case` as c on (c.sample_id = s.sample_id and c.questionnaire_id = qs.questionnaire_id)
|
||||||
|
LEFT JOIN call_restrict as cr on (cr.day_of_week = DAYOFWEEK(CONVERT_TZ(NOW(), 'System' , s.Time_zone_name)) and TIME(CONVERT_TZ(NOW(), 'System' , s.Time_zone_name)) >= cr.start and TIME(CONVERT_TZ(NOW(), 'System' , s.Time_zone_name)) <= cr.end)
|
||||||
|
LEFT JOIN shift as sh on (sh.questionnaire_id = q.questionnaire_id and (CONVERT_TZ(NOW(),'System','UTC') >= sh.start) AND (CONVERT_TZ(NOW(),'System','UTC') <= sh.end))
|
||||||
|
LEFT JOIN questionnaire_sample_exclude_priority AS qsep ON (qsep.questionnaire_id = qs.questionnaire_id AND qsep.sample_id = s.sample_id)
|
||||||
|
WHERE c.case_id is NULL
|
||||||
|
AND ((qsep.questionnaire_id IS NULL) or qsep.exclude = 0)
|
||||||
|
AND !(q.restrict_work_shifts = 1 AND sh.shift_id IS NULL)
|
||||||
|
AND !(si.call_restrict = 1 AND cr.day_of_week IS NULL)
|
||||||
|
AND (SELECT count(*) FROM `questionnaire_sample_quota` WHERE questionnaire_id = qs.questionnaire_id AND sample_import_id = s.import_id AND quota_reached = 1) = 0
|
||||||
|
GROUP BY s.sample_id,qs.questionnaire_id
|
||||||
|
ORDER BY qsep.priority DESC, rand() * qs.random_select, s.sample_id";
|
||||||
|
|
||||||
|
$rs = $db->GetAll($sql);
|
||||||
|
|
||||||
|
$i = 1;
|
||||||
|
foreach ($rs as $r)
|
||||||
|
{
|
||||||
|
$sql = "INSERT INTO questionnaire_sample_exclude_priority (questionnaire_id,sample_id,exclude,priority,sortorder)
|
||||||
|
VALUES ('{$r['questionnaire_id']}', '{$r['sample_id']}', 0, 50,'$i')
|
||||||
|
ON DUPLICATE KEY UPDATE sortorder = '$i'";
|
||||||
|
|
||||||
|
$db->Execute($sql);
|
||||||
|
$i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$result = $db->CompleteTrans();
|
||||||
|
|
||||||
|
$time_end = microtime(true);
|
||||||
|
$timer = $time_end - $time_start;
|
||||||
|
|
||||||
|
if ($result)
|
||||||
|
print T_("Completed sort") . ". " . T_("This task took") . ": $timer " . T_("seconds");
|
||||||
|
else
|
||||||
|
print T_("Failed to complete sort") . ". " . T_("This task took") . ": $timer " . T_("seconds");
|
||||||
|
|
||||||
|
for ($i = 0; $i < (SYSTEM_SORT_MINUTES * 60); $i += $sleepinterval)
|
||||||
|
{
|
||||||
|
if (is_process_killed($process_id)){break;}
|
||||||
|
sleep($sleepinterval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
disable_systemsort();
|
||||||
|
|
||||||
|
ob_get_contents();
|
||||||
|
ob_end_clean();
|
||||||
|
|
||||||
|
?>
|
||||||
@@ -220,6 +220,11 @@ if (!defined('TAB_INFO')) define('TAB_INFO', true);
|
|||||||
*/
|
*/
|
||||||
if (!defined('HEADER_EXPANDER')) define('HEADER_EXPANDER', false);
|
if (!defined('HEADER_EXPANDER')) define('HEADER_EXPANDER', false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define how many minutes between each system sort (defaults to 5 as this is a common interval for appointments)
|
||||||
|
*/
|
||||||
|
if (!defined('SYSTEM_SORT_MINUTES')) define ('SYSTEM_SORT_MINUTES',5);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Database configuration for queXS
|
* Database configuration for queXS
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -176,12 +176,14 @@ CREATE TABLE `case` (
|
|||||||
`current_operator_id` bigint(20) default NULL,
|
`current_operator_id` bigint(20) default NULL,
|
||||||
`current_call_id` bigint(20) default NULL,
|
`current_call_id` bigint(20) default NULL,
|
||||||
`current_outcome_id` int(11) NOT NULL default '1',
|
`current_outcome_id` int(11) NOT NULL default '1',
|
||||||
|
`sortorder` int(11) default NULL,
|
||||||
PRIMARY KEY (`case_id`),
|
PRIMARY KEY (`case_id`),
|
||||||
UNIQUE KEY `onecasepersample` (`sample_id`,`questionnaire_id`),
|
UNIQUE KEY `onecasepersample` (`sample_id`,`questionnaire_id`),
|
||||||
UNIQUE KEY `current_operator_id` (`current_operator_id`),
|
UNIQUE KEY `current_operator_id` (`current_operator_id`),
|
||||||
UNIQUE KEY `current_call_id` (`current_call_id`),
|
UNIQUE KEY `current_call_id` (`current_call_id`),
|
||||||
KEY `sample_id` (`sample_id`),
|
KEY `sample_id` (`sample_id`),
|
||||||
KEY `questionnaire_id` (`questionnaire_id`)
|
KEY `questionnaire_id` (`questionnaire_id`),
|
||||||
|
KEY `sortorder` (`sortorder`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci;
|
) ENGINE=InnoDB DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci;
|
||||||
|
|
||||||
|
|
||||||
@@ -441,6 +443,7 @@ INSERT INTO `outcome_type` VALUES(5, 'Appointments');
|
|||||||
|
|
||||||
CREATE TABLE `process` (
|
CREATE TABLE `process` (
|
||||||
`process_id` bigint(20) NOT NULL auto_increment,
|
`process_id` bigint(20) NOT NULL auto_increment,
|
||||||
|
`type` int(11) NOT NULL default '1',
|
||||||
`start` datetime NOT NULL,
|
`start` datetime NOT NULL,
|
||||||
`stop` datetime default NULL,
|
`stop` datetime default NULL,
|
||||||
`kill` tinyint(1) NOT NULL default '0',
|
`kill` tinyint(1) NOT NULL default '0',
|
||||||
@@ -522,10 +525,12 @@ CREATE TABLE `questionnaire_sample_exclude_priority` (
|
|||||||
`sample_id` bigint(20) NOT NULL,
|
`sample_id` bigint(20) NOT NULL,
|
||||||
`exclude` tinyint(1) NOT NULL default '0',
|
`exclude` tinyint(1) NOT NULL default '0',
|
||||||
`priority` tinyint(3) NOT NULL default '50',
|
`priority` tinyint(3) NOT NULL default '50',
|
||||||
|
`sortorder` int(11) default NULL,
|
||||||
PRIMARY KEY (`questionnaire_id`,`sample_id`),
|
PRIMARY KEY (`questionnaire_id`,`sample_id`),
|
||||||
KEY `exclude` (`exclude`),
|
KEY `exclude` (`exclude`),
|
||||||
KEY `priority` (`priority`),
|
KEY `priority` (`priority`),
|
||||||
KEY `questionnaire_id` (`questionnaire_id`)
|
KEY `questionnaire_id` (`questionnaire_id`),
|
||||||
|
KEY `sortorder` (`sortorder`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
|
||||||
|
|
||||||
--
|
--
|
||||||
|
|||||||
@@ -295,86 +295,122 @@ function get_case_id($operator_id, $create = false)
|
|||||||
{
|
{
|
||||||
if ($create)
|
if ($create)
|
||||||
{
|
{
|
||||||
/**
|
$systemsort = get_setting('systemsort');
|
||||||
* find a case that:
|
|
||||||
* Has not been called in the last x hours based on last outcome
|
|
||||||
* Is available for this operator
|
|
||||||
* Has no appointments scheduled in the future (can also check if outcome is appointment)
|
|
||||||
* Nobody else is servicing the call at the moment
|
|
||||||
* The case is not referred to the supervisor and the operator is not the supervisor
|
|
||||||
* The case is not on a refusal outcome and the operator is not a refusal converter
|
|
||||||
* Give priority if there is an appointment scheduled now
|
|
||||||
* If restricted to shift times to work, make sure we are in those
|
|
||||||
* If restricted to respondent call times, make sure we are in those
|
|
||||||
* Only assign if outcome type is assigned to the operator
|
|
||||||
* Has not reached the quota
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* THINGS TO ADD:
|
|
||||||
*
|
|
||||||
* @todo also option of "time of day" calls - try once in the morning/etc
|
|
||||||
* @todo also could check the respondent_not_available table to see if now is a "bad time" to call
|
|
||||||
*/
|
|
||||||
|
|
||||||
$sql = "SELECT c.case_id as caseid,s.*,apn.*,a.*,sh.*,op.*,cr.*,si.*,CONVERT_TZ(NOW(), 'System' , s.Time_zone_name) as resptime
|
if ($systemsort)
|
||||||
FROM `case` as c
|
{
|
||||||
LEFT JOIN `call` as a on (a.call_id = c.last_call_id)
|
//Just make sure that this case should go to this operator (assigned to this project and skill)
|
||||||
JOIN (sample as s, sample_import as si) on (s.sample_id = c.sample_id and si.sample_import_id = s.import_id)
|
$sql = "SELECT c.case_id as caseid
|
||||||
JOIN (questionnaire_sample as qs, operator_questionnaire as o, questionnaire as q, operator as op, outcome as ou) on (c.questionnaire_id = q.questionnaire_id and op.operator_id = '$operator_id' and qs.sample_import_id = s.import_id and o.operator_id = op.operator_id and o.questionnaire_id = qs.questionnaire_id and q.questionnaire_id = o.questionnaire_id and ou.outcome_id = c.current_outcome_id)
|
FROM `case` as c
|
||||||
LEFT JOIN shift as sh on (sh.questionnaire_id = q.questionnaire_id and (CONVERT_TZ(NOW(),'System','UTC') >= sh.start) AND (CONVERT_TZ(NOW(),'System','UTC') <= sh.end))
|
JOIN operator_questionnaire AS oq ON (oq.operator_id = '$operator_id' AND oq.questionnaire_id = c.questionnaire_id)
|
||||||
LEFT JOIN appointment as ap on (ap.case_id = c.case_id AND ap.completed_call_id is NULL AND (ap.start > CONVERT_TZ(NOW(),'System','UTC')))
|
JOIN outcome as ou ON (ou.outcome_id = c.current_outcome_id)
|
||||||
LEFT JOIN appointment as apn on (apn.case_id = c.case_id AND apn.completed_call_id is NULL AND (CONVERT_TZ(NOW(),'System','UTC') >= apn.start) AND (CONVERT_TZ(NOW(),'System','UTC') <= apn.end))
|
JOIN operator_skill as os ON (os.operator_id = '$operator_id' AND os.outcome_type_id = ou.outcome_type_id)
|
||||||
LEFT JOIN call_restrict as cr on (cr.day_of_week = DAYOFWEEK(CONVERT_TZ(NOW(), 'System' , s.Time_zone_name)) and TIME(CONVERT_TZ(NOW(), 'System' , s.Time_zone_name)) >= cr.start and TIME(CONVERT_TZ(NOW(), 'System' , s.Time_zone_name)) <= cr.end)
|
WHERE c.sortorder IS NOT NULL
|
||||||
LEFT JOIN questionnaire_sample_exclude_priority AS qsep ON (qsep.questionnaire_id = c.questionnaire_id AND qsep.sample_id = c.sample_id)
|
AND c.current_operator_id IS NULL
|
||||||
JOIN operator_skill as os on (os.operator_id = op.operator_id and os.outcome_type_id = ou.outcome_type_id)
|
ORDER BY c.sortorder ASC
|
||||||
WHERE c.current_operator_id IS NULL
|
LIMIT 1";
|
||||||
AND (a.call_id is NULL or (a.end < CONVERT_TZ(DATE_SUB(NOW(), INTERVAL ou.default_delay_minutes MINUTE),'System','UTC')))
|
|
||||||
AND ap.case_id is NULL
|
|
||||||
AND ((qsep.questionnaire_id is NULL) or qsep.exclude = 0)
|
|
||||||
AND !(q.restrict_work_shifts = 1 AND sh.shift_id IS NULL AND os.outcome_type_id != 2)
|
|
||||||
AND !(si.call_restrict = 1 AND cr.day_of_week IS NULL AND os.outcome_type_id != 2)
|
|
||||||
AND ((apn.appointment_id IS NOT NULL) or qs.call_attempt_max = 0 or ((SELECT count(*) FROM call_attempt WHERE case_id = c.case_id) < qs.call_attempt_max))
|
|
||||||
AND ((apn.appointment_id IS NOT NULL) or qs.call_max = 0 or ((SELECT count(*) FROM `call` WHERE case_id = c.case_id) < qs.call_max))
|
|
||||||
AND (SELECT count(*) FROM `questionnaire_sample_quota` WHERE questionnaire_id = c.questionnaire_id AND sample_import_id = s.import_id AND quota_reached = 1) = 0
|
|
||||||
ORDER BY apn.start DESC, a.start ASC, qsep.priority DESC
|
|
||||||
LIMIT 1";
|
|
||||||
|
|
||||||
//apn.appointment_id contains the id of an appointment if we are calling on an appointment
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* find a case that:
|
||||||
|
* Has not been called in the last x hours based on last outcome
|
||||||
|
* Is available for this operator
|
||||||
|
* Has no appointments scheduled in the future (can also check if outcome is appointment)
|
||||||
|
* Nobody else is servicing the call at the moment
|
||||||
|
* The case is not referred to the supervisor and the operator is not the supervisor
|
||||||
|
* The case is not on a refusal outcome and the operator is not a refusal converter
|
||||||
|
* Give priority if there is an appointment scheduled now
|
||||||
|
* If restricted to shift times to work, make sure we are in those
|
||||||
|
* If restricted to respondent call times, make sure we are in those
|
||||||
|
* Only assign if outcome type is assigned to the operator
|
||||||
|
* Has not reached the quota
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* THINGS TO ADD:
|
||||||
|
*
|
||||||
|
* @todo also option of "time of day" calls - try once in the morning/etc
|
||||||
|
* @todo also could check the respondent_not_available table to see if now is a "bad time" to call
|
||||||
|
*/
|
||||||
|
|
||||||
|
$sql = "SELECT c.case_id as caseid,s.*,apn.*,a.*,sh.*,op.*,cr.*,si.*,CONVERT_TZ(NOW(), 'System' , s.Time_zone_name) as resptime
|
||||||
|
FROM `case` as c
|
||||||
|
LEFT JOIN `call` as a on (a.call_id = c.last_call_id)
|
||||||
|
JOIN (sample as s, sample_import as si) on (s.sample_id = c.sample_id and si.sample_import_id = s.import_id)
|
||||||
|
JOIN (questionnaire_sample as qs, operator_questionnaire as o, questionnaire as q, operator as op, outcome as ou) on (c.questionnaire_id = q.questionnaire_id and op.operator_id = '$operator_id' and qs.sample_import_id = s.import_id and o.operator_id = op.operator_id and o.questionnaire_id = qs.questionnaire_id and q.questionnaire_id = o.questionnaire_id and ou.outcome_id = c.current_outcome_id)
|
||||||
|
LEFT JOIN shift as sh on (sh.questionnaire_id = q.questionnaire_id and (CONVERT_TZ(NOW(),'System','UTC') >= sh.start) AND (CONVERT_TZ(NOW(),'System','UTC') <= sh.end))
|
||||||
|
LEFT JOIN appointment as ap on (ap.case_id = c.case_id AND ap.completed_call_id is NULL AND (ap.start > CONVERT_TZ(NOW(),'System','UTC')))
|
||||||
|
LEFT JOIN appointment as apn on (apn.case_id = c.case_id AND apn.completed_call_id is NULL AND (CONVERT_TZ(NOW(),'System','UTC') >= apn.start) AND (CONVERT_TZ(NOW(),'System','UTC') <= apn.end))
|
||||||
|
LEFT JOIN call_restrict as cr on (cr.day_of_week = DAYOFWEEK(CONVERT_TZ(NOW(), 'System' , s.Time_zone_name)) and TIME(CONVERT_TZ(NOW(), 'System' , s.Time_zone_name)) >= cr.start and TIME(CONVERT_TZ(NOW(), 'System' , s.Time_zone_name)) <= cr.end)
|
||||||
|
LEFT JOIN questionnaire_sample_exclude_priority AS qsep ON (qsep.questionnaire_id = c.questionnaire_id AND qsep.sample_id = c.sample_id)
|
||||||
|
JOIN operator_skill as os on (os.operator_id = op.operator_id and os.outcome_type_id = ou.outcome_type_id)
|
||||||
|
WHERE c.current_operator_id IS NULL
|
||||||
|
AND (a.call_id is NULL or (a.end < CONVERT_TZ(DATE_SUB(NOW(), INTERVAL ou.default_delay_minutes MINUTE),'System','UTC')))
|
||||||
|
AND ap.case_id is NULL
|
||||||
|
AND ((qsep.questionnaire_id is NULL) or qsep.exclude = 0)
|
||||||
|
AND !(q.restrict_work_shifts = 1 AND sh.shift_id IS NULL AND os.outcome_type_id != 2)
|
||||||
|
AND !(si.call_restrict = 1 AND cr.day_of_week IS NULL AND os.outcome_type_id != 2)
|
||||||
|
AND ((apn.appointment_id IS NOT NULL) or qs.call_attempt_max = 0 or ((SELECT count(*) FROM call_attempt WHERE case_id = c.case_id) < qs.call_attempt_max))
|
||||||
|
AND ((apn.appointment_id IS NOT NULL) or qs.call_max = 0 or ((SELECT count(*) FROM `call` WHERE case_id = c.case_id) < qs.call_max))
|
||||||
|
AND (SELECT count(*) FROM `questionnaire_sample_quota` WHERE questionnaire_id = c.questionnaire_id AND sample_import_id = s.import_id AND quota_reached = 1) = 0
|
||||||
|
ORDER BY apn.start DESC, a.start ASC, qsep.priority DESC
|
||||||
|
LIMIT 1";
|
||||||
|
|
||||||
|
//apn.appointment_id contains the id of an appointment if we are calling on an appointment
|
||||||
|
}
|
||||||
$r2 = $db->GetRow($sql);
|
$r2 = $db->GetRow($sql);
|
||||||
|
|
||||||
if (empty($r2))
|
if (empty($r2))
|
||||||
{
|
{
|
||||||
|
|
||||||
|
if ($systemsort)
|
||||||
|
{
|
||||||
|
//Just make sure that this case should go to this operator (assigned to this project and skill)
|
||||||
|
$sql = "SELECT qsep.sample_id as sample_id, qsep.questionnaire_id as questionnaire_id
|
||||||
|
FROM questionnaire_sample_exclude_priority as qsep
|
||||||
|
JOIN operator_skill as os ON (os.operator_id = '$operator_id' AND os.outcome_type_id = 1)
|
||||||
|
JOIN operator_questionnaire AS oq ON (oq.operator_id = '$operator_id' AND oq.questionnaire_id = qsep.questionnaire_id)
|
||||||
|
LEFT JOIN `case` as c ON (c.sample_id = qsep.sample_id AND c.questionnaire_id = qsep.sample_id)
|
||||||
|
WHERE qsep.sortorder IS NOT NULL
|
||||||
|
AND c.case_id IS NULL
|
||||||
|
ORDER BY qsep.sortorder ASC
|
||||||
|
LIMIT 1";
|
||||||
|
|
||||||
/**
|
}
|
||||||
* If no case found, we must draw the next available case from the sample
|
else
|
||||||
* only if no case due to lack of cases to call not out of shift time/etc and
|
{
|
||||||
* only draw cases that are new (Temporary outcome_type_id)
|
|
||||||
*
|
/**
|
||||||
*
|
* If no case found, we must draw the next available case from the sample
|
||||||
* Method:
|
* only if no case due to lack of cases to call not out of shift time/etc and
|
||||||
* next available that has not been assigned
|
* only draw cases that are new (Temporary outcome_type_id) - this makes sure that we are not drawing
|
||||||
* if none available - return false? report to operator that no one available to call at currenet settings
|
* a case just because the operator doesn't have access to temporary outcome id's.
|
||||||
*
|
*
|
||||||
*/
|
*
|
||||||
|
* Method:
|
||||||
|
* next available that has not been assigned
|
||||||
|
* if none available - return false? report to operator that no one available to call at currenet settings
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
$sql = "SELECT s.sample_id as sample_id,c.case_id as case_id,qs.questionnaire_id as questionnaire_id,CONVERT_TZ(NOW(), 'System' , s.Time_zone_name) as resptime, q.testing as testing
|
$sql = "SELECT s.sample_id as sample_id,c.case_id as case_id,qs.questionnaire_id as questionnaire_id,CONVERT_TZ(NOW(), 'System' , s.Time_zone_name) as resptime, q.testing as testing
|
||||||
FROM sample as s
|
FROM sample as s
|
||||||
JOIN (questionnaire_sample as qs, operator_questionnaire as o, questionnaire as q, operator as op, sample_import as si, operator_skill as os) on (op.operator_id = '$operator_id' and qs.sample_import_id = s.import_id and o.operator_id = op.operator_id and o.questionnaire_id = qs.questionnaire_id and q.questionnaire_id = o.questionnaire_id and si.sample_import_id = s.import_id and os.operator_id = op.operator_id and os.outcome_type_id = 1)
|
JOIN (questionnaire_sample as qs, operator_questionnaire as o, questionnaire as q, operator as op, sample_import as si, operator_skill as os) on (op.operator_id = '$operator_id' and qs.sample_import_id = s.import_id and o.operator_id = op.operator_id and o.questionnaire_id = qs.questionnaire_id and q.questionnaire_id = o.questionnaire_id and si.sample_import_id = s.import_id and os.operator_id = op.operator_id and os.outcome_type_id = 1)
|
||||||
LEFT JOIN `case` as c on (c.sample_id = s.sample_id and c.questionnaire_id = qs.questionnaire_id)
|
LEFT JOIN `case` as c on (c.sample_id = s.sample_id and c.questionnaire_id = qs.questionnaire_id)
|
||||||
LEFT JOIN call_restrict as cr on (cr.day_of_week = DAYOFWEEK(CONVERT_TZ(NOW(), 'System' , s.Time_zone_name)) and TIME(CONVERT_TZ(NOW(), 'System' , s.Time_zone_name)) >= cr.start and TIME(CONVERT_TZ(NOW(), 'System' , s.Time_zone_name)) <= cr.end)
|
LEFT JOIN call_restrict as cr on (cr.day_of_week = DAYOFWEEK(CONVERT_TZ(NOW(), 'System' , s.Time_zone_name)) and TIME(CONVERT_TZ(NOW(), 'System' , s.Time_zone_name)) >= cr.start and TIME(CONVERT_TZ(NOW(), 'System' , s.Time_zone_name)) <= cr.end)
|
||||||
LEFT JOIN shift as sh on (sh.questionnaire_id = q.questionnaire_id and (CONVERT_TZ(NOW(),'System','UTC') >= sh.start) AND (CONVERT_TZ(NOW(),'System','UTC') <= sh.end))
|
LEFT JOIN shift as sh on (sh.questionnaire_id = q.questionnaire_id and (CONVERT_TZ(NOW(),'System','UTC') >= sh.start) AND (CONVERT_TZ(NOW(),'System','UTC') <= sh.end))
|
||||||
LEFT JOIN questionnaire_sample_exclude_priority AS qsep ON (qsep.questionnaire_id = qs.questionnaire_id AND qsep.sample_id = s.sample_id)
|
LEFT JOIN questionnaire_sample_exclude_priority AS qsep ON (qsep.questionnaire_id = qs.questionnaire_id AND qsep.sample_id = s.sample_id)
|
||||||
|
|
||||||
WHERE c.case_id is NULL
|
|
||||||
AND ((qsep.questionnaire_id IS NULL) or qsep.exclude = 0)
|
|
||||||
AND !(q.restrict_work_shifts = 1 AND sh.shift_id IS NULL)
|
|
||||||
AND !(si.call_restrict = 1 AND cr.day_of_week IS NULL)
|
|
||||||
AND (SELECT count(*) FROM `questionnaire_sample_quota` WHERE questionnaire_id = qs.questionnaire_id AND sample_import_id = s.import_id AND quota_reached = 1) = 0
|
|
||||||
ORDER BY qsep.priority DESC, rand() * qs.random_select, s.sample_id
|
|
||||||
LIMIT 1";
|
|
||||||
|
|
||||||
|
WHERE c.case_id is NULL
|
||||||
|
AND ((qsep.questionnaire_id IS NULL) or qsep.exclude = 0)
|
||||||
|
AND !(q.restrict_work_shifts = 1 AND sh.shift_id IS NULL)
|
||||||
|
AND !(si.call_restrict = 1 AND cr.day_of_week IS NULL)
|
||||||
|
AND (SELECT count(*) FROM `questionnaire_sample_quota` WHERE questionnaire_id = qs.questionnaire_id AND sample_import_id = s.import_id AND quota_reached = 1) = 0
|
||||||
|
ORDER BY qsep.priority DESC, rand() * qs.random_select, s.sample_id
|
||||||
|
LIMIT 1";
|
||||||
|
}
|
||||||
|
|
||||||
$r3 = $db->GetRow($sql);
|
$r3 = $db->GetRow($sql);
|
||||||
|
|
||||||
|
|||||||
@@ -43,15 +43,17 @@ include_once(dirname(__FILE__).'/../db.inc.php');
|
|||||||
/**
|
/**
|
||||||
* Determine if a process is already running
|
* Determine if a process is already running
|
||||||
*
|
*
|
||||||
|
* @param int $type Defaults to 1 - specify the process type (class) to search for
|
||||||
* @return bool|int Return false if no process already running, else return the process_id
|
* @return bool|int Return false if no process already running, else return the process_id
|
||||||
*/
|
*/
|
||||||
function is_process_running()
|
function is_process_running($type = 1)
|
||||||
{
|
{
|
||||||
global $db;
|
global $db;
|
||||||
|
|
||||||
$sql = "SELECT `process_id`
|
$sql = "SELECT `process_id`
|
||||||
FROM `process`
|
FROM `process`
|
||||||
WHERE `stop` IS NULL";
|
WHERE `stop` IS NULL
|
||||||
|
AND type = '$type'";
|
||||||
|
|
||||||
$rs = $db->GetRow($sql);
|
$rs = $db->GetRow($sql);
|
||||||
|
|
||||||
@@ -90,23 +92,24 @@ function is_process_killed($process_id)
|
|||||||
* Start a process
|
* Start a process
|
||||||
*
|
*
|
||||||
* @param string $filename The PHP file of the process to run
|
* @param string $filename The PHP file of the process to run
|
||||||
|
* @param int $type The type (class) of process (so we can run multiple processes at the same time) defaults to 1
|
||||||
* @return bool|int False if we couldnt start a process, else the process id from the process table
|
* @return bool|int False if we couldnt start a process, else the process id from the process table
|
||||||
*
|
*
|
||||||
* @link http://www.djkaty.com/php/fork Cross platform process tutorial (this code adapted from here)
|
* @link http://www.djkaty.com/php/fork Cross platform process tutorial (this code adapted from here)
|
||||||
*/
|
*/
|
||||||
function start_process($filename)
|
function start_process($filename,$type = 1)
|
||||||
{
|
{
|
||||||
//create a record only if no process already running
|
//create a record only if no process already running
|
||||||
global $db;
|
global $db;
|
||||||
|
|
||||||
$db->StartTrans();
|
$db->StartTrans();
|
||||||
|
|
||||||
$process = is_process_running();
|
$process = is_process_running($type);
|
||||||
|
|
||||||
if ($process == false)
|
if ($process == false)
|
||||||
{
|
{
|
||||||
$sql = "INSERT INTO `process` (`process_id`,`start`,`stop`,`kill`,`data`)
|
$sql = "INSERT INTO `process` (`process_id`,`type`,`start`,`stop`,`kill`,`data`)
|
||||||
VALUES (NULL,CONVERT_TZ(NOW(),'System','UTC'),NULL,0,'')";
|
VALUES (NULL,'$type',CONVERT_TZ(NOW(),'System','UTC'),NULL,0,'')";
|
||||||
|
|
||||||
$rs = $db->Execute($sql);
|
$rs = $db->Execute($sql);
|
||||||
$args = $db->Insert_ID();
|
$args = $db->Insert_ID();
|
||||||
@@ -220,15 +223,17 @@ function process_get_data($process_id)
|
|||||||
/**
|
/**
|
||||||
* Get data from the last process run
|
* Get data from the last process run
|
||||||
*
|
*
|
||||||
|
* @param int $type The last processes class (type) defaults to 1
|
||||||
* @return string Data from the last process, or an empty string if not available
|
* @return string Data from the last process, or an empty string if not available
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
function process_get_last_data()
|
function process_get_last_data($type = 1)
|
||||||
{
|
{
|
||||||
global $db;
|
global $db;
|
||||||
|
|
||||||
$sql = "SELECT `data`
|
$sql = "SELECT `data`
|
||||||
FROM `process`
|
FROM `process`
|
||||||
|
WHERE type = '$type'
|
||||||
ORDER BY `process_id` DESC
|
ORDER BY `process_id` DESC
|
||||||
LIMIT 1";
|
LIMIT 1";
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user