Update .htaccess for Apache 2.4 to avoid mod_access_compat dependency
[kivitendo-erp.git] / SL / DB / Helper / Payment.pm
index 3c34dce..c35b12c 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);
+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 exchangerate forex);
 our %EXPORT_TAGS = (
   "ALL" => [@EXPORT, @EXPORT_OK],
 );
 our %EXPORT_TAGS = (
   "ALL" => [@EXPORT, @EXPORT_OK],
 );
@@ -15,6 +15,8 @@ use DateTime;
 use SL::DATEV qw(:CONSTANTS);
 use SL::Locale::String qw(t8);
 use List::Util qw(sum);
 use SL::DATEV qw(:CONSTANTS);
 use SL::Locale::String qw(t8);
 use List::Util qw(sum);
+use SL::DB::Exchangerate;
+use SL::DB::Currency;
 use Carp;
 
 #
 use Carp;
 
 #
@@ -40,7 +42,6 @@ sub pay_invoice {
 
   my $transdate_obj;
   if (ref($params{transdate} eq 'DateTime')) {
 
   my $transdate_obj;
   if (ref($params{transdate} eq 'DateTime')) {
-    print "found transdate ref\n"; sleep 2;
     $transdate_obj = $params{transdate};
   } else {
    $transdate_obj = $::locale->parse_date_to_object($params{transdate});
     $transdate_obj = $params{transdate};
   } else {
    $transdate_obj = $::locale->parse_date_to_object($params{transdate});
@@ -93,7 +94,7 @@ sub pay_invoice {
   if ( $params{'payment_type'} eq 'difference_as_skonto' ) {
     croak "amount $params{amount} doesn't match open amount " . $self->open_amount . ", diff = " . ($params{amount}-$self->open_amount) if $params{amount} && abs($self->open_amount - $params{amount} ) > 0.0000001;
   } elsif ( $params{'payment_type'} eq 'with_skonto_pt' ) {
   if ( $params{'payment_type'} eq 'difference_as_skonto' ) {
     croak "amount $params{amount} doesn't match open amount " . $self->open_amount . ", diff = " . ($params{amount}-$self->open_amount) if $params{amount} && abs($self->open_amount - $params{amount} ) > 0.0000001;
   } elsif ( $params{'payment_type'} eq 'with_skonto_pt' ) {
-    croak "amount $params{amount} doesn't match amount less skonto: " . $self->open_amount . "\n" if $params{amount} && abs($self->amount_less_skonto - $params{amount} ) > 0.0000001;
+    croak "amount $params{amount} doesn't match amount less skonto: " . $self->amount_less_skonto . "\n" if $params{amount} && abs($self->amount_less_skonto - $params{amount} ) > 0.0000001;
     croak "payment type with_skonto_pt can't be used if payments have already been made" if $self->paid != 0;
   };
 
     croak "payment type with_skonto_pt can't be used if payments have already been made" if $self->paid != 0;
   };
 
@@ -111,13 +112,14 @@ sub pay_invoice {
   my $reference_account = $self->reference_account;
   croak "can't find reference account (link = AR/AP) for invoice" unless ref $reference_account;
 
   my $reference_account = $self->reference_account;
   croak "can't find reference account (link = AR/AP) for invoice" unless ref $reference_account;
 
-  my $memo   = $params{'memo'}   || '';
-  my $source = $params{'source'} || '';
+  my $memo   = $params{memo}   // '';
+  my $source = $params{source} // '';
 
   my $rounded_params_amount = _round( $params{amount} ); # / $exchangerate);
 
   my $rounded_params_amount = _round( $params{amount} ); # / $exchangerate);
+  my $fx_gain_loss_amount = 0; # for fx_gain and fx_loss
 
   my $db = $self->db;
 
   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)
@@ -151,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;
@@ -158,8 +161,6 @@ sub pay_invoice {
       # deal with fxtransaction
       if ( $self->currency_id != $::instance_conf->get_currency_id ) {
         my $fxamount = _round($amount - ($amount * $exchangerate));
       # deal with fxtransaction
       if ( $self->currency_id != $::instance_conf->get_currency_id ) {
         my $fxamount = _round($amount - ($amount * $exchangerate));
-        # print "amount: $amount, fxamount = $fxamount\n";
-        # print "amount - (amount * exchangerate) = " . $amount . " - (" . $amount . " - " . $exchangerate . ")\n";
         $new_acc_trans = SL::DB::AccTransaction->new(trans_id       => $self->id,
                                                      chart_id       => $account_bank->id,
                                                      chart_link     => $account_bank->link,
         $new_acc_trans = SL::DB::AccTransaction->new(trans_id       => $self->id,
                                                      chart_id       => $account_bank->id,
                                                      chart_link     => $account_bank->link,
@@ -171,6 +172,28 @@ sub pay_invoice {
                                                      fx_transaction => 1,
                                                      tax_id         => SL::DB::Manager::Tax->find_by(taxkey => 0)->id);
         $new_acc_trans->save;
                                                      fx_transaction => 1,
                                                      tax_id         => SL::DB::Manager::Tax->find_by(taxkey => 0)->id);
         $new_acc_trans->save;
+        # if invoice exchangerate differs from exchangerate of payment
+        # deal with fxloss and fxamount
+        if ($self->exchangerate and $self->exchangerate != 1 and $self->exchangerate != $exchangerate) {
+          my $fxgain_chart = SL::DB::Manager::Chart->find_by(id => $::instance_conf->get_fxgain_accno_id) || die "Can't determine fxgain chart";
+          my $fxloss_chart = SL::DB::Manager::Chart->find_by(id => $::instance_conf->get_fxloss_accno_id) || die "Can't determine fxloss chart";
+          my $gain_loss_amount = _round($amount * ($exchangerate - $self->exchangerate ) * -1,2);
+          my $gain_loss_chart = $gain_loss_amount > 0 ? $fxgain_chart : $fxloss_chart;
+          $fx_gain_loss_amount = $gain_loss_amount;
+
+          $new_acc_trans = SL::DB::AccTransaction->new(trans_id       => $self->id,
+                                                       chart_id       => $gain_loss_chart->id,
+                                                       chart_link     => $gain_loss_chart->link,
+                                                       amount         => $gain_loss_amount,
+                                                       transdate      => $transdate_obj,
+                                                       source         => $source,
+                                                       memo           => $memo,
+                                                       taxkey         => 0,
+                                                       fx_transaction => 0,
+                                                       tax_id         => SL::DB::Manager::Tax->find_by(taxkey => 0)->id);
+          $new_acc_trans->save;
+
+        };
       };
     };
 
       };
     };
 
@@ -238,14 +261,15 @@ sub pay_invoice {
     my $arap_booking= SL::DB::AccTransaction->new(trans_id   => $self->id,
                                                   chart_id   => $reference_account->id,
                                                   chart_link => $reference_account->link,
     my $arap_booking= SL::DB::AccTransaction->new(trans_id   => $self->id,
                                                   chart_id   => $reference_account->id,
                                                   chart_link => $reference_account->link,
-                                                  amount     => _round($arap_amount * $mult * $exchangerate),
+                                                  amount     => _round($arap_amount * $mult * $exchangerate - $fx_gain_loss_amount),
                                                   transdate  => $transdate_obj,
                                                   source     => '', #$params{source},
                                                   taxkey     => 0,
                                                   tax_id     => SL::DB::Manager::Tax->find_by(taxkey => 0)->id);
     $arap_booking->save;
 
                                                   transdate  => $transdate_obj,
                                                   source     => '', #$params{source},
                                                   taxkey     => 0,
                                                   tax_id     => SL::DB::Manager::Tax->find_by(taxkey => 0)->id);
     $arap_booking->save;
 
-    $self->paid($self->paid + _round($paid_amount)) if $paid_amount;
+    $fx_gain_loss_amount *= -1 if $self->is_sales;
+    $self->paid($self->paid + _round($paid_amount) + $fx_gain_loss_amount) if $paid_amount;
     $self->datepaid($transdate_obj);
     $self->save;
 
     $self->datepaid($transdate_obj);
     $self->save;
 
@@ -254,36 +278,35 @@ 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(
+        dbh        => $db->dbh,
+        trans_id   => $self->{id},
+      );
 
 
-    $datev->clean_temporary_directories;
-    $datev->export;
+      $datev->generate_datev_data;
 
 
-    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";
 
@@ -361,7 +384,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 {
@@ -609,11 +632,12 @@ sub get_payment_select_options_for_bank_transaction {
   die unless $bt;
 
   my $open_amount = $self->open_amount;
   die unless $bt;
 
   my $open_amount = $self->open_amount;
-
+  #$main::lxdebug->message(LXDebug->DEBUG2(),"skonto_date=".$self->skonto_date." open amount=".$open_amount);
   my @options;
   if ( $open_amount &&                   # invoice amount not 0
        $self->skonto_date &&             # check whether skonto applies
   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 &&
+       ( abs(abs($self->amount_less_skonto) - abs($bt->amount)) < 0.01 ||
+        ( abs($self->amount_less_skonto) < abs($bt->amount) )) &&
        $self->check_skonto_configuration) {
          if ( $self->within_skonto_period($bt->transdate) ) {
            push(@options, { payment_type => 'without_skonto', display => t8('without skonto') });
        $self->check_skonto_configuration) {
          if ( $self->within_skonto_period($bt->transdate) ) {
            push(@options, { payment_type => 'without_skonto', display => t8('without skonto') });
@@ -628,6 +652,19 @@ sub get_payment_select_options_for_bank_transaction {
 
 };
 
 
 };
 
+sub exchangerate {
+  my ($self) = @_;
+
+  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;
+
+  return $self->is_sales ? $rate->buy : $rate->sell; # also undef if not defined
+};
 
 sub get_payment_suggestions {
 
 
 sub get_payment_suggestions {
 
@@ -667,6 +704,13 @@ sub get_payment_suggestions {
   return 1;
 };
 
   return 1;
 };
 
+# locales for payment type
+#
+# $main::locale->text('without_skonto')
+# $main::locale->text('with_skonto_pt')
+# $main::locale->text('difference_as_skonto')
+#
+
 sub validate_payment_type {
   my $payment_type = shift;
 
 sub validate_payment_type {
   my $payment_type = shift;
 
@@ -676,41 +720,11 @@ 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 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;
@@ -758,6 +772,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:
@@ -1057,16 +1072,18 @@ 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>
+=item C<exchangerate>
+
+Returns 1 immediately if the record uses the default currency.
 
 
-Method used for testing purposes, allows you to quickly create bank
-transactions from invoices to have something to test payments against.
+Returns the exchangerate in database format for the invoice according to that
+invoice's transdate, returning 'buy' for sales, 'sell' for purchases.
 
 
- my $ap = SL::DB::Manager::Invoice->find_by(id => 41);
- $ap->create_bank_transaction(amount => $ap->amount/2, transdate => DateTime->today->add(days => 5));
+If no exchangerate can be found for that day undef is returned.
 
 
-Amount is always relative to the absolute amount of the invoice, use positive
-values for sales and purchases.
+=item C<forex>
+
+Returns 1 if record uses a different currency, 0 if the default currency is used.
 
 =back
 
 
 =back
 
@@ -1079,10 +1096,6 @@ values for sales and purchases.
 when looking at open amount, maybe consider that there may already be queued
 amounts in SEPA Export
 
 when looking at open amount, maybe consider that there may already be queued
 amounts in SEPA Export
 
-=item *
-
-Can only handle default currency.
-
 =back
 
 =head1 AUTHOR
 =back
 
 =head1 AUTHOR