|  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__  |