add signup-duplicate_cc-warn_hours to warn about duplicate signups in a time span...
[freeside.git] / FS / FS / cust_main.pm
index 8345d92..a5ee232 100644 (file)
@@ -324,6 +324,10 @@ A suggestion to events (see L<FS::part_bill_event">) to delay until this unix ti
 
 Discourage individual CDR printing, empty or `Y'
 
+=item edit_subject
+
+Allow self-service editing of ticket subjects, empty or 'Y'
+
 =back
 
 =head1 METHODS
@@ -772,7 +776,7 @@ sub get_prepay {
 
     $prepay_credit = qsearchs(
       'prepay_credit',
-      { 'identifier' => $prepay_credit },
+      { 'identifier' => $identifier },
       '',
       'FOR UPDATE'
     );
@@ -1682,6 +1686,7 @@ sub check {
     || $self->ut_floatn('cdr_termination_percentage')
     || $self->ut_floatn('credit_limit')
     || $self->ut_numbern('billday')
+    || $self->ut_enum('edit_subject', [ '', 'Y' ] )
   ;
 
   #barf.  need message catalogs.  i18n.  etc.
@@ -1848,7 +1853,7 @@ sub check {
 
     my $payinfo = $self->payinfo;
     $payinfo =~ s/\D//g;
-    $payinfo =~ /^(\d{13,16})$/
+    $payinfo =~ /^(\d{13,16}|\d{8,9})$/
       or return gettext('invalid_card'); # . ": ". $self->payinfo;
     $payinfo = $1;
     $self->payinfo($payinfo);
@@ -1860,12 +1865,17 @@ sub check {
       && cardtype($self->payinfo) eq "Unknown";
 
     unless ( $ignore_banned_card ) {
-      my $ban = qsearchs('banned_pay', $self->_banned_pay_hashref);
+      my $ban = FS::banned_pay->ban_search( %{ $self->_banned_pay_hashref } );
       if ( $ban ) {
-        return 'Banned credit card: banned on '.
-               time2str('%a %h %o at %r', $ban->_date).
-               ' by '. $ban->otaker.
-               ' (ban# '. $ban->bannum. ')';
+        if ( $ban->bantype eq 'warn' ) {
+          #or others depending on value of $ban->reason ?
+          return '_duplicate_card' unless $self->override_ban_warn;
+        } else {
+          return 'Banned credit card: banned on '.
+                 time2str('%a %h %o at %r', $ban->_date).
+                 ' by '. $ban->otaker.
+                 ' (ban# '. $ban->bannum. ')';
+        }
       }
     }
 
@@ -1910,8 +1920,12 @@ sub check {
   } elsif ( $check_payinfo && $self->payby =~ /^(CHEK|DCHK)$/ ) {
 
     my $payinfo = $self->payinfo;
-    $payinfo =~ s/[^\d\@]//g;
-    if ( $conf->exists('echeck-nonus') ) {
+    $payinfo =~ s/[^\d\@\.]//g;
+    if ( $conf->exists('cust_main-require-bank-branch') ) {
+      $payinfo =~ /^(\d+)\@(\d+)\.(\d+)$/ or return 'invalid echeck account@branch.bank';
+      $payinfo = "$1\@$2.$3";
+    }
+    elsif ( $conf->exists('echeck-nonus') ) {
       $payinfo =~ /^(\d+)\@(\d+)$/ or return 'invalid echeck account@aba';
       $payinfo = "$1\@$2";
     } else {
@@ -1922,12 +1936,17 @@ sub check {
     $self->paycvv('');
 
     unless ( $ignore_banned_card ) {
-      my $ban = qsearchs('banned_pay', $self->_banned_pay_hashref);
+      my $ban = FS::banned_pay->ban_search( %{ $self->_banned_pay_hashref } );
       if ( $ban ) {
-        return 'Banned ACH account: banned on '.
-               time2str('%a %h %o at %r', $ban->_date).
-               ' by '. $ban->otaker.
-               ' (ban# '. $ban->bannum. ')';
+        if ( $ban->bantype eq 'warn' ) {
+          #or others depending on value of $ban->reason ?
+          return '_duplicate_ach' unless $self->override_ban_warn;
+        } else {
+          return 'Banned ACH account: banned on '.
+                 time2str('%a %h %o at %r', $ban->_date).
+                 ' by '. $ban->otaker.
+                 ' (ban# '. $ban->bannum. ')';
+        }
       }
     }
 
@@ -2207,7 +2226,7 @@ sub cancel {
     return ( "Can't (yet) ban encrypted credit cards" )
       if $self->is_encrypted($self->payinfo);
 
-    my $ban = new FS::banned_pay $self->_banned_pay_hashref;
+    my $ban = new FS::banned_pay $self->_new_banned_pay_hashref;
     my $error = $ban->insert;
     return ( $error ) if $error;
 
@@ -2241,11 +2260,18 @@ sub _banned_pay_hashref {
 
   {
     'payby'   => $payby2ban{$self->payby},
-    'payinfo' => md5_base64($self->payinfo),
+    'payinfo' => $self->payinfo,
     #don't ever *search* on reason! #'reason'  =>
   };
 }
 
+sub _new_banned_pay_hashref {
+  my $self = shift;
+  my $hr = $self->_banned_pay_hashref;
+  $hr->{payinfo} = md5_base64($hr->{payinfo});
+  $hr;
+}
+
 =item notes
 
 Returns all notes (see L<FS::cust_main_note>) for this customer.
@@ -2906,6 +2932,60 @@ sub paydate_monthyear {
   }
 }
 
+=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.
+
+=cut
+
+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);
+  }
+}
+
+=item paydate_epoch_sql
+
+Class method.  Returns an SQL expression to obtain the payment expiration date
+as a number of seconds.
+
+=cut
+
+# 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"
+}
+
 =item tax_exemption TAXNAME
 
 =cut
@@ -3410,7 +3490,7 @@ sub charge {
 sub charge_postal_fee {
   my $self = shift;
 
-  my $pkgpart = $conf->config('postal_invoice-fee_pkgpart');
+  my $pkgpart = $conf->config('postal_invoice-fee_pkgpart', $self->agentnum);
   return '' unless ($pkgpart && grep { $_ eq 'POST' } $self->invoicing_list);
 
   my $cust_pkg = new FS::cust_pkg ( {
@@ -4214,7 +4294,7 @@ sub balance_date_sql {
 =item unapplied_payments_date_sql START_TIME [ END_TIME ]
 
 Returns an SQL fragment to retreive the total unapplied payments for this
-customer, only considering invoices with date earlier than START_TIME, and
+customer, only considering payments with date earlier than START_TIME, and
 optionally not later than END_TIME.
 
 Times are specified as SQL fragments or numeric