Newer
Older
<?php
class ReportModel {
const PERM_AGENTS = 'stats.agents';
static protected $perms = array(
self::PERM_AGENTS => array(
/* @trans */ 'Ability to view stats of other agents in allowed departments',
'primary' => true,
));
static function getPermissions() {
return self::$perms;
}
}
RolePermission::register(/* @trans */ 'Miscellaneous', ReportModel::getPermissions());
class OverviewReport {
var $start;
var $end;
var $format;
function __construct($start, $end='now', $format=null) {
global $cfg;
$this->format = $format ?: $cfg->getDateFormat(true);
function getStartDate($format=null, $translate=true) {
if (!$this->start)
return '';
$format = $format ?: $this->format;
if ($translate) {
$format = str_replace(
array('y', 'Y', 'm'),
array('yy', 'yyyy', 'mm'),
$format);
}
return Format::date(Misc::dbtime($this->start), false, $format);
}
function getDateRange() {
global $cfg;
$start = $this->start ?: 'last month';
$stop = $this->end ?: 'now';
// Convert user time to db time
$start = Misc::dbtime($start);
// Stop time can be relative.
if ($stop[0] == '+') {
// $start time + time(X days)
$now = time();
$stop = $start + (strtotime($stop, $now)-$now);
} else {
$stop = Misc::dbtime($stop);
}
$stop = 'FROM_UNIXTIME('.$stop.')';
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
return array($start, $stop);
}
function getPlotData() {
list($start, $stop) = $this->getDateRange();
# Fetch all types of events over the timeframe
$res = db_query('SELECT DISTINCT(state) FROM '.THREAD_EVENT_TABLE
.' WHERE timestamp BETWEEN '.$start.' AND '.$stop
.' AND state IN ("created", "closed", "reopened", "assigned", "overdue", "transferred")'
.' ORDER BY 1');
$events = array();
while ($row = db_fetch_row($res)) $events[] = $row[0];
# TODO: Handle user => db timezone offset
# XXX: Implement annulled column from the %ticket_event table
$res = db_query('SELECT state, DATE_FORMAT(timestamp, \'%Y-%m-%d\'), '
.'COUNT(DISTINCT T.id)'
.' FROM '.THREAD_EVENT_TABLE. ' E '
.' JOIN '.THREAD_TABLE. ' T
ON (T.id = E.thread_id AND T.object_type = "T") '
.' WHERE E.timestamp BETWEEN '.$start.' AND '.$stop
.' AND NOT annulled'
.' AND E.state IN ("created", "closed", "reopened", "assigned", "overdue", "transferred")'
.' GROUP BY E.state, DATE_FORMAT(E.timestamp, \'%Y-%m-%d\')'
.' ORDER BY 2, 1');
# Initialize array of plot values
$plots = array();
foreach ($events as $e) { $plots[$e] = array(); }
$time = null; $times = array();
# Iterate over result set, adding zeros for missing ticket events
$slots = array();
while ($row = db_fetch_row($res)) {
$row_time = strtotime($row[1]);
if ($time != $row_time) {
# New time (and not the first), figure out which events did
# not have any tickets associated for this time slot
if ($time !== null) {
# Not the first record -- add zeros all the arrays that
# did not have at least one entry for the timeframe
foreach (array_diff($events, $slots) as $slot)
$plots[$slot][] = 0;
}
$slots = array();
$times[] = $time = $row_time;
}
# Keep track of states for this timeframe
$slots[] = $row[0];
$plots[$row[0]][] = (int)$row[2];
}
foreach (array_diff($events, $slots) as $slot)
$plots[$slot][] = 0;
return array("times" => $times, "plots" => $plots, "events" => $events);
}
function enumTabularGroups() {
return array("dept"=>__("Department"), "topic"=>__("Topics"),
# XXX: This will be relative to permissions based on the
# logged-in-staff. For basic staff, this will be 'My Stats'
"staff"=>__("Agent"));
}
function getTabularData($group='dept') {
global $thisstaff;
list($start, $stop) = $this->getDateRange();
$times = ThreadEvent::objects()
->constrain(array(
'thread__entries' => array(
'thread__entries__type' => 'R',
),
))
->constrain(array(
'thread__events' => array(
'thread__events__state' => 'created',
'state' => 'closed',
'annulled' => 0,
),
))
->filter(array(
'timestamp__range' => array($start, $stop, true),
))
'ServiceTime' => SqlAggregate::AVG(SqlFunction::timestampdiff(
new SqlCode('HOUR'), new SqlField('thread__events__timestamp'), new SqlField('timestamp'))
'ResponseTime' => SqlAggregate::AVG(SqlFunction::timestampdiff(
new SqlCode('HOUR'),new SqlField('thread__entries__parent__created'), new SqlField('thread__entries__created')
)),
$stats = ThreadEvent::objects()
->filter(array(
'annulled' => 0,
'timestamp__range' => array($start, $stop, true),
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
'thread__object_type' => 'T',
))
->aggregate(array(
'Opened' => SqlAggregate::COUNT(
SqlCase::N()
->when(new Q(array('state' => 'created')), 1)
),
'Assigned' => SqlAggregate::COUNT(
SqlCase::N()
->when(new Q(array('state' => 'assigned')), 1)
),
'Overdue' => SqlAggregate::COUNT(
SqlCase::N()
->when(new Q(array('state' => 'overdue')), 1)
),
'Closed' => SqlAggregate::COUNT(
SqlCase::N()
->when(new Q(array('state' => 'closed')), 1)
),
'Reopened' => SqlAggregate::COUNT(
SqlCase::N()
->when(new Q(array('state' => 'reopened')), 1)
),
'Deleted' => SqlAggregate::COUNT(
SqlCase::N()
->when(new Q(array('state' => 'deleted')), 1)
),
));
switch ($group) {
case 'dept':
$headers = array(__('Department'));
$header = function($row) { return Dept::getLocalNameById($row['dept_id'], $row['dept__name']); };
$stats = $stats
->filter(array('dept_id__in' => $thisstaff->getDepts()))
$times = $times
->filter(array('dept_id__in' => $thisstaff->getDepts()))
->values('dept__id');
break;
case 'topic':
$headers = array(__('Help Topic'));
$header = function($row) { return Topic::getLocalNameById($row['topic_id'], $row['topic__topic']); };
$pk = 'topic_id';
$topics = Topic::getHelpTopics(false, Topic::DISPLAY_DISABLED);
->filter(array('dept_id__in' => $thisstaff->getDepts(), 'topic_id__gt' => 0, 'topic_id__in' => array_keys($topics)));
$times = $times
->values('topic_id')
->filter(array('topic_id__gt' => 0));
break;
case 'staff':
$headers = array(__('Agent'));
$header = function($row) { return new AgentsName(array(
'first' => $row['staff__firstname'], 'last' => $row['staff__lastname'])); };
$pk = 'staff_id';
$staff = Staff::getStaffMembers();
$stats = $stats
->values('staff_id', 'staff__firstname', 'staff__lastname')
->filter(array('staff_id__in' => array_keys($staff)));
$times = $times->values('staff_id');
$depts = $thisstaff->getManagedDepartments();
if ($thisstaff->hasPerm(ReportModel::PERM_AGENTS))
$depts = array_merge($depts, $thisstaff->getDepts());
$Q = Q::any(array(
'staff_id' => $thisstaff->getId(),
));
if ($depts)
$Q->add(array('dept_id__in' => $depts));
$stats = $stats->filter(array('staff_id__gt'=>0))->filter($Q);
$times = $times->filter(array('staff_id__gt'=>0))->filter($Q);
break;
default:
# XXX: Die if $group not in $groups
}
$timings = array();
foreach ($times as $T) {
$timings[$T[$pk]] = $T;
}
$rows = array();
foreach ($stats as $R) {
if (isset($R['dept__flags'])) {
if ($R['dept__flags'] & Dept::FLAG_ARCHIVED)
$status = ' - '.__('Archived');
elseif ($R['dept__flags'] & Dept::FLAG_ACTIVE)
$status = '';
else
$status = ' - '.__('Disabled');
}
if (isset($R['topic__flags'])) {
if ($R['topic__flags'] & Topic::FLAG_ARCHIVED)
$status = ' - '.__('Archived');
elseif ($R['topic__flags'] & Topic::FLAG_ACTIVE)
$status = '';
else
$status = ' - '.__('Disabled');
}
$rows[] = array($header($R) . $status, $R['Opened'], $R['Assigned'],
$R['Overdue'], $R['Closed'], $R['Reopened'], $R['Deleted'],
number_format($T['ServiceTime'], 1),
number_format($T['ResponseTime'], 1));
}
return array("columns" => array_merge($headers,
array(__('Opened'),__('Assigned'),__('Overdue'),__('Closed'),__('Reopened'),
__('Deleted'),__('Service Time'),__('Response Time'))),