tax data update bug fixes and error message improvements
[freeside.git] / FS / FS / cust_main.pm
index b74c75d..b4f7f8c 100644 (file)
@@ -16,6 +16,8 @@ use Digest::MD5 qw(md5_base64);
 use Date::Format;
 use Date::Parse;
 #use Date::Manip;
+use File::Slurp qw( slurp );
+use File::Temp qw( tempfile );
 use String::Approx qw(amatch);
 use Business::CreditCard 0.28;
 use Locale::Country;
@@ -35,6 +37,7 @@ use FS::cust_credit;
 use FS::cust_refund;
 use FS::part_referral;
 use FS::cust_main_county;
+use FS::cust_tax_location;
 use FS::agent;
 use FS::cust_main_invoice;
 use FS::cust_credit_bill;
@@ -1209,6 +1212,7 @@ sub check {
     || $self->ut_number('agentnum')
     || $self->ut_textn('agent_custid')
     || $self->ut_number('refnum')
+    || $self->ut_textn('custbatch')
     || $self->ut_name('last')
     || $self->ut_name('first')
     || $self->ut_snumbern('birthdate')
@@ -2092,9 +2096,11 @@ sub bill {
 
     my $real_pkgpart = $cust_pkg->pkgpart;
     my %hash = $cust_pkg->hash;
-    my $old_cust_pkg = new FS::cust_pkg \%hash;
 
     foreach my $part_pkg ( $cust_pkg->part_pkg->self_and_bill_linked ) {
+
+      $cust_pkg->set($_, $hash{$_}) foreach qw ( setup last_bill bill );
+
       my $error =
         $self->_make_lines( 'part_pkg'            => $part_pkg,
                             'cust_pkg'            => $cust_pkg,
@@ -2302,13 +2308,12 @@ sub _make_lines {
   my %hash = $cust_pkg->hash;
   my $old_cust_pkg = new FS::cust_pkg \%hash;
 
-  $cust_pkg->pkgpart($part_pkg->pkgpart); 
-  $cust_pkg->set($_, $hash{$_}) foreach qw( setup last_bill bill );
-  
   my @details = ();
 
   my $lineitems = 0;
 
+  $cust_pkg->pkgpart($part_pkg->pkgpart);
+
   ###
   # bill setup
   ###
@@ -2490,18 +2495,20 @@ sub _make_lines {
         unless $part_pkg->pkgpart == $real_pkgpart;
       push @$appended_cust_bill_pkg, $cust_bill_pkg;
 
-      $$total_setup += $cust_bill_pkg->setup;
-      $$total_recur += $cust_bill_pkg->recur;
+      unless ($cust_bill_pkg->duplicate) {
+        $$total_setup += $cust_bill_pkg->setup;
+        $$total_recur += $cust_bill_pkg->recur;
 
-      ###
-      # handle taxes
-      ###
+        ###
+        # handle taxes
+        ###
 
-      unless ( $self->tax =~ /Y/i || $self->payby eq 'COMP' ) {
+        unless ( $self->tax =~ /Y/i || $self->payby eq 'COMP' ) {
 
-        $self->_handle_taxes($part_pkg, $taxlisthash, $cust_bill_pkg);
+          $self->_handle_taxes($part_pkg, $taxlisthash, $cust_bill_pkg);
 
-      } #unless $self->tax =~ /Y/i || $self->payby eq 'COMP'
+        } #unless $self->tax =~ /Y/i || $self->payby eq 'COMP'
+      }
     }
   }
 
@@ -5540,6 +5547,15 @@ sub search_sql {
                    @{ $params->{'current_balance'} };
 
   ##
+  # custbatch
+  ##
+
+  if ( $params->{'custbatch'} =~ /^([\w\/\-\:\.]+)$/ and $1 ) {
+    push @where,
+      "cust_main.custbatch = '$1'";
+  }
+
+  ##
   # setup queries, subs, etc. for the search
   ##
 
@@ -6201,21 +6217,79 @@ sub append_fuzzyfiles {
   1;
 }
 
+=item process_batch_import
+
+Load a batch import as a queued JSRPC job
+
+=cut
+
+use Storable qw(thaw);
+use Data::Dumper;
+use MIME::Base64;
+sub process_batch_import {
+  my $job = shift;
+
+  my $param = thaw(decode_base64(shift));
+  warn Dumper($param) if $DEBUG;
+  
+  my $files = $param->{'uploaded_files'}
+    or die "No files provided.\n";
+
+  my (%files) = map { /^(\w+):([\.\w]+)$/ ? ($1,$2):() } split /,/, $files;
+
+  my $dir = '%%%FREESIDE_CACHE%%%/cache.'. $FS::UID::datasrc. '/';
+  my $file = $dir. $files{'file'};
+
+  my $type;
+  if ( $file =~ /\.(\w+)$/i ) {
+    $type = lc($1);
+  } else {
+    #or error out???
+    warn "can't parse file type from filename $file; defaulting to CSV";
+    $type = 'csv';
+  }
+
+  my $error =
+    FS::cust_main::batch_import( {
+      job       => $job,
+      file      => $file,
+      type      => $type,
+      custbatch => $param->{custbatch},
+      agentnum  => $param->{'agentnum'},
+      refnum    => $param->{'refnum'},
+      pkgpart   => $param->{'pkgpart'},
+      #'fields'  => [qw( cust_pkg.setup dayphone first last address1 address2
+      #                 city state zip comments                          )],
+      'format'  => $param->{'format'},
+    } );
+
+  unlink $file;
+
+  die "$error\n" if $error;
+
+}
+
 =item batch_import
 
 =cut
 
+#some false laziness w/cdr.pm now
 sub batch_import {
   my $param = shift;
-  #warn join('-',keys %$param);
-  my $fh = $param->{filehandle};
-  my $agentnum = $param->{agentnum};
 
-  my $refnum = $param->{refnum};
-  my $pkgpart = $param->{pkgpart};
+  my $job       = $param->{job};
+
+  my $filename  = $param->{file};
+  my $type      = $param->{type} || 'csv';
+
+  my $custbatch = $param->{custbatch};
+
+  my $agentnum  = $param->{agentnum};
+  my $refnum    = $param->{refnum};
+  my $pkgpart   = $param->{pkgpart};
+
+  my $format    = $param->{'format'};
 
-  #my @fields = @{$param->{fields}};
-  my $format = $param->{'format'};
   my @fields;
   my $payby;
   if ( $format eq 'simple' ) {
@@ -6250,14 +6324,34 @@ sub batch_import {
     die "unknown format $format";
   }
 
-  eval "use Text::CSV_XS;";
-  die $@ if $@;
+  my $count;
+  my $parser;
+  my @buffer = ();
+  if ( $type eq 'csv' ) {
 
-  my $csv = new Text::CSV_XS;
-  #warn $csv;
-  #warn $fh;
+    eval "use Text::CSV_XS;";
+    die $@ if $@;
+
+    $parser = new Text::CSV_XS;
+
+    @buffer = split(/\r?\n/, slurp($filename) );
+    $count = scalar(@buffer);
+
+  } elsif ( $type eq 'xls' ) {
+
+    eval "use Spreadsheet::ParseExcel;";
+    die $@ if $@;
+
+    my $excel = new Spreadsheet::ParseExcel::Workbook->Parse($filename);
+    $parser = $excel->{Worksheet}[0]; #first sheet
+
+    $count = $parser->{MaxRow} || $parser->{MinRow};
+    $count++;
+
+  } else {
+    die "Unknown file type $type\n";
+  }
 
-  my $imported = 0;
   #my $columns;
 
   local $SIG{HUP} = 'IGNORE';
@@ -6271,24 +6365,46 @@ sub batch_import {
   local $FS::UID::AutoCommit = 0;
   my $dbh = dbh;
   
-  #while ( $columns = $csv->getline($fh) ) {
   my $line;
-  while ( defined($line=<$fh>) ) {
+  my $row = 0;
+  my( $last, $min_sec ) = ( time, 5 ); #progressbar foo
+  while (1) {
 
-    $csv->parse($line) or do {
-      $dbh->rollback if $oldAutoCommit;
-      return "can't parse: ". $csv->error_input();
-    };
+    my @columns = ();
+    if ( $type eq 'csv' ) {
+
+      last unless scalar(@buffer);
+      $line = shift(@buffer);
+
+      $parser->parse($line) or do {
+        $dbh->rollback if $oldAutoCommit;
+        return "can't parse: ". $parser->error_input();
+      };
+      @columns = $parser->fields();
+
+    } elsif ( $type eq 'xls' ) {
+
+      last if $row > ($parser->{MaxRow} || $parser->{MinRow});
+
+      my @row = @{ $parser->{Cells}[$row] };
+      @columns = map $_->{Val}, @row;
+
+      #my $z = 'A';
+      #warn $z++. ": $_\n" for @columns;
+
+    } else {
+      die "Unknown file type $type\n";
+    }
 
-    my @columns = $csv->fields();
     #warn join('-',@columns);
 
     my %cust_main = (
-      agentnum => $agentnum,
-      refnum   => $refnum,
-      country  => $conf->config('countrydefault') || 'US',
-      payby    => $payby, #default
-      paydate  => '12/2037', #default
+      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 );
@@ -6340,7 +6456,9 @@ sub batch_import {
       }
     }
 
-    $cust_main{'payby'} = 'CARD' if length($cust_main{'payinfo'});
+    $cust_main{'payby'} = 'CARD'
+      if defined $cust_main{'payinfo'}
+      && length  $cust_main{'payinfo'};
 
     my $invoicing_list = $cust_main{'invoicing_list'}
                            ? [ delete $cust_main{'invoicing_list'} ]
@@ -6372,7 +6490,7 @@ sub batch_import {
 
     if ( $error ) {
       $dbh->rollback if $oldAutoCommit;
-      return "can't insert customer for $line: $error";
+      return "can't insert customer". ( $line ? " for $line" : '' ). ": $error";
     }
 
     if ( $format eq 'simple' ) {
@@ -6398,12 +6516,18 @@ sub batch_import {
 
     }
 
-    $imported++;
+    $row++;
+
+    if ( $job && time - $min_sec > $last ) { #progress bar
+      $job->update_statustext( int(100 * $row / $count) );
+      $last = time;
+    }
+
   }
 
-  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;;
 
-  return "Empty file!" unless $imported;
+  return "Empty file!" unless $row;
 
   ''; #no error