tax engine refactoring for Avalara and Billsoft tax vendors, #25718
[freeside.git] / FS / FS / cust_main / Import.pm
index f477323..a243b27 100644 (file)
@@ -2,17 +2,16 @@ package FS::cust_main::Import;
 
 use strict;
 use vars qw( $DEBUG $conf );
-use Storable qw(thaw);
 use Data::Dumper;
-use MIME::Base64;
-use Date::Parse;
 use File::Slurp qw( slurp );
+use FS::Misc::DateTime qw( parse_datetime );
 use FS::UID qw( dbh );
 use FS::Record qw( qsearchs );
 use FS::cust_main;
 use FS::svc_acct;
 use FS::svc_external;
 use FS::svc_phone;
+use FS::svc_hardware;
 use FS::part_referral;
 
 $DEBUG = 0;
@@ -21,6 +20,8 @@ install_callback FS::UID sub {
   $conf = new FS::Conf;
 };
 
+my %is_location = map { $_ => 1 } FS::cust_main::Location->location_fields;
+
 =head1 NAME
 
 FS::cust_main::Import - Batch customer importing
@@ -34,7 +35,8 @@ FS::cust_main::Import - Batch customer importing
     file      => $file,      #filename
     type      => $type,      #csv or xls
     format    => $format,    #extended, extended-plus_company, svc_external,
-                             # or svc_external_svc_phone
+                             #extended-plus_company_and_options
+                             #extended-plus_options, or svc_external_svc_phone
     agentnum  => $agentnum,
     refnum    => $refnum,
     pkgpart   => $pkgpart,
@@ -63,8 +65,7 @@ Load a batch import as a queued JSRPC job
 
 sub process_batch_import {
   my $job = shift;
-
-  my $param = thaw(decode_base64(shift));
+  my $param = shift;
   warn Dumper($param) if $DEBUG;
   
   my $files = $param->{'uploaded_files'}
@@ -144,6 +145,19 @@ sub batch_import {
                   svc_acct.username svc_acct._password 
                 );
     $payby = 'BILL';
+ } elsif ( $format eq 'extended-plus_options' ) {
+    @fields = qw( agent_custid refnum
+                  last first address1 address2 city state zip country
+                  daytime night
+                  ship_last ship_first ship_address1 ship_address2
+                  ship_city ship_state ship_zip ship_country
+                  payinfo paycvv paydate
+                  invoicing_list
+                  cust_pkg.pkgpart
+                  svc_acct.username svc_acct._password 
+                  customer_options
+                );
+    $payby = 'BILL';
  } elsif ( $format eq 'extended-plus_company' ) {
     @fields = qw( agent_custid refnum
                   last first company address1 address2 city state zip country
@@ -156,6 +170,19 @@ sub batch_import {
                   svc_acct.username svc_acct._password 
                 );
     $payby = 'BILL';
+ } elsif ( $format eq 'extended-plus_company_and_options' ) {
+    @fields = qw( agent_custid refnum
+                  last first company address1 address2 city state zip country
+                  daytime night
+                  ship_last ship_first ship_company ship_address1 ship_address2
+                  ship_city ship_state ship_zip ship_country
+                  payinfo paycvv paydate
+                  invoicing_list
+                  cust_pkg.pkgpart
+                  svc_acct.username svc_acct._password 
+                  customer_options
+                );
+    $payby = 'BILL';
  } elsif ( $format =~ /^svc_external/ ) {
     @fields = qw( agent_custid refnum
                   last first company address1 address2 city state zip country
@@ -170,6 +197,37 @@ sub batch_import {
     push @fields, map "svc_phone.$_", qw( countrycode phonenum sip_password pin)
       if $format eq 'svc_external_svc_phone';
     $payby = 'BILL';
+  } elsif ( $format eq 'birthdates-acct_phone_hardware') {
+    @fields = qw( agent_custid refnum
+                  last first company address1 address2 city state zip country
+                  daytime night
+                  ship_last ship_first ship_company ship_address1 ship_address2
+                  ship_city ship_state ship_zip ship_country
+                  birthdate spouse_birthdate
+                  payinfo paycvv paydate
+                  invoicing_list
+                  cust_pkg.pkgpart cust_pkg.bill
+                  svc_acct.username svc_acct._password 
+                );
+    push @fields, map "svc_phone.$_", qw(countrycode phonenum sip_password pin);
+    push @fields, map "svc_hardware.$_", qw(typenum ip_addr hw_addr serial);
+
+    $payby = 'BILL';
+  } elsif ( $format eq 'national_id-acct_phone') {
+    @fields = qw( agent_custid refnum
+                  last first company address1 address2 city state zip country
+                  daytime night
+                  ship_last ship_first ship_company ship_address1 ship_address2
+                  ship_city ship_state ship_zip ship_country
+                  national_id
+                  payinfo paycvv paydate
+                  invoicing_list
+                  cust_pkg.pkgpart cust_pkg.bill
+                  svc_acct.username svc_acct._password svc_acct.slipip
+                );
+    push @fields, map "svc_phone.$_", qw(countrycode phonenum sip_password pin);
+
+    $payby = 'BILL';
   } else {
     die "unknown format $format";
   }
@@ -214,6 +272,10 @@ sub batch_import {
   my $oldAutoCommit = $FS::UID::AutoCommit;
   local $FS::UID::AutoCommit = 0;
   my $dbh = dbh;
+
+  #implies ignore_expired_card
+  local($FS::cust_main::import) = 1;
+  local($FS::cust_main::import) = 1;
   
   my $line;
   my $row = 0;
@@ -253,27 +315,28 @@ sub batch_import {
       custbatch => $custbatch,
       agentnum  => $agentnum,
       refnum    => $refnum,
-      country   => $conf->config('countrydefault') || 'US',
       payby     => $payby, #default
       paydate   => '12/2037', #default
     );
     my $billtime = time;
     my %cust_pkg = ( pkgpart => $pkgpart );
     my %svc_x = ();
+    my %bill_location = ();
+    my %ship_location = ();
     foreach my $field ( @fields ) {
 
       if ( $field =~ /^cust_pkg\.(pkgpart|setup|bill|susp|adjourn|expire|cancel)$/ ) {
 
-        #$cust_pkg{$1} = str2time( shift @$columns );
+        #$cust_pkg{$1} = parse_datetime( shift @$columns );
         if ( $1 eq 'pkgpart' ) {
           $cust_pkg{$1} = shift @columns;
         } elsif ( $1 eq 'setup' ) {
-          $billtime = str2time(shift @columns);
+          $billtime = parse_datetime(shift @columns);
         } else {
-          $cust_pkg{$1} = str2time( shift @columns );
+          $cust_pkg{$1} = parse_datetime( shift @columns );
         } 
 
-      } elsif ( $field =~ /^svc_acct\.(username|_password)$/ ) {
+      } elsif ( $field =~ /^svc_acct\.(username|_password|slipip)$/ ) {
 
         $svc_x{$1} = shift @columns;
 
@@ -283,7 +346,19 @@ sub batch_import {
 
       } elsif ( $field =~ /^svc_phone\.(countrycode|phonenum|sip_password|pin)$/ ) {
         $svc_x{$1} = shift @columns;
-       
+      
+      } elsif ( $field =~ /^svc_hardware\.(typenum|ip_addr|hw_addr|serial)$/ ) {
+
+        $svc_x{$1} = shift @columns;
+
+      } elsif ( $is_location{$field} ) {
+
+        $bill_location{$field} = shift @columns;
+
+      } elsif ( $field =~ /^ship_(.*)$/ and $is_location{$1} ) {
+
+        $ship_location{$1} = shift @columns;
+      
       } else {
 
         #refnum interception
@@ -312,22 +387,50 @@ sub batch_import {
         my $value = shift @columns;
         $cust_main{$field} = $value if length($value);
       }
+    } # foreach my $field
+    # finished importing columns
+
+    $bill_location{'country'} ||= $conf->config('countrydefault') || 'US';
+    $cust_main{'bill_location'} = FS::cust_location->new(\%bill_location);
+    if ( grep $_, values(%ship_location) ) {
+      $ship_location{'country'} ||= $conf->config('countrydefault') || 'US';
+      $cust_main{'ship_location'} = FS::cust_location->new(\%ship_location);
+    } else {
+      $cust_main{'ship_location'} = $cust_main{'bill_location'};
     }
 
-    $cust_main{'payby'} = 'CARD'
-      if defined $cust_main{'payinfo'}
-      && length  $cust_main{'payinfo'};
+    if ( defined $cust_main{'payinfo'} && length $cust_main{'payinfo'} ) {
+      $cust_main{'payby'} = 'CARD';
+      if ($cust_main{'payinfo'} =~ /\s*([AD]?)(.*)\s*$/) {
+        $cust_main{'payby'} = 'DCRD' if $1 eq 'D';
+        $cust_main{'payinfo'} = $2;
+      }
+    }
+
+    $cust_main{$_} = parse_datetime($cust_main{$_})
+      foreach grep $cust_main{$_},
+        qw( birthdate spouse_birthdate anniversary_date );
 
     my $invoicing_list = $cust_main{'invoicing_list'}
                            ? [ delete $cust_main{'invoicing_list'} ]
                            : [];
 
+    my $customer_options = delete $cust_main{customer_options};
+    $cust_main{tax} = 'Y' if $customer_options =~ /taxexempt/i;
+    push @$invoicing_list, 'POST' if $customer_options =~ /postalinvoice/i;
+
     my $cust_main = new FS::cust_main ( \%cust_main );
 
     use Tie::RefHash;
     tie my %hash, 'Tie::RefHash'; #this part is important
 
     if ( $cust_pkg{'pkgpart'} ) {
+
+      unless ( $cust_pkg{'pkgpart'} =~ /^\d+$/ ) {
+        $dbh->rollback if $oldAutoCommit;
+        return 'illegal pkgpart: '. $cust_pkg{'pkgpart'};
+      }
+
       my $cust_pkg = new FS::cust_pkg ( \%cust_pkg );
 
       my @svc_x = ();
@@ -342,11 +445,19 @@ sub batch_import {
       if ( $svc_x{'countrycode'} || $svc_x{'phonenum'} ) {
         $svc_phone = FS::svc_phone->new( {
           map { $_ => delete($svc_x{$_}) }
-              qw( countrycode phonenum sip_password pin)
+              qw( countrycode phonenum sip_password pin )
+        } );
+      }
+
+      my $svc_hardware = '';
+      if ( $svc_x{'typenum'} ) {
+        $svc_hardware = FS::svc_hardware->new( {
+          map { $_ => delete($svc_x{$_}) }
+            qw( typenum ip_addr hw_addr serial )
         } );
       }
 
-      if ( $svcdb || $svc_phone ) {
+      if ( $svcdb || $svc_phone || $svc_hardware ) {
         my $part_pkg = $cust_pkg->part_pkg;
        unless ( $part_pkg ) {
          $dbh->rollback if $oldAutoCommit;
@@ -361,6 +472,11 @@ sub batch_import {
           $svc_phone->svcpart( $part_pkg->svcpart_unique_svcdb('svc_phone') );
           push @svc_x, $svc_phone;
         }
+        if ( $svc_hardware ) {
+          $svc_hardware->svcpart( $part_pkg->svcpart_unique_svcdb('svc_hardware') );
+          push @svc_x, $svc_hardware;
+        }
+
       }
 
       $hash{$cust_pkg} = \@svc_x;