]> wagnertech.de Git - mfinanz.git/blob - SL/Controller/CsvImport/BankTransaction.pm
Merge branch 'master' of http://wagnertech.de/git/mfinanz
[mfinanz.git] / SL / Controller / CsvImport / BankTransaction.pm
1 package SL::Controller::CsvImport::BankTransaction;
2
3 use strict;
4
5 use SL::Helper::Csv;
6 use SL::Controller::CsvImport::Helper::Consistency;
7 use SL::DB::BankTransaction;
8
9 use Data::Dumper;
10
11 use parent qw(SL::Controller::CsvImport::Base);
12
13 use Rose::Object::MakeMethods::Generic
14 (
15  'scalar --get_set_init' => [ qw(bank_accounts_by) ],
16 );
17
18 sub set_profile_defaults {
19   my ($self) = @_;
20
21   $self->controller->profile->_set_defaults(
22                        charset       => 'UTF8',  # override charset from defaults
23                        update_policy => 'skip',
24                       );
25 };
26
27 sub init_class {
28   my ($self) = @_;
29   $self->class('SL::DB::BankTransaction');
30 }
31
32 sub init_bank_accounts_by {
33   my ($self) = @_;
34
35   return { map { my $col = $_; ( $col => { map { ( $_->$col => $_ ) } @{ $self->all_bank_accounts } } ) } qw(id account_number iban) };
36 }
37
38 sub check_objects {
39   my ($self) = @_;
40
41   $self->controller->track_progress(phase => 'building data', progress => 0);
42   my $update_policy  = $self->controller->profile->get('update_policy') || 'skip';
43
44   my $i = 0;
45   my $num_data = scalar @{ $self->controller->data };
46   foreach my $entry (@{ $self->controller->data }) {
47     $self->controller->track_progress(progress => $i/$num_data * 100) if $i % 100 == 0;
48
49     $self->check_bank_account($entry);
50     $self->check_currency($entry, take_default => 1);
51     $self->join_purposes($entry);
52     $self->join_remote_names($entry);
53     $self->extract_end_to_end_id($entry);
54     $self->check_existing($entry) unless @{ $entry->{errors} };
55   } continue {
56     $i++;
57   }
58
59   $self->add_info_columns({ header => $::locale->text('Bank account'), method => 'local_bank_name' });
60   $self->add_raw_data_columns("currency", "currency_id") if grep { /^currency(?:_id)?$/ } @{ $self->csv->header };
61   $self->add_info_columns({ header => $::locale->text('End to end ID'), method => 'end_to_end_id' });
62 }
63
64 sub check_existing {
65   my ($self, $entry) = @_;
66
67   my $object = $entry->{object};
68
69   # for each imported entry (line) we make a database call to find existing entries
70   # we don't use the init_by hash because we have to check several fields
71   # this means that we can't detect duplicates in the import file
72
73   if ( $object->amount ) {
74     # check for same
75     # * purpose
76     # * transdate
77     # * remote_account_number  (may be empty for records of our own bank)
78     # * amount
79     # * local_bank_account_id (case flatrate bank charges for two accounts in one bank: same purpose, transdate, remote_account_number(empty), amount. Just different local_bank_account_id)
80     my $num;
81
82     my @conditions;
83
84     if ($object->end_to_end_id && $::instance_conf->get_check_bt_duplicates_endtoend) {
85       push @conditions, ( end_to_end_id => $object->end_to_end_id );
86     } else {
87       push @conditions, ( purpose => $object->purpose );
88     }
89
90     if ( $num = SL::DB::Manager::BankTransaction->get_all_count(query =>[ remote_account_number => $object->remote_account_number, transdate => $object->transdate, amount => $object->amount, local_bank_account_id => $object->local_bank_account_id, @conditions] ) ) {
91       push(@{$entry->{errors}}, $::locale->text('Skipping due to existing bank transaction in database'));
92     };
93   } else {
94       push(@{$entry->{errors}}, $::locale->text('Skipping because transfer amount is empty.'));
95   };
96 }
97
98 sub _displayable_columns {
99  (
100    { name => 'local_bank_code',       description => $::locale->text('Own bank code') },
101    { name => 'local_account_number',  description => $::locale->text('Own bank account number or IBAN') },
102    { name => 'local_bank_account_id', description => $::locale->text('ID of own bank account') },
103    { name => 'remote_bank_code',      description => $::locale->text('Bank code of the goal/source') },
104    { name => 'remote_account_number', description => $::locale->text('Account number of the goal/source') },
105    { name => 'transdate',             description => $::locale->text('Transdate') },
106    { name => 'valutadate',            description => $::locale->text('Valutadate') },
107    { name => 'amount',                description => $::locale->text('Amount') },
108    { name => 'currency',              description => $::locale->text('Currency') },
109    { name => 'currency_id',           description => $::locale->text('Currency (database ID)')          },
110    { name => 'remote_name',           description => $::locale->text('Name of the goal/source (if field names remote_name and remote_name_1 exist they will be combined into field "remote_name")') },
111    { name => 'remote_name_1',          description => $::locale->text('Name of the goal/source (if field names remote_name and remote_name_1 exist they will be combined into field "remote_name")') },
112    { name => 'purpose',               description => $::locale->text('Purpose (if field names purpose, purpose1, purpose2 ... exist they will all combined into the field "purpose")') },
113    { name => 'purpose1',              description => $::locale->text('Purpose (if field names purpose, purpose1, purpose2 ... exist they will all combined into the field "purpose")') },
114    { name => 'purpose2',              description => $::locale->text('Purpose (if field names purpose, purpose1, purpose2 ... exist they will all combined into the field "purpose")') },
115    { name => 'purpose3',              description => $::locale->text('Purpose (if field names purpose, purpose1, purpose2 ... exist they will all combined into the field "purpose")') },
116    { name => 'purpose4',              description => $::locale->text('Purpose (if field names purpose, purpose1, purpose2 ... exist they will all combined into the field "purpose")') },
117    { name => 'purpose5',              description => $::locale->text('Purpose (if field names purpose, purpose1, purpose2 ... exist they will all combined into the field "purpose")') },
118    { name => 'purpose6',              description => $::locale->text('Purpose (if field names purpose, purpose1, purpose2 ... exist they will all combined into the field "purpose")') },
119    { name => 'purpose7',              description => $::locale->text('Purpose (if field names purpose, purpose1, purpose2 ... exist they will all combined into the field "purpose")') },
120    { name => 'purpose8',              description => $::locale->text('Purpose (if field names purpose, purpose1, purpose2 ... exist they will all combined into the field "purpose")') },
121    { name => 'purpose9',              description => $::locale->text('Purpose (if field names purpose, purpose1, purpose2 ... exist they will all combined into the field "purpose")') },
122    { name => 'purpose10',             description => $::locale->text('Purpose (if field names purpose, purpose1, purpose2 ... exist they will all combined into the field "purpose")') },
123    { name => 'purpose11',             description => $::locale->text('Purpose (if field names purpose, purpose1, purpose2 ... exist they will all combined into the field "purpose")') },
124    { name => 'purpose12',             description => $::locale->text('Purpose (if field names purpose, purpose1, purpose2 ... exist they will all combined into the field "purpose")') },
125    { name => 'purpose13',             description => $::locale->text('Purpose (if field names purpose, purpose1, purpose2 ... exist they will all combined into the field "purpose")') },
126    { name => 'qr_reference',          description => $::locale->text('QR reference') }
127  );
128 }
129
130 sub setup_displayable_columns {
131   my ($self) = @_;
132
133   $self->SUPER::setup_displayable_columns;
134
135   $self->add_displayable_columns($self->_displayable_columns);
136 }
137
138 sub check_bank_account {
139   my ($self, $entry) = @_;
140
141   my $object = $entry->{object};
142
143   # import via id: check whether or not local_bank_account ID exists and is valid.
144   if ($object->local_bank_account_id && !$self->bank_accounts_by->{id}->{ $object->local_bank_account_id }) {
145     push @{ $entry->{errors} }, $::locale->text('Error: unknown local bank account id');
146     return 0;
147   }
148
149   # Check whether or not local_bank_account ID, local_account_number and local_bank_code are consistent.
150   if ($object->local_bank_account_id && $entry->{raw_data}->{local_account_number}) {
151     my $bank_account = $self->bank_accounts_by->{id}->{ $object->local_bank_account_id };
152     if ($bank_account->account_number ne $entry->{raw_data}->{local_account_number}) {
153       push @{ $entry->{errors} }, $::locale->text('Error: local bank account id doesn\'t match local bank account number');
154       return 0;
155     }
156     if ($entry->{raw_data}->{local_bank_code} && $entry->{raw_data}->{local_bank_code} ne $bank_account->bank_code) {
157       push @{ $entry->{errors} }, $::locale->text('Error: local bank account id doesn\'t match local bank code');
158       return 0;
159     }
160
161   }
162
163   # Map account information to ID via local_account_number if no local_bank_account_id was given
164   # local_account_number checks for match of account number or IBAN
165   if (!$object->local_bank_account_id && $entry->{raw_data}->{local_account_number}) {
166     my $bank_account = $self->bank_accounts_by->{account_number}->{ $entry->{raw_data}->{local_account_number} };
167     if (!$bank_account) {
168        $bank_account = $self->bank_accounts_by->{iban}->{ $entry->{raw_data}->{local_account_number} };
169     };
170     if (!$bank_account) {
171       push @{ $entry->{errors} }, $::locale->text('Error: unknown local bank account') . ": " . $entry->{raw_data}->{local_account_number};
172       return 0;
173     }
174     if ($entry->{raw_data}->{local_bank_code} && $entry->{raw_data}->{local_bank_code} ne $bank_account->bank_code) {
175       push @{ $entry->{errors} }, $::locale->text('Error: Found local bank account number but local bank code doesn\'t match') . ": " . $entry->{raw_data}->{local_bank_code};
176       return 0;
177     }
178
179     $object->local_bank_account_id($bank_account->id);
180     $entry->{info_data}->{local_bank_name} = $bank_account->name;
181   }
182
183   # Check if local bank account is marked for bank import
184   if ($object->local_bank_account_id && !$self->bank_accounts_by->{id}->{ $object->local_bank_account_id }->use_with_bank_import) {
185     push @{ $entry->{errors} }, $::locale->text('Error: local bank account is not marked for bank import, check settings under System -> Bank Accounts');
186     return 0;
187   }
188
189   return $object->local_bank_account_id ? 1 : 0;
190 }
191
192 sub join_purposes {
193   my ($self, $entry) = @_;
194
195   my $object = $entry->{object};
196
197   my $purpose =
198     join ' ',
199     grep { ($_ // '') !~ m{^ *$} }
200     map  { $entry->{raw_data}->{"purpose$_"} }
201     ('', 1..13);
202
203   $object->purpose($purpose);
204
205 }
206
207 sub join_remote_names {
208   my ($self, $entry) = @_;
209
210   my $object = $entry->{object};
211
212   my $remote_name = join(' ', $entry->{raw_data}->{remote_name},
213                              $entry->{raw_data}->{remote_name_1} );
214   $object->remote_name($remote_name);
215 }
216
217 sub extract_end_to_end_id {
218   my ($self, $entry) = @_;
219
220   my $object = $entry->{object};
221
222   return if $object->purpose !~ m{\b(?:end\W?to\W?end:|eref\+) *([^ ]+)}i;
223
224   my $id = $1;
225
226   $object->end_to_end_id($id) if $id !~ m{notprovided}i;
227   $entry->{info_data}->{end_to_end_id} = $object->end_to_end_id;
228 }
229
230 sub check_auth {
231   $::auth->assert('config') if ! $::auth->assert('bank_transaction',1);
232 }
233
234 1;