]> wagnertech.de Git - mfinanz.git/blobdiff - SL/Controller/BankTransaction.pm
Bankerweiterung - Zwischenstand, erster Entwurf
[mfinanz.git] / SL / Controller / BankTransaction.pm
diff --git a/SL/Controller/BankTransaction.pm b/SL/Controller/BankTransaction.pm
new file mode 100644 (file)
index 0000000..afa06e9
--- /dev/null
@@ -0,0 +1,621 @@
+package SL::Controller::BankTransaction;
+
+# idee- möglichkeit bankdaten zu übernehmen in stammdaten
+# erst Kontenabgleich, um alle gl-Einträge wegzuhaben
+use strict;
+
+use parent qw(SL::Controller::Base);
+
+use SL::Controller::Helper::GetModels;
+use SL::Controller::Helper::ReportGenerator;
+use SL::ReportGenerator;
+
+use SL::DB::BankTransaction;
+use SL::Helper::Flash;
+use SL::Locale::String;
+use SL::SEPA;
+use SL::DB::Invoice;
+use SL::DB::PurchaseInvoice;
+use SL::DB::RecordLink;
+use SL::JSON;
+use SL::DB::Chart;
+use SL::DB::AccTransaction;
+use SL::DB::Tax;
+use SL::DB::Draft;
+use SL::DB::BankAccount;
+
+use Rose::Object::MakeMethods::Generic
+(
+ 'scalar --get_set_init' => [ qw(models) ],
+);
+
+__PACKAGE__->run_before('check_auth');
+
+
+#
+# actions
+#
+
+sub action_search {
+  my ($self) = @_;
+
+  my $bank_accounts = SL::DB::Manager::BankAccount->get_all();
+
+  $self->render('bank_transactions/search',
+                 label_sub => sub { t8('#1 - Account number #2, bank code #3, #4', $_[0]->name, $_[0]->account_number, $_[0]->bank_code, $_[0]->bank, )},
+                 BANK_ACCOUNTS => $bank_accounts);
+}
+
+sub action_list_all {
+  my ($self) = @_;
+
+  my $transactions = $self->models->get;
+
+  $self->make_filter_summary;
+  $self->prepare_report;
+
+  $self->report_generator_list_objects(report => $self->{report}, objects => $transactions);
+}
+
+sub action_list {
+  my ($self) = @_;
+
+  if (!$::form->{filter}{bank_account}) {
+    flash('error', t8('No bank account chosen!'));
+    $self->action_search;
+    return;
+  }
+
+  my $sort_by = $::form->{sort_by} || 'transdate';
+  $sort_by = 'transdate' if $sort_by eq 'proposal';
+  $sort_by .= $::form->{sort_dir} ? ' DESC' : ' ASC';
+
+  my $fromdate = $::locale->parse_date_to_object(\%::myconfig, $::form->{filter}->{fromdate});
+  my $todate   = $::locale->parse_date_to_object(\%::myconfig, $::form->{filter}->{todate});
+  $todate->add( days => 1 ) if $todate;
+
+  my @where = ();
+  push @where, (transdate => { ge => $fromdate }) if ($fromdate);
+  push @where, (transdate => { lt => $todate })   if ($todate);
+
+  my $bank_transactions = SL::DB::Manager::BankTransaction->get_all(where => [ amount => {ne => \'invoice_amount'},
+                                                                               local_bank_account_id => $::form->{filter}{bank_account},
+                                                                               @where ],
+                                                                    with_objects => [ 'local_bank_account', 'currency' ],
+                                                                    sort_by => $sort_by, limit => 10000);
+
+  my $all_open_ar_invoices = SL::DB::Manager::Invoice->get_all(where => [amount => { gt => \'paid' }], with_objects => 'customer');
+  my $all_open_ap_invoices = SL::DB::Manager::PurchaseInvoice->get_all(where => [amount => { gt => \'paid' }], with_objects => 'vendor');
+
+  my @all_open_invoices;
+  push @all_open_invoices, @{ $all_open_ar_invoices };
+  push @all_open_invoices, @{ $all_open_ap_invoices };
+
+  foreach my $bt (@{ $bank_transactions }) {
+    next unless $bt->{remote_name};  # bank has no name, usually fees, use create invoice to assign
+    foreach my $open_invoice (@all_open_invoices){
+      $open_invoice->{agreement} = 0;
+
+      #compare banking arrangements
+      my ($bank_code, $account_number);
+      $bank_code      = $open_invoice->customer->bank_code      if $open_invoice->is_sales;
+      $account_number = $open_invoice->customer->account_number if $open_invoice->is_sales;
+      $bank_code      = $open_invoice->vendor->bank_code        if ! $open_invoice->is_sales;
+      $account_number = $open_invoice->vendor->account_number   if ! $open_invoice->is_sales;
+      ($bank_code eq $bt->remote_bank_code
+        && $account_number eq $bt->remote_account_number) ? ($open_invoice->{agreement} += 2) : ();
+
+      my $datediff = $bt->transdate->{utc_rd_days} - $open_invoice->transdate->{utc_rd_days};
+      $open_invoice->{datediff} = $datediff;
+
+      #compare amount
+#      (abs($open_invoice->amount) == abs($bt->amount)) ? ($open_invoice->{agreement} += 2) : ();
+# do we need double abs here? 
+      (abs(abs($open_invoice->amount) - abs($bt->amount)) < 0.01) ? ($open_invoice->{agreement} += 4) : ();
+
+      #search invoice number in purpose
+      my $invnumber = $open_invoice->invnumber;
+# possible improvement: match has to have more than 1 character?
+      $bt->purpose =~ /\b$invnumber\b/i ? ($open_invoice->{agreement} += 2) : ();
+
+      #check sign
+      if ( $open_invoice->is_sales && $bt->amount < 0 ) {
+        $open_invoice->{agreement} -= 1;
+      };
+      if ( ! $open_invoice->is_sales && $bt->amount > 0 ) {
+        $open_invoice->{agreement} -= 1;
+      };
+
+      #search customer/vendor number in purpose
+      my $cvnumber;
+      $cvnumber = $open_invoice->customer->customernumber if $open_invoice->is_sales;
+      $cvnumber = $open_invoice->vendor->vendornumber     if ! $open_invoice->is_sales;
+      $bt->purpose =~ /\b$cvnumber\b/i ? ($open_invoice->{agreement}++) : ();
+
+      #compare customer/vendor name and account holder
+      my $cvname;
+      $cvname = $open_invoice->customer->name if $open_invoice->is_sales;
+      $cvname = $open_invoice->vendor->name   if ! $open_invoice->is_sales;
+      $bt->remote_name =~ /\b$cvname\b/i ? ($open_invoice->{agreement}++) : ();
+
+      #Compare transdate of bank_transaction with transdate of invoice
+      #Check if words in remote_name appear in cvname
+      $open_invoice->{agreement} += &check_string($bt->remote_name,$cvname);
+
+      $open_invoice->{agreement} -= 1 if $datediff < -5; # dies hebelt eventuell Vorkasse aus
+      $open_invoice->{agreement} += 1 if $datediff < 30; # dies hebelt eventuell Vorkasse aus
+
+      # only if we already have a good agreement, let date further change value of agreement.
+      # this is so that if there are several open invoices which are all equal (rent jan, rent feb...) the one with the best date match is chose over the others
+      # another way around this is to just pre-filter by periods instead of matching everything
+      if ( $open_invoice->{agreement} > 5 ) {
+        if ( $datediff == 0 ) { 
+          $open_invoice->{agreement} += 3;
+        } elsif  ( $datediff > 0 and $datediff <= 14 ) {
+          $open_invoice->{agreement} += 2;
+        } elsif  ( $datediff >14 and $datediff < 35) {
+          $open_invoice->{agreement} += 1;
+        } elsif  ( $datediff >34 and $datediff < 120) {
+          $open_invoice->{agreement} += 1;
+        } elsif  ( $datediff < 0 ) {
+          $open_invoice->{agreement} -= 1;
+        } else {
+          # e.g. datediff > 120
+        };
+      };
+
+      #if ($open_invoice->transdate->{utc_rd_days} == $bt->transdate->{utc_rd_days}) {  
+        #$open_invoice->{agreement} += 4;
+        #print FH "found matching date for invoice " . $open_invoice->invnumber . " ( " . $bt->transdate->{utc_rd_days} . " . \n";
+      #} elsif (($open_invoice->transdate->{utc_rd_days} + 30) < $bt->transdate->{utc_rd_days}) {  
+        #$open_invoice->{agreement} -= 1;
+      #} else {
+        #$open_invoice->{agreement} -= 2;
+        #print FH "found nomatch date -2 for invoice " . $open_invoice->invnumber . " ( " . $bt->transdate->{utc_rd_days} . " . \n";
+      #};
+      #print FH "agreement after date_agreement: " . $open_invoice->{agreement} . "\n";
+
+
+
+    }
+# finished going through all open_invoices
+
+    # go through each bt
+    # for each open_invoice try to match it to each open_invoice and store agreement in $open_invoice->{agreement} (which gets overwritten each time for each bt)
+    #    calculate 
+#  
+
+    $bt->{proposals} = [];
+    my $agreement = 11;
+    # wird nie ausgeführt, bzw. nur ganz am Ende
+# oder einmal am Anfang?
+# es werden maximal 7 vorschläge gemacht?
+    # 7 mal wird geprüft, ob etwas passt
+    while (scalar @{ $bt->{proposals} } < 1 && $agreement-- > 0) {
+      $bt->{proposals} = [ grep { $_->{agreement} > $agreement } @all_open_invoices ];
+      #Kann wahrscheinlich weg:
+#      map { $_->{style} = "green" } @{ $bt->{proposals} } if $agreement >= 5;
+#      map { $_->{style} = "orange" } @{ $bt->{proposals} } if $agreement < 5 and $agreement >= 3;
+#      map { $_->{style} = "red" } @{ $bt->{proposals} } if $agreement < 3;
+      $bt->{agreement} = $agreement;  # agreement value at cutoff, will correspond to several results if threshold is 7 and several are already above 7
+    }
+  }  # finished one bt
+  # finished all bt
+
+  # separate filter for proposals (second tab, agreement >= 5 and exactly one match)
+  # to qualify as a proposal there has to be
+  # * agreement >= 5
+  # * there must be only one exact match 
+  # * depending on whether sales or purchase the amount has to have the correct sign (so Gutschriften don't work?)
+
+  my @proposals = grep { $_->{agreement} >= 5
+                         and 1 == scalar @{ $_->{proposals} }
+                         and (@{ $_->{proposals} }[0]->is_sales ? abs(@{ $_->{proposals} }[0]->amount - $_->amount) < 0.01  : abs(@{ $_->{proposals} }[0]->amount + $_->amount) < 0.01) } @{ $bank_transactions };
+
+  #Sort bank transactions by quality of proposal
+  $bank_transactions = [ sort { $a->{agreement} <=> $b->{agreement} } @{ $bank_transactions } ] if $::form->{sort_by} eq 'proposal' and $::form->{sort_dir} == 1;
+  $bank_transactions = [ sort { $b->{agreement} <=> $a->{agreement} } @{ $bank_transactions } ] if $::form->{sort_by} eq 'proposal' and $::form->{sort_dir} == 0;
+
+
+  $self->render('bank_transactions/list',
+                title             => t8('List of bank transactions'),
+                BANK_TRANSACTIONS => $bank_transactions,
+                PROPOSALS         => \@proposals,
+                bank_account      => SL::DB::Manager::BankAccount->find_by(id => $::form->{filter}{bank_account}) );
+}
+
+sub check_string {
+    my $bankstring = shift;
+    my $namestring = shift;
+    return 0 unless $bankstring and $namestring;
+
+    my @bankwords = grep(/^\w+$/, split(/\b/,$bankstring));
+
+    my $match = 0;
+    foreach my $bankword ( @bankwords ) {
+        # only try to match strings with more than 2 characters
+        next unless length($bankword)>2; 
+        if ( $namestring =~ /\b$bankword\b/i ) {
+            $match++;
+        };
+    };
+    return $match;
+};
+
+sub action_assign_invoice {
+  my ($self) = @_;
+
+  $self->{transaction} = SL::DB::Manager::BankTransaction->find_by(id => $::form->{bt_id});
+
+  $self->render('bank_transactions/assign_invoice', { layout  => 0 },
+                title      => t8('Assign invoice'),);
+}
+
+sub action_create_invoice {
+  my ($self) = @_;
+  my %myconfig = %main::myconfig;
+
+  $self->{transaction} = SL::DB::Manager::BankTransaction->find_by(id => $::form->{bt_id});
+  my $vendor_of_transaction = SL::DB::Manager::Vendor->find_by(account_number => $self->{transaction}->{remote_account_number});
+
+  my $drafts = SL::DB::Manager::Draft->get_all(where => [ module => 'ap'] , with_objects => 'employee');
+
+  my @filtered_drafts;
+
+  foreach my $draft ( @{ $drafts } ) {
+    my $draft_as_object = YAML::Load($draft->form);
+    my $vendor = SL::DB::Manager::Vendor->find_by(id => $draft_as_object->{vendor_id});
+    $draft->{vendor} = $vendor->name;
+    $draft->{vendor_id} = $vendor->id;
+    push @filtered_drafts, $draft;
+  }
+
+  #Filter drafts
+  @filtered_drafts = grep { $_->{vendor_id} == $vendor_of_transaction->id } @filtered_drafts if $vendor_of_transaction;
+
+  my $all_vendors = SL::DB::Manager::Vendor->get_all();
+
+  $self->render('bank_transactions/create_invoice', { layout  => 0 },
+      title      => t8('Create invoice'),
+      DRAFTS     => \@filtered_drafts,
+      vendor_id  => $vendor_of_transaction ? $vendor_of_transaction->id : undef,
+      vendor_name => $vendor_of_transaction ? $vendor_of_transaction->name : undef,
+      ALL_VENDORS => $all_vendors,
+      limit      => $myconfig{vclimit},
+      callback   => $self->url_for(action                => 'list',
+                                   'filter.bank_account' => $::form->{filter}->{bank_account},
+                                   'filter.todate'       => $::form->{filter}->{todate},
+                                   'filter.fromdate'     => $::form->{filter}->{fromdate}),
+      );
+}
+
+sub action_filter_drafts {
+  my ($self) = @_;
+
+  $self->{transaction} = SL::DB::Manager::BankTransaction->find_by(id => $::form->{bt_id});
+  my $vendor_of_transaction = SL::DB::Manager::Vendor->find_by(account_number => $self->{transaction}->{remote_account_number});
+
+  my $drafts = SL::DB::Manager::Draft->get_all(with_objects => 'employee');
+
+  my @filtered_drafts;
+
+  foreach my $draft ( @{ $drafts } ) {
+    my $draft_as_object = YAML::Load($draft->form);
+    my $vendor = SL::DB::Manager::Vendor->find_by(id => $draft_as_object->{vendor_id});
+    $draft->{vendor} = $vendor->name;
+    $draft->{vendor_id} = $vendor->id;
+    push @filtered_drafts, $draft;
+  }
+
+  my $vendor_name = $::form->{vendor};
+  my $vendor_id = $::form->{vendor_id};
+
+  #Filter drafts
+  @filtered_drafts = grep { $_->{vendor_id} == $vendor_id } @filtered_drafts if $vendor_id;
+  @filtered_drafts = grep { $_->{vendor} =~ /$vendor_name/i } @filtered_drafts if $vendor_name;
+
+  my $output  = $self->render(
+      'bank_transactions/filter_drafts',
+      { output      => 0 },
+      DRAFTS => \@filtered_drafts,
+      );
+
+  my %result = ( count => 0, html => $output );
+
+  $self->render(\to_json(\%result), { type => 'json', process => 0 });
+}
+
+sub action_ajax_add_list {
+  my ($self) = @_;
+
+  my @where_sale     = (amount => { ne => \'paid' });
+  my @where_purchase = (amount => { ne => \'paid' });
+
+  if ($::form->{invnumber}) {
+    push @where_sale,     (invnumber => { ilike => '%' . $::form->{invnumber} . '%'});
+    push @where_purchase, (invnumber => { ilike => '%' . $::form->{invnumber} . '%'});
+  }
+
+  if ($::form->{amount}) {
+    push @where_sale,     (amount => $::form->parse_amount(\%::myconfig, $::form->{amount}));
+    push @where_purchase, (amount => $::form->parse_amount(\%::myconfig, $::form->{amount}));
+  }
+
+  if ($::form->{vcnumber}) {
+    push @where_sale,     ('customer.customernumber' => { ilike => '%' . $::form->{vcnumber} . '%'});
+    push @where_purchase, ('vendor.vendornumber'     => { ilike => '%' . $::form->{vcnumber} . '%'});
+  }
+
+  if ($::form->{vcname}) {
+    push @where_sale,     ('customer.name' => { ilike => '%' . $::form->{vcname} . '%'});
+    push @where_purchase, ('vendor.name'   => { ilike => '%' . $::form->{vcname} . '%'});
+  }
+
+  if ($::form->{transdatefrom}) {
+    my $fromdate = $::locale->parse_date_to_object(\%::myconfig, $::form->{transdatefrom});
+    push @where_sale,     ('transdate' => { ge => $fromdate});
+    push @where_purchase, ('transdate' => { ge => $fromdate});
+  }
+
+  if ($::form->{transdateto}) {
+    my $todate = $::locale->parse_date_to_object(\%::myconfig, $::form->{transdateto});
+    $todate->add(days => 1);
+    push @where_sale,     ('transdate' => { lt => $todate});
+    push @where_purchase, ('transdate' => { lt => $todate});
+  }
+
+  my $all_open_ar_invoices = SL::DB::Manager::Invoice->get_all(where => \@where_sale, with_objects => 'customer');
+  my $all_open_ap_invoices = SL::DB::Manager::PurchaseInvoice->get_all(where => \@where_purchase, with_objects => 'vendor');
+
+  my @all_open_invoices;
+  push @all_open_invoices, @{ $all_open_ar_invoices };
+  push @all_open_invoices, @{ $all_open_ap_invoices };
+
+  @all_open_invoices = sort { $a->id <=> $b->id } @all_open_invoices;
+  #my $all_open_invoices = SL::DB::Manager::Invoice->get_all(where => \@where);
+
+  my $output  = $self->render(
+      'bank_transactions/add_list',
+      { output      => 0 },
+      INVOICES => \@all_open_invoices,
+      );
+
+  my %result = ( count => 0, html => $output );
+
+  $self->render(\to_json(\%result), { type => 'json', process => 0 });
+}
+
+sub action_ajax_accept_invoices {
+  my ($self) = @_;
+
+  my @selected_invoices;
+  foreach my $invoice_id (@{ $::form->{invoice_id} || [] }) {
+    my $invoice_object = SL::DB::Manager::Invoice->find_by(id => $invoice_id);
+    $invoice_object ||= SL::DB::Manager::PurchaseInvoice->find_by(id => $invoice_id);
+
+    push @selected_invoices, $invoice_object;
+  }
+
+  $self->render('bank_transactions/invoices', { layout => 0 },
+                INVOICES => \@selected_invoices,
+                bt_id    => $::form->{bt_id} );
+}
+
+sub action_save_invoices {
+  my ($self) = @_;
+
+  my $invoice_hash = delete $::form->{invoice_ids};
+
+  while ( my ($bt_id, $invoice_ids) = each(%$invoice_hash) ) {
+    my $bank_transaction = SL::DB::Manager::BankTransaction->find_by(id => $bt_id);
+    my $sign = $bank_transaction->amount < 0 ? -1 : 1;
+    my $amount_of_transaction = $sign * $bank_transaction->amount;
+
+    my @invoices;
+    foreach my $invoice_id (@{ $invoice_ids }) {
+      push @invoices, (SL::DB::Manager::Invoice->find_by(id => $invoice_id) || SL::DB::Manager::PurchaseInvoice->find_by(id => $invoice_id));
+    }
+    @invoices = sort { return 1 if ($a->is_sales and $a->amount > 0);
+                          return 1 if (!$a->is_sales and $a->amount < 0);
+                          return -1; } @invoices                if $bank_transaction->amount > 0;
+    @invoices = sort { return -1 if ($a->is_sales and $a->amount > 0);
+                       return -1 if (!$a->is_sales and $a->amount < 0);
+                       return 1; } @invoices                    if $bank_transaction->amount < 0;
+
+    foreach my $invoice (@invoices) {
+      if ($amount_of_transaction == 0) {
+        flash('warning',  $::locale->text('There are invoices which could not be payed by bank transaction #1 (Account number: #2, bank code: #3)!',
+                                            $bank_transaction->purpose,
+                                            $bank_transaction->remote_account_number,
+                                            $bank_transaction->remote_bank_code));
+        last;
+      }
+      #pay invoice or go to the next bank transaction if the amount is not sufficiently high
+      if ($invoice->amount <= $amount_of_transaction) {
+        $invoice->pay_invoice(chart_id => $bank_transaction->local_bank_account->chart_id, trans_id => $invoice->id, amount => $invoice->amount, transdate => $bank_transaction->transdate);
+        if ($invoice->is_sales) {
+          $amount_of_transaction -= $sign * $invoice->amount;
+          $bank_transaction->invoice_amount($bank_transaction->invoice_amount + $invoice->amount);
+        } else {
+          $amount_of_transaction += $sign * $invoice->amount if (!$invoice->is_sales);
+          $bank_transaction->invoice_amount($bank_transaction->invoice_amount - $invoice->amount);
+        }
+      } else {
+        $invoice->pay_invoice(chart_id => $bank_transaction->local_bank_account->chart_id, trans_id => $invoice->id, amount => $amount_of_transaction, transdate => $bank_transaction->transdate);
+        $bank_transaction->invoice_amount($bank_transaction->amount) if $invoice->is_sales;
+        $bank_transaction->invoice_amount($bank_transaction->amount) if !$invoice->is_sales;
+        $amount_of_transaction = 0;
+      }
+
+      #Record a link from the bank transaction to the invoice
+      my @props = (
+          from_table => 'bank_transactions',
+          from_id    => $bt_id,
+          to_table   => $invoice->is_sales ? 'ar' : 'ap',
+          to_id      => $invoice->id,
+          );
+
+      my $existing = SL::DB::Manager::RecordLink->get_all(where => \@props, limit => 1)->[0];
+
+      SL::DB::RecordLink->new(@props)->save if !$existing;
+    }
+    $bank_transaction->save;
+  }
+
+  $self->action_list();
+}
+
+sub action_save_proposals {
+  my ($self) = @_;
+
+  foreach my $bt_id (@{ $::form->{proposal_ids} }) {
+    #mark bt as booked
+    my $bt = SL::DB::Manager::BankTransaction->find_by(id => $bt_id);
+    $bt->invoice_amount($bt->amount);
+    $bt->save;
+
+    #pay invoice
+    my $arap = SL::DB::Manager::Invoice->find_by(id => $::form->{"proposed_invoice_$bt_id"});
+    $arap    = SL::DB::Manager::PurchaseInvoice->find_by(id => $::form->{"proposed_invoice_$bt_id"}) if not defined $arap;
+    $arap->pay_invoice(chart_id  => $bt->local_bank_account->chart_id,
+                       trans_id  => $arap->id,
+                       amount    => $arap->amount,
+                       transdate => $bt->transdate);
+    $arap->save;
+
+    #create record link
+    my @props = (
+        from_table => 'bank_transactions',
+        from_id    => $bt_id,
+        to_table   => $arap->is_sales ? 'ar' : 'ap',
+        to_id      => $arap->id,
+        );
+
+    my $existing = SL::DB::Manager::RecordLink->get_all(where => \@props, limit => 1)->[0];
+
+    SL::DB::RecordLink->new(@props)->save if !$existing;
+  }
+
+  flash('ok', t8('#1 proposal(s) saved.', scalar @{ $::form->{proposal_ids} }));
+
+  $self->action_list();
+}
+
+#
+# filters
+#
+
+sub check_auth {
+  $::auth->assert('bank_transaction');
+}
+
+#
+# helpers
+#
+
+sub make_filter_summary {
+  my ($self) = @_;
+
+  my $filter = $::form->{filter} || {};
+  my @filter_strings;
+
+  my @filters = (
+    [ $filter->{"transdate:date::ge"},  $::locale->text('Transdate') . " " . $::locale->text('From Date') ],
+    [ $filter->{"transdate:date::le"},  $::locale->text('Transdate') . " " . $::locale->text('To Date')   ],
+    [ $filter->{"valutadate:date::ge"}, $::locale->text('Valutadate') . " " . $::locale->text('From Date') ],
+    [ $filter->{"valutadate:date::le"}, $::locale->text('Valutadate') . " " . $::locale->text('To Date')   ],
+    [ $filter->{"amount:number"},       $::locale->text('Amount')                                           ],
+    [ $filter->{"bank_account_id:integer"}, $::locale->text('Local bank account')                                           ],
+  );
+
+  for (@filters) {
+    push @filter_strings, "$_->[1]: $_->[0]" if $_->[0];
+  }
+
+  $self->{filter_summary} = join ', ', @filter_strings;
+}
+
+sub prepare_report {
+  my ($self)      = @_;
+
+  my $callback    = $self->models->get_callback;
+
+  my $report      = SL::ReportGenerator->new(\%::myconfig, $::form);
+  $self->{report} = $report;
+
+  my @columns     = qw(transdate valudate remote_name remote_account_number remote_bank_code amount invoice_amount invoices currency purpose local_account_number local_bank_code id);
+  my @sortable    = qw(transdate valudate remote_name remote_account_number remote_bank_code amount                                  purpose local_account_number local_bank_code);
+
+  my %column_defs = (
+    transdate             => { sub => sub { $_[0]->transdate_as_date } },
+    valutadate            => { sub => sub { $_[0]->valutadate_as_date } },
+    remote_name           => { },
+    remote_account_number => { },
+    remote_bank_code      => { },
+    amount                => { sub => sub { $_[0]->amount_as_number },
+                               align => 'right' },
+    invoice_amount        => { sub => sub { $_[0]->invoice_amount_as_number },
+                               align => 'right' },
+    invoices              => { sub => sub { $_[0]->linked_invoices } },
+    currency              => { sub => sub { $_[0]->currency->name } },
+    purpose               => { },
+    local_account_number  => { sub => sub { $_[0]->local_bank_account->account_number } },
+    local_bank_code       => { sub => sub { $_[0]->local_bank_account->bank_code } },
+    id                    => {},
+  );
+
+  map { $column_defs{$_}->{text} ||= $::locale->text( $self->models->get_sort_spec->{$_}->{title} ) } keys %column_defs;
+
+  $report->set_options(
+    std_column_visibility => 1,
+    controller_class      => 'BankTransaction',
+    output_format         => 'HTML',
+    top_info_text         => $::locale->text('Bank transactions'),
+    title                 => $::locale->text('Bank transactions'),
+    allow_pdf_export      => 1,
+    allow_csv_export      => 1,
+  );
+  $report->set_columns(%column_defs);
+  $report->set_column_order(@columns);
+  $report->set_export_options(qw(list filter));
+  $report->set_options_from_form;
+  $self->models->disable_pagination if $report->{options}{output_format} =~ /^(pdf|csv)$/i;
+  $self->models->set_report_generator_sort_options(report => $report, sortable_columns => \@sortable);
+
+  my $bank_accounts = SL::DB::Manager::BankAccount->get_all();
+  my $label_sub = sub { t8('#1 - Account number #2, bank code #3, #4', $_[0]->name, $_[0]->account_number, $_[0]->bank_code, $_[0]->bank )};
+
+  $report->set_options(
+    raw_top_info_text     => $self->render('bank_transactions/report_top',    { output => 0 }, BANK_ACCOUNTS => $bank_accounts, label_sub => $label_sub),
+    raw_bottom_info_text  => $self->render('bank_transactions/report_bottom', { output => 0 }),
+  );
+}
+
+sub init_models {
+  my ($self) = @_;
+
+  SL::Controller::Helper::GetModels->new(
+    controller => $self,
+    sorted => {
+      _default => {
+        by    => 'transdate',
+        dir   => 1,
+      },
+      transdate             => t8('Transdate'),
+      remote_name           => t8('Remote name'),
+      amount                => t8('Amount'),
+      invoice_amount        => t8('Assigned'),
+      invoices              => t8('Linked invoices'),
+      valutadate            => t8('Valutadate'),
+      remote_account_number => t8('Remote account number'),
+      remote_bank_code      => t8('Remote bank code'),
+      currency              => t8('Currency'),
+      purpose               => t8('Purpose'),
+      local_account_number  => t8('Local account number'),
+      local_bank_code       => t8('Local bank code'),
+    },
+    with_objects => [ 'local_bank_account', 'currency' ],
+  );
+}
+
+1;