allow sending email to specific contact classes, #33316
[freeside.git] / FS / FS / cust_main_Mixin.pm
index 211dc32..94e6eaa 100644 (file)
@@ -380,10 +380,11 @@ HTML body
 
 Text body
 
-=item sub_param
+=item to_contact_classnum
 
-Optional list of parameter hashrefs to be passed
-along to L<FS::msg_template/prepare>.
+The customer contact class (or classes, as a comma-separated list) to send
+the message to. If unspecified, will be sent to any contacts that are marked
+as invoice destinations (the equivalent of specifying 'invoice').
 
 =back
 
@@ -408,6 +409,7 @@ sub email_search_result {
   my $subject = delete $param->{subject};
   my $html_body = delete $param->{html_body};
   my $text_body = delete $param->{text_body};
+  my $to_contact_classnum = delete $param->{to_contact_classnum};
   my $error = '';
 
   my $job = delete $param->{'job'}
@@ -460,12 +462,21 @@ sub email_search_result {
       %message = $msg_template->prepare(
         'cust_main' => $cust_main,
         'object'    => $obj,
+        'to_contact_classnum' => $to_contact_classnum,
       );
-      $message{'sub_param'} = $param->{'sub_param'}
-        if $param->{'sub_param'};
-    }
-    else {
-      my @to = $cust_main->invoicing_list_emailonly;
+
+    } else {
+      # 3.x: false laziness with msg_template.pm; on 4.x, all email notices
+      # are generated from templates and this case goes away
+      my @classes;
+      if ( $opt{'to_contact_classnum'} ) {
+        my $classnum = $opt{'to_contact_classnum'};
+        @classes = ref($classnum) ? @$classnum : split(',', $classnum);
+      }
+      if (!@classes) {
+        @classes = ( 'invoice' );
+      }
+      my @to = $cust_main->contact_list_email(@classes);
       next if !@to;
 
       %message = (
@@ -540,9 +551,7 @@ sub process_email_search_result {
 
   $param->{'search'} = thaw(decode_base64($param->{'search'}))
     or die "process_email_search_result requires search params.\n";
-  $param->{'sub_param'} = thaw(decode_base64($param->{'sub_param'}))
-    or die "process_email_search_result error decoding sub_param\n"
-      if $param->{'sub_param'};
+
 #  $param->{'payby'} = [ split(/\0/, $param->{'payby'}) ]
 #    unless ref($param->{'payby'});
 
@@ -654,6 +663,57 @@ sub time2str_local {
   $string;
 }
 
+=item unsuspend_balance
+
+If conf I<unsuspend_balance> is set and customer's current balance is
+beneath the set threshold, unsuspends customer packages.
+
+=cut
+
+sub unsuspend_balance {
+  my $self = shift;
+  my $cust_main = $self->cust_main;
+  my $conf = $self->conf;
+  my $setting = $conf->config('unsuspend_balance');
+  my $maxbalance;
+  if ($setting eq 'Zero') {
+    $maxbalance = 0;
+
+  # kind of a pain to load/check all cust_bill instead of just open ones,
+  # but if for some reason payment gets applied to later bills before
+  # earlier ones, we still want to consider the later ones as allowable balance
+  } elsif ($setting eq 'Latest invoice charges') {
+    my @cust_bill = $cust_main->cust_bill();
+    my $cust_bill = $cust_bill[-1]; #always want the most recent one
+    if ($cust_bill) {
+      $maxbalance = $cust_bill->charged || 0;
+    } else {
+      $maxbalance = 0;
+    }
+  } elsif ($setting eq 'Charges not past due') {
+    my $now = time;
+    $maxbalance = 0;
+    foreach my $cust_bill ($cust_main->cust_bill()) {
+      next unless $now <= ($cust_bill->due_date || $cust_bill->_date);
+      $maxbalance += $cust_bill->charged || 0;
+    }
+  } elsif (length($setting)) {
+    warn "Unrecognized unsuspend_balance setting $setting";
+    return;
+  } else {
+    return;
+  }
+  my $balance = $cust_main->balance || 0;
+  if ($balance <= $maxbalance) {
+    my @errors = $cust_main->unsuspend;
+    # side-fx with nested transactions?  upstack rolls back?
+    warn "WARNING:Errors unsuspending customer ". $cust_main->custnum. ": ".
+         join(' / ', @errors)
+      if @errors;
+  }
+  return;
+}
+
 =back
 
 =head1 BUGS