-
- $invoice_data{'po_line'} =
- ( $cust_main->payby eq 'BILL' && $cust_main->payinfo )
- ? _latex_escape("Purchase Order #". $cust_main->payinfo)
- : '~';
-
- my @filled_in = ();
- if ( $format eq 'old' ) {
-
- my @line_item = ();
- my @total_item = ();
- while ( @invoice_template ) {
- my $line = shift @invoice_template;
-
- if ( $line =~ /^%%Detail\s*$/ ) {
-
- while ( ( my $line_item_line = shift @invoice_template )
- !~ /^%%EndDetail\s*$/ ) {
- push @line_item, $line_item_line;
- }
- foreach my $line_item ( $self->_items ) {
- #foreach my $line_item ( $self->_items_pkg ) {
- $invoice_data{'ref'} = $line_item->{'pkgnum'};
- $invoice_data{'description'} =
- _latex_escape($line_item->{'description'});
- if ( exists $line_item->{'ext_description'} ) {
- $invoice_data{'description'} .=
- "\\tabularnewline\n~~".
- join( "\\tabularnewline\n~~",
- map _latex_escape($_), @{$line_item->{'ext_description'}}
- );
- }
- $invoice_data{'amount'} = $line_item->{'amount'};
- $invoice_data{'product_code'} = $line_item->{'pkgpart'} || 'N/A';
- push @filled_in,
- map { my $b=$_; $b =~ s/\$(\w+)/$invoice_data{$1}/eg; $b } @line_item;
- }
-
- } elsif ( $line =~ /^%%TotalDetails\s*$/ ) {
-
- while ( ( my $total_item_line = shift @invoice_template )
- !~ /^%%EndTotalDetails\s*$/ ) {
- push @total_item, $total_item_line;
- }
-
- my @total_fill = ();
-
- my $taxtotal = 0;
- foreach my $tax ( $self->_items_tax ) {
- $invoice_data{'total_item'} = _latex_escape($tax->{'description'});
- $taxtotal += $tax->{'amount'};
- $invoice_data{'total_amount'} = '\dollar '. $tax->{'amount'};
- push @total_fill,
- map { my $b=$_; $b =~ s/\$(\w+)/$invoice_data{$1}/eg; $b }
- @total_item;
- }
-
- if ( $taxtotal ) {
- $invoice_data{'total_item'} = 'Sub-total';
- $invoice_data{'total_amount'} =
- '\dollar '. sprintf('%.2f', $self->charged - $taxtotal );
- unshift @total_fill,
- map { my $b=$_; $b =~ s/\$(\w+)/$invoice_data{$1}/eg; $b }
- @total_item;
- }
-
- $invoice_data{'total_item'} = '\textbf{Total}';
- $invoice_data{'total_amount'} =
- '\textbf{\dollar '. sprintf('%.2f', $self->charged + $pr_total ). '}';
- push @total_fill,
- map { my $b=$_; $b =~ s/\$(\w+)/$invoice_data{$1}/eg; $b }
- @total_item;
-
- #foreach my $thing ( sort { $a->_date <=> $b->_date } $self->_items_credits, $self->_items_payments
-
- # credits
- foreach my $credit ( $self->_items_credits ) {
- $invoice_data{'total_item'} = _latex_escape($credit->{'description'});
- #$credittotal
- $invoice_data{'total_amount'} = '-\dollar '. $credit->{'amount'};
- push @total_fill,
- map { my $b=$_; $b =~ s/\$(\w+)/$invoice_data{$1}/eg; $b }
- @total_item;
- }
-
- # payments
- foreach my $payment ( $self->_items_payments ) {
- $invoice_data{'total_item'} = _latex_escape($payment->{'description'});
- #$paymenttotal
- $invoice_data{'total_amount'} = '-\dollar '. $payment->{'amount'};
- push @total_fill,
- map { my $b=$_; $b =~ s/\$(\w+)/$invoice_data{$1}/eg; $b }
- @total_item;
- }
-
- $invoice_data{'total_item'} = '\textbf{'. $self->balance_due_msg. '}';
- $invoice_data{'total_amount'} =
- '\textbf{\dollar '. sprintf('%.2f', $self->owed + $pr_total ). '}';
- push @total_fill,
- map { my $b=$_; $b =~ s/\$(\w+)/$invoice_data{$1}/eg; $b }
- @total_item;
-
- push @filled_in, @total_fill;
-
- } else {
- #$line =~ s/\$(\w+)/$invoice_data{$1}/eg;
- $line =~ s/\$(\w+)/exists($invoice_data{$1}) ? $invoice_data{$1} : nounder($1)/eg;
- push @filled_in, $line;
- }
-
- }
-
- sub nounder {
- my $var = $1;
- $var =~ s/_/\-/g;
- $var;
- }
-
- } elsif ( $format eq 'Text::Template' ) {
-
- my @detail_items = ();
- my @total_items = ();
-
- $invoice_data{'detail_items'} = \@detail_items;
- $invoice_data{'total_items'} = \@total_items;
-
- foreach my $line_item ( $self->_items ) {
- my $detail = {
- ext_description => [],
- };
- $detail->{'ref'} = $line_item->{'pkgnum'};
- $detail->{'quantity'} = 1;
- $detail->{'description'} = _latex_escape($line_item->{'description'});
- if ( exists $line_item->{'ext_description'} ) {
- @{$detail->{'ext_description'}} = map {
- _latex_escape($_);
- } @{$line_item->{'ext_description'}};
- }
- $detail->{'amount'} = $line_item->{'amount'};
- $detail->{'product_code'} = $line_item->{'pkgpart'} || 'N/A';
-
- push @detail_items, $detail;
- }
-
-
- my $taxtotal = 0;
- foreach my $tax ( $self->_items_tax ) {
- my $total = {};
- $total->{'total_item'} = _latex_escape($tax->{'description'});
- $taxtotal += $tax->{'amount'};
- $total->{'total_amount'} = '\dollar '. $tax->{'amount'};
- push @total_items, $total;
- }
-
- if ( $taxtotal ) {
- my $total = {};
- $total->{'total_item'} = 'Sub-total';
- $total->{'total_amount'} =
- '\dollar '. sprintf('%.2f', $self->charged - $taxtotal );
- unshift @total_items, $total;
- }
-
- {
- my $total = {};
- $total->{'total_item'} = '\textbf{Total}';
- $total->{'total_amount'} =
- '\textbf{\dollar '. sprintf('%.2f', $self->charged + $pr_total ). '}';
- push @total_items, $total;
- }
-
- #foreach my $thing ( sort { $a->_date <=> $b->_date } $self->_items_credits, $self->_items_payments
-
- # credits
- foreach my $credit ( $self->_items_credits ) {
- my $total;
- $total->{'total_item'} = _latex_escape($credit->{'description'});
- #$credittotal
- $total->{'total_amount'} = '-\dollar '. $credit->{'amount'};
- push @total_items, $total;
- }
-
- # payments
- foreach my $payment ( $self->_items_payments ) {
- my $total = {};
- $total->{'total_item'} = _latex_escape($payment->{'description'});
- #$paymenttotal
- $total->{'total_amount'} = '-\dollar '. $payment->{'amount'};
- push @total_items, $total;
- }
-
- {
- my $total;
- $total->{'total_item'} = '\textbf{'. $self->balance_due_msg. '}';
- $total->{'total_amount'} =
- '\textbf{\dollar '. sprintf('%.2f', $self->owed + $pr_total ). '}';
- push @total_items, $total;
- }
-
- } else {
- die "guru meditation #54";
- }
-
- my $dir = $FS::UID::conf_dir. "/cache.". $FS::UID::datasrc;
- my $lh = new File::Temp( TEMPLATE => 'invoice.'. $self->invnum. '.XXXXXXXX',
- DIR => $dir,
- SUFFIX => '.eps',
- UNLINK => 0,
- ) or die "can't open temp file: $!\n";
-
- if ($template && $conf->exists("logo_${template}.eps")) {
- print $lh $conf->config_binary("logo_${template}.eps")
- or die "can't write temp file: $!\n";
- }else{
- print $lh $conf->config_binary('logo.eps')
- or die "can't write temp file: $!\n";
- }
- close $lh;
- $invoice_data{'logo_file'} = $lh->filename;
-
- my $fh = new File::Temp( TEMPLATE => 'invoice.'. $self->invnum. '.XXXXXXXX',
- DIR => $dir,
- SUFFIX => '.tex',
- UNLINK => 0,
- ) or die "can't open temp file: $!\n";
- if ( $format eq 'old' ) {
- print $fh join('', @filled_in );
- } elsif ( $format eq 'Text::Template' ) {
- $text_template->fill_in(OUTPUT => $fh, HASH => \%invoice_data);
- } else {
- die "guru meditation #32";
- }
- close $fh;
-
- $fh->filename =~ /^(.*).tex$/ or die "unparsable filename: ". $fh->filename;
- return ($1, $invoice_data{'logo_file'});
-
-}
-
-=item print_ps [ TIME [ , TEMPLATE ] ]
-
-Returns an postscript invoice, as a scalar.
-
-TIME an optional value used to control the printing of overdue messages. The
-default is now. It isn't the date of the invoice; that's the `_date' field.
-It is specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
-L<Time::Local> and L<Date::Parse> for conversion functions.
-
-=cut
-
-sub print_ps {
- my $self = shift;
-
- my ($file, $lfile) = $self->print_latex(@_);
- my $ps = generate_ps($file);
- unlink($lfile);
- $ps;
-
-}
-
-=item print_pdf [ TIME [ , TEMPLATE ] ]
-
-Returns an PDF invoice, as a scalar.
-
-TIME an optional value used to control the printing of overdue messages. The
-default is now. It isn't the date of the invoice; that's the `_date' field.
-It is specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
-L<Time::Local> and L<Date::Parse> for conversion functions.
-
-=cut
-
-sub print_pdf {
- my $self = shift;
-
- my ($file, $lfile) = $self->print_latex(@_);
-
- my $dir = $FS::UID::conf_dir. "/cache.". $FS::UID::datasrc;
- chdir($dir);
-
- #system('pdflatex', "$file.tex");
- #system('pdflatex', "$file.tex");
- #! LaTeX Error: Unknown graphics extension: .eps.
-
- my $sfile = shell_quote $file;
-
- system("pslatex $sfile.tex >/dev/null 2>&1") == 0
- or die "pslatex $file.tex failed; see $file.log for details?\n";
- system("pslatex $sfile.tex >/dev/null 2>&1") == 0
- or die "pslatex $file.tex failed; see $file.log for details?\n";
-
- #system('dvipdf', "$file.dvi", "$file.pdf" );
- system(
- "dvips -q -t letter -f $sfile.dvi ".
- "| gs -q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -sOutputFile=$sfile.pdf ".
- " -c save pop -"
- ) == 0
- or die "dvips | gs failed: $!";
-
- open(PDF, "<$file.pdf")
- or die "can't open $file.pdf: $! (error in LaTeX template?)\n";
-
- unlink("$file.dvi", "$file.log", "$file.aux", "$file.pdf", "$file.tex");
- unlink("$lfile");
-
- my $pdf = '';
- while (<PDF>) {
- $pdf .= $_;
- }
-
- close PDF;
-
- return $pdf;
-
-}
-
-=item print_html [ TIME [ , TEMPLATE [ , CID ] ] ]
-
-Returns an HTML invoice, as a scalar.
-
-TIME an optional value used to control the printing of overdue messages. The
-default is now. It isn't the date of the invoice; that's the `_date' field.
-It is specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
-L<Time::Local> and L<Date::Parse> for conversion functions.
-
-CID is a MIME Content-ID used to create a "cid:" URL for the logo image, used
-when emailing the invoice as part of a multipart/related MIME email.
-
-=cut
-
-#some falze laziness w/print_text and print_latex (and send_csv)
-sub print_html {
- my( $self, $today, $template, $cid ) = @_;
- $today ||= time;
-
- my $cust_main = $self->cust_main;
- $cust_main->payname( $cust_main->first. ' '. $cust_main->getfield('last') )
- unless $cust_main->payname && $cust_main->payby !~ /^(CHEK|DCHK)$/;
-
- $template ||= $self->_agent_template;
- my $templatefile = 'invoice_html';
- my $suffix = length($template) ? "_$template" : '';
- $templatefile .= $suffix;
- my @html_template = map "$_\n", $conf->config($templatefile)
- or die "cannot load config file $templatefile";
-
- my $html_template = new Text::Template(
- TYPE => 'ARRAY',
- SOURCE => \@html_template,
- DELIMITERS => [ '<%=', '%>' ],
- );
-
- $html_template->compile()
- or die 'While compiling ' . $templatefile . ': ' . $Text::Template::ERROR;
-
- my %invoice_data = (
- 'company_name' => scalar( $conf->config('company_name') ),
- 'company_address' => join("\n", $conf->config('company_address') ). "\n",
- 'custnum' => $self->custnum,
- 'invnum' => $self->invnum,
- 'date' => time2str('%b %o, %Y', $self->_date),
- 'today' => time2str('%b %o, %Y', $today),
- 'agent' => encode_entities($cust_main->agent->agent),
- 'payname' => encode_entities($cust_main->payname),
- 'company' => encode_entities($cust_main->company),
- 'address1' => encode_entities($cust_main->address1),
- 'address2' => encode_entities($cust_main->address2),
- 'city' => encode_entities($cust_main->city),
- 'state' => encode_entities($cust_main->state),
- 'zip' => encode_entities($cust_main->zip),
- 'terms' => $self->terms,
- 'cid' => $cid,
- 'template' => $template,
-# 'conf_dir' => "$FS::UID::conf_dir/conf.$FS::UID::datasrc",
- );
-
- if (
- defined( $conf->config_orbase('invoice_htmlreturnaddress', $template) )
- && length( $conf->config_orbase('invoice_htmlreturnaddress', $template) )
- ) {
-
- $invoice_data{'returnaddress'} =
- join("\n", $conf->config_orbase('invoice_htmlreturnaddress', $template) );
-
- } elsif ( grep /\S/,
- $conf->config_orbase( 'invoice_latexreturnaddress', $template ) ) {
-
- $invoice_data{'returnaddress'} =
- join("\n", map {
- s/~/ /g;
- s/\\\\\*?\s*$/<BR>/;
- s/\\hyphenation\{[\w\s\-]+\}//;
- $_;
- }
- $conf->config_orbase( 'invoice_latexreturnaddress',
- $template
- )
- );
-
- } elsif ( grep /\S/, $conf->config('company_address') ) {
-
- $invoice_data{'returnaddress'} =
- join("\n", $conf->config('company_address') );
-
- } else {
-
- my $warning = "Couldn't find a return address; ".
- "do you need to set the company_address configuration value?";
- warn "$warning\n";
- #$invoice_data{'returnaddress'} = $warning;
-
- }
-
- my $countrydefault = $conf->config('countrydefault') || 'US';
- if ( $cust_main->country eq $countrydefault ) {
- $invoice_data{'country'} = '';
- } else {
- $invoice_data{'country'} =
- encode_entities(code2country($cust_main->country));
- }
-
- if (
- defined( $conf->config_orbase('invoice_htmlnotes', $template) )
- && length( $conf->config_orbase('invoice_htmlnotes', $template) )
- ) {
- $invoice_data{'notes'} =
- join("\n", $conf->config_orbase('invoice_htmlnotes', $template) );
- } else {
- $invoice_data{'notes'} =
- join("\n", map {
- s/%%(.*)$/<!-- $1 -->/g;
- s/\\section\*\{\\textsc\{(.)(.*)\}\}/<p><b><font size="+1">$1<\/font>\U$2<\/b>/g;
- s/\\begin\{enumerate\}/<ol>/g;
- s/\\item / <li>/g;
- s/\\end\{enumerate\}/<\/ol>/g;
- s/\\textbf\{(.*)\}/<b>$1<\/b>/g;
- s/\\\\\*/ /;
- s/\\dollar ?/\$/g;
- $_;
- }
- $conf->config_orbase('invoice_latexnotes', $template)
- );
- }
-
-# #do variable substitutions in notes
-# $invoice_data{'notes'} =
-# join("\n",
-# map { my $b=$_; $b =~ s/\$(\w+)/$invoice_data{$1}/eg; $b }
-# $conf->config_orbase('invoice_latexnotes', $suffix)
-# );
-
- if (
- defined( $conf->config_orbase('invoice_htmlfooter', $template) )
- && length( $conf->config_orbase('invoice_htmlfooter', $template) )
- ) {
- $invoice_data{'footer'} =
- join("\n", $conf->config_orbase('invoice_htmlfooter', $template) );
- } else {
- $invoice_data{'footer'} =
- join("\n", map { s/~/ /g; s/\\\\\*?\s*$/<BR>/; $_; }
- $conf->config_orbase('invoice_latexfooter', $template)
- );
- }
-
- $invoice_data{'po_line'} =
- ( $cust_main->payby eq 'BILL' && $cust_main->payinfo )
- ? encode_entities("Purchase Order #". $cust_main->payinfo)
- : '';
-
- my $money_char = $conf->config('money_char') || '$';
-
- foreach my $line_item ( $self->_items ) {
- my $detail = {
- ext_description => [],
- };
- $detail->{'ref'} = $line_item->{'pkgnum'};
- $detail->{'description'} = encode_entities($line_item->{'description'});
- if ( exists $line_item->{'ext_description'} ) {
- @{$detail->{'ext_description'}} = map {
- encode_entities($_);
- } @{$line_item->{'ext_description'}};
- }
- $detail->{'amount'} = $money_char. $line_item->{'amount'};
- $detail->{'product_code'} = $line_item->{'pkgpart'} || 'N/A';
-
- push @{$invoice_data{'detail_items'}}, $detail;
- }
-
-
- my $taxtotal = 0;
- foreach my $tax ( $self->_items_tax ) {
- my $total = {};
- $total->{'total_item'} = encode_entities($tax->{'description'});
- $taxtotal += $tax->{'amount'};
- $total->{'total_amount'} = $money_char. $tax->{'amount'};
- push @{$invoice_data{'total_items'}}, $total;
- }
-
- if ( $taxtotal ) {
- my $total = {};
- $total->{'total_item'} = 'Sub-total';
- $total->{'total_amount'} =
- $money_char. sprintf('%.2f', $self->charged - $taxtotal );
- unshift @{$invoice_data{'total_items'}}, $total;
- }
-
- my( $pr_total, @pr_cust_bill ) = $self->previous; #previous balance
- {
- my $total = {};
- $total->{'total_item'} = '<b>Total</b>';
- $total->{'total_amount'} =
- "<b>$money_char". sprintf('%.2f', $self->charged + $pr_total ). '</b>';
- push @{$invoice_data{'total_items'}}, $total;
- }
-
- #foreach my $thing ( sort { $a->_date <=> $b->_date } $self->_items_credits, $self->_items_payments
-
- # credits
- foreach my $credit ( $self->_items_credits ) {
- my $total;
- $total->{'total_item'} = encode_entities($credit->{'description'});
- #$credittotal
- $total->{'total_amount'} = "-$money_char". $credit->{'amount'};
- push @{$invoice_data{'total_items'}}, $total;
- }
-
- # payments
- foreach my $payment ( $self->_items_payments ) {
- my $total = {};
- $total->{'total_item'} = encode_entities($payment->{'description'});
- #$paymenttotal
- $total->{'total_amount'} = "-$money_char". $payment->{'amount'};
- push @{$invoice_data{'total_items'}}, $total;
- }
-
- {
- my $total;
- $total->{'total_item'} = '<b>'. $self->balance_due_msg. '</b>';
- $total->{'total_amount'} =
- "<b>$money_char". sprintf('%.2f', $self->owed + $pr_total ). '</b>';
- push @{$invoice_data{'total_items'}}, $total;
- }
-
- warn "filling in HTML template for invoice ". $self->invnum. "\n"
- if $DEBUG;
- warn join("\n", map " $_ => ".$invoice_data{$_}, keys %invoice_data ). "\n"
- if $DEBUG > 1;
-
- $html_template->fill_in( HASH => \%invoice_data);
-}
-
-# quick subroutine for print_latex
-#
-# There are ten characters that LaTeX treats as special characters, which
-# means that they do not simply typeset themselves:
-# # $ % & ~ _ ^ \ { }
-#
-# TeX ignores blanks following an escaped character; if you want a blank (as
-# in "10% of ..."), you have to "escape" the blank as well ("10\%\ of ...").
-
-sub _latex_escape {
- my $value = shift;
- $value =~ s/([#\$%&~_\^{}])( )?/"\\$1". ( ( defined($2) && length($2) ) ? "\\$2" : '' )/ge;
- $value =~ s/([<>])/\$$1\$/g;
- $value;
-}
-
-#utility methods for print_*
-
-sub terms {
- my $self = shift;
-
- #check for an invoice- specific override (eventually)
-
- #check for a customer- specific override
- return $self->cust_main->invoice_terms
- if $self->cust_main->invoice_terms;
-
- #use configured default or default default
- $conf->config('invoice_default_terms') || 'Payable upon receipt';
-}
-
-sub due_date {
- my $self = shift;
- my $duedate = '';
- if ( $self->terms =~ /^\s*Net\s*(\d+)\s*$/ ) {
- $duedate = $self->_date() + ( $1 * 86400 );
- }
- $duedate;
-}
-
-sub due_date2str {
- my $self = shift;
- $self->due_date ? time2str(shift, $self->due_date) : '';
-}
-
-sub balance_due_msg {
- my $self = shift;
- my $msg = 'Balance Due';
- return $msg unless $self->terms;
- if ( $self->due_date ) {
- $msg .= ' - Please pay by '. $self->due_date2str('%x');
- } elsif ( $self->terms ) {
- $msg .= ' - '. $self->terms;
- }
- $msg;
-}
-
-sub _items {
- my $self = shift;
- my @display = scalar(@_)
- ? @_
- : qw( _items_previous _items_pkg );
- #: qw( _items_pkg );
- #: qw( _items_previous _items_pkg _items_tax _items_credits _items_payments );
- my @b = ();
- foreach my $display ( @display ) {
- push @b, $self->$display(@_);
- }
- @b;
-}