2018 holidays, RT#18117
[freeside.git] / FS / FS / pay_batch / eft_canada.pm
1 package FS::pay_batch::eft_canada;
2
3 use strict;
4 use vars qw(@ISA %import_info %export_info $name);
5 use FS::Record 'qsearch';
6 use FS::Conf;
7 use FS::cust_pay_batch;
8 use DateTime;
9
10 my $conf;
11 my $origid;
12
13 $name = 'eft_canada';
14
15 %import_info = ( filetype  => 'NONE' ); # see FS/bin/freeside-eftca-download
16
17 my ($business_trans_code, $personal_trans_code, $trans_code);
18 my $req_date; # requested process date, in %D format
19
20 # use Date::Holidays::CA for this?
21 #ref http://gocanada.about.com/od/canadatravelplanner/a/canada_holidays.htm
22 my %holiday_yearly = (
23    1 => { map {$_=>1}  1 }, #new year's
24    7 => { map {$_=>1}  1 }, #canada day
25   11 => { map {$_=>1} 11 }, #remembrance day
26   12 => { map {$_=>1} 25 }, #christmas
27   12 => { map {$_=>1} 26 }, #boxing day
28 );
29 my %holiday = (
30   2016 => {  2 => { map {$_=>1} 15 }, #family day
31              3 => { map {$_=>1} 25 }, #good friday
32              3 => { map {$_=>1} 28 }, #easter monday
33              5 => { map {$_=>1} 23 }, #victoria day
34              8 => { map {$_=>1}  1 }, #First Monday of August Civic Holiday
35              9 => { map {$_=>1}  5 }, #labour day
36             10 => { map {$_=>1} 10 }, #thanksgiving
37           },
38   2017 => {  2 => { map {$_=>1} 20 }, #family day
39              4 => { map {$_=>1} 14 }, #good friday
40              4 => { map {$_=>1} 17 }, #easter monday
41              5 => { map {$_=>1} 22 }, #victoria day
42              8 => { map {$_=>1}  7 }, #First Monday of August Civic Holiday
43              9 => { map {$_=>1}  4 }, #labour day
44             10 => { map {$_=>1}  9 }, #thanksgiving
45           },
46   2018 => {  2 => { map {$_=>1} 19 }, #family day
47              3 => { map {$_=>1} 30 }, #good friday
48              4 => { map {$_=>1}  2 }, #easter monday
49              5 => { map {$_=>1} 21 }, #victoria day
50              8 => { map {$_=>1}  6 }, #First Monday of August Civic Holiday
51              9 => { map {$_=>1}  3 }, #labour day
52             10 => { map {$_=>1}  8 }, #thanksgiving
53           },
54 );
55
56 sub is_holiday {
57   my $dt = shift;
58   return 1 if exists( $holiday_yearly{$dt->month} )
59           and exists( $holiday_yearly{$dt->month}{$dt->day} );
60   return 1 if exists( $holiday{$dt->year} )
61           and exists( $holiday{$dt->year}{$dt->month} )
62           and exists( $holiday{$dt->year}{$dt->month}{$dt->day} );
63   return 0;
64 }
65
66 %export_info = (
67
68   init => sub {
69     my $conf = shift;
70     my $agentnum = shift;
71     my @config;
72     if ( $conf->exists('batch-spoolagent') ) {
73       @config = $conf->config('batchconfig-eft_canada', $agentnum);
74     } else {
75       @config = $conf->config('batchconfig-eft_canada');
76     }
77     # SFTP login, password, business and personal trans codes, delay time
78     ($business_trans_code) = $config[2];
79     ($personal_trans_code) = $config[3];
80
81     my ($process_date) = process_dates($conf, $agentnum);
82     $req_date = $process_date->strftime('%D');
83   },
84
85   delimiter => '', # avoid blank lines for header/footer
86
87   # EFT Upload Specification for .CSV Files, Rev. 2.0
88   # not a true CSV format--strings aren't quoted, so be careful
89   row => sub {
90     my ($cust_pay_batch, $pay_batch) = @_;
91     my @fields;
92     # company + empty or first + last
93     my $company = sprintf('%.64s', $cust_pay_batch->cust_main->company);
94     if ( $company ) {
95       push @fields, 'Business';
96       push @fields, $company, '';
97       $trans_code = $business_trans_code;
98     }
99     else {
100       push @fields, 'Personal';
101       push @fields, map { sprintf('%.64s', $_) } 
102         $cust_pay_batch->first, $cust_pay_batch->last;
103         $trans_code = $personal_trans_code;
104     }
105     my ($account, $aba) = split('@', $cust_pay_batch->payinfo);
106     my($bankno, $branch);
107     if ( $aba =~ /^0(\d{3})(\d{5})$/ ) { # standard format for Canadian bank ID
108       ($bankno, $branch) = ( $1, $2 );
109     } elsif ( $aba =~ /^(\d{5})\.(\d{3})$/ ) { #how we store branches
110       ($branch, $bankno) = ( $1, $2 );
111     } else {
112       die "invalid branch/routing number '$aba'\n";
113     }
114     push @fields, sprintf('%05s', $branch),
115                   sprintf('%03s', $bankno),
116                   $account,
117                   sprintf('%.02f', $cust_pay_batch->amount);
118     # DB = debit
119     push @fields, 'DB', $trans_code, $req_date;
120     push @fields, $cust_pay_batch->paybatchnum; # reference
121     # strip illegal characters that might occur in customer name
122     s/[,|']//g foreach @fields; # better substitution for these?
123     return join(',', @fields) . "\n";
124   },
125
126 );
127
128 sub download_note { # is a class method
129   my $class = shift;
130   my $pay_batch = shift;
131   my $conf = FS::Conf->new;
132   my $agentnum = $pay_batch->agentnum;
133   my ($process_date, $upload_date) = process_dates($conf, $agentnum);
134   my $date_format = $conf->config('date_format') || '%D';
135   my $days_until_upload = $upload_date->delta_days(DateTime->now);
136
137   my $note = '';
138   if ( $days_until_upload->days == 0 ) {
139     $note = 'Upload this file before 11:00 AM today'. 
140             ' (' . $upload_date->strftime($date_format) . '). ';
141   } elsif ( $days_until_upload->days == 1 ) {
142     $note = 'Upload this file before 11:00 AM tomorrow'. 
143             ' (' . $upload_date->strftime($date_format) . '). ';
144   } else {
145     $note = 'Upload this file before 11:00 AM on '.
146       $upload_date->strftime($date_format) . '. ';
147   }
148   $note .= 'Payments will be processed on '.
149     $process_date->strftime($date_format) . '.';
150
151   $note;
152 }
153
154 sub process_dates { # returns both process and upload dates
155   my ($conf, $agentnum) = @_;
156   my @config;
157   if ( $conf->exists('batch-spoolagent') ) {
158     @config = $conf->config('batchconfig-eft_canada', $agentnum);
159   } else {
160     @config = $conf->config('batchconfig-eft_canada');
161   }
162   
163   my $process_delay = $config[4] || 1;
164
165   my $ut = DateTime->now; # the latest time we assume the user
166                           # could upload the file
167   $ut->truncate(to => 'day')->set_hour(10); # is 10 AM on whatever day
168   if ( $ut < DateTime->now ) {
169     # then we would submit the file today but it's already too late
170     $ut->add(days => 1);
171   }
172   while (    $ut->day_of_week == 6 # Saturday
173           or $ut->day_of_week == 7 # Sunday
174           or is_holiday($ut)
175         )
176   {
177     $ut->add(days => 1);
178   }
179   # $ut is now the latest time that the user can upload the file.
180
181   # that time, plus the process delay, is the _earliest_ process date we can
182   # request. if that's on a weekend or holiday, the process date has to be
183   # later.
184
185   my $pt = $ut->clone();
186   $pt->add(days => $process_delay);
187   while (    $pt->day_of_week == 6
188           or $pt->day_of_week == 7
189           or is_holiday($pt)
190         )
191   {
192     $pt->add(days => 1);
193   }
194
195   ($pt, $ut);
196 }
197
198 1;