X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2Fcust_main%2FBilling.pm;h=29535f25c14c4bae614166bb19c22255db3c17f1;hb=47ac2424ffb654df0def63cc805a87bafb6376bd;hp=87be4e68d3f12ffa30c2ddc48db0e228f4aaddd8;hpb=5b193ee2c5fae0743699adb1a28591f2a1735eb7;p=freeside.git diff --git a/FS/FS/cust_main/Billing.pm b/FS/FS/cust_main/Billing.pm index 87be4e68d..29535f25c 100644 --- a/FS/FS/cust_main/Billing.pm +++ b/FS/FS/cust_main/Billing.pm @@ -880,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 { @@ -989,9 +999,10 @@ sub _make_lines { # - it doesn't already HAVE a setup date # - or a start date in the future # - and it's not suspended + # - and it doesn't have an expire date in the past # - # The last condition used to check the "disable_setup_suspended" option but - # that's obsolete. We now never set the setup date on a suspended package. + # The "disable_setup_suspended" option is now obsolete; we never set the + # setup date on a suspended package. if ( ! $options{recurring_only} and ! $options{cancel} and ( $options{'resetup'} @@ -1002,6 +1013,8 @@ sub _make_lines { && ( ! $cust_pkg->getfield('susp') ) ) ) + and ( ! $cust_pkg->expire + || $cust_pkg->expire > $cmp_time ) ) { @@ -1014,8 +1027,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'}; @@ -1043,6 +1062,20 @@ sub _make_lines { my $recur_billed_currency = ''; my $recur_billed_amount = 0; my $sdate; + # Conditions for billing the recurring fee: + # - the package doesn't have a future start date + # - and it's not suspended + # - unless suspend_bill is enabled on the package or package def + # - but still not, if the package is on hold + # - or it's suspended for a delayed cancellation + # - and its next bill date is in the past + # - or it doesn't have a next bill date yet + # - or it's a one-time charge + # - or it's a CDR plan with the "bill_every_call" option + # - or it's being canceled + # - and it doesn't have an expire date in the past (this can happen with + # advance billing) + # - again, unless it's being canceled if ( ! $cust_pkg->start_date and ( ! $cust_pkg->susp @@ -1061,6 +1094,12 @@ sub _make_lines { && $part_pkg->option('bill_every_call') ) || $options{cancel} + + and + ( ! $cust_pkg->expire + || $cust_pkg->expire > $cmp_time + || $options{cancel} + ) ) { # XXX should this be a package event? probably. events are called @@ -1133,21 +1172,42 @@ 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 + return "unparsable frequency: ". + ($options{freq_override} || $part_pkg->freq) if $next_bill == -1; } @@ -1166,7 +1226,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++; } @@ -2177,6 +2237,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. @@ -2319,6 +2380,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. @@ -2348,7 +2410,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;