epic-ts
[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 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;
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->check_existing($entry) unless @{ $entry->{errors} };
54   } continue {
55     $i++;
56   }
57
58   $self->add_info_columns({ header => $::locale->text('Bank account'), method => 'local_bank_name' });
59 }
60
61 sub check_existing {
62   my ($self, $entry) = @_;
63
64   my $object = $entry->{object};
65
66   # for each imported entry (line) we make a database call to find existing entries
67   # we don't use the init_by hash because we have to check several fields
68   # this means that we can't detect duplicates in the import file
69
70   if ( $object->amount ) {
71     # check for same
72     # * purpose
73     # * transdate
74     # * remote_account_number  (may be empty for records of our own bank)
75     # * amount
76     my $num;
77     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] ) ) {
78       push(@{$entry->{errors}}, $::locale->text('Skipping due to existing bank transaction in database'));
79     };
80   } else {
81       push(@{$entry->{errors}}, $::locale->text('Skipping because transfer amount is empty.'));
82   };
83 }
84
85 sub setup_displayable_columns {
86   my ($self) = @_;
87
88   $self->SUPER::setup_displayable_columns;
89
90   # TODO: don't show fields cleared, invoice_amount and transaction_id in the help text, as these should not be imported
91   $self->add_displayable_columns({ name => 'local_bank_code',       description => $::locale->text('Own bank code') },
92                                  { name => 'local_account_number',  description => $::locale->text('Own bank account number or IBAN') },
93                                  { name => 'local_bank_account_id', description => $::locale->text('ID of own bank account') },
94                                  { name => 'remote_bank_code',      description => $::locale->text('Bank code of the goal/source') },
95                                  { name => 'remote_account_number', description => $::locale->text('Account number of the goal/source') },
96                                  { name => 'transdate',             description => $::locale->text('Date of transaction') },
97                                  { name => 'valutadate',            description => $::locale->text('Valuta date') },
98                                  { name => 'amount',                description => $::locale->text('Amount') },
99                                  { name => 'currency',              description => $::locale->text('Currency') },
100                                  { name => 'currency_id',           description => $::locale->text('Currency (database ID)')          },
101                                  { 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")') },
102                                  { name => 'purpose',               description => $::locale->text('Purpose (if field names purpose, purpose1, purpose2 ... exist they will all combined into the field "purpose")') },
103                                  );
104 }
105
106 sub check_bank_account {
107   my ($self, $entry) = @_;
108
109   my $object = $entry->{object};
110
111   # import via id: check whether or not local_bank_account ID exists and is valid.
112   if ($object->local_bank_account_id && !$self->bank_accounts_by->{id}->{ $object->local_bank_account_id }) {
113     push @{ $entry->{errors} }, $::locale->text('Error: unknown local bank account id');
114     return 0;
115   }
116
117   # Check whether or not local_bank_account ID, local_account_number and local_bank_code are consistent.
118   if ($object->local_bank_account_id && $entry->{raw_data}->{local_account_number}) {
119     my $bank_account = $self->bank_accounts_by->{id}->{ $object->local_bank_account_id };
120     if ($bank_account->account_number ne $entry->{raw_data}->{local_account_number}) {
121       push @{ $entry->{errors} }, $::locale->text('Error: local bank account id doesn\'t match local bank account number');
122       return 0;
123     }
124     if ($entry->{raw_data}->{local_bank_code} && $entry->{raw_data}->{local_bank_code} ne $bank_account->bank_code) {
125       push @{ $entry->{errors} }, $::locale->text('Error: local bank account id doesn\'t match local bank code');
126       return 0;
127     }
128
129   }
130
131   # Map account information to ID via local_account_number if no local_bank_account_id was given
132   # local_account_number checks for match of account number or IBAN
133   if (!$object->local_bank_account_id && $entry->{raw_data}->{local_account_number}) {
134     my $bank_account = $self->bank_accounts_by->{account_number}->{ $entry->{raw_data}->{local_account_number} };
135     if (!$bank_account) {
136        $bank_account = $self->bank_accounts_by->{iban}->{ $entry->{raw_data}->{local_account_number} };
137     };
138     if (!$bank_account) {
139       push @{ $entry->{errors} }, $::locale->text('Error: unknown local bank account') . ": " . $entry->{raw_data}->{local_account_number};
140       return 0;
141     }
142     if ($entry->{raw_data}->{local_bank_code} && $entry->{raw_data}->{local_bank_code} ne $bank_account->bank_code) {
143       push @{ $entry->{errors} }, $::locale->text('Error: Found local bank account number but local bank code doesn\'t match') . ": " . $entry->{raw_data}->{local_bank_code};
144       return 0;
145     }
146
147     $object->local_bank_account_id($bank_account->id);
148     $entry->{info_data}->{local_bank_name} = $bank_account->name;
149   }
150
151   return $object->local_bank_account_id ? 1 : 0;
152 }
153
154 sub join_purposes {
155   my ($self, $entry) = @_;
156
157   my $object = $entry->{object};
158
159   my $purpose = join('', $entry->{raw_data}->{purpose},
160                          $entry->{raw_data}->{purpose1},
161                          $entry->{raw_data}->{purpose2},
162                          $entry->{raw_data}->{purpose3},
163                          $entry->{raw_data}->{purpose4},
164                          $entry->{raw_data}->{purpose5},
165                          $entry->{raw_data}->{purpose6},
166                          $entry->{raw_data}->{purpose7},
167                          $entry->{raw_data}->{purpose8},
168                          $entry->{raw_data}->{purpose9},
169                          $entry->{raw_data}->{purpose10},
170                          $entry->{raw_data}->{purpose11} );
171   $object->purpose($purpose);
172
173 }
174
175 sub join_remote_names {
176   my ($self, $entry) = @_;
177
178   my $object = $entry->{object};
179
180   my $remote_name = join('', $entry->{raw_data}->{remote_name},
181                              $entry->{raw_data}->{remote_name_1} );
182   $object->remote_name($remote_name);
183 }
184
185 1;