Sammelcommit Bankerweiterung und Skonto
[kivitendo-erp.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 init_class {
19   my ($self) = @_;
20   $self->class('SL::DB::BankTransaction');
21 }
22
23 sub init_bank_accounts_by {
24   my ($self) = @_;
25
26   return { map { my $col = $_; ( $col => { map { ( $_->$col => $_ ) } @{ $self->all_bank_accounts } } ) } qw(id account_number iban) };
27 }
28
29 sub check_objects {
30   my ($self) = @_;
31
32   $self->controller->track_progress(phase => 'building data', progress => 0);
33   my $update_policy  = $self->controller->profile->get('update_policy') || 'skip';
34
35   my $i;
36   my $num_data = scalar @{ $self->controller->data };
37   foreach my $entry (@{ $self->controller->data }) {
38     $self->controller->track_progress(progress => $i/$num_data * 100) if $i % 100 == 0;
39
40     $self->check_bank_account($entry);
41     $self->check_currency($entry, take_default => 1);
42     $self->join_purposes($entry);
43     $self->join_remote_names($entry);
44     $self->check_existing($entry) unless @{ $entry->{errors} };
45   } continue {
46     $i++;
47   }
48
49   $self->add_info_columns({ header => $::locale->text('Bank account'), method => 'local_bank_name' });
50 }
51
52 sub check_existing {
53   my ($self, $entry) = @_;
54
55   my $object = $entry->{object};
56
57   # for each imported entry (line) we make a database call to find existing entries
58   # we don't use the init_by hash because we have to check several fields
59   # this means that we can't detect duplicates in the import file
60
61   if ( $object->amount ) {
62     # check for same
63     # * purpose
64     # * transdate
65     # * remote_account_number  (may be empty for records of our own bank)
66     # * amount
67     my $num;
68     if ( $num = SL::DB::Manager::BankTransaction->get_all_count(query =>[ remote_account_number => $object->remote_account_number, transdate => $object->transdate, purpose => $object->purpose, amount => $object->amount] ) ) {
69       push(@{$entry->{errors}}, $::locale->text('Skipping due to existing bank transaction in database'));
70     };
71   } else {
72       push(@{$entry->{errors}}, $::locale->text('Skipping because transfer amount is empty.'));
73   };
74 }
75
76 sub setup_displayable_columns {
77   my ($self) = @_;
78
79   $self->SUPER::setup_displayable_columns;
80
81   # TODO: don't show fields cleared, invoice_amount and transaction_id in the help text, as these should not be imported
82   $self->add_displayable_columns({ name => 'local_bank_code',       description => $::locale->text('Own bank code') },
83                                  { name => 'local_account_number',  description => $::locale->text('Own bank account number or IBAN') },
84                                  { name => 'local_bank_account_id', description => $::locale->text('ID of own bank account') },
85                                  { name => 'remote_bank_code',      description => $::locale->text('Bank code of the goal/source') },
86                                  { name => 'remote_account_number', description => $::locale->text('Account number of the goal/source') },
87                                  { name => 'transdate',             description => $::locale->text('Date of transaction') },
88                                  { name => 'valutadate',            description => $::locale->text('Valuta date') },
89                                  { name => 'amount',                description => $::locale->text('Amount') },
90                                  { name => 'currency',              description => $::locale->text('Currency') },
91                                  { name => 'currency_id',           description => $::locale->text('Currency (database ID)')          },
92                                  { 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")') },
93                                  { name => 'purpose',               description => $::locale->text('Purpose (if field names purpose, purpose1, purpose2 ... exist they will all combined into the field "purpose")') },
94                                  );
95 }
96
97 sub check_bank_account {
98   my ($self, $entry) = @_;
99
100   my $object = $entry->{object};
101
102   # Check whether or not local_bank_account ID exists and is valid.
103   if ($object->local_bank_account_id && !$self->bank_accounts_by->{id}->{ $object->local_bank_account_id }) {
104     push @{ $entry->{errors} }, $::locale->text('Error: Invalid local bank account');
105     return 0;
106   }
107
108   # Check whether or not local_bank_account ID, local_account_number and local_bank_code are consistent.
109   if ($object->local_bank_account_id && $entry->{raw_data}->{local_account_number}) {
110     my $bank_account = $self->bank_accounts_by->{id}->{ $object->local_bank_account_id };
111     if ($bank_account->account_number ne $entry->{raw_data}->{local_account_number}) {
112       push @{ $entry->{errors} }, $::locale->text('Error: Invalid local bank account');
113       return 0;
114     }
115     if ($entry->{raw_data}->{local_bank_code} && $entry->{raw_data}->{local_bank_code} ne $bank_account->bank_code) {
116       push @{ $entry->{errors} }, $::locale->text('Error: Invalid local bank account');
117       return 0;
118     }
119
120   }
121
122   # Map account information to ID via local_account_number if no local_bank_account_id was given
123   # local_account_number checks for match of account number or IBAN
124   if (!$object->local_bank_account_id && $entry->{raw_data}->{local_account_number}) {
125     my $bank_account = $self->bank_accounts_by->{account_number}->{ $entry->{raw_data}->{local_account_number} };
126     if (!$bank_account) {
127        $bank_account = $self->bank_accounts_by->{iban}->{ $entry->{raw_data}->{local_account_number} };
128     };
129     if (!$bank_account) {
130       push @{ $entry->{errors} }, $::locale->text('Error: Invalid local bank account');
131       return 0;
132     }
133     if ($entry->{raw_data}->{local_bank_code} && $entry->{raw_data}->{local_bank_code} ne $bank_account->bank_code) {
134       push @{ $entry->{errors} }, $::locale->text('Error: Invalid local bank account');
135       return 0;
136     }
137
138     $object->local_bank_account_id($bank_account->id);
139     $entry->{info_data}->{local_bank_name} = $bank_account->name;
140   }
141
142   return $object->local_bank_account_id ? 1 : 0;
143 }
144
145 sub join_purposes {
146   my ($self, $entry) = @_;
147
148   my $object = $entry->{object};
149
150   my $purpose = join('', $entry->{raw_data}->{purpose},
151                          $entry->{raw_data}->{purpose1},
152                          $entry->{raw_data}->{purpose2},
153                          $entry->{raw_data}->{purpose3},
154                          $entry->{raw_data}->{purpose4},
155                          $entry->{raw_data}->{purpose5},
156                          $entry->{raw_data}->{purpose6},
157                          $entry->{raw_data}->{purpose7},
158                          $entry->{raw_data}->{purpose8},
159                          $entry->{raw_data}->{purpose9},
160                          $entry->{raw_data}->{purpose10},
161                          $entry->{raw_data}->{purpose11} );
162   $object->purpose($purpose);
163
164 }
165
166 sub join_remote_names {
167   my ($self, $entry) = @_;
168
169   my $object = $entry->{object};
170
171   my $remote_name = join('', $entry->{raw_data}->{remote_name},
172                              $entry->{raw_data}->{remote_name_1} );
173   $object->remote_name($remote_name);
174 }
175
176 1;