RT#28648: Unsuspend when past due balance is paid [v3 merge]
[freeside.git] / FS / FS / cust_main_Mixin.pm
index 92dea28..fcd2c0b 100644 (file)
@@ -2,11 +2,12 @@ package FS::cust_main_Mixin;
 
 use strict;
 use vars qw( $DEBUG $me );
-use Carp qw( confess );
+use Carp qw( confess carp cluck );
 use FS::UID qw(dbh);
 use FS::cust_main;
 use FS::Record qw( qsearch qsearchs );
 use FS::Misc qw( send_email generate_email );
+use HTML::Entities;
 
 $DEBUG = 0;
 $me = '[FS::cust_main_Mixin]';
@@ -37,6 +38,7 @@ sub cust_linked { $_[0]->custnum; }
 
 sub cust_main { 
   my $self = shift;
+  cluck ref($self). '->cust_main called' if $DEBUG;
   $self->cust_linked ? qsearchs('cust_main', {custnum => $self->custnum}) : '';
 }
 
@@ -582,16 +584,31 @@ sub mt {
   return $lh->maketext(@_);
 }
 
-=item time2str_local FORMAT, TIME
+=item time2str_local FORMAT, TIME[, ESCAPE]
 
 Localizes a date (see L<Date::Language>) for the customer's locale.
 
+FORMAT can be a L<Date::Format> string, or one of these special words:
+
+- "short": the value of the "date_format" config setting for the customer's 
+  locale, defaulting to "%x".
+- "rdate": the same as "short" except that the default has a four-digit year.
+- "long": the value of the "date_format_long" config setting for the 
+  customer's locale, defaulting to "%b %o, %Y".
+
+ESCAPE, if specified, is one of "latex" or "html", and will escape non-ASCII
+characters and convert spaces to nonbreaking spaces.
+
 =cut
 
 sub time2str_local {
   # renamed so that we don't have to change every single reference to 
   # time2str everywhere
   my $self = shift;
+  my ($format, $time, $escape) = @_;
+  return '' unless $time > 0; # work around time2str's traditional stupidity
+
+  $self->{_date_format} ||= {};
   if (!exists($self->{_dh})) {
     my $cust_main = $self->cust_main;
     my $locale = $cust_main->locale  if $cust_main;
@@ -601,7 +618,70 @@ sub time2str_local {
              Date::Language->new(); # fall back to English
     $self->{_dh} = $dh;
   }
-  $self->{_dh}->time2str(@_);
+
+  if ($format eq 'short') {
+    $format = $self->{_date_format}->{short}
+            ||= $self->conf->config('date_format') || '%x';
+  } elsif ($format eq 'rdate') {
+    $format = $self->{_date_format}->{rdate}
+            ||= $self->conf->config('date_format') || '%m/%d/%Y';
+  } elsif ($format eq 'long') {
+    $format = $self->{_date_format}->{long}
+            ||= $self->conf->config('date_format_long') || '%b %o, %Y';
+  }
+
+  # actually render the date
+  my $string = $self->{_dh}->time2str($format, $time);
+
+  if ($escape) {
+    if ($escape eq 'html') {
+      $string = encode_entities($string);
+      $string =~ s/ +/&nbsp;/g;
+    } elsif ($escape eq 'latex') { # just do nbsp's here
+      $string =~ s/ +/~/g;
+    }
+  }
+  
+  $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;
+  } 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
+    return unless $cust_bill;
+    $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) {
+    # or should this be 
+    # my @errors = grep { ($_->get('setup')) && $_->unsuspend } $cust_main->unflagged_suspended_pkgs;
+    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