2 # make_invoice makes latex invoices, and is released
3 # under the terms of the GPL version 2, or any later version, at your
4 # option. See the file README and COPYING for more information.
5 # Copyright 2008 by Don Armstrong <don@donarmstrong.com>.
6 # $Id: perl_script 495 2006-08-10 08:02:01Z don $
17 make_invoice - makes invoices using latex
21 make_invoice [options]
24 --log,-l the log file to use to make the invoice
25 --template,-t the template to use to make the invoice
26 --min-time-interval, -m minimum time to bill, default 0
27 --time-granularity, -g time granularity, default 0
28 --hourly-fee, -f hourly fee, default 50.00
29 --svn,-s whether to use subversion or not
30 --debug, -d debugging level (Default 0)
31 --help, -h display this help
32 --man, -m display manual
40 The log file to use to generate the invoice
42 =item B<--template, -t>
44 The tex template to use to generate the invoice
48 Whether to use subversion or not; defaults to yes if .svn exists in
49 the current directory.
53 Invoice directory to place invoice in (automatically calculated if not
56 =item B<--min-time-interval, -m>
58 Minimum time interval to bill, defaults to 0.
60 =item B<--time-granularity, -g>
62 Time granularity, defaults to 0.
64 =item B<--hourly-fee,-f>
66 Hourly fee, defaults to 50.00
70 Debug verbosity. (Default 0)
74 Display brief useage information.
91 use POSIX qw(ceil strftime);
95 use Params::Validate qw(validate_with :types);
97 my %options = (log => undef,
101 time_interval => 0.00,
102 time_granularity => 0.00,
103 hourly_fee => '50.00',
109 GetOptions(\%options,
110 'log|l=s','template|t=s','invoice|i=s','svn|s!',
111 'time_granularity|time-granularity|g=s',
112 'time_interval|min-time-interval|T=s',
113 'hourly_fee|hourly-fee|f=s',
114 'debug|d+','help|h|?','man|m');
116 pod2usage() if $options{help};
117 pod2usage({verbose=>2}) if $options{man};
119 $DEBUG = $options{debug};
122 if (not defined $options{log}) {
123 push @USAGE_ERRORS,"You must pass a log file with --log";
125 if (not defined $options{template}) {
126 push @USAGE_ERRORS,"You must pass a template file with --template";
129 pod2usage(join("\n",@USAGE_ERRORS)) if @USAGE_ERRORS;
131 if (not defined $options{svn}) {
132 $options{svn} = -e '.svn';
136 my $log_fh = IO::File->new($options{log},'r') or
137 die "Unable to open $options{log} for reading: $!";
138 my $template_fh = IO::File->new($options{template},'r') or
139 die "Unable to open $options{template} for reading: $!";
143 my $tex_log = <<'EOF';
144 \setlength\LTleft{0pt plus 1fill minus 1fill}%
146 \begin{longtable}{|p{9cm}|r|r|r|r|}%
149 \mbox{Description} & Item Cost & Quantity & Cost & Total \\
152 my $totaldelta = undef;
156 my $first_date = undef;
157 my $last_date = undef;
167 $tex_log .= format_events(date => $date,
177 my ($d1,$d2) = map {s/^\s*\*\s*//;
180 if (not defined $first_date) {
184 my $delta = DateCalc($d1,$d2);
185 my $hours = Delta_Format($delta,0,'%ht');
186 if ($hours < $options{time_interval}) {
187 $hours = $options{time_interval}
189 if ($options{time_granularity}) {
190 $hours = ceil($hours / $options{time_granularity})*$options{time_granularity};
192 $delta = ParseDateDelta($hours * 60 * 60 . ' sec');
193 ($date,$date2) = ($d1,$d2);
195 $totaldelta = defined($totaldelta)?DateCalc($delta,$totaldelta):$delta;
196 $calc_log .= qq($string [).Delta_Format($delta,2,q(%ht)).qq(] [).Delta_Format($totaldelta,2,q(%ht)).qq(]\n);
198 elsif (/^\s+-\s*(.+)/) {
202 $calc_log .= $_.qq(\n);
205 $calc_log .= $_.qq(\n);
208 $calc_log .= "\nTotal: ".Delta_Format($totaldelta,2,q(%ht)).qq(\n);
210 $tex_log .= format_events(date => $date,
223 \multicolumn{4}{|r|}{\textbf{Total}} & \$%
226 $tex_log .= sprintf('%.2f',$total)."%\n";
237 $template = <$template_fh>;
240 my $invoice_start = UnixDate($first_date,'%B %e, %Y');
241 my $invoice_stop = UnixDate($last_date,'%B %e, %Y');
243 my $tt = Text::Template->new(TYPE=>'string',
245 DELIMITERS => ['{--','--}'],
247 my $tex_invoice = $tt->fill_in(HASH=>{start => $invoice_start,
248 stop => $invoice_stop,
250 total => sprintf('%0.2f',$total),
253 if (not defined $tex_invoice) {
254 die $Text::Template::ERROR;
257 my $invoice_date = UnixDate($last_date,'%m_%d_%Y');
258 my $invoice_dir = "invoice_$invoice_date";
260 if (not -d $invoice_dir) {
262 system('svn','mkdir',$invoice_dir) == 0 or
263 die "Unable to create invoice directory $invoice_dir";
266 system('mkdir','-p',$invoice_dir) == 0 or
267 die "Unable to create invoice directory $invoice_dir";
272 if (-e 'common_makefile' and not -e '$invoice_dir/Makefile') {
274 system('ln','-sf','../common_makefile','Makefile') == 0 or
275 die "Unable to link common_makefile to Makefile";
277 system('svn','add','Makefile') == 0 or
278 die "Unable to add Makefile";
283 # now we write stuff out
285 my $calc_log_fh = IO::File->new("log_${invoice_date}",'w') or
286 die "Unable to open log_${invoice_date} for writing: $!";
287 print {$calc_log_fh} $calc_log;
290 my $tex_invoice_fh = IO::File->new("invoice_${invoice_date}.tex",'w') or
291 die "Unable to open log_${invoice_date} for writing: $!";
292 print {$tex_invoice_fh} $tex_invoice;
293 close($tex_invoice_fh);
297 "log_${invoice_date}",
298 "invoice_${invoice_date}.tex",
299 ) == 0 or die "Unable to add log and invoice to svn";
300 system('svn','propset','svn:ignore',
301 "*.aux\n*.log\n*.dvi\n*.ps\n*.pdf\nauto\n",
303 ) == 0 or die "Unable to set svn:ignore";
308 my %param = validate_with(params => \@_,
309 spec => {time => {type => SCALAR,
311 date => {type => SCALAR,
313 date2 => {type => SCALAR,
315 total => {type => SCALARREF,
317 events => {type => ARRAYREF,
321 ${$param{total}} += $param{time} * $options{hourly_fee};
323 # $param{date} =~ s/\s+\d+\:\d+\:\d+\s+[A-Z]{0,3}\s*//;
324 my $output = '\hline'."\n".' \mbox{'.strftime('%A, %B %e, %H:%M',localtime(UnixDate($param{date},'%s'))).
325 ' to '.strftime('%H:%M %Z',localtime(UnixDate($param{date2},'%s')))."}\n\n".
326 ' \begin{itemize*}'."\n";
327 $output .= join('',map { s/_/\\_/g; " \\item $_\n";} @{$param{events}});
328 $output .= ' \end{itemize*} & \$'.sprintf('%.2f',$options{hourly_fee}).' & '.sprintf('%.2f',$param{time}).
329 ' & \$'.sprintf('%.2f',$param{time}*$options{hourly_fee}).' & \$'.
330 sprintf('%.2f',${$param{total}}) .
335 ## sub format_events{
336 ## my ($date,$date2,$time,@events) = @_;
337 ## my $output = ' \Fee{'.strftime('%A, %B %e, %H:%M',localtime(UnixDate($date,'%s'))).
338 ## ' to '.strftime('%H:%M %Z',localtime(UnixDate($date2,'%s')))."\n".
339 ## ' \begin{itemize*}'."\n";
340 ## $output .= join('',map {" \\item $_\n"} @events);
341 ## $output .= ' \end{itemize*}}{50.00}{'.$time.'}'."\n";