| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package Finance::Bank::Postbank_de::Account; |
|
2
|
14
|
|
|
14
|
|
5258
|
use 5.006; |
|
|
14
|
|
|
|
|
50
|
|
|
3
|
14
|
|
|
14
|
|
71
|
use strict; |
|
|
14
|
|
|
|
|
33
|
|
|
|
14
|
|
|
|
|
289
|
|
|
4
|
14
|
|
|
14
|
|
64
|
use warnings; |
|
|
14
|
|
|
|
|
29
|
|
|
|
14
|
|
|
|
|
438
|
|
|
5
|
14
|
|
|
14
|
|
79
|
use Carp qw(croak); |
|
|
14
|
|
|
|
|
29
|
|
|
|
14
|
|
|
|
|
756
|
|
|
6
|
14
|
|
|
14
|
|
2479
|
use POSIX qw(strftime); |
|
|
14
|
|
|
|
|
31150
|
|
|
|
14
|
|
|
|
|
75
|
|
|
7
|
14
|
|
|
14
|
|
10572
|
use Moo 2; |
|
|
14
|
|
|
|
|
56641
|
|
|
|
14
|
|
|
|
|
85
|
|
|
8
|
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
our $VERSION = '0.57'; |
|
10
|
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
has [ |
|
12
|
|
|
|
|
|
|
'number', |
|
13
|
|
|
|
|
|
|
'balance', |
|
14
|
|
|
|
|
|
|
'balance_unavailable', |
|
15
|
|
|
|
|
|
|
'balance_prev', |
|
16
|
|
|
|
|
|
|
'transactions_future', |
|
17
|
|
|
|
|
|
|
'iban', |
|
18
|
|
|
|
|
|
|
'blz', |
|
19
|
|
|
|
|
|
|
'account_type', |
|
20
|
|
|
|
|
|
|
'name', |
|
21
|
|
|
|
|
|
|
] => ( is => 'rw' ); |
|
22
|
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
around BUILDARGS => sub { |
|
24
|
|
|
|
|
|
|
my ($orig, $class,%args) = @_; |
|
25
|
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
if( exists $args{ number } and exists $args{ kontonummer } |
|
27
|
|
|
|
|
|
|
and $args{ number } ne $args{ kontonummer } ) { |
|
28
|
|
|
|
|
|
|
croak "'kontonummer' is '$args{kontonummer}' and 'number' is '$args{ number }'"; |
|
29
|
|
|
|
|
|
|
}; |
|
30
|
|
|
|
|
|
|
my $num = delete $args{number} || delete $args{kontonummer}; |
|
31
|
|
|
|
|
|
|
$args{ number } = $num |
|
32
|
|
|
|
|
|
|
if defined $num; |
|
33
|
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
$orig->( $class, %args ); |
|
35
|
|
|
|
|
|
|
}; |
|
36
|
|
|
|
|
|
|
|
|
37
|
14
|
|
|
14
|
|
12589
|
{ no warnings 'once'; |
|
|
14
|
|
|
|
|
30
|
|
|
|
14
|
|
|
|
|
33743
|
|
|
38
|
|
|
|
|
|
|
*kontonummer = *number; |
|
39
|
|
|
|
|
|
|
} |
|
40
|
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
our %safety_check = ( |
|
42
|
|
|
|
|
|
|
name => 1, |
|
43
|
|
|
|
|
|
|
kontonummer => 1, |
|
44
|
|
|
|
|
|
|
); |
|
45
|
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
our %tags = ( |
|
47
|
|
|
|
|
|
|
#Girokonto => [qw(Name BLZ Kontonummer IBAN)], |
|
48
|
|
|
|
|
|
|
"gebuchte Ums\x{00E4}tze" => [qw(Name BLZ Kontonummer IBAN)], |
|
49
|
|
|
|
|
|
|
Tagesgeldkonto => [qw(Name BLZ Kontonummer IBAN)], |
|
50
|
|
|
|
|
|
|
Sparcard => [qw(Name BLZ Kontonummer IBAN)], |
|
51
|
|
|
|
|
|
|
Sparkonto => [qw(Name BLZ Kontonummer IBAN)], |
|
52
|
|
|
|
|
|
|
Kreditkarte => [qw(Name BLZ Kontonummer IBAN)], |
|
53
|
|
|
|
|
|
|
'' => [qw(Name BLZ Kontonummer IBAN)], |
|
54
|
|
|
|
|
|
|
); |
|
55
|
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
our %totals = ( |
|
57
|
|
|
|
|
|
|
"gebuchte Ums\x{00E4}tze" => [ |
|
58
|
|
|
|
|
|
|
[qr'^Aktueller Kontostand' => 'balance'], |
|
59
|
|
|
|
|
|
|
[qr'^Summe vorgemerkter Ums.tze' => 'transactions_future'], |
|
60
|
|
|
|
|
|
|
[qr'^Davon noch nicht verf.gbar' => 'balance_unavailable'], |
|
61
|
|
|
|
|
|
|
], |
|
62
|
|
|
|
|
|
|
Sparcard => [[qr'Aktueller Kontostand' => 'balance'],], |
|
63
|
|
|
|
|
|
|
Sparkonto => [[qr'Aktueller Kontostand' => 'balance'],], |
|
64
|
|
|
|
|
|
|
Tagesgeldkonto => [[qr'Aktueller Kontostand' => 'balance'],], |
|
65
|
|
|
|
|
|
|
"" => [ |
|
66
|
|
|
|
|
|
|
[qr'^Aktueller Kontostand' => 'balance'], |
|
67
|
|
|
|
|
|
|
[qr'^Summe der Ums.tze in den n.chsten 14 Tagen' => 'transactions_future'], |
|
68
|
|
|
|
|
|
|
[qr'^Davon noch nicht verf.gbar' => 'balance_unavailable'], |
|
69
|
|
|
|
|
|
|
], |
|
70
|
|
|
|
|
|
|
); |
|
71
|
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
our %columns = ( |
|
73
|
|
|
|
|
|
|
qr'Buchungsdatum' => 'tradedate', |
|
74
|
|
|
|
|
|
|
qr'Wertstellung' => 'valuedate', |
|
75
|
|
|
|
|
|
|
qr'Umsatzart' => 'type', |
|
76
|
|
|
|
|
|
|
qr'Buchungsdetails' => 'comment', |
|
77
|
|
|
|
|
|
|
qr'Auftraggeber' => 'sender', |
|
78
|
|
|
|
|
|
|
qr'Empf.nger' => 'receiver', |
|
79
|
|
|
|
|
|
|
qr"Betrag \((?:\x{20AC}|\x{80})\)" => 'amount', |
|
80
|
|
|
|
|
|
|
qr"Saldo \((?:\x{20AC}|\x{80})\)" => 'running_total', |
|
81
|
|
|
|
|
|
|
); |
|
82
|
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
sub parse_date { |
|
84
|
367
|
|
|
367
|
0
|
4480
|
my ($self,$date) = @_; |
|
85
|
367
|
100
|
|
|
|
1294
|
$date =~ /^(\d{2})\.(\d{2})\.(\d{4})$/ |
|
86
|
|
|
|
|
|
|
or die "Unknown date format '$date'. A date must be in the format 'DD.MM.YYYY'\n"; |
|
87
|
364
|
|
|
|
|
1339
|
$3.$2.$1; |
|
88
|
|
|
|
|
|
|
}; |
|
89
|
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
sub parse_amount { |
|
91
|
404
|
|
|
404
|
0
|
7091
|
my ($self,$amount) = @_; |
|
92
|
|
|
|
|
|
|
# '¿ 5.314,05' |
|
93
|
404
|
100
|
|
|
|
2036
|
die "String '$amount' does not look like a number" |
|
94
|
|
|
|
|
|
|
unless $amount =~ /^(-?)(?:\s*\x{20AC}\s*|\s*\x{80}\s*|\s*\x{A4}\s*)?([0-9]{1,3}(?:\.\d{3})*,\d{2})(?:\s*\x{20AC}|\s*\x{80})?$/; |
|
95
|
401
|
|
100
|
|
|
1509
|
$amount = ($1||'') . $2; |
|
96
|
401
|
|
|
|
|
821
|
$amount =~ tr/.//d; |
|
97
|
401
|
|
|
|
|
1281
|
$amount =~ s/,/./; |
|
98
|
401
|
|
|
|
|
1302
|
$amount; |
|
99
|
|
|
|
|
|
|
}; |
|
100
|
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
sub slurp_file { |
|
102
|
3
|
|
|
3
|
0
|
6
|
my ($self,$filename) = @_; |
|
103
|
3
|
|
|
|
|
13
|
local $/ = undef; |
|
104
|
3
|
50
|
|
|
|
154
|
open my $fh, "< $filename" |
|
105
|
|
|
|
|
|
|
or croak "Couldn't read from file '$filename' : $!"; |
|
106
|
3
|
|
|
|
|
51
|
binmode $fh, ':encoding(UTF-8)'; |
|
107
|
3
|
|
|
|
|
288
|
<$fh>; |
|
108
|
|
|
|
|
|
|
}; |
|
109
|
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
sub parse_statement { |
|
111
|
27
|
|
|
27
|
1
|
47813
|
my ($self,%args) = @_; |
|
112
|
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
# If $self is just a string, we want to make a new class out of us |
|
114
|
27
|
100
|
|
|
|
626
|
$self = $self->new |
|
115
|
|
|
|
|
|
|
unless ref $self; |
|
116
|
27
|
|
|
|
|
678
|
my $filename = $args{file}; |
|
117
|
27
|
|
|
|
|
74
|
my $raw_statement = $args{content}; |
|
118
|
27
|
100
|
|
|
|
125
|
if ($filename) { |
|
|
|
100
|
|
|
|
|
|
|
119
|
4
|
|
|
|
|
12
|
$raw_statement = $self->slurp_file($filename); |
|
120
|
|
|
|
|
|
|
} elsif (! defined $raw_statement) { |
|
121
|
|
|
|
|
|
|
croak "Need an account number if I have to retrieve the statement online" |
|
122
|
4
|
100
|
|
|
|
204
|
unless $args{number}; |
|
123
|
|
|
|
|
|
|
croak "Need a password if I have to retrieve the statement online" |
|
124
|
3
|
100
|
|
|
|
122
|
unless exists $args{password}; |
|
125
|
2
|
|
66
|
|
|
16
|
my $login = $args{login} || $args{number}; |
|
126
|
|
|
|
|
|
|
|
|
127
|
2
|
|
|
|
|
10
|
require Finance::Bank::Postbank_de; |
|
128
|
2
|
|
|
|
|
13
|
return Finance::Bank::Postbank_de->new( login => $login, password => $args{password}, past_days => $args{past_days} )->get_account_statement; |
|
129
|
|
|
|
|
|
|
}; |
|
130
|
|
|
|
|
|
|
|
|
131
|
22
|
100
|
|
|
|
342
|
croak "Don't know what to do with empty content" |
|
132
|
|
|
|
|
|
|
unless $raw_statement; |
|
133
|
|
|
|
|
|
|
|
|
134
|
21
|
|
|
|
|
814
|
my @lines = split /\r?\n/, $raw_statement; |
|
135
|
21
|
100
|
|
|
|
234
|
croak "No valid account statement: '$lines[0]'" |
|
136
|
|
|
|
|
|
|
unless $lines[0] =~ /^Umsatzauskunft;$/; |
|
137
|
20
|
|
|
|
|
76
|
shift @lines; |
|
138
|
|
|
|
|
|
|
|
|
139
|
20
|
|
|
|
|
56
|
my $account_type = ''; |
|
140
|
|
|
|
|
|
|
#my $account_type = $1; |
|
141
|
|
|
|
|
|
|
#if( ! exists $tags{ $account_type }) { |
|
142
|
|
|
|
|
|
|
# $account_type =~ s!([^\x00-\x7f])!sprintf '%08x', ord($1)!ge; |
|
143
|
|
|
|
|
|
|
# croak "Unknown account type '$account_type' (" . (join ",",keys %tags) . ")" |
|
144
|
|
|
|
|
|
|
# unless exists $tags{$account_type}; |
|
145
|
|
|
|
|
|
|
#}; |
|
146
|
|
|
|
|
|
|
#$self->account_type($account_type); |
|
147
|
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
# Name: PETRA PFIFFIG |
|
149
|
|
|
|
|
|
|
#for my $tag (@{ $tags{ $self->account_type }||[] }) { |
|
150
|
20
|
|
|
|
|
66
|
my $sep = ";"; |
|
151
|
20
|
100
|
|
|
|
96
|
if( $lines[0] =~ /([\t;])/) { |
|
152
|
19
|
|
|
|
|
62
|
$sep = $1; |
|
153
|
|
|
|
|
|
|
}; |
|
154
|
20
|
50
|
|
|
|
44
|
for my $tag (@{ $tags{ $account_type }||[] }) { |
|
|
20
|
|
|
|
|
119
|
|
|
155
|
73
|
100
|
|
|
|
2145
|
$lines[0] =~ /^\Q$tag\E$sep(.*?)$sep?$/ |
|
156
|
|
|
|
|
|
|
or croak "Field '$tag' not found in account statement ($lines[0])"; |
|
157
|
69
|
|
|
|
|
204
|
my $method = lc($tag); |
|
158
|
69
|
|
|
|
|
204
|
my $value = $1; |
|
159
|
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
# special check for special fields: |
|
161
|
|
|
|
|
|
|
croak "Wrong/mixed account $method: Got '$value', expected '" . $self->$method . "'" |
|
162
|
69
|
100
|
100
|
|
|
605
|
if (exists $safety_check{$method} and defined $self->$method and $self->$method ne $value); |
|
|
|
|
100
|
|
|
|
|
|
163
|
|
|
|
|
|
|
|
|
164
|
68
|
|
|
|
|
259
|
$self->$method($value); |
|
165
|
68
|
|
|
|
|
189
|
shift @lines; |
|
166
|
|
|
|
|
|
|
}; |
|
167
|
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
|
|
169
|
15
|
|
|
|
|
122
|
while ($lines[0] !~ /^\s*$/) { |
|
170
|
30
|
|
|
|
|
77
|
my $line = shift @lines; |
|
171
|
30
|
|
|
|
|
67
|
my ($method,$balance); |
|
172
|
30
|
50
|
|
|
|
61
|
for my $total (@{ $totals{ $account_type }||[] }) { |
|
|
30
|
|
|
|
|
138
|
|
|
173
|
90
|
|
|
|
|
265
|
my ($re,$possible_method) = @$total; |
|
174
|
90
|
100
|
|
|
|
3170
|
if ($line =~ /$re$sep\s*(?:(?:(\S+)\s*(?:\x{20AC}|\x{80}))|(null))$sep$/) { |
|
175
|
30
|
|
|
|
|
95
|
$method = $possible_method; |
|
176
|
30
|
|
33
|
|
|
131
|
$balance = $1 || $2; |
|
177
|
30
|
50
|
|
|
|
166
|
if ($balance =~ /^(-?[0-9.,]+)\s*$/) { |
|
|
|
0
|
|
|
|
|
|
|
178
|
30
|
|
|
|
|
145
|
$self->$method( ['????????',$self->parse_amount($balance)]); |
|
179
|
|
|
|
|
|
|
} elsif ('null' eq $balance) { |
|
180
|
0
|
|
|
|
|
0
|
$self->$method( ['????????',$self->parse_amount("0,00")]); |
|
181
|
|
|
|
|
|
|
} else { |
|
182
|
0
|
|
|
|
|
0
|
die "Invalid number '$balance' found for $method in '$line'"; |
|
183
|
|
|
|
|
|
|
}; |
|
184
|
|
|
|
|
|
|
}; |
|
185
|
|
|
|
|
|
|
}; |
|
186
|
30
|
50
|
|
|
|
210
|
if (! $method) { |
|
187
|
0
|
|
|
|
|
0
|
$account_type =~ s!([^\x00-\x7f])!sprintf '%08x', ord($1)!ge; |
|
|
0
|
|
|
|
|
0
|
|
|
188
|
0
|
|
|
|
|
0
|
$line =~ s!([^\x00-\x7f])!sprintf '%08x', ord($1)!ge; |
|
|
0
|
|
|
|
|
0
|
|
|
189
|
0
|
|
|
|
|
0
|
croak "No summary found in account '$account_type' statement ($line)"; |
|
190
|
|
|
|
|
|
|
}; |
|
191
|
|
|
|
|
|
|
}; |
|
192
|
|
|
|
|
|
|
|
|
193
|
15
|
50
|
|
|
|
126
|
$lines[0] =~ m!^\s*$! |
|
194
|
|
|
|
|
|
|
or croak "Expected an empty line after the account balances, got '$lines[0]'"; |
|
195
|
15
|
|
|
|
|
38
|
shift @lines; |
|
196
|
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
# Now skip over the transactions in the future |
|
198
|
15
|
|
|
|
|
82
|
while( $lines[0] !~ /^gebuchte Ums.tze/ ) { |
|
199
|
105
|
|
|
|
|
256
|
shift @lines; |
|
200
|
|
|
|
|
|
|
}; |
|
201
|
15
|
|
|
|
|
37
|
shift @lines; |
|
202
|
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
# Now parse the lines for each cashflow : |
|
204
|
15
|
50
|
|
|
|
156
|
$lines[0] =~ /^Buchungsdatum${sep}Wertstellung${sep}Umsatzart/ |
|
205
|
|
|
|
|
|
|
or croak "Couldn't find start of transactions ($lines[0])"; |
|
206
|
|
|
|
|
|
|
|
|
207
|
15
|
|
|
|
|
90
|
my (@fields); |
|
208
|
|
|
|
|
|
|
COLUMN: |
|
209
|
15
|
|
|
|
|
149
|
for my $col (split /$sep/, $lines[0]) { |
|
210
|
120
|
|
|
|
|
441
|
for my $target (keys %columns) { |
|
211
|
540
|
100
|
|
|
|
10655
|
if ($col =~ m!^["']?$target["']?$!) { |
|
212
|
120
|
|
|
|
|
467
|
push @fields, $columns{$target}; |
|
213
|
120
|
|
|
|
|
392
|
next COLUMN; |
|
214
|
|
|
|
|
|
|
}; |
|
215
|
|
|
|
|
|
|
}; |
|
216
|
0
|
|
|
|
|
0
|
die "Unknown column '$col' in '$lines[0]'"; |
|
217
|
|
|
|
|
|
|
}; |
|
218
|
15
|
|
|
|
|
60
|
shift @lines; |
|
219
|
|
|
|
|
|
|
|
|
220
|
15
|
|
|
|
|
140
|
my (%convert) = ( |
|
221
|
|
|
|
|
|
|
tradedate => \&parse_date, |
|
222
|
|
|
|
|
|
|
valuedate => \&parse_date, |
|
223
|
|
|
|
|
|
|
amount => \&parse_amount, |
|
224
|
|
|
|
|
|
|
running_total => \&parse_amount, |
|
225
|
|
|
|
|
|
|
); |
|
226
|
|
|
|
|
|
|
|
|
227
|
15
|
|
|
|
|
41
|
my @transactions; |
|
228
|
|
|
|
|
|
|
my $line; |
|
229
|
15
|
|
|
|
|
47
|
for $line (@lines) { |
|
230
|
180
|
50
|
|
|
|
590
|
next if $line =~ /^\s*$/; |
|
231
|
180
|
|
|
|
|
897
|
my (@row) = split /$sep/, $line; |
|
232
|
180
|
50
|
|
|
|
450
|
scalar @row == scalar @fields |
|
233
|
|
|
|
|
|
|
or die "Malformed cashflow ($line): Expected ".scalar(@fields)." entries, got ".scalar(@row); |
|
234
|
|
|
|
|
|
|
|
|
235
|
180
|
|
|
|
|
363
|
for (@row) { |
|
236
|
1440
|
50
|
|
|
|
3448
|
$_ = $1 |
|
237
|
|
|
|
|
|
|
if /^\s*["']\s*(.*?)\s*["']\s*$/; |
|
238
|
|
|
|
|
|
|
}; |
|
239
|
|
|
|
|
|
|
|
|
240
|
180
|
|
|
|
|
276
|
my (%rec); |
|
241
|
180
|
|
|
|
|
998
|
@rec{@fields} = @row; |
|
242
|
180
|
|
|
|
|
507
|
for (keys %convert) { |
|
243
|
720
|
|
|
|
|
1533
|
$rec{$_} = $convert{$_}->($self,$rec{$_}); |
|
244
|
|
|
|
|
|
|
}; |
|
245
|
|
|
|
|
|
|
|
|
246
|
180
|
|
|
|
|
591
|
push @transactions, \%rec; |
|
247
|
|
|
|
|
|
|
}; |
|
248
|
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
# Filter the transactions |
|
250
|
15
|
|
|
|
|
92
|
$self->{transactions} = \@transactions; |
|
251
|
|
|
|
|
|
|
|
|
252
|
15
|
|
|
|
|
225
|
$self |
|
253
|
|
|
|
|
|
|
}; |
|
254
|
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
sub transactions { |
|
256
|
84
|
|
|
84
|
1
|
108799
|
my ($self,%args) = @_; |
|
257
|
|
|
|
|
|
|
|
|
258
|
84
|
|
|
|
|
179
|
my ($start_date,$end_date); |
|
259
|
84
|
100
|
|
|
|
202
|
if (exists $args{on}) { |
|
260
|
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
croak "Options 'since'+'upto' and 'on' are incompatible" |
|
262
|
33
|
100
|
100
|
|
|
299
|
if (exists $args{since} and exists $args{upto}); |
|
263
|
|
|
|
|
|
|
croak "Options 'since' and 'on' are incompatible" |
|
264
|
32
|
100
|
|
|
|
152
|
if (exists $args{since}); |
|
265
|
|
|
|
|
|
|
croak "Options 'upto' and 'on' are incompatible" |
|
266
|
31
|
100
|
|
|
|
146
|
if (exists $args{upto}); |
|
267
|
|
|
|
|
|
|
$args{on} = strftime('%Y%m%d',localtime()) |
|
268
|
30
|
100
|
|
|
|
183
|
if ($args{on} eq 'today'); |
|
269
|
30
|
50
|
|
|
|
145
|
$args{on} =~ /^\d{8}$/ or croak "Argument {on => '$args{on}'} dosen't look like a date to me."; |
|
270
|
|
|
|
|
|
|
|
|
271
|
30
|
|
|
|
|
58
|
$start_date = $args{on} -1; |
|
272
|
30
|
|
|
|
|
52
|
$end_date = $args{on}; |
|
273
|
|
|
|
|
|
|
} else { |
|
274
|
51
|
|
100
|
|
|
151
|
$start_date = $args{since} || "00000000"; |
|
275
|
51
|
|
100
|
|
|
166
|
$end_date = $args{upto} || "99999999"; |
|
276
|
51
|
100
|
|
|
|
820
|
$start_date =~ /^\d{8}$/ or croak "Argument {since => '$start_date'} dosen't look like a date to me."; |
|
277
|
44
|
100
|
|
|
|
715
|
$end_date =~ /^\d{8}$/ or croak "Argument {upto => '$end_date'} dosen't look like a date to me."; |
|
278
|
37
|
100
|
|
|
|
276
|
$start_date < $end_date or croak "The 'since' argument must be less than the 'upto' argument"; |
|
279
|
|
|
|
|
|
|
}; |
|
280
|
|
|
|
|
|
|
|
|
281
|
|
|
|
|
|
|
# Filter the transactions |
|
282
|
65
|
100
|
|
|
|
95
|
grep { $_->{tradedate} > $start_date and $_->{tradedate} <= $end_date } @{$self->{transactions}}; |
|
|
780
|
|
|
|
|
2182
|
|
|
|
65
|
|
|
|
|
177
|
|
|
283
|
|
|
|
|
|
|
}; |
|
284
|
|
|
|
|
|
|
|
|
285
|
|
|
|
|
|
|
sub value_dates { |
|
286
|
1
|
|
|
1
|
1
|
7
|
my ($self) = @_; |
|
287
|
1
|
|
|
|
|
2
|
my %dates; |
|
288
|
1
|
|
|
|
|
3
|
$dates{$_->{valuedate}} = 1 for $self->transactions(); |
|
289
|
1
|
|
|
|
|
22
|
sort keys %dates; |
|
290
|
|
|
|
|
|
|
}; |
|
291
|
|
|
|
|
|
|
|
|
292
|
|
|
|
|
|
|
sub trade_dates { |
|
293
|
1
|
|
|
1
|
1
|
1319
|
my ($self) = @_; |
|
294
|
1
|
|
|
|
|
1
|
my %dates; |
|
295
|
1
|
|
|
|
|
3
|
$dates{$_->{tradedate}} = 1 for $self->transactions(); |
|
296
|
1
|
|
|
|
|
12
|
sort keys %dates; |
|
297
|
|
|
|
|
|
|
}; |
|
298
|
|
|
|
|
|
|
|
|
299
|
|
|
|
|
|
|
1; |
|
300
|
|
|
|
|
|
|
__END__ |