eliminate some false laziness in FS::Misc::send_email vs. msg_template/email.pm send_...
[freeside.git] / FS / FS / TaxEngine / avalara.pm
index 183555d..cb841c3 100644 (file)
@@ -8,11 +8,11 @@ use FS::cust_pkg;
 use FS::cust_location;
 use FS::cust_bill_pkg;
 use FS::tax_rate;
-use JSON;
+use Cpanel::JSON::XS;
 use Geo::StreetAddress::US;
 
-our $DEBUG = 2;
-our $json = JSON->new->pretty(1);
+our $DEBUG = 0;
+our $json = Cpanel::JSON::XS->new->pretty(1);
 
 our $conf;
 
@@ -29,20 +29,12 @@ FS::UID->install_callback( sub {
 #}
 # Avalara address standardization would be nice but isn't necessary
 
-# XXX this is just here to avoid reworking the framework right now. By the
-# 4.0 release, ALL tax calculations should be done after the invoice has 
-# been inserted into the database.
-
 # nothing to do here
 sub add_sale {}
 
 sub build_request {
   my ($self, %opt) = @_;
 
-  my $oldAutoCommit = $FS::UID::AutoCommit;
-  local $FS::UID::AutoCommit = 0;
-  my $dbh = dbh;
-
   my $cust_bill = $self->{cust_bill};
   my $cust_main = $cust_bill->cust_main;
 
@@ -85,6 +77,8 @@ sub build_request {
     };
     push @lines, $line;
   }
+  # don't make the request unless there are some eligible line items
+  return '' if !@lines;
 
   # assemble address records for any cust_locations we used here, plus
   # the company address
@@ -92,7 +86,13 @@ sub build_request {
   my $our_address = join(' ', 
     $conf->config('company_address', $cust_main->agentnum)
   );
-  my $company_address = Geo::StreetAddress::US->parse_address($our_address);
+  my $company_address = Geo::StreetAddress::US->parse_location($our_address);
+  if (!$company_address->{street}
+      or !$company_address->{city}
+      or !$company_address->{zip}) {
+    die "Your company address could not be parsed. Avalara tax calculation requires a company address with street, city, and zip code.\n";
+  }
+
   my $address1 = join(' ', grep $_, @{$company_address}{qw(
       number prefix street type suffix
   )});
@@ -141,6 +141,7 @@ sub build_request {
 
   # create the top level object
   my $date = DateTime->from_epoch(epoch => $self->{invoice_time});
+  my $doctype = $self->{estimate} ? 'SalesOrder' : 'SalesInvoice';
   return {
     'CustomerCode'      => $cust_main->custnum,
     'DocDate'           => $date->strftime('%Y-%m-%d'),
@@ -149,7 +150,7 @@ sub build_request {
     'DocCode'           => $cust_bill->invnum,
     'DetailLevel'       => 'Tax',
     'Commit'            => 'false',
-    'DocType'           => 'SalesInvoice', # ???
+    'DocType'           => $doctype,
     'CustomerUsageType' => $cust_main->taxstatus,
     # ExemptionNo, Discount, TaxOverride, PurchaseOrderNo,
     'Addresses'         => \@addrs,
@@ -163,8 +164,8 @@ sub calculate_taxes {
 
   my $cust_bill = shift;
   if (!$cust_bill->invnum) {
-    warn "FS::TaxEngine::avalara: can't calculate taxes on a non-inserted invoice";
-    return;
+    # then something is wrong
+    die "FS::TaxEngine::avalara: can't calculate taxes on a non-inserted invoice\n";
   }
   $self->{cust_bill} = $cust_bill;
 
@@ -196,6 +197,10 @@ account number, and license key.
 
   # assemble the request hash
   my $request = $self->build_request;
+  if (!$request) {
+    warn "no tax-eligible items on this invoice\n" if $DEBUG;
+    return [];
+  }
 
   warn "sending Avalara tax request\n" if $DEBUG;
   my $request_json = $json->encode($request);
@@ -209,8 +214,9 @@ account number, and license key.
   my %tax_item_named;
 
   if ( $response->{ResultCode} ne 'Success' ) {
-    return "invoice#".$cust_bill->invnum.": ".
-           join("\n", @{ $response->{Messages} });
+    die "Avalara tax error on invoice#".$cust_bill->invnum.": ".
+           join("\n", @{ $response->{Messages} }).
+           "\n";
   }
   warn "creating taxes for inv#$invnum\n" if $DEBUG > 1;
   foreach my $TaxLine (@{ $response->{TaxLines} }) {
@@ -245,8 +251,9 @@ account number, and license key.
           fee         => 0,
       });
       my $error = $tax_rate->find_or_insert;
-      return "error inserting tax_rate record for '$taxname': $error\n"
+      die "error inserting tax_rate record for '$taxname': $error\n"
         if $error;
+      $tax_rate = $tax_rate->replace_old; # get its taxnum if there wasn't one
 
       # create a tax_rate_location record
       my $tax_rate_location = FS::tax_rate_location->new({
@@ -260,13 +267,13 @@ account number, and license key.
                         # country?
       });
       $error = $tax_rate_location->find_or_insert;
-      return "error inserting tax_rate_location record for ".
+      die "error inserting tax_rate_location record for ".
               $TaxDetail->{JurisCode} .": $error\n"
         if $error;
 
       # create a link record
       my $tax_link = FS::cust_bill_pkg_tax_rate_location->new({
-          cust_bill_pkg       => $tax_item,
+          tax_cust_bill_pkg   => $tax_item,
           taxtype             => 'FS::tax_rate',
           taxnum              => $tax_rate->taxnum,
           taxratelocationnum  => $tax_rate_location->taxratelocationnum,