X-Git-Url: http://wagnertech.de/git?a=blobdiff_plain;f=SL%2FController%2FBankImport.pm;fp=SL%2FController%2FBankImport.pm;h=1b20deca3b8a2705506c38b1aba1dedc044ab44b;hb=53593baa211863fbf66540cf1bcc36c8fb37257f;hp=867589f104b0a68aae98012f51d6ffaa8a7b1210;hpb=deb4d2dbb676d7d6f69dfe7815d6e0cb09bd4a44;p=kivitendo-erp.git diff --git a/SL/Controller/BankImport.pm b/SL/Controller/BankImport.pm index 867589f10..1b20deca3 100644 --- a/SL/Controller/BankImport.pm +++ b/SL/Controller/BankImport.pm @@ -1,44 +1,231 @@ package SL::Controller::BankImport; + use strict; -use Data::Dumper; + use parent qw(SL::Controller::Base); -use SL::Locale::String qw(t8); -use SL::DB::CsvImportProfile; -use SL::Helper::MT940; +use List::MoreUtils qw(apply); +use List::Util qw(max min); + +use SL::DB::BankAccount; +use SL::DB::BankTransaction; +use SL::DB::Default; +use SL::Helper::Flash; +use SL::MT940; +use SL::SessionFile::Random; + +use Rose::Object::MakeMethods::Generic +( + scalar => [ qw(file_name transactions statistics charset) ], + 'scalar --get_set_init' => [ qw(bank_accounts) ], +); __PACKAGE__->run_before('check_auth'); sub action_upload_mt940 { my ($self, %params) = @_; - my $profile = SL::DB::Manager::CsvImportProfile->find_by(name => 'MT940', login => $::myconfig{login}); - $self->render('bankimport/form', title => $::locale->text('MT940 import'), profile => $profile ? 1 : 0); + $self->setup_upload_mt940_action_bar; + $self->render('bank_import/upload_mt940', title => $::locale->text('MT940 import')); +} + +sub action_import_mt940_preview { + my ($self, %params) = @_; + + if (!$::form->{file}) { + flash_later('error', $::locale->text('You have to upload an MT940 file to import.')); + return $self->redirect_to(action => 'upload_mt940'); + } + die "missing file for action import_mt940_preview" unless $::form->{file}; + + my $file = SL::SessionFile::Random->new(mode => '>'); + $file->fh->print($::form->{file}); + $file->fh->close; + + $self->charset($::form->{charset}); + $self->file_name($file->file_name); + $self->parse_and_analyze_transactions; + + $self->setup_upload_mt940_preview_action_bar; + $self->render('bank_import/import_mt940', title => $::locale->text('MT940 import preview'), preview => 1); } sub action_import_mt940 { my ($self, %params) = @_; - die "missing file for action import" unless $::form->{file}; + die "missing file for action import_mt940" unless $::form->{file_name}; - my $converted_data = SL::Helper::MT940::convert_mt940_data($::form->{file}); + $self->file_name($::form->{file_name}); + $self->charset($::form->{charset}); + $self->parse_and_analyze_transactions; + $self->import_transactions; - # store the converted data in a session file with a name expected by the profile type "bank_transactions" - my $file = SL::SessionFile->new("csv-import-bank_transactions.csv", mode => '>'); - $file->fh->print($converted_data); - $file->fh->close; + $self->render('bank_import/import_mt940', title => $::locale->text('MT940 import result')); +} + +sub parse_and_analyze_transactions { + my ($self, %params) = @_; + + my $errors = 0; + my $duplicates = 0; + my ($min_date, $max_date); + + my $currency_id = SL::DB::Default->get->currency_id; + + $self->transactions([ sort { $a->{transdate} cmp $b->{transdate} } SL::MT940->parse($self->file_name, charset => $self->charset) ]); + + foreach my $transaction (@{ $self->transactions }) { + $transaction->{bank_account} = $self->bank_accounts->{ make_bank_account_idx($transaction->{local_bank_code}, $transaction->{local_account_number}) }; + $transaction->{bank_account} //= $self->bank_accounts->{ make_bank_account_idx('IBAN', $transaction->{local_account_number}) }; + + if (!$transaction->{bank_account}) { + $transaction->{error} = $::locale->text('No bank account configured for bank code/BIC #1, account number/IBAN #2.', $transaction->{local_bank_code}, $transaction->{local_account_number}); + $errors++; + next; + } + + $transaction->{local_bank_account_id} = $transaction->{bank_account}->id; + $transaction->{currency_id} = $currency_id; + + $min_date = min($min_date // $transaction->{transdate}, $transaction->{transdate}); + $max_date = max($max_date // $transaction->{transdate}, $transaction->{transdate}); + } - my $profile = SL::DB::Manager::CsvImportProfile->find_by(name => 'MT940', login => $::myconfig{login}); - die t8("The MT940 import needs an import profile called MT940") unless $profile; + my %existing_bank_transactions; - $self->redirect_to(controller => 'controller.pl', action => 'CsvImport/test', 'profile.type' => 'bank_transactions', 'profile.id' => $profile->id, force_profile => 1); + if ((scalar(@{ $self->transactions }) - $errors) > 0) { + my @entries = + @{ SL::DB::Manager::BankTransaction->get_all( + where => [ + transdate => { ge => $min_date }, + transdate => { lt => $max_date->clone->add(days => 1) }, + ], + inject_results => 1) }; + + %existing_bank_transactions = map { (make_transaction_idx($_) => 1) } @entries; + } + + foreach my $transaction (@{ $self->transactions }) { + next if $transaction->{error}; + + if ($existing_bank_transactions{make_transaction_idx($transaction)}) { + $transaction->{duplicate} = 1; + $duplicates++; + next; + } + } + + $self->statistics({ + total => scalar(@{ $self->transactions }), + errors => $errors, + duplicates => $duplicates, + to_import => scalar(@{ $self->transactions }) - $errors - $duplicates, + }); +} + +sub import_transactions { + my ($self, %params) = @_; -}; + my $imported = 0; + + SL::DB::client->with_transaction(sub { + # make Emacs happy + 1; + + foreach my $transaction (@{ $self->transactions }) { + next if $transaction->{error} || $transaction->{duplicate}; + + SL::DB::BankTransaction->new( + map { ($_ => $transaction->{$_}) } qw(amount currency_id local_bank_account_id purpose remote_account_number remote_bank_code remote_name transaction_code transdate valutadate) + )->save; + + $imported++; + } + + 1; + }); + + $self->statistics->{imported} = $imported; +} sub check_auth { $::auth->assert('bank_transaction'); } -1; +sub make_bank_account_idx { + return join '/', map { my $q = $_; $q =~ s{ +}{}g; $q } @_; +} + +sub normalize_text { + my ($text) = @_; + + $text = lc($text // ''); + $text =~ s{ }{}g; + + return $text; +} + +sub make_transaction_idx { + my ($transaction) = @_; + + if (ref($transaction) eq 'SL::DB::BankTransaction') { + $transaction = { map { ($_ => $transaction->$_) } qw(local_bank_account_id transdate valutadate amount purpose) }; + } + return normalize_text(join '/', + map { $_ // '' } + ($transaction->{local_bank_account_id}, + $transaction->{transdate}->ymd, + $transaction->{valutadate}->ymd, + (apply { s{0+$}{} } $transaction->{amount} * 1), + $transaction->{purpose})); +} + +sub init_bank_accounts { + my ($self) = @_; + + my %bank_accounts; + + foreach my $bank_account (@{ SL::DB::Manager::BankAccount->get_all }) { + if ($bank_account->bank_code && $bank_account->account_number) { + $bank_accounts{make_bank_account_idx($bank_account->bank_code, $bank_account->account_number)} = $bank_account; + } + if ($bank_account->iban) { + $bank_accounts{make_bank_account_idx('IBAN', $bank_account->iban)} = $bank_account; + } + } + + return \%bank_accounts; +} + +sub setup_upload_mt940_action_bar { + my ($self) = @_; + + for my $bar ($::request->layout->get('actionbar')) { + $bar->add( + action => [ + $::locale->text('Preview'), + submit => [ '#form', { action => 'BankImport/import_mt940_preview' } ], + accesskey => 'enter', + ], + ); + } +} + +sub setup_upload_mt940_preview_action_bar { + my ($self) = @_; + + for my $bar ($::request->layout->get('actionbar')) { + $bar->add( + action => [ + $::locale->text('Import'), + submit => [ '#form', { action => 'BankImport/import_mt940' } ], + accesskey => 'enter', + disabled => $self->statistics->{to_import} ? undef : $::locale->text('No entries can be imported.'), + ], + ); + } +} + +1;