eliminate some false laziness in FS::Misc::send_email vs. msg_template/email.pm send_...
[freeside.git] / FS / FS / tax_class.pm
index 480fa10..904b575 100644 (file)
@@ -5,6 +5,8 @@ use vars qw( @ISA );
 use FS::UID qw(dbh);
 use FS::Record qw( qsearch qsearchs );
 use FS::Misc qw( csv_from_fixed );
+use FS::part_pkg_taxrate;
+use FS::part_pkg_taxoverride;
 
 @ISA = qw(FS::Record);
 
@@ -29,26 +31,24 @@ FS::tax_class - Object methods for tax_class records
 
 =head1 DESCRIPTION
 
-An FS::tax_class object represents a tax class.  FS::tax_class
-inherits from FS::Record.  The following fields are currently supported:
+An FS::tax_class object represents a class of tax definitions.  FS::tax_class
+inherits from FS::Record.
 
-=over 4
-
-=item taxclassnum
-
-Primary key
+This should not be confused with L<FS::part_pkg_taxclass>, which defines tax
+classes for I<package> definitions.  The two kinds of tax classes are 
+completely unrelated.
 
-=item data_vendor
+The following fields are currently supported:
 
-Vendor of the tax data
+=over 4
 
-=item taxclass
+=item taxclassnum - Primary key
 
-Tax class
+=item data_vendor - Vendor of the tax data ('cch' or 'billsoft')
 
-=item description
+=item taxclass - The identifier used in the tax tables for this class.
 
-Human readable description of the tax class
+=item description -  Human readable description of the tax class.
 
 =back
 
@@ -83,20 +83,39 @@ Delete this record from the database.
 sub delete {
   my $self = shift;
 
-  return "Can't delete a tax class which has tax rates!"
-    if qsearch( 'tax_rate', { 'taxclassnum' => $self->taxclassnum } );
-
-  return "Can't delete a tax class which has package tax rates!"
-    if qsearch( 'part_pkg_taxrate', { 'taxclassnum' => $self->taxclassnum } );
-
-  return "Can't delete a tax class which has package tax rates!"
-    if qsearch( 'part_pkg_taxrate', { 'taxclassnumtaxed' => $self->taxclassnum } );
+  #return "Can't delete a tax class which has package tax rates!"
+  #if qsearch( 'part_pkg_taxrate', { 'taxclassnumtaxed' => $self->taxclassnum    
+  # If this tax class is manually assigned to a package,
+  # then return a useful error message instead of just having a conniption.
+  my @overrides = qsearch( 'part_pkg_taxoverride', {
+                    'taxclassnum' => $self->taxclassnum
+                  } );
+  if (@overrides) {
+    return "Tried to delete tax class " . $self->taxclass .
+      ", which is assigned to package definition " .
+      join(', ', map { '#'.$_->pkgpart} @overrides) .
+      ".";
+  }
 
-  return "Can't delete a tax class which has package tax overrides!"
-    if qsearch( 'part_pkg_taxoverride', { 'taxclassnum' => $self->taxclassnum } );
+  # part_pkg_taxrate.taxclass identifies taxes belonging to this taxclass.
+  # part_pkg_taxrate.taxclassnumtaxed identifies taxes applying to this 
+  # taxclass.
+  # If this taxclass goes away, remove all of them. (CCH upgrade CAN'T 
+  # remove them, because it removes the tax_class first and then doesn't 
+  # know what the taxclassnum was. Yeah, I know. So it will just skip 
+  # over them at the TXMATRIX stage.)
+  my @part_pkg_taxrate = (
+    qsearch('part_pkg_taxrate', { 'taxclassnum' => $self->taxclassnum }),
+    qsearch('part_pkg_taxrate', { 'taxclassnumtaxed' => $self->taxclassnum })
+  );
+  foreach (@part_pkg_taxrate) {
+    my $error = $_->delete;
+    return "when deleting taxclass ".$self->taxclass.": $error"
+      if $error;
+  }
 
   $self->SUPER::delete(@_);
-  
+
 }
 
 =item replace OLD_RECORD
@@ -246,21 +265,30 @@ sub batch_import {
           if ( $job ) {  # progress bar
             if ( time - $min_sec > $last ) {
               my $error = $job->update_statustext(
-                int( 100 * $imported / $count )
+                int( 100 * $imported / $count ). ",Importing tax classes"
               );
               die $error if $error;
               $last = time;
             }
           }
 
-          my $tax_class =
-            new FS::tax_class( { 'data_vendor' => 'cch',
-                                 'taxclass'    => $type->[0].':'.$cat->[0],
-                                 'description' => $type->[1].':'.$cat->[1],
-                             } );
-          my $error = $tax_class->insert;
-          return $error if $error;
+          my %hash = ( 'data_vendor' => 'cch',
+                       'taxclass'    => $type->[0].':'.$cat->[0],
+                       'description' => $type->[1].':'.$cat->[1],
+                     );
+          unless ( qsearchs('tax_class', \%hash) ) {
+            my $tax_class = new FS::tax_class \%hash;
+            my $error = $tax_class->insert;
+
+            return "can't insert tax_class for ".
+                   " old TAXTYPE ". $type->[0].':'.$type->[1].
+                   " and new TAXCAT ". $cat->[0].':'. $cat->[1].
+                   " : $error"
+              if $error;
+          }
+
           $imported++;
+          
         }
       }
 
@@ -270,7 +298,7 @@ sub batch_import {
           if ( $job ) {  # progress bar
             if ( time - $min_sec > $last ) {
               my $error = $job->update_statustext(
-                int( 100 * $imported / $count )
+                int( 100 * $imported / $count ). ",Importing tax classes"
               );
               die $error if $error;
               $last = time;
@@ -283,7 +311,7 @@ sub batch_import {
                                  'description' => $type->[1].':'.$cat->[1],
                              } );
           my $error = $tax_class->insert;
-          return $error if $error;
+          return "can't insert tax_class for new TAXTYPE $type and TAXCAT $cat: $error" if $error;
           $imported++;
         }
       }
@@ -291,6 +319,13 @@ sub batch_import {
       '';
     };
 
+  } elsif ( $format eq 'billsoft' ) {
+    # Billsoft doesn't actually have a format for this; it's just my own
+    # invention to have a way to load the list of tax classes from the 
+    # documentation.
+    @fields = qw( taxclass description );
+    $endhook = $hook = sub {};
+
   } elsif ( $format eq 'extended' ) {
     die "unimplemented\n";
     @fields = qw( );
@@ -319,7 +354,7 @@ sub batch_import {
     if ( $job ) {  # progress bar
       if ( time - $min_sec > $last ) {
         my $error = $job->update_statustext(
-          int( 100 * $imported / $count )
+          int( 100 * $imported / $count ). ",Importing tax classes"
         );
         die $error if $error;
         $last = time;
@@ -339,7 +374,7 @@ sub batch_import {
     }
     if ( scalar( @columns ) ) {
       $dbh->rollback if $oldAutoCommit;
-      return "Unexpected trailing columns in line (wrong format?): $line";
+      return "Unexpected trailing columns in line (wrong format?) importing tax_class: $line";
     }
 
     my $error = &{$hook}(\%tax_class);
@@ -363,7 +398,7 @@ sub batch_import {
   my $error = &{$endhook}();
   if ( $error ) {
     $dbh->rollback if $oldAutoCommit;
-    return "can't insert tax_class for $line: $error";
+    return "can't run end hook: $error";
   }
 
   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
@@ -378,9 +413,6 @@ sub batch_import {
 
 =head1 BUGS
 
-  batch_import does not handle mixed I and D records in the same file for
-  format cch-update
-
 =head1 SEE ALSO
 
 L<FS::Record>, schema.html from the base documentation.