<%args>
$startdate => undef
$enddate => undef
$queues => undef
</%args>
<& /Elements/Header, Title => $title &>
<& /Tools/Reports/Elements/Tabs, current_tab => 'Tools/Reports/TimeWorkedCustomer.html', Title => $title &>
<hr>
<%init>
my ($start_date, $end_date, $effective_end_date, $title);
$title = loc('Time worked report');
$start_date = RT::Date->new($session{'CurrentUser'});
$end_date = RT::Date->new($session{'CurrentUser'});
# If we have a value for start date, parse it into an RT::Date object
if ($startdate) {
$start_date->Set(Format => 'unknown', Value => $startdate);
# And then get it back as an ISO string for display purposes, in the form field and
# report header
$startdate = $start_date->AsString(Format => 'ISO', Timezone => 'server');
}
# Same treatment for end date
if ($enddate) {
$end_date->Set(Format => 'unknown', Value => $enddate);
$enddate = $end_date->AsString(Format => 'ISO', Timezone => 'server');
}
</%init>
<form method="post" action="TimeWorkedCustomer.html">
<br />
<&|/l&>Start date</&>:
<& /Elements/SelectDate, Name => 'startdate', Default => ($startdate) ? $start_date->AsString(Format => 'ISO', Timezone => 'server') : ''&>
(report will start from midnight on this day unless you indicate otherwise)
<br />
<&|/l&>End date</&>:
<& /Elements/SelectDate, Name => 'enddate', Default => ($enddate) ? $end_date->AsString(Format => 'ISO', Timezone => 'server') : ''&>
(report will -not- be inclusive of this day unless you change the time from midnight)
<br />
<&|/l&>Queues</&>:
<& /Elements/SelectMultiQueue, Name => 'queues', Default => ($queues) ? $queues : ''&>
<& /Elements/Submit&>
</form>
<%perl>
# TimeWorkedReport
# Version 0.04 2009-09-28
#
# Fran Fabrizio, UAB CIS, fran@cis.uab.edu
use strict;
# if we are just getting here and the form values are empty, we are done
if (!$startdate || !$enddate) {
return;
}
# get the queue object(s)
my $queuesobj = new RT::Queues($session{CurrentUser});
my ($queuelist, %queuesofinterest);
# The user's choice of queues will come in from the web form in the $queues variable, which is
# mapped to the SELECT field on the web interface for the report. Unfortunately, if the user
# chooses just one queue, $queues will have a scalar value, but if the user chooses multiple
# queues, it will be an arrayref. So we need to check for each case and process differently.
#
# What we want to construct is the %queuesofinterest simple lookup hash which defines a key
# that is the queue ID for each queue selected, and the $queuelist string, which is just for
# displaying the list of queues in the report header
if (ref $queues) {
# multiple queues selected
for (@$queues) {
$queuesobj->Limit(FIELD => "Id", OPERATOR => "=", VALUE => $_, ENTRYAGGREGATOR => "OR");
$queuesofinterest{$_} = 1;
}
$queuelist = join ", ", map {$_->Name} @{$queuesobj->ItemsArrayRef};
} else {
my $queue = new RT::Queue;
$queue->Load(Id => $queues);
$queuesofinterest{$queues} = 1;
$queuelist = $queue->Name;
}
# hash to hold statistics
# %stats will be a multilevel hash - first level keys are the usernames, second level keys are
# the ticket IDs, and for each ticket, we store an anonymous hash with keys Subject and TimeWorked
# (this implies that a single ticket can live under two+ users if they both worked the ticket)
my %stats;
# Get a new transactions object to hold transaction search results for this ticket
my $trans = new RT::Transactions($session{'CurrentUser'});
# only in the period of interest
$trans->Limit(FIELD => 'Created', OPERATOR => '>', VALUE => $startdate);
$trans->Limit(FIELD => 'Created', OPERATOR => '<', VALUE => $enddate, ENTRYAGGREGATOR => 'AND');
# now start counting all the TimeTaken by examining transactions associated with this ticket
while (my $tr = $trans->Next) {
# did this transaction take any time? RT records this -either- in TimeTaken column or by
# indicating "TimeWorked" in the Field column, depending on how the user inputted the time.
if ($tr->ObjectType eq 'RT::Ticket') {
# Got a hot one - what ticket is this?
my $t = new RT::Ticket($session{'CurrentUser'});
$t->Load($tr->ObjectId);
if (!$t) {
# unable to retrieve a ticket for this transaction
# hopefully we don't ever reach here!
next;
} else {
# Is this ticket in a queue we care about?
if (!$queuesofinterest{$t->Queue}) {
next;
}
}
# what customer is this?
my $c = "";
my $cfs = $t->CustomFieldValues;
while (my $cf = $cfs->Next) {
if ($cf->CustomField == 19) {
$c = $cf->Content;
}
}
if (!$c) {
# unable to retrieve a customer for this ticket
# hopefully we don't ever reach here!
next;
}
# we've got some time to account for
# is this the first time this person is charging time to this ticket?
# if so, add this ticket subject to the data structure
if (!exists($stats{$c}{$t->id}{Subject})) {
$stats{$c}{$t->id}{Subject} = $t->Subject;
}
if ($tr->TimeTaken != 0) {
# this was a comment or correspondence where the user also added some time worked
# value of interest appears in Transaction's TimeTaken column
$stats{$c}{$t->id}{TimeWorked} += $tr->TimeTaken;
} elsif ($tr->Field && $tr->Field eq 'TimeWorked') {
# this was a direct update of the time worked field from the Basics or Jumbo ticket update page
# values of interest appear in Transaction's OldValue and NewValue columns
# RT does not use the TimeTaken column in this instance.
$stats{$c}{$t->id}{TimeWorked} += $tr->NewValue - $tr->OldValue;
} else {
$stats{$c}{$t->id}{TimeWorked} += 0;
}
}
}
# report output starts here
# output:
# normal user: their own time worked report, most worked ticket to least worked ticket
# superuser: everyone's time worked report, in username alpha order, then by most worked to least worked
# superuser+byticket: most worked ticket first, with everyone's contribution ranked by biggest contribution to smallest
print "<h2>TIME WORKED REPORT FOR QUEUE(S) " . $queuelist . "</h2>";
print "<h3>Date Range: $startdate TO $enddate</h3>";
# the existing %stats data structure is perfect for the default report, no data transform needed
for my $customer (sort keys %stats) {
print "<h3>" . $customer . "</h3>";
print "<TABLE BORDER=0 CELLSPACING=5>";
print "<TR><TH>ID</TH><TH>HOURS</TH><TH></TH><TH></TH><TH>SUBJECT</TH></TR>";
my $totalMinutes = 0;
for my $tid (sort {$stats{$customer}{$b}{TimeWorked} <=> $stats{$customer}{$a}{TimeWorked}} keys %{$stats{$customer}}) {
my $minutes = $stats{$customer}{$tid}{TimeWorked};
my $subject = $stats{$customer}{$tid}{Subject};
my $hours = int($minutes/60*100)/100;
$hours =~ s/\./,/;
# print "<TR><TD ALIGN=RIGHT>$tid</TD><TD ALIGN=RIGHT>" . sprintf("%.1f",($minutes/60)) . "</TD><TD></TD><TD></TD>" .
# "<TD><A TARGET=\"_TimeWorked\" HREF=\"/Ticket/Display.html?id=$tid\">$subject</A></TD></TR>";
print "<TR><TD ALIGN=RIGHT>$tid</TD><TD ALIGN=RIGHT>$hours</TD><TD></TD><TD></TD>" .
"<TD><A TARGET=\"_TimeWorked\" HREF=\"/Ticket/Display.html?id=$tid\">$subject</A></TD></TR>";
$totalMinutes += $minutes;
}
print "<TR><TD ALIGN=RIGHT><B>TOTAL</B></TD><TD ALIGN=RIGHT><B>" . sprintf("%.1f",($totalMinutes/60)) . "</B></TD><TD></TD><TD></TD><TD></TD></TR>";
print "</TABLE>";
}
##### helper functions below
sub form_date_string {
# expects seven input params - year, month, day, hour, minute, second, offset
my $year = $_[0] - 1900;
my $mon = $_[1] - 1;
my $day = $_[2];
my $hour = $_[3] ? $_[3] : 0;
my $min = $_[4] ? $_[4] : 0;
my $sec = $_[5] ? $_[5] : 0;
my $offset = $_[6] ? $_[6] : 0;
# convert to seconds since epoch, then adjust for the $offset, which is also in seconds
# we do this so we don't have to do fancy date arithmetic - we can just subtract one seconds
# value from the other seconds value
my $starttime = timelocal($sec,$min,$hour,$day,$mon,$year) - $offset;
# convert back to component parts now that we've adjusted for offset
# this gives us the components which represent the GMT time for the local time that was entered
# on the command line
($sec,$min,$hour,$day,$mon,$year) = localtime($starttime);
# format the date string, padding with zeros if needed
return sprintf("%04d-%02d-%02d %02d:%02d:%02d", ($year+1900), ($mon+1), $day, $hour, $min, $sec);
}
</%perl>