X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2Fcust_main%2FBilling.pm;h=eee0958e0632e3322b2ea15df30423a4eeb5af08;hb=61a0dc609fd2b7db3571f8f86672481d1e064331;hp=367745ddb9cbd63824242e2eaca4a4331d5a5e45;hpb=c14803e37f1bfdcff882ee5cb35a9b10a93a88c9;p=freeside.git diff --git a/FS/FS/cust_main/Billing.pm b/FS/FS/cust_main/Billing.pm index 367745ddb..eee0958e0 100644 --- a/FS/FS/cust_main/Billing.pm +++ b/FS/FS/cust_main/Billing.pm @@ -818,16 +818,17 @@ sub bill { # calculate and append taxes if ( ! $tax_is_batch) { - my $arrayref_or_error = $tax_engines{$pass}->calculate_taxes($cust_bill); + local $@; + my $arrayref = eval { $tax_engines{$pass}->calculate_taxes($cust_bill) }; - unless ( ref( $arrayref_or_error ) ) { + if ( $@ ) { $dbh->rollback if $oldAutoCommit && !$options{no_commit}; - return $arrayref_or_error; + return $@; } # or should this be in TaxEngine? my $total_tax = 0; - foreach my $taxline ( @$arrayref_or_error ) { + foreach my $taxline ( @$arrayref ) { $total_tax += $taxline->setup; $taxline->set('invnum' => $cust_bill->invnum); # just to be sure push @cust_bill_pkg, $taxline; # for return_bill @@ -879,56 +880,66 @@ sub bill { } #discard bundled packages of 0 value +# XXX we should reconsider whether we even need this sub _omit_zero_value_bundles { my @in = @_; - my @cust_bill_pkg = (); - my @cust_bill_pkg_bundle = (); - my $sum = 0; - my $discount_show_always = 0; - + my @out = (); + my @bundle = (); + my $discount_show_always = $conf->exists('discount-show-always'); + my $show_this = 0; + + # Sort @in the same way we do during invoice rendering, so we can identify + # bundles. See FS::Template_Mixin::_items_nontax. + @in = sort { $a->pkgnum <=> $b->pkgnum or + $a->sdate <=> $b->sdate or + ($a->pkgpart_override ? 0 : -1) or + ($b->pkgpart_override ? 0 : 1) or + $b->hidden cmp $a->hidden or + $a->pkgpart_override <=> $b->pkgpart_override + } @in; + + # this is a pack-and-deliver pattern. every time there's a cust_bill_pkg + # _without_ pkgpart_override, that's the start of the new bundle. if there's + # an existing bundle, and it contains a nonzero amount (or a zero amount + # that's displayable anyway), push all line items in the bundle. foreach my $cust_bill_pkg ( @in ) { - $discount_show_always = ($cust_bill_pkg->get('discounts') - && scalar(@{$cust_bill_pkg->get('discounts')}) - && $conf->exists('discount-show-always')); - - warn " pkgnum ". $cust_bill_pkg->pkgnum. " sum $sum, ". - "setup_show_zero ". $cust_bill_pkg->setup_show_zero. - "recur_show_zero ". $cust_bill_pkg->recur_show_zero. "\n" - if $DEBUG > 0; - - if (scalar(@cust_bill_pkg_bundle) && !$cust_bill_pkg->pkgpart_override) { - push @cust_bill_pkg, @cust_bill_pkg_bundle - if $sum > 0 - || ($sum == 0 && ( $discount_show_always - || grep {$_->recur_show_zero || $_->setup_show_zero} - @cust_bill_pkg_bundle - ) - ); - @cust_bill_pkg_bundle = (); - $sum = 0; + if (scalar(@bundle) and !$cust_bill_pkg->pkgpart_override) { + # ship out this bundle and reset it + if ( $show_this ) { + push @out, @bundle; + } + @bundle = (); + $show_this = 0; } - $sum += $cust_bill_pkg->setup + $cust_bill_pkg->recur; - push @cust_bill_pkg_bundle, $cust_bill_pkg; + # add this item to the current bundle + push @bundle, $cust_bill_pkg; + # determine if it makes the bundle displayable + if ( $cust_bill_pkg->setup > 0 + or $cust_bill_pkg->recur > 0 + or $cust_bill_pkg->setup_show_zero + or $cust_bill_pkg->recur_show_zero + or ($discount_show_always + and scalar(@{ $cust_bill_pkg->get('discounts')}) + ) + ) { + $show_this++; + } } - push @cust_bill_pkg, @cust_bill_pkg_bundle - if $sum > 0 - || ($sum == 0 && ( $discount_show_always - || grep {$_->recur_show_zero || $_->setup_show_zero} - @cust_bill_pkg_bundle - ) - ); + # last bundle + if ( $show_this) { + push @out, @bundle; + } warn " _omit_zero_value_bundles: ". scalar(@in). - '->'. scalar(@cust_bill_pkg). "\n" #. Dumper(@cust_bill_pkg). "\n" + '->'. scalar(@out). "\n" #. Dumper(@out). "\n" if $DEBUG > 2; - (@cust_bill_pkg); - + @out; } sub _make_lines { @@ -1013,8 +1024,14 @@ sub _make_lines { return "$@ running calc_setup for $cust_pkg\n" if $@; - $unitsetup = $cust_pkg->base_setup() - || $setup; #XXX uuh + # Only increment unitsetup here if there IS a setup fee. + # prorate_defer_bill may cause calc_setup on a setup-stage package + # to return zero, and the setup fee to be charged later. (This happens + # when it's first billed on the prorate cutoff day. RT#31276.) + if ( $setup ) { + $unitsetup = $cust_pkg->base_setup() + || $setup; #XXX uuh + } if ( $setup_param{'billed_currency'} ) { $setup_billed_currency = delete $setup_param{'billed_currency'}; @@ -1106,6 +1123,14 @@ sub _make_lines { return "$@ running $method for $cust_pkg\n" if ( $@ ); + if ($recur eq 'NOTHING') { + # then calc_cancel (or calc_recur but that's not used) has declined to + # generate a recurring lineitem at all. treat this as zero, but also + # try not to generate a lineitem. + $recur = 0; + $lineitems--; + } + #base_cancel??? $unitrecur = $cust_pkg->base_recur( \$sdate ) || $recur; #XXX uuh, better @@ -1124,19 +1149,39 @@ sub _make_lines { # its frequency my $main_pkg_freq = $main_pkg->part_pkg->freq; my $supp_pkg_freq = $part_pkg->freq; - my $ratio = $supp_pkg_freq / $main_pkg_freq; - if ( $ratio != int($ratio) ) { + if ( $supp_pkg_freq == 0 or $main_pkg_freq == 0 ) { # the UI should prevent setting up packages like this, but just # in case - return "supplemental package period is not an integer multiple of main package period"; + return "unable to calculate supplemental package period ratio"; } - $next_bill = $sdate; - for (1..$ratio) { - $next_bill = $part_pkg->add_freq( $next_bill, $main_pkg_freq ); + my $ratio = $supp_pkg_freq / $main_pkg_freq; + if ( $ratio == int($ratio) ) { + # simple case: main package is X months, supp package is X*A months, + # advance supp package to where the main package will be in A cycles. + $next_bill = $sdate; + for (1..$ratio) { + $next_bill = $part_pkg->add_freq( $next_bill, $main_pkg_freq ); + } + } else { + # harder case: main package is X months, supp package is Y months. + # advance supp package by Y months. then if they're within half a + # month of each other, resync them. this may result in the period + # not being exactly Y months. + $next_bill = $part_pkg->add_freq( $sdate, $supp_pkg_freq ); + my $main_next_bill = $main_pkg->bill; + if ( $main_pkg->bill <= $time ) { + # then the main package has not yet been billed on this cycle; + # predict what its bill date will be. + $main_next_bill = + $part_pkg->add_freq( $main_next_bill, $main_pkg_freq ); + } + if ( abs($main_next_bill - $next_bill) < 86400*15 ) { + $next_bill = $main_next_bill; + } } } else { - # the normal case + # the normal case, not a supplemental package $next_bill = $part_pkg->add_freq($sdate, $options{freq_override} || 0); return "unparsable frequency: ". $part_pkg->freq if $next_bill == -1; @@ -1157,7 +1202,7 @@ sub _make_lines { # Add an additional setup fee at the billing stage. # Used for prorate_defer_bill. $setup += $param{'setup_fee'}; - $unitsetup += $param{'setup_fee'}; + $unitsetup = $cust_pkg->base_setup(); $lineitems++; } @@ -1421,7 +1466,7 @@ sub _handle_taxes { return if ( $self->payby eq 'COMP' ); #dubious - if ( $conf->exists('enable_taxproducts') + if ( $conf->config('enable_taxproducts') && ( scalar($part_item->part_pkg_taxoverride) || $part_item->has_taxproduct ) @@ -2168,6 +2213,7 @@ sub due_cust_event { =item apply_payments_and_credits [ OPTION => VALUE ... ] Applies unapplied payments and credits. +Payments with the no_auto_apply flag set will not be applied. In most cases, this new method should be used in place of sequential apply_payments and apply_credits methods. @@ -2310,6 +2356,7 @@ sub apply_credits { Applies (see L) unapplied payments (see L) to outstanding invoice balances in chronological order. +Payments with the no_auto_apply flag set will not be applied. #and returns the value of any remaining unapplied payments. @@ -2339,7 +2386,7 @@ sub apply_payments { #return 0 unless - my @payments = $self->unapplied_cust_pay; + my @payments = grep { !$_->no_auto_apply } $self->unapplied_cust_pay; my @invoices = $self->open_cust_bill;