Payment-Helfer: Rechnen mit undefinierten Werten vermeiden
[kivitendo-erp.git] / SL / DB / Helper / Payment.pm
index ed95971..64d6a63 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 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 exchangerate);
+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 exchangerate forex);
 our %EXPORT_TAGS = (
   "ALL" => [@EXPORT, @EXPORT_OK],
 );
 our %EXPORT_TAGS = (
   "ALL" => [@EXPORT, @EXPORT_OK],
 );
@@ -119,7 +119,7 @@ sub pay_invoice {
   my $fx_gain_loss_amount = 0; # for fx_gain and fx_loss
 
   my $db = $self->db;
   my $fx_gain_loss_amount = 0; # for fx_gain and fx_loss
 
   my $db = $self->db;
-  $db->do_transaction(sub {
+  $db->with_transaction(sub {
     my $new_acc_trans;
 
     # all three payment type create 1 AR/AP booking (the paid part)
     my $new_acc_trans;
 
     # all three payment type create 1 AR/AP booking (the paid part)
@@ -153,6 +153,7 @@ sub pay_invoice {
                                                    transdate  => $transdate_obj,
                                                    source     => $source,
                                                    memo       => $memo,
                                                    transdate  => $transdate_obj,
                                                    source     => $source,
                                                    memo       => $memo,
+                                                   project_id => $params{project_id} ? $params{project_id} : undef,
                                                    taxkey     => 0,
                                                    tax_id     => SL::DB::Manager::Tax->find_by(taxkey => 0)->id);
       $new_acc_trans->save;
                                                    taxkey     => 0,
                                                    tax_id     => SL::DB::Manager::Tax->find_by(taxkey => 0)->id);
       $new_acc_trans->save;
@@ -277,36 +278,38 @@ sub pay_invoice {
     # than adding them to the transaction relation array.
     $self->forget_related('transactions');
 
     # 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  ) ||
-         ( !$self->invoice && $::instance_conf->get_datev_check_on_ar_transaction )) {
-      $datev_check = 1;
-    };
-  } else {
-    if ( (  $self->invoice && $::instance_conf->get_datev_check_on_purchase_invoice ) ||
-         ( !$self->invoice && $::instance_conf->get_datev_check_on_ap_transaction   )) {
-      $datev_check = 1;
-    };
-  };
+    my $datev_check = 0;
+    if ( $is_sales )  {
+      if ( (  $self->invoice && $::instance_conf->get_datev_check_on_sales_invoice  ) ||
+           ( !$self->invoice && $::instance_conf->get_datev_check_on_ar_transaction )) {
+        $datev_check = 1;
+      }
+    } else {
+      if ( (  $self->invoice && $::instance_conf->get_datev_check_on_purchase_invoice ) ||
+           ( !$self->invoice && $::instance_conf->get_datev_check_on_ap_transaction   )) {
+        $datev_check = 1;
+      }
+    }
 
 
-  if ( $datev_check ) {
+    if ( $datev_check ) {
 
 
-    my $datev = SL::DATEV->new(
-      exporttype => DATEV_ET_BUCHUNGEN,
-      format     => DATEV_FORMAT_KNE,
-      dbh        => $db->dbh,
-      trans_id   => $self->{id},
-    );
+      my $datev = SL::DATEV->new(
+        exporttype => DATEV_ET_BUCHUNGEN,
+        format     => DATEV_FORMAT_KNE,
+        dbh        => $db->dbh,
+        trans_id   => $self->{id},
+      );
 
 
-    $datev->clean_temporary_directories;
-    $datev->export;
+      $datev->clean_temporary_directories;
+      $datev->export;
 
 
-    if ($datev->errors) {
-      # this exception should be caught by do_transaction, which handles the rollback
-      die join "\n", $::locale->text('DATEV check returned errors:'), $datev->errors;
+      if ($datev->errors) {
+        # this exception should be caught by with_transaction, which handles the rollback
+        die join "\n", $::locale->text('DATEV check returned errors:'), $datev->errors;
+      }
     }
     }
-  };
+
+    1;
 
   }) || die t8('error while paying invoice #1 : ', $self->invnumber) . $db->error . "\n";
 
 
   }) || die t8('error while paying invoice #1 : ', $self->invnumber) . $db->error . "\n";
 
@@ -384,7 +387,7 @@ sub open_amount {
   # if the difference is 0.01 Cent this may end up as 0.009999999999998
   # numerically, so round this value when checking for cent threshold >= 0.01
 
   # if the difference is 0.01 Cent this may end up as 0.009999999999998
   # numerically, so round this value when checking for cent threshold >= 0.01
 
-  return $self->amount - $self->paid;
+  return ($self->amount // 0) - ($self->paid // 0);
 };
 
 sub open_percent {
 };
 
 sub open_percent {
@@ -656,11 +659,13 @@ sub exchangerate {
 
   return 1 if $self->currency_id == $::instance_conf->get_currency_id;
 
 
   return 1 if $self->currency_id == $::instance_conf->get_currency_id;
 
+  die "transdate isn't a DateTime object:" . ref($self->transdate) unless ref($self->transdate) eq 'DateTime';
   my $rate = SL::DB::Manager::Exchangerate->find_by(currency_id => $self->currency_id,
                                                     transdate   => $self->transdate,
                                                    );
   return undef unless $rate;
   my $rate = SL::DB::Manager::Exchangerate->find_by(currency_id => $self->currency_id,
                                                     transdate   => $self->transdate,
                                                    );
   return undef unless $rate;
-  $self->is_sales ? return $rate->sell : return $rate->buy;
+
+  return $self->is_sales ? $rate->buy : $rate->sell; # also undef if not defined
 };
 
 sub get_payment_suggestions {
 };
 
 sub get_payment_suggestions {
@@ -740,11 +745,16 @@ sub create_bank_transaction {
     amount                => $::form->round_amount($amount, 2),
     currency              => $self->currency->id,
     remote_name           => $self->customervendor->depositor,
     amount                => $::form->round_amount($amount, 2),
     currency              => $self->currency->id,
     remote_name           => $self->customervendor->depositor,
-    purpose               => $self->invnumber
+    purpose               => $params{purpose} || $self->invnumber
   )->save;
 };
 
 
   )->save;
 };
 
 
+sub forex {
+  my ($self) = @_;
+  $self->currency_id == $::instance_conf->get_currency_id ? return 0 : return 1;
+};
+
 sub _round {
   my $value = shift;
   my $num_dec = 2;
 sub _round {
   my $value = shift;
   my $num_dec = 2;
@@ -792,6 +802,7 @@ Example:
                    memo          => 'foobar',
                    source        => 'barfoo',
                    payment_type  => 'without_skonto',  # default if not specified
                    memo          => 'foobar',
                    source        => 'barfoo',
                    payment_type  => 'without_skonto',  # default if not specified
+                   project_id    => 25,
                   );
 
 or with skonto:
                   );
 
 or with skonto:
@@ -1099,13 +1110,43 @@ 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));
 
  my $ap = SL::DB::Manager::Invoice->find_by(id => 41);
  $ap->create_bank_transaction(amount => $ap->amount/2, transdate => DateTime->today->add(days => 5));
 
+To create a payment for 3 invoices that were all paid together, all with skonto:
+ my $ar1 = SL::DB::Manager::Invoice->find_by(invnumber=>'20');
+ my $ar2 = SL::DB::Manager::Invoice->find_by(invnumber=>'21');
+ my $ar3 = SL::DB::Manager::Invoice->find_by(invnumber=>'22');
+ $ar1->create_bank_transaction(amount  => ($ar1->amount_less_skonto + $ar2->amount_less_skonto + $ar2->amount_less_skonto),
+                               purpose => 'Rechnungen 20, 21, 22',
+                              );
+
 Amount is always relative to the absolute amount of the invoice, use positive
 values for sales and purchases.
 
 Amount is always relative to the absolute amount of the invoice, use positive
 values for sales and purchases.
 
+The following params can be passed to override the defaults:
+
+=over 2
+
+=item * amount
+
+=item * purpose
+
+=item * chart_id (the chart the amount is to be paid to)
+
+=item * transdate
+
+=back
+
 =item C<exchangerate>
 
 =item C<exchangerate>
 
-Returns the exchangerate in database format for the invoice according to that invoice's transdate.
-Returns 'sell' for sales, 'buy' for purchases.
+Returns 1 immediately if the record uses the default currency.
+
+Returns the exchangerate in database format for the invoice according to that
+invoice's transdate, returning 'buy' for sales, 'sell' for purchases.
+
+If no exchangerate can be found for that day undef is returned.
+
+=item C<forex>
+
+Returns 1 if record uses a different currency, 0 if the default currency is used.
 
 =back
 
 
 =back