sort customer locations in package list, RT#41119, RT#39822
[freeside.git] / FS / FS / cust_main.pm
index 0279d17..5f77ab5 100644 (file)
@@ -28,6 +28,7 @@ use Date::Format;
 #use Date::Manip;
 use File::Temp; #qw( tempfile );
 use Business::CreditCard 0.28;
+use List::Util qw(min);
 use FS::UID qw( dbh driver_name );
 use FS::Record qw( qsearchs qsearch dbdef regexp_sql );
 use FS::Cursor;
@@ -1872,7 +1873,7 @@ sub check {
 sub check_payinfo_cardtype {
   my $self = shift;
 
-  return '' unless $self->payby =~ /^(CARD|CHEK)$/;
+  return '' unless $self->payby =~ /^(CARD|DCRD)$/;
 
   my $payinfo = $self->payinfo;
   $payinfo =~ s/\D//g;
@@ -1950,8 +1951,13 @@ Returns all locations (see L<FS::cust_location>) for this customer.
 
 sub cust_location {
   my $self = shift;
-  qsearch('cust_location', { 'custnum'     => $self->custnum,
-                             'prospectnum' => '' } );
+  qsearch({
+    'table'   => 'cust_location',
+    'hashref' => { 'custnum'     => $self->custnum,
+                   'prospectnum' => '',
+                 },
+    'order_by' => 'ORDER BY country, LOWER(state), LOWER(city), LOWER(county), LOWER(address1), LOWER(address2)',
+  });
 }
 
 =item cust_contact
@@ -2701,64 +2707,34 @@ sub payment_info {
 
 =item paydate_epoch
 
-Returns the exact time in seconds corresponding to the payment method 
-expiration date.  For CARD/DCRD customers this is the end of the month;
-for others (COMP is the only other payby that uses paydate) it's the start.
-Returns 0 if the paydate is empty or set to the far future.
+Returns the next payment expiration date for this customer. If they have no
+payment methods that will expire, returns 0.
 
 =cut
 
-#XXX i need to be updated for 4.x+
 sub paydate_epoch {
   my $self = shift;
-  my ($month, $year) = $self->paydate_monthyear;
-  return 0 if !$year or $year >= 2037;
-  if ( $self->payby eq 'CARD' or $self->payby eq 'DCRD' ) {
-    $month++;
-    if ( $month == 13 ) {
-      $month = 1;
-      $year++;
-    }
-    return timelocal(0,0,0,1,$month-1,$year) - 1;
-  }
-  else {
-    return timelocal(0,0,0,1,$month-1,$year);
-  }
+  # filter out the ones that individually return 0, but then return 0 if
+  # there are no results
+  my @epochs = grep { $_ > 0 } map { $_->paydate_epoch } $self->cust_payby;
+  min( @epochs ) || 0;
 }
 
 =item paydate_epoch_sql
 
-Class method.  Returns an SQL expression to obtain the payment expiration date
-as a number of seconds.
+Returns an SQL expression to get the next payment expiration date for a
+customer. Returns 2143260000 (2037-12-01) if there are no payment expiration
+dates, so that it's safe to test for "will it expire before date X" for any
+date up to then.
 
 =cut
 
-# XXX i need to be updated for 4.x+
-# Special expiration date behavior for non-CARD/DCRD customers has been 
-# carefully preserved.  Do we really use that?
 sub paydate_epoch_sql {
   my $class = shift;
-  my $table = shift || 'cust_main';
-  my ($case1, $case2);
-  if ( driver_name eq 'Pg' ) {
-    $case1 = "EXTRACT( EPOCH FROM CAST( $table.paydate AS TIMESTAMP ) + INTERVAL '1 month') - 1";
-    $case2 = "EXTRACT( EPOCH FROM CAST( $table.paydate AS TIMESTAMP ) )";
-  }
-  elsif ( lc(driver_name) eq 'mysql' ) {
-    $case1 = "UNIX_TIMESTAMP( DATE_ADD( CAST( $table.paydate AS DATETIME ), INTERVAL 1 month ) ) - 1";
-    $case2 = "UNIX_TIMESTAMP( CAST( $table.paydate AS DATETIME ) )";
-  }
-  else { return '' }
-  return "CASE WHEN $table.payby IN('CARD','DCRD') 
-  THEN ($case1)
-  ELSE ($case2)
-  END"
+  my $paydate = FS::cust_payby->paydate_epoch_sql;
+  "(SELECT COALESCE(MIN($paydate), 2143260000) FROM cust_payby WHERE cust_payby.custnum = cust_main.custnum)";
 }
 
-=item tax_exemption TAXNAME
-
-=cut
-
 sub tax_exemption {
   my( $self, $taxname ) = @_;
 
@@ -3393,9 +3369,12 @@ Returns all the credits (see L<FS::cust_credit>) for this customer.
 
 sub cust_credit {
   my $self = shift;
-  map { $_ } #return $self->num_cust_credit unless wantarray;
-  sort { $a->_date <=> $b->_date }
-    qsearch( 'cust_credit', { 'custnum' => $self->custnum } )
+
+  #return $self->num_cust_credit unless wantarray;
+
+  map { $_ } #behavior of sort undefined in scalar context
+    sort { $a->_date <=> $b->_date }
+      qsearch( 'cust_credit', { 'custnum' => $self->custnum } )
 }
 
 =item cust_credit_pkgnum
@@ -5144,71 +5123,6 @@ sub _upgrade_data { #class method
 
   }
 
-  unless ( FS::upgrade_journal->is_done('cust_main__cust_payby') ) {
-
-    #we don't want to decrypt them, just stuff them as-is into cust_payby
-    local(@encrypted_fields) = ();
-
-    local($FS::cust_payby::ignore_expired_card) = 1;
-    local($FS::cust_payby::ignore_banned_card) = 1;
-
-    my @payfields = qw( payby payinfo paycvv paymask
-                        paydate paystart_month paystart_year payissue
-                        payname paystate paytype payip
-                      );
-
-    my $search = new FS::Cursor {
-      'table'     => 'cust_main',
-      'extra_sql' => " WHERE ( payby IS NOT NULL AND payby != '' ) ",
-    };
-
-    while (my $cust_main = $search->fetch) {
-
-      unless ( $cust_main->payby =~ /^(BILL|COMP)$/ ) {
-
-        my $cust_payby = new FS::cust_payby {
-          'custnum' => $cust_main->custnum,
-          'weight'  => 1,
-          map { $_ => $cust_main->$_(); } @payfields
-        };
-
-        my $error = $cust_payby->insert;
-        die $error if $error;
-
-      }
-
-      # at the time we do this, also migrate paytype into cust_pay_batch
-      # so that batches that are open before the migration can still be 
-      # processed
-      my @cust_pay_batch = qsearch('cust_pay_batch', {
-          'custnum' => $cust_main->custnum,
-          'payby'   => 'CHEK',
-          'paytype' => '',
-      });
-      foreach my $cust_pay_batch (@cust_pay_batch) {
-        $cust_pay_batch->set('paytype', $cust_main->get('paytype'));
-        my $error = $cust_pay_batch->replace;
-        die "$error (setting cust_pay_batch.paytype)" if $error;
-      }
-
-      $cust_main->complimentary('Y') if $cust_main->payby eq 'COMP';
-
-      $cust_main->invoice_attn( $cust_main->payname )
-        if $cust_main->payby eq 'BILL' && $cust_main->payname;
-      $cust_main->po_number( $cust_main->payinfo )
-        if $cust_main->payby eq 'BILL' && $cust_main->payinfo;
-
-      $cust_main->setfield($_, '') foreach @payfields;
-      my $error = $cust_main->replace;
-      die "Error upgradging payment information for custnum ".
-          $cust_main->custnum. ": $error"
-        if $error;
-
-    };
-
-    FS::upgrade_journal->set_done('cust_main__cust_payby');
-  }
-
   $class->_upgrade_otaker(%opts);
 
 }