per-package flags to override package def level suspend_bill flag, RT#18376
[freeside.git] / FS / FS / cust_main.pm
index d45af58..765e90e 100644 (file)
@@ -70,6 +70,7 @@ use FS::cust_main_note;
 use FS::cust_attachment;
 use FS::contact;
 use FS::Locales;
+use FS::upgrade_journal;
 
 # 1 is mostly method/subroutine entry and options
 # 2 traces progress of some operations
@@ -398,8 +399,9 @@ The I<noexport> option is deprecated.  If I<noexport> is set true, no
 provisioning jobs (exports) are scheduled.  (You can schedule them later with
 the B<reexport> method.)
 
-The I<tax_exemption> option can be set to an arrayref of tax names.
-FS::cust_main_exemption records will be created and inserted.
+The I<tax_exemption> option can be set to an arrayref of tax names or a hashref
+of tax names and exemption numbers.  FS::cust_main_exemption records will be
+created and inserted.
 
 If I<prospectnum> is set, moves contacts and locations from that prospect.
 
@@ -544,10 +546,15 @@ sub insert {
 
   my $tax_exemption = delete $options{'tax_exemption'};
   if ( $tax_exemption ) {
-    foreach my $taxname ( @$tax_exemption ) {
+
+    $tax_exemption = { map { $_ => '' } @$tax_exemption }
+      if ref($tax_exemption) eq 'ARRAY';
+
+    foreach my $taxname ( keys %$tax_exemption ) {
       my $cust_main_exemption = new FS::cust_main_exemption {
-        'custnum' => $self->custnum,
-        'taxname' => $taxname,
+        'custnum'       => $self->custnum,
+        'taxname'       => $taxname,
+        'exempt_number' => $tax_exemption->{$taxname},
       };
       my $error = $cust_main_exemption->insert;
       if ( $error ) {
@@ -1460,8 +1467,9 @@ check_invoicing_list first.  Here's an example:
 
 Currently available options are: I<tax_exemption>.
 
-The I<tax_exemption> option can be set to an arrayref of tax names.
-FS::cust_main_exemption records will be deleted and inserted as appropriate.
+The I<tax_exemption> option can be set to an arrayref of tax names or a hashref
+of tax names and exemption numbers.  FS::cust_main_exemption records will be
+deleted and inserted as appropriate.
 
 =cut
 
@@ -1537,6 +1545,10 @@ sub replace {
     $self->censusyear($conf->config('census_year')||'2012');
   }
 
+  return "Invoicing locale is required"
+    if $old->locale
+    && ! $self->locale
+    && $conf->exists('cust_main-require_locale');
 
   local $SIG{HUP} = 'IGNORE';
   local $SIG{INT} = 'IGNORE';
@@ -1593,17 +1605,27 @@ sub replace {
   my $tax_exemption = delete $options{'tax_exemption'};
   if ( $tax_exemption ) {
 
+    $tax_exemption = { map { $_ => '' } @$tax_exemption }
+      if ref($tax_exemption) eq 'ARRAY';
+
     my %cust_main_exemption =
       map { $_->taxname => $_ }
           qsearch('cust_main_exemption', { 'custnum' => $old->custnum } );
 
-    foreach my $taxname ( @$tax_exemption ) {
+    foreach my $taxname ( keys %$tax_exemption ) {
 
-      next if delete $cust_main_exemption{$taxname};
+      if ( $cust_main_exemption{$taxname} && 
+           $cust_main_exemption{$taxname}->exempt_number eq $tax_exemption->{$taxname}
+         )
+      {
+        delete $cust_main_exemption{$taxname};
+        next;
+      }
 
       my $cust_main_exemption = new FS::cust_main_exemption {
-        'custnum' => $self->custnum,
-        'taxname' => $taxname,
+        'custnum'       => $self->custnum,
+        'taxname'       => $taxname,
+        'exempt_number' => $tax_exemption->{$taxname},
       };
       my $error = $cust_main_exemption->insert;
       if ( $error ) {
@@ -1778,6 +1800,7 @@ sub check {
     || $self->ut_numbern('billday')
     || $self->ut_enum('edit_subject', [ '', 'Y' ] )
     || $self->ut_enum('calling_list_exempt', [ '', 'Y' ] )
+    || $self->ut_enum('invoice_noemail', [ '', 'Y' ] )
     || $self->ut_enum('locale', [ '', FS::Locales->locales ])
   ;
 
@@ -2138,6 +2161,11 @@ sub check {
     $self->payname($1);
   }
 
+  return "Please select an invoicing locale"
+    if ! $self->locale
+    && ! $self->custnum
+    && $conf->exists('cust_main-require_locale');
+
   foreach my $flag (qw( tax spool_cdr squelch_cdr archived email_csv_cdr )) {
     $self->$flag() =~ /^(Y?)$/ or return "Illegal $flag: ". $self->$flag();
     $self->$flag($1);
@@ -3911,12 +3939,32 @@ cust_main-default_agent_custid is set and it has a value, custnum otherwise.
 
 sub display_custnum {
   my $self = shift;
+
+  my $prefix = $conf->config('cust_main-custnum-display_prefix', $self->agentnum) || '';
+  if ( my $special = $conf->config('cust_main-custnum-display_special') ) {
+    if ( $special eq 'CoStAg' ) {
+      $prefix = uc( join('',
+        $self->country,
+        ($self->state =~ /^(..)/),
+        $prefix || ($self->agent->agent =~ /^(..)/)
+      ) );
+    }
+    elsif ( $special eq 'CoStCl' ) {
+      $prefix = uc( join('',
+        $self->country,
+        ($self->state =~ /^(..)/),
+        ($self->classnum ? $self->cust_class->classname =~ /^(..)/ : '__')
+      ) );
+    }
+    # add any others here if needed
+  }
+
   my $length = $conf->config('cust_main-custnum-display_length');
   if ( $conf->exists('cust_main-default_agent_custid') && $self->agent_custid ){
     return $self->agent_custid;
-  } elsif ( $conf->config('cust_main-custnum-display_prefix') ) {
+  } elsif ( $prefix ) {
     $length = 8 if !defined($length);
-    return $conf->config('cust_main-custnum-display_prefix').
+    return $prefix . 
            sprintf('%0'.$length.'d', $self->custnum)
   } elsif ( $length ) {
     return sprintf('%0'.$length.'d', $self->custnum);
@@ -5011,25 +5059,44 @@ sub _upgrade_data { #class method
 
   my @statements = (
     'UPDATE h_cust_main SET paycvv = NULL WHERE paycvv IS NOT NULL',
-    'UPDATE cust_main SET signupdate = (SELECT signupdate FROM h_cust_main WHERE signupdate IS NOT NULL AND h_cust_main.custnum = cust_main.custnum ORDER BY historynum DESC LIMIT 1) WHERE signupdate IS NULL',
   );
-  # fix yyyy-m-dd formatted paydates
-  if ( driver_name =~ /^mysql/i ) {
+
+  #this seems to be the only expensive one.. why does it take so long?
+  unless ( FS::upgrade_journal->is_done('cust_main__signupdate') ) {
     push @statements,
-    "UPDATE cust_main SET paydate = CONCAT( SUBSTRING(paydate FROM 1 FOR 5), '0', SUBSTRING(paydate FROM 6) ) WHERE SUBSTRING(paydate FROM 7 FOR 1) = '-'";
+      'UPDATE cust_main SET signupdate = (SELECT signupdate FROM h_cust_main WHERE signupdate IS NOT NULL AND h_cust_main.custnum = cust_main.custnum ORDER BY historynum DESC LIMIT 1) WHERE signupdate IS NULL';
+    FS::upgrade_journal->set_done('cust_main__signupdate');
   }
-  else { # the SQL standard
-    push @statements, 
-    "UPDATE cust_main SET paydate = SUBSTRING(paydate FROM 1 FOR 5) || '0' || SUBSTRING(paydate FROM 6) WHERE SUBSTRING(paydate FROM 7 FOR 1) = '-'";
+
+  unless ( FS::upgrade_journal->is_done('cust_main__paydate') ) {
+
+    # fix yyyy-m-dd formatted paydates
+    if ( driver_name =~ /^mysql/i ) {
+      push @statements,
+      "UPDATE cust_main SET paydate = CONCAT( SUBSTRING(paydate FROM 1 FOR 5), '0', SUBSTRING(paydate FROM 6) ) WHERE SUBSTRING(paydate FROM 7 FOR 1) = '-'";
+    } else { # the SQL standard
+      push @statements, 
+      "UPDATE cust_main SET paydate = SUBSTRING(paydate FROM 1 FOR 5) || '0' || SUBSTRING(paydate FROM 6) WHERE SUBSTRING(paydate FROM 7 FOR 1) = '-'";
+    }
+    FS::upgrade_journal->set_done('cust_main__paydate');
   }
 
-  push @statements, #fix the weird BILL with a cc# in payinfo problem
-    #DCRD to be safe
-    "UPDATE cust_main SET payby = 'DCRD' WHERE payby = 'BILL' and length(payinfo) = 16 and payinfo ". regexp_sql. q( '^[0-9]*$' );
+  unless ( FS::upgrade_journal->is_done('cust_main__payinfo') ) {
+
+    push @statements, #fix the weird BILL with a cc# in payinfo problem
+      #DCRD to be safe
+      "UPDATE cust_main SET payby = 'DCRD' WHERE payby = 'BILL' and length(payinfo) = 16 and payinfo ". regexp_sql. q( '^[0-9]*$' );
+
+    FS::upgrade_journal->set_done('cust_main__payinfo');
+    
+  }
 
+  my $t = time;
   foreach my $sql ( @statements ) {
     my $sth = dbh->prepare($sql) or die dbh->errstr;
     $sth->execute or die $sth->errstr;
+    #warn ( (time - $t). " seconds\n" );
+    #$t = time;
   }
 
   local($ignore_expired_card) = 1;