1 package SL::Controller::BankImport;
 
   5 use parent qw(SL::Controller::Base);
 
   7 use List::MoreUtils qw(apply);
 
   8 use List::Util qw(max min);
 
  10 use SL::DB::BankAccount;
 
  11 use SL::DB::BankTransaction;
 
  13 use SL::Helper::Flash;
 
  15 use SL::SessionFile::Random;
 
  17 use Rose::Object::MakeMethods::Generic
 
  19   scalar                  => [ qw(file_name transactions statistics) ],
 
  20   'scalar --get_set_init' => [ qw(bank_accounts) ],
 
  23 __PACKAGE__->run_before('check_auth');
 
  25 sub action_upload_mt940 {
 
  26   my ($self, %params) = @_;
 
  28   $self->setup_upload_mt940_action_bar;
 
  29   $self->render('bank_import/upload_mt940', title => $::locale->text('MT940 import'));
 
  32 sub action_import_mt940_preview {
 
  33   my ($self, %params) = @_;
 
  35   if (!$::form->{file}) {
 
  36     flash_later('error', $::locale->text('You have to upload an MT940 file to import.'));
 
  37     return $self->redirect_to(action => 'upload_mt940');
 
  40   die "missing file for action import_mt940_preview" unless $::form->{file};
 
  42   my $file = SL::SessionFile::Random->new(mode => '>');
 
  43   $file->fh->print($::form->{file});
 
  46   $self->file_name($file->file_name);
 
  47   $self->parse_and_analyze_transactions;
 
  49   $self->setup_upload_mt940_preview_action_bar;
 
  50   $self->render('bank_import/import_mt940', title => $::locale->text('MT940 import preview'), preview => 1);
 
  53 sub action_import_mt940 {
 
  54   my ($self, %params) = @_;
 
  56   die "missing file for action import_mt940" unless $::form->{file_name};
 
  58   $self->file_name($::form->{file_name});
 
  59   $self->parse_and_analyze_transactions;
 
  60   $self->import_transactions;
 
  62   $self->render('bank_import/import_mt940', title => $::locale->text('MT940 import result'));
 
  65 sub parse_and_analyze_transactions {
 
  66   my ($self, %params) = @_;
 
  70   my ($min_date, $max_date);
 
  72   my $currency_id = SL::DB::Default->get->currency_id;
 
  74   $self->transactions([ sort { $a->{transdate} cmp $b->{transdate} } SL::MT940->parse($self->file_name) ]);
 
  76   foreach my $transaction (@{ $self->transactions }) {
 
  77     $transaction->{bank_account}   = $self->bank_accounts->{ make_bank_account_idx($transaction->{local_bank_code}, $transaction->{local_account_number}) };
 
  78     $transaction->{bank_account} //= $self->bank_accounts->{ make_bank_account_idx('IBAN',                          $transaction->{local_account_number}) };
 
  80     if (!$transaction->{bank_account}) {
 
  81       $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});
 
  86     $transaction->{local_bank_account_id} = $transaction->{bank_account}->id;
 
  87     $transaction->{currency_id}           = $currency_id;
 
  89     $min_date = min($min_date // $transaction->{transdate}, $transaction->{transdate});
 
  90     $max_date = max($max_date // $transaction->{transdate}, $transaction->{transdate});
 
  93   my %existing_bank_transactions;
 
  95   if ((scalar(@{ $self->transactions }) - $errors) > 0) {
 
  97       @{ SL::DB::Manager::BankTransaction->get_all(
 
  99             transdate => { ge => $min_date },
 
 100             transdate => { lt => $max_date->clone->add(days => 1) },
 
 102           inject_results => 1) };
 
 104     %existing_bank_transactions = map { (make_transaction_idx($_) => 1) } @entries;
 
 107   foreach my $transaction (@{ $self->transactions }) {
 
 108     next if $transaction->{error};
 
 110     if ($existing_bank_transactions{make_transaction_idx($transaction)}) {
 
 111       $transaction->{duplicate} = 1;
 
 118     total      => scalar(@{ $self->transactions }),
 
 120     duplicates => $duplicates,
 
 121     to_import  => scalar(@{ $self->transactions }) - $errors - $duplicates,
 
 125 sub import_transactions {
 
 126   my ($self, %params) = @_;
 
 130   SL::DB::client->with_transaction(sub {
 
 134     foreach my $transaction (@{ $self->transactions }) {
 
 135       next if $transaction->{error} || $transaction->{duplicate};
 
 137       SL::DB::BankTransaction->new(
 
 138         map { ($_ => $transaction->{$_}) } qw(amount currency_id local_bank_account_id purpose remote_account_number remote_bank_code remote_name transaction_code transdate valutadate)
 
 147   $self->statistics->{imported} = $imported;
 
 151   $::auth->assert('bank_transaction');
 
 154 sub make_bank_account_idx {
 
 155   return join '/', map { my $q = $_; $q =~ s{ +}{}g; $q } @_;
 
 161   $text = lc($text // '');
 
 167 sub make_transaction_idx {
 
 168   my ($transaction) = @_;
 
 170   if (ref($transaction) eq 'SL::DB::BankTransaction') {
 
 171     $transaction = { map { ($_ => $transaction->$_) } qw(local_bank_account_id transdate valutadate amount purpose) };
 
 174   return normalize_text(join '/',
 
 176                         ($transaction->{local_bank_account_id},
 
 177                          $transaction->{transdate}->ymd,
 
 178                          $transaction->{valutadate}->ymd,
 
 179                          (apply { s{0+$}{} } $transaction->{amount} * 1),
 
 180                          $transaction->{purpose}));
 
 183 sub init_bank_accounts {
 
 188   foreach my $bank_account (@{ SL::DB::Manager::BankAccount->get_all }) {
 
 189     if ($bank_account->bank_code && $bank_account->account_number) {
 
 190       $bank_accounts{make_bank_account_idx($bank_account->bank_code, $bank_account->account_number)} = $bank_account;
 
 192     if ($bank_account->iban) {
 
 193       $bank_accounts{make_bank_account_idx('IBAN', $bank_account->iban)} = $bank_account;
 
 197   return \%bank_accounts;
 
 200 sub setup_upload_mt940_action_bar {
 
 203   for my $bar ($::request->layout->get('actionbar')) {
 
 206         $::locale->text('Preview'),
 
 207         submit    => [ '#form', { action => 'BankImport/import_mt940_preview' } ],
 
 208         accesskey => 'enter',
 
 214 sub setup_upload_mt940_preview_action_bar {
 
 217   for my $bar ($::request->layout->get('actionbar')) {
 
 220         $::locale->text('Import'),
 
 221         submit    => [ '#form', { action => 'BankImport/import_mt940' } ],
 
 222         accesskey => 'enter',
 
 223         disabled  => $self->statistics->{to_import} ? undef : $::locale->text('No entries can be imported.'),