#! /usr/bin/perl # InfoCon Time Tracker # Copyright (c) 2007,8,13,14,15 Martin Schulze # # This program 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. # # This program 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 this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. use strict; use warnings; use DBI; use Getopt::Long; use Term::ReadLine; my $table = "stempel"; my $engine = "dbi:Pg:dbname=infocon"; my $dbh = DBI->connect($engine); die "Access to database denied!\n" unless $dbh; $dbh->do("SET DateStyle = 'ISO'"); sub min2hour { my $minutes = shift; return sprintf('%02d:%02d', $minutes/60, $minutes%60); } sub is_open { my $query = q{SELECT count(*) AS count FROM stempel WHERE stop IS NULL}; my $sth = $dbh->prepare ($query); if ($sth && $sth->execute > 0) { my $row = $sth->fetchrow_hashref; return $row->{count} > 0 ? 1 : 0; } return 0; } sub customerlist { my @res; my $query = q{SELECT DISTINCT customer FROM stempel WHERE start > now() - interval '1 year' ORDER BY customer}; my $sth = $dbh->prepare ($query); if ($sth && $sth->execute > 0) { while ((my $row = $sth->fetchrow_hashref)) { push @res, $row->{customer}; } } return \@res; } sub tasklist { my $customer = shift; my @res; my $query = q{SELECT DISTINCT task FROM stempel WHERE start > now() - interval '1 month' AND customer = ? ORDER BY task}; my $sth = $dbh->prepare($query); if ($sth && $sth->execute($customer) > 0) { while ((my $row = $sth->fetchrow_hashref)) { push @res, $row->{task}; } } return \@res; } sub complete_customer { my ($customer, $text, $line, $start) = @_; # return () unless exists $answers->{category} && length $answers->{category}; my $sql = sprintf("SELECT DISTINCT task FROM stempel WHERE start > now() - interval '1 month' AND customer = '%s' AND task LIKE '%s%%' ORDER BY task", $customer, $line); my $sth = $dbh->prepare($sql); $sth->execute; my @complete; while (my $row = $sth->fetchrow_hashref) { $row->{task} = substr $row->{task}, $start if $start; push @complete, $row->{task}; } return @complete; } sub open_task { my $term = new Term::ReadLine ''; my $customers = customerlist; my $attribs = $term->Attribs; $attribs->{completion_entry_function} = $attribs->{list_completion_function}; $attribs->{completion_word} = $customers; printf "[%s]\n", join (', ', @$customers); my $customer = $term->readline ('Kunde: '); if (defined $customer && length($customer) > 0) { $customer =~ s/\s*$//; my $tasks = tasklist $customer; $term->addhistory($_) foreach (@$tasks); $attribs->{completion_word} = $tasks; $attribs->{completion_entry_function} = undef; $term->{completion_function} = sub {return complete_customer $customer, @_}; while (1) { my $task = $term->readline ('Aufgabe: '); return unless length $task; return if $task eq 'q'; if ($task eq '?') { printf " %s\n", $_ foreach (@$tasks); next; } my $query = q{INSERT INTO stempel (start,customer,task) VALUES('now()',?,?)}; my $sth = $dbh->prepare($query); $sth->execute($customer, $task); last; } } } sub quarter { my $min = shift; return 15 if $min < 15; return $min - $min%15 if ($min%15 < 3); return $min + 15-$min%15; } sub hdiff { my ($sh, $sm, $eh, $em) = @_; if ($em >= $sm) { return ($eh-$sh)*60 + $em-$sm; } else { return ($eh-$sh)*60 - ($sm-$em); } } sub close_task { my ($d_sec,$d_min,$d_hour,$d_mday,$d_mon,$d_year,$d_wday,$d_isdst) = localtime(); my $query = q{SELECT oid,start FROM stempel WHERE stop IS NULL}; my $sth = $dbh->prepare ($query); if ($sth && $sth->execute > 0) { while ((my $row = $sth->fetchrow_hashref)) { my @arr = split(/ /, $row->{start}); my @d = split(/-/, $arr[0]); if ($d[0] != $d_year+1900 || $d[1] != $d_mon+1 || $d[2] != $d_mday) { printf "Task not started today, aborting.\n"; next; } my @t = split(/:/, $arr[1]); my $int = quarter(hdiff($t[0], $t[1], $d_hour, $d_min)); $query = sprintf('UPDATE stempel SET stop=now(),time=%d WHERE oid = %d', $int, $row->{oid}); $dbh->do ($query); printf "Wrote %s.\n", min2hour($int); } } exit(0); } sub delete_task { my $query = q{DELETE FROM stempel WHERE stop IS NULL}; $dbh->do ($query); exit(0); } sub reopen_task { my $sql = q{SELECT max(id) AS max_id FROM stempel}; my $sth = $dbh->prepare($sql); if ($sth && $sth->execute > 0) { my $row = $sth->fetchrow_hashref; $sql = sprintf("UPDATE stempel SET stop = NULL, time = NULL WHERE id = %d", $row->{max_id}); $dbh->do($sql); } exit(0); } sub list_open { my $exit = shift; my ($d_sec,$d_min,$d_hour,$d_mday,$d_mon,$d_year,$d_wday,$d_isdst) = localtime(); my $query = q{SELECT customer,start,task FROM stempel WHERE time IS NULL ORDER BY start,customer}; my $sth = $dbh->prepare ($query); if ($sth && $sth->execute > 0) { while ((my $row = $sth->fetchrow_hashref)) { my @arr = split(/ /, $row->{start}); my $day = $arr[0]; my @d = split(/-/, $day); if ($d[0] != $d_year+1900 || $d[1] != $d_mon+1 || $d[2] != $d_mday) { printf "Task not started today.\n"; next if $exit eq 1; } my @t = split(/:/, $arr[1]); my $time = sprintf('%02d:%02d', $t[0], $t[1]); my $int = quarter(hdiff($t[0], $t[1], $d_hour, $d_min)); printf "%-15s %s %s (%s) %s\n", $row->{customer}, $day, $time, min2hour($int), $row->{task}; } } exit 0 if $exit; } sub list { my $month = shift; my ($d_sec,$d_min,$d_hour,$d_mday,$d_mon,$d_year,$d_wday,$d_isdst) = localtime(); my $query = q{SELECT start,customer,time,task FROM stempel WHERE time IS NOT NULL }; if ($month =~ /^(\d{4})-?(\d\d?)$/) { my $pivot = $1 . '-' . $2 . '-01'; $query .= "AND start >= '$pivot' AND start <= '$pivot'::date + interval '1 month' "; } elsif ($month =~ /^(\d{4})$/) { my $pivot = $1 . '-01-01'; $query .= "AND start >= '$pivot' AND start <= '$pivot'::date + interval '1 year' "; } elsif ($month =~ /^(\d\d?)$/) { my $pivot = $d_year+1900 . '-' . $1 . '-01'; $query .= "AND start >= '$pivot' AND start <= '$pivot'::date + interval '1 month' "; } elsif ($month !~ /^all$/) { my $pivot = sprintf('%04d-%02d-01', $d_year+1900, $d_mon+1); $query .= "AND start >= '$pivot' AND start <= '$pivot'::date + interval '1 month' "; } $query .= "ORDER BY customer,start"; my $sth = $dbh->prepare ($query); if ($sth && $sth->execute > 0) { while ((my $row = $sth->fetchrow_hashref)) { if (defined $row->{time}) { my $day = (split(/ /, $row->{start}))[0]; my $time = min2hour $row->{time}; printf "%-15s %s %s %s\n", $row->{customer}, $day, $time, $row->{task}; } } } exit 0; } sub toggle_task { if (is_open) { list_open 1; } else { open_task; } } sub late_close_task { my $delta = shift; my ($d_sec,$d_min,$d_hour,$d_mday,$d_mon,$d_year,$d_wday,$d_isdst) = localtime(); return unless is_open; my $query = sprintf("UPDATE stempel SET stop=start + interval '%d minutes',time=%d WHERE stop IS NULL", $delta, quarter($delta)); $dbh->do($query); } sub move_task { my $minutes = shift; return unless is_open; my $query = sprintf("UPDATE stempel SET start = start - interval '%d minutes' WHERE stop IS NULL", $minutes); $dbh->do($query); } sub alter_task { return unless is_open; list_open; my $term = new Term::ReadLine ''; my $sql = q{SELECT customer,task FROM stempel WHERE stop IS NULL}; my $sth = $dbh->prepare($sql); $sth->execute; my $row = $sth->fetchrow_hashref; my $tasks = tasklist $row->{'customer'}; $term->addhistory($_) foreach (@$tasks); my $attribs = $term->Attribs; $attribs->{completion_word} = $tasks; $term->{completion_function} = sub {return complete_customer $row->{'customer'}, @_}; my $task = $term->readline ('Aufgabe: '); if (length($task) > 0) { my $query = q{UPDATE stempel SET task=? WHERE stop IS NULL}; my $sth = $dbh->prepare($query); $sth->execute($task); } exit 0; } sub help { print <<"END"; stempel Copyright (c) 2007,8 Martin Schulze --list [month] list month [default=current|all] --back n move start of current task back by n minutes --open list open --close time close open task --help this text --end|-d terminate task --delete delete open task --reopen re-open last task --task alter task content END exit; } my $opt_close = undef; my $opt_list = undef; my $opt_back = undef; my %options = ('list:s' => \$opt_list, 'back=i' => \$opt_back, 'open' => \&list_open, 'help' => \&help, 'close=i' => \$opt_close, 'delete' => \&delete_task, 'reopen' => \&reopen_task, 'task|t' => \&alter_task, 'terminate|end|d' => \&close_task, ); GetOptions %options; if ($opt_close) { late_close_task $opt_close; } elsif (defined $opt_list) { list $opt_list; } elsif (defined $opt_back) { move_task $opt_back; } else { toggle_task; }