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::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;
 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},
     where        => [
       amount                => {ne => \'invoice_amount'},
       local_bank_account_id => $::form->{filter}{bank_account},
+      cleared               => 0,
       @where
     ],
   );
       @where
     ],
   );
@@ -233,7 +235,7 @@ sub action_list {
 
   # for testing with t/bank/banktransaction.t :
   if ( $::form->{dont_render_for_test} ) {
 
   # 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");
   }
 
   $::request->layout->add_javascripts("kivi.BankTransaction.js");
@@ -556,20 +558,14 @@ sub save_single_bank_transaction {
 
   my $bank_transaction = $data{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 (@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;
 
     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';
       };
       } 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
       # 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,
       );
         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!
 
       # "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' },
                                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 } },
     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,
     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.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).
 
 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).
 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
 
 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
 
 The whole function is wrapped in a database transaction. If an
 exception occurs the bank transaction is not posted at all. The same