1 package FS::pay_batch::eft_canada;
4 use vars qw(@ISA %import_info %export_info $name);
5 use FS::Record 'qsearch';
7 use FS::cust_pay_batch;
15 %import_info = ( filetype => 'NONE' ); # see FS/bin/freeside-eftca-download
17 my ($business_trans_code, $personal_trans_code, $trans_code);
18 my $req_date; # requested process date, in %D format
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 11 => { map {$_=>1} 11 }, #remembrance day
25 12 => { map {$_=>1} 25 }, #christmas
26 12 => { map {$_=>1} 26 }, #boxing day
29 2016 => { 2 => { map {$_=>1} 15 }, #family day
30 3 => { map {$_=>1} 25 }, #good friday
31 3 => { map {$_=>1} 28 }, #easter monday
32 5 => { map {$_=>1} 23 }, #victoria day
33 7 => { map {$_=>1} 1 }, #canada 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
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 7 => { map {$_=>1} 1 }, #canada day
43 8 => { map {$_=>1} 7 }, #First Monday of August Civic Holiday
44 9 => { map {$_=>1} 4 }, #labour day
45 10 => { map {$_=>1} 9 }, #thanksgiving
47 2018 => { 2 => { map {$_=>1} 19 }, #family day
48 3 => { map {$_=>1} 30 }, #good friday
49 4 => { map {$_=>1} 2 }, #easter monday
50 5 => { map {$_=>1} 21 }, #victoria day
51 8 => { map {$_=>1} 6 }, #First Monday of August Civic Holiday
52 9 => { map {$_=>1} 3 }, #labour day
53 10 => { map {$_=>1} 8 }, #thanksgiving
55 2019 => { 2 => { map {$_=>1} 18 }, #family day
56 4 => { map {$_=>1} 19 }, #good friday
57 4 => { map {$_=>1} 22 }, #easter monday
58 5 => { map {$_=>1} 20 }, #victoria day
59 8 => { map {$_=>1} 5 }, #First Monday of August Civic Holiday
60 9 => { map {$_=>1} 2 }, #labour day
61 10 => { map {$_=>1} 14 }, #thanksgiving
67 return 1 if exists( $holiday_yearly{$dt->month} )
68 and exists( $holiday_yearly{$dt->month}{$dt->day} );
69 return 1 if exists( $holiday{$dt->year} )
70 and exists( $holiday{$dt->year}{$dt->month} )
71 and exists( $holiday{$dt->year}{$dt->month}{$dt->day} );
81 if ( $conf->exists('batch-spoolagent') ) {
82 @config = $conf->config('batchconfig-eft_canada', $agentnum);
84 @config = $conf->config('batchconfig-eft_canada');
86 # SFTP login, password, business and personal trans codes, delay time
87 ($business_trans_code) = $config[2];
88 ($personal_trans_code) = $config[3];
90 my ($process_date) = process_dates($conf, $agentnum);
91 $req_date = $process_date->strftime('%D');
94 delimiter => '', # avoid blank lines for header/footer
96 # EFT Upload Specification for .CSV Files, Rev. 2.0
97 # not a true CSV format--strings aren't quoted, so be careful
99 my ($cust_pay_batch, $pay_batch) = @_;
101 # company + empty or first + last
102 my $company = sprintf('%.64s', $cust_pay_batch->cust_main->company);
104 push @fields, 'Business';
105 push @fields, $company, '';
106 $trans_code = $business_trans_code;
109 push @fields, 'Personal';
110 push @fields, map { sprintf('%.64s', $_) }
111 $cust_pay_batch->first, $cust_pay_batch->last;
112 $trans_code = $personal_trans_code;
114 my ($account, $aba) = split('@', $cust_pay_batch->payinfo);
115 my($bankno, $branch);
116 if ( $aba =~ /^0(\d{3})(\d{5})$/ ) { # standard format for Canadian bank ID
117 ($bankno, $branch) = ( $1, $2 );
118 } elsif ( $aba =~ /^(\d{5})\.(\d{3})$/ ) { #how we store branches
119 ($branch, $bankno) = ( $1, $2 );
121 die "invalid branch/routing number '$aba'\n";
123 push @fields, sprintf('%05s', $branch),
124 sprintf('%03s', $bankno),
126 sprintf('%.02f', $cust_pay_batch->amount);
128 push @fields, 'DB', $trans_code, $req_date;
129 push @fields, $cust_pay_batch->paybatchnum; # reference
130 # strip illegal characters that might occur in customer name
131 s/[,|']//g foreach @fields; # better substitution for these?
132 return join(',', @fields) . "\n";
137 sub download_note { # is a class method
139 my $pay_batch = shift;
140 my $conf = FS::Conf->new;
141 my $agentnum = $pay_batch->agentnum;
142 my ($process_date, $upload_date) = process_dates($conf, $agentnum);
143 my $date_format = $conf->config('date_format') || '%D';
144 my $days_until_upload = $upload_date->delta_days(DateTime->now);
147 if ( $days_until_upload->days == 0 ) {
148 $note = 'Upload this file before 11:00 AM today'.
149 ' (' . $upload_date->strftime($date_format) . '). ';
150 } elsif ( $days_until_upload->days == 1 ) {
151 $note = 'Upload this file before 11:00 AM tomorrow'.
152 ' (' . $upload_date->strftime($date_format) . '). ';
154 $note = 'Upload this file before 11:00 AM on '.
155 $upload_date->strftime($date_format) . '. ';
157 $note .= 'Payments will be processed on '.
158 $process_date->strftime($date_format) . '.';
163 sub process_dates { # returns both process and upload dates
164 my ($conf, $agentnum) = @_;
166 if ( $conf->exists('batch-spoolagent') ) {
167 @config = $conf->config('batchconfig-eft_canada', $agentnum);
169 @config = $conf->config('batchconfig-eft_canada');
172 my $process_delay = $config[4] || 1;
174 my $ut = DateTime->now; # the latest time we assume the user
175 # could upload the file
176 $ut->truncate(to => 'day')->set_hour(10); # is 10 AM on whatever day
177 if ( $ut < DateTime->now ) {
178 # then we would submit the file today but it's already too late
181 while ( $ut->day_of_week == 6 # Saturday
182 or $ut->day_of_week == 7 # Sunday
188 # $ut is now the latest time that the user can upload the file.
190 # that time, plus the process delay, is the _earliest_ process date we can
191 # request. if that's on a weekend or holiday, the process date has to be
194 my $pt = $ut->clone();
195 $pt->add(days => $process_delay);
196 while ( $pt->day_of_week == 6
197 or $pt->day_of_week == 7