MT940 parse Feld 25 nach östereichischen Vorgaben
[kivitendo-erp.git] / SL / MT940.pm
1 package SL::MT940;
2
3 use strict;
4 use warnings;
5
6 use Data::Dumper;
7 use DateTime;
8 use Encode;
9 use File::Slurp qw(read_file);
10
11 sub _join_entries {
12   my ($parts, $from, $to, $separator) = @_;
13
14   $separator //= ' ';
15
16   return
17     join $separator,
18     grep { $_ }
19     map  { s{^\s+|\s+$}{}g; $_ }
20     grep { $_ }
21     map  { $parts->{$_} }
22     ($from..$to);
23 }
24
25 sub parse {
26   my ($class, $file_name) = @_;
27
28   my ($local_bank_code, $local_account_number, %transaction, @transactions, @lines);
29   my $line_number = 0;
30
31   my $store_transaction = sub {
32     if (%transaction) {
33       push @transactions, { %transaction };
34       %transaction = ();
35     }
36   };
37
38   foreach my $line (read_file($file_name)) {
39     chomp $line;
40     $line = Encode::decode('UTF-8', $line);
41     $line =~ s{\r+}{};
42     $line_number++;
43
44     if (@lines && ($line =~ m{^\%})) {
45       $lines[-1]->[0] .= substr($line, 1);
46
47     } else {
48       push @lines, [ $line, $line_number ];
49     }
50   }
51
52   foreach my $line (@lines) {
53     # AT MT940 has the format  :25://AT20151/00797453990/EUR
54     # DE MT940 has the format  :25:BLZ/Konto
55     # https://www.bankaustria.at/files/MBS_MT940_V5107.pdf
56     if ($line->[0] =~ m{^:25:(?://AT)?(\d+)/(\d+)}) {
57
58       $local_bank_code      = $1;
59       $local_account_number = $2;
60
61     } elsif ($line->[0] =~ m{^:61: (\d{2}) (\d{2}) (\d{2}) (\d{2}) (\d{2}) (C|D|RC|RD) (.) (\d+) (?:, (\d*))? N (.{3}) (.*)}x) {
62       #                       1       2       3       4       5       6                7   8          9         10     11
63       # :61:2008060806CR952,N051NONREF
64
65       $store_transaction->();
66
67       my $valuta_year      = $1 * 1 + 2000;
68       my $valuta_month     = $2;
69       my $valuta_day       = $3;
70       my $trans_month      = $4;
71       my $trans_day        = $5;
72       my $debit_credit     = $6;
73       my $currency         = $7;
74       my $amount1          = $8;
75       my $amount2          = $9 || 0;
76       my $transaction_code = $10;
77       my $reference        = $11;
78
79       my $valuta_date      = DateTime->new_local(year => $valuta_year, month => $valuta_month, day => $valuta_day);
80       my $trans_date       = DateTime->new_local(year => $valuta_year, month => $trans_month,  day => $trans_day);
81       my $diff             = $valuta_date->subtract_datetime($trans_date);
82       my $trans_year_diff  = $diff->months < 6           ?  0
83                            : $valuta_date  > $trans_date ?  1
84                            :                               -1;
85       $trans_date          = DateTime->new_local(year => $valuta_year + $trans_year_diff, month => $trans_month,  day => $trans_day);
86       my $sign             = ($debit_credit eq 'D') || ($debit_credit eq 'RC') ? -1 : 1;
87       $reference           =~ s{//.*}{};
88       $reference           = '' if $reference eq 'NONREF';
89
90       %transaction = (
91         line_number          => $line->[1],
92         currency             => $currency,
93         valutadate           => $valuta_date,
94         transdate            => $trans_date,
95         amount               => ($amount1 * 1 + ($amount2 / (10 ** length($amount2))))* $sign,
96         reference            => $reference,
97         transaction_code     => $transaction_code,
98         local_bank_code      => $local_bank_code,
99         local_account_number => $local_account_number,
100       );
101
102     } elsif (%transaction && ($line->[0] =~ m{^:86:})) {
103       if ($line->[0] =~ m{^:86:\d+\?(.+)}) {
104         # structured
105         my %parts = map { ((substr($_, 0, 2) // '0') * 1 => substr($_, 2)) } split m{\?}, $1;
106
107         $transaction{purpose}               = _join_entries(\%parts, 20, 29);
108         $transaction{remote_name}           = _join_entries(\%parts, 32, 33, '');
109         $transaction{remote_bank_code}      = $parts{30};
110         $transaction{remote_account_number} = $parts{31};
111
112       } else {
113         # unstructured
114         $transaction{purpose} = substr($line->[0], 5);
115       }
116
117       $store_transaction->();
118     }
119   }
120
121   $store_transaction->();
122
123   return @transactions;
124 }
125
126 1;