fix a problem running "letter" events more than once in a process, #32603
[freeside.git] / FS / FS / msg_template.pm
index bef2b4b..c52b633 100644 (file)
@@ -1,8 +1,19 @@
 package FS::msg_template;
+use base qw( FS::Record );
 
 use strict;
-use base qw( FS::Record );
+use vars qw( $DEBUG $conf );
+
+use Date::Format qw( time2str );
+use File::Temp;
+use IPC::Run qw(run);
 use Text::Template;
+
+use HTML::Entities qw( decode_entities encode_entities ) ;
+use HTML::FormatText;
+use HTML::TreeBuilder;
+use Encode;
+
 use FS::Misc qw( generate_email send_email do_print );
 use FS::Conf;
 use FS::Record qw( qsearch qsearchs );
@@ -12,16 +23,6 @@ use FS::cust_main;
 use FS::cust_msg;
 use FS::template_content;
 
-use Date::Format qw( time2str );
-use HTML::Entities qw( decode_entities encode_entities ) ;
-use HTML::FormatText;
-use HTML::TreeBuilder;
-use Encode;
-
-use File::Temp;
-use IPC::Run qw(run);
-use vars qw( $DEBUG $conf );
-
 FS::UID->install_callback( sub { $conf = new FS::Conf; } );
 
 $DEBUG=0;
@@ -277,16 +278,17 @@ A hash reference of additional substitutions
 sub prepare {
   my( $self, %opt ) = @_;
 
-  my $cust_main = $opt{'cust_main'} or die 'cust_main required';
+  my $cust_main = $opt{'cust_main'}; # or die 'cust_main required';
   my $object = $opt{'object'} or die 'object required';
 
   # localization
-  my $locale = $cust_main->locale || '';
+  my $locale = $cust_main && $cust_main->locale || '';
   warn "no locale for cust#".$cust_main->custnum."; using default content\n"
-    if $DEBUG and !$locale;
-  my $content = $self->content($cust_main->locale);
-  warn "preparing template '".$self->msgname."' to cust#".$cust_main->custnum."\n"
-    if($DEBUG);
+    if $DEBUG and $cust_main && !$locale;
+  my $content = $self->content($locale);
+
+  warn "preparing template '".$self->msgname."\n"
+    if $DEBUG;
 
   my $subs = $self->substitutions;
 
@@ -294,7 +296,8 @@ sub prepare {
   # create substitution table
   ###  
   my %hash;
-  my @objects = ($cust_main);
+  my @objects = ();
+  push @objects, $cust_main if $cust_main;
   my @prefixes = ('');
   my $svc;
   if( ref $object ) {
@@ -384,21 +387,22 @@ sub prepare {
   my @to;
   if ( exists($opt{'to'}) ) {
     @to = split(/\s*,\s*/, $opt{'to'});
-  }
-  else {
+  } elsif ( $cust_main ) {
     @to = $cust_main->invoicing_list_emailonly;
+  } else {
+    die 'no To: address or cust_main object specified';
   }
-  # no warning when preparing with no destination
 
   my $from_addr = $self->from_addr;
 
   if ( !$from_addr ) {
+
+    my $agentnum = $cust_main ? $cust_main->agentnum : '';
+
     if ( $opt{'from_config'} ) {
-      $from_addr = scalar( $conf->config($opt{'from_config'}, 
-                                         $cust_main->agentnum) );
+      $from_addr = $conf->config($opt{'from_config'}, $agentnum);
     }
-    $from_addr ||= scalar( $conf->config('invoice_from',
-                                         $cust_main->agentnum) );
+    $from_addr ||= $conf->invoice_from_full($agentnum);
   }
 #  my @cust_msg = ();
 #  if ( $conf->exists('log_sent_mail') and !$opt{'preview'} ) {
@@ -416,11 +420,11 @@ sub prepare {
                       ->format( HTML::TreeBuilder->new_from_content($body) )
                   );
   (
-    'custnum' => $cust_main->custnum,
-    'msgnum'  => $self->msgnum,
-    'from' => $from_addr,
-    'to'   => \@to,
-    'bcc'  => $self->bcc_addr || undef,
+    'custnum'   => ( $cust_main ? $cust_main->custnum : ''),
+    'msgnum'    => $self->msgnum,
+    'from'      => $from_addr,
+    'to'        => \@to,
+    'bcc'       => $self->bcc_addr || undef,
     'subject'   => $subject,
     'html_body' => $body,
     'text_body' => $text_body
@@ -463,9 +467,11 @@ sub render {
 
   # Graphics/stylesheets should probably go in /var/www on the Freeside 
   # machine.
+  my $script_path = `/usr/bin/which freeside-wkhtmltopdf`;
+  chomp $script_path;
   my $kit = PDF::WebKit->new(\$html); #%options
   # hack to use our wrapper script
-  $kit->configure(sub { shift->wkhtmltopdf('freeside-wkhtmltopdf') });
+  $kit->configure(sub { shift->wkhtmltopdf($script_path) });
 
   $kit->to_pdf;
 }
@@ -527,7 +533,7 @@ sub substitutions {
       balance
       credit_limit
       invoicing_list_emailonly
-      cust_status ucfirst_cust_status cust_statuscolor
+      cust_status ucfirst_cust_status cust_statuscolor cust_status_label
 
       signupdate dundate
       packages recurdates
@@ -558,6 +564,9 @@ sub substitutions {
       [ company_phonenum  => sub {
           $conf->config('company_phonenum', shift->agentnum)
         } ],
+      [ selfservice_server_base_url => sub { 
+          $conf->config('selfservice_server-base_url') #, shift->agentnum) 
+        } ],
     ],
     # next_bill_date
     'cust_pkg'  => [qw( 
@@ -592,7 +601,11 @@ sub substitutions {
     'cust_bill' => [qw(
       invnum
       _date
-    )],
+      _date_pretty
+      due_date
+    ),
+      [ due_date2str      => sub { shift->due_date2str('short') } ],
+    ],
     #XXX not really thinking about cust_bill substitutions quite yet
     
     # for welcome and limit warning messages
@@ -693,13 +706,13 @@ Returns the L<FS::agent> object for this template.
 
 =cut
 
-sub agent {
-  qsearchs('agent', { 'agentnum' => $_[0]->agentnum });
-}
-
 sub _upgrade_data {
   my ($self, %opts) = @_;
 
+  ###
+  # First move any historical templates in config to real message templates
+  ###
+
   my @fixes = (
     [ 'alerter_msgnum',  'alerter_template',   '',               '', '' ],
     [ 'cancel_msgnum',   'cancelmessage',      'cancelsubject',  '', '' ],
@@ -732,7 +745,70 @@ sub _upgrade_data {
         $conf->delete($subject, $agentnum) if $subject;
       }
     }
+
+    if ( $conf->exists('alert_expiration', $agentnum) ) {
+      my $msgnum = $conf->exists('alerter_msgnum', $agentnum);
+      my $template = FS::msg_template->by_key($msgnum) if $msgnum;
+      if (!$template) {
+        warn "template for alerter_msgnum $msgnum not found\n";
+        next;
+      }
+      # this is now a set of billing events
+      foreach my $days (30, 15, 5) {
+        my $event = FS::part_event->new({
+            'agentnum'    => $agentnum,
+            'event'       => "Card expiration warning - $days days",
+            'eventtable'  => 'cust_main',
+            'check_freq'  => '1d',
+            'action'      => 'notice',
+            'disabled'    => 'Y', #initialize first
+        });
+        my $error = $event->insert( 'msgnum' => $msgnum );
+        if ($error) {
+          warn "error creating expiration alert event:\n$error\n\n";
+          next;
+        }
+        # make it work like before:
+        # only send each warning once before the card expires,
+        # only warn active customers,
+        # only warn customers with CARD/DCRD,
+        # only warn customers who get email invoices
+        my %conds = (
+          'once_every'          => { 'run_delay' => '30d' },
+          'cust_paydate_within' => { 'within' => $days.'d' },
+          'cust_status'         => { 'status' => { 'active' => 1 } },
+          'payby'               => { 'payby'  => { 'CARD' => 1,
+                                                   'DCRD' => 1, }
+                                   },
+          'message_email'       => {},
+        );
+        foreach (keys %conds) {
+          my $condition = FS::part_event_condition->new({
+              'conditionname' => $_,
+              'eventpart'     => $event->eventpart,
+          });
+          $error = $condition->insert( %{ $conds{$_} });
+          if ( $error ) {
+            warn "error creating expiration alert event:\n$error\n\n";
+            next;
+          }
+        }
+        $error = $event->initialize;
+        if ( $error ) {
+          warn "expiration alert event was created, but not initialized:\n$error\n\n";
+        }
+      } # foreach $days
+      $conf->delete('alerter_msgnum', $agentnum);
+      $conf->delete('alert_expiration', $agentnum);
+
+    } # if alerter_msgnum
+
   }
+
+  ###
+  # Move subject and body from msg_template to template_content
+  ###
+
   foreach my $msg_template ( qsearch('msg_template', {}) ) {
     if ( $msg_template->subject || $msg_template->body ) {
       # create new default content
@@ -756,6 +832,35 @@ sub _upgrade_data {
       die $error if $error;
     }
   }
+
+  ###
+  # Add new-style default templates if missing
+  ###
+  $self->_populate_initial_data;
+
+}
+
+sub _populate_initial_data { #class method
+  #my($class, %opts) = @_;
+  #my $class = shift;
+
+  eval "use FS::msg_template::InitialData;";
+  die $@ if $@;
+
+  my $initial_data = FS::msg_template::InitialData->_initial_data;
+
+  foreach my $hash ( @$initial_data ) {
+
+    next if $hash->{_conf} && $conf->config( $hash->{_conf} );
+
+    my $msg_template = new FS::msg_template($hash);
+    my $error = $msg_template->insert( @{ $hash->{_insert_args} || [] } );
+    die $error if $error;
+
+    $conf->set( $hash->{_conf}, $msg_template->msgnum ) if $hash->{_conf};
+  
+  }
+
 }
 
 sub eviscerate {