| 1 |
# Copyright (C) 2006, 2007 Software Freedom Law Center, Inc. |
|---|
| 2 |
# Author: Bradley M. Kuhn <bkuhn@softwarefreedom.org> |
|---|
| 3 |
# |
|---|
| 4 |
# This software gives you freedom; it is licensed to you under version |
|---|
| 5 |
# 3 of the GNU Affero General Public License. |
|---|
| 6 |
# |
|---|
| 7 |
# This software is distributed WITHOUT ANY WARRANTY, without even the |
|---|
| 8 |
# implied warranties of MERCHANTABILITY and FITNESS FOR A PARTICULAR |
|---|
| 9 |
# PURPOSE. See the GNU Affero General Public License for further |
|---|
| 10 |
# details. |
|---|
| 11 |
# |
|---|
| 12 |
# You should have received a copy of the GNU Affero General Public |
|---|
| 13 |
# License, version 3 along with this software. If not, see |
|---|
| 14 |
# <http://www.gnu.org/licenses/>. |
|---|
| 15 |
# Reports.pm -*- Perl -*- |
|---|
| 16 |
# Database module for SFLC time tracker |
|---|
| 17 |
|
|---|
| 18 |
# NOTE: some of these reports assume an epoch of 1 February 2000, that |
|---|
| 19 |
# should be fixed, OTOH, it was written that way because using earlier |
|---|
| 20 |
# epocs was tooo slow when the DB got large. |
|---|
| 21 |
|
|---|
| 22 |
package SFLC::TimeTracker::Reports; |
|---|
| 23 |
|
|---|
| 24 |
use strict; |
|---|
| 25 |
use warnings; |
|---|
| 26 |
|
|---|
| 27 |
|
|---|
| 28 |
use Lingua::EN::Inflect qw ( PL PL_N PL_V); |
|---|
| 29 |
require Exporter; |
|---|
| 30 |
#use AutoLoader qw(AUTOLOAD); |
|---|
| 31 |
|
|---|
| 32 |
our @ISA = qw(Exporter); |
|---|
| 33 |
|
|---|
| 34 |
our @EXPORT_OK = (); |
|---|
| 35 |
|
|---|
| 36 |
our @EXPORT = qw( ); |
|---|
| 37 |
|
|---|
| 38 |
our $VERSION = '0.01'; |
|---|
| 39 |
|
|---|
| 40 |
# FIXME: stop hard coding this everywhere |
|---|
| 41 |
|
|---|
| 42 |
my @VALID_USERS = qw/user1 user2 user3/; |
|---|
| 43 |
|
|---|
| 44 |
use Carp; |
|---|
| 45 |
|
|---|
| 46 |
use Date::Manip; |
|---|
| 47 |
use Lingua::EN::Inflect qw ( PL PL_N PL_V); |
|---|
| 48 |
|
|---|
| 49 |
use SFLC::TimeTracker::Input; |
|---|
| 50 |
use SFLC::TimeTracker::DB; |
|---|
| 51 |
|
|---|
| 52 |
use Data::Dumper; |
|---|
| 53 |
|
|---|
| 54 |
my($f_email, $f_client, $f_date, $f_matter, $f_user, $f_note, $f_hours); |
|---|
| 55 |
|
|---|
| 56 |
# format DETAIL_REPORT_TOP = |
|---|
| 57 |
# Client Matter Employee Date Hours Note |
|---|
| 58 |
# -------------------------------------------------------------------------------- |
|---|
| 59 |
# . |
|---|
| 60 |
|
|---|
| 61 |
my $F_MATTER_LEN_DETAIL = 17; |
|---|
| 62 |
format DETAIL_REPORT = |
|---|
| 63 |
@<<<<<<<<< @<<<<<<<<<<<<<<<<< @<<<<<<< @<<<<<<<<<< @####.# ^<<<<<<<<<<<<<< |
|---|
| 64 |
$f_client, $f_matter, $f_user, $f_date, $f_hours,$f_note |
|---|
| 65 |
~~ ^<<<<<<<<<<<<<< |
|---|
| 66 |
$f_note |
|---|
| 67 |
. |
|---|
| 68 |
|
|---|
| 69 |
|
|---|
| 70 |
my $F_MATTER_LEN_SUMMARY = 21; |
|---|
| 71 |
format SUMMARY_REPORT = |
|---|
| 72 |
@<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<< @<<<<<<< @####.# ^<<<<<<<<<<<<<<<<<<<< |
|---|
| 73 |
$f_client, $f_matter, $f_user, $f_hours, $f_note |
|---|
| 74 |
~~ ^<<<<<<<<<<<<<<<<<<<< |
|---|
| 75 |
$f_note |
|---|
| 76 |
. |
|---|
| 77 |
|
|---|
| 78 |
############################################################################### |
|---|
| 79 |
sub _BuildDataStructure ($$$$$$) { |
|---|
| 80 |
my($db, $i_user, $i_startDate, $i_endDate, $i_client, $i_matter) = @_; |
|---|
| 81 |
my(@users) = ($i_user =~ /^ALL$/i) ? $db->getUserList : ($i_user); |
|---|
| 82 |
|
|---|
| 83 |
my @entries; |
|---|
| 84 |
foreach my $userHandle (@users) { |
|---|
| 85 |
push(@entries, $db->getEntriesInDateRange('main', |
|---|
| 86 |
$userHandle, $i_startDate, $i_endDate)); |
|---|
| 87 |
} |
|---|
| 88 |
|
|---|
| 89 |
# This code |
|---|
| 90 |
# my $filterRE = '^\s*/legal'; |
|---|
| 91 |
# if ($i_client =~ /^\s*ALL\s*$/i) { |
|---|
| 92 |
# if ($i_matter =~ /^\s*ALL\s*$/i) { |
|---|
| 93 |
# } else { |
|---|
| 94 |
# $filterRE .= '/\S+/' . $i_matter; |
|---|
| 95 |
# } |
|---|
| 96 |
# } else { |
|---|
| 97 |
# if ($i_matter =~ /^\s*ALL\s*$/i) { |
|---|
| 98 |
# $filterRE .= '/' . $i_client; |
|---|
| 99 |
# } else { |
|---|
| 100 |
# $filterRE .= '/' . $i_client . '/' . $i_matter; |
|---|
| 101 |
# } |
|---|
| 102 |
# } |
|---|
| 103 |
|
|---|
| 104 |
my $filterRE; |
|---|
| 105 |
$i_client = "legal/client/$i_client/" |
|---|
| 106 |
if ($i_client ne "admin" and $i_client ne "tech" and |
|---|
| 107 |
$i_client !~ /^\s*ALL\s*$/i); |
|---|
| 108 |
|
|---|
| 109 |
if ($i_client =~ /^\s*ALL\s*$/i) { |
|---|
| 110 |
if ($i_matter =~ /^\s*ALL\s*$/i) { |
|---|
| 111 |
$filterRE = undef; |
|---|
| 112 |
} else { |
|---|
| 113 |
$filterRE = $i_matter .'\s*$'; |
|---|
| 114 |
} |
|---|
| 115 |
} else { |
|---|
| 116 |
if ($i_matter =~ /^\s*ALL\s*$/i) { |
|---|
| 117 |
$filterRE = '^\s*/' . $i_client; |
|---|
| 118 |
} else { |
|---|
| 119 |
$filterRE = '^\s*/' . $i_client . $i_matter .'\s*$'; |
|---|
| 120 |
} |
|---|
| 121 |
} |
|---|
| 122 |
|
|---|
| 123 |
my($lastTopLevel, $lastClient, $lastMatter, $lastUser) = ("", "", "", ""); |
|---|
| 124 |
my $lastDate = '1975-01-01'; |
|---|
| 125 |
|
|---|
| 126 |
my $firstTime = 1; |
|---|
| 127 |
|
|---|
| 128 |
my %r; |
|---|
| 129 |
foreach my $entry (@entries) { |
|---|
| 130 |
my $category = $entry->get('category')->prettyPrint; |
|---|
| 131 |
# Skip categories we don't want |
|---|
| 132 |
next if defined $filterRE and $category !~ /$filterRE/i; |
|---|
| 133 |
|
|---|
| 134 |
# Find new client/matter |
|---|
| 135 |
my($newTopLevel, $newClient, $newMatter) = ("BORKEN", "BORKEN", "BORKEN"); |
|---|
| 136 |
if ($category =~ s/^\/legal\/client\///) { |
|---|
| 137 |
$newTopLevel = "legal"; |
|---|
| 138 |
if ($category =~ s/^(someone\/withsubmatters)\///) { |
|---|
| 139 |
$newClient = $1; |
|---|
| 140 |
} else { |
|---|
| 141 |
$category =~ s/^([^\/]+)\///; |
|---|
| 142 |
$newClient = $1; |
|---|
| 143 |
} |
|---|
| 144 |
$newMatter = $category; |
|---|
| 145 |
} else { |
|---|
| 146 |
$category =~ s/^\/([^\/]+)\///; |
|---|
| 147 |
$newClient = $1; |
|---|
| 148 |
$newMatter = $category; |
|---|
| 149 |
$newTopLevel = $newClient; |
|---|
| 150 |
} |
|---|
| 151 |
$newTopLevel = 'other' |
|---|
| 152 |
if ($newClient eq "admin" and |
|---|
| 153 |
$newMatter =~ /^office\-chat|sick|vacation|holiday|break|lunch|personal$/); |
|---|
| 154 |
|
|---|
| 155 |
my $newDate = $entry->get('dateOccurred'); |
|---|
| 156 |
my $newUser = $entry->get('userHandle'); |
|---|
| 157 |
die( "invalid date in " . $entry->get('id')) if not defined $newDate; |
|---|
| 158 |
die( "invalid user in " . $entry->get('id')) if not defined $newUser; |
|---|
| 159 |
|
|---|
| 160 |
# print $entry->get('id'), ": \"$newTopLevel\", \"$newClient\", \"$newMatter\", \"$newUser\", \"$newDate\"\n"; |
|---|
| 161 |
if (not exists |
|---|
| 162 |
$r{$newTopLevel}{$newClient}{$newMatter}{$newUser}{$newDate}) { |
|---|
| 163 |
$r{$newTopLevel}{$newClient}{$newMatter}{$newUser}{$newDate}{hours} |
|---|
| 164 |
= 0.0; |
|---|
| 165 |
$r{$newTopLevel}{$newClient}{$newMatter}{$newUser}{$newDate}{notes} |
|---|
| 166 |
= []; |
|---|
| 167 |
} |
|---|
| 168 |
my $h = Delta_Format($entry->get('amountTime'), 1, '%ht'); |
|---|
| 169 |
die ("bad amount in " . $entry->{id}) if (not defined $h or $h =~ /^\s*$/); |
|---|
| 170 |
$r{$newTopLevel}{$newClient}{$newMatter}{$newUser}{$newDate}{hours} += $h; |
|---|
| 171 |
my $n = $entry->get('note'); |
|---|
| 172 |
if (defined $n and $n !~ /^\s*$/) { |
|---|
| 173 |
# Add the new note, $n, to the old list of notes, if it's not already in there |
|---|
| 174 |
# in it. |
|---|
| 175 |
|
|---|
| 176 |
unless (grep /$n/i, |
|---|
| 177 |
@{$r{$newTopLevel}{$newClient}{$newMatter}{$newUser}{$newDate}{notes}}) { |
|---|
| 178 |
push(@{$r{$newTopLevel}{$newClient}{$newMatter}{$newUser}{$newDate}{notes}}, |
|---|
| 179 |
$n); |
|---|
| 180 |
} |
|---|
| 181 |
} |
|---|
| 182 |
} |
|---|
| 183 |
return %r; |
|---|
| 184 |
} |
|---|
| 185 |
############################################################################### |
|---|
| 186 |
# $startDate and $endDate should already be in Date::Manip format |
|---|
| 187 |
sub Detail ($$$$$$$$) { |
|---|
| 188 |
my($db, $i_user, $i_startDate, $i_endDate, $i_client, $i_matter, $email, |
|---|
| 189 |
$emailNote) = @_; |
|---|
| 190 |
|
|---|
| 191 |
my $startPretty = UnixDate($i_startDate, '%F'); |
|---|
| 192 |
my $endPretty = UnixDate($i_endDate, '%F'); |
|---|
| 193 |
|
|---|
| 194 |
my $startSmall = UnixDate($i_startDate, "%Y-%m-%d"); |
|---|
| 195 |
my $endSmall = UnixDate($i_endDate, "%Y-%m-%d"); |
|---|
| 196 |
|
|---|
| 197 |
open(DETAIL_REPORT, "|/usr/sbin/sendmail -f time\@example.org -t"); |
|---|
| 198 |
# open(DETAIL_REPORT, ">$i_user.report"); |
|---|
| 199 |
print DETAIL_REPORT <<HEADER; |
|---|
| 200 |
To: $email |
|---|
| 201 |
From: Tim <time\@example.org> |
|---|
| 202 |
Subject: Time Detail: $startSmall to $endSmall |
|---|
| 203 |
|
|---|
| 204 |
$emailNote |
|---|
| 205 |
Time Detail Report |
|---|
| 206 |
Report Criteria |
|---|
| 207 |
Start Date: $startPretty |
|---|
| 208 |
End Date: $endPretty |
|---|
| 209 |
Client: $i_client |
|---|
| 210 |
Matter: $i_matter |
|---|
| 211 |
Employee: $i_user |
|---|
| 212 |
|
|---|
| 213 |
|
|---|
| 214 |
Client Matter Employee Date Hours Note |
|---|
| 215 |
-------------------------------------------------------------------------------- |
|---|
| 216 |
HEADER |
|---|
| 217 |
|
|---|
| 218 |
|
|---|
| 219 |
my(%r) = _BuildDataStructure($db, $i_user, $i_startDate, $i_endDate, |
|---|
| 220 |
$i_client, $i_matter); |
|---|
| 221 |
my($overallTotal, $overallNoOther) = (0.0, 0.0); |
|---|
| 222 |
foreach my $topLevel (sort { return -1 if $a eq "legal"; |
|---|
| 223 |
return 1 if $b eq 'legal'; |
|---|
| 224 |
return 1 if $a eq 'other'; |
|---|
| 225 |
return -1 if $b eq 'other'; |
|---|
| 226 |
return $a cmp $b; } |
|---|
| 227 |
keys %r) { |
|---|
| 228 |
my $topLevelTotal = 0.0; |
|---|
| 229 |
foreach my $client (sort { |
|---|
| 230 |
return -1 if ($a =~ /^(?:admin|tech)/ and |
|---|
| 231 |
$b !~ /^(?:admin|tech)/); |
|---|
| 232 |
return 1 if ($b =~ /^(?:admin|tech)/ and |
|---|
| 233 |
$a !~ /^(?:admin|tech)/); |
|---|
| 234 |
return $a cmp $b; |
|---|
| 235 |
} keys %{$r{$topLevel}}) { |
|---|
| 236 |
my $clientTotal = 0.0; |
|---|
| 237 |
my $clientFirst = 1; |
|---|
| 238 |
foreach my $matter (sort { $a cmp $b } |
|---|
| 239 |
keys %{$r{$topLevel}{$client}}) { |
|---|
| 240 |
my $matterTotal = 0.0; |
|---|
| 241 |
my $matterFirst = 1; |
|---|
| 242 |
$clientFirst = 1; # Dan likes to see the client again at beginning |
|---|
| 243 |
# of each matter |
|---|
| 244 |
foreach my $user |
|---|
| 245 |
(sort { $a cmp $b } keys %{$r{$topLevel}{$client}{$matter}}) { |
|---|
| 246 |
my $userTotal = 0.0; |
|---|
| 247 |
my $userFirst = 1; |
|---|
| 248 |
print DETAIL_REPORT "\n"; $---; |
|---|
| 249 |
foreach my $date |
|---|
| 250 |
(sort { $a cmp $b } |
|---|
| 251 |
keys %{$r{$topLevel}{$client}{$matter}{$user}}) { |
|---|
| 252 |
$f_matter = $matterFirst ? $matter : ""; |
|---|
| 253 |
my $len = length($f_matter); |
|---|
| 254 |
$f_matter = ($len <= $F_MATTER_LEN_DETAIL) ? $f_matter |
|---|
| 255 |
: ( substr($f_matter, 0, 2) . "..." . |
|---|
| 256 |
substr($f_matter, |
|---|
| 257 |
$len - ($F_MATTER_LEN_DETAIL - 4),$len)); |
|---|
| 258 |
|
|---|
| 259 |
$f_client = $clientFirst ? $client : ""; |
|---|
| 260 |
$f_user = $userFirst ? $user : ""; |
|---|
| 261 |
$f_date = $date; |
|---|
| 262 |
$f_hours = $r{$topLevel}{$client}{$matter}{$user}{$date}{hours}; |
|---|
| 263 |
$f_note = join("; ", |
|---|
| 264 |
@{$r{$topLevel}{$client}{$matter}{$user}{$date}{notes}}); |
|---|
| 265 |
write DETAIL_REPORT; |
|---|
| 266 |
|
|---|
| 267 |
$userTotal += |
|---|
| 268 |
$r{$topLevel}{$client}{$matter}{$user}{$date}{hours}; |
|---|
| 269 |
|
|---|
| 270 |
$userFirst = $matterFirst = $clientFirst = 0; |
|---|
| 271 |
} # date |
|---|
| 272 |
$matterTotal += $userTotal; |
|---|
| 273 |
|
|---|
| 274 |
print DETAIL_REPORT "\n"; $---; |
|---|
| 275 |
$f_date = $f_hours = $f_client = $f_matter = $f_user = $f_note = ""; |
|---|
| 276 |
# $f_client = $client; $f_matter = $matter; $f_user = $user; |
|---|
| 277 |
$f_date = "TOTAL"; |
|---|
| 278 |
$f_hours = $userTotal; |
|---|
| 279 |
write DETAIL_REPORT; |
|---|
| 280 |
|
|---|
| 281 |
|
|---|
| 282 |
} # user |
|---|
| 283 |
$clientTotal += $matterTotal; |
|---|
| 284 |
|
|---|
| 285 |
print DETAIL_REPORT "\n"; $---; |
|---|
| 286 |
$f_date = $f_hours = $f_client = $f_matter = $f_user = $f_note = ""; |
|---|
| 287 |
# $f_client = $client; $f_matter = $matter; |
|---|
| 288 |
$f_user = "TOTAL"; |
|---|
| 289 |
$f_hours = $matterTotal; |
|---|
| 290 |
write DETAIL_REPORT; |
|---|
| 291 |
print DETAIL_REPORT "\n"; $---; |
|---|
| 292 |
|
|---|
| 293 |
} #matter |
|---|
| 294 |
$topLevelTotal += $clientTotal; |
|---|
| 295 |
|
|---|
| 296 |
print DETAIL_REPORT "\n"; $---; |
|---|
| 297 |
$f_date = $f_hours = $f_client = $f_matter = $f_user = $f_note = ""; |
|---|
| 298 |
# $f_client = $client; |
|---|
| 299 |
$f_matter = "TOTAL"; |
|---|
| 300 |
$f_hours = $clientTotal; |
|---|
| 301 |
write DETAIL_REPORT; |
|---|
| 302 |
print DETAIL_REPORT "\n"; $---; |
|---|
| 303 |
|
|---|
| 304 |
|
|---|
| 305 |
} #client |
|---|
| 306 |
$overallTotal += $topLevelTotal; |
|---|
| 307 |
$overallNoOther += $topLevelTotal unless $topLevel =~ /^other/; |
|---|
| 308 |
|
|---|
| 309 |
$f_date = $f_hours = $f_client = $f_matter = $f_user = $f_note = ""; |
|---|
| 310 |
print DETAIL_REPORT "\n"; $---; |
|---|
| 311 |
$f_client = "TOT-$topLevel"; |
|---|
| 312 |
$f_hours = $topLevelTotal; |
|---|
| 313 |
write DETAIL_REPORT; |
|---|
| 314 |
|
|---|
| 315 |
} #toplevel |
|---|
| 316 |
if ($overallTotal != $overallNoOther) { |
|---|
| 317 |
print DETAIL_REPORT "\n"; $---; |
|---|
| 318 |
$f_date = $f_hours = $f_client = $f_matter = $f_user = $f_note = ""; |
|---|
| 319 |
$f_client = "TOT-NOOTHR"; |
|---|
| 320 |
$f_hours = $overallNoOther; |
|---|
| 321 |
write DETAIL_REPORT; |
|---|
| 322 |
} |
|---|
| 323 |
|
|---|
| 324 |
print DETAIL_REPORT "\n"; $---; |
|---|
| 325 |
$f_date = $f_hours = $f_client = $f_matter = $f_user = $f_note = ""; |
|---|
| 326 |
$f_client = "TOTAL ALL"; |
|---|
| 327 |
$f_hours = $overallTotal; |
|---|
| 328 |
write DETAIL_REPORT; |
|---|
| 329 |
print DETAIL_REPORT "\n"; $---; |
|---|
| 330 |
|
|---|
| 331 |
close(DETAIL_REPORT); |
|---|
| 332 |
} |
|---|
| 333 |
############################################################################### |
|---|
| 334 |
# $startDate and $endDate should already be in Date::Manip format |
|---|
| 335 |
sub Summary ($$$$$$$$) { |
|---|
| 336 |
my($db, $i_user, $i_startDate, $i_endDate, $i_client, $i_matter, $email, |
|---|
| 337 |
$emailNote) = @_; |
|---|
| 338 |
|
|---|
| 339 |
|
|---|
| 340 |
my $startPretty = UnixDate($i_startDate, '%F'); |
|---|
| 341 |
my $endPretty = UnixDate($i_endDate, '%F'); |
|---|
| 342 |
|
|---|
| 343 |
my $startSmall = UnixDate($i_startDate, "%Y-%m-%d"); |
|---|
| 344 |
my $endSmall = UnixDate($i_endDate, "%Y-%m-%d"); |
|---|
| 345 |
|
|---|
| 346 |
open(SUMMARY_REPORT, "|/usr/sbin/sendmail -f time\@example.org -t"); |
|---|
| 347 |
# open(SUMMARY_REPORT, ">$i_user.report"); |
|---|
| 348 |
print SUMMARY_REPORT <<HEADER; |
|---|
| 349 |
To: $email |
|---|
| 350 |
From: Tim <time\@example.org> |
|---|
| 351 |
Subject: Time Summary: $startSmall to $endSmall |
|---|
| 352 |
|
|---|
| 353 |
$emailNote |
|---|
| 354 |
Time Summary Report |
|---|
| 355 |
Report Criteria |
|---|
| 356 |
Start Date: $startPretty |
|---|
| 357 |
End Date: $endPretty |
|---|
| 358 |
Client: $i_client |
|---|
| 359 |
Matter: $i_matter |
|---|
| 360 |
Employee: $i_user |
|---|
| 361 |
|
|---|
| 362 |
|
|---|
| 363 |
Client Matter Employee Hours Notes |
|---|
| 364 |
------------------------------------------------------------------------------- |
|---|
| 365 |
HEADER |
|---|
| 366 |
|
|---|
| 367 |
|
|---|
| 368 |
my(%r) = _BuildDataStructure($db, $i_user, $i_startDate, $i_endDate, |
|---|
| 369 |
$i_client, $i_matter); |
|---|
| 370 |
my($overallTotal, $overallNoOther) = (0.0, 0.0); |
|---|
| 371 |
$f_date = $f_hours = $f_client = $f_matter = $f_user = $f_note = ""; |
|---|
| 372 |
foreach my $topLevel (sort { return -1 if $a eq "legal"; |
|---|
| 373 |
return 1 if $b eq 'legal'; |
|---|
| 374 |
return 1 if $a eq 'other'; |
|---|
| 375 |
return -1 if $b eq 'other'; |
|---|
| 376 |
return $a cmp $b; } |
|---|
| 377 |
keys %r) { |
|---|
| 378 |
my $topLevelTotal = 0.0; |
|---|
| 379 |
foreach my $client (sort { |
|---|
| 380 |
return -1 if ($a =~ /^(?:admin|tech)/ and |
|---|
| 381 |
$b !~ /^(?:admin|tech)/); |
|---|
| 382 |
return 1 if ($b =~ /^(?:admin|tech)/ and |
|---|
| 383 |
$a !~ /^(?:admin|tech)/); |
|---|
| 384 |
return $a cmp $b; |
|---|
| 385 |
} keys %{$r{$topLevel}}) { |
|---|
| 386 |
my $clientTotal = 0.0; |
|---|
| 387 |
my $clientFirst = 1; |
|---|
| 388 |
foreach my $matter (sort { $a cmp $b } |
|---|
| 389 |
keys %{$r{$topLevel}{$client}}) { |
|---|
| 390 |
my $matterTotal = 0.0; |
|---|
| 391 |
my $matterFirst = 1; |
|---|
| 392 |
$clientFirst = 1; |
|---|
| 393 |
foreach my $user |
|---|
| 394 |
(sort { $a cmp $b } keys %{$r{$topLevel}{$client}{$matter}}) { |
|---|
| 395 |
my $userTotal = 0.0; |
|---|
| 396 |
my $userFirst = 1; |
|---|
| 397 |
my $bigNote = ""; |
|---|
| 398 |
foreach my $date (keys %{$r{$topLevel}{$client}{$matter}{$user}}) { |
|---|
| 399 |
foreach my $thisNote ( |
|---|
| 400 |
@{$r{$topLevel}{$client}{$matter}{$user}{$date}{notes}}) { |
|---|
| 401 |
my $quotedThisNote = quotemeta $thisNote; |
|---|
| 402 |
$bigNote .= ($bigNote eq "") ? $thisNote : "; $thisNote" |
|---|
| 403 |
if $thisNote !~ /^\s*$/ and $bigNote !~ /$quotedThisNote/i; |
|---|
| 404 |
} |
|---|
| 405 |
$userTotal += |
|---|
| 406 |
$r{$topLevel}{$client}{$matter}{$user}{$date}{hours}; |
|---|
| 407 |
|
|---|
| 408 |
} # date |
|---|
| 409 |
$matterTotal += $userTotal; |
|---|
| 410 |
|
|---|
| 411 |
$f_matter = $matterFirst ? $matter : ""; |
|---|
| 412 |
my $len = length($f_matter); |
|---|
| 413 |
$f_matter = ($len <= $F_MATTER_LEN_SUMMARY) ? $f_matter |
|---|
| 414 |
: ( substr($f_matter, 0, 2) . "..." . |
|---|
| 415 |
substr($f_matter, |
|---|
| 416 |
$len - ($F_MATTER_LEN_SUMMARY - 4),$len)); |
|---|
| 417 |
|
|---|
| 418 |
$f_client = $clientFirst ? $client : ""; |
|---|
| 419 |
$f_user = $user; |
|---|
| 420 |
$f_hours = $userTotal; |
|---|
| 421 |
$f_note = $bigNote; |
|---|
| 422 |
write SUMMARY_REPORT; |
|---|
| 423 |
|
|---|
| 424 |
$userFirst = $matterFirst = $clientFirst = 0; |
|---|
| 425 |
} # user |
|---|
| 426 |
$clientTotal += $matterTotal; |
|---|
| 427 |
|
|---|
| 428 |
print SUMMARY_REPORT "\n"; $---; |
|---|
| 429 |
$f_date = $f_hours = $f_client = $f_matter = $f_user = $f_note = ""; |
|---|
| 430 |
# $f_client = $client; $f_matter = $matter; |
|---|
| 431 |
$f_user = "TOTAL"; |
|---|
| 432 |
$f_hours = $matterTotal; |
|---|
| 433 |
write SUMMARY_REPORT; |
|---|
| 434 |
print SUMMARY_REPORT "\n"; $---; |
|---|
| 435 |
|
|---|
| 436 |
} #matter |
|---|
| 437 |
$topLevelTotal += $clientTotal; |
|---|
| 438 |
|
|---|
| 439 |
$f_date = $f_hours = $f_client = $f_matter = $f_user = $f_note = ""; |
|---|
| 440 |
# $f_client = $client; |
|---|
| 441 |
$f_matter = "TOTAL"; |
|---|
| 442 |
$f_hours = $clientTotal; |
|---|
| 443 |
write SUMMARY_REPORT; |
|---|
| 444 |
print SUMMARY_REPORT "\n"; $---; |
|---|
| 445 |
|
|---|
| 446 |
|
|---|
| 447 |
} #client |
|---|
| 448 |
$overallTotal += $topLevelTotal; |
|---|
| 449 |
$overallNoOther += $topLevelTotal unless $topLevel =~ /^other/; |
|---|
| 450 |
|
|---|
| 451 |
$f_date = $f_hours = $f_client = $f_matter = $f_user = $f_note = ""; |
|---|
| 452 |
print SUMMARY_REPORT "\n"; $---; |
|---|
| 453 |
$f_client = "TOT-$topLevel"; |
|---|
| 454 |
$f_hours = $topLevelTotal; |
|---|
| 455 |
write SUMMARY_REPORT; |
|---|
| 456 |
print SUMMARY_REPORT "\n"; $---; |
|---|
| 457 |
|
|---|
| 458 |
} #toplevel |
|---|
| 459 |
if ($overallTotal != $overallNoOther) { |
|---|
| 460 |
print SUMMARY_REPORT "\n"; $---; |
|---|
| 461 |
$f_date = $f_hours = $f_client = $f_matter = $f_user = $f_note = ""; |
|---|
| 462 |
$f_client = "TOT-NOOTHR"; |
|---|
| 463 |
$f_hours = $overallNoOther; |
|---|
| 464 |
write SUMMARY_REPORT; |
|---|
| 465 |
} |
|---|
| 466 |
|
|---|
| 467 |
print SUMMARY_REPORT "\n"; $---; |
|---|
| 468 |
$f_date = $f_hours = $f_client = $f_matter = $f_user = $f_note = ""; |
|---|
| 469 |
$f_client = "TOTAL ALL"; |
|---|
| 470 |
$f_hours = $overallTotal; |
|---|
| 471 |
write SUMMARY_REPORT; |
|---|
| 472 |
print SUMMARY_REPORT "\n"; $---; |
|---|
| 473 |
|
|---|
| 474 |
close(SUMMARY_REPORT); |
|---|
| 475 |
} |
|---|
| 476 |
############################################################################### |
|---|
| 477 |
sub _FormatDeltaNice ($) { |
|---|
| 478 |
my($amountTime) = @_; |
|---|
| 479 |
|
|---|
| 480 |
my $formatted = (defined $amountTime) ? |
|---|
| 481 |
Delta_Format($amountTime, 1, |
|---|
| 482 |
'%ht ' . PL_N("hour", $amountTime)) : ""; |
|---|
| 483 |
# If we are going to say "0.0 hours, just redo it in minutes |
|---|
| 484 |
if ($formatted =~ /^\s*0.0+\s+hour/) { |
|---|
| 485 |
$formatted = Delta_Format($amountTime, 1, |
|---|
| 486 |
'%mt ' . PL_N("minute", $amountTime)); |
|---|
| 487 |
} |
|---|
| 488 |
# If we are going to say "0.0 minutes, just redo it in seconds |
|---|
| 489 |
if ($formatted =~ /^\s*0.0+\s+minute/) { |
|---|
| 490 |
$formatted = Delta_Format($amountTime, 2, |
|---|
| 491 |
'%st ' . PL_N("second", $amountTime)); |
|---|
| 492 |
} |
|---|
| 493 |
$formatted =~ s/s\s*$// if ($formatted =~ /^\s*1.0\s+/); |
|---|
| 494 |
$formatted = "none" if $formatted =~ /^\s*$/; |
|---|
| 495 |
|
|---|
| 496 |
|
|---|
| 497 |
return $formatted; |
|---|
| 498 |
} |
|---|
| 499 |
############################################################################### |
|---|
| 500 |
sub QuickSummary ($$$$$$$$$) { |
|---|
| 501 |
my($db, $userHandle, $startDate, $endDate, $verbose, $email, $now, |
|---|
| 502 |
$noteFilter, $showPending) = @_; |
|---|
| 503 |
|
|---|
| 504 |
my @answers; |
|---|
| 505 |
my $iWannaBeSedated = DateCalc($now, "24 hours ago"); |
|---|
| 506 |
|
|---|
| 507 |
for (my $day = $startDate; $day le $endDate; |
|---|
| 508 |
$day = DateCalc($day, "+1 day")) { |
|---|
| 509 |
push(@answers, "\n\n") if $email and $day ne $startDate; |
|---|
| 510 |
push(@answers, "Status for " . UnixDate($day, ' %a, %b %E') . ":\n"); |
|---|
| 511 |
|
|---|
| 512 |
my(@completed) = $db->getEntriesOnDateInDB('main', $userHandle, $day); |
|---|
| 513 |
my %cats; |
|---|
| 514 |
foreach my $entry (sort { $a->{id} cmp $b->{id} } @completed) { |
|---|
| 515 |
my $category = $entry->get('category')->prettyPrint(); |
|---|
| 516 |
my $amount = $entry->get('amountTime'); |
|---|
| 517 |
$cats{$category} = '+0:0:0:0:0:0:0' |
|---|
| 518 |
if not defined $cats{$category}; |
|---|
| 519 |
|
|---|
| 520 |
my $note = $entry->get('note'); |
|---|
| 521 |
next if (defined $noteFilter and |
|---|
| 522 |
(not defined $note or $note !~ /$noteFilter/)); |
|---|
| 523 |
|
|---|
| 524 |
|
|---|
| 525 |
$cats{$category} = DateCalc($cats{$category}, $amount); |
|---|
| 526 |
if ($verbose) { |
|---|
| 527 |
push(@answers, " ", $entry->get('id'), ": ", |
|---|
| 528 |
_FormatDeltaNice($amount), "in $category" . |
|---|
| 529 |
((defined $note and $note !~ /^\s*$/) ? ", note: $note" : "") . "\n"); |
|---|
| 530 |
} |
|---|
| 531 |
} |
|---|
| 532 |
my $total = '+0:0:0:0:0:0:0'; |
|---|
| 533 |
foreach my $cat (sort {$a cmp $b} keys %cats) { |
|---|
| 534 |
push(@answers, sprintf(" %s: %s\n", $cat, |
|---|
| 535 |
_FormatDeltaNice($cats{$cat}))) |
|---|
| 536 |
if not $verbose; |
|---|
| 537 |
$total = DateCalc($total, $cats{$cat}); |
|---|
| 538 |
} |
|---|
| 539 |
push(@answers, "\n") if $email; |
|---|
| 540 |
push(@answers, ($total eq '+0:0:0:0:0:0:0') |
|---|
| 541 |
? " No completed entries.\n" : |
|---|
| 542 |
(" Total time completed is " . _FormatDeltaNice($total) |
|---|
| 543 |
. "\n")); |
|---|
| 544 |
} |
|---|
| 545 |
|
|---|
| 546 |
if ($showPending) { |
|---|
| 547 |
my $pending = $db->getPendingEntriesByUserHandle($userHandle); |
|---|
| 548 |
|
|---|
| 549 |
my(@pending) = values %{$pending}; |
|---|
| 550 |
if (@pending > 0) { |
|---|
| 551 |
push(@answers, "\n\n") if $email; |
|---|
| 552 |
push(@answers, "Pending Entries as of " . |
|---|
| 553 |
UnixDate($now, '%i:%M%p (on %b %E)') . ": \n"); |
|---|
| 554 |
} |
|---|
| 555 |
foreach my $entry (sort { $a->{id} cmp $b->{id} } @pending) { |
|---|
| 556 |
my $category = $entry->get('category'); |
|---|
| 557 |
my $startTime = $entry->get('startTime'); |
|---|
| 558 |
my $endTime = $entry->get('endTime'); |
|---|
| 559 |
my $time = $startTime; |
|---|
| 560 |
if (defined $time) { |
|---|
| 561 |
my $oldTime = $time; |
|---|
| 562 |
$time = UnixDate($oldTime, 'started at %i:%M%p'); |
|---|
| 563 |
$time .= UnixDate($oldTime, ' (on %b %E)') |
|---|
| 564 |
if $email or ($oldTime lt $iWannaBeSedated); |
|---|
| 565 |
} else { |
|---|
| 566 |
$time = $entry->get('amountTime'); |
|---|
| 567 |
$time = "that lasted for " . _FormatDeltaNice($time); |
|---|
| 568 |
} |
|---|
| 569 |
my $answer = " "; |
|---|
| 570 |
$answer .= ($entry->{id} . ": ") if ($verbose); |
|---|
| 571 |
$answer = " Pending entry $time"; |
|---|
| 572 |
$answer .= $entry->needs('category') ? |
|---|
| 573 |
" needs a category" : (" in " . $category->prettyPrint); |
|---|
| 574 |
$answer .= $entry->needs('endTime') ? ' is still running.' : "."; |
|---|
| 575 |
push(@answers, "$answer\n"); |
|---|
| 576 |
} |
|---|
| 577 |
} |
|---|
| 578 |
if ($email) { |
|---|
| 579 |
my $startPretty = UnixDate($startDate, '%F'); |
|---|
| 580 |
my $endPretty = UnixDate($endDate, '%F'); |
|---|
| 581 |
|
|---|
| 582 |
my $startSmall = UnixDate($startDate, "%Y-%m-%d"); |
|---|
| 583 |
my $endSmall = UnixDate($endDate, "%Y-%m-%d"); |
|---|
| 584 |
|
|---|
| 585 |
open(QUICK_SUMMARY_REPORT, |
|---|
| 586 |
"|/usr/sbin/sendmail -f time\@example.org -t"); |
|---|
| 587 |
print QUICK_SUMMARY_REPORT <<HEADER; |
|---|
| 588 |
To: $userHandle\@example.org |
|---|
| 589 |
From: Tim <time\@example.org> |
|---|
| 590 |
Subject: Time Status Report: $startSmall to $endSmall |
|---|
| 591 |
|
|---|
| 592 |
Time Status Report |
|---|
| 593 |
Report Criteria |
|---|
| 594 |
Start Date: $startPretty |
|---|
| 595 |
End Date: $endPretty |
|---|
| 596 |
HEADER |
|---|
| 597 |
|
|---|
| 598 |
print QUICK_SUMMARY_REPORT " Note Filter: $noteFilter\n" if defined $noteFilter; |
|---|
| 599 |
print QUICK_SUMMARY_REPORT "\n\n", @answers, "\n"; |
|---|
| 600 |
close QUICK_SUMMARY_REPORT; |
|---|
| 601 |
my $msg = "Your status report " . ( ($startPretty eq $endPretty) ? |
|---|
| 602 |
"for $startPretty" : |
|---|
| 603 |
"for the dates from $startPretty to $endPretty (inclusive)" ) . |
|---|
| 604 |
( (defined $noteFilter) ? " with note filter, \"$noteFilter\", " :"") . |
|---|
| 605 |
" has been emailed to you."; |
|---|
| 606 |
(@answers) = ($? == 0) ? ($msg) |
|---|
| 607 |
: ("REPORT_THIS: An unexpected error ($!) prohibited me " |
|---|
| 608 |
. "from sending your status report via email"); |
|---|
| 609 |
} |
|---|
| 610 |
return @answers; |
|---|
| 611 |
} |
|---|
| 612 |
|
|---|
| 613 |
############################################################################### |
|---|
| 614 |
sub CategoryTotals ($$$$$$;$$) { |
|---|
| 615 |
my($db, $i_user, $i_startDate, $i_endDate, $email, $emailNote, $i_client, |
|---|
| 616 |
$i_matter) = @_; |
|---|
| 617 |
|
|---|
| 618 |
$i_client = 'ALL' if (not defined $i_client); |
|---|
| 619 |
$i_matter = 'ALL' if (not defined $i_matter); |
|---|
| 620 |
|
|---|
| 621 |
my(%r) = _BuildDataStructure($db, $i_user, $i_startDate, $i_endDate, |
|---|
| 622 |
$i_client, $i_matter); |
|---|
| 623 |
my %userCats; |
|---|
| 624 |
|
|---|
| 625 |
my $overheadCatsRE = '^(?:/admin/overhead)'; |
|---|
| 626 |
my $ignoredCatsRE = '^/other'; |
|---|
| 627 |
|
|---|
| 628 |
my $startPretty = UnixDate($i_startDate, '%F'); |
|---|
| 629 |
my $endPretty = UnixDate($i_endDate, '%F'); |
|---|
| 630 |
|
|---|
| 631 |
my $startSmall = UnixDate($i_startDate, "%Y-%m-%d"); |
|---|
| 632 |
my $endSmall = UnixDate($i_endDate, "%Y-%m-%d"); |
|---|
| 633 |
|
|---|
| 634 |
if (defined $email) { |
|---|
| 635 |
open(CAT_REPORT, "|/usr/sbin/sendmail -f time\@example.org -t"); |
|---|
| 636 |
} else { |
|---|
| 637 |
*CAT_REPORT = *STDOUT; |
|---|
| 638 |
} |
|---|
| 639 |
$emailNote = "" unless defined $emailNote; |
|---|
| 640 |
|
|---|
| 641 |
print CAT_REPORT <<HEADER; |
|---|
| 642 |
To: $email |
|---|
| 643 |
From: Tim <time\@example.org> |
|---|
| 644 |
Subject: CategoryReport: $startSmall to $endSmall |
|---|
| 645 |
|
|---|
| 646 |
$emailNote |
|---|
| 647 |
Category Totals Report |
|---|
| 648 |
Report Criteria |
|---|
| 649 |
Start Date: $startPretty |
|---|
| 650 |
End Date: $endPretty |
|---|
| 651 |
Client: $i_client |
|---|
| 652 |
Matter: $i_matter |
|---|
| 653 |
HEADER |
|---|
| 654 |
foreach my $topLevel(keys %r) { |
|---|
| 655 |
foreach my $client (keys %{$r{$topLevel}}) { |
|---|
| 656 |
foreach my $matter (keys %{$r{$topLevel}{$client}}) { |
|---|
| 657 |
foreach my $user (keys %{$r{$topLevel}{$client}{$matter}}) { |
|---|
| 658 |
foreach my $date (keys %{$r{$topLevel}{$client}{$matter}{$user}}) { |
|---|
| 659 |
my $cat = "/$topLevel"; |
|---|
| 660 |
$cat .= "/$client" unless $topLevel eq $client; |
|---|
| 661 |
$cat .= "/$matter"; |
|---|
| 662 |
$userCats{$user}{$cat} = 0 unless defined $userCats{$user}{$cat}; |
|---|
| 663 |
$userCats{$user}{$cat} += |
|---|
| 664 |
$r{$topLevel}{$client}{$matter}{$user}{$date}{hours}; |
|---|
| 665 |
if ($cat =~ m%$overheadCatsRE%) { |
|---|
| 666 |
$userCats{$user}{'__TOTAL_OVERHEAD__'} += |
|---|
| 667 |
$r{$topLevel}{$client}{$matter}{$user}{$date}{hours}; |
|---|
| 668 |
} elsif ($cat =~ m%$ignoredCatsRE%) { |
|---|
| 669 |
$userCats{$user}{'__TOTAL_TIME_OFF__'} += |
|---|
| 670 |
$r{$topLevel}{$client}{$matter}{$user}{$date}{hours}; |
|---|
| 671 |
} else { |
|---|
| 672 |
$userCats{$user}{'__TOTAL_PROGRAM_ACTIVITY__'} += |
|---|
| 673 |
$r{$topLevel}{$client}{$matter}{$user}{$date}{hours}; |
|---|
| 674 |
} |
|---|
| 675 |
} |
|---|
| 676 |
} |
|---|
| 677 |
} |
|---|
| 678 |
} |
|---|
| 679 |
} |
|---|
| 680 |
|
|---|
| 681 |
my %everyone; |
|---|
| 682 |
|
|---|
| 683 |
my $ret = ""; |
|---|
| 684 |
foreach my $user (sort { $b cmp $a } keys %userCats) { |
|---|
| 685 |
$ret .= "\n\n$user:\n"; |
|---|
| 686 |
my @finalCats; |
|---|
| 687 |
foreach my $cat (sort { $userCats{$user}{$b} <=> $userCats{$user}{$a} } |
|---|
| 688 |
keys %{$userCats{$user}}) { |
|---|
| 689 |
$everyone{$cat} = 0 unless defined $everyone{$cat}; |
|---|
| 690 |
$everyone{$cat} += $userCats{$user}{$cat}; |
|---|
| 691 |
if ($cat =~ /^__TOTAL/) { |
|---|
| 692 |
push(@finalCats, $cat); |
|---|
| 693 |
next; |
|---|
| 694 |
} |
|---|
| 695 |
$ret .= sprintf(" %-60.60s: %7.1f\n", $cat, $userCats{$user}{$cat}); |
|---|
| 696 |
} |
|---|
| 697 |
$ret .= "\n"; |
|---|
| 698 |
foreach my $cat (sort @finalCats) { |
|---|
| 699 |
$ret .= sprintf(" %-60.60s: %7.1f\n", $cat, $userCats{$user}{$cat}); |
|---|
| 700 |
} |
|---|
| 701 |
} |
|---|
| 702 |
$ret .= "\n\nEVERYONE:\n"; |
|---|
| 703 |
my @finalCats; |
|---|
| 704 |
foreach my $cat (sort { $everyone{$b} <=> $everyone{$a}} keys %everyone) { |
|---|
| 705 |
if ($cat =~ /^__TOTAL/) { |
|---|
| 706 |
push(@finalCats, $cat); |
|---|
| 707 |
next; |
|---|
| 708 |
} |
|---|
| 709 |
$ret .= sprintf(" %-60.60s: %7.1f\n", $cat, $everyone{$cat}); |
|---|
| 710 |
} |
|---|
| 711 |
$ret .= "\n"; |
|---|
| 712 |
foreach my $cat (sort @finalCats) { |
|---|
| 713 |
$ret .= sprintf(" %-60.60s: %7.1f\n", $cat, $everyone{$cat}); |
|---|
| 714 |
} |
|---|
| 715 |
print CAT_REPORT $ret; |
|---|
| 716 |
} |
|---|
| 717 |
############################################################################### |
|---|
| 718 |
sub PercentageOfMonth ($$) { |
|---|
| 719 |
my($startDate, $endDate) = @_; |
|---|
| 720 |
my $monthYear = UnixDate($startDate, "%B %Y"); |
|---|
| 721 |
my $isoDateTime = UnixDate($startDate, "%Y-%m-%d"); |
|---|
| 722 |
my $firstDate = ParseDate(UnixDate($startDate, "%Y-%m-01")); |
|---|
| 723 |
die "PercentageOfMonth requires two dates in same month: $startDate, $endDate" |
|---|
| 724 |
unless ($monthYear eq UnixDate($endDate, "%B %Y")); |
|---|
| 725 |
my $daysInPeriod = Delta_Format(DateCalc(ParseDate($isoDateTime), |
|---|
| 726 |
ParseDate("last day in $monthYear")), 0, "%dt"); |
|---|
| 727 |
my $daysInMonth = Delta_Format(DateCalc($firstDate, |
|---|
| 728 |
ParseDate("last day in $monthYear")),0, "%dt"); |
|---|
| 729 |
return $daysInPeriod / $daysInMonth; |
|---|
| 730 |
} |
|---|
| 731 |
############################################################################### |
|---|
| 732 |
#FIXME: EVIL EVIL EVIL HARD CODING! It makes the baby Jesus cry. For the love |
|---|
| 733 |
# of all that is holy, put this in LDAP! |
|---|
| 734 |
my %START_DATES = (user1 => '2001-01-01', user2 => '2002-02-05'); |
|---|
| 735 |
|
|---|
| 736 |
my %PART_TIME_BOUNDRIES = (user2 => [ { startDate => ParseDate('2003-03-01'), |
|---|
| 737 |
endDate => ParseDate('2004-01-15'), |
|---|
| 738 |
percentage => 0.5 } |
|---|
| 739 |
]); |
|---|
| 740 |
my $FIRST_TWO_YEAR_ACCURAL = (13 * 8)/ 12; |
|---|
| 741 |
my $THEREAFER_ACCURAL = (18 * 8)/ 12; |
|---|
| 742 |
my $MAXIMUM_BANKED = 160; |
|---|
| 743 |
############################################################################### |
|---|
| 744 |
sub PartTimeCheckAndCalc ($$$) { |
|---|
| 745 |
# $currentDate is assumed to be in Date::Manip format |
|---|
| 746 |
my($user, $currentDate, $accuralAmount) = @_; |
|---|
| 747 |
# FIXME: this doesn't implement part-time months properly!!!! |
|---|
| 748 |
# It should technically figure out |
|---|
| 749 |
my $percentage = 1; |
|---|
| 750 |
foreach my $period (@{$PART_TIME_BOUNDRIES{$user}}) { |
|---|
| 751 |
if ($currentDate ge $period->{startDate}) { |
|---|
| 752 |
if ($currentDate le $period->{endDate}) { |
|---|
| 753 |
return $period->{percentage} * $accuralAmount; |
|---|
| 754 |
} elsif (UnixDate($currentDate, "%Y-%m") eq |
|---|
| 755 |
UnixDate($period->{endDate}, "%Y-%m")) { |
|---|
| 756 |
my $percentFullTime = PercentageOfMonth($period->{endDate}, |
|---|
| 757 |
$currentDate); |
|---|
| 758 |
return ($percentFullTime * $accuralAmount) + |
|---|
| 759 |
($period->{percentage} * (1.0 - $percentFullTime) * $accuralAmount); |
|---|
| 760 |
} |
|---|
| 761 |
} |
|---|
| 762 |
} |
|---|
| 763 |
return ($percentage * $accuralAmount); |
|---|
| 764 |
} |
|---|
| 765 |
############################################################################### |
|---|
| 766 |
sub Vacation ($$$$$) { |
|---|
| 767 |
my($db, $i_user, $i_endDate, $email, $emailNote) = @_; |
|---|
| 768 |
|
|---|
| 769 |
my $endPretty = UnixDate($i_endDate, '%F'); |
|---|
| 770 |
my $endSmall = UnixDate($i_endDate, "%Y-%m-%d"); |
|---|
| 771 |
|
|---|
| 772 |
if (defined $email) { |
|---|
| 773 |
open(VAC_REPORT, "|/usr/sbin/sendmail -f time\@example.org -t"); |
|---|
| 774 |
} else { |
|---|
| 775 |
*VAC_REPORT = *STDOUT; |
|---|
| 776 |
$email = "STDOUT"; |
|---|
| 777 |
} |
|---|
| 778 |
$emailNote = "" unless defined $emailNote; |
|---|
| 779 |
|
|---|
| 780 |
print VAC_REPORT <<HEADER; |
|---|
| 781 |
To: $email |
|---|
| 782 |
From: Tim <time\@example.org> |
|---|
| 783 |
Subject: Time Off Remaining Report: ending $endSmall |
|---|
| 784 |
|
|---|
| 785 |
$emailNote |
|---|
| 786 |
Time-Off Used/Remaining Report |
|---|
| 787 |
Report Criteria |
|---|
| 788 |
Employee: $i_user |
|---|
| 789 |
End Date: $endPretty |
|---|
| 790 |
|
|---|
| 791 |
|
|---|
| 792 |
HEADER |
|---|
| 793 |
|
|---|
| 794 |
my %accruedMatrix; |
|---|
| 795 |
my %totalsWithoutExpiration; |
|---|
| 796 |
my %twoYearMark; |
|---|
| 797 |
my(@users) = ($i_user eq "ALL") ? $db->getUserList() : ($i_user); |
|---|
| 798 |
my %users; @users{@users} = @users; |
|---|
| 799 |
delete $users{'aaronw'}; |
|---|
| 800 |
#I am evil |
|---|
| 801 |
@users = keys %users; |
|---|
| 802 |
|
|---|
| 803 |
# Normalize start dates |
|---|
| 804 |
foreach my $user (@users) { |
|---|
| 805 |
$START_DATES{$user} = ParseDate($START_DATES{$user}); |
|---|
| 806 |
my $err; |
|---|
| 807 |
my $x = $twoYearMark{$user}{dateManipFormat} = |
|---|
| 808 |
DateCalc($START_DATES{$user}, "+ 2 years", \$err); |
|---|
| 809 |
$twoYearMark{$user}{yearMonth} = UnixDate($x, "%Y-%m"); |
|---|
| 810 |
$twoYearMark{$user}{yearMonthDay} = UnixDate($x, "%Y-%m-%d"); |
|---|
| 811 |
$totalsWithoutExpiration{$user} = 0.0; |
|---|
| 812 |
} |
|---|
| 813 |
my $err; |
|---|
| 814 |
|
|---|
| 815 |
for (my $lastDayOfMonth = ParseDate('last day in February 2000'), |
|---|
| 816 |
my $startOfMonth = ParseDate('2000-02-01'); |
|---|
| 817 |
$lastDayOfMonth le $i_endDate; |
|---|
| 818 |
$startOfMonth = DateCalc($startOfMonth, "+ 1 month", \$err), |
|---|
| 819 |
$lastDayOfMonth = ParseDate(UnixDate($startOfMonth, |
|---|
| 820 |
"last day in %B %Y"))) { |
|---|
| 821 |
my $yearMonth = UnixDate($lastDayOfMonth, "%Y-%m"); |
|---|
| 822 |
foreach my $user (@users) { |
|---|
| 823 |
my $startDateYearMonth = UnixDate($START_DATES{$user}, "%Y-%m"); |
|---|
| 824 |
if ($startDateYearMonth gt $yearMonth) { |
|---|
| 825 |
$totalsWithoutExpiration{$user} = |
|---|
| 826 |
$accruedMatrix{$lastDayOfMonth}{$user}{accruedThisMonth} = 0; |
|---|
| 827 |
next; |
|---|
| 828 |
} |
|---|
| 829 |
my $hoursToAdd = 0; |
|---|
| 830 |
if ($twoYearMark{$user}{yearMonth} lt $yearMonth) { |
|---|
| 831 |
$hoursToAdd = $THEREAFER_ACCURAL; |
|---|
| 832 |
} elsif ($twoYearMark{$user}{yearMonth} eq $yearMonth) { |
|---|
| 833 |
my $percent = $accruedMatrix{$lastDayOfMonth}{$user}{twoYearMark} = |
|---|
| 834 |
PercentageOfMonth($twoYearMark{$user}{dateManipFormat}, |
|---|
| 835 |
ParseDate(UnixDate( |
|---|
| 836 |
$twoYearMark{$user}{dateManipFormat}, "last day in %B %Y"))); |
|---|
| 837 |
$hoursToAdd = ($percent * |
|---|
| 838 |
($THEREAFER_ACCURAL - $FIRST_TWO_YEAR_ACCURAL)) |
|---|
| 839 |
+ $FIRST_TWO_YEAR_ACCURAL; |
|---|
| 840 |
} elsif ($startDateYearMonth eq $yearMonth) { |
|---|
| 841 |
$hoursToAdd = PercentageOfMonth($START_DATES{$user}, ParseDate( |
|---|
| 842 |
UnixDate($START_DATES{$user}, "last day in %B %Y"))) |
|---|
| 843 |
* $FIRST_TWO_YEAR_ACCURAL; |
|---|
| 844 |
} else { |
|---|
| 845 |
$hoursToAdd = $FIRST_TWO_YEAR_ACCURAL; |
|---|
| 846 |
} |
|---|
| 847 |
$hoursToAdd = PartTimeCheckAndCalc($user, $lastDayOfMonth, $hoursToAdd); |
|---|
| 848 |
$accruedMatrix{$lastDayOfMonth}{$user}{accruedThisMonth} = $hoursToAdd; |
|---|
| 849 |
$totalsWithoutExpiration{$user} += $hoursToAdd; |
|---|
| 850 |
} |
|---|
| 851 |
} |
|---|
| 852 |
my(%r) = _BuildDataStructure($db, $i_user, ParseDate('2000-02-01'), |
|---|
| 853 |
$i_endDate, 'ALL', 'ALL'); |
|---|
| 854 |
foreach my $topLevel(keys %r) { |
|---|
| 855 |
foreach my $client (keys %{$r{$topLevel}}) { |
|---|
| 856 |
foreach my $matter (keys %{$r{$topLevel}{$client}}) { |
|---|
| 857 |
foreach my $user (keys %{$r{$topLevel}{$client}{$matter}}) { |
|---|
| 858 |
foreach my $date (keys %{$r{$topLevel}{$client}{$matter}{$user}}) { |
|---|
| 859 |
my $cat = "/$topLevel"; |
|---|
| 860 |
$cat .= "/$client" unless $topLevel eq $client; |
|---|
| 861 |
$cat .= "/$matter"; |
|---|
| 862 |
next unless $cat =~ |
|---|
| 863 |
m%^(/other)?/admin/(?:vacation|personal|paid-time-off)%i; |
|---|
| 864 |
my $lastDayOfMonth =ParseDate(UnixDate($date,"last day in %B %Y")); |
|---|
| 865 |
$accruedMatrix{$lastDayOfMonth}{$user}{tookThisMonth} = 0.0 |
|---|
| 866 |
unless defined |
|---|
| 867 |
$accruedMatrix{$lastDayOfMonth}{$user}{tookThisMonth}; |
|---|
| 868 |
$accruedMatrix{$lastDayOfMonth}{$user}{tookThisMonth} += |
|---|
| 869 |
$r{$topLevel}{$client}{$matter}{$user}{$date}{hours}; |
|---|
| 870 |
$accruedMatrix{$lastDayOfMonth}{$user}{accruedThisMonth} -= |
|---|
| 871 |
$r{$topLevel}{$client}{$matter}{$user}{$date}{hours}; |
|---|
| 872 |
} |
|---|
| 873 |
} |
|---|
| 874 |
} |
|---|
| 875 |
} |
|---|
| 876 |
} |
|---|
| 877 |
my %userTotals; |
|---|
| 878 |
foreach my $lastDayOfMonth (sort keys %accruedMatrix) { |
|---|
| 879 |
foreach my $user (sort @users) { |
|---|
| 880 |
$userTotals{$user}{totalBalance} = 0.0 |
|---|
| 881 |
unless defined $userTotals{$user}{totalBalance}; |
|---|
| 882 |
my $monthYear = UnixDate($lastDayOfMonth, "%Y-%m"); |
|---|
| 883 |
my $accrual = $accruedMatrix{$lastDayOfMonth}{$user}{accruedThisMonth}; |
|---|
| 884 |
$userTotals{$user}{$monthYear}{usedOrEarnedThisMonth} = $accrual; |
|---|
| 885 |
$userTotals{$user}{totalBalance} += $accrual; |
|---|
| 886 |
$userTotals{$user}{$monthYear}{tookThisMonth} = |
|---|
| 887 |
$accruedMatrix{$lastDayOfMonth}{$user}{tookThisMonth}; |
|---|
| 888 |
if ($userTotals{$user}{totalBalance} > $MAXIMUM_BANKED and |
|---|
| 889 |
UnixDate($lastDayOfMonth, "%B") |
|---|
| 890 |
eq UnixDate($START_DATES{$user}, "%B")) { |
|---|
| 891 |
$userTotals{$user}{$monthYear}{truncatedBy} = $MAXIMUM_BANKED - |
|---|
| 892 |
$userTotals{$user}{totalBalance}; |
|---|
| 893 |
$userTotals{$user}{totalBalance} = $MAXIMUM_BANKED; |
|---|
| 894 |
} |
|---|
| 895 |
} |
|---|
| 896 |
} |
|---|
| 897 |
foreach my $user (sort keys %userTotals) { |
|---|
| 898 |
print VAC_REPORT "User: $user\n"; |
|---|
| 899 |
print VAC_REPORT "Start Date: ", UnixDate($START_DATES{$user}, |
|---|
| 900 |
"%Y-%m-%d"), "\n"; |
|---|
| 901 |
print VAC_REPORT "Time-Off Bank: ", sprintf("%.2f ", |
|---|
| 902 |
$userTotals{$user}{totalBalance}), |
|---|
| 903 |
PL("hour", $userTotals{$user}{totalBalance}); |
|---|
| 904 |
print VAC_REPORT "\n\nDetails (reverse chronological order by month):\n"; |
|---|
| 905 |
my $startMonth = UnixDate($START_DATES{$user}, "%Y-%m"); |
|---|
| 906 |
foreach my $month (sort { $b cmp $a } keys %{$userTotals{$user}}) { |
|---|
| 907 |
next if $month eq 'totalBalance' or $month lt $startMonth; |
|---|
| 908 |
my $val = $userTotals{$user}{$month}{usedOrEarnedThisMonth}; |
|---|
| 909 |
print VAC_REPORT " $month: ", sprintf("%8.2f ", abs($val)), |
|---|
| 910 |
PL("hour", $val), |
|---|
| 911 |
($val <= 0.00) ? " subtracted from" : " added to", |
|---|
| 912 |
" your time-off bank\n"; |
|---|
| 913 |
if (defined $userTotals{$user}{$month}{tookThisMonth}) { |
|---|
| 914 |
my $took = $userTotals{$user}{$month}{tookThisMonth}; |
|---|
| 915 |
print VAC_REPORT " (", sprintf("%8.2f ", |
|---|
| 916 |
abs($took)), |
|---|
| 917 |
PL("hour", $took), " taken)\n"; |
|---|
| 918 |
} |
|---|
| 919 |
if (defined $userTotals{$user}{$month}{truncatedBy}) { |
|---|
| 920 |
my $trunc = $userTotals{$user}{$month}{truncatedBy}; |
|---|
| 921 |
print VAC_REPORT " (", sprintf("%8.2f ", |
|---|
| 922 |
abs($trunc)), |
|---|
| 923 |
PL("hour", $trunc), " lost due to balance beyond $MAXIMUM_BANKED)\n"; |
|---|
| 924 |
} |
|---|
| 925 |
} |
|---|
| 926 |
print VAC_REPORT "\n\n", "=" x 77, "\n"; |
|---|
| 927 |
} |
|---|
| 928 |
} |
|---|
| 929 |
|
|---|
| 930 |
|
|---|
| 931 |
1; |
|---|
| 932 |
__END__ |
|---|
| 933 |
# Local variables: |
|---|
| 934 |
# compile-command: "perl -I ../../../Modules -c Reports.pm" |
|---|
| 935 |
# End: |
|---|