Projekt

Allgemein

Profil

« Zurück | Weiter » 

Revision 58dbf540

Von Jan Büren vor fast 2 Jahren hinzugefügt

  • ID 58dbf540acafcff6415e49f6dc9b25cae33b42f9
  • Vorgänger 3804562a
  • Nachfolger aa184cd2

Wechselkurs beim Bankauszug verbuchen.

> Testfälle i.O. (bank_transaction.t)
> manuelle Prüfung fast i.O. (sechs Fälle) Rundungsdifferenzen
> Debugs n.i.O.
> automatisierte Testfälle n.i.O.

Unterschiede anzeigen:

SL/Controller/BankTransaction.pm
675 675
    # payment_type to the helper and get the corresponding bank_transaction values back
676 676
    # hotfix to get the signs right - compare absolute values and later set the signs
677 677
    # should be better done elsewhere - changing not_assigned_amount to abs feels seriously bogus
678

  
678
    # default open amount
679 679
    my $open_amount = $payment_type eq 'with_skonto_pt' ? $invoice->amount_less_skonto : $invoice->open_amount;
680
    $open_amount            = abs($open_amount);
681
    $open_amount           -= $free_skonto_amount if ($payment_type eq 'free_skonto');
682
    my $not_assigned_amount = abs($bank_transaction->not_assigned_amount);
683
    my $amount_for_payment  = ($open_amount < $not_assigned_amount) ? $open_amount : $not_assigned_amount;
684
    my $amount_for_booking  = ($open_amount < $not_assigned_amount) ? $open_amount : $not_assigned_amount;
685

  
686
    # get the right direction for the payment bookings (all amounts < 0 are stornos, credit notes or negative ap)
687
    $amount_for_payment *= -1 if $invoice->amount < 0;
688
    $free_skonto_amount *= -1 if ($free_skonto_amount && $invoice->amount < 0);
689
    # get the right direction for the bank transaction
690
    # sign is simply the sign of amount in bank_transactions: positive for increase and negative for decrease
691
    $amount_for_booking *= $sign;
692

  
693
    # check exchangerate and if fx_loss calculate new booking_amount for this invoice
694
    if ($fx_rate > 0)  {
680
    # if fx calc new open amount with skonto pt and set new exchange rate (default or for bank_transaction)
681
    if ($fx_rate > 0) {
682
      # 1. set new open amount
695 683
      die "Exchangerate without currency"                     unless $currency_id;
696 684
      die "Invoice currency differs from user input currency" unless $currency_id == $invoice->currency->id;
697

  
698
      # 1 set daily default or custom record exchange rate
685
      $open_amount  = $payment_type eq 'with_skonto_pt' ? $invoice->amount_less_skonto_fx($fx_rate) : $invoice->open_amount_fx($fx_rate);
686
      # 2. set daily default or custom record exchange rate
699 687
      my $default_rate = $invoice->get_exchangerate_for_bank_transaction($bank_transaction->id);
700 688
      if (!$default_rate) { # set new daily default
701
        # helper
702 689
        my $buysell = $invoice->is_sales ? 'buy' : 'sell';
703 690
        my $ex = SL::DB::Manager::Exchangerate->find_by(currency_id => $currency_id,
704 691
                                                        transdate => $bank_transaction->valutadate)
......
709 696
      } elsif ($default_rate != $fx_rate) {           # set record (banktransaction) exchangerate
710 697
        $bank_transaction->exchangerate($fx_rate);    # custom rate, will be displayed in ap, ir, is
711 698
      } elsif (abs($default_rate - $fx_rate) < 0.001) {
712
        # should be last valid state -> do nothing
699
        # last valid state default rate is (nearly) the same as user input -> do nothing
713 700
      } else { die "Invalid exchange rate state:" . $default_rate . " " . $fx_rate; }
701
    } # end fx hook
702

  
703
    # open amount is in default currency -> free_skonto is in default currency, no need to change
704
    $open_amount            = abs($open_amount);
705
    $open_amount           -= $free_skonto_amount if ($payment_type eq 'free_skonto');
706
    my $not_assigned_amount = abs($bank_transaction->not_assigned_amount);
707
    my $amount_for_booking  = ($open_amount < $not_assigned_amount) ? $open_amount : $not_assigned_amount;
708
    my $fx_fee_amount       = $fx_book && ($open_amount < $not_assigned_amount) ? $not_assigned_amount - $open_amount : 0;
709
    my $amount_for_payment  = $amount_for_booking;
710
    # add booking amount
711
    # $amount_for_booking
714 712

  
713
    # get the right direction for the payment bookings (all amounts < 0 are stornos, credit notes or negative ap)
714
    $amount_for_payment *= -1 if $invoice->amount < 0;
715
    $free_skonto_amount *= -1 if ($free_skonto_amount && $invoice->amount < 0);
716
    # get the right direction for the bank transaction
717
    # sign is simply the sign of amount in bank_transactions: positive for increase and negative for decrease
718
    $amount_for_booking *= $sign;
719

  
720
    $main::lxdebug->message(0, 'amount for payment:' . $amount_for_payment);
721
    # check exchangerate and if fx_loss calculate new booking_amount for this invoice
722
    #if ($fx_rate > 0)  {
723
    #  die "Exchangerate without currency"                     unless $currency_id;
724
    #  die "Invoice currency differs from user input currency" unless $currency_id == $invoice->currency->id;
725

  
726

  
727
      # open_amount is open_amount for default currency
728
      # TODO
729
      # use helper to calc everything (even)
715 730
      # 2 if fx_loss, we probably need a higher amount to pay the original amount of the ap invoice
716
      if ($invoice->get_exchangerate < $fx_rate) {
731
      # no, just calc new open_amount and check if user wants a fee booking
732
    #  my $open_amount_fx = $invoice->open_amount_fx($fx_rate);
733
    #  $amount_for_payment = $fx_book ? $not_assigned_amount : $invoice->open_amount_fx($fx_rate);
734

  
735
    #  die $open_amount_fx;
736
    #  if ($invoice->get_exchangerate < $fx_rate) {
717 737
        # set whole bank_transaction amount -> pay_invoice will try to calculate losses and bank fees
718
        my $not_assigned_amount = abs($bank_transaction->not_assigned_amount);
719
        $amount_for_payment = $not_assigned_amount;
720
        $amount_for_payment *= -1 if $invoice->amount < 0;
721
      } elsif ($invoice->get_exchangerate >= $fx_rate) {
738
    #    my $not_assigned_amount = abs($bank_transaction->not_assigned_amount);
739
    #    $amount_for_payment = $not_assigned_amount;
740
    #    $amount_for_payment *= -1 if $invoice->amount < 0;
741
    #  } elsif ($invoice->get_exchangerate >= $fx_rate) {
722 742
        # if fx_gain do nothing, because gain
723 743
        # bla bla
724
      } else {
725
        die "Invalid exchange rate state for record:" . $invoice->get_exchangerate . " " . $fx_rate;
726
      }
727
    }
744
        # TODO copy paste
745
    #    my $not_assigned_amount = abs($bank_transaction->not_assigned_amount);
746
    #    $amount_for_payment = $not_assigned_amount;
747
    #    $amount_for_payment *= -1 if $invoice->amount < 0;
748
    #  } else {
749
    #    die "Invalid exchange rate state for record:" . $invoice->get_exchangerate . " " . $fx_rate;
750
    #  }
751
    #}
752
    #$main::lxdebug->message(0, 'amount for payment2:' . $amount_for_payment);
728 753
    # ... and then pay the invoice
729 754
    my @acc_ids = $invoice->pay_invoice(chart_id => $bank_transaction->local_bank_account->chart_id,
730 755
                          trans_id      => $invoice->id,
......
735 760
                          skonto_amount => $free_skonto_amount,
736 761
                          exchangerate  => $fx_rate,
737 762
                          fx_book       => $fx_book,
763
                          fx_fee_amount => $fx_fee_amount,
738 764
                          currency_id   => $currency_id,
739 765
                          bt_id         => $bt_id,
740 766
                          transdate     => $bank_transaction->valutadate->to_kivitendo);
741 767
    # First element is the booked amount for accno bank
742
    my $booked_amount = shift @acc_ids;
768
    my $bank_amount = shift @acc_ids;
769
    $main::lxdebug->message(0, 'a' . $bank_amount->{return_bank_amount});
770
    $main::lxdebug->message(0, 'b' . $amount_for_booking);
743 771
    if (!$invoice->forex) {
744
      die "Invalid state, calculated invoice_amount differs from expected invoice amount" unless abs(abs($booked_amount) - abs($amount_for_booking)) < 0.001;
772
      die "Invalid state, calculated invoice_amount differs from expected invoice amount" unless (abs($bank_amount->{return_bank_amount}) - abs($amount_for_booking) < 0.001);
745 773
      $bank_transaction->invoice_amount($bank_transaction->invoice_amount + $amount_for_booking);
746 774
    } else {
747
      $bank_transaction->invoice_amount($bank_transaction->invoice_amount + $booked_amount * $sign);
775
      die "Invalid state, calculated invoice_amount differs from expected invoice amount" unless $fx_book || (abs($bank_amount->{return_bank_amount}) - abs($amount_for_booking) < 0.001);
776
      $bank_transaction->invoice_amount($bank_transaction->invoice_amount + $bank_amount->{return_bank_amount});
777
      #$bank_transaction->invoice_amount($bank_transaction->invoice_amount + $amount_for_booking);
748 778
    }
749 779
    # ... and record the origin via BankTransactionAccTrans
750 780
    if (scalar(@acc_ids) < 2) {
SL/DB/Helper/Payment.pm
4 4

  
5 5
use parent qw(Exporter);
6 6
our @EXPORT = qw(pay_invoice);
7
our @EXPORT_OK = qw(skonto_date amount_less_skonto within_skonto_period percent_skonto reference_account open_amount skonto_amount valid_skonto_amount validate_payment_type get_payment_select_options_for_bank_transaction forex _skonto_charts_and_tax_correction get_exchangerate_for_bank_transaction get_exchangerate _add_bank_fx_fees);
7
our @EXPORT_OK = qw(skonto_date amount_less_skonto within_skonto_period percent_skonto reference_account open_amount skonto_amount valid_skonto_amount validate_payment_type get_payment_select_options_for_bank_transaction forex _skonto_charts_and_tax_correction get_exchangerate_for_bank_transaction get_exchangerate _add_bank_fx_fees open_amount_fx);
8 8
our %EXPORT_TAGS = (
9 9
  "ALL" => [@EXPORT, @EXPORT_OK],
10 10
);
......
70 70
    croak "payment type with_skonto_pt can't be used if payments have already been made" if $self->paid != 0;
71 71
  }
72 72

  
73

  
74 73
  my $transdate_obj;
75 74
  if (ref($params{transdate}) eq 'DateTime') {
76 75
    $transdate_obj = $params{transdate};
......
92 91

  
93 92
  # currency has to be passed and caller has to be sure to assign it for a forex invoice
94 93
  # dies if called for a invoice with the default currency (TODO: Params::Validate before)
95
  my ($exchangerate, $currency, $fx_gain_loss_amount, $return_bank_amount);
94
  my ($exchangerate, $currency, $return_bank_amount , $fx_gain_loss_amount);
96 95
  $return_bank_amount = 0;
97 96
  if ($params{currency} || $params{currency_id} && $self->forex) { # currency was specified
98 97
    $currency = SL::DB::Manager::Currency->find_by(name => $params{currency}) || SL::DB::Manager::Currency->find_by(id => $params{currency_id});
99
    # set exchangerate - no fallback
100
    # die "No exchange rate" unless $params{exchangerate} > 0;
98

  
99
    die "No currency found" unless ref $currency eq 'SL::DB::Currency';
100
    die "No exchange rate" unless $params{exchangerate} > 0;
101

  
101 102
    $exchangerate = $params{exchangerate};
102
    # hook for gl_bookings $book_fx_bank_fees;
103
    # and calculus fidibus total fx
104
    # self->amount - paid / self->exchangerate * banktransaction.exchangerate = total new amount EUR
103

  
105 104
    my $new_open_amount = ( $self->open_amount / $self->get_exchangerate ) * $exchangerate;
106 105
    # VORHER
107 106
    # my $gain_loss_amount = _round($amount * ($exchangerate - $self->get_exchangerate ) * -1,2);
108
    $fx_gain_loss_amount = _round( $self->open_amount - $new_open_amount);
107
    # $fx_gain_loss_amount = _round( $self->open_amount - $new_open_amount);
108
    # $fx_gain_loss_amount = _round($self->open_amount / $self->get_exchangerate - $new_open_amount / $exchangerate);
109 109
    # works for ap, but change sign for ar (todo credit notes and negative ap transactions
110
    $fx_gain_loss_amount *= -1 if $self->is_sales;
111
    $main::lxdebug->message(0, 'h 1 ' . $new_open_amount . ' h 3 ' . $params{amount} . ' und fx ' . $fx_gain_loss_amount );
112
    if ($new_open_amount < $params{amount}) {
113
      # if new open amount for payment booking is smaller than original amount use this
114
      # assume that the rest are fees, if the user selected this
115
      if($params{fx_book}) {
116
        die "Bank Fees can only be added for AP transactions" if $self->is_sales;
117
        $self->_add_bank_fx_fees(fee           => _round($params{amount} - $new_open_amount),
118
                                 bt_id         => $params{bt_id},
119
                                 bank_chart_id => $params{chart_id},
120
                                 memo          => $params{memo},
121
                                 source        => $params{source},
122
                                 transdate_obj => $transdate_obj  );
123
        # invoice_amount add gl booking
124
        $return_bank_amount += _round($params{amount} - $new_open_amount);
125
      } else {
126
        # invoice_amount without gl booking
127
        # $return_bank_amount = $new_open_amount;
128
      }
129
        # with or without fees simply assign the new open amount for bank (fx_gain follows later)
130
        $params{amount} = $new_open_amount;
110
    # $fx_gain_loss_amount *= -1 if $self->is_sales;
111
    $main::lxdebug->message(0, 'h 1 ' . $new_open_amount . ' h 3 ' . $params{amount});
112
    # if new open amount for payment booking is smaller than original amount use this
113
    # assume that the rest are fees, if the user selected this
114
    if ($params{fx_book} && $params{fx_fee_amount} > 0) {
115
      die "Bank Fees can only be added for AP transactions or Sales Credit Notes"
116
        unless $self->invoice_type =~ m/purchase_invoice|ap_transaction|credit_note/;
117

  
118
      $self->_add_bank_fx_fees(fee           => _round($params{fx_fee_amount}),
119
                               bt_id         => $params{bt_id},
120
                               bank_chart_id => $params{chart_id},
121
                               memo          => $params{memo},
122
                               source        => $params{source},
123
                               transdate_obj => $transdate_obj  );
124
      # invoice_amount add gl booking
125
      $main::lxdebug->message(0, 'fee ' . $params{fx_fee_amount});
126
      $return_bank_amount += _round($params{fx_fee_amount}); # invoice_type needs negative bank_amount
127
      $main::lxdebug->message(0, 'bank_amount' . $return_bank_amount);
128
      #$fx_gain_loss_amount = _round($params{amount} - ($params{amount} / $self->get_exchangerate * $exchangerate)  );
131 129
    }
132
    $main::lxdebug->message(0, 'return 1gl booking ' . $return_bank_amount); # stimmt f
133
    # $paid_amount    = $new_open_amount;
134 130
  } elsif (!$self->forex) { # invoices uses default currency. no exchangerate
135 131
    $exchangerate = 1;
136
    # $return_bank_amount = _round($params{amount}); # no forex
137 132
  } else {
138 133
    die "Cannot calculate exchange rate, if invoices uses the default currency";
139 134
  }
......
178 173
      $paid_amount += $pay_amount;
179 174

  
180 175
      my $amount = (-1 * $pay_amount) * $mult;
181

  
176
      $main::lxdebug->message(0, 'bank pay amount:' . $pay_amount);
177
      $main::lxdebug->message(0, 'paidamount:' . $paid_amount);
182 178

  
183 179
      # total amount against bank, do we already know this by now?
184 180
      # Yes, method requires this
......
193 189
                                                   taxkey     => 0,
194 190
                                                   tax_id     => SL::DB::Manager::Tax->find_by(taxkey => 0)->id);
195 191
      $new_acc_trans->save;
196
      $return_bank_amount += abs($amount); # add sign
197
      $main::lxdebug->message(0, 'return 5 ' . $return_bank_amount);
192
      $return_bank_amount += $amount;
193
      $main::lxdebug->message(0, 'return 5 :' . $return_bank_amount);
194
      $main::lxdebug->message(0, 'paid amount hier 1 :' . $paid_amount);
198 195
      push @new_acc_ids, $new_acc_trans->acc_trans_id;
199 196
      # deal with fxtransaction ...
200 197
      # if invoice exchangerate differs from exchangerate of payment
201 198
      # add fxloss or fxgain
202
      if ($fx_gain_loss_amount && $exchangerate != 1 && $self->get_exchangerate and $self->get_exchangerate != 1 and $self->get_exchangerate != $exchangerate) {
203
        # (self->amount - self->paid) / $self->exchangerate
199
      if ($exchangerate != 1 && $self->get_exchangerate and $self->get_exchangerate != 1 and $self->get_exchangerate != $exchangerate) {
204 200
        my $fxgain_chart = SL::DB::Manager::Chart->find_by(id => $::instance_conf->get_fxgain_accno_id) || die "Can't determine fxgain chart";
205 201
        my $fxloss_chart = SL::DB::Manager::Chart->find_by(id => $::instance_conf->get_fxloss_accno_id) || die "Can't determine fxloss chart";
206
        $main::lxdebug->message(0, 'was sagt gain loss' . $fx_gain_loss_amount);
202
        #                              AMOUNT == EUR / fx rate pay * (fx rate invoice - fx rate pa)
203
        # rate invoice = 2,  fx rate paid = 1.75
204
        # partial payment of 15000 EUR invtotal 20000
205
        #                                         15000/1.75  * (2 - 1.75)  = 2142. EUR gain if invoice is purchase                                             # sql ledger (with fx amount):
206
        # AR.pm
207
        # $amount = $form->round_amount($form->{"paid_$i"} * ($form->{exchangerate} - $form->{"exchangerate_$i"}) * -1, 2);
208
        # AP.pm
209
        # exchangerate gain/loss
210
        # $amount = $form->round_amount($form->{"paid_$i"} * ($form->{exchangerate} - $form->{"exchangerate_$i"}), 2);
211
        # ==>
212
        # my $fx_gain_loss_sign   = $is_sales ? -1 : 1;  # multiplier for getting the right sign depending on ar/ap
213

  
214
        #my $fx_gain_loss_sign = $self->invoice_type =~ m/purchase_invoice|ap_transaction|credit_note/                             ?  1
215
        #                      : $self->invoice_type =~ m/invoice|ar_transaction|purchase_credit_note|invoice_for_advance_payment/ ? -1
216
        #                      : die "invalid state";
217

  
218
        $fx_gain_loss_amount = _round($amount / $exchangerate * ( $self->get_exchangerate - $exchangerate)); # * $fx_gain_loss_sign;
219

  
220
        $main::lxdebug->message(0, 'was sagt gain loss 2 ' . $fx_gain_loss_amount);
221
        # die "huchz" . $fx_gain_loss_amount;
207 222
        my $gain_loss_chart  = $fx_gain_loss_amount > 0 ? $fxgain_chart : $fxloss_chart;
208 223
        # $paid_amount += abs($fx_gain_loss_amount); # if $fx_gain_loss_amount < 0; # only add if we have fx_loss
209
        $paid_amount += abs($fx_gain_loss_amount) if $fx_gain_loss_amount < 0; # only add if we have fx_loss
210
        $paid_amount -= abs($fx_gain_loss_amount) if $fx_gain_loss_amount > 0; # but extract if we have gain to match original invoice amount (ar)
224
        $main::lxdebug->message(0, 'paid hier 1 ' . $paid_amount);
225
        # for sales add loss to ar.paid and subtract gain from ar.paid
226
        # for purchase add gain to ap.paid and subtract loss from ap.paid
227
        $paid_amount += abs($fx_gain_loss_amount) if $fx_gain_loss_amount < 0 && $self->is_sales; # extract if we have fx_loss
228
        $paid_amount -= abs($fx_gain_loss_amount) if $fx_gain_loss_amount > 0 && $self->is_sales; # but add if to match original invoice amount (arap)
229
        $paid_amount += abs($fx_gain_loss_amount) if $fx_gain_loss_amount > 0 && !$self->is_sales; # but add if to match original invoice amount (arap)
230
        $paid_amount -= abs($fx_gain_loss_amount) if $fx_gain_loss_amount < 0 && !$self->is_sales; # extract if we have fx_loss
231
        # (self->amount - self->paid) / $self->exchangerate
232
        $main::lxdebug->message(0, 'paid dort 2 ' . $paid_amount);
233

  
211 234
        $main::lxdebug->message(0, 'return 1 ' . $return_bank_amount);
212 235
        $main::lxdebug->message(0, 'paid amount hier 2 ' . $paid_amount);
213 236
        # $return_bank_amount += $fx_gain_loss_amount if $fx_gain_loss_amount < 0; # only add if we have fx_loss
......
397 420
    1;
398 421

  
399 422
  }) || die t8('error while paying invoice #1 : ', $self->invnumber) . $db->error . "\n";
400
  return wantarray ? (abs($return_bank_amount), @new_acc_ids) : 1;
423

  
424
  $return_bank_amount *= -1;   # negative booking is positive bank transaction
425
                               # positive booking is negative bank transaction
426
  return wantarray ? ( { return_bank_amount => $return_bank_amount }, @new_acc_ids) : 1;
401 427
}
402 428

  
403 429
sub skonto_date {
......
442 468
  return ($self->amount // 0) - ($self->paid // 0);
443 469
}
444 470

  
471
sub open_amount_fx {
472
  # validate shift == $self
473
  validate_pos(
474
    @_,
475
      {  can       => [ qw(forex get_exchangerate) ],
476
         callbacks => { 'has forex'      => sub { return $_[0]->forex } } },
477
      {  callbacks => {
478
           'is a positive real'          => sub { return $_[0] =~ m/^[+]?\d+(\.\d+)?$/ }, },
479
      }
480
  );
481

  
482
  my ($self, $fx_rate) = @_;
483

  
484
  return ( $self->open_amount / $self->get_exchangerate ) * $fx_rate;
485

  
486
}
487

  
488
sub amount_less_skonto_fx {
489
  # validate shift == $self
490
  validate_pos(
491
    @_,
492
      {  can       => [ qw(forex get_exchangerate percent_skonto) ],
493
         callbacks => { 'has forex'      => sub { return $_[0]->forex } } },
494
      {  callbacks => {
495
           'is a positive real'          => sub { return $_[0] =~ m/^[+]?\d+(\.\d+)?$/ }, },
496
      }
497
  );
498

  
499
  my ($self, $fx_rate) = @_;
500

  
501
  return ( $self->amount_less_skonto / $self->get_exchangerate ) * $fx_rate;
502
}
503

  
504

  
505

  
445 506
sub skonto_amount {
446 507
  my $self = shift;
447 508

  
......
534 595
    # linked gl booking will appear in tab linked records
535 596
    # this is just a link for convenience
536 597
    %props_rl = (
537
         #from_table => $is_sales ? 'ar' : 'ap',
538
         from_table => 'ap',
598
         from_table => $self->is_sales ? 'ar' : 'ap', # yep sales credit notes
599
         #from_table => 'ap',
539 600
         from_id    => $self->id,
540 601
         to_table   => 'gl',
541 602
         to_id      => $current_transaction->id,

Auch abrufbar als: Unified diff