X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=blobdiff_plain;f=FS%2FFS%2Fcust_main%2FBilling.pm;h=9fa1e41685eec6b30a90d42d1d728f30d4006a97;hp=96ffe41f4ee935c895c6a4c2fee081aa2cb360fe;hb=74e058c8a010ef6feb539248a550d0bb169c1e94;hpb=5c9d03cf63378dbca8fc062ed25e781f9c7bb61b diff --git a/FS/FS/cust_main/Billing.pm b/FS/FS/cust_main/Billing.pm index 96ffe41f4..9fa1e4168 100644 --- a/FS/FS/cust_main/Billing.pm +++ b/FS/FS/cust_main/Billing.pm @@ -12,7 +12,6 @@ use FS::cust_bill_pkg; use FS::cust_bill_pkg_display; use FS::cust_bill_pay; use FS::cust_credit_bill; -use FS::cust_pkg; use FS::cust_tax_adjustment; use FS::tax_rate; use FS::tax_rate_location; @@ -20,6 +19,7 @@ use FS::cust_bill_pkg_tax_location; use FS::cust_bill_pkg_tax_rate_location; use FS::part_event; use FS::part_event_condition; +use FS::pkg_category; # 1 is mostly method/subroutine entry and options # 2 traces progress of some operations @@ -309,7 +309,7 @@ sub bill { return '' if $self->payby eq 'COMP'; - local($DEBUG) = $cust_main::DEBUG if $cust_main::DEBUG > $DEBUG; + local($DEBUG) = $FS::cust_main::DEBUG if $FS::cust_main::DEBUG > $DEBUG; warn "$me bill customer ". $self->custnum. "\n" if $DEBUG; @@ -435,8 +435,8 @@ sub bill { next unless @cust_bill_pkg; #don't create an invoice w/o line items - warn "$me billing pass $pass\n". - Dumper(\@cust_bill_pkg)."\n" + warn "$me billing pass $pass\n" + #.Dumper(\@cust_bill_pkg)."\n" if $DEBUG > 2; if ( scalar( grep { $_->recur && $_->recur > 0 } @cust_bill_pkg) || @@ -641,10 +641,10 @@ jurisdictions (i.e. Texas) have tax exemptions which are date sensitive. sub calculate_taxes { my ($self, $cust_bill_pkg, $taxlisthash, $invoice_time) = @_; - local($DEBUG) = $cust_main::DEBUG if $cust_main::DEBUG > $DEBUG; + local($DEBUG) = $FS::cust_main::DEBUG if $FS::cust_main::DEBUG > $DEBUG; - warn "$me calculate_taxes\n". - Dumper($self, $cust_bill_pkg, $taxlisthash, $invoice_time). "\n" + warn "$me calculate_taxes\n" + #.Dumper($self, $cust_bill_pkg, $taxlisthash, $invoice_time). "\n" if $DEBUG > 2; my @tax_line_items = (); @@ -720,7 +720,6 @@ sub calculate_taxes { foreach my $tax ( keys %$taxlisthash ) { foreach ( @{ $taxlisthash->{$tax} }[1 ... scalar(@{ $taxlisthash->{$tax} })] ) { next unless ref($_) eq 'FS::cust_bill_pkg'; - push @{ $packagemap{$_->pkgnum}->_cust_tax_exempt_pkg }, splice( @{ $_->_cust_tax_exempt_pkg } ); } @@ -786,7 +785,7 @@ sub calculate_taxes { sub _make_lines { my ($self, %params) = @_; - local($DEBUG) = $cust_main::DEBUG if $cust_main::DEBUG > $DEBUG; + local($DEBUG) = $FS::cust_main::DEBUG if $FS::cust_main::DEBUG > $DEBUG; my $part_pkg = $params{part_pkg} or die "no part_pkg specified"; my $cust_pkg = $params{cust_pkg} or die "no cust_pkg specified"; @@ -857,15 +856,14 @@ sub _make_lines { my $recur = 0; my $unitrecur = 0; my $sdate; - if ( ! $cust_pkg->get('susp') - and ! $cust_pkg->get('start_date') - and ( $part_pkg->getfield('freq') ne '0' - && ( $cust_pkg->getfield('bill') || 0 ) <= $time - ) - || ( $part_pkg->plan eq 'voip_cdr' - && $part_pkg->option('bill_every_call') - ) - || ( $options{cancel} ) + 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->plan eq 'voip_cdr' + && $part_pkg->option('bill_every_call') + ) + || $options{cancel} ) { # XXX should this be a package event? probably. events are called @@ -899,6 +897,7 @@ sub _make_lines { # There may be some part_pkg for which this is wrong. Only those # which can_discount are supported. + # (the UI should prevent adding discounts to these at the moment) $recur = eval { $cust_pkg->$method( \$sdate, \@details, \%param ) }; return "$@ running $method for $cust_pkg\n" @@ -932,7 +931,7 @@ sub _make_lines { # If $cust_pkg has been modified, update it (if we're a real pkgpart) ### - if ( $lineitems || $options{has_hidden} ) { + if ( $lineitems ) { if ( $cust_pkg->modified && $cust_pkg->pkgpart == $real_pkgpart ) { # hmm.. and if just the options are modified in some weird price plan? @@ -1029,7 +1028,7 @@ sub _handle_taxes { my $real_pkgpart = shift; my $options = shift; - local($DEBUG) = $cust_main::DEBUG if $cust_main::DEBUG > $DEBUG; + local($DEBUG) = $FS::cust_main::DEBUG if $FS::cust_main::DEBUG > $DEBUG; my %cust_bill_pkg = (); my %taxes = (); @@ -1049,18 +1048,14 @@ sub _handle_taxes { ) { - if ( $conf->exists('tax-pkg_address') && $cust_pkg->locationnum ) { - return "fatal: Can't (yet) use tax-pkg_address with taxproducts"; - } - foreach my $class (@classes) { - my $err_or_ref = $self->_gather_taxes( $part_pkg, $class ); + my $err_or_ref = $self->_gather_taxes( $part_pkg, $class, $cust_pkg ); return $err_or_ref unless ref($err_or_ref); $taxes{$class} = $err_or_ref; } unless (exists $taxes{''}) { - my $err_or_ref = $self->_gather_taxes( $part_pkg, '' ); + my $err_or_ref = $self->_gather_taxes( $part_pkg, '', $cust_pkg ); return $err_or_ref unless ref($err_or_ref); $taxes{''} = $err_or_ref; } @@ -1227,11 +1222,18 @@ sub _gather_taxes { my $self = shift; my $part_pkg = shift; my $class = shift; + my $cust_pkg = shift; + + local($DEBUG) = $FS::cust_main::DEBUG if $FS::cust_main::DEBUG > $DEBUG; - local($DEBUG) = $cust_main::DEBUG if $cust_main::DEBUG > $DEBUG; + my $geocode; + if ( $cust_pkg->locationnum && $conf->exists('tax-pkg_address') ) { + $geocode = $cust_pkg->cust_location->geocode('cch'); + } else { + $geocode = $self->geocode('cch'); + } my @taxes = (); - my $geocode = $self->geocode('cch'); my @taxclassnums = map { $_->taxclassnum } $part_pkg->part_pkg_taxoverride($class); @@ -1310,7 +1312,7 @@ Debugging level. Default is 0 (no debugging), or can be set to 1 (passed-in opt sub collect { my( $self, %options ) = @_; - local($DEBUG) = $cust_main::DEBUG if $cust_main::DEBUG > $DEBUG; + local($DEBUG) = $FS::cust_main::DEBUG if $FS::cust_main::DEBUG > $DEBUG; my $invoice_time = $options{'invoice_time'} || time; @@ -1496,7 +1498,7 @@ Debugging level. Default is 0 (no debugging), or can be set to 1 (passed-in opt sub do_cust_event { my( $self, %options ) = @_; - local($DEBUG) = $cust_main::DEBUG if $cust_main::DEBUG > $DEBUG; + local($DEBUG) = $FS::cust_main::DEBUG if $FS::cust_main::DEBUG > $DEBUG; my $time = $options{'time'} || time; @@ -1625,174 +1627,6 @@ Explicitly pass the objects to be tested (typically used with eventtable). Set to true to return the objects, but not actually insert them into the database. -=item discount_terms - -Returns a list of lengths for term discounts - -=cut - -sub _discount_pkgs_and_bill { -my $self = shift; - - my @cust_bill = $self->cust_bill; - my $cust_bill = pop @cust_bill; - return () unless $cust_bill && $cust_bill->owed; - - my @where = (); - push @where, "cust_bill_pkg.invnum = ". $cust_bill->invnum; - push @where, "cust_bill_pkg.pkgpart_override IS NULL"; - push @where, "part_pkg.freq = '1'"; - push @where, "(cust_pkg.cancel IS NULL OR cust_pkg.cancel = 0)"; - push @where, "(cust_pkg.susp IS NULL OR cust_pkg.susp = 0)"; - push @where, "0<(SELECT count(*) FROM part_pkg_discount - WHERE part_pkg.pkgpart = part_pkg_discount.pkgpart)"; - push @where, - "0=(SELECT count(*) FROM cust_bill_pkg_discount - WHERE cust_bill_pkg.billpkgnum = cust_bill_pkg_discount.billpkgnum)"; - - my $extra_sql = 'WHERE '. join(' AND ', @where); - - my @cust_pkg = - qsearch({ - 'table' => 'cust_pkg', - 'select' => "DISTINCT cust_pkg.*", - 'addl_from' => 'JOIN cust_bill_pkg USING(pkgnum) '. - 'JOIN part_pkg USING(pkgpart)', - 'hashref' => {}, - 'extra_sql' => $extra_sql, - }); - - ($cust_bill, @cust_pkg); -} - -sub _discountable_pkgs_at_term { - my ($term, @pkgs) = @_; - my $part_pkg = new FS::part_pkg { freq => $term - 1 }; - grep { ( !$_->adjourn || $_->adjourn > $part_pkg->add_freq($_->bill) ) && - ( !$_->expire || $_->expire > $part_pkg->add_freq($_->bill) ) - } - @pkgs; -} - -=item discount_terms - -Returns a list of lengths for term discounts - -=cut - -sub discount_terms { -my $self = shift; - - my %terms = (); - - my @discount_pkgs = $self->_discount_pkgs_and_bill; - shift @discount_pkgs; #discard bill; - - map { $terms{$_->months} = 1 } - grep { $_->months && $_->months > 1 } - map { $_->discount } - map { $_->part_pkg->part_pkg_discount } - @discount_pkgs; - - return sort { $a <=> $b } keys %terms; - -} - -=back - -=item discount_term_values MONTHS - -Returns a list with credit, dollar amount saved, and total bill acheived -by prepaying the most recent invoice for MONTHS. - -=cut - -sub discount_term_values { - my $self = shift; - my $term = shift; - - local($DEBUG) = $cust_main::DEBUG if $cust_main::DEBUG > $DEBUG; - - warn "$me discount_term_values called with $term\n" if $DEBUG; - - my %result = (); - - my @packages = $self->_discount_pkgs_and_bill; - my $cust_bill = shift(@packages); - @packages = _discountable_pkgs_at_term( $term, @packages ); - return () unless scalar(@packages); - - $_->bill($_->last_bill) foreach @packages; - my @final = map { new FS::cust_pkg { $_->hash } } @packages; - - my %options = ( - 'recurring_only' => 1, - 'no_usage_reset' => 1, - 'no_commit' => 1, - ); - - my %params = ( - 'return_bill' => [], - 'pkg_list' => \@packages, - 'time' => $cust_bill->_date, - ); - - my $error = $self->bill(%options, %params); - die $error if $error; # XXX think about this a bit more - - my $credit = 0; - $credit += $_->charged foreach @{$params{return_bill}}; - $credit = sprintf('%.2f', $credit); - warn "$me discount_term_values $term credit: $credit\n" if $DEBUG; - - %params = ( - 'return_bill' => [], - 'pkg_list' => \@packages, - 'time' => $packages[0]->part_pkg->add_freq($cust_bill->_date) - ); - - $error = $self->bill(%options, %params); - die $error if $error; # XXX think about this a bit more - - my $next = 0; - $next += $_->charged foreach @{$params{return_bill}}; - warn "$me discount_term_values $term next: $next\n" if $DEBUG; - - %params = ( - 'return_bill' => [], - 'pkg_list' => \@final, - 'time' => $cust_bill->_date, - 'freq_override' => $term, - ); - - $error = $self->bill(%options, %params); - die $error if $error; # XXX think about this a bit more - - my $final = $self->balance - $credit; - $final += $_->charged foreach @{$params{return_bill}}; - $final = sprintf('%.2f', $final); - warn "$me discount_term_values $term final: $final\n" if $DEBUG; - - my $savings = sprintf('%.2f', $self->balance + ($term - 1) * $next - $final); - - ( $credit, $savings, $final ); - -} - -sub discount_terms_hash { - my $self = shift; - - my %result = (); - my @terms = $self->discount_terms; - foreach my $term (@terms) { - my @result = $self->discount_term_values($term); - $result{$term} = [ @result ] if scalar(@result); - } - - return %result; - -} - =back =cut @@ -1805,7 +1639,7 @@ sub due_cust_event { #my $DEBUG = $opt{'debug'} local($DEBUG) = $opt{'debug'} if defined($opt{'debug'}) && $opt{'debug'} > $DEBUG; - $DEBUG = $cust_main::DEBUG if $cust_main::DEBUG > $DEBUG; + $DEBUG = $FS::cust_main::DEBUG if $FS::cust_main::DEBUG > $DEBUG; warn "$me due_cust_event called with options ". join(', ', map { "$_: $opt{$_}" } keys %opt). "\n" @@ -2244,6 +2078,28 @@ sub apply_payments { return $total_unapplied_payments; } +=back + +=head1 FLOW + + bill_and_collect + + cancel_expired_pkgs + suspend_adjourned_pkgs + + bill + (do_cust_event pre-bill) + _make_lines + _handle_taxes + (vendor-only) _gather_taxes + _omit_zero_value_bundles + calculate_taxes + + apply_payments_and_credits + collect + do_cust_event + due_cust_event + =head1 BUGS =head1 SEE ALSO