kivitendo/SL/Controller/BankImport.pm @ 912e5eff
d180d84e | Geoffrey Richardson | package SL::Controller::BankImport;
|
||
789de0c0 | Moritz Bunkus | |||
d180d84e | Geoffrey Richardson | use strict;
|
||
789de0c0 | Moritz Bunkus | |||
d180d84e | Geoffrey Richardson | use parent qw(SL::Controller::Base);
|
||
789de0c0 | Moritz Bunkus | 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;
|
||||
401fc133 | Bernd Bleßmann | use SL::SessionFile::Random;
|
||
d180d84e | Geoffrey Richardson | |||
4fef0591 | Bernd Bleßmann | use Rose::Object::MakeMethods::Generic
|
||
(
|
||||
502a10d4 | Moritz Bunkus | scalar => [ qw(file_name transactions statistics charset) ],
|
||
789de0c0 | Moritz Bunkus | 'scalar --get_set_init' => [ qw(bank_accounts) ],
|
||
4fef0591 | Bernd Bleßmann | );
|
||
6ebec7f9 | Geoffrey Richardson | __PACKAGE__->run_before('check_auth');
|
||
d180d84e | Geoffrey Richardson | |||
sub action_upload_mt940 {
|
||||
my ($self, %params) = @_;
|
||||
37c03103 | Moritz Bunkus | $self->setup_upload_mt940_action_bar;
|
||
789de0c0 | Moritz Bunkus | $self->render('bank_import/upload_mt940', title => $::locale->text('MT940 import'));
|
||
d180d84e | Geoffrey Richardson | }
|
||
789de0c0 | Moritz Bunkus | sub action_import_mt940_preview {
|
||
d180d84e | Geoffrey Richardson | my ($self, %params) = @_;
|
||
789de0c0 | Moritz Bunkus | if (!$::form->{file}) {
|
||
flash_later('error', $::locale->text('You have to upload an MT940 file to import.'));
|
||||
return $self->redirect_to(action => 'upload_mt940');
|
||||
}
|
||||
d180d84e | Geoffrey Richardson | |||
789de0c0 | Moritz Bunkus | die "missing file for action import_mt940_preview" unless $::form->{file};
|
||
d180d84e | Geoffrey Richardson | |||
401fc133 | Bernd Bleßmann | my $file = SL::SessionFile::Random->new(mode => '>');
|
||
789de0c0 | Moritz Bunkus | $file->fh->print($::form->{file});
|
||
d180d84e | Geoffrey Richardson | $file->fh->close;
|
||
502a10d4 | Moritz Bunkus | $self->charset($::form->{charset});
|
||
789de0c0 | Moritz Bunkus | $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_mt940" unless $::form->{file_name};
|
||||
$self->file_name($::form->{file_name});
|
||||
502a10d4 | Moritz Bunkus | $self->charset($::form->{charset});
|
||
789de0c0 | Moritz Bunkus | $self->parse_and_analyze_transactions;
|
||
$self->import_transactions;
|
||||
$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);
|
||||
d180d84e | Geoffrey Richardson | |||
789de0c0 | Moritz Bunkus | my $currency_id = SL::DB::Default->get->currency_id;
|
||
502a10d4 | Moritz Bunkus | $self->transactions([ sort { $a->{transdate} cmp $b->{transdate} } SL::MT940->parse($self->file_name, charset => $self->charset) ]);
|
||
789de0c0 | Moritz Bunkus | |||
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 %existing_bank_transactions;
|
||||
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;
|
||||
37c03103 | Moritz Bunkus | }
|
||
d180d84e | Geoffrey Richardson | |||
6ebec7f9 | Geoffrey Richardson | sub check_auth {
|
||
$::auth->assert('bank_transaction');
|
||||
}
|
||||
789de0c0 | Moritz Bunkus | 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) };
|
||||
4fef0591 | Bernd Bleßmann | }
|
||
789de0c0 | Moritz Bunkus | |||
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;
|
||||
4fef0591 | Bernd Bleßmann | }
|
||
37c03103 | Moritz Bunkus | sub setup_upload_mt940_action_bar {
|
||
my ($self) = @_;
|
||||
for my $bar ($::request->layout->get('actionbar')) {
|
||||
$bar->add(
|
||||
action => [
|
||||
$::locale->text('Preview'),
|
||||
789de0c0 | Moritz Bunkus | 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'),
|
||||
37c03103 | Moritz Bunkus | submit => [ '#form', { action => 'BankImport/import_mt940' } ],
|
||
accesskey => 'enter',
|
||||
789de0c0 | Moritz Bunkus | disabled => $self->statistics->{to_import} ? undef : $::locale->text('No entries can be imported.'),
|
||
37c03103 | Moritz Bunkus | ],
|
||
);
|
||||
}
|
||||
}
|
||||
d180d84e | Geoffrey Richardson | |||
37c03103 | Moritz Bunkus | 1;
|