1 package FS::Cron::alert_expiration;
3 use vars qw( @ISA @EXPORT_OK);
5 use FS::Record qw(qsearch qsearchs);
10 use Date::Parse qw(str2time);
13 @ISA = qw( Exporter );
14 @EXPORT_OK = qw( alert_expiration );
16 my $warning_time = 30 * 24 * 60 * 60;
17 my $urgent_time = 15 * 24 * 60 * 60;
18 my $panic_time = 5 * 24 * 60 * 60;
19 my $window_time = 24 * 60 * 60;
21 sub alert_expiration {
22 my $conf = new FS::Conf;
23 my $smtpmachine = $conf->config('smtpmachine');
26 my ($_date) = $opt{'d'} ? str2time($opt{'d'}) : $^T;
27 $_date += $opt{'y'} * 86400 if $opt{'y'};
28 my ($sec, $min, $hour, $mday, $mon, $year) = (localtime($_date)) [0..5];
32 $debug = 1 if $opt{'v'};
33 $debug = $opt{'l'} if $opt{'l'};
35 $FS::cust_main::DEBUG = $debug;
37 # Get a list of customers.
40 $limit{'agentnum'} = $opt{'a'} if $opt{'a'};
41 $limit{'payby'} = $opt{'p'} if $opt{'p'};
45 if(my @custnums = @ARGV) {
46 # We're given an explicit list of custnums, so select those. Then check against
47 # -a and -p to avoid doing anything unexpected.
49 my $customer = FS::cust_main->by_key($_);
50 if($customer and (!$opt{'a'} or $customer->agentnum == $opt{'a'})
51 and (!$opt{'p'} or $customer->payby eq $opt{'p'}) ) {
52 push @customers, $customer;
57 @customers = qsearch('cust_main', \%limit);
59 return if(!@customers);
60 foreach my $customer (@customers) {
61 next if !($customer->ncancelled_pkgs); # skip inactive customers
62 my $paydate = $customer->paydate;
63 next if $paydate =~ /^\s*$/; # skip empty expiration dates
65 my $custnum = $customer->custnum;
66 my $first = $customer->first;
67 my $last = $customer->last;
68 my $company = $customer->company;
69 my $payby = $customer->payby;
70 my $payinfo = $customer->payinfo;
71 my $daytime = $customer->daytime;
72 my $night = $customer->night;
74 my ($paymonth, $payyear) = $customer->paydate_monthyear;
75 $paymonth--; # localtime() convention
76 $payday = 1; # This is enforced by FS::cust_main::check.
78 if($payby eq 'CARD' || $payby eq 'DCRD') {
79 # Credit cards expire at the end of the month/year.
86 $expire_time = timelocal(0,0,0,$payday,$paymonth,$payyear) - 1;
89 $expire_time = timelocal(0,0,0,$payday,$paymonth,$payyear);
92 if (grep { $expire_time < $_date + $_ &&
93 $expire_time > $_date + $_ - $window_time }
94 ($warning_time, $urgent_time, $panic_time) ) {
95 # Send an expiration notice.
96 my $agentnum = $customer->agentnum;
99 my $msgnum = $conf->config('alerter_msgnum', $agentnum);
100 if ( $msgnum ) { # new hotness
101 my $msg_template = qsearchs('msg_template', { msgnum => $msgnum } );
102 $customer->setfield('expdate', $expire_time);
103 $error = $msg_template->send('cust_main' => $customer);
105 else { #!$msgnum, the hard way
106 $mail_sender = $conf->config('invoice_from', $agentnum);
107 $failure_recipient = $conf->config('invoice_from', $agentnum)
110 my @alerter_template = $conf->config('alerter_template', $agentnum)
111 or die 'cannot load config file alerter_template';
113 my $alerter = new Text::Template(TYPE => 'ARRAY',
115 map "$_\n", @alerter_template
117 or die "can't create Text::Template object: $Text::Template::ERROR";
120 or die "can't compile template: $Text::Template::ERROR";
122 my @invoicing_list = $customer->invoicing_list;
123 my @to_addrs = grep { $_ ne 'POST' } @invoicing_list;
125 # Set up template fields.
127 $fill_in{$_} = $customer->getfield($_)
128 foreach(qw(first last company));
129 $fill_in{'expdate'} = $expire_time;
130 $fill_in{'company_name'} = $conf->config('company_name', $agentnum);
131 $fill_in{'company_address'} =
132 join("\n",$conf->config('company_address',$agentnum))."\n";
133 if($payby eq 'CARD' || $payby eq 'DCRD') {
134 $fill_in{'payby'} = "credit card (".
135 substr($customer->payinfo, 0, 2) . "xxxxxxxxxx" .
136 substr($payinfo, -4) . ")";
138 elsif($payby eq 'COMP') {
139 $fill_in{'payby'} = 'complimentary account';
142 $fill_in{'payby'} = 'current method';
145 $error = FS::Misc::send_email (
146 from => $mail_sender,
148 subject => 'Billing Arrangement Expiration',
149 body => [ $alerter->fill_in( HASH => \%fill_in ) ],
152 else { # if(@to_addrs)
153 push @{$agent_failure_body{$customer->agentnum}},
154 sprintf(qq{%5d %-32.32s %4s %10s %12s %12s},
156 $first . " " . $last . " " . $company,
164 # should we die here rather than report failure as below?
165 die "can't send expiration alert: $error"
169 } # foreach(@customers)
171 # Failure notification
172 foreach my $agentnum (keys %agent_failure_body) {
173 $mail_sender = $conf->config('invoice_from', $agentnum)
174 if($conf->exists('invoice_from', $agentnum));
175 $failure_recipient = $conf->config('invoice_from', $agentnum)
176 if($conf->exists('invoice_from', $agentnum));
177 my $error = FS::Misc::send_email (
178 from => $mail_sender,
179 to => $failure_recipient,
180 subject => 'Unnotified Billing Arrangement Expirations',
181 body => [ @{$agent_failure_body{$agentnum}} ],
183 die "can't send alerter failure email to $failure_recipient: $error"