X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=blobdiff_plain;f=FS%2FFS%2Fcust_main%2FBilling.pm;h=e94cd84938724988acd82095f29886f521fff951;hp=cbe8b64663f1f7026e6cce1234c2da492b2b010d;hb=077bb34b3467c3320440c49b76064f664c0eee98;hpb=b08fdcdd5b0c7e32e0a666fd9a044651dd575589 diff --git a/FS/FS/cust_main/Billing.pm b/FS/FS/cust_main/Billing.pm index cbe8b6466..e94cd8493 100644 --- a/FS/FS/cust_main/Billing.pm +++ b/FS/FS/cust_main/Billing.pm @@ -20,6 +20,7 @@ use FS::cust_bill_pkg_tax_rate_location; use FS::part_event; use FS::part_event_condition; use FS::pkg_category; +use POSIX; # 1 is mostly method/subroutine entry and options # 2 traces progress of some operations @@ -112,7 +113,7 @@ sub bill_and_collect { my $job = $options{'job'}; $job->update_statustext('0,cleaning expired packages') if $job; - $error = $self->cancel_expired_pkgs( $options{actual_time} ); + $error = $self->cancel_expired_pkgs( $self->day_end( $options{actual_time} ) ); if ( $error ) { $error = "Error expiring custnum ". $self->custnum. ": $error"; if ( $options{fatal} && $options{fatal} eq 'return' ) { return $error; } @@ -120,7 +121,7 @@ sub bill_and_collect { else { warn $error; } } - $error = $self->suspend_adjourned_pkgs( $options{actual_time} ); + $error = $self->suspend_adjourned_pkgs( $self->day_end( $options{actual_time} ) ); if ( $error ) { $error = "Error adjourning custnum ". $self->custnum. ": $error"; if ( $options{fatal} && $options{fatal} eq 'return' ) { return $error; } @@ -164,9 +165,22 @@ sub bill_and_collect { } +sub day_end { + # XXX: sometimes "incorrect" if crossing DST boundaries? + + my $self = shift; + my $time = shift; + + return $time unless $conf->exists('next-bill-ignore-time'); + + my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = + localtime($time); + mktime(59,59,23,$mday,$mon,$year,$wday,$yday,$isdst); +} + sub cancel_expired_pkgs { my ( $self, $time, %options ) = @_; - + my @cancel_pkgs = $self->ncancelled_pkgs( { 'extra_sql' => " AND expire IS NOT NULL AND expire > 0 AND expire <= $time " } ); @@ -189,7 +203,7 @@ sub cancel_expired_pkgs { sub suspend_adjourned_pkgs { my ( $self, $time, %options ) = @_; - + my @susp_pkgs = $self->ncancelled_pkgs( { 'extra_sql' => " AND ( susp IS NULL OR susp = 0 ) @@ -402,18 +416,27 @@ sub bill { my $pass = ($cust_pkg->no_auto || $part_pkg->no_auto) ? 'no_auto' : ''; - my $error = - $self->_make_lines( 'part_pkg' => $part_pkg, - 'cust_pkg' => $cust_pkg, - 'precommit_hooks' => \@precommit_hooks, - 'line_items' => $cust_bill_pkg{$pass}, - 'setup' => $total_setup{$pass}, - 'recur' => $total_recur{$pass}, - 'tax_matrix' => $taxlisthash{$pass}, - 'time' => $time, - 'real_pkgpart' => $real_pkgpart, - 'options' => \%options, - ); + my $next_bill = $cust_pkg->getfield('bill') || 0; + my $error; + while ( $next_bill <= $time ) { + $error = + $self->_make_lines( 'part_pkg' => $part_pkg, + 'cust_pkg' => $cust_pkg, + 'precommit_hooks' => \@precommit_hooks, + 'line_items' => $cust_bill_pkg{$pass}, + 'setup' => $total_setup{$pass}, + 'recur' => $total_recur{$pass}, + 'tax_matrix' => $taxlisthash{$pass}, + 'time' => $time, + 'real_pkgpart' => $real_pkgpart, + 'options' => \%options, + ); + # Stop if anything goes wrong, or if we're not incrementing + # the bill date. + last if $error; + last if ($cust_pkg->getfield('bill') || 0) == $next_bill; + $next_bill = $cust_pkg->getfield('bill') || 0; + } if ($error) { $dbh->rollback if $oldAutoCommit && !$options{no_commit}; return $error; @@ -580,27 +603,52 @@ sub bill { #discard bundled packages of 0 value sub _omit_zero_value_bundles { + my @in = @_; my @cust_bill_pkg = (); my @cust_bill_pkg_bundle = (); my $sum = 0; my $discount_show_always = 0; - foreach my $cust_bill_pkg ( @_ ) { + 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)); + 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; } + $sum += $cust_bill_pkg->setup + $cust_bill_pkg->recur; push @cust_bill_pkg_bundle, $cust_bill_pkg; + } + push @cust_bill_pkg, @cust_bill_pkg_bundle - if ($sum > 0 || ($sum == 0 && $discount_show_always)); + if $sum > 0 + || ($sum == 0 && ( $discount_show_always + || grep {$_->recur_show_zero || $_->setup_show_zero} + @cust_bill_pkg_bundle + ) + ); + + warn " _omit_zero_value_bundles: ". scalar(@in). + '->'. scalar(@cust_bill_pkg). "\n" #. Dumper(@cust_bill_pkg). "\n" + if $DEBUG > 2; (@cust_bill_pkg); @@ -828,12 +876,13 @@ sub _make_lines { my $setup = 0; my $unitsetup = 0; + my %setup_param = (); if ( ! $options{recurring_only} and ! $options{cancel} and ( $options{'resetup'} || ( ! $cust_pkg->setup && ( ! $cust_pkg->start_date - || $cust_pkg->start_date <= $time + || $cust_pkg->start_date <= $self->day_end($time) ) && ( ! $conf->exists('disable_setup_suspended_pkgs') || ( $conf->exists('disable_setup_suspended_pkgs') && @@ -846,13 +895,16 @@ sub _make_lines { { warn " bill setup\n" if $DEBUG > 1; - $lineitems++; - $setup = eval { $cust_pkg->calc_setup( $time, \@details ) }; - return "$@ running calc_setup for $cust_pkg\n" - if $@; + unless ( $cust_pkg->waive_setup ) { + $lineitems++; - $unitsetup = $cust_pkg->part_pkg->unit_setup || $setup; #XXX uuh + $setup = eval { $cust_pkg->calc_setup( $time, \@details, \%setup_param ) }; + return "$@ running calc_setup for $cust_pkg\n" + if $@; + + $unitsetup = $cust_pkg->part_pkg->unit_setup || $setup; #XXX uuh + } $cust_pkg->setfield('setup', $time) unless $cust_pkg->setup; @@ -875,7 +927,7 @@ sub _make_lines { if ( ! $cust_pkg->start_date and ( ! $cust_pkg->susp || $part_pkg->option('suspend_bill', 1) ) and - ( $part_pkg->freq ne '0' && ( $cust_pkg->bill || 0 ) <= $time ) + ( $part_pkg->freq ne '0' && ( $cust_pkg->bill || 0 ) <= $self->day_end($time) ) || ( $part_pkg->plan eq 'voip_cdr' && $part_pkg->option('bill_every_call') ) @@ -899,7 +951,7 @@ sub _make_lines { #over two params! lets at least switch to a hashref for the rest... my $increment_next_bill = ( $part_pkg->freq ne '0' - && ( $cust_pkg->getfield('bill') || 0 ) <= $time + && ( $cust_pkg->getfield('bill') || 0 ) <= $self->day_end($time) && !$options{cancel} ); my %param = ( 'precommit_hooks' => $precommit_hooks, @@ -908,6 +960,7 @@ sub _make_lines { 'real_pkgpart' => $real_pkgpart, 'freq_override' => $options{freq_override} || '', 'setup_fee' => 0, + %setup_param, ); my $method = $options{cancel} ? 'calc_cancel' : 'calc_recur'; @@ -916,8 +969,9 @@ sub _make_lines { # which can_discount are supported. # (the UI should prevent adding discounts to these at the moment) - warn "calling $method on cust_pkg ". $cust_pkg->pkgnum. " with params ". - join(' / ', map "$_=>$params{$_}", keys %param). "\n" + warn "calling $method on cust_pkg ". $cust_pkg->pkgnum. + " for pkgpart ". $cust_pkg->pkgpart. + " with params ". join(' / ', map "$_=>$param{$_}", keys %param). "\n" if $DEBUG > 2; $recur = eval { $cust_pkg->$method( \$sdate, \@details, \%param ) }; @@ -949,6 +1003,12 @@ sub _make_lines { $lineitems++; } + if ( defined $param{'discount_left_setup'} ) { + foreach my $discount_setup ( values %{$param{'discount_left_setup'}} ) { + $setup -= $discount_setup; + } + } + } warn "\$setup is undefined" unless defined($setup); @@ -989,10 +1049,13 @@ sub _make_lines { my $discount_show_always = ($recur == 0 && scalar(@discounts) && $conf->exists('discount-show-always')); - if ( $setup != 0 || - $recur != 0 || - (!$part_pkg->hidden && $options{has_hidden}) || #include some $0 lines - $discount_show_always ) + if ( $setup != 0 + || $recur != 0 + || (!$part_pkg->hidden && $options{has_hidden}) #include some $0 lines + || $discount_show_always + || ($setup == 0 && $cust_pkg->_X_show_zero('setup')) + || ($recur == 0 && $cust_pkg->_X_show_zero('recur')) + ) { warn " charges (setup=$setup, recur=$recur); adding line items\n" @@ -1018,11 +1081,11 @@ sub _make_lines { 'freq' => $part_pkg->freq, }; - if ( $part_pkg->option('recur_temporality', 1) eq 'preceding' ) { + if ( $part_pkg->recur_temporality eq 'preceding' ) { $cust_bill_pkg->sdate( $hash{last_bill} ); $cust_bill_pkg->edate( $sdate - 86399 ); #60s*60m*24h-1 $cust_bill_pkg->edate( $time ) if $options{cancel}; - } else { #if ( $part_pkg->option('recur_temporality', 1) eq 'upcoming' ) { + } else { #if ( $part_pkg->recur_temporality eq 'upcoming' ) { $cust_bill_pkg->sdate( $sdate ); $cust_bill_pkg->edate( $cust_pkg->bill ); #$cust_bill_pkg->edate( $time ) if $options{cancel}; @@ -1606,7 +1669,7 @@ sub do_cust_event { if $DEBUG > 1; #if ( my $error = $cust_event->do_event(%options) ) { #XXX %options? - if ( my $error = $cust_event->do_event() ) { + if ( my $error = $cust_event->do_event( 'time' => $time ) ) { #XXX wtf is this? figure out a proper dealio with return value #from do_event return $error; @@ -2090,11 +2153,14 @@ sub apply_payments { my $amount = min( $payment->unapplied, $owed ); - my $cust_bill_pay = new FS::cust_bill_pay ( { + my $cbp = { 'paynum' => $payment->paynum, 'invnum' => $cust_bill->invnum, 'amount' => $amount, - } ); + }; + $cbp->{_date} = $payment->_date + if $options{'manual'} && $options{'backdate_application'}; + my $cust_bill_pay = new FS::cust_bill_pay($cbp); $cust_bill_pay->pkgnum( $payment->pkgnum ) if $conf->exists('pkg-balances') && $payment->pkgnum; my $error = $cust_bill_pay->insert(%options);