import torrus 1.0.9
[freeside.git] / FS / FS / cust_main / Billing.pm
index 8021d38..9fa1e41 100644 (file)
@@ -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_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;
 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::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
 
 # 1 is mostly method/subroutine entry and options
 # 2 traces progress of some operations
@@ -435,8 +435,8 @@ sub bill {
 
     next unless @cust_bill_pkg; #don't create an invoice w/o line items
 
 
     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) ||
       if $DEBUG > 2;
 
     if ( scalar( grep { $_->recur && $_->recur > 0 } @cust_bill_pkg) ||
@@ -643,8 +643,8 @@ sub calculate_taxes {
 
   local($DEBUG) = $FS::cust_main::DEBUG if $FS::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 = ();
     if $DEBUG > 2;
 
   my @tax_line_items = ();
@@ -717,11 +717,9 @@ sub calculate_taxes {
 
   #move the cust_tax_exempt_pkg records to the cust_bill_pkgs we will commit
   my %packagemap = map { $_->pkgnum => $_ } @$cust_bill_pkg;
 
   #move the cust_tax_exempt_pkg records to the cust_bill_pkgs we will commit
   my %packagemap = map { $_->pkgnum => $_ } @$cust_bill_pkg;
-  warn Dumper(\%packagemap) if $DEBUG > 2;
   foreach my $tax ( keys %$taxlisthash ) {
     foreach ( @{ $taxlisthash->{$tax} }[1 ... scalar(@{ $taxlisthash->{$tax} })] ) {
       next unless ref($_) eq 'FS::cust_bill_pkg';
   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 } );
     }
       push @{ $packagemap{$_->pkgnum}->_cust_tax_exempt_pkg }, 
         splice( @{ $_->_cust_tax_exempt_pkg } );
     }
@@ -858,15 +856,14 @@ sub _make_lines {
   my $recur = 0;
   my $unitrecur = 0;
   my $sdate;
   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
   ) {
 
     # XXX should this be a package event?  probably.  events are called
@@ -900,6 +897,7 @@ sub _make_lines {
 
     # There may be some part_pkg for which this is wrong.  Only those
     # which can_discount are supported.
 
     # 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"
 
     $recur = eval { $cust_pkg->$method( \$sdate, \@details, \%param ) };
     return "$@ running $method for $cust_pkg\n"
@@ -933,7 +931,7 @@ sub _make_lines {
   # If $cust_pkg has been modified, update it (if we're a real pkgpart)
   ###
 
   # 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?
 
     if ( $cust_pkg->modified && $cust_pkg->pkgpart == $real_pkgpart ) {
       # hmm.. and if just the options are modified in some weird price plan?
@@ -1050,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) {
       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{''}) {
         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;
       }
         return $err_or_ref unless ref($err_or_ref);
         $taxes{''} = $err_or_ref;
       }
@@ -1228,11 +1222,18 @@ sub _gather_taxes {
   my $self = shift;
   my $part_pkg = shift;
   my $class = shift;
   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) = $FS::cust_main::DEBUG if $FS::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 @taxes = ();
-  my $geocode = $self->geocode('cch');
 
   my @taxclassnums = map { $_->taxclassnum }
                      $part_pkg->part_pkg_taxoverride($class);
 
   my @taxclassnums = map { $_->taxclassnum }
                      $part_pkg->part_pkg_taxoverride($class);
@@ -1626,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.
 
 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) = $FS::cust_main::DEBUG if $FS::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
 =back
 
 =cut
@@ -2245,6 +2078,28 @@ sub apply_payments {
   return $total_unapplied_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
 =head1 BUGS
 
 =head1 SEE ALSO