X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2Fcust_bill.pm;h=0a1577f63ad02bb51a4f02d88806896c493ec600;hb=80511cb4158b98db01deec317e5408675487bc6e;hp=1d3ddb8ee473fb16efa335170cdb6b643e1d48d5;hpb=cbc0261db8ac2e1105e5a32d7b5dd90c4adaa720;p=freeside.git diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm index 1d3ddb8ee..0a1577f63 100644 --- a/FS/FS/cust_bill.pm +++ b/FS/FS/cust_bill.pm @@ -243,7 +243,6 @@ sub delete { cust_event cust_credit_bill cust_bill_pay - cust_bill_pay cust_credit_bill cust_pay_batch cust_bill_pay_batch @@ -2662,7 +2661,8 @@ sub print_generic { # eval to avoid death for unimplemented languages my $dh = eval { Date::Language->new($info{'name'}) } || Date::Language->new(); # fall back to English - $invoice_data{'time2str'} = sub { $dh->time2str(@_) }; + # prototype here to silence warnings + $invoice_data{'time2str'} = sub ($;$$) { $dh->time2str(@_) }; # eventually use this date handle everywhere in here, too my $min_sdate = 999999999999; @@ -2855,7 +2855,7 @@ sub print_generic { my $previous_section = { 'description' => $self->mt('Previous Charges'), 'subtotal' => $other_money_char. sprintf('%.2f', $pr_total), - 'summarized' => $summarypage ? 'Y' : '', + 'summarized' => '', #why? $summarypage ? 'Y' : '', }; $previous_section->{posttotal} = '0 / 30 / 60 / 90 days overdue '. join(' / ', map { $cust_main->balance_date_range(@$_) } @@ -2866,12 +2866,11 @@ sub print_generic { my $taxtotal = 0; my $tax_section = { 'description' => $self->mt('Taxes, Surcharges, and Fees'), 'subtotal' => $taxtotal, # adjusted below - 'summarized' => $summarypage ? 'Y' : '', }; my $tax_weight = _pkg_category($tax_section->{description}) ? _pkg_category($tax_section->{description})->weight : 0; - $tax_section->{'summarized'} = $summarypage && !$tax_weight ? 'Y' : ''; + $tax_section->{'summarized'} = ''; #why? $summarypage && !$tax_weight ? 'Y' : ''; $tax_section->{'sort_weight'} = $tax_weight; @@ -2879,12 +2878,11 @@ sub print_generic { my $adjust_section = { 'description' => $self->mt('Credits, Payments, and Adjustments'), 'subtotal' => 0, # adjusted below - 'summarized' => $summarypage ? 'Y' : '', }; my $adjust_weight = _pkg_category($adjust_section->{description}) ? _pkg_category($adjust_section->{description})->weight : 0; - $adjust_section->{'summarized'} = $summarypage && !$adjust_weight ? 'Y' : ''; + $adjust_section->{'summarized'} = ''; #why? $summarypage && !$adjust_weight ? 'Y' : ''; $adjust_section->{'sort_weight'} = $adjust_weight; my $unsquelched = $params{unsquelch_cdr} || $cust_main->squelch_cdr ne 'Y'; @@ -2924,7 +2922,8 @@ sub print_generic { } } else {# not multisection # make a default section - push @sections, { 'description' => '', 'subtotal' => '' }; + push @sections, { 'description' => '', 'subtotal' => '', + 'no_subtotal' => 1 }; # and calculate the finance charge total, since it won't get done otherwise. # XXX possibly other totals? # XXX possibly finance_pkgclass should not be used in this manner? @@ -2990,9 +2989,9 @@ sub print_generic { my ($didsummary,$minutes) = $self->_did_summary; my $didsummary_desc = 'DID Activity Summary (since last invoice)'; push @detail_items, - { 'description' => $didsummary_desc, - 'ext_description' => [ $didsummary, $minutes ], - }; + { 'description' => $didsummary_desc, + 'ext_description' => [ $didsummary, $minutes ], + }; } foreach my $section (@sections, @$late_sections) { @@ -3088,7 +3087,7 @@ sub print_generic { } } - + $invoice_data{current_less_finance} = sprintf('%.2f', $self->charged - $invoice_data{finance_amount} ); @@ -3267,6 +3266,7 @@ sub print_generic { unless $adjust_section->{sort_weight}; } + # create Balance Due message { my $total; $total->{'total_item'} = &$embolden_function($self->balance_due_msg); @@ -3327,6 +3327,26 @@ sub print_generic { if $unsquelched; } + # make a discounts-available section, even without multisection + if ( $conf->exists('discount-show_available') + and my @discounts_avail = $self->_items_discounts_avail ) { + my $discount_section = { + 'description' => $self->mt('Discounts Available'), + 'subtotal' => '', + 'no_subtotal' => 1, + }; + + push @sections, $discount_section; + push @detail_items, map { +{ + 'ref' => '', #should this be something else? + 'section' => $discount_section, + 'description' => &$escape_function( $_->{description} ), + 'amount' => $money_char . &$escape_function( $_->{amount} ), + 'ext_description' => [ &$escape_function($_->{ext_description}) || () ], + } } @discounts_avail; + } + + # All sections and items are built; now fill in templates. my @includelist = (); push @includelist, 'summary' if $summarypage; foreach my $include ( @includelist ) { @@ -3389,8 +3409,7 @@ sub print_generic { } #setup subroutine for the template - #sub FS::cust_bill::_template::invoice_lines { # good god, no - $invoice_data{invoice_lines} = sub { # much better + $invoice_data{invoice_lines} = sub { my $lines = shift || scalar(@buf); map { scalar(@buf) @@ -5147,6 +5166,114 @@ sub _items_payments { } +=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 +a setup fee if the discount is allowed to apply to setup fees. + +=cut + +sub _items_discounts_avail { + my $self = shift; + my %terms; + my $list_pkgnums = 0; # if any packages are not eligible for all discounts + + my ($previous_balance) = $self->previous; + + foreach (qsearch('discount',{ 'months' => { op => '>', value => 1} })) { + $terms{$_->months} = { + pkgnums => [], + base => $previous_balance || 0, # pre-discount sum of charges + discounted => $previous_balance || 0, # post-discount sum + list_pkgnums => 0, # whether any packages are not discounted + } + } + foreach my $months (keys %terms) { + my $hash = $terms{$months}; + + # tricky, because packages may not all be eligible for the same discounts + foreach my $cust_bill_pkg ( $self->cust_bill_pkg ) { + my $cust_pkg = $cust_bill_pkg->cust_pkg or next; + my $part_pkg = $cust_pkg->part_pkg or next; + + next if $part_pkg->freq ne '1'; + my $setup = $cust_bill_pkg->setup || 0; + my $recur = $cust_bill_pkg->recur || 0; + my $permonth = $part_pkg->base_recur_permonth || 0; + + my ($discount) = grep { $_->months == $months } + map { $_->discount } $part_pkg->part_pkg_discount; + + $hash->{base} += $setup + $recur + ($months - 1) * $permonth; + if ($discount) { + + my $discountable; + if ( $discount->setup ) { + $discountable += $setup; + } + else { + $hash->{discounted} += $setup; + } + + if ( $discount->percent ) { + $discountable += $months * $permonth; + $discountable -= ($discountable * $discount->percent / 100); + $discountable -= ($permonth - $recur); # correct for prorate + $hash->{discounted} += $discountable; + } + else { + $discountable += $recur; + $discountable -= $discount->amount * $recur/$permonth; + + $discountable += ($months - 1) * max($permonth - $discount->amount,0); + } + + $hash->{discounted} += $discountable; + push @{ $hash->{pkgnums} }, $cust_pkg->pkgnum; + } + else { #no discount + $hash->{discounted} += $setup + $recur + ($months - 1) * $permonth; + $hash->{list_pkgnums} = 1; + } + } #foreach $cust_bill_pkg + + # don't show this line if no packages have discounts at this term + # or if there are no new charges to apply the discount to + delete $terms{$months} if $hash->{base} == $hash->{discounted} + or $hash->{base} == 0; + + } + + $list_pkgnums = grep { $_->{list_pkgnums} > 0 } values %terms; + + foreach my $months (keys %terms) { + my $hash = $terms{$months}; + my $term_total = sprintf('%.2f', $hash->{discounted}); + # possibly shouldn't include previous balance in these? + my $percent = sprintf('%.0f', 100 * (1 - $term_total / $hash->{base}) ); + my $permonth = sprintf('%.2f', $term_total / $months); + + $hash->{description} = $self->mt('Save [_1]% by paying for [_2] months', + $percent, $months + ); + $hash->{amount} = $self->mt('[_1] ([_2] per month)', + $term_total, $money_char.$permonth + ); + + my @detail; + if ( $list_pkgnums ) { + push @detail, $self->mt('discount on item'). ' '. + join(', ', map { "#$_" } @{ $hash->{pkgnums} }); + } + $hash->{ext_description} = join ', ', @detail; + } + + map { $terms{$_} } sort {$b <=> $a} keys %terms; +} + =item call_details [ OPTION => VALUE ... ] Returns an array of CSV strings representing the call details for this invoice