- @inc_src = map { s/\[\@--/$delimiters{$format}[0]/g;
- s/--\@\]/$delimiters{$format}[1]/g;
- $_;
- }
- &$convert_map( $conf->config($inc_file, $agentnum) );
-
- }
-
- my $inc_tt = new Text::Template (
- TYPE => 'ARRAY',
- SOURCE => [ map "$_\n", @inc_src ],
- DELIMITERS => $delimiters{$format},
- ) or die "Can't create new Text::Template object: $Text::Template::ERROR";
-
- unless ( $inc_tt->compile() ) {
- my $error = "Can't compile $inc_file template: $Text::Template::ERROR\n";
- warn $error. "Template:\n". join('', map "$_\n", @inc_src);
- die $error;
- }
-
- $invoice_data{$include} = $inc_tt->fill_in( HASH => \%invoice_data );
-
- $invoice_data{$include} =~ s/\n+$//
- if ($format eq 'latex');
- }
-
- $invoice_data{'po_line'} =
- ( $cust_main->payby eq 'BILL' && $cust_main->payinfo )
- ? &$escape_function("Purchase Order #". $cust_main->payinfo)
- : $nbsp;
-
- my %money_chars = ( 'latex' => '',
- 'html' => $conf->config('money_char') || '$',
- 'template' => '',
- );
- my $money_char = $money_chars{$format};
-
- my %other_money_chars = ( 'latex' => '\dollar ',#XXX should be a config too
- 'html' => $conf->config('money_char') || '$',
- 'template' => '',
- );
- my $other_money_char = $other_money_chars{$format};
- $invoice_data{'dollar'} = $other_money_char;
-
- 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;
-
- my $previous_section = { 'description' => 'Previous Charges',
- 'subtotal' => $other_money_char.
- sprintf('%.2f', $pr_total),
- 'summarized' => $summarypage ? 'Y' : '',
- };
-
- my $taxtotal = 0;
- my $tax_section = { 'description' => 'Taxes, Surcharges, and Fees',
- 'subtotal' => $taxtotal, # adjusted below
- 'summarized' => $summarypage ? 'Y' : '',
- };
-
- my $adjusttotal = 0;
- my $adjust_section = { 'description' => 'Credits, Payments, and Adjustments',
- 'subtotal' => 0, # adjusted below
- 'summarized' => $summarypage ? 'Y' : '',
- };
-
- my $unsquelched = $params{unsquelch_cdr} || $cust_main->squelch_cdr ne 'Y';
- my $multisection = $conf->exists('invoice_sections', $cust_main->agentnum);
- my $late_sections = [];
- if ( $multisection ) {
- push @sections,
- $self->_items_sections( $late_sections, $summarypage, $escape_function );
- }else{
- push @sections, { 'description' => '', 'subtotal' => '' };
- }
-
- unless ( $conf->exists('disable_previous_balance')
- || $conf->exists('previous_balance-summary_only')
- )
- {
-
- 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'}};
- }
- $detail->{'amount'} = ( $old_latex ? '' : $money_char).
- $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'}),
- ];
- }
-
- }
-
- if ( @pr_cust_bill && !$conf->exists('disable_previous_balance') ) {
- push @buf, ['','-----------'];
- push @buf, [ 'Total Previous Balance',
- $money_char. sprintf("%10.2f", $pr_total) ];
- push @buf, ['',''];
- }
-
- foreach my $section (@sections, @$late_sections) {
-
- $invoice_data{finance_amount} = sprintf('%.2f', $section->{'subtotal'} )
- if ( $invoice_data{finance_section} &&
- $section->{'description'} eq $invoice_data{finance_section} );
-
- $section->{'subtotal'} = $other_money_char.
- sprintf('%.2f', $section->{'subtotal'})
- if $multisection;
-
- if ( $section->{'description'} ) {
- push @buf, ( [ &$escape_function($section->{'description'}), '' ],
- [ '', '' ],
- );
- }
-
- my %options = ();
- $options{'section'} = $section if $multisection;
- $options{'format'} = $format;
- $options{'escape_function'} = $escape_function;
- $options{'format_function'} = sub { () } unless $unsquelched;
- $options{'unsquelched'} = $unsquelched;
- $options{'summary_page'} = $summarypage;
-
- foreach my $line_item ( $self->_items_pkg(%options) ) {
- my $detail = {
- ext_description => [],
- };
- $detail->{'ref'} = $line_item->{'pkgnum'};
- $detail->{'quantity'} = $line_item->{'quantity'};
- $detail->{'section'} = $section;
- $detail->{'description'} = &$escape_function($line_item->{'description'});
- if ( exists $line_item->{'ext_description'} ) {
- @{$detail->{'ext_description'}} = @{$line_item->{'ext_description'}};
- }
- $detail->{'amount'} = ( $old_latex ? '' : $money_char ).
- $line_item->{'amount'};
- $detail->{'unit_amount'} = ( $old_latex ? '' : $money_char ).
- $line_item->{'unit_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'})
- ],
- [ '', '' ],
- [ '', '' ],
- );
- }
-
- }
-
- $invoice_data{current_less_finance} =
- sprintf('%.2f', $self->charged - $invoice_data{finance_amount} );
-
- if ( $multisection && !$conf->exists('disable_previous_balance') ) {
- unshift @sections, $previous_section if $pr_total;
- }
-
- foreach my $tax ( $self->_items_tax ) {
-
- $taxtotal += $tax->{'amount'};
-
- my $description = &$escape_function( $tax->{'description'} );
- my $amount = sprintf( '%.2f', $tax->{'amount'} );
-
- if ( $multisection ) {
-
- my $money = $old_latex ? '' : $money_char;
- push @detail_items, {
- ext_description => [],
- ref => '',
- quantity => '',
- description => $description,
- amount => $money. $amount,
- product_code => '',
- section => $tax_section,
- };
-
- } else {
-
- push @total_items, {
- 'total_item' => $description,
- 'total_amount' => $other_money_char. $amount,
- };
-
- }
-
- push @buf,[ $description,
- $money_char. $amount,
- ];
-
- }
-
- if ( $taxtotal ) {
- my $total = {};
- $total->{'total_item'} = 'Sub-total';
- $total->{'total_amount'} =
- $other_money_char. sprintf('%.2f', $self->charged - $taxtotal );
-
- if ( $multisection ) {
- $tax_section->{'subtotal'} = $other_money_char.
- sprintf('%.2f', $taxtotal);
- $tax_section->{'pretotal'} = 'New charges sub-total '.
- $total->{'total_amount'};
- push @sections, $tax_section if $taxtotal;
- }else{
- unshift @total_items, $total;
- }
- }
- $invoice_data{'taxtotal'} = sprintf('%.2f', $taxtotal);
-
- push @buf,['','-----------'];
- push @buf,[( $conf->exists('disable_previous_balance')
- ? 'Total Charges'
- : 'Total New Charges'
- ),
- $money_char. sprintf("%10.2f",$self->charged) ];
- push @buf,['',''];
-
- {
- my $total = {};
- $total->{'total_item'} = &$embolden_function('Total');
- $total->{'total_amount'} =
- &$embolden_function(
- $other_money_char.
- sprintf( '%.2f',
- $self->charged + ( $conf->exists('disable_previous_balance')
- ? 0
- : $pr_total
- )
- )
- );
- if ( $multisection ) {
- $adjust_section->{'pretotal'} = 'New charges total '. $other_money_char.
- sprintf('%.2f', $self->charged );
- }else{
- push @total_items, $total;
- }
- push @buf,['','-----------'];
- push @buf,['Total Charges',
- $money_char.
- sprintf( '%10.2f', $self->charged +
- ( $conf->exists('disable_previous_balance')
- ? 0
- : $pr_total
- )
- )
- ];
- push @buf,['',''];
- }
-
- unless ( $conf->exists('disable_previous_balance') ) {
- #foreach my $thing ( sort { $a->_date <=> $b->_date } $self->_items_credits, $self->_items_payments
-
- # credits
- my $credittotal = 0;
- foreach my $credit ( $self->_items_credits('trim_len'=>60) ) {
-
- my $total;
- $total->{'total_item'} = &$escape_function($credit->{'description'});
- $credittotal += $credit->{'amount'};
- $total->{'total_amount'} = '-'. $other_money_char. $credit->{'amount'};
- $adjusttotal += $credit->{'amount'};
- if ( $multisection ) {
- my $money = $old_latex ? '' : $money_char;
- push @detail_items, {
- ext_description => [],
- ref => '',
- quantity => '',
- description => &$escape_function($credit->{'description'}),
- amount => $money. $credit->{'amount'},
- product_code => '',
- section => $adjust_section,
- };
- } else {
- push @total_items, $total;
- }
-
- }
- $invoice_data{'credittotal'} = sprintf('%.2f', $credittotal);
-
- #credits (again)
- foreach my $credit ( $self->_items_credits('trim_len'=>32) ) {
- push @buf, [ $credit->{'description'}, $money_char.$credit->{'amount'} ];
- }
-
- # payments
- my $paymenttotal = 0;
- foreach my $payment ( $self->_items_payments ) {
- my $total = {};
- $total->{'total_item'} = &$escape_function($payment->{'description'});
- $paymenttotal += $payment->{'amount'};
- $total->{'total_amount'} = '-'. $other_money_char. $payment->{'amount'};
- $adjusttotal += $payment->{'amount'};
- if ( $multisection ) {
- my $money = $old_latex ? '' : $money_char;
- push @detail_items, {
- ext_description => [],
- ref => '',
- quantity => '',
- description => &$escape_function($payment->{'description'}),
- amount => $money. $payment->{'amount'},
- product_code => '',
- section => $adjust_section,
- };
- }else{
- push @total_items, $total;
- }
- push @buf, [ $payment->{'description'},
- $money_char. sprintf("%10.2f", $payment->{'amount'}),
- ];
- }
- $invoice_data{'paymenttotal'} = sprintf('%.2f', $paymenttotal);
-
- if ( $multisection ) {
- $adjust_section->{'subtotal'} = $other_money_char.
- sprintf('%.2f', $adjusttotal);
- push @sections, $adjust_section;
- }
-
- {
- my $total;
- $total->{'total_item'} = &$embolden_function($self->balance_due_msg);
- $total->{'total_amount'} =
- &$embolden_function(
- $other_money_char. sprintf('%.2f', $summarypage
- ? $self->charged +
- $self->billing_balance
- : $self->owed + $pr_total
- )
- );
- if ( $multisection ) {
- $adjust_section->{'posttotal'} = $total->{'total_item'}. ' '.
- $total->{'total_amount'};
- }else{
- push @total_items, $total;
- }
- push @buf,['','-----------'];
- push @buf,[$self->balance_due_msg, $money_char.
- sprintf("%10.2f", $balance_due ) ];
- }
- }
-
- if ( $multisection ) {
- push @sections, @$late_sections
- if $unsquelched;
- }
-
- my @includelist = ();
- push @includelist, 'summary' if $summarypage;
- foreach my $include ( @includelist ) {
-
- my $inc_file = $conf->key_orbase("invoice_${format}$include", $template);
- my @inc_src;
-
- if ( length( $conf->config($inc_file, $agentnum) ) ) {
-
- @inc_src = $conf->config($inc_file, $agentnum);
-
- } else {
-
- $inc_file = $conf->key_orbase("invoice_latex$include", $template);
-
- my $convert_map = $convert_maps{$format}{$include};
-
- @inc_src = map { s/\[\@--/$delimiters{$format}[0]/g;
- s/--\@\]/$delimiters{$format}[1]/g;
- $_;
- }
- &$convert_map( $conf->config($inc_file, $agentnum) );
-
- }
-
- my $inc_tt = new Text::Template (
- TYPE => 'ARRAY',
- SOURCE => [ map "$_\n", @inc_src ],
- DELIMITERS => $delimiters{$format},
- ) or die "Can't create new Text::Template object: $Text::Template::ERROR";
-
- unless ( $inc_tt->compile() ) {
- my $error = "Can't compile $inc_file template: $Text::Template::ERROR\n";
- warn $error. "Template:\n". join('', map "$_\n", @inc_src);
- die $error;
- }
-
- $invoice_data{$include} = $inc_tt->fill_in( HASH => \%invoice_data );
-
- $invoice_data{$include} =~ s/\n+$//
- if ($format eq 'latex');
- }
-
- $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?"
- if ( $format eq 'template' && !$wasfunc );
-
- 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 HASHREF | [ TIME [ , TEMPLATE ] ]
-
-Returns an postscript invoice, as a scalar.
-
-Options can be passed as a hashref (recommended) or as a list of time, template
-and then any key/value pairs for any other options.
-
-I<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.
-
-I<notice_name>, if specified, overrides "Invoice" as the name of the sent document (templates from 10/2009 or newer required)
-
-=cut
-
-sub print_ps {
- my $self = shift;
-
- my ($file, $lfile) = $self->print_latex(@_);
- my $ps = generate_ps($file);
- unlink($lfile);
-
- $ps;
-}
-
-=item print_pdf HASHREF | [ TIME [ , TEMPLATE ] ]
-
-Returns an PDF invoice, as a scalar.
-
-Options can be passed as a hashref (recommended) or as a list of time, template
-and then any key/value pairs for any other options.
-
-I<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.
-
-I<template>, if specified, is the name of a suffix for alternate invoices.
-
-I<notice_name>, if specified, overrides "Invoice" as the name of the sent document (templates from 10/2009 or newer required)
-
-=cut
-
-sub print_pdf {
- my $self = shift;
-
- my ($file, $lfile) = $self->print_latex(@_);
- my $pdf = generate_pdf($file);
- unlink($lfile);
-
- $pdf;
-}
-
-=item print_html HASHREF | [ TIME [ , TEMPLATE [ , CID ] ] ]
-
-Returns an HTML invoice, as a scalar.
-
-I<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.
-
-I<template>, if specified, is the name of a suffix for alternate invoices.
-
-I<notice_name>, if specified, overrides "Invoice" as the name of the sent document (templates from 10/2009 or newer required)
-
-I<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
-
-sub print_html {
- my $self = shift;
- my %params;
- if ( ref($_[0]) ) {
- %params = %{ shift() };
- }else{
- $params{'time'} = shift;
- $params{'template'} = shift;
- $params{'cid'} = shift;
- }
-
- $params{'format'} = 'html';
-
- $self->print_generic( %params );
-}
-
-# 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 _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;
-
- #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
- $conf->config('invoice_default_terms') || '';
-}
-
-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 balance_due_date {
- my $self = shift;
- my $duedate = '';
- if ( $conf->exists('invoice_default_terms')
- && $conf->config('invoice_default_terms')=~ /^\s*Net\s*(\d+)\s*$/ ) {
- $duedate = time2str("%m/%d/%Y", $self->_date + ($1*86400) );
- }
- $duedate;
-}
-
-=item invnum_date_pretty
-
-Returns a string with the invoice number and date, for example:
-"Invoice #54 (3/20/2008)"
-
-=cut
-
-sub invnum_date_pretty {
- my $self = shift;
- 'Invoice #'. $self->invnum. ' ('. $self->_date_pretty. ')';
-}
-
-=item _date_pretty
-
-Returns a string with the date, for example: "3/20/2008"
-
-=cut
-
-sub _date_pretty {
- my $self = shift;
- time2str('%x', $self->_date);
-}
-
-sub _items_sections {
- my $self = shift;
- my $late = shift;
- my $summarypage = shift;
- my $escape = shift;
-
- my %s = ();
- my %l = ();
- my %not_tax = ();
-
- foreach my $cust_bill_pkg ( $self->cust_bill_pkg )
- {
-
-
- my $usage = $cust_bill_pkg->usage;
-
- foreach my $display ($cust_bill_pkg->cust_bill_pkg_display) {
- next if ( $display->summary && $summarypage );
-
- my $desc = $display->section;
- my $type = $display->type;
-
- if ( $cust_bill_pkg->pkgnum > 0 ) {
- $not_tax{$desc} = 1;
- }
-
- if ( $display->post_total && !$summarypage ) {
- if (! $type || $type eq 'S') {
- $l{$desc} += $cust_bill_pkg->setup
- if ( $cust_bill_pkg->setup != 0 );
- }
-
- if (! $type) {
- $l{$desc} += $cust_bill_pkg->recur
- if ( $cust_bill_pkg->recur != 0 );
- }
-
- if ($type && $type eq 'R') {
- $l{$desc} += $cust_bill_pkg->recur - $usage
- if ( $cust_bill_pkg->recur != 0 );
- }
-
- if ($type && $type eq 'U') {
- $l{$desc} += $usage;
- }
-
- } else {
- if (! $type || $type eq 'S') {
- $s{$desc} += $cust_bill_pkg->setup
- if ( $cust_bill_pkg->setup != 0 );
- }
-
- if (! $type) {
- $s{$desc} += $cust_bill_pkg->recur
- if ( $cust_bill_pkg->recur != 0 );
- }
-
- if ($type && $type eq 'R') {
- $s{$desc} += $cust_bill_pkg->recur - $usage
- if ( $cust_bill_pkg->recur != 0 );
- }
-
- if ($type && $type eq 'U') {
- $s{$desc} += $usage;
- }
-
- }
-
- }
-
- }
-
- my %cache = map { $_->categoryname => $_ }
- qsearch( 'pkg_category', {disabled => 'Y'} );
- $cache{$_->categoryname} = $_
- foreach qsearch( 'pkg_category', {disabled => ''} );
-
- push @$late, map { { 'description' => &{$escape}($_),
- 'subtotal' => $l{$_},
- 'post_total' => 1,
- } }
- sort { $cache{$a}->weight <=> $cache{$b}->weight } keys %l;
-
- map { { 'description' => &{$escape}($_),
- 'subtotal' => $s{$_},
- 'summarized' => $not_tax{$_} ? '' : 'Y',
- 'tax_section' => $not_tax{$_} ? '' : 'Y',
- } }
- sort { $cache{$a}->weight <=> $cache{$b}->weight }
- ( $summarypage
- ? ( grep { exists($s{$_}) || !$cache{$_}->disabled } keys %cache )
- : ( keys %s )
- );
-
-}
-
-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 @display = qw( _items_previous _items_pkg );
-
- 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 _taxsort {
- return 0 unless $a cmp $b;
- return -1 if $b eq 'Tax';
- return 1 if $a eq 'Tax';
- return -1 if $b eq 'Other surcharges';
- return 1 if $a eq 'Other surcharges';
- $a cmp $b;
-}
-
-sub _items_tax {
- my $self = shift;
- my @cust_bill_pkg = sort _taxsort 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 %opt = @_;
-
- my $format = $opt{format} || '';
- my $escape_function = $opt{escape_function} || sub { shift };
- my $format_function = $opt{format_function} || '';
- my $unsquelched = $opt{unsquelched} || '';
- my $section = $opt{section}->{description} if $opt{section};
- my $summary_page = $opt{summary_page} || '';
-
- my @b = ();
- my ($s, $r, $u) = ( undef, undef, undef );
- foreach my $cust_bill_pkg ( @$cust_bill_pkg )
- {
-
- foreach ( $s, $r, $u ) {
- if ( $_ && !$cust_bill_pkg->hidden ) {
- $_->{amount} = sprintf( "%.2f", $_->{amount} ),
- $_->{unit_amount} = sprintf( "%.2f", $_->{unit_amount} ),
- push @b, { %$_ };
- $_ = undef;
- }
- }
-
- foreach my $display ( grep { defined($section)
- ? $_->section eq $section
- : 1
- }
- grep { $_->summary || !$summary_page }
- $cust_bill_pkg->cust_bill_pkg_display
- )
- {
-
- my $type = $display->type;
-
- my $desc = $cust_bill_pkg->desc;
- $desc = substr($desc, 0, 50). '...'
- if $format eq 'latex' && length($desc) > 50;
-
- my %details_opt = ( 'format' => $format,
- 'escape_function' => $escape_function,
- 'format_function' => $format_function,
- );
-
- if ( $cust_bill_pkg->pkgnum > 0 ) {
-
- my $cust_pkg = $cust_bill_pkg->cust_pkg;
-
- if ( $cust_bill_pkg->setup != 0 && (!$type || $type eq 'S') ) {
-
- my $description = $desc;
- $description .= ' Setup' if $cust_bill_pkg->recur != 0;
-
- my @d = ();
- push @d, map &{$escape_function}($_),
- $cust_pkg->h_labels_short($self->_date)
- unless $cust_pkg->part_pkg->hide_svc_detail
- || $cust_bill_pkg->hidden;
- push @d, $cust_bill_pkg->details(%details_opt)
- if $cust_bill_pkg->recur == 0;
-
- if ( $cust_bill_pkg->hidden ) {
- $s->{amount} += $cust_bill_pkg->setup;
- $s->{unit_amount} += $cust_bill_pkg->unitsetup;
- push @{ $s->{ext_description} }, @d;
- } else {
- $s = {
- description => $description,
- #pkgpart => $part_pkg->pkgpart,
- pkgnum => $cust_bill_pkg->pkgnum,
- amount => $cust_bill_pkg->setup,
- unit_amount => $cust_bill_pkg->unitsetup,
- quantity => $cust_bill_pkg->quantity,
- ext_description => \@d,
- };
- };
-
- }
-
- if ( $cust_bill_pkg->recur != 0 &&
- ( !$type || $type eq 'R' || $type eq 'U' )
- )
- {
-
- my $is_summary = $display->summary;
- my $description = ($is_summary && $type && $type eq 'U')
- ? "Usage charges" : $desc;
-
- unless ( $conf->exists('disable_line_item_date_ranges') ) {
- $description .= " (" . time2str("%x", $cust_bill_pkg->sdate).
- " - ". time2str("%x", $cust_bill_pkg->edate). ")";
- }
-
- my @d = ();
-
- #at least until cust_bill_pkg has "past" ranges in addition to
- #the "future" sdate/edate ones... see #3032
- my @dates = ( $self->_date );
- my $prev = $cust_bill_pkg->previous_cust_bill_pkg;
- push @dates, $prev->sdate if $prev;
-
- push @d, map &{$escape_function}($_),
- $cust_pkg->h_labels_short(@dates)
- #$cust_bill_pkg->edate,
- #$cust_bill_pkg->sdate)
- unless $cust_pkg->part_pkg->hide_svc_detail
- || $cust_bill_pkg->itemdesc
- || $cust_bill_pkg->hidden
- || $is_summary && $type && $type eq 'U';
-
- push @d, $cust_bill_pkg->details(%details_opt)
- unless ($is_summary || $type && $type eq 'R');
-
- my $amount = 0;
- if (!$type) {
- $amount = $cust_bill_pkg->recur;
- }elsif($type eq 'R') {
- $amount = $cust_bill_pkg->recur - $cust_bill_pkg->usage;
- }elsif($type eq 'U') {
- $amount = $cust_bill_pkg->usage;
- }
-
- if ( !$type || $type eq 'R' ) {
-
- if ( $cust_bill_pkg->hidden ) {
- $r->{amount} += $amount;
- $r->{unit_amount} += $cust_bill_pkg->unitrecur;
- push @{ $r->{ext_description} }, @d;
- } else {
- $r = {
- description => $description,
- #pkgpart => $part_pkg->pkgpart,
- pkgnum => $cust_bill_pkg->pkgnum,
- amount => $amount,
- unit_amount => $cust_bill_pkg->unitrecur,
- quantity => $cust_bill_pkg->quantity,
- ext_description => \@d,
- };
- }
-
- } elsif ( $amount ) { # && $type eq 'U'
-
- if ( $cust_bill_pkg->hidden ) {
- $u->{amount} += $amount;
- $u->{unit_amount} += $cust_bill_pkg->unitrecur;
- push @{ $u->{ext_description} }, @d;
- } else {
- $u = {
- description => $description,
- #pkgpart => $part_pkg->pkgpart,
- pkgnum => $cust_bill_pkg->pkgnum,
- amount => $amount,
- unit_amount => $cust_bill_pkg->unitrecur,
- quantity => $cust_bill_pkg->quantity,
- ext_description => \@d,
- };
- }
-
- }
-
- } # recurring or usage with recurring charge
-
- } else { #pkgnum tax or one-shot line item (??)
-
- if ( $cust_bill_pkg->setup != 0 ) {
- push @b, {
- 'description' => $desc,
- 'amount' => sprintf("%.2f", $cust_bill_pkg->setup),
- };
- }
- if ( $cust_bill_pkg->recur != 0 ) {
- push @b, {
- 'description' => "$desc (".
- time2str("%x", $cust_bill_pkg->sdate). ' - '.
- time2str("%x", $cust_bill_pkg->edate). ')',
- 'amount' => sprintf("%.2f", $cust_bill_pkg->recur),
- };
- }
-
- }
-
- }
-
- }
-
- foreach ( $s, $r, $u ) {
- if ( $_ ) {
- $_->{amount} = sprintf( "%.2f", $_->{amount} ),
- $_->{unit_amount} = sprintf( "%.2f", $_->{unit_amount} ),
- push @b, { %$_ };
- }
- }
-
- @b;
-
-}
-
-sub _items_credits {
- my( $self, %opt ) = @_;
- my $trim_len = $opt{'trim_len'} || 60;
-
- my @b;
- #credits
- foreach ( $self->cust_credited ) {
-
- #something more elaborate if $_->amount ne $_->cust_credit->credited ?
-
- my $reason = substr($_->cust_credit->reason, 0, $trim_len);
- $reason .= '...' if length($reason) < length($_->cust_credit->reason);
- $reason = " ($reason) " if $reason;
-
- push @b, {
- #'description' => 'Credit ref\#'. $_->crednum.
- # " (". time2str("%x",$_->cust_credit->_date) .")".
- # $reason,
- 'description' => 'Credit applied '.
- time2str("%x",$_->cust_credit->_date). $reason,
- 'amount' => sprintf("%.2f",$_->amount),
- };
- }
-
- @b;
-
-}
-
-sub _items_payments {
- my $self = shift;
-
- my @b;
- #get & print payments
- foreach ( $self->cust_bill_pay ) {
-
- #something more elaborate if $_->amount ne ->cust_pay->paid ?
-
- push @b, {
- 'description' => "Payment received ".
- time2str("%x",$_->cust_pay->_date ),
- 'amount' => sprintf("%.2f", $_->amount )
- };
- }
-
- @b;
-
-}
-
-=item call_details [ OPTION => VALUE ... ]
-
-Returns an array of CSV strings representing the call details for this invoice
-The only option available is the boolean prepend_billed_number
-
-=cut
-
-sub call_details {
- my ($self, %opt) = @_;
-
- my $format_function = sub { shift };