summaryrefslogtreecommitdiff
path: root/FS/FS/Template_Mixin.pm
diff options
context:
space:
mode:
authorMitch Jackson <mitch@freeside.biz>2017-11-15 07:51:40 +0000
committerMitch Jackson <mitch@freeside.biz>2017-11-16 05:30:09 +0000
commit22dd0016d0938f6acb2127d8168a4a1c5e296d3f (patch)
tree361e4a80f7f8812a1d6506d303c97430ad277c30 /FS/FS/Template_Mixin.pm
parentf228534e7bc57e615657d6a8b9c09069f34f914e (diff)
Fixed invoice inconsistencies with various conf flags RT#78190
Applying different invoicing conf flags manifested different variations of the same problem. Addressed by this fix: - Incorrect items listed for Previous Balance - Incorrect Items listed for applied payments and credits - Incorrect subtotals for various sections - Invoice amounts, subtotals, balances displayed did not reconcile. Because of which data was selected for display, columns could appear to have bad math. No account balances were factually incorrect. - Items disappearing from invoices used a payment receipts or "statements" giving a false impression of overpayment or credits - Applied payments or credits appearing on the wrong statements - A single applied credit appearing on up to 3 invoices - When viewing older invoices, future payments for future bills shown on, and appearing to apply to, the older invoice - Inconsistencies of line items and numbers between website, email, pdf and txt version invoices. - Invoice summary page numbers not matching the invoice - Incorrect balances shown on on aging line - Update item order on invoice_htmlsummary mason template Conf flags involved in these issues: - disable_previous_balance - previous_balance-payments_since - previous_balance-summary_only - previous_balance-show_on_statements - previous_balance-section - previous_balance-exclude_from_total - invoice_include_aging - invoice_show_prior_due_date - invoice_usesummary New invoice template stash variables made available: - aged_balance_current - aged_balance_30d - aged_balance_60d - aged_balance_90d Solved by updating, or creating, FS::cust_bill helper methods that generate data to be displayed on invoices. These helper methods are responsive to various conf flags. Updated template pipeline to use these helpers instead of inconsistent sql queries. Resolves: #78190 See Also: #75709, #76161, #74426
Diffstat (limited to 'FS/FS/Template_Mixin.pm')
-rw-r--r--FS/FS/Template_Mixin.pm527
1 files changed, 256 insertions, 271 deletions
diff --git a/FS/FS/Template_Mixin.pm b/FS/FS/Template_Mixin.pm
index 7dc813993..7d92d21af 100644
--- a/FS/FS/Template_Mixin.pm
+++ b/FS/FS/Template_Mixin.pm
@@ -29,9 +29,9 @@ use FS::L10N;
$DEBUG = 0;
$me = '[FS::Template_Mixin]';
-FS::UID->install_callback( sub {
+FS::UID->install_callback( sub {
my $conf = new FS::Conf; #global
- $money_char = $conf->config('money_char') || '$';
+ $money_char = $conf->config('money_char') || '$';
$date_format = $conf->config('date_format') || '%x'; #/YY
} );
@@ -121,7 +121,7 @@ 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<template>, if specified, is the name of a suffix for alternate invoices.
This is strongly deprecated; see L<FS::invoice_conf> for the right way to
customize invoice templates for different purposes.
@@ -175,7 +175,7 @@ sub print_latex {
close $lh;
$params{'logo_file'} = $lh->filename;
- if( $conf->exists('invoice-barcode')
+ if( $conf->exists('invoice-barcode')
&& $self->can('invoice_barcode')
&& $self->invnum ) { # don't try to barcode statements
my $png_file = $self->invoice_barcode($dir);
@@ -187,7 +187,7 @@ sub print_latex {
$eps_file = $1;
my $curr_dir = cwd();
- chdir($dir);
+ chdir($dir);
# after painfuly long experimentation, it was determined that sam2p won't
# accept : and other chars in the path, no matter how hard I tried to
# escape them, hence the chdir (and chdir back, just to be safe)
@@ -200,7 +200,7 @@ sub print_latex {
}
my @filled_in = $self->print_generic( %params );
-
+
my $fh = new File::Temp( TEMPLATE => $tmp_template,
DIR => $dir,
SUFFIX => '.tex',
@@ -299,7 +299,7 @@ before that line item (quotations only)
=item template
-Dprecated. Used as a suffix for a configuration template. Please
+Dprecated. Used as a suffix for a configuration template. Please
don't use this, it deprecated in favor of more flexible alternatives.
=back
@@ -344,6 +344,8 @@ sub print_generic {
$templatefile .= "_$template"
if length($template) && $conf->exists($templatefile."_$template");
+ $self->set('_template',$template);
+
# the base template
my @invoice_template = map "$_\n", $conf->config($templatefile)
or die "cannot load config data $templatefile";
@@ -355,7 +357,7 @@ sub print_generic {
"patch with conf/invoice_latex.diff or use new conf/invoice_latex*\n";
#$old_latex = 'true';
#@invoice_template = _translate_old_latex_format(@invoice_template);
- }
+ }
warn "$me print_generic creating T:T object\n"
if $DEBUG > 1;
@@ -374,7 +376,7 @@ sub print_generic {
# additional substitution could possibly cause breakage in existing templates
- my %convert_maps = (
+ my %convert_maps = (
'latex' => {
'notes' => sub { map "$_", @_ },
'footer' => sub { map "$_", @_ },
@@ -386,7 +388,7 @@ sub print_generic {
'html' => {
'notes' =>
sub {
- map {
+ map {
s/%%(.*)$/<!-- $1 -->/g;
s/\\section\*\{\\textsc\{(.)(.*)\}\}/<p><b><font size="+1">$1<\/font>\U$2<\/b>/g;
s/\\begin\{enumerate\}/<ol>/g;
@@ -406,7 +408,7 @@ sub print_generic {
sub { map { s/~/&nbsp;/g; s/\\\\\*?\s*$/<BR>/; $_; } @_ },
'returnaddress' =>
sub {
- map {
+ map {
s/~/&nbsp;/g;
s/\\\\\*?\s*$/<BR>/;
s/\\hyphenation\{[\w\s\-]+}//;
@@ -420,7 +422,7 @@ sub print_generic {
'template' => {
'notes' =>
sub {
- map {
+ map {
s/%%.*$//g;
s/\\section\*\{\\textsc\{(.*)\}\}/\U$1/g;
s/\\begin\{enumerate\}//g;
@@ -438,7 +440,7 @@ sub print_generic {
sub { map { s/~/ /g; s/\\\\\*?\s*$/\n/; $_; } @_ },
'returnaddress' =>
sub {
- map {
+ map {
s/~/ /g;
s/\\\\\*?\s*$/\n/; # dubious
s/\\hyphenation\{[\w\s\-]+}//;
@@ -548,7 +550,7 @@ sub print_generic {
'quotationnum' => $self->quotationnum,
'no_date' => $params{'no_date'},
'_date' => ( $params{'no_date'} ? '' : $self->_date ),
- # workaround for inconsistent behavior in the early plain text
+ # workaround for inconsistent behavior in the early plain text
# templates; see RT#28271
'date' => ( $params{'no_date'}
? ''
@@ -581,7 +583,7 @@ sub print_generic {
'smallernotes' => $conf->exists('invoice-smallernotes'),
'smallerfooter' => $conf->exists('invoice-smallerfooter'),
'balance_due_below_line' => $conf->exists('balance_due_below_line'),
-
+
#layout info -- would be fancy to calc some of this and bury the template
# here in the code
'topmargin' => scalar($conf->config('invoice_latextopmargin', $agentnum)),
@@ -606,7 +608,7 @@ sub print_generic {
#quotations have $name
$invoice_data{'name'} = $invoice_data{'payname'};
-
+
#localization
$invoice_data{'emt'} = sub { &$escape_function($self->mt(@_)) };
# prototype here to silence warnings
@@ -624,7 +626,7 @@ sub print_generic {
$invoice_data{'bill_period'} = '';
$invoice_data{'bill_period'} =
- $self->time2str_local('%e %h', $min_sdate, $format)
+ $self->time2str_local('%e %h', $min_sdate, $format)
. " to " .
$self->time2str_local('%e %h', $max_edate, $format)
if ($max_edate != 0 && $min_sdate != 999999999999);
@@ -634,7 +636,7 @@ sub print_generic {
my $pkg_class =
qsearchs('pkg_class', { classnum => $conf->config('finance_pkgclass') });
$invoice_data{finance_section} = $pkg_class->categoryname;
- }
+ }
$invoice_data{finance_amount} = '0.00';
$invoice_data{finance_section} ||= 'Finance Charges'; #avoid config confusion
@@ -651,7 +653,7 @@ sub print_generic {
$invoice_data{'ship_contact'} = $escape_function->($cust_main->contact);
$invoice_data{'ship_country'} = ''
if ( $invoice_data{'ship_country'} eq $countrydefault );
-
+
$invoice_data{'cid'} = $params{'cid'}
if $params{'cid'};
@@ -691,18 +693,34 @@ sub print_generic {
$invoice_data{'barcode_cid'} = $params{'barcode_cid'}
if $params{'barcode_cid'};
- 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;
- if ( $self->enable_previous ) {
- $balance_due += $pr_total;
- }
- # otherwise the previous balance is not shown, so including it in the
- # balance due is just confusing
- # the sum of amount owed on all invoices
- # (this is used in the summary & on the payment coupon)
+ # re: rt:78190
+ # using owed_on_invoice() instead of owed() here for $balance_due
+ # using _items_previous_total() instead of ->previous() for $pr_total
+ #
+ # owed_on_invoice() is aware of configuration flags that affect how an
+ # invoice is rendered. May not return actual current balance. Will
+ # return balance appropriate for the invoice being rendered, based
+ # on which past due items, current charges, and future payments are
+ # displayed.
+ #
+ # Going forward, usage of owed(), or bypassing cust_bill helper methods
+ # when generating invoice lines may lead to incorrect or misleading
+ # math on invoices.
+ #
+ # Helper methods that are aware of invoicing conf flags:
+ # - owed_on_invoice # use instead of owed()
+ # - _items_previous() # use instead of previous()
+ # - _items_credits() # use instead of cust_credit()
+ # - _items_payments()
+ # - _items_total()
+ # - _items_previous_total() # use instead of previous()
+ # - _items_payments_total()
+ # - _items_credits_total() # use instead of cust_credit()
+
+ my $pr_total = $self->_items_previous_total();
+
+ my $balance_due = $self->owed_on_invoice();
$invoice_data{'balance'} = sprintf("%.2f", $balance_due);
# flag telling this invoice to have a first-page summary
@@ -712,127 +730,100 @@ sub print_generic {
# XXX should be an FS::cust_bill method to set the defaults, instead
# of checking the type here
- # info from customer's last invoice before this one, for some
+ # info from customer's last invoice before this one, for some
# summary formats
$invoice_data{'last_bill'} = {};
-
- my $last_bill = $self->previous_bill;
- if ( $last_bill ) {
- # "balance_date_range" unfortunately is unsuitable for this, since it
- # cares about application dates. We want to know the sum of all
- # _top-level transactions_ dated before the last invoice.
- #
- # still do this for the "Previous Balance" line of the summary block
- my @sql =
- map "$_ WHERE _date <= ? AND custnum = ?", (
- "SELECT COALESCE( SUM(charged), 0 ) FROM cust_bill",
- "SELECT -1 * COALESCE( SUM(amount), 0 ) FROM cust_credit",
- "SELECT -1 * COALESCE( SUM(paid), 0 ) FROM cust_pay",
- "SELECT COALESCE( SUM(refund), 0 ) FROM cust_refund",
- );
-
- # the customer's current balance immediately after generating the last
- # bill
-
- my $last_bill_balance = $last_bill->charged;
- foreach (@sql) {
- my $delta = FS::Record->scalar_sql(
- $_,
- $last_bill->_date - 1,
- $self->custnum,
- );
- $last_bill_balance += $delta;
- }
+ # my $last_bill = $self->previous_bill;
+ # if ( $last_bill ) {
- $last_bill_balance = sprintf("%.2f", $last_bill_balance);
-
- warn sprintf("LAST BILL: INVNUM %d, DATE %s, BALANCE %.2f\n\n",
- $last_bill->invnum,
- $self->time2str_local('%D', $last_bill->_date),
- $last_bill_balance
- ) if $DEBUG > 0;
- # ("true_previous_balance" is a terrible name, but at least it's no
- # longer stored in the database)
- $invoice_data{'true_previous_balance'} = $last_bill_balance;
-
- # Now, get all applications of credits/payments dated on or after the
- # previous bill, to invoices before the current bill. (The
- # credit/payment date restriction prevents these from intersecting
- # the "Previous Balance" set.)
- # These are "adjustments". The past due balance will be shown as
- # Previous Balance - Adjustments.
- my $adjustments = 0;
- @sql = map {
- "SELECT COALESCE(SUM(y.amount),0) FROM $_ JOIN cust_bill USING (invnum)
- WHERE cust_bill._date < ?
- AND x._date >= ?
- AND cust_bill.custnum = ?"
- } "cust_credit AS x JOIN cust_credit_bill y USING (crednum)",
- "cust_pay AS x JOIN cust_bill_pay y USING (paynum)"
- ;
- foreach (@sql) {
- my $delta = FS::Record->scalar_sql(
- $_,
- $self->_date,
- $last_bill->_date,
- $self->custnum,
- );
- $adjustments += $delta;
- }
- $invoice_data{'balance_adjustments'} = sprintf("%.2f", $adjustments);
+ # Populate template stash for previous balance and payments
+ if ($pr_total) {
+ # Used on summary page as "Previous Balance"
+ $invoice_data{'true_previous_balance'} = sprintf("%.2f", $pr_total);
- warn sprintf("BALANCE ADJUSTMENTS: %.2f\n\n",
- $invoice_data{'balance_adjustments'}
- ) if $DEBUG > 0;
+ # Used on summary page as "Payments"
+ $invoice_data{'balance_adjustments'} = sprintf("%.2f",
+ $self->_items_payments_total() + $self->_items_credits_total()
+ );
- # the sum of amount owed on all previous invoices
- # ($pr_total is used elsewhere but not as $previous_balance)
+ # Used in invoice template as "Previous Balance"
$invoice_data{'previous_balance'} = sprintf("%.2f", $pr_total);
- $invoice_data{'last_bill'}{'_date'} = $last_bill->_date; #unformatted
- my (@payments, @credits);
- # for formats that itemize previous payments
- foreach my $cust_pay ( qsearch('cust_pay', {
- 'custnum' => $self->custnum,
- '_date' => { op => '>=',
- value => $last_bill->_date }
- } ) )
- {
- next if $cust_pay->_date > $self->_date;
- push @payments, {
- '_date' => $cust_pay->_date,
- 'date' => $self->time2str_local('long', $cust_pay->_date, $format),
- 'payinfo' => $cust_pay->payby_payinfo_pretty,
- 'amount' => sprintf('%.2f', $cust_pay->paid),
- };
- # not concerned about applications
- }
- foreach my $cust_credit ( qsearch('cust_credit', {
- 'custnum' => $self->custnum,
- '_date' => { op => '>=',
- value => $last_bill->_date }
- } ) )
- {
- next if $cust_credit->_date > $self->_date;
- push @credits, {
- '_date' => $cust_credit->_date,
- 'date' => $self->time2str_local('long', $cust_credit->_date, $format),
- 'creditreason'=> $cust_credit->reason,
- 'amount' => sprintf('%.2f', $cust_credit->amount),
- };
+ # $invoice_data{last_bill}{_date}:
+ # Not used in default templates, but may be in use by someone
+ #
+ # ! May be a problem field if they are using it... this field
+ # stores the date of the previous invoice... it is possible to
+ # carry a balance, but have the immediately previous invoice paid off.
+ # In this case, this field might be presenting bad data? Not
+ # altering the problematic behavior, because someone might be
+ # expecting this bad behavior in their templates for some other
+ # purpose, such as a "your last bill was dated %_date%"
+ my $last_bill = $self->previous_bill;
+ $invoice_data{'last_bill'}{'_date'}
+ = ref $last_bill
+ ? $last_bill->_date()
+ : undef;
+
+ # $invoice_data{previous_payments}
+ # Not used in default templates, but may be in use by someone
+ #
+ # Returns an array of hrefs representing payments, each with keys:
+ # - _date: epoch timestamp
+ # - date: text formatted date
+ # - amount: money formatted amount string
+ # - payinfo: string from payby_payinfo_pretty()
+ # - paynum: id for cust_pay
+ # - description: Text description for bill line item
+ #
+ my @payments = $self->_items_payments();
+ $invoice_data{previous_payments} = \@payments;
+
+ # $invoice_data{previous_credits}
+ # Not used in default templates, but may be in use by someone
+ #
+ # Returns an array of hrefs representing credits, each with keys:
+ # - _date: epoch timestamp
+ # - date: text formatted date
+ # - amount: money formatted amount string
+ # - crednum: id for cust_credit
+ # - description: Text description for bill line item
+ # - creditreason: reason() from cust_credit
+ #
+ my @credits = $self->_items_credits();
+ $invoice_data{previous_credits} = \@credits;
+
+ # Populate formatted date field
+ for my $pmt_href (@payments, @credits) {
+ $pmt_href->{date} = $self->time2str_local(
+ 'long',
+ $pmt_href->{_date},
+ $format
+ );
}
- $invoice_data{'previous_payments'} = \@payments;
- $invoice_data{'previous_credits'} = \@credits;
+
} else {
- # there is no $last_bill
+ # There are no outstanding invoices = YAPH
$invoice_data{'true_previous_balance'} =
$invoice_data{'balance_adjustments'} =
$invoice_data{'previous_balance'} = '0.00';
- $invoice_data{'previous_payments'} = [];
- $invoice_data{'previous_credits'} = [];
+ $invoice_data{'previous_payments'} =
+ $invoice_data{'previous_credits'} = [];
+ }
+
+ # Condencing a lot of debug staements here
+ if ($DEBUG) {
+ warn "\$invoice_data{$_}: $invoice_data{$_}"
+ for qw(
+ true_previous_balance
+ balance_adjustments
+ previous_balance
+ previous_payments
+ previous_credits
+ );
}
-
+
if ( $conf->exists('invoice_usesummary', $agentnum) ) {
$invoice_data{'summarypage'} = $summarypage = 1;
}
@@ -900,9 +891,9 @@ sub print_generic {
# if (well, probably when) we still need PO numbers in the brave new world of
# 4.x, then we'll have to add them back as their own customer fields
# # let invoices use either of these as needed
-# $invoice_data{'po_num'} = ($cust_main->payby eq 'BILL')
+# $invoice_data{'po_num'} = ($cust_main->payby eq 'BILL')
# ? $cust_main->payinfo : '';
-# $invoice_data{'po_line'} =
+# $invoice_data{'po_line'} =
# ( $cust_main->payby eq 'BILL' && $cust_main->payinfo )
# ? &$escape_function($self->mt("Purchase Order #").$cust_main->payinfo)
# : $nbsp;
@@ -950,30 +941,40 @@ sub print_generic {
# default section ('Charges')
my $default_section = { 'description' => '',
- 'subtotal' => '',
+ 'subtotal' => '',
'no_subtotal' => 1,
};
# Previous Charges section
# subtotal is the first return value from $self->previous
my $previous_section;
- # if the invoice has major sections, or if we're summarizing previous
+ # if the invoice has major sections, or if we're summarizing previous
# charges with a single line, or if we've been specifically told to put them
# in a section, create a section for previous charges:
if ( $multisection or
$conf->exists('previous_balance-summary_only') or
$conf->exists('previous_balance-section') ) {
-
+
$previous_section = { 'description' => $self->mt('Previous Charges'),
'subtotal' => $other_money_char.
sprintf('%.2f', $pr_total),
'summarized' => '', #why? $summarypage ? 'Y' : '',
};
- $previous_section->{posttotal} = '0 / 30 / 60 / 90 days overdue '.
- join(' / ', map { $cust_main->balance_date_range(@$_) }
- $self->_prior_month30s
- )
- if $conf->exists('invoice_include_aging');
+
+ # Include balance aging line and template variables
+ my @aged_balances = $self->_items_aging_balances();
+ ( $invoice_data{aged_balance_current},
+ $invoice_data{aged_balance_30d},
+ $invoice_data{aged_balance_60d},
+ $invoice_data{aged_balance_90d}
+ ) = @aged_balances;
+
+ if ($conf->exists('invoice_include_aging')) {
+ $previous_section->{posttotal} = sprintf(
+ '0 / 30 / 60 / 90 days overdue %.2f / %.2f / %.2f / %.2f',
+ @aged_balances,
+ );
+ }
} else {
# otherwise put them in the main section
@@ -1006,7 +1007,7 @@ sub print_generic {
push @detail_items, @$extra_lines if $extra_lines;
# the code is written so that both methods can be used together, but
- # we haven't yet changed the template to take advantage of that, so for
+ # we haven't yet changed the template to take advantage of that, so for
# now, treat them as mutually exclusive.
my %section_method = ( by_category => 1 );
if ( $conf->config($tc.'sections_method') eq 'location' ) {
@@ -1052,7 +1053,7 @@ sub print_generic {
my @finance_charges;
my @charges;
foreach my $cust_bill_pkg ( $self->cust_bill_pkg ) {
- if ( $invoice_data{finance_section} and
+ if ( $invoice_data{finance_section} and
grep { $_->section eq $invoice_data{finance_section} }
$cust_bill_pkg->cust_bill_pkg_display ) {
# I think these are always setup fees, but just to be sure...
@@ -1061,7 +1062,7 @@ sub print_generic {
push @charges, $cust_bill_pkg->recur + $cust_bill_pkg->setup;
}
}
- $invoice_data{finance_amount} =
+ $invoice_data{finance_amount} =
sprintf('%.2f', sum( @finance_charges ) || 0);
$default_section->{subtotal} = $other_money_char.
sprintf('%.2f', sum( @charges ) || 0);
@@ -1115,7 +1116,7 @@ sub print_generic {
#quantity => 1, # not really correct
section => $previous_section, # which might be $default_section
description => &$escape_function($line_item->{'description'}),
- ext_description => [ map { &$escape_function($_) }
+ ext_description => [ map { &$escape_function($_) }
@{ $line_item->{'ext_description'} || [] }
],
amount => $money_char . $line_item->{'amount'},
@@ -1130,20 +1131,20 @@ sub print_generic {
}
- if ( @pr_cust_bill && $self->enable_previous ) {
+ if ( $pr_total && $self->enable_previous ) {
push @buf, ['','-----------'];
push @buf, [ $self->mt('Total Previous Balance'),
$money_char. sprintf("%10.2f", $pr_total) ];
push @buf, ['',''];
}
-
+
if ( $conf->exists('svc_phone-did-summary') && $self->can('_did_summary') ) {
warn "$me adding DID summary\n"
if $DEBUG > 1;
my ($didsummary,$minutes) = $self->_did_summary;
my $didsummary_desc = 'DID Activity Summary (since last invoice)';
- push @detail_items,
+ push @detail_items,
{ 'description' => $didsummary_desc,
'ext_description' => [ $didsummary, $minutes ],
};
@@ -1229,20 +1230,20 @@ sub print_generic {
$line_item->{'unit_amount'} = $money_char.$line_item->{'unit_amount'};
}
$line_item->{'ext_description'} ||= [];
-
+
push @detail_items, $line_item;
}
if ( $section->{'description'} ) {
push @buf, ( ['','-----------'],
[ $section->{'description'}. ' sub-total',
- $section->{'subtotal'} # already formatted this
+ $section->{'subtotal'} # already formatted this
],
[ '', '' ],
[ '', '' ],
);
}
-
+
}
$invoice_data{current_less_finance} =
@@ -1312,7 +1313,7 @@ sub print_generic {
];
}
-
+
if ( @items_tax ) {
my $total = {};
$total->{'total_item'} = $self->mt('Sub-total');
@@ -1368,7 +1369,7 @@ sub print_generic {
}
}
-
+
if ( $self->can('_items_total') ) { # should always be true now
# even for multisection, need plain text version
@@ -1399,12 +1400,12 @@ sub print_generic {
push @buf, [ '', '' ];
# if we're showing previous invoices, also show previous
- # credits and payments
- if ( $self->enable_previous
+ # credits and payments
+ if ( $self->enable_previous
and $self->can('_items_credits')
and $self->can('_items_payments') )
{
-
+
# credits
my $credittotal = 0;
foreach my $credit (
@@ -1466,7 +1467,7 @@ sub print_generic {
];
}
$invoice_data{'paymenttotal'} = sprintf('%.2f', $paymenttotal);
-
+
if ( $multisection ) {
$adjust_section->{'subtotal'} = $other_money_char.
sprintf('%.2f', $credittotal + $paymenttotal);
@@ -1475,21 +1476,21 @@ sub print_generic {
#in @extra_sections instead of @sections. obviously.
push @sections, $adjust_section
unless $adjust_section->{sort_weight};
- # do not summarize; adjustments there are shown according to
+ # do not summarize; adjustments there are shown according to
# different rules
}
# create Balance Due message
- {
+ {
my $total;
$total->{'total_item'} = &$embolden_function($self->balance_due_msg);
$total->{'total_amount'} =
&$embolden_function(
- $other_money_char. sprintf('%.2f', #why? $summarypage
+ $other_money_char. sprintf('%.2f', #why? $summarypage
# ? $self->charged +
# $self->billing_balance
# :
- $self->owed + $pr_total
+ $balance_due
)
);
if ( $multisection && !$adjust_section->{sort_weight} ) {
@@ -1499,7 +1500,7 @@ sub print_generic {
push @total_items, $total;
}
push @buf,['','-----------'];
- push @buf,[$self->balance_due_msg, $money_char.
+ push @buf,[$self->balance_due_msg, $money_char.
sprintf("%10.2f", $balance_due ) ];
}
@@ -1519,7 +1520,7 @@ sub print_generic {
push @total_items, $credit_total;
}
push @buf,['','-----------'];
- push @buf,[$self->credit_balance_msg, $money_char.
+ push @buf,[$self->credit_balance_msg, $money_char.
sprintf("%10.2f", -$cust_main->balance ) ];
}
}
@@ -1535,7 +1536,7 @@ sub print_generic {
$total->{'total_item'} = &$embolden_function($self->balance_due_msg);
$total->{'total_amount'} =
&$embolden_function(
- $other_money_char. sprintf('%.2f', $self->owed + $pr_total)
+ $other_money_char. sprintf('%.2f', $balance_due)
);
my $last_section = pop @sections;
$last_section->{'posttotal'} = $total->{'total_item'}. ' '.
@@ -1547,7 +1548,7 @@ sub print_generic {
}
# make a discounts-available section, even without multisection
- if ( $conf->exists('discount-show_available')
+ if ( $conf->exists('discount-show_available')
and my @discounts_avail = $self->_items_discounts_avail ) {
my $discount_section = {
'description' => $self->mt('Discounts Available'),
@@ -1579,7 +1580,7 @@ sub print_generic {
}
# invoice history "section" (not really a section)
- # not to be included in any subtotals, completely independent of
+ # not to be included in any subtotals, completely independent of
# everything...
if ( $conf->exists('previous_invoice_history') and $cust_main->isa('FS::cust_main') ) {
my %history;
@@ -1610,7 +1611,7 @@ sub print_generic {
}
$invoice_data{location_info} = \%location_info;
- # debugging hook: call this with 'diag' => 1 to just get a hash of
+ # debugging hook: call this with 'diag' => 1 to just get a hash of
# the invoice variables
return \%invoice_data if ( $params{'diag'} );
@@ -1635,7 +1636,7 @@ sub print_generic {
@inc_src = map { s/\[\@--/$delimiters{$format}[0]/g;
s/--\@\]/$delimiters{$format}[1]/g;
$_;
- }
+ }
&$convert_map( $conf->config($inc_file, $agentnum) );
}
@@ -1677,7 +1678,7 @@ sub print_generic {
#setup subroutine for the template
$invoice_data{invoice_lines} = sub {
my $lines = shift || scalar(@buf);
- map {
+ map {
scalar(@buf)
? shift @buf
: [ '', '' ];
@@ -1722,22 +1723,6 @@ sub template_conf { warn "bare FS::Template_Mixin::template_conf";
'invoice_';
}
-# helper routine for generating date ranges
-sub _prior_month30s {
- my $self = shift;
- my @ranges = (
- [ 1, 2592000 ], # 0-30 days ago
- [ 2592000, 5184000 ], # 30-60 days ago
- [ 5184000, 7776000 ], # 60-90 days ago
- [ 7776000, 0 ], # 90+ days ago
- );
-
- map { [ $_->[0] ? $self->_date - $_->[0] - 1 : '',
- $_->[1] ? $self->_date - $_->[1] - 1 : '',
- ] }
- @ranges;
-}
-
=item print_ps HASHREF | [ TIME [ , TEMPLATE ] ]
Returns an postscript invoice, as a scalar.
@@ -1816,23 +1801,23 @@ sub print_html {
my $self = shift;
my %params;
if ( ref($_[0]) ) {
- %params = %{ shift() };
+ %params = %{ shift() };
} else {
%params = @_;
}
$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:
+# 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 ...").
+# in "10% of ..."), you have to "escape" the blank as well ("10\%\ of ...").
sub _latex_escape {
my $value = shift;
@@ -1857,18 +1842,18 @@ sub _html_escape_nbsp {
sub _translate_old_latex_format {
warn "_translate_old_latex_format called\n"
- if $DEBUG;
+ 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! $_tr_line->{'description'} .= !,
q! "\\tabularnewline\n~~".!,
q! join( "\\tabularnewline\n~~",!,
q! @{$_tr_line->{'ext_description'}}!,
@@ -1904,9 +1889,9 @@ sub _translate_old_latex_format {
} else {
$line =~ s/\$(\w+)/[\@-- \$$1 --\@]/g;
- push @template, $line;
+ push @template, $line;
}
-
+
}
if ($DEBUG) {
@@ -1926,7 +1911,7 @@ sub terms {
#check for an invoice-specific override
return $self->invoice_terms if $self->invoice_terms;
-
+
#check for a customer- specific override
my $cust_main = $self->cust_main;
return $cust_main->invoice_terms if $cust_main && $cust_main->invoice_terms;
@@ -1980,7 +1965,7 @@ sub balance_due_msg {
return $msg unless $self->terms; # huh?
if ( !$self->conf->exists('invoice_show_prior_due_date')
or $self->conf->exists('invoice_sections') ) {
- # if enabled, the due date is shown with Total New Charges (see
+ # if enabled, the due date is shown with Total New Charges (see
# _items_total) and not here
# (yes, or if invoice_sections is enabled; this is just for compatibility)
if ( $self->due_date ) {
@@ -2012,7 +1997,7 @@ sub balance_due_date {
$duedate;
}
-sub credit_balance_msg {
+sub credit_balance_msg {
my $self = shift;
$self->mt('Credit Balance Remaining')
}
@@ -2180,7 +2165,7 @@ sub generate_email {
if $DEBUG;
@text = $conf->config($tc.'email_pdf_note');
$html = join('<BR>', @text);
-
+
} # else use the plain text invoice
}
@@ -2242,7 +2227,7 @@ sub generate_email {
'Filename' => 'logo.png',
'Content-ID' => "<$content_id>",
;
-
+
if ( ref($self) eq 'FS::cust_bill' && $conf->exists('invoice-barcode') ) {
my $barcode_content_id = join('.', rand()*(2**32), $$, time). "\@$from";
push @related_parts, build MIME::Entity
@@ -2283,7 +2268,7 @@ sub generate_email {
'Data' => [ '<html>',
' <head>',
' <title>',
- ' '. encode_entities($return{'subject'}),
+ ' '. encode_entities($return{'subject'}),
' </title>',
' </head>',
' <body bgcolor="#e8e8e8">',
@@ -2338,7 +2323,7 @@ sub generate_email {
;
} else { # } elsif ( $conf->config('voip-cdr_email_attach') eq 'csv' ) {
-
+
push @otherparts, build MIME::Entity
'Type' => 'text/csv',
'Encoding' => '7bit',
@@ -2458,7 +2443,7 @@ sub postal_mail_fsinc {
my $pages = CAM::PDF->new($file)->numPages;
my $ua = LWP::UserAgent->new(
- 'ssl_opts' => {
+ 'ssl_opts' => {
verify_hostname => 0,
SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE,
SSL_version => 'SSLv3',
@@ -2515,13 +2500,13 @@ sub postal_mail_fsinc {
Generate section information for all items appearing on this invoice.
This will only be called for multi-section invoices.
-For each line item (L<FS::cust_bill_pkg> record), this will fetch all
-related display records (L<FS::cust_bill_pkg_display>) and organize
-them into two groups ("early" and "late" according to whether they come
-before or after the total), then into sections. A subtotal is calculated
+For each line item (L<FS::cust_bill_pkg> record), this will fetch all
+related display records (L<FS::cust_bill_pkg_display>) and organize
+them into two groups ("early" and "late" according to whether they come
+before or after the total), then into sections. A subtotal is calculated
for each section.
-Section descriptions are returned in sort weight order. Each consists
+Section descriptions are returned in sort weight order. Each consists
of a hash containing:
description: the package category name, escaped
@@ -2530,7 +2515,7 @@ tax_section: a flag indicating that the section contains only tax charges
summarized: same as tax_section, for some reason
sort_weight: the package category's sort weight
-If 'condense' is set on the display record, it also contains everything
+If 'condense' is set on the display record, it also contains everything
returned from C<_condense_section()>, i.e. C<_condensed_foo_generator>
coderefs to generate parts of the invoice. This is not advised.
@@ -2539,32 +2524,32 @@ sections.
OPTIONS may include:
-by_location: a flag to divide the invoice into sections by location.
-Each section hash will have a 'location' element containing a hashref of
+by_location: a flag to divide the invoice into sections by location.
+Each section hash will have a 'location' element containing a hashref of
the location fields (see L<FS::cust_location>). The section description
-will be the location label, but the template can use any of the location
+will be the location label, but the template can use any of the location
fields to create a suitable label.
-by_category: a flag to divide the invoice into sections using display
-records (see L<FS::cust_bill_pkg_display>). This is the "traditional"
+by_category: a flag to divide the invoice into sections using display
+records (see L<FS::cust_bill_pkg_display>). This is the "traditional"
behavior. Each section hash will have a 'category' element containing
-the section name from the display record (which probably equals the
+the section name from the display record (which probably equals the
category name of the package, but may not in some cases).
summary: a flag indicating that this is a summary-format invoice.
Turning this on has the following effects:
- Ignores display items with the 'summary' flag.
- Places all sections in the "early" group even if they have post_total.
-- Creates sections for all non-disabled package categories, even if they
+- Creates sections for all non-disabled package categories, even if they
have no charges on this invoice, as well as a section with no name.
escape: an escape function to use for section titles.
-extra_sections: an arrayref of additional sections to return after the
-sorted list. If there are any of these, section subtotals exclude
+extra_sections: an arrayref of additional sections to return after the
+sorted list. If there are any of these, section subtotals exclude
usage charges.
-format: 'latex', 'html', or 'template' (i.e. text). Not used, but
+format: 'latex', 'html', or 'template' (i.e. text). Not used, but
passed through to C<_condense_section()>.
=cut
@@ -2573,7 +2558,7 @@ use vars qw(%pkg_category_cache);
sub _items_sections {
my $self = shift;
my %opt = @_;
-
+
my $escape = $opt{escape};
my @extra_sections = @{ $opt{extra_sections} || [] };
@@ -2586,12 +2571,12 @@ sub _items_sections {
my %not_tax = ();
# About tax items + multisection invoices:
- # If either invoice_*summary option is enabled, AND there is a
- # package category with the name of the tax, then there will be
+ # If either invoice_*summary option is enabled, AND there is a
+ # package category with the name of the tax, then there will be
# a display record assigning the tax item to that category.
#
# However, the taxes are always placed in the "Taxes, Surcharges,
- # and Fees" section regardless of that. The only effect of the
+ # and Fees" section regardless of that. The only effect of the
# display record is to create a subtotal for the summary page.
# cache these
@@ -2630,11 +2615,11 @@ sub _items_sections {
if $cust_bill_pkg->pkgnum or $cust_bill_pkg->feepart;
# there's actually a very important piece of logic buried in here:
- # incrementing $late_subtotal{$section} CREATES
- # $late_subtotal{$section}. keys(%late_subtotal) is later used
+ # incrementing $late_subtotal{$section} CREATES
+ # $late_subtotal{$section}. keys(%late_subtotal) is later used
# to define the list of late sections, and likewise keys(%subtotal).
- # When _items_cust_bill_pkg is called to generate line items for
- # real, it will be called with 'section' => $section for each
+ # When _items_cust_bill_pkg is called to generate line items for
+ # real, it will be called with 'section' => $section for each
# of these.
if ( $display->post_total && !$opt{summary} ) {
if (! $type || $type eq 'S') {
@@ -2654,7 +2639,7 @@ sub _items_sections {
if $cust_bill_pkg->recur != 0
|| $cust_bill_pkg->recur_show_zero;
}
-
+
if ($type && $type eq 'U') {
$late_subtotal{$locationnum}{$section} += $usage
unless scalar(@extra_sections);
@@ -2716,10 +2701,10 @@ sub _items_sections {
$section->{'locationnum'} = $locationnum;
my $location = FS::cust_location->by_key($locationnum);
$section->{'description'} = &{ $escape }($location->location_label);
- # Better ideas? This will roughly group them by proximity,
+ # Better ideas? This will roughly group them by proximity,
# which alpha sorting on any of the address fields won't.
# Sorting by locationnum is meaningless.
- # We have to sort on _something_ or the order may change
+ # We have to sort on _something_ or the order may change
# randomly from one invoice to the next, which will confuse
# people.
$section->{'sort_weight'} = sprintf('%012s',$location->zip) .
@@ -2748,10 +2733,10 @@ sub _items_sections {
} # foreach $sectionname
} #foreach $locationnum
push @these, @extra_sections if $post_total == 0;
- # need an alpha sort for location sections, because postal codes can
+ # need an alpha sort for location sections, because postal codes can
# be non-numeric
$sections[ $post_total ] = [ sort {
- $opt{'by_location'} ?
+ $opt{'by_location'} ?
($a->{sort_weight} cmp $b->{sort_weight}) :
($a->{sort_weight} <=> $b->{sort_weight})
} @these ];
@@ -2996,8 +2981,8 @@ sub _condensed_total_line_generator {
=item _items_pkg [ OPTIONS ]
-Return line item hashes for each package item on this invoice. Nearly
-equivalent to
+Return line item hashes for each package item on this invoice. Nearly
+equivalent to
$self->_items_cust_bill_pkg([ $self->cust_bill_pkg ])
@@ -3065,7 +3050,7 @@ sub _items_fee {
}
} # otherwise include them all in the main section
# XXX what to do when sectioning by location?
-
+
my @ext_desc;
my %base_invnums; # invnum => invoice date
foreach ($cust_bill_pkg->cust_bill_pkg_fee) {
@@ -3153,7 +3138,7 @@ sub _taxsort {
sub _items_tax {
my $self = shift;
- my @cust_bill_pkg = sort _taxsort grep { ! $_->pkgnum and ! $_->feepart }
+ my @cust_bill_pkg = sort _taxsort grep { ! $_->pkgnum and ! $_->feepart }
$self->cust_bill_pkg;
my @items = $self->_items_cust_bill_pkg(\@cust_bill_pkg, @_);
@@ -3182,7 +3167,7 @@ escape_function: the function used to escape strings.
DEPRECATED? (expensive, mostly unused?)
format_function: the function used to format CDRs.
-section: a hashref containing 'category' and/or 'locationnum'; if this
+section: a hashref containing 'category' and/or 'locationnum'; if this
is present, only returns line items that belong to that category and/or
location (whichever is defined).
@@ -3191,8 +3176,8 @@ which does something complicated.
Returns a list of hashrefs, each of which may contain:
-pkgnum, description, amount, unit_amount, quantity, pkgpart, _is_setup, and
-ext_description, which is an arrayref of detail lines to show below
+pkgnum, description, amount, unit_amount, quantity, pkgpart, _is_setup, and
+ext_description, which is an arrayref of detail lines to show below
the package line.
=cut
@@ -3278,13 +3263,13 @@ sub _items_cust_bill_pkg {
# this is a location section; skip packages that aren't at this
# service location.
next if $cust_bill_pkg->pkgnum == 0; # skips fees...
- next if $self->cust_pkg_hash->{ $cust_bill_pkg->pkgnum }->locationnum
+ next if $self->cust_pkg_hash->{ $cust_bill_pkg->pkgnum }->locationnum
!= $locationnum;
}
# Consider display records for this item to determine if it belongs
# in this section. Note that if there are no display records, there
- # will be a default pseudo-record that includes all charge types
+ # will be a default pseudo-record that includes all charge types
# and has no section name.
my @cust_bill_pkg_display = $cust_bill_pkg->can('cust_bill_pkg_display')
? $cust_bill_pkg->cust_bill_pkg_display
@@ -3301,7 +3286,7 @@ sub _items_cust_bill_pkg {
@cust_bill_pkg_display;
} else {
# otherwise, process all display records that aren't usage summaries
- # (I don't think there should be usage summaries if you aren't using
+ # (I don't think there should be usage summaries if you aren't using
# category sections, but this is the historical behavior)
@cust_bill_pkg_display = grep { !$_->summary }
@cust_bill_pkg_display;
@@ -3332,14 +3317,14 @@ sub _items_cust_bill_pkg {
warn "$me _items_cust_bill_pkg cust_bill_pkg is non-tax\n"
if $DEBUG > 1;
-
+
my $cust_pkg = $cust_bill_pkg->cust_pkg;
my $part_pkg = $cust_pkg->part_pkg;
# which pkgpart to show for display purposes?
my $pkgpart = $cust_bill_pkg->pkgpart_override || $cust_pkg->pkgpart;
- # start/end dates for invoice formats that do nonstandard
+ # start/end dates for invoice formats that do nonstandard
# things with them
my %item_dates = ();
%item_dates = map { $_ => $cust_bill_pkg->$_ } ('sdate', 'edate')
@@ -3360,7 +3345,7 @@ sub _items_cust_bill_pkg {
if $DEBUG > 1;
# append the word 'Setup' to the setup line if there's going to be
- # a recur line for the same package (i.e. not a one-time charge)
+ # a recur line for the same package (i.e. not a one-time charge)
# XXX localization
my $description = $desc;
$description .= ' Setup'
@@ -3382,7 +3367,7 @@ sub _items_cust_bill_pkg {
unless ( $part_pkg->hide_svc_detail ) {
- # still pass the svc_label through to the template, even if
+ # still pass the svc_label through to the template, even if
# not displaying it as an ext_description
@svc_labels = map &{$escape_function}($_),
$cust_pkg->h_labels_short($self->_date,
@@ -3499,7 +3484,7 @@ sub _items_cust_bill_pkg {
# or this is a usage summary line
|| $is_summary && $type && $type eq 'U'
# or this is a usage line and there's a recurring line
- # for the package in the same section (which will
+ # for the package in the same section (which will
# have service labels already)
|| ($type eq 'U' and defined($r))
)
@@ -3525,17 +3510,17 @@ sub _items_cust_bill_pkg {
# Display of seconds_since_sqlradacct:
# On the invoice, when processing @detail_items, look for a field
- # named 'seconds'. This will contain total seconds for each
- # service, in the same order as @ext_description. For services
+ # named 'seconds'. This will contain total seconds for each
+ # service, in the same order as @ext_description. For services
# that don't support this it will show undef.
- if ( $conf->exists('svc_acct-usage_seconds')
+ if ( $conf->exists('svc_acct-usage_seconds')
and ! $cust_bill_pkg->pkgpart_override ) {
- foreach my $cust_svc (
- $cust_pkg->h_cust_svc(@dates, 'I')
+ foreach my $cust_svc (
+ $cust_pkg->h_cust_svc(@dates, 'I')
) {
- # eval because not having any part_export_usage exports
- # is a fatal error, last_bill/_date because that's how
+ # eval because not having any part_export_usage exports
+ # is a fatal error, last_bill/_date because that's how
# sqlradius_hour billing does it
my $sec = eval {
$cust_svc->seconds_since_sqlradacct($dates[1] || 0, $dates[0]);
@@ -3560,7 +3545,7 @@ sub _items_cust_bill_pkg {
warn "$me _items_cust_bill_pkg calculating amount\n"
if $DEBUG > 1;
-
+
my $amount = 0;
if (!$type) {
$amount = $cust_bill_pkg->recur;
@@ -3569,7 +3554,7 @@ sub _items_cust_bill_pkg {
} elsif ($type eq 'U') {
$amount = $cust_bill_pkg->usage;
}
-
+
if ( !$type || $type eq 'R' ) {
warn "$me _items_cust_bill_pkg adding recur\n"
@@ -3637,7 +3622,7 @@ sub _items_cust_bill_pkg {
# items of this kind should normally not have sdate/edate.
push @b, {
'description' => $desc,
- 'amount' => sprintf('%.2f', $cust_bill_pkg->setup
+ 'amount' => sprintf('%.2f', $cust_bill_pkg->setup
+ $cust_bill_pkg->recur)
};
@@ -3650,7 +3635,7 @@ sub _items_cust_bill_pkg {
# case 2: we are showing a setup line for a package that has
# no base recurring fee
or ( $type eq 'S' and $cust_bill_pkg->unitrecur == 0 )
- # case 3: we are showing a recur line for a package that has
+ # case 3: we are showing a recur line for a package that has
# a base recurring fee
or ( $type eq 'R' and $cust_bill_pkg->unitrecur > 0 )
) {
@@ -3667,7 +3652,7 @@ sub _items_cust_bill_pkg {
$_ = &{$escape_function}($_) foreach @{ $d->{ext_description} };
}
- # update the active line (before the discount) to show the
+ # update the active line (before the discount) to show the
# original price (whether this is a hidden line or not)
$s->{amount} -= $item_discount->{setup_amount} if $s;
@@ -3712,9 +3697,9 @@ sub _items_cust_bill_pkg {
=item _items_discounts_avail
Returns an array of line item hashrefs representing available term discounts
-for this invoice. This makes the same assumptions that apply to term
-discounts in general: that the package is billed monthly, at a flat rate,
-with no usage charges. A prorated first month will be handled, as will
+for this invoice. This makes the same assumptions that apply to term
+discounts in general: that the package is billed monthly, at a flat rate,
+with no usage charges. A prorated first month will be handled, as will
a setup fee if the discount is allowed to apply to setup fees.
=cut
@@ -3722,7 +3707,7 @@ a setup fee if the discount is allowed to apply to setup fees.
sub _items_discounts_avail {
my $self = shift;
- #maybe move this method from cust_bill when quotations support discount_plans
+ #maybe move this method from cust_bill when quotations support discount_plans
return () unless $self->can('discount_plans');
my %plans = $self->discount_plans;
@@ -3734,7 +3719,7 @@ sub _items_discounts_avail {
my $plan = $plans{$months};
my $term_total = sprintf('%.2f', $plan->discounted_total);
- my $percent = sprintf('%.0f',
+ my $percent = sprintf('%.0f',
100 * (1 - $term_total / $plan->base_total) );
my $permonth = sprintf('%.2f', $term_total / $months);
my $detail = $self->mt('discount on item'). ' '.
@@ -3747,7 +3732,7 @@ sub _items_discounts_avail {
+{
description => $self->mt('Save [_1]% by paying for [_2] months',
$percent, $months),
- amount => $self->mt('[_1] ([_2] per month)',
+ amount => $self->mt('[_1] ([_2] per month)',
$term_total, $money_char.$permonth),
ext_description => ($detail || ''),
}