+ map { [ " ". $_->[0]. ": ". $_->[1], '' ] }
+ $cust_bill_pkg->cust_pkg->h_labels( $cust_bill_pkg->edate,
+ $cust_bill_pkg->sdate );
+ }
+
+ push @buf, map { [ " $_", '' ] } $cust_bill_pkg->details;
+
+ } else { #pkgnum tax or one-shot line item
+
+ if ( $cust_bill_pkg->setup != 0 ) {
+ push @buf, [ $desc,
+ $money_char. sprintf("%10.2f", $cust_bill_pkg->setup) ];
+ }
+ if ( $cust_bill_pkg->recur != 0 ) {
+ push @buf, [ "$desc (". time2str("%x", $cust_bill_pkg->sdate). " - "
+ . time2str("%x", $cust_bill_pkg->edate). ")",
+ $money_char. sprintf("%10.2f", $cust_bill_pkg->recur)
+ ];
+ }
+
+ }
+
+ }
+
+ push @buf,['','-----------'];
+ push @buf,['Total New Charges',
+ $money_char. sprintf("%10.2f",$self->charged) ];
+ push @buf,['',''];
+
+ push @buf,['','-----------'];
+ push @buf,['Total Charges',
+ $money_char. sprintf("%10.2f",$self->charged + $pr_total) ];
+ push @buf,['',''];
+
+ #credits
+ foreach ( $self->cust_credited ) {
+
+ #something more elaborate if $_->amount ne $_->cust_credit->credited ?
+
+ my $reason = substr($_->cust_credit->reason,0,32);
+ $reason .= '...' if length($reason) < length($_->cust_credit->reason);
+ $reason = " ($reason) " if $reason;
+ push @buf,[
+ "Credit #". $_->crednum. " (". time2str("%x",$_->cust_credit->_date) .")".
+ $reason,
+ $money_char. sprintf("%10.2f",$_->amount)
+ ];
+ }
+ #foreach ( @cr_cust_credit ) {
+ # push @buf,[
+ # "Credit #". $_->crednum. " (" . time2str("%x",$_->_date) .")",
+ # $money_char. sprintf("%10.2f",$_->credited)
+ # ];
+ #}
+
+ #get & print payments
+ foreach ( $self->cust_bill_pay ) {
+
+ #something more elaborate if $_->amount ne ->cust_pay->paid ?
+
+ push @buf,[
+ "Payment received ". time2str("%x",$_->cust_pay->_date ),
+ $money_char. sprintf("%10.2f",$_->amount )
+ ];
+ }
+
+ #balance due
+ my $balance_due_msg = $self->balance_due_msg;
+
+ push @buf,['','-----------'];
+ push @buf,[$balance_due_msg, $money_char.
+ sprintf("%10.2f", $balance_due ) ];
+
+ #create the template
+ $template ||= $self->_agent_template;
+ my $templatefile = 'invoice_template';
+ $templatefile .= "_$template" if length($template);
+ my @invoice_template = $conf->config($templatefile)
+ or die "cannot load config file $templatefile";
+ $invoice_lines = 0;
+ my $wasfunc = 0;
+ foreach ( grep /invoice_lines\(\d*\)/, @invoice_template ) { #kludgy
+ /invoice_lines\((\d*)\)/;
+ $invoice_lines += $1 || scalar(@buf);
+ $wasfunc=1;
+ }
+ die "no invoice_lines() functions in template?" unless $wasfunc;
+ my $invoice_template = new Text::Template (
+ TYPE => 'ARRAY',
+ SOURCE => [ map "$_\n", @invoice_template ],
+ ) or die "can't create new Text::Template object: $Text::Template::ERROR";
+ $invoice_template->compile()
+ or die "can't compile template: $Text::Template::ERROR";
+
+ #setup template variables
+ package FS::cust_bill::_template; #!
+ use vars qw( $invnum $date $page $total_pages @address $overdue @buf $agent );
+
+ $invnum = $self->invnum;
+ $date = $self->_date;
+ $page = 1;
+ $agent = $self->cust_main->agent->agent;
+
+ if ( $FS::cust_bill::invoice_lines ) {
+ $total_pages =
+ int( scalar(@FS::cust_bill::buf) / $FS::cust_bill::invoice_lines );
+ $total_pages++
+ if scalar(@FS::cust_bill::buf) % $FS::cust_bill::invoice_lines;
+ } else {
+ $total_pages = 1;
+ }
+
+ #format address (variable for the template)
+ my $l = 0;
+ @address = ( '', '', '', '', '', '' );
+ package FS::cust_bill; #!
+ $FS::cust_bill::_template::address[$l++] =
+ $cust_main->payname.
+ ( ( $cust_main->payby eq 'BILL' ) && $cust_main->payinfo
+ ? " (P.O. #". $cust_main->payinfo. ")"
+ : ''
+ )
+ ;
+ $FS::cust_bill::_template::address[$l++] = $cust_main->company
+ if $cust_main->company;
+ $FS::cust_bill::_template::address[$l++] = $cust_main->address1;
+ $FS::cust_bill::_template::address[$l++] = $cust_main->address2
+ if $cust_main->address2;
+ $FS::cust_bill::_template::address[$l++] =
+ $cust_main->city. ", ". $cust_main->state. " ". $cust_main->zip;
+
+ my $countrydefault = $conf->config('countrydefault') || 'US';
+ $FS::cust_bill::_template::address[$l++] = code2country($cust_main->country)
+ unless $cust_main->country eq $countrydefault;
+
+ # #overdue? (variable for the template)
+ # $FS::cust_bill::_template::overdue = (
+ # $balance_due > 0
+ # && $today > $self->_date
+ ## && $self->printed > 1
+ # && $self->printed > 0
+ # );
+
+ #and subroutine for the template
+ sub FS::cust_bill::_template::invoice_lines {
+ my $lines = shift || scalar(@buf);
+ map {
+ scalar(@buf) ? shift @buf : [ '', '' ];
+ }
+ ( 1 .. $lines );
+ }
+
+ #and fill it in
+ $FS::cust_bill::_template::page = 1;
+ my $lines;
+ my @collect;
+ while (@buf) {
+ push @collect, split("\n",
+ $invoice_template->fill_in( PACKAGE => 'FS::cust_bill::_template' )
+ );
+ $FS::cust_bill::_template::page++;
+ }
+
+ map "$_\n", @collect;
+
+}
+
+=item print_latex [ TIME [ , TEMPLATE ] ]
+
+Internal method - returns a filename of a filled-in LaTeX template for this
+invoice (Note: add ".tex" to get the actual filename).
+
+See print_ps and print_pdf for methods that return PostScript and PDF output.
+
+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
+
+#still some false laziness w/print_text and print_html (and send_csv) (mostly print_text should use _items stuff though)
+sub print_latex {
+
+ my( $self, $today, $template ) = @_;
+ $today ||= time;
+ warn "FS::cust_bill::print_latex called on $self with suffix $template\n"
+ if $DEBUG;
+
+ 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)$/;
+
+ my( $pr_total, @pr_cust_bill ) = $self->previous; #previous balance
+# my( $cr_total, @cr_cust_credit ) = $self->cust_credit; #credits
+ #my $balance_due = $self->owed + $pr_total - $cr_total;
+ my $balance_due = $self->owed + $pr_total;
+
+ #create the template
+ $template ||= $self->_agent_template;
+ my $templatefile = 'invoice_latex';
+ my $suffix = length($template) ? "_$template" : '';
+ $templatefile .= $suffix;
+ my @invoice_template = map "$_\n", $conf->config($templatefile)
+ or die "cannot load config file $templatefile";
+
+ my($format, $text_template);
+ if ( grep { /^%%Detail/ } @invoice_template ) {
+ #change this to a die when the old code is removed
+ warn "old-style invoice template $templatefile; ".
+ "patch with conf/invoice_latex.diff or use new conf/invoice_latex*\n";
+ $format = 'old';
+ } else {
+ $format = 'Text::Template';
+ $text_template = new Text::Template(
+ TYPE => 'ARRAY',
+ SOURCE => \@invoice_template,
+ DELIMITERS => [ '[@--', '--@]' ],
+ );
+
+ $text_template->compile()
+ or die 'While compiling ' . $templatefile . ': ' . $Text::Template::ERROR;
+ }
+
+ my $returnaddress;
+ if ( length($conf->config_orbase('invoice_latexreturnaddress', $template)) ) {
+ $returnaddress = join("\n",
+ $conf->config_orbase('invoice_latexreturnaddress', $template)
+ );
+ } else {
+ $returnaddress = '~';
+ }
+
+ my %invoice_data = (
+ 'invnum' => $self->invnum,
+ 'date' => time2str('%b %o, %Y', $self->_date),
+ 'today' => time2str('%b %o, %Y', $today),
+ 'agent' => _latex_escape($cust_main->agent->agent),
+ 'payname' => _latex_escape($cust_main->payname),
+ 'company' => _latex_escape($cust_main->company),
+ 'address1' => _latex_escape($cust_main->address1),
+ 'address2' => _latex_escape($cust_main->address2),
+ 'city' => _latex_escape($cust_main->city),
+ 'state' => _latex_escape($cust_main->state),
+ 'zip' => _latex_escape($cust_main->zip),
+ 'footer' => join("\n", $conf->config_orbase('invoice_latexfooter', $template) ),
+ 'smallfooter' => join("\n", $conf->config_orbase('invoice_latexsmallfooter', $template) ),
+ 'returnaddress' => $returnaddress,
+ 'quantity' => 1,
+ 'terms' => $conf->config('invoice_default_terms') || 'Payable upon receipt',
+ #'notes' => join("\n", $conf->config('invoice_latexnotes') ),
+ 'conf_dir' => "$FS::UID::conf_dir/conf.$FS::UID::datasrc",
+ );
+
+ my $countrydefault = $conf->config('countrydefault') || 'US';
+ if ( $cust_main->country eq $countrydefault ) {
+ $invoice_data{'country'} = '';
+ } else {
+ $invoice_data{'country'} = _latex_escape(code2country($cust_main->country));
+ }
+
+ $invoice_data{'notes'} =
+ join("\n",
+# #do variable substitutions in notes
+# map { my $b=$_; $b =~ s/\$(\w+)/$invoice_data{$1}/eg; $b }
+ $conf->config_orbase('invoice_latexnotes', $template)
+ );
+ warn "invoice notes: ". $invoice_data{'notes'}. "\n"
+ if $DEBUG;
+
+ $invoice_data{'footer'} =~ s/\n+$//;
+ $invoice_data{'smallfooter'} =~ s/\n+$//;
+ $invoice_data{'notes'} =~ s/\n+$//;
+
+ $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 $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;
+
+}
+
+=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 = $self->print_latex(@_);
+
+ my $dir = $FS::UID::conf_dir. "cache.". $FS::UID::datasrc;
+ chdir($dir);
+
+ 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('dvips', '-q', '-t', 'letter', "$file.dvi", '-o', "$file.ps" ) == 0
+ or die "dvips failed";
+
+ open(POSTSCRIPT, "<$file.ps")
+ or die "can't open $file.ps: $! (error in LaTeX template?)\n";
+
+ unlink("$file.dvi", "$file.log", "$file.aux", "$file.ps", "$file.tex");
+
+ my $ps = '';
+ while (<POSTSCRIPT>) {
+ $ps .= $_;
+ }
+
+ close POSTSCRIPT;
+
+ return $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 = $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");
+
+ 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 = (
+ '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' => $conf->config('invoice_default_terms')
+ || 'Payable upon receipt',
+ '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('invoice_htmlreturnaddress', $template) );
+ } else {
+ $invoice_data{'returnaddress'} =
+ join("\n", map {
+ s/~/ /g;
+ s/\\\\\*?\s*$/<BR>/;
+ s/\\hyphenation\{[\w\s\-]+\}//;
+ $_;
+ }
+ $conf->config_orbase( 'invoice_latexreturnaddress',
+ $template
+ )
+ );
+ }
+
+ 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 -->/;
+ s/\\section\*\{\\textsc\{(.)(.*)\}\}/<p><b><font size="+1">$1<\/font>\U$2<\/b>/;
+ s/\\begin\{enumerate\}/<ol>/;
+ s/\\item / <li>/;
+ s/\\end\{enumerate\}/<\/ol>/;
+ s/\\textbf\{(.*)\}/<b>$1<\/b>/;
+ $_;
+ }
+ $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;
+ }
+
+ $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 balance_due_msg {
+ my $self = shift;
+ my $msg = 'Balance Due';
+ return $msg unless $conf->exists('invoice_default_terms');
+ if ( $conf->config('invoice_default_terms') =~ /^\s*Net\s*(\d+)\s*$/ ) {
+ $msg .= ' - Please pay by '. time2str("%x", $self->_date + ($1*86400) );
+ } elsif ( $conf->config('invoice_default_terms') ) {
+ $msg .= ' - '. $conf->config('invoice_default_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;
+}
+
+sub _items_previous {
+ my $self = shift;
+ my $cust_main = $self->cust_main;
+ my( $pr_total, @pr_cust_bill ) = $self->previous; #previous balance
+ my @b = ();
+ foreach ( @pr_cust_bill ) {
+ push @b, {
+ 'description' => 'Previous Balance, Invoice #'. $_->invnum.
+ ' ('. time2str('%x',$_->_date). ')',
+ #'pkgpart' => 'N/A',
+ 'pkgnum' => 'N/A',
+ 'amount' => sprintf("%.2f", $_->owed),
+ };
+ }
+ @b;
+
+ #{
+ # 'description' => 'Previous Balance',
+ # #'pkgpart' => 'N/A',
+ # 'pkgnum' => 'N/A',
+ # 'amount' => sprintf("%10.2f", $pr_total ),
+ # 'ext_description' => [ map {
+ # "Invoice ". $_->invnum.
+ # " (". time2str("%x",$_->_date). ") ".
+ # sprintf("%10.2f", $_->owed)
+ # } @pr_cust_bill ],
+
+ #};
+}
+
+sub _items_pkg {
+ my $self = shift;
+ my @cust_bill_pkg = grep { $_->pkgnum } $self->cust_bill_pkg;
+ $self->_items_cust_bill_pkg(\@cust_bill_pkg, @_);
+}
+
+sub _items_tax {
+ my $self = shift;
+ my @cust_bill_pkg = grep { ! $_->pkgnum } $self->cust_bill_pkg;
+ $self->_items_cust_bill_pkg(\@cust_bill_pkg, @_);
+}
+
+sub _items_cust_bill_pkg {
+ my $self = shift;
+ my $cust_bill_pkg = shift;
+
+ my @b = ();
+ foreach my $cust_bill_pkg ( @$cust_bill_pkg ) {
+
+ my $desc = $cust_bill_pkg->desc;
+
+ if ( $cust_bill_pkg->pkgnum > 0 ) {
+
+ if ( $cust_bill_pkg->setup != 0 ) {
+ my $description = $desc;
+ $description .= ' Setup' if $cust_bill_pkg->recur != 0;
+ my @d = $cust_bill_pkg->cust_pkg->h_labels_short($self->_date);
+ push @d, $cust_bill_pkg->details if $cust_bill_pkg->recur == 0;
+ push @b, {
+ description => $description,
+ #pkgpart => $part_pkg->pkgpart,
+ pkgnum => $cust_bill_pkg->pkgnum,
+ amount => sprintf("%.2f", $cust_bill_pkg->setup),
+ ext_description => \@d,
+ };