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 if (/\s*\*\s*CLOCK:\s+\[([^\]]+)\]--\[([^\]]+)\]/) {
192 $d1 = UnixDate(ParseDate($1),'%s');
193 $d2 = UnixDate(ParseDate($2),'%s');
195 ($d1,$d2) = map {s/^\s*\*\s*//;
196 UnixDate(ParseDate($_),'%s')
199 my $string = '* '.strftime('%A, %B %e, %H:%M:%S',localtime($d1)).' - '.
200 strftime('%A, %B %e, %H:%M:%S',localtime($d2));
201 if (not defined $first_date) {
208 my $hours = $delta / (60*60);
209 if ($hours < $options{time_interval}) {
210 $hours = $options{time_interval}
212 if ($options{time_granularity} > 0) {
213 $hours = ceil($hours / $options{time_granularity})*$options{time_granularity};
216 $totaldelta += $delta;
217 $calc_log .= $string.q( [).sprintf('%.2f',$hours).qq(] [).sprintf('%.2f',$totaldelta/(60*60)).qq(]\n);
219 elsif (/^\s+-\s*(.+)/) {
223 $calc_log .= $_.qq(\n);
226 $calc_log .= $_.qq(\n);
229 $calc_log .= "\nTotal: ".sprintf('%.2f',$totaldelta/(60*60)).qq(\n);
231 $tex_log .= format_events(date => $date,
244 \multicolumn{4}{|r|}{\textbf{Total}} & \$%
247 $tex_log .= sprintf('%.2f',$total)."%\n";
258 $template = <$template_fh>;
261 my $invoice_start = strftime('%c',localtime($first_date));
262 my $invoice_stop = strftime('%c',localtime($last_date));
264 my $tt = Text::Template->new(TYPE=>'string',
266 DELIMITERS => ['{--','--}'],
268 my $tex_invoice = $tt->fill_in(HASH=>{start => $invoice_start,
269 stop => $invoice_stop,
271 total => sprintf('%0.2f',$total),
274 if (not defined $tex_invoice) {
275 die $Text::Template::ERROR;
278 if ($options{log_only}) {
283 if ($options{tex_only}) {
289 my $invoice_date = strftime('%Y_%m_%d',localtime($last_date));
290 my $invoice_dir = "invoice_$invoice_date";
292 if (not -d $invoice_dir) {
294 system('svn','mkdir',$invoice_dir) == 0 or
295 die "Unable to create invoice directory $invoice_dir";
298 system('mkdir','-p',$invoice_dir) == 0 or
299 die "Unable to create invoice directory $invoice_dir";
304 if (-e 'common_makefile' and not -e '$invoice_dir/Makefile') {
306 system('ln','-sf','../common_makefile','Makefile') == 0 or
307 die "Unable to link common_makefile to Makefile";
309 system('svn','add','Makefile') == 0 or
310 die "Unable to add Makefile";
315 # now we write stuff out
317 my $calc_log_fh = IO::File->new("log_${invoice_date}",'w') or
318 die "Unable to open log_${invoice_date} for writing: $!";
319 print {$calc_log_fh} $calc_log;
322 my $tex_invoice_fh = IO::File->new("invoice_${invoice_date}.tex",'w') or
323 die "Unable to open log_${invoice_date} for writing: $!";
324 print {$tex_invoice_fh} $tex_invoice;
325 close($tex_invoice_fh);
329 "log_${invoice_date}",
330 "invoice_${invoice_date}.tex",
331 ) == 0 or die "Unable to add log and invoice to svn";
332 system('svn','propset','svn:ignore',
333 "*.aux\n*.log\n*.dvi\n*.ps\n*.pdf\nauto\n",
335 ) == 0 or die "Unable to set svn:ignore";
340 my %param = validate_with(params => \@_,
341 spec => {time => {type => SCALAR,
343 date => {type => SCALAR,
345 date2 => {type => SCALAR,
347 total => {type => SCALARREF,
349 events => {type => ARRAYREF,
353 ${$param{total}} += $param{time} * $options{hourly_fee};
355 # $param{date} =~ s/\s+\d+\:\d+\:\d+\s+[A-Z]{0,3}\s*//;
356 my $output = '\hline'."\n".' \mbox{'.strftime('%A, %B %e, %H:%M',localtime($param{date})).
357 ' to '.strftime('%H:%M %Z',localtime($param{date2}))."}\n\n".
358 ' \begin{itemize*}'."\n";
359 $output .= join('',map { s/_/\\_/g; " \\item $_\n";} @{$param{events}});
360 $output .= ' \end{itemize*} & \$'.sprintf('%.2f',$options{hourly_fee}).' & '.sprintf('%.2f',$param{time}).
361 ' & \$'.sprintf('%.2f',$param{time}*$options{hourly_fee}).' & \$'.
362 sprintf('%.2f',${$param{total}}) .
367 ## sub format_events{
368 ## my ($date,$date2,$time,@events) = @_;
369 ## my $output = ' \Fee{'.strftime('%A, %B %e, %H:%M',localtime(UnixDate($date,'%s'))).
370 ## ' to '.strftime('%H:%M %Z',localtime(UnixDate($date2,'%s')))."\n".
371 ## ' \begin{itemize*}'."\n";
372 ## $output .= join('',map {" \\item $_\n"} @events);
373 ## $output .= ' \end{itemize*}}{50.00}{'.$time.'}'."\n";