| blib/lib/Finance/Bank/ID/BPRKS.pm | |||
|---|---|---|---|
| Criterion | Covered | Total | % |
| statement | 74 | 201 | 36.8 |
| branch | 21 | 90 | 23.3 |
| condition | 2 | 14 | 14.2 |
| subroutine | 12 | 24 | 50.0 |
| pod | 5 | 6 | 83.3 |
| total | 114 | 335 | 34.0 |
| line | stmt | bran | cond | sub | pod | time | code | |
|---|---|---|---|---|---|---|---|---|
| 1 | package Finance::Bank::ID::BPRKS; | |||||||
| 2 | ||||||||
| 3 | our $DATE = '2015-08-17'; # DATE | |||||||
| 4 | our $VERSION = '0.04'; # VERSION | |||||||
| 5 | ||||||||
| 6 | 1 | 1 | 217714 | use 5.010001; | ||||
| 1 | 3 | |||||||
| 7 | 1 | 1 | 813 | use Moo; | ||||
| 1 | 30317 | |||||||
| 1 | 7 | |||||||
| 8 | 1 | 1 | 1512 | use DateTime; | ||||
| 1 | 3 | |||||||
| 1 | 22 | |||||||
| 9 | 1 | 1 | 1133 | use Log::Any::IfLOG '$log'; | ||||
| 1 | 11 | |||||||
| 1 | 5 | |||||||
| 10 | ||||||||
| 11 | 1 | 1 | 720 | use Parse::Number::ID qw(parse_number_id); | ||||
| 1 | 616 | |||||||
| 1 | 2307 | |||||||
| 12 | ||||||||
| 13 | extends 'Finance::Bank::ID::Base'; | |||||||
| 14 | ||||||||
| 15 | has _variant => (is => 'rw'); # 'individual' only, for now | |||||||
| 16 | ###bca | |||||||
| 17 | ###has skip_NEXT => (is => 'rw'); | |||||||
| 18 | ||||||||
| 19 | sub BUILD { | |||||||
| 20 | 1 | 1 | 0 | 4262 | my ($self, $args) = @_; | |||
| 21 | ||||||||
| 22 | 1 | 50 | 20 | $self->site("https://ib.bprks.co.id") unless $self->site; | ||||
| 23 | 1 | 50 | 27 | $self->https_host("ib.bprks.co.id") unless $self->https_host; | ||||
| 24 | } | |||||||
| 25 | ||||||||
| 26 | sub _req { | |||||||
| 27 | 0 | 0 | 0 | my ($self, @args) = @_; | ||||
| 28 | ||||||||
| 29 | # 2012-03-12 - KlikBCA server since a few week ago rejects TE request | |||||||
| 30 | # header, so we do not send them. | |||||||
| 31 | ###bca | |||||||
| 32 | ###local @LWP::Protocol::http::EXTRA_SOCK_OPTS = | |||||||
| 33 | ### @LWP::Protocol::http::EXTRA_SOCK_OPTS; | |||||||
| 34 | ###push(@LWP::Protocol::http::EXTRA_SOCK_OPTS, SendTE => 0); | |||||||
| 35 | #$log->tracef("EXTRA_SOCK_OPTS=%s", \@LWP::Protocol::http::EXTRA_SOCK_OPTS); | |||||||
| 36 | ||||||||
| 37 | 0 | 0 | $self->SUPER::_req(@args); | |||||
| 38 | } | |||||||
| 39 | ||||||||
| 40 | # XXX tmp, should be in an indo date utility module | |||||||
| 41 | sub _parse_mon { | |||||||
| 42 | 4 | 4 | 7 | my $self = shift; | ||||
| 43 | 4 | 33 | local $_ = lc(shift); | |||||
| 44 | 4 | 50 | 61 | if (/^(?:jan|januar[iy])$/) { | ||||
| 50 | ||||||||
| 50 | ||||||||
| 50 | ||||||||
| 100 | ||||||||
| 50 | ||||||||
| 0 | ||||||||
| 0 | ||||||||
| 0 | ||||||||
| 0 | ||||||||
| 0 | ||||||||
| 0 | ||||||||
| 45 | 0 | 0 | return 1; | |||||
| 46 | } elsif (/^(?:[fp]eb|[fp]ebruar[iy])$/) { | |||||||
| 47 | 0 | 0 | return 2; | |||||
| 48 | } elsif (/^(?:mar|mrt|maret|march)$/) { | |||||||
| 49 | 0 | 0 | return 3; | |||||
| 50 | } elsif (/^(?:apr|april)$/) { | |||||||
| 51 | 0 | 0 | return 4; | |||||
| 52 | } elsif (/^(?:mei|may)$/) { | |||||||
| 53 | 2 | 25 | return 5; | |||||
| 54 | } elsif (/^(?:jun|jun[eiy])$/) { | |||||||
| 55 | 2 | 13 | return 6; | |||||
| 56 | } elsif (/^(?:jul|jul[iy])$/) { | |||||||
| 57 | 0 | 0 | return 7; | |||||
| 58 | } elsif (/^(?:agu|aug|ags?t|august|agustus)$/) { | |||||||
| 59 | 0 | 0 | return 8; | |||||
| 60 | } elsif (/^(?:sep|september)$/) { | |||||||
| 61 | 0 | 0 | return 9; | |||||
| 62 | } elsif (/^(?:o[kc]t|o[ct]ober)$/) { | |||||||
| 63 | 0 | 0 | return 10; | |||||
| 64 | } elsif (/^(?:no[pv]|no[pv]ember)$/) { | |||||||
| 65 | 0 | 0 | return 11; | |||||
| 66 | } elsif (/^(?:de[sc]|de[sc]ember)$/) { | |||||||
| 67 | 0 | 0 | return 12; | |||||
| 68 | } else { | |||||||
| 69 | 0 | 0 | die "Can't parse month: $_"; | |||||
| 70 | #return 0; | |||||||
| 71 | } | |||||||
| 72 | } | |||||||
| 73 | ||||||||
| 74 | sub _parse_num { | |||||||
| 75 | 12 | 12 | 23 | my ($self, $s) = @_; | ||||
| 76 | 12 | 32 | my $neg = $s =~ s/^\((.+)\)$/$1/; | |||||
| 77 | 12 | 37 | my $n = parse_number_id(text => $s); | |||||
| 78 | 12 | 100 | 474 | $neg ? -$n : $n; | ||||
| 79 | } | |||||||
| 80 | ||||||||
| 81 | sub login { | |||||||
| 82 | 0 | 0 | 1 | 0 | die "Not yet implemented"; | |||
| 83 | 0 | 0 | my ($self) = @_; | |||||
| 84 | 0 | 0 | my $s = $self->site; | |||||
| 85 | ||||||||
| 86 | 0 | 0 | 0 | return 1 if $self->logged_in; | ||||
| 87 | 0 | 0 | 0 | die "400 Username not supplied" unless $self->username; | ||||
| 88 | 0 | 0 | 0 | die "400 Password not supplied" unless $self->password; | ||||
| 89 | ||||||||
| 90 | 0 | 0 | $self->logger->debug('Logging in ...'); | |||||
| 91 | 0 | 0 | $self->_req(get => [$s]); | |||||
| 92 | $self->_req(submit_form => [ | |||||||
| 93 | form_number => 1, | |||||||
| 94 | fields => {'value(user_id)'=>$self->username, | |||||||
| 95 | 'value(pswd)'=>$self->password, | |||||||
| 96 | }, | |||||||
| 97 | button => 'value(Submit)', | |||||||
| 98 | ], | |||||||
| 99 | sub { | |||||||
| 100 | 0 | 0 | 0 | my ($mech) = @_; | ||||
| 101 | 0 | 0 | 0 | $mech->content =~ /var err='(.+?)'/ and return $1; | ||||
| 102 | 0 | 0 | 0 | $mech->content =~ /=logout"/ and return; | ||||
| 103 | 0 | 0 | "unknown login result page"; | |||||
| 104 | } | |||||||
| 105 | 0 | 0 | ); | |||||
| 106 | 0 | 0 | $self->logged_in(1); | |||||
| 107 | 0 | 0 | $self->_req(get => ["$s/authentication.do?value(actions)=welcome"]); | |||||
| 108 | #$self->_req(get => ["$s/nav_bar_indo/menu_nav.htm"]); # failed? | |||||||
| 109 | } | |||||||
| 110 | ||||||||
| 111 | sub logout { | |||||||
| 112 | 0 | 0 | 1 | 0 | die "Not yet implemented"; | |||
| 113 | 0 | 0 | my ($self) = @_; | |||||
| 114 | ||||||||
| 115 | 0 | 0 | 0 | return 1 unless $self->logged_in; | ||||
| 116 | 0 | 0 | $self->logger->debug('Logging out ...'); | |||||
| 117 | 0 | 0 | $self->_req(get => [$self->site . "/authentication.do?value(actions)=logout"]); | |||||
| 118 | 0 | 0 | $self->logged_in(0); | |||||
| 119 | } | |||||||
| 120 | ||||||||
| 121 | sub _menu { | |||||||
| 122 | 0 | 0 | 0 | my ($self) = @_; | ||||
| 123 | 0 | 0 | my $s = $self->site; | |||||
| 124 | 0 | 0 | $self->_req(get => ["$s/nav_bar_indo/account_information_menu.htm"]); | |||||
| 125 | } | |||||||
| 126 | ||||||||
| 127 | sub list_cards { | |||||||
| 128 | 0 | 0 | 1 | 0 | die "Not yet implemented"; | |||
| 129 | 0 | 0 | my ($self) = @_; | |||||
| 130 | 0 | 0 | $self->login; | |||||
| 131 | 0 | 0 | $self->logger->info("Listing ATM cards"); | |||||
| 132 | 0 | 0 | map { $_->{account} } $self->_check_balances; | |||||
| 0 | 0 | |||||||
| 133 | } | |||||||
| 134 | ||||||||
| 135 | sub _check_balances { | |||||||
| 136 | 0 | 0 | 0 | my ($self) = @_; | ||||
| 137 | 0 | 0 | my $s = $self->site; | |||||
| 138 | ||||||||
| 139 | 0 | 0 | my $re = qr! | |||||
| 140 | ||||||||
| 141 | ]+>\s* ]+>\s*]+>\s*(\d+)\s*\s* \s* | \s*
|||||||
| 142 | ]+>\s* ]+>\s*]+>\s*([^<]*?)\s*\s* \s* | \s*
|||||||
| 143 | ]+>\s* ]+>\s*]+>\s*([A-Z]+)\s*\s* \s* | \s*
|||||||
| 144 | ]+>\s* ]+>\s*]+>\s*([0-9,.]+)\.(\d\d)\s*\s* \s* |
|||||||
| 145 | !x; | |||||||
| 146 | ||||||||
| 147 | 0 | 0 | $self->login; | |||||
| 148 | 0 | 0 | $self->_menu; | |||||
| 149 | $self->_req(post => ["$s/balanceinquiry.do"], | |||||||
| 150 | sub { | |||||||
| 151 | 0 | 0 | 0 | my ($mech) = @_; | ||||
| 152 | 0 | 0 | 0 | $mech->content =~ $re or | ||||
| 153 | return "can't find balances, maybe page layout changed?"; | |||||||
| 154 | 0 | 0 | ''; | |||||
| 155 | } | |||||||
| 156 | 0 | 0 | ); | |||||
| 157 | ||||||||
| 158 | 0 | 0 | my @res; | |||||
| 159 | 0 | 0 | my $content = $self->mech->content; | |||||
| 160 | 0 | 0 | while ($content =~ m/$re/og) { | |||||
| 161 | 0 | 0 | push @res, { account => $1, | |||||
| 162 | account_type => $2, | |||||||
| 163 | currency => $3, | |||||||
| 164 | balance => $self->_stripD($4) + 0.01*$5, | |||||||
| 165 | }; | |||||||
| 166 | } | |||||||
| 167 | 0 | 0 | @res; | |||||
| 168 | } | |||||||
| 169 | ||||||||
| 170 | sub check_balance { | |||||||
| 171 | 0 | 0 | 1 | 0 | die "Not yet implemented"; | |||
| 172 | 0 | 0 | my ($self, $account) = @_; | |||||
| 173 | 0 | 0 | my @bals = $self->_check_balances; | |||||
| 174 | 0 | 0 | 0 | return unless @bals; | ||||
| 175 | 0 | 0 | 0 | return $bals[0]{balance} if !$account; | ||||
| 176 | 0 | 0 | for (@bals) { | |||||
| 177 | 0 | 0 | 0 | return $_->{balance} if $_->{account} eq $account; | ||||
| 178 | } | |||||||
| 179 | 0 | 0 | return; | |||||
| 180 | } | |||||||
| 181 | ||||||||
| 182 | sub get_statement { | |||||||
| 183 | 0 | 0 | 1 | 0 | die "Not yet implemented"; | |||
| 184 | 0 | 0 | my ($self, %args) = @_; | |||||
| 185 | 0 | 0 | my $s = $self->site; | |||||
| 186 | 0 | 0 | my $max_days = 31; | |||||
| 187 | ||||||||
| 188 | 0 | 0 | $self->login; | |||||
| 189 | 0 | 0 | $self->_menu; | |||||
| 190 | $self->logger->info("Getting statement for ". | |||||||
| 191 | 0 | 0 | 0 | ($args{account} ? "account `$args{account}'" : "default account")." ..."); | ||||
| 192 | $self->_req(post => ["$s/accountstmt.do?value(actions)=acct_stmt"], | |||||||
| 193 | sub { | |||||||
| 194 | 0 | 0 | 0 | my ($mech) = @_; | ||||
| 195 | 0 | 0 | 0 | $mech->content =~ / | ||||
| 196 | return "no form found, maybe we got logged out?"; | |||||||
| 197 | 0 | 0 | ''; | |||||
| 198 | 0 | 0 | }); | |||||
| 199 | ||||||||
| 200 | 0 | 0 | my $form = $self->mech->form_number(1); | |||||
| 201 | ||||||||
| 202 | # in the site this is done by javascript onSubmit(), so we emulate it here | |||||||
| 203 | 0 | 0 | $form->action("$s/accountstmt.do?value(actions)=acctstmtview"); | |||||
| 204 | ||||||||
| 205 | # in the case of the current date being a saturday/sunday/holiday, end | |||||||
| 206 | # date will be forwarded 1 or more days from the current date by the site, | |||||||
| 207 | # so we need to know end date and optionally forward start date when needed, | |||||||
| 208 | # to avoid total number of days being > 31. | |||||||
| 209 | ||||||||
| 210 | 0 | 0 | my $today = DateTime->today; | |||||
| 211 | 0 | 0 | my $max_dt = DateTime->new(day => $form->value("value(endDt)"), | |||||
| 212 | month => $form->value("value(endMt)"), | |||||||
| 213 | year => $form->value("value(endYr)")); | |||||||
| 214 | 0 | 0 | my $cmp = DateTime->compare($today, $max_dt); | |||||
| 215 | 0 | 0 | my $delta_days = $cmp * $today->subtract_datetime($max_dt, $today)->days; | |||||
| 216 | 0 | 0 | 0 | if ($delta_days > 0) { | ||||
| 217 | 0 | 0 | $self->logger->warn("Something weird is going on, end date is being ". | |||||
| 218 | "set less than today's date by the site (". | |||||||
| 219 | $self->_fmtdate($max_dt)."). ". | |||||||
| 220 | "Please check your computer's date setting. ". | |||||||
| 221 | "Continuing anyway."); | |||||||
| 222 | } | |||||||
| 223 | 0 | 0 | my $min_dt = $max_dt->clone->subtract(days => ($max_days-1)); | |||||
| 224 | ||||||||
| 225 | 0 | 0 | 0 | my $end_dt = $args{end_date} || $max_dt; | ||||
| 226 | my $start_dt = $args{start_date} || | |||||||
| 227 | 0 | 0 | 0 | $end_dt->clone->subtract(days => (($args{days} || $max_days)-1)); | ||||
| 228 | 0 | 0 | 0 | if (DateTime->compare($start_dt, $min_dt) == -1) { | ||||
| 229 | 0 | 0 | $self->logger->warn("Start date ".$self->_fmtdate($start_dt)." is less than ". | |||||
| 230 | "minimum date ".$self->_fmtdate($min_dt).". Setting to ". | |||||||
| 231 | "minimum date instead."); | |||||||
| 232 | 0 | 0 | $start_dt = $min_dt; | |||||
| 233 | } | |||||||
| 234 | 0 | 0 | 0 | if (DateTime->compare($start_dt, $max_dt) == 1) { | ||||
| 235 | 0 | 0 | $self->logger->warn("Start date ".$self->_fmtdate($start_dt)." is greater than ". | |||||
| 236 | "maximum date ".$self->_fmtdate($max_dt).". Setting to ". | |||||||
| 237 | "maximum date instead."); | |||||||
| 238 | 0 | 0 | $start_dt = $max_dt; | |||||
| 239 | } | |||||||
| 240 | 0 | 0 | 0 | if (DateTime->compare($end_dt, $min_dt) == -1) { | ||||
| 241 | 0 | 0 | $self->logger->warn("End date ".$self->_fmtdate($end_dt)." is less than ". | |||||
| 242 | "minimum date ".$self->_fmtdate($min_dt).". Setting to ". | |||||||
| 243 | "minimum date instead."); | |||||||
| 244 | 0 | 0 | $end_dt = $min_dt; | |||||
| 245 | } | |||||||
| 246 | 0 | 0 | 0 | if (DateTime->compare($end_dt, $max_dt) == 1) { | ||||
| 247 | 0 | 0 | $self->logger->warn("End date ".$self->_fmtdate($end_dt)." is greater than ". | |||||
| 248 | "maximum date ".$self->_fmtdate($max_dt).". Setting to ". | |||||||
| 249 | "maximum date instead."); | |||||||
| 250 | 0 | 0 | $end_dt = $max_dt; | |||||
| 251 | } | |||||||
| 252 | 0 | 0 | 0 | if (DateTime->compare($start_dt, $end_dt) == 1) { | ||||
| 253 | 0 | 0 | $self->logger->warn("Start date ".$self->_fmtdate($start_dt)." is greater than ". | |||||
| 254 | "end date ".$self->_fmtdate($end_dt).". Setting to ". | |||||||
| 255 | "end date instead."); | |||||||
| 256 | 0 | 0 | $start_dt = $end_dt; | |||||
| 257 | } | |||||||
| 258 | ||||||||
| 259 | 0 | 0 | my $select = $form->find_input("value(D1)"); | |||||
| 260 | 0 | 0 | my $d1 = $select->value; | |||||
| 261 | 0 | 0 | 0 | if ($args{account}) { | ||||
| 262 | 0 | 0 | my @d1 = $select->possible_values; | |||||
| 263 | 0 | 0 | my @accts = $select->value_names; | |||||
| 264 | 0 | 0 | for (0..$#accts) { | |||||
| 265 | 0 | 0 | 0 | if ($args{account} eq $accts[$_]) { | ||||
| 266 | 0 | 0 | $d1 = $d1[$_]; | |||||
| 267 | 0 | 0 | last; | |||||
| 268 | } | |||||||
| 269 | } | |||||||
| 270 | } | |||||||
| 271 | ||||||||
| 272 | $self->_req(submit_form => [ | |||||||
| 273 | form_number => 1, | |||||||
| 274 | fields => { | |||||||
| 275 | "value(D1)" => $d1, | |||||||
| 276 | "value(startDt)" => $start_dt->day, | |||||||
| 277 | "value(startMt)" => $start_dt->month, | |||||||
| 278 | "value(startYr)" => $start_dt->year, | |||||||
| 279 | "value(endDt)" => $end_dt->day, | |||||||
| 280 | "value(endMt)" => $end_dt->month, | |||||||
| 281 | "value(endYr)" => $end_dt->year, | |||||||
| 282 | }, | |||||||
| 283 | ], | |||||||
| 284 | sub { | |||||||
| 285 | 0 | 0 | 0 | my ($mech) = @_; | ||||
| 286 | 0 | 0 | ''; # XXX check for error | |||||
| 287 | 0 | 0 | }); | |||||
| 288 | 0 | 0 | 0 | my $parse_opts = $args{parse_opts} // {}; | ||||
| 289 | 0 | 0 | my $resp = $self->parse_statement($self->mech->content, %$parse_opts); | |||||
| 290 | 0 | 0 | 0 | 0 | return if !$resp || $resp->[0] != 200; | |||
| 291 | 0 | 0 | $resp->[2]; | |||||
| 292 | } | |||||||
| 293 | ||||||||
| 294 | sub _ps_detect { | |||||||
| 295 | 2 | 2 | 26458 | my ($self, $page) = @_; | ||||
| 296 | 2 | 50 | 30 | unless ($page =~ />Detail Informasi Mutasi Rekening) { | ||||
| 297 | 0 | 0 | return "No BPR KS statement page signature found"; | |||||
| 298 | } | |||||||
| 299 | 2 | 15 | $self->_variant('individual'); | |||||
| 300 | 2 | 6 | ""; | |||||
| 301 | } | |||||||
| 302 | ||||||||
| 303 | sub _ps_get_metadata { | |||||||
| 304 | 2 | 2 | 15 | my ($self, $page, $stmt) = @_; | ||||
| 305 | ||||||||
| 306 | 2 | 50 | 95 | unless ($page =~ m! | \s*]*>(\d+) | !s) {|||
| 307 | 0 | 0 | return "can't get account number"; | |||||
| 308 | } | |||||||
| 309 | 2 | 14 | $stmt->{account} = $1; | |||||
| 310 | ||||||||
| 311 | 2 | 5 | my $adv1 = "probably the statement format changed, or input incomplete"; | |||||
| 312 | ||||||||
| 313 | 2 | 50 | 64 | unless ($page =~ m! | \s*\s*(? | !s) {
|||
| 314 | 0 | 0 | return "can't get statement period, $adv1"; | |||||
| 315 | } | |||||||
| 316 | 1 | 1 | 831 | $stmt->{start_date} = DateTime->new(day=>$+{d1}, month=>$self->_parse_mon($+{m1}), year=>$+{y1}); | ||||
| 1 | 470 | |||||||
| 1 | 689 | |||||||
| 2 | 20 | |||||||
| 317 | 2 | 675 | $stmt->{end_date} = DateTime->new(day=>$+{d2}, month=>$self->_parse_mon($+{m2}), year=>$+{y2}); | |||||
| 318 | ||||||||
| 319 | 2 | 50 | 464 | unless ($page =~ m! | \s*]*>(\w+) | !s) {|||
| 320 | 0 | 0 | return "can't get currency, $adv1"; | |||||
| 321 | } | |||||||
| 322 | 2 | 50 | 14 | $stmt->{currency} = ($1 eq 'Rp' ? 'IDR' : $1); | ||||
| 323 | ||||||||
| 324 | 2 | 50 | 52 | unless ($page =~ m! | \s*]*>([^<]+?)\s* | !s) {|||
| 325 | 0 | 0 | return "can't get account holder, $adv1"; | |||||
| 326 | } | |||||||
| 327 | 2 | 6 | $stmt->{account_holder} = $1; | |||||
| 328 | ||||||||
| 329 | # additional: Tipe: Tabungan | |||||||
| 330 | ||||||||
| 331 | 2 | 50 | 67 | unless ($page =~ m! | \s*]*>([^<]+) | !s) {|||
| 332 | 0 | 0 | return "can't get total credit, $adv1"; | |||||
| 333 | } | |||||||
| 334 | 2 | 10 | $stmt->{_total_credit_in_stmt} = $self->_parse_num($1); | |||||
| 335 | # no _num_credit_tx_in_stmt, pity cause it's required for proper checking | |||||||
| 336 | ||||||||
| 337 | 2 | 50 | 65 | unless ($page =~ m! | \s*]*>([^<]+) | !s) {|||
| 338 | 0 | 0 | return "can't get total credit, $adv1"; | |||||
| 339 | } | |||||||
| 340 | 2 | 7 | $stmt->{_total_debit_in_stmt} = -$self->_parse_num($1); | |||||
| 341 | # no _num_debit_tx_in_stmt, pity cause it's required for proper checking | |||||||
| 342 | 2 | 7 | ""; | |||||
| 343 | } | |||||||
| 344 | ||||||||
| 345 | sub _ps_get_transactions { | |||||||
| 346 | 2 | 2 | 13 | my ($self, $page, $stmt) = @_; | ||||
| 347 | ||||||||
| 348 | 2 | 4 | my @e; | |||||
| 349 | 2 | 112 | while ($page =~ m! | |||||
| 350 | ||||||||
| 351 | ]+>\s* (? | \s*
|||||||
| 352 | ]+>\s* (? | \s*
|||||||
| 353 | ]+>\s* (?[^<]+?) \s* | \s*|||||||
| 354 | ]+>\s* (? | \s*
|||||||
| 355 | ]+>\s* (? | \s*
|||||||
| 356 | ||||||||
| 357 | 4 | 158 | my %m = %+; | |||||
| 358 | 4 | 53 | push @e, \%m; | |||||
| 359 | } | |||||||
| 360 | ||||||||
| 361 | 2 | 4 | my @tx; | |||||
| 362 | my @skipped_tx; | |||||||
| 363 | 0 | 0 | my $last_date; | |||||
| 364 | 0 | 0 | my $seq; | |||||
| 365 | 2 | 2 | my $i = 0; | |||||
| 366 | 2 | 6 | for my $e (@e) { | |||||
| 367 | 4 | 6 | $i++; | |||||
| 368 | 4 | 6 | my $tx = {}; | |||||
| 369 | #$tx->{stmt_start_date} = $stmt->{start_date}; | |||||||
| 370 | ||||||||
| 371 | ### bca | |||||||
| 372 | ###if ($e->{date} =~ /NEXT/) { | |||||||
| 373 | ### $tx->{date} = $stmt->{end_date}; | |||||||
| 374 | ### $tx->{is_next} = 1; | |||||||
| 375 | ###} elsif ($e->{date} =~ /PEND/) { | |||||||
| 376 | ### $tx->{date} = $stmt->{end_date}; | |||||||
| 377 | ### $tx->{is_pending} = 1; | |||||||
| 378 | ###} else { | |||||||
| 379 | 4 | 16 | my ($day, $mon, $year) = split m!/!, $e->{date}; | |||||
| 380 | 4 | 17 | my $last_nonpend_date = DateTime->new( | |||||
| 381 | year => $year, | |||||||
| 382 | month => $mon, | |||||||
| 383 | day => $day); | |||||||
| 384 | 4 | 765 | $tx->{date} = $last_nonpend_date; | |||||
| 385 | ###$tx->{is_pending} = 0; | |||||||
| 386 | ###} | |||||||
| 387 | ||||||||
| 388 | 4 | 11 | $tx->{description} = $e->{desc}; | |||||
| 389 | ||||||||
| 390 | 4 | 10 | $tx->{amount} = $self->_parse_num($e->{amt}); | |||||
| 391 | 4 | 12 | $tx->{balance} = $self->_parse_num($e->{bal}); | |||||
| 392 | ||||||||
| 393 | ### bca | |||||||
| 394 | ###if ($tx->{is_next} && $self->skip_NEXT) { | |||||||
| 395 | ###} | |||||||
| 396 | ||||||||
| 397 | 4 | 100 | 66 | 22 | if (!$last_date || DateTime->compare($last_date, $tx->{date})) { | |||
| 398 | 2 | 2 | $seq = 1; | |||||
| 399 | 2 | 5 | $last_date = $tx->{date}; | |||||
| 400 | } else { | |||||||
| 401 | 2 | 273 | $seq++; | |||||
| 402 | } | |||||||
| 403 | 4 | 9 | $tx->{seq} = $seq; | |||||
| 404 | ||||||||
| 405 | ### bca | |||||||
| 406 | ###if ($self->_variant eq 'individual' && | |||||||
| 407 | ### $tx->{date}->dow =~ /6|7/ && | |||||||
| 408 | ### $tx->{description} !~ /^(BIAYA ADM|BUNGA|CR KOREKSI BUNGA|PAJAK BUNGA)$/) { | |||||||
| 409 | ### return "check failed in tx#$i: In KlikBCA Perorangan, all ". | |||||||
| 410 | ### "transactions must not be in Sat/Sun except for Interest and ". | |||||||
| 411 | ### "Admin Fee"; | |||||||
| 412 | ### # note: in Tahapan perorangan, BIAYA ADM is set on | |||||||
| 413 | ### # Fridays, but for Tapres (?) on last day of the month | |||||||
| 414 | ###} | |||||||
| 415 | ||||||||
| 416 | ###if ($self->_variant eq 'bisnis' && | |||||||
| 417 | ### $tx->{date}->dow =~ /6|7/ && | |||||||
| 418 | ### $tx->{description} !~ /^(BIAYA ADM|BUNGA|CR KOREKSI BUNGA|PAJAK BUNGA)$/) { | |||||||
| 419 | ### return "check failed in tx#$i: In KlikBCA Bisnis, all ". | |||||||
| 420 | ### "transactions must not be in Sat/Sun except for Interest and ". | |||||||
| 421 | ### "Admin Fee"; | |||||||
| 422 | ### # note: in KlikBCA bisnis, BIAYA ADM is set on the last day of the | |||||||
| 423 | ### # month, regardless of whether it's Sat/Sun or not | |||||||
| 424 | ###} | |||||||
| 425 | ||||||||
| 426 | ###if ($tx->{is_next} && $self->skip_NEXT) { | |||||||
| 427 | ### push @skipped_tx, $tx; | |||||||
| 428 | ### $seq--; | |||||||
| 429 | ###} else { | |||||||
| 430 | 4 | 13 | push @tx, $tx; | |||||
| 431 | ###} | |||||||
| 432 | } | |||||||
| 433 | 2 | 6 | $stmt->{transactions} = \@tx; | |||||
| 434 | 2 | 5 | $stmt->{skipped_transactions} = \@skipped_tx; | |||||
| 435 | 2 | 13 | ""; | |||||
| 436 | } | |||||||
| 437 | ||||||||
| 438 | 1; | |||||||
| 439 | # ABSTRACT: Check your BPR KS accounts from Perl | |||||||
| 440 | ||||||||
| 441 | __END__ |