Merge branch 'master' of https://github.com/jgoodman/Freeside
[freeside.git] / FS / FS / cust_credit_bill_pkg.pm
index 019a1a8..1f741b2 100644 (file)
@@ -1,17 +1,12 @@
 package FS::cust_credit_bill_pkg;
+use base qw( FS::cust_main_Mixin FS::Record );
 
 use strict;
-use vars qw( @ISA );
 use FS::Record qw( qsearch qsearchs dbh );
-use FS::cust_main_Mixin;
-use FS::cust_credit_bill;
-use FS::cust_bill_pkg;
 use FS::cust_bill_pkg_tax_location;
 use FS::cust_bill_pkg_tax_rate_location;
 use FS::cust_tax_exempt_pkg;
 
-@ISA = qw( FS::cust_main_Mixin FS::Record );
-
 =head1 NAME
 
 FS::cust_credit_bill_pkg - Object methods for cust_credit_bill_pkg records
@@ -103,15 +98,22 @@ sub insert {
     return $error;
   }
 
-  my $payable = $self->cust_bill_pkg->payable($self->setuprecur);
-  my $taxable = $self->_is_taxable ? $payable : 0;
-  my $part_pkg = $self->cust_bill_pkg->part_pkg;
-  my $freq = $part_pkg ? $part_pkg->freq || 1 : 1;# assume unchanged
-  my $taxable_per_month = sprintf("%.2f", $taxable / $freq );
+  my $cust_bill_pkg = $self->cust_bill_pkg;
+  #'payable' is the amount charged (either setup or recur)
+  # minus any credit applications, including this one
+  my $payable = $cust_bill_pkg->payable($self->setuprecur);
+  my $part_pkg = $cust_bill_pkg->part_pkg;
+  my $freq = $cust_bill_pkg->freq;
+  unless ($freq) {
+    $freq = $part_pkg ? ($part_pkg->freq || 1) : 1;#fallback.. assumes unchanged
+  }
+  my $taxable_per_month = sprintf("%.2f", $payable / $freq );
   my $credit_per_month = sprintf("%.2f", $self->amount / $freq ); #pennies?
 
   if ($taxable_per_month >= 0) {  #panic if its subzero?
-    my $groupby = 'taxnum,year,month';
+    my $groupby = join(',',
+      qw(taxnum year month exempt_monthly exempt_cust 
+         exempt_cust_taxname exempt_setup exempt_recur));
     my $sum = 'SUM(amount)';
     my @exemptions = qsearch(
       {
@@ -121,25 +123,60 @@ sub insert {
         'extra_sql' => "GROUP BY $groupby HAVING $sum > 0",
       }
     ); 
+    # each $exemption is now the sum of all monthly exemptions applied to 
+    # this line item for a particular taxnum and month.
     foreach my $exemption ( @exemptions ) {
-      next if $taxable_per_month >= $exemption->amount;
-      my $amount = $exemption->amount - $taxable_per_month;
-      if ($amount > $credit_per_month) {
-             "cust_bill_pkg ". $self->billpkgnum. "  Reducing.\n";
-        $amount = $credit_per_month;
+      my $amount = 0;
+      if ( $exemption->exempt_monthly ) {
+        # finite exemptions
+        # $taxable_per_month is AFTER inserting the credit application, so 
+        # if it's still larger than the exemption, we don't need to adjust
+        next if $taxable_per_month >= $exemption->amount;
+        # the amount of 'excess' exemption already in place (above the 
+        # remaining charged amount).  We'll de-exempt that much, or the 
+        # amount of the new credit, whichever is smaller.
+        $amount = $exemption->amount - $taxable_per_month;
+        # $amount is the amount of 'excess' exemption already existing 
+        # (above the remaining taxable charge amount).  We'll "de-exempt"
+        # that much, or the amount of the new credit, whichever is smaller.
+        if ($amount > $credit_per_month) {
+               "cust_bill_pkg ". $self->billpkgnum. "  Reducing.\n";
+          $amount = $credit_per_month;
+        }
+      } elsif ( $exemption->exempt_setup or $exemption->exempt_recur ) {
+        # package defined exemptions: may be setup only, recur only, or both
+        my $method = 'exempt_'.$self->setuprecur;
+        if ( $exemption->$method ) {
+          # then it's exempt from the portion of the charge that this 
+          # credit is being applied to
+          $amount = $self->amount;
+        }
+      } else {
+        # other types of exemptions: always equal to the amount of
+        # the charge
+        $amount = $self->amount;
       }
+      next if $amount == 0;
+
+      # create a negative exemption
       my $cust_tax_exempt_pkg = new FS::cust_tax_exempt_pkg {
+         $exemption->hash, # for exempt_ flags, taxnum, month/year
         'billpkgnum'       => $self->billpkgnum,
         'creditbillpkgnum' => $self->creditbillpkgnum,
         'amount'           => sprintf('%.2f', 0-$amount),
-        map { $_ => $exemption->$_ } split(',', $groupby)
       };
-      my $error = $cust_tax_exempt_pkg->insert;
-      if ( $error ) {
-        $dbh->rollback if $oldAutoCommit;
-        return "error inserting cust_tax_exempt_pkg: $error";
+
+      if ( $cust_tax_exempt_pkg->cust_main_county ) {
+
+        my $error = $cust_tax_exempt_pkg->insert;
+        if ( $error ) {
+          $dbh->rollback if $oldAutoCommit;
+          return "error inserting cust_tax_exempt_pkg: $error";
+        }
+
       }
-    }
+
+    } #foreach $exemption
   }
 
   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
@@ -186,65 +223,21 @@ sub delete {
   local $FS::UID::AutoCommit = 0;
   my $dbh = dbh;
 
-  my $original_cust_bill_pkg = $self->cust_bill_pkg;
-  my $cust_bill = $original_cust_bill_pkg->cust_bill;
-
-  my %hash = $original_cust_bill_pkg->hash;
-  delete $hash{$_} for qw( billpkgnum setup recur );
-  $hash{$self->setuprecur} = $self->amount;
-  my $cust_bill_pkg = new FS::cust_bill_pkg { %hash };
-
-  use Data::Dumper;
-  my @exemptions = qsearch( 'cust_tax_exempt_pkg', 
-                            { creditbillpkgnum => $self->creditbillpkgnum }
-                          );
-  my %seen = ();
-  my @generated_exemptions = ();
-  my @unseen_exemptions = ();
-  foreach my $exemption ( @exemptions ) {
-    my $error = $exemption->delete;
+  my @negative_exemptions = qsearch('cust_tax_exempt_pkg', {
+      'creditbillpkgnum' => $self->creditbillpkgnum
+  });
+
+  # de-anti-exempt those negative exemptions
+  my $error;
+  foreach (@negative_exemptions) {
+    $error = $_->delete;
     if ( $error ) {
       $dbh->rollback if $oldAutoCommit;
-      return "error deleting cust_tax_exempt_pkg: $error";
+      return $error;
     }
-
-    next if $seen{$exemption->taxnum};
-    $seen{$exemption->taxnum} = 1;
-    push @unseen_exemptions, $exemption;
   }
 
-  foreach my $exemption ( @unseen_exemptions ) {
-    my $tax_object = $exemption->cust_main_county;
-    unless ($tax_object) {
-      $dbh->rollback if $oldAutoCommit;
-      return "can't find exempted tax";
-    }
-    
-    my $hashref_or_error =
-      $tax_object->taxline( [ $cust_bill_pkg ], 
-                            'custnum'      => $cust_bill->custnum,
-                            'invoice_time' => $cust_bill->_date,
-                          );
-    unless (ref($hashref_or_error)) {
-      $dbh->rollback if $oldAutoCommit;
-      return "error calculating taxes: $hashref_or_error";
-    }
-
-    push @generated_exemptions, @{ $cust_bill_pkg->_cust_tax_exempt_pkg || [] };
-  }
-                          
-  foreach my $taxnum ( keys %seen ) {
-    my $sum = 0;
-    $sum += $_->amount for grep {$_->taxnum == $taxnum} @exemptions;
-    $sum -= $_->amount for grep {$_->taxnum == $taxnum} @generated_exemptions;
-    $sum = sprintf("%.2f", $sum);
-    unless ($sum eq '0.00' || $sum eq '-0.00') {
-      $dbh->rollback if $oldAutoCommit;
-      return "Can't unapply credit without charging tax";
-    }
-  }
-   
-  my $error = $self->SUPER::delete(@_);
+  $error = $self->SUPER::delete(@_);
   if ( $error ) {
     $dbh->rollback if $oldAutoCommit;
     return $error;
@@ -299,25 +292,15 @@ sub check {
   $self->SUPER::check;
 }
 
-sub cust_credit_bill {
-  my $self = shift;
-  qsearchs('cust_credit_bill', { 'creditbillnum' => $self->creditbillnum } );
-}
-
-sub cust_bill_pkg {
-  my $self = shift;
-  qsearchs('cust_bill_pkg', { 'billpkgnum' => $self->billpkgnum } );
-}
-
 sub cust_bill_pkg_tax_Xlocation {
   my $self = shift;
-  if ($self->billpkg_tax_locationnum) {
+  if ($self->billpkgtaxlocationnum) {
     return qsearchs(
       'cust_bill_pkg_tax_location',
       { 'billpkgtaxlocationnum' => $self->billpkgtaxlocationnum },
     );
  
-  } elsif ($self->billpkg_tax_rate_locationnum) {
+  } elsif ($self->billpkgtaxratelocationnum) {
     return qsearchs(
       'cust_bill_pkg_tax_rate_location',
       { 'billpkgtaxratelocationnum' => $self->billpkgtaxratelocationnum },
@@ -334,13 +317,13 @@ sub cust_bill_pkg_tax_Xlocation {
 B<setuprecur> field is a kludge to compensate for cust_bill_pkg having separate
 setup and recur fields.  It should be removed once that's fixed.
 
-B<insert> method assumes that the frequency of the package associated with the
-associated line item remains unchanged during the lifetime of the system.
-It may get the tax exemption adjustments wrong if package definitions change
-frequency.  The presense of delete methods in FS::cust_main_county and
-FS::tax_rate makes crediting of old "texas tax" unreliable in the presense of
-changing taxes.  Explicit tax credit requests?  Carry 'taxable' onto line
-items?
+B<insert> method used to assume that the frequency of the package associated
+with the associated line item remained unchanged during the lifetime of the
+system.  That is still used as a fallback.  It may get the tax exemption
+adjustments wrong if package definitions change frequency.  The presense of
+delete methods in FS::cust_main_county and FS::tax_rate makes crediting of
+old "texas tax" unreliable in the presense of changing taxes.  Explicit tax
+credit requests?  Carry 'taxable' onto line items?
 
 =head1 SEE ALSO