From d8e19d73efa750780648146fd45fe701c70c3465 Mon Sep 17 00:00:00 2001 From: mark Date: Wed, 24 Jun 2009 09:07:21 +0000 Subject: [PATCH] Move expiration alerts into FS::Cron::alert_expiration --- FS/FS/Cron/alert_expiration.pm | 177 +++++++++++++++++++++++++++++++++++++++++ FS/bin/freeside-daily | 4 + 2 files changed, 181 insertions(+) create mode 100644 FS/FS/Cron/alert_expiration.pm diff --git a/FS/FS/Cron/alert_expiration.pm b/FS/FS/Cron/alert_expiration.pm new file mode 100644 index 000000000..a9b9da9e9 --- /dev/null +++ b/FS/FS/Cron/alert_expiration.pm @@ -0,0 +1,177 @@ +package FS::Cron::alert_expiration; + +use vars qw( @ISA @EXPORT_OK); +use Exporter; +use FS::Record qw(qsearch); +use FS::Conf; +use FS::cust_main; +use FS::Misc; +use Time::Local; +use Date::Parse qw(str2time); + + +@ISA = qw( Exporter ); +@EXPORT_OK = qw( alert_expiration ); + +my $warning_time = 30 * 24 * 60 * 60; +my $urgent_time = 15 * 24 * 60 * 60; +my $panic_time = 5 * 24 * 60 * 60; +my $window_time = 24 * 60 * 60; + +sub alert_expiration { + my $conf = new FS::Conf; + my $smtpmachine = $conf->config('smtpmachine'); + + my %opt = @_; + my ($_date) = $opt{'d'} ? str2time($opt{'d'}) : $^T; + $_date += $opt{'y'} * 86400 if $opt{'y'}; + my ($sec, $min, $hour, $mday, $mon, $year) = (localtime($_date)) [0..5]; + $mon++; + + my $debug = 0; + $debug = 1 if $opt{'v'}; + $debug = $opt{'l'} if $opt{'l'}; + + $FS::cust_main::DEBUG = $debug; + + # Get a list of customers. + + my %limit; + $limit{'agentnum'} = $opt{'a'} if $opt{'a'}; + $limit{'payby'} = $opt{'p'} if $opt{'p'}; + + my @customers; + + if(my @custnums = @ARGV) { + # We're given an explicit list of custnums, so select those. Then check against + # -a and -p to avoid doing anything unexpected. + foreach (@custnums) { + my $customer = FS::cust_main->by_key($_); + if($customer and (!$opt{'a'} or $customer->agentnum == $opt{'a'}) + and (!$opt{'p'} or $customer->payby eq $opt{'p'}) ) { + push @customers, $customer; + } + } + } + else { # no @ARGV + @customers = qsearch('cust_main', \%limit); + } + return if(!@customers); + foreach my $customer (@customers) { + my $paydate = $customer->paydate; + next if $paydate =~ /^\s*$/; # skip empty expiration dates + + my $custnum = $customer->custnum; + my $first = $customer->first; + my $last = $customer->last; + my $company = $customer->company; + my $payby = $customer->payby; + my $payinfo = $customer->payinfo; + my $daytime = $customer->daytime; + my $night = $customer->night; + + my ($paymonth, $payyear) = $customer->paydate_monthyear; + $paymonth--; # localtime() convention + $payday = 1; # This is enforced by FS::cust_main::check. + my $expire_time; + if($payby eq 'CARD' || $payby eq 'DCRD') { + # Credit cards expire at the end of the month/year. + if($paymonth == 11) { + $payyear++; + $paymonth = 0; + } else { + $paymonth++; + } + $expire_time = timelocal(0,0,0,$payday,$paymonth,$payyear) - 1; + } + else { + $expire_time = timelocal(0,0,0,$payday,$paymonth,$payyear); + } + + if (grep { $expire_time < $_date + $_ && + $expire_time > $_date + $_ - $window_time } + ($warning_time, $urgent_time, $panic_time) ) { + my $agentnum = $customer->agentnum; + $mail_sender = $conf->config('invoice_from', $agentnum); + $failure_recipient = $conf->config('invoice_from', $agentnum) + || 'postmaster'; + + my @alerter_template = $conf->config('alerter_template', $agentnum) + or die 'cannot load config file alerter_template'; + + my $alerter = new Text::Template(TYPE => 'ARRAY', + SOURCE => [ + map "$_\n", @alerter_template + ]) + or die "can't create Text::Template object: $Text::Template::ERROR"; + + $alerter->compile() + or die "can't compile template: $Text::Template::ERROR"; + + my @packages = $customer->ncancelled_pkgs; + if(@packages) { + my @invoicing_list = $customer->invoicing_list; + my @to_addrs = grep { $_ ne 'POST' } @invoicing_list; + if(@to_addrs) { + # Set up template fields. + my %fill_in; + $fill_in{$_} = $customer->getfield($_) + foreach(qw(first last company)); + $fill_in{'expdate'} = $expire_time; + $fill_in{'company_name'} = $conf->config('company_name', $agentnum); + $fill_in{'company_address'} = + join("\n",$conf->config('company_address',$agentnum))."\n"; + if($payby eq 'CARD' || $payby eq 'DCRD') { + $fill_in{'payby'} = "credit card (". + substr($customer->payinfo, 0, 2) . "xxxxxxxxxx" . + substr($payinfo, -4) . ")"; + } + elsif($payby eq 'COMP') { + $fill_in{'payby'} = 'complimentary account'; + } + else { + $fill_in{'payby'} = 'current method'; + } + # Send it already! + my $error = FS::Misc::send_email ( + from => $mail_sender, + to => [ @to_addrs ], + subject => 'Billing Arrangement Expiration', + body => [ $alerter->fill_in( HASH => \%fill_in ) ], + ); + die "can't send expiration alert: $error" + if $error; + } + else { # if(@to_addrs) + push @{$agent_failure_body{$customer->agentnum}}, + sprintf(qq{%5d %-32.32s %4s %10s %12s %12s}, + $custnum, + $first . " " . $last . " " . $company, + $payby, + $paydate, + $daytime, + $night ); + } + } # if(@packages) + } # if(expired) + } # foreach(@customers) + + # Failure notification + foreach my $agentnum (keys %agent_failure_body) { + $mail_sender = $conf->config('invoice_from', $agentnum) + if($conf->exists('invoice_from', $agentnum)); + $failure_recipient = $conf->config('invoice_from', $agentnum) + if($conf->exists('invoice_from', $agentnum)); + my $error = FS::Misc::send_email ( + from => $mail_sender, + to => $failure_recipient, + subject => 'Unnotified Billing Arrangement Expirations', + body => [ @{$agent_failure_body{$agentnum}} ], + ); + die "can't send alerter failure email to $failure_recipient: $error" + if $error; + } + +} + +1; diff --git a/FS/bin/freeside-daily b/FS/bin/freeside-daily index bda42f025..acf795087 100755 --- a/FS/bin/freeside-daily +++ b/FS/bin/freeside-daily @@ -14,6 +14,10 @@ adminsuidsetup $user; use FS::Cron::bill qw(bill); bill(%opt); +# Send alerts about upcoming credit card expiration. +use FS::Cron::alert_expiration qw(alert_expiration); +alert_expiration(%opt); + #what to do about the below when using -m? that is the question. #you don't want to skip this, besides, it should be cheap -- 2.11.0