File Coverage

blib/lib/Business/NAB/AccountInformation/Account.pm
Criterion Covered Total %
statement 74 76 97.3
branch 11 14 78.5
condition 2 3 66.6
subroutine 13 13 100.0
pod 3 3 100.0
total 103 109 94.5


line stmt bran cond sub pod time code
1             package Business::NAB::AccountInformation::Account;
2             $Business::NAB::AccountInformation::Account::VERSION = '0.03';
3             =head1 NAME
4              
5             Business::NAB::AccountInformation::Account
6              
7             =head1 SYNOPSIS
8              
9             use Business::NAB::AccountInformation::Account;
10              
11             my $Account = Business::NAB::AccountInformation::Account->new(
12             commercial_account_number => $commercial_account_number,
13             currency_code => $currency_code,
14             transaction_code_values => $transaction_code_values,
15             control_total_a => $total_a,
16             control_total_b => $total_b,
17             );
18              
19             =head1 DESCRIPTION
20              
21             Class for parsing a NAB "Account Information File (NAI/BAI2)" account
22             identifier line (type C<03>).
23              
24             =cut
25              
26 3     3   1069476 use strict;
  3         11  
  3         142  
27 3     3   19 use warnings;
  3         7  
  3         273  
28 3     3   24 use feature qw/ signatures state /;
  3         6  
  3         511  
29 3     3   69 use Carp qw/ croak /;
  3         8  
  3         246  
30              
31 3     3   662 use Moose;
  3         550342  
  3         29  
32 3     3   23143 use Moose::Util::TypeConstraints;
  3         6  
  3         35  
33 3     3   7183 no warnings qw/ experimental::signatures /;
  3         9  
  3         156  
34              
35 3     3   1738 use Text::CSV_XS qw/ csv /;
  3         11671  
  3         217  
36 3         3170 use Business::NAB::Types qw/
37             add_max_string_attribute
38 3     3   461 /;
  3         10  
39              
40             =head1 ATTRIBUTES
41              
42             =over
43              
44             =item commercial_account_number (Str, max length 4096)
45              
46             =item currency_code (Str, max length 3)
47              
48             =item transaction_code_values (HashRef)
49              
50             =item control_total_a (Int)
51              
52             =item control_total_b (Int)
53              
54             =item number_of_records (Int)
55              
56             =item transactions (ArrayRef[Business::NAB::AccountInformation::Transaction])
57              
58             =back
59              
60             =cut
61              
62             foreach my $str_attr (
63             'commercial_account_number[4096]',
64             'currency_code[3]',
65             ) {
66             __PACKAGE__->add_max_string_attribute(
67             $str_attr,
68             is => 'ro',
69             required => 1,
70             );
71             }
72              
73             has 'transaction_code_values' => (
74             isa => 'HashRef',
75             is => 'ro',
76             required => 1,
77             );
78              
79             has [
80             qw/
81             control_total_a
82             control_total_b
83             number_of_records
84             /
85             ] => (
86             isa => 'Int',
87             is => 'rw',
88             );
89              
90             subtype "Transactions"
91             => as "ArrayRef[Business::NAB::AccountInformation::Transaction]";
92              
93             has 'transactions' => (
94             traits => [ 'Array' ],
95             is => 'rw',
96             isa => 'Transactions',
97             default => sub { [] },
98             handles => {
99             "add_transaction" => 'push',
100             },
101             );
102              
103             =head1 METHODS
104              
105             =head2 new_from_raw_record
106              
107             Returns a new instance of the class with attributes populated from
108             the result of parsing the passed line:
109              
110             my $Account = Business::NAB::AccountInformation::Account
111             ::Payments::DescriptiveRecord->new_from_raw_record( $line );
112              
113             =cut
114              
115 2     2 1 3046 sub new_from_raw_record ( $class, $line ) {
  2         8  
  2         5  
  2         5  
116              
117 2 50       13 my $aoa = csv( in => \$line )
118             or croak( Text::CSV->error_diag );
119              
120 2         1351 return $class->new_from_record( $aoa->[ 0 ]->@* );
121             }
122              
123             =head2 new_from_record
124              
125             Returns a new instance of the class with attributes populated from
126             the result of parsing the already parsed line:
127              
128             my $Account = Business::NAB::AccountInformation::Account
129             ::Payments::DescriptiveRecord->new_from_record( @record );
130              
131             =cut
132              
133 10     10 1 21 sub new_from_record ( $class, @record ) {
  10         17  
  10         89  
  10         15  
134              
135             my (
136 10         102 $record_type,
137             $commercial_account_number,
138             $currency_code,
139             @transaction_code_values,
140             ) = @record;
141              
142 10 100       23 if ( $record_type ne '03' ) {
143 1         24 croak( "unsupported record type ($record_type)" );
144             }
145              
146 9         14 my $transaction_code_values = {};
147 9         25 my %account_summary_codes = $class->_account_summary_codes;
148              
149             # the record fields are different depending on if the file
150             # is the NAI or BAI2 type - the *easiest* way to deal with
151             # this is to just strip out the empty fields, as even though
152             # they are present for BAI2 the spec says they are "Empty"
153 9         29 @transaction_code_values = grep { $_ ne "" } @transaction_code_values;
  378         562  
154              
155 9         35 while ( my @tc_pair = splice( @transaction_code_values, 0, 2 ) ) {
156 126         158 my ( $transaction_code, $amount_or_count ) = @tc_pair;
157              
158             # amounts can use an optional trailing sign, to indicate
159             # a credit/debit so we need to take that into account
160 126         158 $amount_or_count =~ s/(\D)//g;
161              
162 126 100 66     226 if ( $1 && $1 =~ /-/ ) {
163 1         3 $amount_or_count *= -1;
164             }
165              
166 126         306 $transaction_code_values->{ $transaction_code }
167             = $amount_or_count;
168             }
169              
170 9         347 return $class->new(
171             commercial_account_number => $commercial_account_number,
172             currency_code => $currency_code,
173             transaction_code_values => $transaction_code_values,
174             );
175             }
176              
177             =head2 validate_totals
178              
179             Checks if the control_total_a and control_total_b values match the
180             expected totals of the contained transaction items and transaction
181             code values
182              
183             $Account->validate_totals( my $is_bai2 = 0 );
184              
185             Will throw an exception if any total doesn't match the expected value.
186              
187             Takes an optional boolean param to stipulate if the file type is BAI2
188             (defaults to false).
189              
190             =cut
191              
192 13     13 1 24 sub validate_totals ( $self, $is_bai2 = 0 ) {
  13         16  
  13         19  
  13         20  
193              
194 13         23 my ( $trans_total, $excl_tax_interest ) = ( 0, 0 );
195              
196 13         405 foreach my $Transaction ( $self->transactions->@* ) {
197 29         761 $trans_total += $Transaction->amount_minor_units;
198 29         728 $excl_tax_interest += $Transaction->amount_minor_units;
199             }
200              
201 13         377 my $tc_values = $self->transaction_code_values;
202 13         99 TC: foreach my $transaction_code ( sort keys $tc_values->%* ) {
203 182         237 $trans_total += int( $tc_values->{ $transaction_code } );
204              
205             # control_total_b excludes tax and interest
206 182 100       202 next TC if grep { $_ eq $transaction_code } 965 .. 969;
  910         1277  
207 117         164 $excl_tax_interest += int( $tc_values->{ $transaction_code } );
208             }
209              
210             croak(
211 13 50       386 "calculated sum ($trans_total) != control_total_a "
212 0         0 . "(@{[$self->control_total_a]})"
213             ) if $trans_total != $self->control_total_a;
214              
215 13 100       27 if ( !$is_bai2 ) {
216 6 50       170 croak(
217             "calculated sum ($excl_tax_interest) != control_total_b "
218 0         0 . "(@{[$self->control_total_b]})"
219             ) if $excl_tax_interest != $self->control_total_b;
220             }
221              
222 13         37 return 1;
223             }
224              
225 9     9   13 sub _account_summary_codes ( $self ) {
  9         11  
  9         12  
226              
227 9         47 state %account_summary_codes = (
228             '001' => 'Customer number',
229             '003' => 'Number of segments for the account',
230             '010' => 'Opening Balance',
231             '015' => 'Closing balance',
232             '100' => 'Total credits',
233             '102' => 'Number of credit transactions',
234             '400' => 'Total debits',
235             '402' => 'Number of debit transactions',
236             '500' => 'Accrued (unposted) credit interest',
237             '501' => 'Accrued (unposted) debit interest',
238             '502' => 'Account limit',
239             '503' => 'Available limit',
240             '965' => 'Effective Debit interest rate',
241             '966' => 'Effective Credit interest rate',
242             '967' => 'Accrued State Government Duty',
243             '968' => 'Accrued Government Credit Tax',
244             '969' => 'Accrued Government Debit Tax',
245             );
246              
247 9         97 return %account_summary_codes;
248             }
249              
250             =head1 SEE ALSO
251              
252             L<Business::NAB::Types>
253              
254             L<Business::NAB::AccountInformation::Transaction>
255              
256             =cut
257              
258             __PACKAGE__->meta->make_immutable;