Konfigurierbares Angebotsgültigkeits-Intervall hinzugefügt
[kivitendo-erp.git] / SL / Form.pm
index 7030454..7cbf60f 100644 (file)
@@ -948,24 +948,45 @@ sub parse_amount {
 }
 
 sub round_amount {
-  $main::lxdebug->enter_sub(2);
-
   my ($self, $amount, $places) = @_;
-  my $round_amount;
 
   # Rounding like "Kaufmannsrunden" (see http://de.wikipedia.org/wiki/Rundung )
 
-  # Round amounts to eight places before rounding to the requested
-  # number of places. This gets rid of errors due to internal floating
-  # point representation.
-  $amount       = $self->round_amount($amount, 8) if $places < 8;
-  $amount       = $amount * (10**($places));
-  $round_amount = int($amount + .5 * ($amount <=> 0)) / (10**($places));
-
-  $main::lxdebug->leave_sub(2);
-
-  return $round_amount;
+  # If you search for rounding in Perl, you'll likely get the first version of
+  # this algorithm:
+  #
+  # ($amount <=> 0) * int(abs($amount) * 10**$places) + .5) / 10**$places
+  #
+  # That doesn't work. It falls apart for certain values that are exactly 0.5
+  # over the cutoff, because the internal IEEE754 representation is slightly
+  # below the cutoff. Perl makes matters worse in that it really, really tries to
+  # recognize exact values for presentation to you, even if they are not.
+  #
+  # Example: take the value 64.475 and round to 2 places.
+  #
+  # printf("%.20f\n", 64.475) gives you 64.47499999999999431566
+  #
+  # Then 64.475 * 100 + 0.5 is 6447.99999999999909050530, and
+  # int(64.475 * 100 + 0.5) / 100 = 64.47
+  #
+  # Trying to round with more precision first only shifts the problem to rarer
+  # cases, which nevertheless exist.
+  #
+  # Now we exploit the presentation rounding of Perl. Since it really tries hard
+  # to recognize integers, we double $amount, and let Perl give us a representation.
+  # If Perl recognizes it as a slightly too small integer, and rounds up to the
+  # next odd integer, we follow suit and treat the fraction as .5 or greater.
+
+  my $sign               = $amount <=> 0;
+  $amount                = abs $amount;
+
+  my $shift              = 10 ** ($places);
+  my $shifted_and_double = $amount * $shift * 2;
+  my $rounding_bias      = sprintf('%f', $shifted_and_double) % 2;
+  $amount                = int($amount * $shift) + $rounding_bias;
+  $amount                = $amount / $shift * $sign;
 
+  return $amount;
 }
 
 sub parse_template {
@@ -1026,7 +1047,7 @@ sub parse_template {
                                       %{ $self->{TEMPLATE_DRIVER_OPTIONS} || {} });
 
   # Copy the notes from the invoice/sales order etc. back to the variable "notes" because that is where most templates expect it to be.
-  $self->{"notes"} = $self->{ $self->{"formname"} . "notes" };
+  $self->{"notes"} = $self->{ $self->{"formname"} . "notes" } if exists $self->{ $self->{"formname"} . "notes" };
 
   if (!$self->{employee_id}) {
     $self->{"employee_${_}"} = $myconfig->{$_} for qw(email tel fax name signature);
@@ -2137,8 +2158,10 @@ sub _get_taxzones {
   my ($self, $dbh, $key) = @_;
 
   $key = "all_taxzones" unless ($key);
+  my $tzfilter = "";
+  $tzfilter = "WHERE obsolete is FALSE" if $key eq 'ALL_ACTIVE_TAXZONES'; 
 
-  my $query = qq|SELECT * FROM tax_zones ORDER BY id|;
+  my $query = qq|SELECT * FROM tax_zones $tzfilter ORDER BY sortkey|;
 
   $self->{$key} = selectall_hashref_query($self, $dbh, $query);
 
@@ -3344,7 +3367,7 @@ sub prepare_for_printing {
 
   # Load shipping address from database if shipto_id is set.
   if ($self->{shipto_id}) {
-    my $shipto  = SL::DB::Shipto->new(id => $self->{shipto_id})->load;
+    my $shipto  = SL::DB::Shipto->new(shipto_id => $self->{shipto_id})->load;
     $self->{$_} = $shipto->$_ for grep { m{^shipto} } map { $_->name } @{ $shipto->meta->columns };
   }
 
@@ -3353,15 +3376,15 @@ sub prepare_for_printing {
   my ($language_tc, $output_numberformat, $output_dateformat, $output_longdates);
   if ($self->{language_id}) {
     ($language_tc, $output_numberformat, $output_dateformat, $output_longdates) = AM->get_language_details(\%::myconfig, $self, $self->{language_id});
-  } else {
-    $output_dateformat   = $::myconfig{dateformat};
-    $output_numberformat = $::myconfig{numberformat};
-    $output_longdates    = 1;
   }
 
-  $self->{myconfig_output_dateformat}   = $output_dateformat;
-  $self->{myconfig_output_longdates}    = $output_longdates;
-  $self->{myconfig_output_numberformat} = $output_numberformat;
+  $output_dateformat   ||= $::myconfig{dateformat};
+  $output_numberformat ||= $::myconfig{numberformat};
+  $output_longdates    //= 1;
+
+  $self->{myconfig_output_dateformat}   = $output_dateformat   // $::myconfig{dateformat};
+  $self->{myconfig_output_longdates}    = $output_longdates    // 1;
+  $self->{myconfig_output_numberformat} = $output_numberformat // $::myconfig{numberformat};
 
   # Retrieve accounts for tax calculation.
   IC->retrieve_accounts(\%::myconfig, $self, map { $_ => $self->{"id_$_"} } 1 .. $self->{rowcount});