From 162a742110ede26cbb904b1f38c6a99e4b692eef Mon Sep 17 00:00:00 2001 From: jeff Date: Fri, 29 Feb 2008 17:57:38 +0000 Subject: [PATCH] refactor print_*; invoice sections by package class; could still stand some more refactoring --- FS/FS/Conf.pm | 7 + FS/FS/cust_bill.pm | 1295 +++++++++++++++++++++++++--------------------------- conf/invoice_html | 78 +++- conf/invoice_latex | 96 ++-- 4 files changed, 734 insertions(+), 742 deletions(-) diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index fc67c35aa..0c0485a2a 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -845,6 +845,13 @@ worry that config_items is freeside-specific and icky. 'select_enum' => [ '', 'Payable upon receipt', 'Net 0', 'Net 10', 'Net 15', 'Net 30', 'Net 45', 'Net 60' ], }, + { + 'key' => 'invoice_sections', + 'section' => 'billing', + 'description' => 'Split invoice into sections and label according to package type when enabled.', + 'type' => 'checkbox', + }, + { 'key' => 'payment_receipt_email', 'section' => 'billing', diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm index 2ad28b8ee..ee95be83a 100644 --- a/FS/FS/cust_bill.pm +++ b/FS/FS/cust_bill.pm @@ -1519,325 +1519,284 @@ L and L for conversion functions. =cut -#still some false laziness w/_items stuff (and send_csv) sub print_text { - my( $self, $today, $template ) = @_; - $today ||= time; - -# my $invnum = $self->invnum; - 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; - - #my @collect = (); - #my($description,$amount); - @buf = (); - - #previous balance - foreach ( @pr_cust_bill ) { - push @buf, [ - "Previous Balance, Invoice #". $_->invnum. - " (". time2str("%x",$_->_date). ")", - $money_char. sprintf("%10.2f",$_->owed) - ]; - } - if (@pr_cust_bill) { - push @buf,['','-----------']; - push @buf,[ 'Total Previous Balance', - $money_char. sprintf("%10.2f",$pr_total ) ]; - push @buf,['','']; - } - - #new charges - foreach my $cust_bill_pkg ( - ( grep { $_->pkgnum } $self->cust_bill_pkg ), #packages first - ( grep { ! $_->pkgnum } $self->cust_bill_pkg ), #then taxes - ) { - - 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; - push @buf, [ $description, - $money_char. sprintf("%10.2f", $cust_bill_pkg->setup) ]; - push @buf, - map { [ " ". $_->[0]. ": ". $_->[1], '' ] } - $cust_bill_pkg->cust_pkg->h_labels($self->_date); - } - - if ( $cust_bill_pkg->recur != 0 ) { - push @buf, [ - $desc . - ( $conf->exists('disable_line_item_date_ranges') - ? '' - : " (" . time2str("%x", $cust_bill_pkg->sdate) . " - " . - time2str("%x", $cust_bill_pkg->edate) . ")" - ), - $money_char. sprintf("%10.2f", $cust_bill_pkg->recur) - ]; - push @buf, - 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,['','']; + my %params = ( 'format' => 'template' ); + $params{'time'} = $today if $today; + $params{'template'} = $template if $template; - push @buf,['','-----------']; - push @buf,['Total Charges', - $money_char. sprintf("%10.2f",$self->charged + $pr_total) ]; - push @buf,['','']; + $self->print_generic( %params ); +} - #credits - foreach ( $self->cust_credited ) { +=item print_latex [ TIME [ , TEMPLATE ] ] - #something more elaborate if $_->amount ne $_->cust_credit->credited ? +Internal method - returns a filename of a filled-in LaTeX template for this +invoice (Note: add ".tex" to get the actual filename), and a filename of +an associated logo (with the .eps extension included). - 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) - # ]; - #} +See print_ps and print_pdf for methods that return PostScript and PDF output. - #get & print payments - foreach ( $self->cust_bill_pay ) { +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. Also see +L and L for conversion functions. - #something more elaborate if $_->amount ne ->cust_pay->paid ? +=cut - push @buf,[ - "Payment received ". time2str("%x",$_->cust_pay->_date ), - $money_char. sprintf("%10.2f",$_->amount ) - ]; - } +sub print_latex { - #balance due - my $balance_due_msg = $self->balance_due_msg; + my( $self, $today, $template ) = @_; - push @buf,['','-----------']; - push @buf,[$balance_due_msg, $money_char. - sprintf("%10.2f", $balance_due ) ]; + my %params = ( 'format' => 'latex' ); + $params{'time'} = $today if $today; + $params{'template'} = $template if $template; - #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( $company_name $company_address - $custnum $invnum $date $agent @address $overdue - $page $total_pages @buf - ); - $custnum = $self->custnum; - $invnum = $self->invnum; - $date = $self->_date; - $agent = $self->cust_main->agent->agent; - $page = 1; - - 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 $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"; - 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 - # ); - - $FS::cust_bill::_template::company_name = $conf->config('company_name'); - $FS::cust_bill::_template::company_address = - join("\n", $conf->config('company_address') ). "\n"; - - #and subroutine for the template - sub FS::cust_bill::_template::invoice_lines { - my $lines = shift || scalar(@buf); - map { - scalar(@buf) ? shift @buf : [ '', '' ]; - } - ( 1 .. $lines ); + 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; + $params{'logo_file'} = $lh->filename; - #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++; - } + my @filled_in = $self->print_generic( %params ); + + my $fh = new File::Temp( TEMPLATE => 'invoice.'. $self->invnum. '.XXXXXXXX', + DIR => $dir, + SUFFIX => '.tex', + UNLINK => 0, + ) or die "can't open temp file: $!\n"; + print $fh join('', @filled_in ); + close $fh; - map "$_\n", @collect; + $fh->filename =~ /^(.*).tex$/ or die "unparsable filename: ". $fh->filename; + return ($1, $params{'logo_file'}); } -=item print_latex [ TIME [ , TEMPLATE ] ] +=item print_generic OPTIONS_HASH -Internal method - returns a filename of a filled-in LaTeX template for this -invoice (Note: add ".tex" to get the actual filename), and a filename of -an associated logo (with the .eps extension included). +Internal method - returns a filled-in template for this invoice as a scalar. 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 +Non optional options include + format - latex, html, template + +Optional options include + +template - a value used as a suffix for a configuration template + +time - a 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. Also see L and L for conversion functions. +cid - + =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 { +sub print_generic { - my( $self, $today, $template ) = @_; - $today ||= time; - warn "FS::cust_bill::print_latex called on $self with suffix $template\n" + my( $self, %params ) = @_; + my $today = $params{today} ? $params{today} : time; + warn "FS::cust_bill::print_generic called on $self with suffix $params{template}\n" if $DEBUG; + my $format = $params{format}; + die "Unknown format: $format" + unless $format =~ /^(latex|html|template)$/; + 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; + + my %delimiters = ( 'latex' => [ '[@--', '--@]' ], + 'html' => [ '<%=', '%>' ], + 'template' => [ '{', '}' ], + ); #create the template - $template ||= $self->_agent_template; - my $templatefile = 'invoice_latex'; - my $suffix = length($template) ? "_$template" : ''; - $templatefile .= $suffix; + my $template = $params{template} ? $params{template} : $self->_agent_template; + my $templatefile = "invoice_$format"; + $templatefile .= "_$template" + if length($template); my @invoice_template = map "$_\n", $conf->config($templatefile) or die "cannot load config file $templatefile"; - my($format, $text_template); - if ( grep { /^%%Detail/ } @invoice_template ) { + my $old_latex = ''; + if ( $format eq 'latex' && 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 => [ '[@--', '--@]' ], - ); + $old_latex = 'true'; + @invoice_template = _translate_old_latex_format(@invoice_template); + } + + my $text_template = new Text::Template( + TYPE => 'ARRAY', + SOURCE => \@invoice_template, + DELIMITERS => $delimiters{$format}, + ); + + $text_template->compile() + or die 'While compiling ' . $templatefile . ': ' . $Text::Template::ERROR; + + + # additional substitution could possibly cause breakage in existing templates + my %convert_maps = ( + 'latex' => { + 'notes' => sub { map "$_", @_ }, + 'footer' => sub { map "$_", @_ }, + 'smallfooter' => sub { map "$_", @_ }, + 'returnaddress' => sub { map "$_", @_ }, + }, + 'html' => { + 'notes' => + sub { + map { + s/%%(.*)$//g; + s/\\section\*\{\\textsc\{(.)(.*)\}\}/

$1<\/font>\U$2<\/b>/g; + s/\\begin\{enumerate\}/

    /g; + s/\\item /
  1. /g; + s/\\end\{enumerate\}/<\/ol>/g; + s/\\textbf\{(.*)\}/$1<\/b>/g; + s/\\\\\*/ /; + s/\\dollar ?/\$/g; + $_; + } @_ + }, + 'footer' => + sub { map { s/~/ /g; s/\\\\\*?\s*$/
    /; $_; } @_ }, + 'smallfooter' => + sub { map { s/~/ /g; s/\\\\\*?\s*$/
    /; $_; } @_ }, + 'returnaddress' => + sub { + map { + s/~/ /g; + s/\\\\\*?\s*$/
    /; + s/\\hyphenation\{[\w\s\-]+}//; + $_; + } @_ + }, + }, + 'template' => { + 'notes' => + sub { + map { + s/%%.*$//g; + s/\\section\*\{\\textsc\{(.*)\}\}/\U$1/g; + s/\\begin\{enumerate\}//g; + s/\\item / * /g; + s/\\end\{enumerate\}//g; + s/\\textbf\{(.*)\}/$1/g; + s/\\\\\*/ /; + s/\\dollar ?/\$/g; + $_; + } @_ + }, + 'footer' => + sub { map { s/~/ /g; s/\\\\\*?\s*$/\n/; $_; } @_ }, + 'smallfooter' => + sub { map { s/~/ /g; s/\\\\\*?\s*$/\n/; $_; } @_ }, + 'returnaddress' => + sub { + map { + s/~/ /g; + s/\\\\\*?\s*$/\n/; # dubious + s/\\hyphenation\{[\w\s\-]+}//; + $_; + } @_ + }, + }, + ); - $text_template->compile() - or die 'While compiling ' . $templatefile . ': ' . $Text::Template::ERROR; - } + # hashes for differing output formats + my %nbsps = ( 'latex' => '~', + 'html' => '', # '&nbps;' would be nice + 'template' => '', # not used + ); + my $nbsp = $nbsps{$format}; + + my %escape_functions = ( 'latex' => \&_latex_escape, + 'html' => \&encode_entities, + 'template' => sub { shift }, + ); + my $escape_function = $escape_functions{$format}; + + my %date_formats = ( 'latex' => '%b, %o, %Y', + 'html' => '%b %o, %Y', + 'template' => '%s', + ); + my $date_format = $date_formats{$format}; + + my %embolden_functions = ( 'latex' => sub { return '\textbf{'. shift(). '}' + }, + 'html' => sub { return ''. shift(). '' + }, + 'template' => sub { shift }, + ); + my $embolden_function = $embolden_functions{$format}; + + + # generate template variables my $returnaddress; - if ( length($conf->config_orbase('invoice_latexreturnaddress', $template)) ) { + if ( + defined( $conf->config_orbase( "invoice_${format}returnaddress", + $template + ) + ) + && length( $conf->config_orbase( "invoice_${format}returnaddress", + $template + ) + ) + ) { $returnaddress = join("\n", - $conf->config_orbase('invoice_latexreturnaddress', $template) + $conf->config_orbase("invoice_${format}returnaddress", $template) ); + } elsif ( grep /\S/, + $conf->config_orbase('invoice_latexreturnaddress', $template) ) { + + my $convert_map = $convert_maps{$format}{'returnaddress'}; + $returnaddress = + join( "\n", + &$convert_map( $conf->config_orbase( "invoice_latexreturnaddress", + $template + ) + ) + ); } elsif ( grep /\S/, $conf->config('company_address') ) { + $returnaddress = join( "\n", $conf->config('company_address') ); + $returnaddress = join( '\\*'."\n", map s/( {2,})/'~' x length($1)/eg, $conf->config('company_address') - ); + ) + if $format eq 'latex'; } else { my $warning = "Couldn't find a return address; ". "do you need to set the company_address configuration value?"; warn "$warning\n"; - $returnaddress = '~'; + $returnaddress = $nbsp; #$returnaddress = $warning; } @@ -1847,40 +1806,84 @@ sub print_latex { 'company_address' => join("\n", $conf->config('company_address') ). "\n", 'custnum' => $self->custnum, 'invnum' => $self->invnum, - 'date' => time2str('%b %o, %Y', $self->_date), + 'date' => time2str($date_format, $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), + 'agent' => &$escape_function($cust_main->agent->agent), + 'payname' => &$escape_function($cust_main->payname), + 'company' => &$escape_function($cust_main->company), + 'address1' => &$escape_function($cust_main->address1), + 'address2' => &$escape_function($cust_main->address2), + 'city' => &$escape_function($cust_main->city), + 'state' => &$escape_function($cust_main->state), + 'zip' => &$escape_function($cust_main->zip), 'returnaddress' => $returnaddress, 'quantity' => 1, 'terms' => $self->terms, + 'template' => $params{'template'}, #'notes' => join("\n", $conf->config('invoice_latexnotes') ), # better hang on to conf_dir for a while 'conf_dir' => "$FS::UID::conf_dir/conf.$FS::UID::datasrc", + 'page' => 1, + 'total_pages' => 1, ); + $invoice_data{'cid'} = $params{'cid'} + if $params{'cid'}; + 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{'country'} = &$escape_function(code2country($cust_main->country)); } + my @address = (); + $invoice_data{'address'} = \@address; + push @address, + $cust_main->payname. + ( ( $cust_main->payby eq 'BILL' ) && $cust_main->payinfo + ? " (P.O. #". $cust_main->payinfo. ")" + : '' + ) + ; + push @address, $cust_main->company + if $cust_main->company; + push @address, $cust_main->address1; + push @address, $cust_main->address2 + if $cust_main->address2; + push @address, + $cust_main->city. ", ". $cust_main->state. " ". $cust_main->zip; + push @address, $invoice_data{'country'} + if $invoice_data{'country'}; + push @address, '' + while (scalar(@address) < 5); + #do variable substitution in notes, footer, smallfooter foreach my $include (qw( notes footer smallfooter )) { + my @inc_src = $conf->config_orbase("invoice_latex$include", $template ); + my $convert_map = $convert_maps{$format}{$include}; + + if ( + defined( $conf->config_orbase("invoice_${format}$include", $template) ) + && length( $conf->config_orbase('invoice_${format}$include', $template) ) + ) { + @inc_src = $conf->config_orbase("invoice_${format}$include", $template ); + } else { + @inc_src = + map { s/\[@--/$delimiters{$format}[0]/g; + s/--@]/$delimiters{$format}[1]/g; + $_; + } + &$convert_map( + $conf->config_orbase("invoice_latex$include", $template ) + ); + } + my $inc_tt = new Text::Template ( TYPE => 'ARRAY', - SOURCE => [ map "$_\n", - $conf->config_orbase("invoice_latex$include", $template ) - ], - DELIMITERS => [ '[@--', '--@]' ], + SOURCE => [ map "$_\n", @inc_src ], + DELIMITERS => $delimiters{$format}, ) or die "can't create new Text::Template object: $Text::Template::ERROR"; $inc_tt->compile() @@ -1888,244 +1891,291 @@ sub print_latex { $invoice_data{$include} = $inc_tt->fill_in( HASH => \%invoice_data ); - $invoice_data{$include} =~ s/\n+$//; + $invoice_data{$include} =~ s/\n+$// + if ($format eq 'latex'); } $invoice_data{'po_line'} = ( $cust_main->payby eq 'BILL' && $cust_main->payinfo ) - ? _latex_escape("Purchase Order #". $cust_main->payinfo) - : '~'; + ? &$escape_function("Purchase Order #". $cust_main->payinfo) + : $nbsp; - 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; + 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; + + my %money_chars = ( 'latex' => '', + 'html' => $conf->config('money_char') || '$', + 'template' => '', + ); + my $money_char = $money_chars{$format}; + + my %other_money_chars = ( 'latex' => '\dollar ', + 'html' => $conf->config('money_char') || '$', + 'template' => '', + ); + my $other_money_char = $other_money_chars{$format}; + + my @detail_items = (); + my @total_items = (); + my @buf = (); + my @sections = (); + + $invoice_data{'detail_items'} = \@detail_items; + $invoice_data{'total_items'} = \@total_items; + $invoice_data{'buf'} = \@buf; + $invoice_data{'sections'} = \@sections; - push @filled_in, @total_fill; + my $previous_section = { 'description' => 'Previous Charges', + 'subtotal' => $other_money_char. + sprintf('%.2f', $pr_total), + }; + + my $multisection = $conf->exists('invoice_sections', $cust_main->agentnum); + if ( $multisection ) { + push @sections, $self->_items_sections; + }else{ + push @sections, { 'description' => '', 'subtotal' => '' }; + } + + foreach my $line_item ( $self->_items_previous ) { + my $detail = { + ext_description => [], + }; + $detail->{'ref'} = $line_item->{'pkgnum'}; + $detail->{'quantity'} = 1; + $detail->{'section'} = $previous_section; + $detail->{'description'} = &$escape_function($line_item->{'description'}); + if ( exists $line_item->{'ext_description'} ) { + @{$detail->{'ext_description'}} = map { + &$escape_function($_); + } @{$line_item->{'ext_description'}}; + } + { + my $money = $old_latex ? '' : $money_char; + $detail->{'amount'} = $money. $line_item->{'amount'}; + } + $detail->{'product_code'} = $line_item->{'pkgpart'} || 'N/A'; - } 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; - } + push @detail_items, $detail; + push @buf, [ $detail->{'description'}, + $money_char. sprintf("%10.2f", $line_item->{'amount'}), + ]; + } - } + if (@pr_cust_bill) { + push @buf, ['','-----------']; + push @buf, [ 'Total Previous Balance', + $money_char. sprintf("%10.2f", $pr_total) ]; + push @buf, ['','']; + } - sub nounder { - my $var = $1; - $var =~ s/_/\-/g; - $var; - } + foreach my $section (@sections) { + + $section->{'subtotal'} = $other_money_char. + sprintf('%.2f', $section->{'subtotal'}) + if $multisection; - } elsif ( $format eq 'Text::Template' ) { + if ( $section->{'description'} ) { + push @buf, ( [ &$escape_function($section->{'description'}), '' ], + [ '', '' ], + ); + } - my @detail_items = (); - my @total_items = (); + my %options = (); + $options{'section'} = $section if $multisection; - $invoice_data{'detail_items'} = \@detail_items; - $invoice_data{'total_items'} = \@total_items; - - foreach my $line_item ( $self->_items ) { + foreach my $line_item ( $self->_items_pkg(%options) ) { my $detail = { ext_description => [], }; $detail->{'ref'} = $line_item->{'pkgnum'}; $detail->{'quantity'} = 1; - $detail->{'description'} = _latex_escape($line_item->{'description'}); + $detail->{'section'} = $section; + $detail->{'description'} = &$escape_function($line_item->{'description'}); if ( exists $line_item->{'ext_description'} ) { @{$detail->{'ext_description'}} = map { - _latex_escape($_); + &$escape_function($_); } @{$line_item->{'ext_description'}}; } - $detail->{'amount'} = $line_item->{'amount'}; + { + my $money = $old_latex ? '' : $money_char; + $detail->{'amount'} = $money. $line_item->{'amount'}; + } $detail->{'product_code'} = $line_item->{'pkgpart'} || 'N/A'; push @detail_items, $detail; + push @buf, ( [ $detail->{'description'}, + $money_char. sprintf("%10.2f", $line_item->{'amount'}), + ], + map { [ " ". $_, '' ] } @{$detail->{'ext_description'}}, + ); + } + + if ( $section->{'description'} ) { + push @buf, ( ['','-----------'], + [ $section->{'description'}. ' sub-total', + $money_char. sprintf("%10.2f", $section->{'subtotal'}) + ], + [ '', '' ], + [ '', '' ], + ); } + } - 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 ( $multisection ) { + unshift @sections, $previous_section; + } + + my $taxtotal = 0; + foreach my $tax ( $self->_items_tax ) { + my $total = {}; + $total->{'total_item'} = &$escape_function($tax->{'description'}); + $taxtotal += $tax->{'amount'}; + $total->{'total_amount'} = $other_money_char. $tax->{'amount'}; + push @total_items, $total; + push @buf,[ $total->{'total_item'}, + $money_char. sprintf("%10.2f", $total->{'total_amount'}), + ]; + + } - if ( $taxtotal ) { - my $total = {}; + if ( $taxtotal ) { + my $total = {}; + if ( $multisection ) { + $total->{'total_item'} = 'New charges sub-total'; + }else{ $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; } + $total->{'total_amount'} = + $other_money_char. sprintf('%.2f', $self->charged - $taxtotal ); + unshift @total_items, $total; + } - #foreach my $thing ( sort { $a->_date <=> $b->_date } $self->_items_credits, $self->_items_payments + push @buf,['','-----------']; + push @buf,['Total New Charges', + $money_char. sprintf("%10.2f",$self->charged) ]; + push @buf,['','']; + + { + my $total = {}; + $total->{'total_item'} = &$embolden_function('Total'); + $total->{'total_amount'} = + $total->{'total_amount'} = + &$embolden_function( + $other_money_char. sprintf('%.2f', $self->charged + $pr_total ) + ); + push @total_items, $total; + push @buf,['','-----------']; + push @buf,['Total Charges', + $money_char. sprintf("%10.2f",$self->charged + $pr_total) ]; + push @buf,['','']; + } - # 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; - } + + #foreach my $thing ( sort { $a->_date <=> $b->_date } $self->_items_credits, $self->_items_payments - # 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; - } + # credits + foreach my $credit ( $self->_items_credits ) { + my $total; + $total->{'total_item'} = &$escape_function($credit->{'description'}); + #$credittotal + $total->{'total_amount'} = '-'. $other_money_char. $credit->{'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; - } + # credits (again) + foreach ( $self->cust_credited ) { - } else { - die "guru meditation #54"; - } + #something more elaborate if $_->amount ne $_->cust_credit->credited ? - 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"; + 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) + ]; + } - 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"; + # payments + foreach my $payment ( $self->_items_payments ) { + my $total = {}; + $total->{'total_item'} = &$escape_function($payment->{'description'}); + #$paymenttotal + $total->{'total_amount'} = '-'. $other_money_char. $payment->{'amount'}; + push @total_items, $total; + push @buf, [ $payment->{'description'}, + $money_char. sprintf("%10.2f", $payment->{'amount'}), + ]; + } + + { + my $total; + $total->{'total_item'} = &$embolden_function($self->balance_due_msg); + $total->{'total_amount'} = + &$embolden_function( + $other_money_char. sprintf('%.2f', $self->owed + $pr_total ) + ); + push @total_items, $total; + push @buf,['','-----------']; + push @buf,[$self->balance_due_msg, $money_char. + sprintf("%10.2f", $balance_due ) ]; } - 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"; + $invoice_data{'logo_file'} = $params{'logo_file'} + if $params{'logo_file'}; + + $invoice_lines = 0; + my $wasfunc = 0; + foreach ( grep /invoice_lines\(\d*\)/, @invoice_template ) { #kludgy + /invoice_lines\((\d*)\)/; + $invoice_lines += $1 || scalar(@buf); + $wasfunc=1; } - close $fh; + die "no invoice_lines() functions in template?" + if ( $format eq 'template' && !$wasfunc ); - $fh->filename =~ /^(.*).tex$/ or die "unparsable filename: ". $fh->filename; - return ($1, $invoice_data{'logo_file'}); + if ($format eq 'template') { + + if ( $invoice_lines ) { + $invoice_data{'total_pages'} = int( scalar(@buf) / $invoice_lines ); + $invoice_data{'total_pages'}++ + if scalar(@buf) % $invoice_lines; + } + + #setup subroutine for the template + sub FS::cust_bill::_template::invoice_lines { + my $lines = shift || scalar(@FS::cust_bill::_template::buf); + map { + scalar(@FS::cust_bill::_template::buf) + ? shift @FS::cust_bill::_template::buf + : [ '', '' ]; + } + ( 1 .. $lines ); + } + + my $lines; + my @collect; + while (@buf) { + push @collect, split("\n", + $text_template->fill_in( HASH => \%invoice_data, + PACKAGE => 'FS::cust_bill::_template' + ) + ); + $FS::cust_bill::_template::page++; + } + map "$_\n", @collect; + }else{ + warn "filling in template for invoice ". $self->invnum. "\n" + if $DEBUG; + warn join("\n", map " $_ => ". $invoice_data{$_}, keys %invoice_data). "\n" + if $DEBUG > 1; + $text_template->fill_in(HASH => \%invoice_data); + } } =item print_ps [ TIME [ , TEMPLATE ] ] @@ -2218,225 +2268,15 @@ 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*$/
    /; - 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/%%(.*)$//g; - s/\\section\*\{\\textsc\{(.)(.*)\}\}/

    $1<\/font>\U$2<\/b>/g; - s/\\begin\{enumerate\}/

      /g; - s/\\item /
    1. /g; - s/\\end\{enumerate\}/<\/ol>/g; - s/\\textbf\{(.*)\}/$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*$/
      /; $_; } - $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'} = 'Total'; - $total->{'total_amount'} = - "$money_char". sprintf('%.2f', $self->charged + $pr_total ). ''; - 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'} = ''. $self->balance_due_msg. ''; - $total->{'total_amount'} = - "$money_char". sprintf('%.2f', $self->owed + $pr_total ). ''; - 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; + my %params = ( 'format' => 'html' ); + $params{'time'} = $today if $today; + $params{'template'} = $template if $template; + $params{'cid'} = $cid if $cid; - $html_template->fill_in( HASH => \%invoice_data); + $self->print_generic( %params ); } # quick subroutine for print_latex @@ -2457,6 +2297,67 @@ sub _latex_escape { #utility methods for print_* +sub _translate_old_latex_format { + warn "_translate_old_latex_format called\n" + if $DEBUG; + + my @template = (); + while ( @_ ) { + my $line = shift; + + if ( $line =~ /^%%Detail\s*$/ ) { + + push @template, q![@--!, + q! foreach my $_tr_line (@detail_items) {!, + q! if ( scalar ($_tr_item->{'ext_description'} ) ) {!, + q! $_tr_line->{'description'} .= !, + q! "\\tabularnewline\n~~".!, + q! join( "\\tabularnewline\n~~",!, + q! @{$_tr_line->{'ext_description'}}!, + q! );!, + q! }!; + + while ( ( my $line_item_line = shift ) + !~ /^%%EndDetail\s*$/ ) { + $line_item_line =~ s/'/\\'/g; # nice LTS + $line_item_line =~ s/\\/\\\\/g; # escape quotes and backslashes + $line_item_line =~ s/\$(\w+)/'. \$_tr_line->{$1}. '/g; + push @template, " \$OUT .= '$line_item_line';"; + } + + push @template, '}', + '--@]'; + + } elsif ( $line =~ /^%%TotalDetails\s*$/ ) { + + push @template, '[@--', + ' foreach my $_tr_line (@total_items) {'; + + while ( ( my $total_item_line = shift ) + !~ /^%%EndTotalDetails\s*$/ ) { + $total_item_line =~ s/'/\\'/g; # nice LTS + $total_item_line =~ s/\\/\\\\/g; # escape quotes and backslashes + $total_item_line =~ s/\$(\w+)/'. \$_tr_line->{$1}. '/g; + push @template, " \$OUT .= '$total_item_line';"; + } + + push @template, '}', + '--@]'; + + } else { + $line =~ s/\$(\w+)/[\@-- \$$1 --\@]/g; + push @template, $line; + } + + } + + if ($DEBUG) { + warn "$_\n" foreach @template; + } + + (@template); +} + sub terms { my $self = shift; @@ -2496,6 +2397,30 @@ sub balance_due_msg { $msg; } +sub _items_sections { + my $self = shift; + + my %s = (); + foreach my $cust_bill_pkg ( $self->cust_bill_pkg ) { + + if ( $cust_bill_pkg->pkgnum > 0 ) { + + my $desc = $cust_bill_pkg->cust_pkg->part_pkg->classname; + + $s{$desc} += $cust_bill_pkg->setup + if ( $cust_bill_pkg->setup != 0 ); + + $s{$desc} += $cust_bill_pkg->recur + if ( $cust_bill_pkg->recur != 0 ); + + } + + } + + map { {'description' => $_, 'subtotal' => $s{$_}} } sort keys %s; + +} + sub _items { my $self = shift; my @display = scalar(@_) @@ -2542,8 +2467,16 @@ sub _items_previous { sub _items_pkg { my $self = shift; - my @cust_bill_pkg = grep { $_->pkgnum } $self->cust_bill_pkg; - $self->_items_cust_bill_pkg(\@cust_bill_pkg, @_); + my %options = @_; + my $section = delete $options{'section'}; + my @cust_bill_pkg = + grep { $_->pkgnum && + ( defined($section) + ? $_->cust_pkg->part_pkg->classname eq $section->{'description'} + : 1 + ) + } $self->cust_bill_pkg; + $self->_items_cust_bill_pkg(\@cust_bill_pkg, %options); } sub _items_tax { diff --git a/conf/invoice_html b/conf/invoice_html index b13b08f46..9d97243e4 100644 --- a/conf/invoice_html +++ b/conf/invoice_html @@ -68,31 +68,67 @@ -

      CHARGES -

      - - - - - - - <%= + <%= + foreach my $section ( @sections ) { + $OUT .= '
      RefDescriptionAmount
      '; + if ($section->{'description'}) { + $OUT .= + '

      '. uc(substr($section->{'description'},0,1)). + ''. uc(substr($section->{'description'},1)). + ''. + '

      '; + }else{ + $OUT .= + '

      CHARGES'. + '

      '; + } + $OUT .= '

      '; - foreach my $line ( @detail_items ) { $OUT .= - ''. - ''. $line->{'ref'}. ''. - ''. $line->{'description'}. ''. - ''. $line->{'amount'}. ''. - '' - ; - foreach my $ext_desc ( @{$line->{'ext_description'} } ) { + ''. + ''. + ''. + ''. + ''. + ''; + + foreach my $line ( + grep { ( scalar(@sections) > 1 + ? $section->{'description'} eq $_->{'section'}->{'description'} + : 1 + ) } + @detail_items ) + { $OUT .= - ''. - ''. - ''. - ''. + ''. + ''. + ''. + ''. '' + ; + foreach my $ext_desc ( @{$line->{'ext_description'} } ) { + $OUT .= + ''. + ''. + ''. + ''. + '' + } + } + + + if (scalar(@sections) > 1) { + my $style = 'border-top: 3px solid #000000;'. + 'border-bottom: 3px solid #000000;'; + $OUT .= + ''. + qq(). + qq('. + qq('. + '' + ; } } diff --git a/conf/invoice_latex b/conf/invoice_latex index 660c4d586..6a81c4c2e 100644 --- a/conf/invoice_latex +++ b/conf/invoice_latex @@ -187,58 +187,74 @@ Terms: [@-- $terms --@]\\ \end{minipage}} \vspace{1.5cm} % -\section*{\textsc{Charges}} -\begin{longtable}{clr} -\hline -\rule{0pt}{2.5ex} -\makebox[1.4cm]{\textbf{Ref}} & -\makebox[12.8cm][l]{\textbf{Description}} & -\makebox[2.5cm][r]{\textbf{Amount}} \\ -\hline -\endfirsthead -\multicolumn{3}{r}{\rule{0pt}{2.5ex}Continued from previous page}\\ -\hline -\rule{0pt}{2.5ex} -\makebox[1.4cm]{\textbf{Ref}} & -\makebox[12.8cm][l]{\textbf{Description}} & -\makebox[2.5cm][r]{\textbf{Amount}} \\ -\hline -\endhead -\multicolumn{3}{r}{\rule{0pt}{2.5ex}Continued on next page...}\\ -\endfoot -\hline [@-- + foreach my $section ( @sections ) { + $OUT .= '\section*{\textsc{'; + $OUT .= ($section->{'description'}) ? $section->{'description'} : 'Charges'; + $OUT .= '}}\begin{longtable}{clr}'; + $OUT .= '\hline'; + $OUT .= '\rule{0pt}{2.5ex}'; + $OUT .= '\makebox[1.4cm]{\textbf{Ref}} & '; + $OUT .= '\makebox[12.8cm][l]{\textbf{Description}} & '; + $OUT .= '\makebox[2.5cm][r]{\textbf{Amount}} \\\\'; + $OUT .= '\hline'; + $OUT .= '\endfirsthead'; + $OUT .= '\multicolumn{3}{r}{\rule{0pt}{2.5ex}Continued from previous page}\\\\'; + $OUT .= '\hline'; + $OUT .= '\rule{0pt}{2.5ex}'; + $OUT .= '\makebox[1.4cm]{\textbf{Ref}} & '; + $OUT .= '\makebox[12.8cm][l]{\textbf{Description}} & '; + $OUT .= '\makebox[2.5cm][r]{\textbf{Amount}} \\\\'; + $OUT .= '\hline'; + $OUT .= '\endhead'; + $OUT .= '\multicolumn{3}{r}{\rule{0pt}{2.5ex}Continued on next page...}\\\\'; + $OUT .= '\endfoot'; + $OUT .= '\hline'; - foreach my $line (@total_items) { - $OUT .= '\FStotaldesc{' . $line->{'total_item'} . '}' . - '{' . $line->{'total_amount'} . '}' . "\n"; - } + if (scalar(@sections) > 1) { + $OUT .= '\FStotaldesc{' . $section->{'description'} . ' Total}' . + '{' . $section->{'subtotal'} . '}' . "\n"; + } ---@] -\hline -\endlastfoot -[@-- + if ($section == $sections[$#sections]) { + foreach my $line (@total_items) { + $OUT .= '\FStotaldesc{' . $line->{'total_item'} . '}' . + '{' . $line->{'total_amount'} . '}' . "\n"; + } + } - foreach my $line (@detail_items) { - my $ext_description = $line->{'ext_description'}; + $OUT .= '\hline'; + $OUT .= '\endlastfoot'; - # Don't break-up small packages. - my $rowbreak = @$ext_description < 5 ? '*' : ''; + foreach my $line ( + grep { ( scalar( @sections ) > 1 + ? $section->{'description'} eq $_->{'section'}->{'description'} + : 1 + ) } + @detail_items ) + { + my $ext_description = $line->{'ext_description'}; + + # Don't break-up small packages. + my $rowbreak = @$ext_description < 5 ? '*' : ''; + + $OUT .= "\\hline\n"; + $OUT .= '\FSdesc{' . $line->{'ref'} . '}{' . $line->{'description'} . '}' . + '{' . $line->{'amount'} . "}${rowbreak}\n"; - $OUT .= "\\hline\n"; - $OUT .= '\FSdesc{' . $line->{'ref'} . '}{' . $line->{'description'} . '}' . - '{' . $line->{'amount'} . "}${rowbreak}\n"; + foreach my $ext_desc (@$ext_description) { + $ext_desc = substr($ext_desc, 0, 80) . '...' + if (length($ext_desc) > 80); + $OUT .= '\FSextdesc{' . $ext_desc . '}' . "${rowbreak}\n"; + } - foreach my $ext_desc (@$ext_description) { - $ext_desc = substr($ext_desc, 0, 80) . '...' - if (length($ext_desc) > 80); - $OUT .= '\FSextdesc{' . $ext_desc . '}' . "${rowbreak}\n"; } + $OUT .= '\end{longtable}'; + } --@] -\end{longtable} \vfill [@-- $notes --@] \end{document} -- 2.11.0
      RefDescriptionAmount
      - '. $ext_desc. '
      '. $line->{'ref'}. ''. $line->{'description'}. ''. $line->{'amount'}. '
      - '. $ext_desc. '
       ). + $section->{'description'}. ' Total ). + $section->{'subtotal'}. '