Dialogbuchungen aus Bankbewegungen teilweise Verbuchungen erlauben
[kivitendo-erp.git] / SL / Controller / BankTransaction.pm
index 0420b8b..2b33831 100644 (file)
@@ -20,6 +20,7 @@ use SL::DB::RecordLink;
 use SL::JSON;
 use SL::DB::Chart;
 use SL::DB::AccTransaction;
+use SL::DB::BankTransactionAccTrans;
 use SL::DB::Tax;
 use SL::DB::BankAccount;
 use SL::DB::RecordTemplate;
@@ -98,6 +99,7 @@ sub action_list {
     where        => [
       amount                => {ne => \'invoice_amount'},
       local_bank_account_id => $::form->{filter}{bank_account},
+      cleared               => 0,
       @where
     ],
   );
@@ -233,7 +235,7 @@ sub action_list {
 
   # for testing with t/bank/banktransaction.t :
   if ( $::form->{dont_render_for_test} ) {
-    return $bank_transactions;
+    return ( $bank_transactions , \@proposals );
   }
 
   $::request->layout->add_javascripts("kivi.BankTransaction.js");
@@ -556,20 +558,14 @@ sub save_single_bank_transaction {
 
   my $bank_transaction = $data{bank_transaction};
 
-  # see pod
-  if (@{ $bank_transaction->linked_invoices } || $bank_transaction->invoice_amount != 0) {
-        return {
-          %data,
-          result  => 'error',
-          message => $::locale->text("Bank transaction with id #1 has already been linked to one or more record and/or some amount is already assigned.", $bank_transaction->id),
-        };
-      }
   my (@warnings);
 
   my $worker = sub {
     my $bt_id                 = $data{bank_transaction_id};
     my $sign                  = $bank_transaction->amount < 0 ? -1 : 1;
     my $amount_of_transaction = $sign * $bank_transaction->amount;
+    my $assigned_amount       = $sign * $bank_transaction->invoice_amount;
+    my $not_assigned_amount   = $amount_of_transaction - $assigned_amount;
     my $payment_received      = $bank_transaction->amount > 0;
     my $payment_sent          = $bank_transaction->amount < 0;
 
@@ -632,73 +628,49 @@ sub save_single_bank_transaction {
       } else {
         $payment_type = 'without_skonto';
       };
-
-
-      # pay invoice or go to the next bank transaction if the amount is not sufficiently high
-      if ($invoice->open_amount <= $amount_of_transaction && $n_invoices < $max_invoices) {
-        my $open_amount = ($payment_type eq 'with_skonto_pt'?$invoice->amount_less_skonto:$invoice->open_amount);
-        # first calculate new bank transaction amount ...
-        if ($invoice->is_sales) {
-          $amount_of_transaction -= $sign * $open_amount;
-          $bank_transaction->invoice_amount($bank_transaction->invoice_amount + $open_amount);
-        } else {
-          $amount_of_transaction += $sign * $open_amount;
-          $bank_transaction->invoice_amount($bank_transaction->invoice_amount - $open_amount);
-        }
-        # ... and then pay the invoice
-        $invoice->pay_invoice(chart_id     => $bank_transaction->local_bank_account->chart_id,
-                              trans_id     => $invoice->id,
-                              amount       => $open_amount,
-                              payment_type => $payment_type,
-                              source       => $source,
-                              memo         => $memo,
-                              transdate    => $bank_transaction->transdate->to_kivitendo);
-      } else {
-        # use the whole amount of the bank transaction for the invoice, overpay the invoice if necessary
-
-        # this catches credit_notes and negative sales invoices
-        if ( $invoice->is_sales && $invoice->amount < 0 ) {
-          # $invoice->open_amount     is negative for credit_notes
-          # $bank_transaction->amount is negative for outgoing transactions
-          # so $amount_of_transaction is negative but needs positive
-          $amount_of_transaction *= -1;
-
-        } elsif (!$invoice->is_sales && $invoice->invoice_type =~ m/ap_transaction|purchase_invoice/) {
-          # $invoice->open_amount may be negative for ap_transaction but may be positiv for negativ ap_transaction
-          # if $invoice->open_amount is negative $bank_transaction->amount is positve
-          # if $invoice->open_amount is positive $bank_transaction->amount is negative
-          # but amount of transaction is for both positive
-          $amount_of_transaction *= -1 if $invoice->open_amount == - $amount_of_transaction;
-        }
-
-        my $overpaid_amount = $amount_of_transaction - $invoice->open_amount;
-        $invoice->pay_invoice(chart_id     => $bank_transaction->local_bank_account->chart_id,
-                              trans_id     => $invoice->id,
-                              amount       => $amount_of_transaction,
-                              payment_type => $payment_type,
-                              source       => $source,
-                              memo         => $memo,
-                              transdate    => $bank_transaction->transdate->to_kivitendo);
-        $bank_transaction->invoice_amount($bank_transaction->amount);
-        $amount_of_transaction = 0;
-
-        if ($overpaid_amount >= 0.01) {
-          push @warnings, {
-            %data,
-            result  => 'warning',
-            message => $::locale->text('Invoice #1 was overpaid by #2.', $invoice->invnumber, $::form->format_amount(\%::myconfig, $overpaid_amount, 2)),
-          };
-        }
-      }
+    # pay invoice
+    # TODO rewrite this: really booked amount should be a return value of Payment.pm
+    # also this controller shouldnt care about how to calc skonto. we simply delegate the
+    # payment_type to the helper and get the corresponding bank_transaction values back
+
+    my $open_amount = ($payment_type eq 'with_skonto_pt' ? $invoice->amount_less_skonto : $invoice->open_amount);
+    my $amount_for_booking = abs(($open_amount < $not_assigned_amount) ? $open_amount : $not_assigned_amount);
+    $amount_for_booking *= $sign;
+    $bank_transaction->invoice_amount($bank_transaction->invoice_amount + $amount_for_booking);
+
+    # ... and then pay the invoice
+    my @acc_ids = $invoice->pay_invoice(chart_id => $bank_transaction->local_bank_account->chart_id,
+                          trans_id     => $invoice->id,
+                          amount       => ($open_amount < $not_assigned_amount) ? $open_amount : $not_assigned_amount,
+                          payment_type => $payment_type,
+                          source       => $source,
+                          memo         => $memo,
+                          transdate    => $bank_transaction->transdate->to_kivitendo);
+    # ... and record the origin via BankTransactionAccTrans
+    if (scalar(@acc_ids) != 2) {
+      return {
+        %data,
+        result  => 'error',
+        message => $::locale->text("Unable to book transactions for bank purpose #1", $bank_transaction->purpose),
+      };
+    }
+    foreach my $acc_trans_id (@acc_ids) {
+        my $id_type = $invoice->is_sales ? 'ar' : 'ap';
+        my  %props_acc = (
+          acc_trans_id        => $acc_trans_id,
+          bank_transaction_id => $bank_transaction->id,
+          $id_type            => $invoice->id,
+        );
+        SL::DB::BankTransactionAccTrans->new(%props_acc)->save;
+    }
       # Record a record link from the bank transaction to the invoice
-      my @props = (
+      my %props = (
         from_table => 'bank_transactions',
         from_id    => $bt_id,
         to_table   => $invoice->is_sales ? 'ar' : 'ap',
         to_id      => $invoice->id,
       );
-
-      SL::DB::RecordLink->new(@props)->save;
+      SL::DB::RecordLink->new(%props)->save;
 
       # "close" a sepa_export_item if it exists
       # code duplicated in action_save_proposals!
@@ -797,7 +769,8 @@ sub prepare_report {
                                align => 'right' },
     invoice_amount        => { sub   => sub { $_[0]->invoice_amount_as_number },
                                align => 'right' },
-    invoices              => { sub   => sub { $_[0]->linked_invoices } },
+    invoices              => { sub   => sub { my @invnumbers; for my $obj (@{ $_[0]->linked_invoices }) {
+                                                                next unless $obj; push @invnumbers, $obj->invnumber } return \@invnumbers } },
     currency              => { sub   => sub { $_[0]->currency->name } },
     purpose               => { },
     local_account_number  => { sub   => sub { $_[0]->local_bank_account->account_number } },
@@ -886,10 +859,11 @@ sub load_gl_record_template_url {
     controller                           => 'gl.pl',
     action                               => 'load_record_template',
     id                                   => $template->id,
-    'form_defaults.amount_1'             => abs($self->transaction->amount), # always positive
+    'form_defaults.amount_1'             => abs($self->transaction->not_assigned_amount), # always positive
     'form_defaults.transdate'            => $self->transaction->transdate_as_date,
     'form_defaults.callback'             => $self->callback,
     'form_defaults.bt_id'                => $self->transaction->id,
+    'form_defaults.bt_chart_id'          => $self->transaction->local_bank_account->chart->id,
   );
 }
 
@@ -944,18 +918,15 @@ tries to post its amount to a certain number of invoices (parameter
 C<invoice_ids>, an array ref of database IDs to purchase or sales
 invoice objects).
 
+This method handles already partly assigned bank transactions.
+
 This method cannot handle already partly assigned bank transactions, i.e.
 a bank transaction that has a invoice_amount <> 0 but not the fully
 transaction amount (invoice_amount == amount).
-Currently this state is impossible from the point of the user interface,
-but for double safety and further reliance posting an bank_transaction
-where some invoice_amount is already assigned or a RecordLink from
-bank to document exists will not be accepted.
 
 If the amount of the bank transaction is higher than the sum of
-the assigned invoices (1 .. n) the last invoice will be overpayed.
-
-Therefore this function implements not all valid uses cases.
+the assigned invoices (1 .. n) the bank transaction will only be
+partly assigned.
 
 The whole function is wrapped in a database transaction. If an
 exception occurs the bank transaction is not posted at all. The same