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 Only output the LaTeX file
74 Only output the log file
78 Debug verbosity. (Default 0)
82 Display brief useage information.
99 use POSIX qw(ceil strftime);
103 use Params::Validate qw(validate_with :types);
105 my %options = (log => undef,
109 time_interval => 0.00,
110 time_granularity => 0.00,
111 hourly_fee => '50.00',
119 GetOptions(\%options,
120 'log|l=s','template|t=s','invoice|i=s','svn|s!',
121 'time_granularity|time-granularity|g=s',
122 'time_interval|min-time-interval|T=s',
123 'hourly_fee|hourly-fee|f=s',
126 'debug|d+','help|h|?','man|m');
128 pod2usage() if $options{help};
129 pod2usage({verbose=>2}) if $options{man};
131 $DEBUG = $options{debug};
134 if (not defined $options{log}) {
135 push @USAGE_ERRORS,"You must pass a log file with --log";
137 if (not defined $options{template}) {
138 push @USAGE_ERRORS,"You must pass a template file with --template";
141 pod2usage(join("\n",@USAGE_ERRORS)) if @USAGE_ERRORS;
143 if (not defined $options{svn}) {
144 $options{svn} = -e '.svn';
148 my $log_fh = IO::File->new($options{log},'r') or
149 die "Unable to open $options{log} for reading: $!";
150 my $template_fh = IO::File->new($options{template},'r') or
151 die "Unable to open $options{template} for reading: $!";
155 my $tex_log = <<'EOF';
156 \setlength\LTleft{0pt plus 1fill minus 1fill}%
158 \begin{longtable}{|p{9cm}|r|r|r|r|}%
161 \mbox{Description} & Item Cost & Quantity & Cost & Total \\
164 my $totaldelta = undef;
168 my $first_date = undef;
169 my $last_date = undef;
177 next if /^Total: \d+\.\d{2}$/;
180 $tex_log .= format_events(date => $date,
189 s/\s*\[[\.\d]+\]\s*\[[\.\d]+\]\s*$//;
191 my ($d1,$d2) = map {s/^\s*\*\s*//;
192 UnixDate(ParseDate($_),'%s')
194 if (not defined $first_date) {
201 my $hours = $delta / (60*60);
202 if ($hours < $options{time_interval}) {
203 $hours = $options{time_interval}
205 if ($options{time_granularity} > 0) {
206 $hours = ceil($hours / $options{time_granularity})*$options{time_granularity};
209 $totaldelta += $delta;
210 $calc_log .= qq($string [).sprintf('%.2f',$hours).qq(] [).sprintf('%.2f',$totaldelta/(60*60)).qq(]\n);
212 elsif (/^\s+-\s*(.+)/) {
216 $calc_log .= $_.qq(\n);
219 $calc_log .= $_.qq(\n);
222 $calc_log .= "\nTotal: ".sprintf('%.2f',$totaldelta/(60*60)).qq(\n);
224 $tex_log .= format_events(date => $date,
237 \multicolumn{4}{|r|}{\textbf{Total}} & \$%
240 $tex_log .= sprintf('%.2f',$total)."%\n";
251 $template = <$template_fh>;
254 my $invoice_start = strftime('%c',localtime($first_date));
255 my $invoice_stop = strftime('%c',localtime($last_date));
257 my $tt = Text::Template->new(TYPE=>'string',
259 DELIMITERS => ['{--','--}'],
261 my $tex_invoice = $tt->fill_in(HASH=>{start => $invoice_start,
262 stop => $invoice_stop,
264 total => sprintf('%0.2f',$total),
267 if (not defined $tex_invoice) {
268 die $Text::Template::ERROR;
271 if ($options{log_only}) {
276 if ($options{tex_only}) {
282 my $invoice_date = strftime('%Y_%m_%d',localtime($last_date));
283 my $invoice_dir = "invoice_$invoice_date";
285 if (not -d $invoice_dir) {
287 system('svn','mkdir',$invoice_dir) == 0 or
288 die "Unable to create invoice directory $invoice_dir";
291 system('mkdir','-p',$invoice_dir) == 0 or
292 die "Unable to create invoice directory $invoice_dir";
297 if (-e 'common_makefile' and not -e '$invoice_dir/Makefile') {
299 system('ln','-sf','../common_makefile','Makefile') == 0 or
300 die "Unable to link common_makefile to Makefile";
302 system('svn','add','Makefile') == 0 or
303 die "Unable to add Makefile";
308 # now we write stuff out
310 my $calc_log_fh = IO::File->new("log_${invoice_date}",'w') or
311 die "Unable to open log_${invoice_date} for writing: $!";
312 print {$calc_log_fh} $calc_log;
315 my $tex_invoice_fh = IO::File->new("invoice_${invoice_date}.tex",'w') or
316 die "Unable to open log_${invoice_date} for writing: $!";
317 print {$tex_invoice_fh} $tex_invoice;
318 close($tex_invoice_fh);
322 "log_${invoice_date}",
323 "invoice_${invoice_date}.tex",
324 ) == 0 or die "Unable to add log and invoice to svn";
325 system('svn','propset','svn:ignore',
326 "*.aux\n*.log\n*.dvi\n*.ps\n*.pdf\nauto\n",
328 ) == 0 or die "Unable to set svn:ignore";
333 my %param = validate_with(params => \@_,
334 spec => {time => {type => SCALAR,
336 date => {type => SCALAR,
338 date2 => {type => SCALAR,
340 total => {type => SCALARREF,
342 events => {type => ARRAYREF,
346 ${$param{total}} += $param{time} * $options{hourly_fee};
348 # $param{date} =~ s/\s+\d+\:\d+\:\d+\s+[A-Z]{0,3}\s*//;
349 my $output = '\hline'."\n".' \mbox{'.strftime('%A, %B %e, %H:%M',localtime($param{date})).
350 ' to '.strftime('%H:%M %Z',localtime($param{date2}))."}\n\n".
351 ' \begin{itemize*}'."\n";
352 $output .= join('',map { s/_/\\_/g; " \\item $_\n";} @{$param{events}});
353 $output .= ' \end{itemize*} & \$'.sprintf('%.2f',$options{hourly_fee}).' & '.sprintf('%.2f',$param{time}).
354 ' & \$'.sprintf('%.2f',$param{time}*$options{hourly_fee}).' & \$'.
355 sprintf('%.2f',${$param{total}}) .
360 ## sub format_events{
361 ## my ($date,$date2,$time,@events) = @_;
362 ## my $output = ' \Fee{'.strftime('%A, %B %e, %H:%M',localtime(UnixDate($date,'%s'))).
363 ## ' to '.strftime('%H:%M %Z',localtime(UnixDate($date2,'%s')))."\n".
364 ## ' \begin{itemize*}'."\n";
365 ## $output .= join('',map {" \\item $_\n"} @events);
366 ## $output .= ' \end{itemize*}}{50.00}{'.$time.'}'."\n";