]> git.donarmstrong.com Git - bin.git/commitdiff
add make invoice command
authorDon Armstrong <don@donarmstrong.com>
Mon, 18 Feb 2008 10:06:57 +0000 (10:06 +0000)
committerDon Armstrong <don@donarmstrong.com>
Mon, 18 Feb 2008 10:06:57 +0000 (10:06 +0000)
make_invoice [new file with mode: 0755]

diff --git a/make_invoice b/make_invoice
new file mode 100755 (executable)
index 0000000..bce9c8a
--- /dev/null
@@ -0,0 +1,282 @@
+#! /usr/bin/perl
+# make_invoice makes latex invoices, and is released
+# under the terms of the GPL version 2, or any later version, at your
+# option. See the file README and COPYING for more information.
+# Copyright 2008 by Don Armstrong <don@donarmstrong.com>.
+# $Id: perl_script 495 2006-08-10 08:02:01Z don $
+
+
+use warnings;
+use strict;
+
+use Getopt::Long;
+use Pod::Usage;
+
+=head1 NAME
+
+make_invoice - makes invoices using latex
+
+=head1 SYNOPSIS
+
+ make_invoice [options]
+
+ Options:
+  --log, the log file to use to make the invoice
+  --template, the template to use to make the invoice
+  --svn, whether to use subversion or not
+  --debug, -d debugging level (Default 0)
+  --help, -h display this help
+  --man, -m display manual
+
+=head1 OPTIONS
+
+=over
+
+=item B<--log, -l>
+
+The log file to use to generate the invoice
+
+=item B<--template, -t>
+
+The tex template to use to generate the invoice
+
+=item B<--svn, -s>
+
+Whether to use subversion or not; defaults to yes if .svn exists in
+the current directory.
+
+=item B<--invoice,-i>
+
+Invoice directory to place invoice in (automatically calculated if not
+passed.)
+
+=item B<--min-time-interval, -m>
+
+Minimum time interval to bill, defaults to 0.
+
+=item B<--time-granularity, -g>
+
+Time granularity, defaults to 0.
+
+=item B<--hourly-fee,-f>
+
+Hourly fee, defaults to 50.00
+
+=item B<--debug, -d>
+
+Debug verbosity. (Default 0)
+
+=item B<--help, -h>
+
+Display brief useage information.
+
+=item B<--man, -m>
+
+Display this manual.
+
+=back
+
+=head1 EXAMPLES
+
+
+=cut
+
+
+use vars qw($DEBUG);
+
+use Date::Manip;
+use POSIX qw(ceil);
+use Cwd qw(cwd);
+use Text::Template;
+
+my %options = (log             => undef,
+              template        => undef,
+              svn             => undef,
+              invoice         => undef,
+              time_interval   => 0,
+              time_granularity => 0,
+              hourly_fee      => '50.00',
+              debug           => 0,
+              help            => 0,
+              man             => 0,
+              );
+
+GetOptions(\%options,
+          'log|l=s','template|t=s','invoice|i=s','svn|s!',
+          'time_granularity|time-granularity|g=s',
+          'time_interval|min-time-interval|T=s',
+          'hourly_fee|hourly-fee|f=s',
+          'debug|d+','help|h|?','man|m');
+
+pod2usage() if $options{help};
+pod2usage({verbose=>2}) if $options{man};
+
+$DEBUG = $options{debug};
+
+my @USAGE_ERRORS;
+if (not defined $options{log}) {
+     push @USAGE_ERRORS,"You must pass a log file with --log";
+}
+if (not defined $options{template}) {
+     push @USAGE_ERRORS,"You must pass a template file with --template";
+}
+
+pod2usage(join("\n",@USAGE_ERRORS)) if @USAGE_ERRORS;
+
+if (not defined $options{svn}) {
+     $options{svn} = -e '.svn';
+}
+
+
+my $log_fh = IO::File->new($options{log},'r') or
+     die "Unable to open $options{log} for reading: $!";
+my $template_fh = IO::File->new($options{template},'r') or
+     die "Unable to open $options{template} for reading: $!";
+
+
+my $calc_log = '';
+my $tex_log = '';
+my $totaldelta = undef;
+
+my $first_date = undef;
+my $last_date = undef;
+my $time = undef;
+my $date = undef;
+my $date2 = undef;
+my @events;
+
+while (<$log_fh>) {
+     chomp;
+     if (/^\s*\* /) {
+         if (defined $time) {
+              $tex_log .= format_events($date,$date2,$time,@events);
+              @events = ();
+              $date = undef;
+              $time = undef;
+         }
+         my $string = $_;
+         my ($d1,$d2) = map {s/^\s*\*\s*//;
+                            ParseDate($_)
+                       } split /\s*-\s*/;
+         if (not defined $first_date) {
+              $first_date = $d1;
+         }
+         $last_date = $d2;
+         my $delta = DateCalc($d1,$d2);
+         my $hours = Delta_Format($delta,0,'%ht');
+         if ($hours < $options{time_interval}) {
+              $hours = $options{time_interval}
+         }
+         if ($options{time_granularity}) {
+              $hours = ceil($hours / $options{time_granularity})*$options{time_granularity};
+         }
+         $delta = ParseDateDelta($hours * 60 * 60 . ' sec');
+         ($date,$date2) = ($d1,$d2);
+         $time = $hours;
+         $totaldelta = defined($totaldelta)?DateCalc($delta,$totaldelta):$delta;
+         $calc_log .= qq($string [).Delta_Format($delta,2,q(%ht)).qq(] [).Delta_Format($totaldelta,2,q(%ht)).qq(]\n);
+     }
+     elsif (/^\s+-\s*(.+)/) {
+         my $event = $1;
+         chomp $event;
+         push @events,$event;
+         $calc_log .= $_.qq(\n);
+     }
+     else {
+         $calc_log .= $_.qq(\n);
+     }
+}
+$calc_log .= "\nTotal: ".Delta_Format($totaldelta,2,q(%ht)).qq(\n);
+if (defined $time) {
+     $tex_log .= format_events($date,$date2,$time,@events);
+     @events = ();
+     $date = undef;
+     $date2 = undef;
+     $time = undef;
+}
+
+
+my $template;
+{
+     local $/;
+     $template = <$template_fh>;
+}
+
+my $invoice_start = UnixDate($first_date,'%B %e, %Y');
+my $invoice_stop = UnixDate($last_date,'%B %e, %Y');
+
+my $tt = Text::Template->new(TYPE=>'string',
+                            SOURCE => $template,
+                            DELIMITERS => ['{--','--}'],
+                           );
+my $tex_invoice = $tt->fill_in(HASH=>{start => $invoice_start,
+                                     stop  => $invoice_stop,
+                                     log   => $tex_log,
+                                    }
+                             );
+if (not defined $tex_invoice) {
+     die $Text::Template::ERROR;
+}
+
+my $invoice_date = UnixDate($last_date,'%m_%d_%Y');
+my $invoice_dir = "invoice_$invoice_date";
+
+if (not -d $invoice_dir) {
+     if ($options{svn}) {
+         system('svn','mkdir',$invoice_dir) == 0 or
+              die "Unable to create invoice directory $invoice_dir";
+     }
+     else {
+         system('mkdir','-p',$invoice_dir) == 0 or
+              die "Unable to create invoice directory $invoice_dir";
+     }
+}
+
+my $cwd = cwd;
+if (-e 'common_makefile' and not -e '$invoice_dir/Makefile') {
+     chdir($invoice_dir);
+     system('ln','-sf','../common_makefile','Makefile') == 0 or
+         die "Unable to link common_makefile to Makefile";
+     if ($options{svn}) {
+         system('svn','add','Makefile') == 0 or
+              die "Unable to add Makefile";
+     }
+     chdir($cwd);
+}
+
+# now we write stuff out
+chdir($invoice_dir);
+my $calc_log_fh = IO::File->new("log_${invoice_date}",'w') or
+     die "Unable to open log_${invoice_date} for writing: $!";
+print {$calc_log_fh} $calc_log;
+close($calc_log_fh);
+
+my $tex_invoice_fh = IO::File->new("invoice_${invoice_date}.tex",'w') or
+     die "Unable to open log_${invoice_date} for writing: $!";
+print {$tex_invoice_fh} $tex_invoice;
+close($tex_invoice_fh);
+
+if ($options{svn}) {
+     system('svn','add',
+           "log_${invoice_date}",
+           "invoice_${invoice_date}.tex",
+          ) == 0 or die "Unable to add log and invoice to svn";
+     system('svn','propset','svn:ignore',
+           "*.aux\n*.log\n*.dvi\n*.ps\n*.pdf\n",
+           '.'
+          ) == 0 or die "Unable to set svn:ignore";
+}
+
+
+sub format_events{
+     my ($date,$date2,$time,@events) = @_;
+     my $output = '        \Fee{'.UnixDate($date,'%A, %B %e, %H:%M').
+         ' to '.UnixDate($date2,'%H:%M %Z')."\n".
+         '         \begin{itemize*}'."\n";
+     $output .= join('',map {"           \\item $_\n"} @events);
+     $output .= '         \end{itemize*}}{50.00}{'.$time.'}'."\n";
+     return $output;
+}
+
+
+__END__