Projekt

Allgemein

Profil

« Zurück | Weiter » 

Revision 0e68056c

Von Kivitendo Admin vor etwa 10 Jahren hinzugefügt

  • ID 0e68056cbc17b531266c16454f3a74ae1e57dea6
  • Vorgänger 71233175
  • Nachfolger 3270e883

Rundung bei Debitorenbuchung, Kreditorenbuchung und Dialogbuchung

Zwei neue Hilfsfunktionen für Form eingeführt die von ap/ar/gl genutzt
werden:

  • calculate_tax wird zur Berechnung der Steuer bei
    • update in ar, ap und gl
    • post_transaction in AR.pm und AP.pm innerhalb von calculate_arap
  • calculate_arap berechnet netamount, amount und totaltax anhand einer
    vorhandenen $form in ar/ap, und formatiert und rundet die amount_$i and
    tax_$i in der $form.

Das Ziel war es, daß diese drei Belege mit den gleichen Formeln zur
Steuerberechnung bei Steuer inkl./exkl. arbeiten. Ein Vorteil im
Vergleich zur Einkaufs- und Verkaufsrechnung ist, daß es hier keine
Positionen/Mengen/Rabatte/etc gibt, sondern direkt Werte auf Konten
bebucht werden, man muß nur Währungskurse berücksichtigen und beim
Schreiben in die Datenbank auf die Vorzeichen achten. Einkaufs- und
Verkaufsrechnungen werden hiervon nicht beeinflußt.

Vor allem bei der Einstellung "MwSt. inkl" wird die Steuer nun immer auf
zwei Stellen gerundet in die Datenbank geschrieben, und nicht (wie
bisher in manchen Fällen) mit 5 Nachkommastellen, was zu mehreren Bugs
geführt hat.

Es gibt einen neuen Test t/form/arap.t der die Berechnungen dieser
beiden Funktionen testet, nicht aber das eigentliche Buchen der Belege.

Mit diesem Commit sollten für zukünftige Buchungen folgende Bugs behoben
sein:

1691 - Rundung bei Berichten bei Buchungen mit MwSt inkl.
2029 - Rundungsfehler bei Dialogbuchung
2033 - Unterschiede in Rundungen durch taxincluded
2094 - Rundungsprobleme in Kreditorenbuchungen: Cent "kippt" bei Zahlungseinbuchung
2435 - Rundungsfehler in Kreditorenbuchungen (Netto vs. Brutto)

Dieser Commit greift in die Eingeweide von Uraltcode ein, wo die
diversen Stellen alle mal mit copy&paste entstanden und dann langsam
divergiert sind, hat also hohes Fehlerpotential. Es lohnt sich, den
DATEV-Check anzuschalten.

Unterschiede anzeigen:

SL/AP.pm
55 55
  my $exchangerate = 0;
56 56

  
57 57
  $form->{defaultcurrency} = $form->get_default_currency($myconfig);
58
  $form->{taxincluded} = 0 unless $form->{taxincluded};
58 59

  
59 60
  ($null, $form->{department_id}) = split(/--/, $form->{department});
60 61

  
......
69 70
    $form->{AP_amounts}{"amount_$i"} =
70 71
      (split(/--/, $form->{"AP_amount_$i"}))[0];
71 72
  }
73

  
72 74
  ($form->{AP_amounts}{payables}) = split(/--/, $form->{APselected});
73 75
  ($form->{AP_payables})          = split(/--/, $form->{APselected});
74 76

  
75
  # reverse and parse amounts
76
  for my $i (1 .. $form->{rowcount}) {
77
    $form->{"amount_$i"} =
78
      $form->round_amount(
79
                         $form->parse_amount($myconfig, $form->{"amount_$i"}) *
80
                           $form->{exchangerate} * -1,
81
                         2);
82
    $amount += ($form->{"amount_$i"} * -1);
83

  
84
    # parse tax_$i for later
85
    $form->{"tax_$i"} = $form->parse_amount($myconfig, $form->{"tax_$i"}) * -1;
86
  }
87

  
88
  # this is for ap
89
  $form->{amount} = $amount;
90

  
91
  # taxincluded doesn't make sense if there is no amount
92
  $form->{taxincluded} = 0 if ($form->{amount} == 0);
93

  
94
  for my $i (1 .. $form->{rowcount}) {
95
    ($form->{"tax_id_$i"}, undef) = split /--/, $form->{"taxchart_$i"};
96

  
97
    my $query =
98
      qq|SELECT c.accno, t.taxkey, t.rate | .
99
      qq|FROM tax t LEFT JOIN chart c on (c.id=t.chart_id) | .
100
      qq|WHERE t.id = ? | .
101
      qq|ORDER BY c.accno|;
102
    my $sth = $dbh->prepare($query);
103
    $sth->execute($form->{"tax_id_$i"}) || $form->dberror($query . " (" . $form->{"tax_id_$i"} . ")");
104
    ($form->{AP_amounts}{"tax_$i"}, $form->{"taxkey_$i"}, $form->{"taxrate_$i"}) = $sth->fetchrow_array();
105

  
106
    $sth->finish;
107

  
108
    my ($tax, $diff);
109
    if ($form->{taxincluded} *= 1) {
110
      $tax = $form->{"amount_$i"} - ($form->{"amount_$i"} / ($form->{"taxrate_$i"} + 1));
111
      $amount = $form->{"amount_$i"} - $tax;
112
      $form->{"amount_$i"} = $form->round_amount($amount, 2);
113
      $diff += $amount - $form->{"amount_$i"};
114
      $form->{"tax_$i"} = $form->round_amount($tax, 2);
115
      $form->{netamount} += $form->{"amount_$i"};
116
    } else {
117
      $form->{"tax_$i"} = $form->{"amount_$i"} * $form->{"taxrate_$i"};
118
      $form->{netamount} += $form->{"amount_$i"};
119
    }
120
    $form->{total_tax} += $form->{"tax_$i"} * -1;
121
  }
77
  # calculate the totals while calculating and reformatting the $amount_$i and $tax_$i
78
  ($form->{netamount},$form->{total_tax},$form->{invtotal}) = $form->calculate_arap('buy',$form->{taxincluded}, $form->{exchangerate});
122 79

  
123 80
  # adjust paidaccounts if there is no date in the last row
124 81
  $form->{paidaccounts}-- unless ($form->{"datepaid_$form->{paidaccounts}"});
125 82

  
126 83
  $form->{invpaid} = 0;
127
  $form->{netamount} *= -1;
128 84

  
129 85
  # add payments
130 86
  for my $i (1 .. $form->{paidaccounts}) {
......
140 96
  $form->{invpaid} =
141 97
    $form->round_amount($form->{invpaid} * $form->{exchangerate}, 2);
142 98

  
143
  # store invoice total, this goes into ap table
144
  $form->{invtotal} = $form->{netamount} + $form->{total_tax};
99
  # # store invoice total, this goes into ap table
100
  # $form->{invtotal} = $form->{netamount} + $form->{total_tax};
145 101

  
146 102
  # amount for total AP
147 103
  $form->{payables} = $form->{invtotal};
SL/AR.pm
69 69
  $form->{AR_amounts}{receivables} = $form->{ARselected};
70 70
  $form->{AR}{receivables}         = $form->{ARselected};
71 71

  
72
  # parsing
73
  for $i (1 .. $form->{rowcount}) {
74
    $form->{"amount_$i"} = $form->round_amount($form->parse_amount($myconfig, $form->{"amount_$i"}) * $form->{exchangerate}, 2);
75
    $form->{amount}     += $form->{"amount_$i"};
76
    $form->{"tax_$i"}    = $form->parse_amount($myconfig, $form->{"tax_$i"});
77
  }
78

  
79
  # this is for ar
80
  $form->{tax}       = 0;
81
  $form->{netamount} = 0;
82
  $form->{total_tax} = 0;
83

  
84
  # taxincluded doesn't make sense if there is no amount
85
  $form->{taxincluded} = 0 unless $form->{amount};
86

  
87
  for $i (1 .. $form->{rowcount}) {
88
    ($form->{"tax_id_$i"}) = split /--/, $form->{"taxchart_$i"};
72
  $form->{tax}       = 0; # is this still needed?
89 73

  
90
    $query = qq|SELECT c.accno, t.taxkey, t.rate FROM tax t LEFT JOIN chart c ON (c.id = t.chart_id) WHERE t.id = ? ORDER BY c.accno|;
91
    ($form->{AR_amounts}{"tax_$i"}, $form->{"taxkey_$i"}, $form->{"taxrate_$i"}) = selectrow_query($form, $dbh, $query, $form->{"tax_id_$i"});
92

  
93
    if ($form->{taxincluded} *= 1) {
94
      $tax = $form->{"korrektur_$i"}
95
        ? $form->{"tax_$i"}
96
        : $form->{"amount_$i"} - ($form->{"amount_$i"} / ($form->{"taxrate_$i"} + 1)); # should be same as taxrate * amount / (taxrate + 1)
97
      $form->{"amount_$i"} = $form->round_amount($form->{"amount_$i"} - $tax, 2);
98
      $form->{"tax_$i"}    = $form->round_amount($tax, 2);
99
    } else {
100
      $form->{"tax_$i"}    = $form->{"amount_$i"} * $form->{"taxrate_$i"} unless $form->{"korrektur_$i"};
101
      $form->{"tax_$i"}    = $form->round_amount($form->{"tax_$i"} * $form->{exchangerate}, 2);
102
    }
103
    $form->{netamount}  += $form->{"amount_$i"};
104
    $form->{total_tax}  += $form->{"tax_$i"};
105
  }
74
  # main calculation of rowcount loop inside Form method, amount_$i and tax_$i get formatted
75
  $form->{taxincluded} = 0 unless $form->{taxincluded};
76
  ($form->{netamount},$form->{total_tax},$form->{amount}) = $form->calculate_arap('sell', $form->{taxincluded}, $form->{exchangerate});
106 77

  
107 78
  # adjust paidaccounts if there is no date in the last row
108 79
  # this does not apply to stornos, where the paid field is set manually
......
117 88
      $form->{datepaid}  = $form->{"datepaid_$i"};
118 89
    }
119 90

  
120
    $form->{amount} = $form->{netamount} + $form->{total_tax};
121 91
  }
122 92
  $form->{paid}   = $form->round_amount($form->{paid} * ($form->{exchangerate} || 1), 2);
123 93

  
SL/Form.pm
79 79
use URI;
80 80
use List::Util qw(first max min sum);
81 81
use List::MoreUtils qw(all any apply);
82
use SL::DB::Tax;
82 83

  
83 84
use strict;
84 85

  
......
3446 3447
  return $self;
3447 3448
}
3448 3449

  
3450
sub calculate_arap {
3451
  my ($self,$buysell,$taxincluded,$exchangerate,$roundplaces) = @_;
3452

  
3453
  # this function is used to calculate netamount, total_tax and amount for AP and
3454
  # AR transactions (Kreditoren-/Debitorenbuchungen) by going over all lines
3455
  # (1..$rowcount)
3456
  # Thus it needs a fully prepared $form to work on.
3457
  # calculate_arap assumes $form->{amount_$i} entries still need to be parsed
3458

  
3459
  # The calculated total values are all rounded (default is to 2 places) and
3460
  # returned as parameters rather than directly modifying form.  The aim is to
3461
  # make the calculation of AP and AR behave identically.  There is a test-case
3462
  # for this function in t/form/arap.t
3463

  
3464
  # While calculating the totals $form->{amount_$i} and $form->{tax_$i} are
3465
  # modified and formatted and receive the correct sign for writing straight to
3466
  # acc_trans, depending on whether they are ar or ap.
3467

  
3468
  # check parameters
3469
  die "taxincluded needed in Form->calculate_arap" unless defined $taxincluded;
3470
  die "exchangerate needed in Form->calculate_arap" unless defined $exchangerate;
3471
  die 'illegal buysell parameter, has to be \"buy\" or \"sell\" in Form->calculate_arap\n' unless $buysell =~ /^(buy|sell)$/;
3472
  $roundplaces = 2 unless $roundplaces;
3473

  
3474
  my $sign = 1;  # adjust final results for writing amount to acc_trans
3475
  $sign = -1 if $buysell eq 'buy';
3476

  
3477
  my ($netamount,$total_tax,$amount);
3478

  
3479
  my $tax;
3480

  
3481
  # parse and round amounts, setting correct sign for writing to acc_trans
3482
  for my $i (1 .. $self->{rowcount}) {
3483
    $self->{"amount_$i"} = $self->round_amount($self->parse_amount(\%::myconfig, $self->{"amount_$i"}) * $exchangerate * $sign, $roundplaces);
3484

  
3485
    $amount += $self->{"amount_$i"} * $sign;
3486
  }
3487

  
3488
  for my $i (1 .. $self->{rowcount}) {
3489
    next unless $self->{"amount_$i"};
3490
    ($self->{"tax_id_$i"}) = split /--/, $self->{"taxchart_$i"};
3491
    my $tax_id = $self->{"tax_id_$i"};
3492

  
3493
    my $selected_tax = SL::DB::Manager::Tax->find_by(id => "$tax_id");
3494

  
3495
    if ( $selected_tax ) {
3496

  
3497
      if ( $buysell eq 'sell' ) {
3498
        $self->{AR_amounts}{"tax_$i"} = $selected_tax->chart->accno unless $selected_tax->taxkey == 0;
3499
      } else {
3500
        $self->{AP_amounts}{"tax_$i"} = $selected_tax->chart->accno unless $selected_tax->taxkey == 0;
3501
      };
3502

  
3503
      $self->{"taxkey_$i"} = $selected_tax->taxkey;
3504
      $self->{"taxrate_$i"} = $selected_tax->rate;
3505
    };
3506

  
3507
    ($self->{"amount_$i"}, $self->{"tax_$i"}) = $self->calculate_tax($self->{"amount_$i"},$self->{"taxrate_$i"},$taxincluded,$roundplaces);
3508

  
3509
    $netamount  += $self->{"amount_$i"};
3510
    $total_tax  += $self->{"tax_$i"};
3511

  
3512
  }
3513
  $amount = $netamount + $total_tax;
3514

  
3515
  # due to $sign amount_$i und tax_$i already have the right sign for acc_trans
3516
  # but reverse sign of totals for writing amounts to ar
3517
  if ( $buysell eq 'buy' ) {
3518
    $netamount *= -1;
3519
    $amount    *= -1;
3520
    $total_tax *= -1;
3521
  };
3522

  
3523
  return($netamount,$total_tax,$amount);
3524
}
3525

  
3449 3526
sub format_dates {
3450 3527
  my ($self, $dateformat, $longformat, @indices) = @_;
3451 3528

  
......
3558 3635
  return $layout;
3559 3636
}
3560 3637

  
3638
sub calculate_tax {
3639
  # this function calculates the net amount and tax for the lines in ar, ap and
3640
  # gl and is used for update as well as post. When used with update the return
3641
  # value of amount isn't needed
3642

  
3643
  # calculate_tax should always work with positive values, or rather as the user inputs them
3644
  # calculate_tax uses db/perl numberformat, i.e. parsed numbers
3645
  # convert to negative numbers (when necessary) only when writing to acc_trans
3646
  # the amount from $form for ap/ar/gl is currently always rounded to 2 decimals before it reaches here
3647
  # for post_transaction amount already contains exchangerate and correct sign and is rounded
3648
  # calculate_tax doesn't (need to) know anything about exchangerate
3649

  
3650
  my ($self,$amount,$taxrate,$taxincluded,$roundplaces) = @_;
3651

  
3652
  $roundplaces = 2 unless defined $roundplaces;
3653

  
3654
  my $tax;
3655

  
3656
  if ($taxincluded *= 1) {
3657
    # calculate tax (unrounded), subtract from amount, round amount and round tax
3658
    $tax       = $amount - ($amount / ($taxrate + 1)); # equivalent to: taxrate * amount / (taxrate + 1)
3659
    $amount    = $self->round_amount($amount - $tax, $roundplaces);
3660
    $tax       = $self->round_amount($tax, $roundplaces);
3661
  } else {
3662
    $tax       = $amount * $taxrate;
3663
    $tax       = $self->round_amount($tax, $roundplaces);
3664
  }
3665

  
3666
  $tax = 0 unless $tax;
3667

  
3668
  return ($amount,$tax);
3669
};
3670

  
3561 3671
1;
3562 3672

  
3563 3673
__END__
bin/mozilla/ap.pl
483 483
  my $count = 0;
484 484
  my (@a, $j, $totaltax);
485 485
  for my $i (1 .. $form->{rowcount}) {
486
    $form->{"amount_$i"} =
487
      $form->parse_amount(\%myconfig, $form->{"amount_$i"});
488
    $form->{"tax_$i"} = $form->parse_amount(\%myconfig, $form->{"tax_$i"});
486
    $form->{"amount_$i"} = $form->parse_amount(\%myconfig, $form->{"amount_$i"});
489 487
    if ($form->{"amount_$i"}) {
490 488
      push @a, {};
491 489
      $j = $#a;
492 490
      my ($taxkey, $rate) = split(/--/, $form->{"taxchart_$i"});
493
      if ($taxkey > 1) {
494
        if ($form->{taxincluded}) {
495
          $form->{"tax_$i"} = $form->{"amount_$i"} / ($rate + 1) * $rate;
496
        } else {
497
          $form->{"tax_$i"} = $form->{"amount_$i"} * $rate;
498
        }
499
      } else {
500
        $form->{"tax_$i"} = 0;
501
      }
502
      $form->{"tax_$i"} = $form->round_amount($form->{"tax_$i"}, 2);
491

  
492
      # calculate tax exactly the same way as AP in post_transaction via form->calculate_tax
493
      my $tmpnetamount;
494
      ($tmpnetamount,$form->{"tax_$i"}) = $form->calculate_tax($form->{"amount_$i"},$rate,$form->{taxincluded},2);
503 495

  
504 496
      $totaltax += $form->{"tax_$i"};
505 497
      map { $a[$j]->{$_} = $form->{"${_}_$i"} } @flds;
bin/mozilla/ar.pl
522 522

  
523 523
  for my $i (1 .. $form->{rowcount}) {
524 524
    $form->{"amount_$i"} = $form->parse_amount(\%myconfig, $form->{"amount_$i"});
525
    $form->{"tax_$i"} = $form->parse_amount(\%myconfig, $form->{"tax_$i"});
526 525
    if ($form->{"amount_$i"}) {
527 526
      push @a, {};
528 527
      my $j = $#a;
529 528
      my ($taxkey, $rate) = split(/--/, $form->{"taxchart_$i"});
530
      if ($taxkey > 1) {
531
        if ($form->{taxincluded}) {
532
          $form->{"tax_$i"} = $form->{"amount_$i"} / ($rate + 1) * $rate;
533
        } else {
534
          $form->{"tax_$i"} = $form->{"amount_$i"} * $rate;
535
        }
536
      } else {
537
        $form->{"tax_$i"} = 0;
538
      }
539
      $form->{"tax_$i"} = $form->round_amount($form->{"tax_$i"}, 2);
529

  
530
      my $tmpnetamount;
531
      ($tmpnetamount,$form->{"tax_$i"}) = $form->calculate_tax($form->{"amount_$i"},$rate,$form->{taxincluded},2);
540 532

  
541 533
      $totaltax += $form->{"tax_$i"};
542 534
      map { $a[$j]->{$_} = $form->{"${_}_$i"} } @flds;
bin/mozilla/gl.pl
594 594
        } else {
595 595
          $credittax = 1;
596 596
        }
597
        if ($form->{taxincluded}) {
598
          $form->{"tax_$i"} = $amount / ($rate + 1) * $rate;
599
        } else {
600
          $form->{"tax_$i"} = $amount * $rate;
601
        }
602
      } else {
603
        $form->{"tax_$i"} = 0;
604
      }
597
      };
598
      my ($tmpnetamount,$tmpdiff);
599
      ($tmpnetamount,$form->{"tax_$i"},$tmpdiff) = $form->calculate_tax($amount,$rate,$form->{taxincluded} *= 1,2);
605 600

  
606 601
      for (@flds) { $a[$j]->{$_} = $form->{"${_}_$i"} }
607 602
      $count++;
......
1067 1062
      } else {
1068 1063
        $credittax = 1;
1069 1064
      }
1070
      if ($form->{taxincluded}) {
1071
        $form->{"tax_$i"} = $amount / ($rate + 1) * $rate;
1072
        if ($debitcredit) {
1073
          $form->{"debit_$i"} = $form->{"debit_$i"} - $form->{"tax_$i"};
1074
        } else {
1075
          $form->{"credit_$i"} = $form->{"credit_$i"} - $form->{"tax_$i"};
1076
        }
1065

  
1066
      my ($tmpnetamount,$tmpdiff);
1067
      ($tmpnetamount,$form->{"tax_$i"},$tmpdiff) = $form->calculate_tax($amount,$rate,$form->{taxincluded} *= 1,2);
1068
      if ($debitcredit) {
1069
        $form->{"debit_$i"} = $tmpnetamount;
1077 1070
      } else {
1078
        $form->{"tax_$i"} = $amount * $rate;
1071
        $form->{"credit_$i"} = $tmpnetamount;
1079 1072
      }
1073

  
1080 1074
    } else {
1081 1075
      $form->{"tax_$i"} = 0;
1082 1076
    }
t/form/arap.t
1
use strict;
2
use Test::More;
3

  
4
use lib 't';
5
use Support::TestSetup;
6
use Carp;
7
use Test::Exception;
8

  
9
# this test tests the functions calculate_arap and calculate_tax in SL/Form.pm
10
# calculate_arap is used for post_invoice in AR and AP
11
# calculate_tax is used in calculate_arap as well as update in ar/ap/gl and post_transaction in gl 
12

  
13
my ($ar_tax_19, $ar_tax_7,$ar_tax_0);
14
my $config = {};
15
$config->{numberformat} = '1.000,00';
16

  
17
sub reset_state {
18
  my %params = @_;
19

  
20
  $params{$_} ||= {} for qw(ar_tax_19 ar_tax_7 ar_tax_0 );
21

  
22
  # delete rowcount lines in form, would be better to reset form completely
23
  for my $hv ( 1 .. 10 ) {
24
      foreach my $type ( qw(amount tax tax_id tax_chart) ) {
25
          delete $::form{"$type\_$hv"};
26
      };
27
  };
28

  
29
  $ar_tax_19 = SL::DB::Manager::Tax->find_by(taxkey => 3, rate => 0.19, %{ $params{ar_tax_19} })  || croak "No 19% tax";
30
  $ar_tax_7  = SL::DB::Manager::Tax->find_by(taxkey => 2, rate => 0.07, %{ $params{ar_tax_7} })   || croak "No 7% tax";
31
  $ar_tax_0  = SL::DB::Manager::Tax->find_by(taxkey => 0, rate => 0.00, %{ $params{ar_tax_0} })   || croak "No 0% tax";
32

  
33
};
34

  
35
sub arap_test {
36
  my ($testcase) = @_;
37

  
38
  reset_state;
39

  
40
  # values from testcase
41
  $::form->{taxincluded}     = $testcase->{taxincluded};
42
  $::form->{currency}        = $testcase->{currency};
43
  $::form->{rowcount}        = scalar @{$testcase->{lines}};
44

  
45
  # parse exchangerate, because it was added in the same numberformat as the
46
  # other amounts in the testcases
47
  $testcase->{exchangerate}    = $::form->parse_amount(\%::myconfig, $testcase->{exchangerate});
48

  
49
  foreach my $a ( 1 .. scalar @{$testcase->{lines}} ) {
50
    my ($taxrate, $form_amount, $netamount, $taxamount, $totalamount) = @{ @{ $testcase->{lines} }[$a-1] };
51
    my $tax;
52
    if ( $taxrate == 19 ) {
53
        $tax = $ar_tax_19;
54
    } elsif ( $taxrate == 7 ) {
55
        $tax = $ar_tax_7;
56
    } elsif ( $taxrate == 0 ) {
57
        $tax = $ar_tax_0;
58
    } else {
59
        croak "illegal taxrate $taxrate";
60
    };
61

  
62
    $::form->{"amount_$a"}   = $form_amount;
63
    $::form->{"tax_$a"}      = $taxamount;  # tax according to UI, will recalculate anyway?
64
    $::form->{"taxchart_$a"} = $tax->id . '--' . $tax->rate;
65

  
66
  };
67

  
68
  # calculate totals using lines in $::form
69
  ($::form->{netamount},$::form->{total_tax},$::form->{amount}) = $::form->calculate_arap($testcase->{'buysell'}, $::form->{taxincluded}, $testcase->{'exchangerate'});
70

  
71
  # create tests comparing calculated and expected values
72
  is($::form->format_amount(\%::myconfig , $::form->{total_tax} , 2) , $testcase->{'total_taxamount'} , "total tax   = $testcase->{'total_taxamount'}");
73
  is($::form->format_amount(\%::myconfig , $::form->{netamount} , 2) , $testcase->{'total_netamount'} , "netamount   = $testcase->{'total_netamount'}");
74
  is($::form->format_amount(\%::myconfig , $::form->{amount}    , 2) , $testcase->{'total_amount'}    , "totalamount = $testcase->{'total_amount'}");
75
  is($::form->{taxincluded}, $testcase->{'taxincluded'}, "taxincluded = $testcase->{'taxincluded'}");
76

  
77
};
78

  
79
sub calculate_tax_test {
80
  my ($amount, $rate, $taxincluded, $net, $tax, $total, $dec) = @_;
81
  # amount, rate and taxincluded are the values that we want to calculate with
82
  # net, tax and total are the values that we expect, dec is the number of decimals we round to
83

  
84
  my ($calculated_net,$calculated_tax) = $::form->calculate_tax($amount,$rate,$taxincluded,$dec);
85

  
86
  is($tax, $calculated_tax, "calculated tax for taxincluded = $taxincluded for net $amount and rate $rate is = $calculated_tax");
87
  is($calculated_net, $net, "calculated net for taxincluded = $taxincluded for net $amount and rate $rate is = $net");
88
};
89

  
90
Support::TestSetup::login();
91

  
92
# define the various lines that can be used for the testcases
93
# always use positive values for buy/sell, like in the interface
94
#                   tax  input   net      tax   total  type
95
my @testline1  = qw(19   56,53   47,50   9,03   56,53  sell);
96
my @testline2  = qw(19   11,90   10,00   1,90   11,90  sell);
97
my @testline3  = qw( 7   14,39   13,45   0,94   11,90  sell);
98
my @testline4  = qw(19  133,08  133,08  25,29  158,37  sell);
99
my @testline5  = qw( 0  100,00   83,00   0,00   83,00  sell);  # exchangerate of 0,83
100
my @testline6  = qw(19   56,53   47,50   9,03   56,53   buy);
101
my @testline7  = qw(19  309,86  309,86  58,87  368,73   buy);
102
my @testline8  = qw( 7  130,00  121,50   8,50  130,00   buy);
103
my @testline9  = qw( 7  121,49  121,49   8,50  129,99   buy);
104
my @testline10 = qw( 7  121,50  121,50   8,51  130,01   buy);
105
my @testline11 = qw(19   -2,77   -2,77  -0,53   -3,30   buy);
106
my @testline12 = qw( 7   12,88   12,88   0,90   13,78   buy);
107
my @testline13 = qw(19   41,93   41,93   7,97   49,90   buy);
108
my @testline14 = qw(19   84,65   84,65  16,08  107,73   buy);
109
my @testline15 = qw(19    8,39    8,39   1,59    9,98   buy);
110
my @testline16 = qw(19  100,73   84,65  16,08  107,73   buy);
111
my @testline17 = qw(19    9,99    8,39   1,60    9,99   buy);
112

  
113
# create testcases, made up of one or more lines, with expected values
114

  
115
my $testcase1 = {
116
    lines           => [ \@testline1 ], # lines to be used in testcase
117
    total_amount    => '56,53',  # expected result
118
    total_netamount => '47,50',  # expected result
119
    total_taxamount => '9,03',   # expected result
120
    # invoice parameters:
121
    taxincluded     => 1,
122
    exchangerate    => 1,
123
    currency        => 'EUR',
124
    buysell         => 'sell',
125
};
126

  
127
my $testcase2 = {
128
    lines           => [ \@testline1, \@testline2, \@testline3 ],
129
    total_amount    => '82,82',
130
    total_netamount => '70,95',
131
    total_taxamount => '11,87',
132
    taxincluded     => 1,
133
    exchangerate    => 1,
134
    currency        => 'EUR',
135
    buysell         => 'sell',
136
};
137

  
138
my $testcase3 = {
139
    lines           => [ \@testline4 ],
140
    total_amount    => '158,37',
141
    total_netamount => '133,08',
142
    total_taxamount => '25,29',
143
    taxincluded     => 0,
144
    exchangerate    => 1,
145
    currency        => 'EUR',
146
    buysell         => 'sell',
147
};
148

  
149
my $testcase4 = {
150
    lines           => [ \@testline5 ],
151
    total_amount    => '83,00',
152
    total_netamount => '83,00',
153
    total_taxamount => '0,00',
154
    taxincluded     => 0,
155
    exchangerate    => '0,83',
156
    currency        => 'USD',
157
    buysell         => 'sell',
158
};
159

  
160
my $testcase6 = {
161
    lines           => [ \@testline6 ],
162
    total_amount    => '56,53',
163
    total_netamount => '47,50',
164
    total_taxamount => '9,03',
165
    taxincluded     => 1,
166
    exchangerate    => 1,
167
    currency        => 'EUR',
168
    buysell         => 'buy',
169
};
170

  
171
my $testcase7 = {
172
    lines           => [ \@testline7 ],
173
    total_netamount => '309,86',
174
    total_taxamount => '58,87',
175
    total_amount    => '368,73',
176
    taxincluded     => 0,
177
    exchangerate    => 1,
178
    currency        => 'EUR',
179
    buysell         => 'buy',
180
};
181

  
182
my $testcase8 = {
183
    lines           => [ \@testline8 ],
184
    total_netamount => '121,50',
185
    total_taxamount => '8,50',
186
    total_amount    => '130,00',
187
    taxincluded     => 1,
188
    exchangerate    => 1,
189
    currency        => 'EUR',
190
    buysell         => 'buy',
191
};
192

  
193
my $testcase9 = {
194
    lines           => [ \@testline9 ],
195
    total_netamount => '121,49',
196
    total_taxamount => '8,50',
197
    total_amount    => '129,99',
198
    taxincluded     => 0,
199
    exchangerate    => 1,
200
    currency        => 'EUR',
201
    buysell         => 'buy',
202
};
203

  
204
my $testcase10 = {
205
    lines           => [ \@testline10 ],
206
    total_netamount => '121,50',
207
    total_taxamount => '8,51',
208
    total_amount    => '130,01',
209
    taxincluded     => 0,
210
    exchangerate    => 1,
211
    currency        => 'EUR',
212
    buysell         => 'buy',
213
};
214

  
215
my $testcase11 = {
216
    # mixed invoices, -2,77€ net with 19% as credit note, 12,88€ net with 7%
217
    lines           => [ \@testline11 , \@testline12 ],
218
    total_netamount => '10,11',
219
    total_taxamount => '0,37',
220
    total_amount    => '10,48',
221
    taxincluded     => 0,
222
    exchangerate    => 1,
223
    currency        => 'EUR',
224
    buysell         => 'buy',
225
};
226

  
227
my $testcase12 = {
228
    # ap transaction, example from bug 2435
229
    lines           => [ \@testline13 ],
230
    total_netamount => '41,93',
231
    total_taxamount => '7,97',
232
    total_amount    => '49,90',
233
    taxincluded     => 0,
234
    exchangerate    => 1,
235
    currency        => 'EUR',
236
    buysell         => 'buy',
237
};
238

  
239
my $testcase13 = {
240
    # ap transaction, example from bug 2094, tax not included
241
    lines           => [ \@testline14 , \@testline15 ],
242
    total_netamount => '93,04',
243
    total_taxamount => '17,67',
244
    total_amount    => '110,71',
245
    taxincluded     => 0,
246
    exchangerate    => 1,
247
    currency        => 'EUR',
248
    buysell         => 'buy',
249
};
250

  
251
my $testcase14 = {
252
    # ap transaction, example from bug 2094, tax included
253
    lines           => [ \@testline16 , \@testline17 ],
254
    total_netamount => '93,04',
255
    total_taxamount => '17,68',
256
    total_amount    => '110,72',
257
    taxincluded     => 1,
258
    exchangerate    => 1,
259
    currency        => 'EUR',
260
    buysell         => 'buy',
261
};
262

  
263
# run tests
264
arap_test($testcase1);
265
arap_test($testcase2);
266
arap_test($testcase3);
267
arap_test($testcase4);
268
arap_test($testcase6);
269
arap_test($testcase7);
270
arap_test($testcase8);
271
arap_test($testcase9);
272
arap_test($testcase10);
273
arap_test($testcase11);
274
arap_test($testcase12);
275
arap_test($testcase13);
276
arap_test($testcase14);
277

  
278
# tests for calculate_tax:
279

  
280
# tests for 1 Cent, calculated tax should be 0
281
calculate_tax_test(0.01,0.07,1,0.01,0.00,0.01,2);
282
calculate_tax_test(0.01,0.19,1,0.01,0.00,0.01,2);
283

  
284
# tax for rate 7% taxincluded flips at 0.08
285
calculate_tax_test(0.07,0.07,1,0.07,0.00,0.07,2);
286
calculate_tax_test(0.08,0.07,1,0.07,0.01,0.08,2);
287

  
288
# tax for rate 7% taxexcluded flips at 0.08
289
calculate_tax_test(0.07,0.07,0,0.07,0.00,0.07,2);
290
calculate_tax_test(0.08,0.07,0,0.08,0.01,0.09,2);
291

  
292
# tax for rate 19% taxexcluded flips at 0.03
293
calculate_tax_test(0.02,0.19,0,0.02,0.00,0.02,2);
294
calculate_tax_test(0.03,0.19,0,0.03,0.01,0.04,2);
295

  
296
# tax for rate 19% taxincluded flips at 0.04
297
calculate_tax_test(0.03,0.19,1,0.03,0.00,0.03,2);
298
calculate_tax_test(0.04,0.19,1,0.03,0.01,0.04,2);
299

  
300
calculate_tax_test(8.39,0.19,0,8.39,1.59,9.98,2);
301
calculate_tax_test(9.99,0.19,1,8.39,1.60,9.99,2);
302

  
303
calculate_tax_test(11.21,0.07,0,11.21,0.78,11.99,2);
304
calculate_tax_test(11.22,0.07,0,11.22,0.79,12.01,2);
305
calculate_tax_test(12.00,0.07,1,11.21,0.79,12.00,2);
306

  
307
done_testing(82);
308

  
309
1;

Auch abrufbar als: Unified diff