Neue PaymentHelper Funktion create_bank_transaction
[kivitendo-erp.git] / SL / DB / Helper / Payment.pm
index baa2366..6ab8691 100644 (file)
@@ -4,7 +4,7 @@ use strict;
 
 use parent qw(Exporter);
 our @EXPORT = qw(pay_invoice);
 
 use parent qw(Exporter);
 our @EXPORT = qw(pay_invoice);
-our @EXPORT_OK = qw(skonto_date skonto_charts amount_less_skonto within_skonto_period percent_skonto reference_account reference_amount transactions open_amount open_percent remaining_skonto_days skonto_amount check_skonto_configuration valid_skonto_amount get_payment_suggestions validate_payment_type open_sepa_transfer_amount get_payment_select_options_for_bank_transaction);
+our @EXPORT_OK = qw(skonto_date skonto_charts amount_less_skonto within_skonto_period percent_skonto reference_account reference_amount open_amount open_percent remaining_skonto_days skonto_amount check_skonto_configuration valid_skonto_amount get_payment_suggestions validate_payment_type open_sepa_transfer_amount get_payment_select_options_for_bank_transaction create_bank_transaction);
 our %EXPORT_TAGS = (
   "ALL" => [@EXPORT, @EXPORT_OK],
 );
 our %EXPORT_TAGS = (
   "ALL" => [@EXPORT, @EXPORT_OK],
 );
@@ -154,13 +154,17 @@ sub pay_invoice {
                                                      source     => $params{source},
                                                      taxkey     => 0,
                                                      tax_id     => SL::DB::Manager::Tax->find_by(taxkey => 0)->id);
                                                      source     => $params{source},
                                                      taxkey     => 0,
                                                      tax_id     => SL::DB::Manager::Tax->find_by(taxkey => 0)->id);
+
+        # the acc_trans entries are saved individually, not added to $self and then saved all at once
         $new_acc_trans->save;
 
         $reference_amount -= abs($amount);
         $paid_amount      += -1 * $amount;
         $skonto_amount_check -= $skonto_booking->{'skonto_amount'};
       };
         $new_acc_trans->save;
 
         $reference_amount -= abs($amount);
         $paid_amount      += -1 * $amount;
         $skonto_amount_check -= $skonto_booking->{'skonto_amount'};
       };
-      die "difference_as_skonto calculated incorrectly, sum of calculated payments doesn't add up to open amount $total_open_amount, reference_amount = $reference_amount\n" unless _round($reference_amount) == 0;
+      if ( $params{payment_type} eq 'difference_as_skonto' ) {
+          die "difference_as_skonto calculated incorrectly, sum of calculated payments doesn't add up to open amount $total_open_amount, reference_amount = $reference_amount\n" unless _round($reference_amount) == 0;
+      }
 
     };
 
 
     };
 
@@ -193,6 +197,11 @@ sub pay_invoice {
     $self->datepaid($transdate_obj);
     $self->save;
 
     $self->datepaid($transdate_obj);
     $self->save;
 
+    # make sure transactions will be reloaded the next time $self->transactions
+    # is called, as pay_invoice saves the acc_trans objects individually rather
+    # than adding them to the transaction relation array.
+    $self->forget_related('transactions');
+
   my $datev_check = 0;
   if ( $is_sales )  {
     if ( (  $self->invoice && $::instance_conf->get_datev_check_on_sales_invoice  ) ||
   my $datev_check = 0;
   if ( $is_sales )  {
     if ( (  $self->invoice && $::instance_conf->get_datev_check_on_sales_invoice  ) ||
@@ -361,7 +370,7 @@ sub amount_less_skonto {
 
   my $is_sales = ref($self) eq 'SL::DB::Invoice';
 
 
   my $is_sales = ref($self) eq 'SL::DB::Invoice';
 
-  my $percent_skonto = $self->percent_skonto;
+  my $percent_skonto = $self->percent_skonto || 0;
 
   return _round($self->amount - ( $self->amount * $percent_skonto) );
 
 
   return _round($self->amount - ( $self->amount * $percent_skonto) );
 
@@ -374,8 +383,8 @@ sub check_skonto_configuration {
 
   my $skonto_configured = 1; # default is assume skonto works
 
 
   my $skonto_configured = 1; # default is assume skonto works
 
-  my $transactions = $self->transactions;
-  foreach my $transaction (@{ $transactions }) {
+  my $transactions = $self->transactions;
+  foreach my $transaction (@{ $self->transactions }) {
     # find all transactions with an AR_amount or AP_amount link
     my $tax = SL::DB::Manager::Tax->get_first( where => [taxkey => $transaction->{taxkey}]);
     croak "no tax for taxkey " . $transaction->{taxkey} unless ref $tax;
     # find all transactions with an AR_amount or AP_amount link
     my $tax = SL::DB::Manager::Tax->get_first( where => [taxkey => $transaction->{taxkey}]);
     croak "no tax for taxkey " . $transaction->{taxkey} unless ref $tax;
@@ -448,8 +457,8 @@ sub skonto_charts {
 
   my $reference_ARAP_amount = 0;
 
 
   my $reference_ARAP_amount = 0;
 
-  my $transactions = $self->transactions;
-  foreach my $transaction (@{ $transactions }) {
+  my $transactions = $self->transactions;
+  foreach my $transaction (@{ $self->transactions }) {
     # find all transactions with an AR_amount or AP_amount link
     $transaction->{chartlinks} = { map { $_ => 1 } split(m/:/, $transaction->{chart_link}) };
     # second condition is that we can determine an automatic Skonto account for each AR_amount entry
     # find all transactions with an AR_amount or AP_amount link
     $transaction->{chartlinks} = { map { $_ => 1 } split(m/:/, $transaction->{chart_link}) };
     # second condition is that we can determine an automatic Skonto account for each AR_amount entry
@@ -551,13 +560,14 @@ sub get_payment_select_options_for_bank_transaction {
 
   my @options;
   if ( $open_amount &&                   # invoice amount not 0
 
   my @options;
   if ( $open_amount &&                   # invoice amount not 0
+       $self->skonto_date &&             # check whether skonto applies
        abs(abs($self->amount_less_skonto) - abs($bt->amount)) < 0.01 &&
        $self->check_skonto_configuration) {
          if ( $self->within_skonto_period($bt->transdate) ) {
            push(@options, { payment_type => 'without_skonto', display => t8('without skonto') });
            push(@options, { payment_type => 'with_skonto_pt', display => t8('with skonto acc. to pt'), selected => 1 });
          } else {
        abs(abs($self->amount_less_skonto) - abs($bt->amount)) < 0.01 &&
        $self->check_skonto_configuration) {
          if ( $self->within_skonto_period($bt->transdate) ) {
            push(@options, { payment_type => 'without_skonto', display => t8('without skonto') });
            push(@options, { payment_type => 'with_skonto_pt', display => t8('with skonto acc. to pt'), selected => 1 });
          } else {
-           push(@options, { payment_type => 'without_skonto', display => t8('without skonto') }, selected => 1 );
+           push(@options, { payment_type => 'without_skonto', display => t8('without skonto') , selected => 1 });
            push(@options, { payment_type => 'with_skonto_pt', display => t8('with skonto acc. to pt')});
          };
   };
            push(@options, { payment_type => 'with_skonto_pt', display => t8('with skonto acc. to pt')});
          };
   };
@@ -605,15 +615,6 @@ sub get_payment_suggestions {
   return 1;
 };
 
   return 1;
 };
 
-sub transactions {
-  my ($self) = @_;
-
-  return unless $self->id;
-
-  require SL::DB::AccTransaction;
-  SL::DB::Manager::AccTransaction->get_all(query => [ trans_id => $self->id ]);
-}
-
 sub validate_payment_type {
   my $payment_type = shift;
 
 sub validate_payment_type {
   my $payment_type = shift;
 
@@ -623,6 +624,41 @@ sub validate_payment_type {
   return 1;
 }
 
   return 1;
 }
 
+sub create_bank_transaction {
+  my ($self, %params) = @_;
+
+  require SL::DB::Chart;
+  require SL::DB::BankAccount;
+
+  my $bank_chart;
+  if ( $params{chart_id} ) {
+    $bank_chart = SL::DB::Manager::Chart->find_by(chart_id => $params{chart_id}) or die "Can't find bank chart";
+  } elsif ( $::instance_conf->get_ar_paid_accno_id ) {
+    $bank_chart   = SL::DB::Manager::Chart->find_by(id => $::instance_conf->get_ar_paid_accno_id);
+  } else {
+    $bank_chart = SL::DB::Manager::Chart->find_by(description => 'Bank') or die "Can't find bank chart";
+  };
+  my $bank_account = SL::DB::Manager::BankAccount->find_by(chart_id => $bank_chart->id) or die "Can't find bank account for chart";
+
+  my $multiplier = $self->is_sales ? 1 : -1;
+  my $amount = ($params{amount} || $self->amount) * $multiplier;
+
+  my $transdate = $params{transdate} || DateTime->today;
+
+  my $bt = SL::DB::BankTransaction->new(
+    local_bank_account_id => $bank_account->id,
+    remote_bank_code      => $self->customervendor->bank_code,
+    remote_account_number => $self->customervendor->account_number,
+    transdate             => $transdate,
+    valutadate            => $transdate,
+    amount                => $::form->round_amount($amount, 2),
+    currency              => $self->currency->id,
+    remote_name           => $self->customervendor->depositor,
+    purpose               => $self->invnumber
+  )->save;
+};
+
+
 sub _round {
   my $value = shift;
   my $num_dec = 2;
 sub _round {
   my $value = shift;
   my $num_dec = 2;
@@ -667,8 +703,8 @@ Example:
   $ap->pay_invoice(chart_id      => $bank->chart_id,
                    amount        => $ap->open_amount,
                    transdate     => DateTime->now->to_kivitendo,
   $ap->pay_invoice(chart_id      => $bank->chart_id,
                    amount        => $ap->open_amount,
                    transdate     => DateTime->now->to_kivitendo,
-                   memo          => 'foobar;
-                   source        => 'barfoo;
+                   memo          => 'foobar',
+                   source        => 'barfoo',
                    payment_type  => 'without_skonto',  # default if not specified
                   );
 
                    payment_type  => 'without_skonto',  # default if not specified
                   );
 
@@ -676,8 +712,8 @@ or with skonto:
   $ap->pay_invoice(chart_id      => $bank->chart_id,
                    amount        => $ap->amount,       # doesn't need to be specified
                    transdate     => DateTime->now->to_kivitendo,
   $ap->pay_invoice(chart_id      => $bank->chart_id,
                    amount        => $ap->amount,       # doesn't need to be specified
                    transdate     => DateTime->now->to_kivitendo,
-                   memo          => 'foobar;
-                   source        => 'barfoo;
+                   memo          => 'foobar',
+                   source        => 'barfoo',
                    payment_type  => 'with_skonto',
                   );
 
                    payment_type  => 'with_skonto',
                   );
 
@@ -941,23 +977,6 @@ defaults. E.g. when creating a SEPA bank transfer for vendor invoices a company
 might always want to pay quickly making use of skonto, while another company
 might always want to pay as late as possible.
 
 might always want to pay quickly making use of skonto, while another company
 might always want to pay as late as possible.
 
-=item C<transactions>
-
-Returns all acc_trans Objects of an ar/ap object.
-
-Example in console to print account numbers and booked amounts of an invoice:
-  my $invoice = invoice(invnumber => '144');
-  foreach my $acc_trans ( @{ $invoice->transactions } ) {
-    print $acc_trans->chart->accno . " : " . $acc_trans->amount_as_number . "\n"
-  };
-  # 1200 : 226,00000
-  # 1800 : -226,00000
-  # 4300 : 100,00000
-  # 3801 : 7,00000
-  # 3806 : 19,00000
-  # 4400 : 100,00000
-  # 1200 : -226,00000
-
 =item C<get_payment_select_options_for_bank_transaction $banktransaction_id %params>
 
 Make suggestion for a skonto payment type by returning an HTML blob of the options
 =item C<get_payment_select_options_for_bank_transaction $banktransaction_id %params>
 
 Make suggestion for a skonto payment type by returning an HTML blob of the options
@@ -971,6 +990,17 @@ If skonto is possible (skonto_date exists), add two possibilities:
 without_skonto and with_skonto_pt if payment date is within skonto_date,
 preselect with_skonto_pt, otherwise preselect without skonto.
 
 without_skonto and with_skonto_pt if payment date is within skonto_date,
 preselect with_skonto_pt, otherwise preselect without skonto.
 
+=item C<create_bank_transaction %params>
+
+Method used for testing purposes, allows you to quickly create bank
+transactions from invoices to have something to test payments against.
+
+ my $ap = SL::DB::Manager::Invoice->find_by(id => 41);
+ $ap->create_bank_transaction(amount => $ap->amount/2, transdate => DateTime->today->add(days => 5));
+
+Amount is always relative to the absolute amount of the invoice, use positive
+values for sales and purchases.
+
 =back
 
 =head1 TODO AND CAVEATS
 =back
 
 =head1 TODO AND CAVEATS