kein parse_amount für skonto_in_percent
[kivitendo-erp.git] / SL / Form.pm
index 38d87ad..8915ca2 100644 (file)
@@ -56,6 +56,8 @@ use SL::DBUtils;
 use SL::DO;
 use SL::IC;
 use SL::IS;
+use SL::Layout::Dispatcher;
+use SL::Locale;
 use SL::Mailer;
 use SL::Menu;
 use SL::MoreCommon qw(uri_encode uri_decode);
@@ -311,7 +313,7 @@ sub info {
     <script type="text/javascript">
     <!--
     // If JavaScript is enabled, the whole thing will be reloaded.
-    // The reason is: When one changes his menu setup (HTML / XUL / CSS ...)
+    // The reason is: When one changes his menu setup (HTML / CSS ...)
     // it now loads the correct code into the browser instead of do nothing.
     setTimeout("top.frames.location.href='login.pl'",500);
     //-->
@@ -446,32 +448,38 @@ sub create_http_response {
   return $output;
 }
 
-sub use_stylesheet {
-  my $self = shift;
-
-  $self->{stylesheet} = [ $self->{stylesheet} ] unless ref $self->{stylesheet} eq 'ARRAY';
-  $self->{stylesheet} = [ grep { -f                       }
-                          map  { m:^css/: ? $_ : "css/$_" }
-                          grep { $_                       }
-                               (@{ $self->{stylesheet} }, @_)
-                        ];
-
-  return @{ $self->{stylesheet} };
-}
-
 sub header {
   $::lxdebug->enter_sub;
 
-  # extra code is currently only used by menuv3 and menuv4 to set their css.
-  # it is strongly deprecated, and will be changed in a future version.
   my ($self, %params) = @_;
   my $db_charset = $::lx_office_conf{system}->{dbcharset} || Common::DEFAULT_CHARSET;
   my @header;
 
   $::lxdebug->leave_sub and return if !$ENV{HTTP_USER_AGENT} || $self->{header}++;
 
+  if ($params{no_layout}) {
+    $::request->{layout} = SL::Layout::Dispatcher->new(style => 'none');
+  }
+
+  my $layout = $::request->{layout};
+
+  # standard css for all
+  # this should gradually move to the layouts that need it
+  $layout->use_stylesheet("$_.css") for qw(
+    main menu tabcontent list_accounts jquery.autocomplete
+    jquery.multiselect2side frame_header/header
+    ui-lightness/jquery-ui-1.8.12.custom
+    js/jscalendar/calendar-win2k-1
+  );
+
+  $layout->use_javascript("$_.js") for qw(
+    jquery common jscalendar/calendar jscalendar/lang/calendar-de
+    jscalendar/calendar-setup part_selection jquery-ui jquery.cookie jqModal
+    switchmenuframe
+  );
+
   $self->{favicon} ||= "favicon.ico";
-  $self->{titlebar}  = "$self->{title} - $self->{titlebar}" if $self->{title};
+  $self->{titlebar} = join ' - ', grep $_, $self->{title}, $self->{login}, $::myconfig{dbname}, $self->{version} if $self->{title};
 
   # build includes
   if ($self->{refresh_url} || $self->{refresh_time}) {
@@ -480,43 +488,18 @@ sub header {
     push @header, "<meta http-equiv='refresh' content='$refresh_time;$refresh_url'>";
   }
 
-  push @header, map { qq|<link rel="stylesheet" href="$_" type="text/css" title="Lx-Office stylesheet">| } $self->use_stylesheet;
-
-  push @header, "<style type='text/css'>\@page { size:landscape; }</style>" if $self->{landscape};
-  push @header, "<link rel='shortcut icon' href='$self->{favicon}' type='image/x-icon'>" if -f $self->{favicon};
-  push @header, '<script type="text/javascript" src="js/jquery.js"></script>',
-                '<script type="text/javascript" src="js/common.js"></script>',
-                '<link rel="stylesheet" type="text/css" href="js/jscalendar/calendar-win2k-1.css">',
-                '<script type="text/javascript" src="js/jscalendar/calendar.js"></script>',
-                '<script type="text/javascript" src="js/jscalendar/lang/calendar-de.js"></script>',
-                '<script type="text/javascript" src="js/jscalendar/calendar-setup.js"></script>',
-                '<script type="text/javascript" src="js/part_selection.js"></script>',
-                '<script type="text/javascript" src="js/jquery-ui.js"></script>',
-                '<script type="text/javascript" src="js/jqModal.js"></script>',
-                '<link rel="stylesheet" type="text/css" href="css/ui-lightness/jquery-ui-1.8.12.custom.css">';
+  push @header, map { qq|<link rel="stylesheet" href="$_" type="text/css" title="Stylesheet">| } $layout->stylesheets;
+  push @header, "<style type='text/css'>\@page { size:landscape; }</style> "                     if $self->{landscape};
+  push @header, "<link rel='shortcut icon' href='$self->{favicon}' type='image/x-icon'>"         if -f $self->{favicon};
+  push @header, map { qq|<script type="text/javascript" src="$_"></script>| }                    $layout->javascripts;
   push @header, $self->{javascript} if $self->{javascript};
   push @header, map { $_->show_javascript } @{ $self->{AJAX} || [] };
-  push @header, "<script type='text/javascript'>function fokus(){ document.$self->{fokus}.focus(); }</script>" if $self->{fokus};
-  push @header, sprintf "<script type='text/javascript'>top.document.title='%s';</script>",
-    join ' - ', grep $_, $self->{title}, $self->{login}, $::myconfig{dbname}, $self->{version} if $self->{title};
-
-  # if there is a title, we put some JavaScript in to the page, wich writes a
-  # meaningful title-tag for our frameset.
-  my $title_hack = '';
-  if ($self->{title}) {
-    $title_hack = qq|
-    <script type="text/javascript">
-    <!--
-      // Write a meaningful title-tag for our frameset.
-      top.document.title="| . $self->{"title"} . qq| - | . $self->{"login"} . qq| - | . $::myconfig{dbname} . qq| - V| . $self->{"version"} . qq|";
-    //-->
-    </script>|;
-  }
 
   my  %doctypes = (
     strict       => qq|<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">|,
     transitional => qq|<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">|,
     frameset     => qq|<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">|,
+    html5        => qq|<!DOCTYPE html>|,
   );
 
   # output
@@ -530,9 +513,7 @@ sub header {
 EOT
   print "  $_\n" for @header;
   print <<EOT;
-  <link rel="stylesheet" href="css/jquery.autocomplete.css" type="text/css">
   <meta name="robots" content="noindex,nofollow">
-  <link rel="stylesheet" type="text/css" href="css/tabcontent.css">
   <script type="text/javascript" src="js/tabcontent.js">
 
   /***********************************************
@@ -542,15 +523,34 @@ EOT
    ***********************************************/
 
   </script>
-  $params{extra_code}
-  $title_hack
  </head>
+ <body>
 
 EOT
+  print $::request->{layout}->pre_content;
+  print $::request->{layout}->start_content;
+
+  $layout->header_done;
 
   $::lxdebug->leave_sub;
 }
 
+sub footer {
+  return unless $::request->{layout}->need_footer;
+
+  print $::request->{layout}->end_content;
+  print $::request->{layout}->post_content;
+
+  if (my @inline_scripts = $::request->{layout}->javascripts_inline) {
+    print "<script type='text/javascript'>@inline_scripts</script>\n";
+  }
+
+  print <<EOL
+ </body>
+</html>
+EOL
+}
+
 sub ajax_response_header {
   $main::lxdebug->enter_sub();
 
@@ -581,7 +581,7 @@ sub set_standard_title {
   $::lxdebug->enter_sub;
   my $self = shift;
 
-  $self->{titlebar}  = "Lx-Office " . $::locale->text('Version') . " $self->{version}";
+  $self->{titlebar}  = "kivitendo " . $::locale->text('Version') . " $self->{version}";
   $self->{titlebar} .= "- $::myconfig{name}"   if $::myconfig{name};
   $self->{titlebar} .= "- $::myconfig{dbname}" if $::myconfig{name};
 
@@ -681,6 +681,8 @@ sub init_template {
 
   return $self->template if $self->template;
 
+  # Force scripts/locales.pl to pick up the exception handling template.
+  # parse_html_template('generic/exception')
   return $self->template(Template->new({
      'INTERPOLATE'  => 0,
      'EVAL_PERL'    => 0,
@@ -690,6 +692,7 @@ sub init_template {
      'INCLUDE_PATH' => '.:templates/webpages',
      'COMPILE_EXT'  => '.tcc',
      'COMPILE_DIR'  => $::lx_office_conf{paths}->{userspath} . '/templates-cache',
+     'ERROR'        => 'templates/webpages/generic/exception.html',
   })) || die;
 }
 
@@ -774,10 +777,8 @@ sub write_trigger {
   # default
   my %dateformats = (
     "dd.mm.yy" => "%d.%m.%Y",
-    "dd-mm-yy" => "%d-%m-%Y",
     "dd/mm/yy" => "%d/%m/%Y",
     "mm/dd/yy" => "%m/%d/%Y",
-    "mm-dd-yy" => "%m-%d-%Y",
     "yyyy-mm-dd" => "%Y-%m-%d",
     );
 
@@ -851,37 +852,30 @@ sub format_amount {
   $main::lxdebug->enter_sub(2);
 
   my ($self, $myconfig, $amount, $places, $dash) = @_;
+  $amount ||= 0;
+  $dash   ||= '';
+  my $neg = $amount < 0;
+  my $force_places = defined $places && $places >= 0;
 
-  if ($amount eq "") {
-    $amount = 0;
-  }
-
-  # Hey watch out! The amount can be an exponential term like 1.13686837721616e-13
-
-  my $neg = ($amount =~ s/^-//);
-  my $exp = ($amount =~ m/[e]/) ? 1 : 0;
+  $amount = $self->round_amount($amount, abs $places) if $force_places;
+  $amount = sprintf "%.*f", ($force_places ? $places : 10), abs $amount; # 6 is default for %fa
 
-  if (defined($places) && ($places ne '')) {
-    if (not $exp) {
-      if ($places < 0) {
-        $amount *= 1;
-        $places *= -1;
+  # before the sprintf amount was a number, afterwards it's a string. because of the dynamic nature of perl
+  # this is easy to confuse, so keep in mind: before this comment no s///, m//, concat or other strong ops on
+  # $amount. after this comment no +,-,*,/,abs. it will only introduce subtle bugs.
 
-        my ($actual_places) = ($amount =~ /\.(\d+)/);
-        $actual_places = length($actual_places);
-        $places = $actual_places > $places ? $actual_places : $places;
-      }
-    }
-    $amount = $self->round_amount($amount, $places);
-  }
+  $amount =~ s/0*$// unless defined $places && $places == 0;             # cull trailing 0s
 
   my @d = map { s/\d//g; reverse split // } my $tmp = $myconfig->{numberformat}; # get delim chars
-  my @p = split(/\./, $amount); # split amount at decimal point
-
-  $p[0] =~ s/\B(?=(...)*$)/$d[1]/g if $d[1]; # add 1,000 delimiters
+  my @p = split(/\./, $amount);                                          # split amount at decimal point
 
+  $p[0] =~ s/\B(?=(...)*$)/$d[1]/g if $d[1];                             # add 1,000 delimiters
   $amount = $p[0];
-  $amount .= $d[0].$p[1].(0 x ($places - length $p[1])) if ($places || $p[1] ne '');
+  if ($places || $p[1]) {
+    $amount .= $d[0]
+            .  ( $p[1] || '' )
+            .  (0 x (abs($places || 0) - length ($p[1]||'')));           # pad the fraction
+  }
 
   $amount = do {
     ($dash =~ /-/)    ? ($neg ? "($amount)"                            : "$amount" )                              :
@@ -889,7 +883,6 @@ sub format_amount {
                         ($neg ? "-$amount"                             : "$amount" )                              ;
   };
 
-
   $main::lxdebug->leave_sub(2);
   return $amount;
 }
@@ -1094,30 +1087,32 @@ sub parse_template {
 
   # OUT is used for the media, screen, printer, email
   # for postscript we store a copy in a temporary file
-  my $fileid = time;
-  my $prepend_userspath;
-
-  if (!$self->{tmpfile}) {
-    $self->{tmpfile}   = "${fileid}.$self->{IN}";
-    $prepend_userspath = 1;
-  }
-
-  $prepend_userspath = 1 if substr($self->{tmpfile}, 0, length $userspath) eq $userspath;
-
-  $self->{tmpfile} =~ s|.*/||;
-  $self->{tmpfile} =~ s/[^a-zA-Z0-9\._\ \-]//g;
-  $self->{tmpfile} = "$userspath/$self->{tmpfile}" if $prepend_userspath;
+  my ($temp_fh, $suffix);
+  $suffix =  $self->{IN};
+  $suffix =~ s/.*\.//;
+  ($temp_fh, $self->{tmpfile}) = File::Temp::tempfile(
+    'kivitendo-printXXXXXX',
+    SUFFIX => '.' . ($suffix || 'tex'),
+    DIR    => $userspath,
+    UNLINK => ($::lx_office_conf{debug} && $::lx_office_conf{debug}->{keep_temp_files})? 0 : 1,
+  );
+  close $temp_fh;
 
   if ($template->uses_temp_file() || $self->{media} eq 'email') {
-    $out = $self->{OUT};
-    $out_mode = $self->{OUT_MODE} || '>';
-    $self->{OUT} = "$self->{tmpfile}";
+    $out              = $self->{OUT};
+    $out_mode         = $self->{OUT_MODE} || '>';
+    $self->{OUT}      = "$self->{tmpfile}";
     $self->{OUT_MODE} = '>';
   }
 
   my $result;
+  my $command_formatter = sub {
+    my ($out_mode, $out) = @_;
+    return $out_mode eq '|-' ? SL::Template::create(type => 'ShellCommand', form => $self)->parse($out) : $out;
+  };
 
   if ($self->{OUT}) {
+    $self->{OUT} = $command_formatter->($self->{OUT_MODE}, $self->{OUT});
     open(OUT, $self->{OUT_MODE}, $self->{OUT}) or $self->error("error on opening $self->{OUT} with mode $self->{OUT_MODE} : $!");
   } else {
     *OUT = ($::dispatcher->get_standard_filehandles)[1];
@@ -1152,24 +1147,20 @@ sub parse_template {
       $mail->{charset} = $::lx_office_conf{system}->{dbcharset} || Common::DEFAULT_CHARSET;
       $mail->{to} = $self->{EMAIL_RECIPIENT} ? $self->{EMAIL_RECIPIENT} : $self->{email};
       $mail->{from}   = qq|"$myconfig->{name}" <$myconfig->{email}>|;
-      $mail->{fileid} = "$fileid.";
+      $mail->{fileid} = time() . '.' . $$ . '.';
       $myconfig->{signature} =~ s/\r//g;
 
       # if we send html or plain text inline
       if (($self->{format} eq 'html') && ($self->{sendmode} eq 'inline')) {
-        $mail->{contenttype} = "text/html";
-
-        $mail->{message}       =~ s/\r//g;
-        $mail->{message}       =~ s/\n/<br>\n/g;
-        $myconfig->{signature} =~ s/\n/<br>\n/g;
-        $mail->{message} .= "<br>\n-- <br>\n$myconfig->{signature}\n<br>";
+        $mail->{contenttype}    =  "text/html";
+        $mail->{message}        =~ s/\r//g;
+        $mail->{message}        =~ s/\n/<br>\n/g;
+        $myconfig->{signature}  =~ s/\n/<br>\n/g;
+        $mail->{message}       .=  "<br>\n-- <br>\n$myconfig->{signature}\n<br>";
 
         open(IN, "<", $self->{tmpfile})
           or $self->error($self->cleanup . "$self->{tmpfile} : $!");
-        while (<IN>) {
-          $mail->{message} .= $_;
-        }
-
+        $mail->{message} .= $_ while <IN>;
         close(IN);
 
       } else {
@@ -1206,10 +1197,12 @@ sub parse_template {
       #print(STDERR "OUT $self->{OUT}\n");
       for my $i (1 .. $self->{copies}) {
         if ($self->{OUT}) {
-          open OUT, $self->{OUT_MODE}, $self->{OUT} or $self->error($self->cleanup . "$self->{OUT} : $!");
+          $self->{OUT} = $command_formatter->($self->{OUT_MODE}, $self->{OUT});
+
+          open  OUT, $self->{OUT_MODE}, $self->{OUT} or $self->error($self->cleanup . "$self->{OUT} : $!");
           print OUT $_ while <IN>;
           close OUT;
-          seek IN, 0, 0;
+          seek  IN, 0, 0;
 
         } else {
           $self->{attachment_filename} = ($self->{attachment_filename})
@@ -1244,6 +1237,9 @@ sub get_formname_translation {
 
   $formname ||= $self->{formname};
 
+  $self->{recipient_locale} ||=  Locale->lang_to_locale($self->{language});
+  local $::locale = Locale->new($self->{recipient_locale});
+
   my %formname_translations = (
     bin_list                => $main::locale->text('Bin List'),
     credit_note             => $main::locale->text('Credit Note'),
@@ -1261,7 +1257,7 @@ sub get_formname_translation {
   );
 
   $main::lxdebug->leave_sub();
-  return $formname_translations{$formname}
+  return $formname_translations{$formname};
 }
 
 sub get_number_prefix_for_type {
@@ -1297,11 +1293,14 @@ sub generate_attachment_filename {
   $main::lxdebug->enter_sub();
   my ($self) = @_;
 
+  $self->{recipient_locale} ||=  Locale->lang_to_locale($self->{language});
+  my $recipient_locale = Locale->new($self->{recipient_locale});
+
   my $attachment_filename = $main::locale->unquote_special_chars('HTML', $self->get_formname_translation());
   my $prefix              = $self->get_number_prefix_for_type();
 
   if ($self->{preview} && (first { $self->{type} eq $_ } qw(invoice credit_note))) {
-    $attachment_filename .= ' (' . $main::locale->text('Preview') . ')' . $self->get_extension_for_format();
+    $attachment_filename .= ' (' . $recipient_locale->text('Preview') . ')' . $self->get_extension_for_format();
 
   } elsif ($attachment_filename && $self->{"${prefix}number"}) {
     $attachment_filename .=  "_" . $self->{"${prefix}number"} . $self->get_extension_for_format();
@@ -1742,10 +1741,9 @@ sub set_payment_options {
     $amounts{invtotal} = $self->{invtotal};
     $amounts{total}    = $self->{total};
   }
-  $amounts{skonto_in_percent} = 100.0 * $self->{percent_skonto};
-
   map { $amounts{$_} = $self->parse_amount($myconfig, $amounts{$_}) } keys %amounts;
 
+  $amounts{skonto_in_percent}  = 100.0 * $self->{percent_skonto};
   $amounts{skonto_amount}      = $amounts{invtotal} * $self->{percent_skonto};
   $amounts{invtotal_wo_skonto} = $amounts{invtotal} * (1 - $self->{percent_skonto});
   $amounts{total_wo_skonto}    = $amounts{total}    * (1 - $self->{percent_skonto});
@@ -1955,7 +1953,7 @@ sub get_employee_data {
   my ($login)  = selectrow_query($self, $dbh, qq|SELECT login FROM employee WHERE id = ?|, conv_i($params{id}));
 
   if ($login) {
-    my $user = User->new($login);
+    my $user = User->new(login => $login);
     map { $self->{$params{prefix} . "_${_}"} = $user->{$_}; } qw(address businessnumber co_ustid company duns email fax name signature taxnumber tel);
 
     $self->{$params{prefix} . '_login'}   = $login;
@@ -1973,8 +1971,17 @@ sub get_duedate {
   $reference_date = $reference_date ? conv_dateq($reference_date) . '::DATE' : 'current_date';
 
   my $dbh         = $self->get_standard_dbh($myconfig);
+  my $payment_id;
+
+  if($self->{payment_id}) {
+    $payment_id = $self->{payment_id};
+  } elsif($self->{vendor_id}) {
+    my $query = 'SELECT payment_id FROM vendor WHERE id = ?';
+    ($payment_id) = selectrow_query($self, $dbh, $query, $self->{vendor_id});
+  }
+
   my $query       = qq|SELECT ${reference_date} + terms_netto FROM payment_terms WHERE id = ?|;
-  my ($duedate)   = selectrow_query($self, $dbh, $query, $self->{payment_id});
+  my ($duedate)   = selectrow_query($self, $dbh, $query, $payment_id);
 
   $main::lxdebug->leave_sub();
 
@@ -2512,7 +2519,7 @@ sub get_name {
   return scalar(@{ $self->{name_list} });
 }
 
-# the selection sub is used in the AR, AP, IS, IR and OE module
+# the selection sub is used in the AR, AP, IS, IR, DO and OE module
 #
 sub all_vc {
   $main::lxdebug->enter_sub();
@@ -2524,13 +2531,17 @@ sub all_vc {
 
   $table = $table eq "customer" ? "customer" : "vendor";
 
-  my $query = qq|SELECT count(*) FROM $table|;
+  # build selection list
+  # Hotfix für Bug 1837 - Besser wäre es alte Buchungsbelege
+  # OHNE Auswahlliste (reines Textfeld) zu laden. Hilft aber auch
+  # nicht für veränderbare Belege (oe, do, ...)
+  my $obsolete = "WHERE NOT obsolete" unless $self->{id};
+  my $query = qq|SELECT count(*) FROM $table $obsolete|;
   my ($count) = selectrow_query($self, $dbh, $query);
 
-  # build selection list
-  if ($count <= $myconfig->{vclimit}) {
+  if ($count < $myconfig->{vclimit}) {
     $query = qq|SELECT id, name, salesman_id
-                FROM $table WHERE NOT obsolete
+                FROM $table $obsolete
                 ORDER BY name|;
     $self->{"all_$table"} = selectall_hashref_query($self, $dbh, $query);
   }
@@ -2541,7 +2552,8 @@ sub all_vc {
   # setup sales contacts
   $query = qq|SELECT e.id, e.name
               FROM employee e
-              WHERE (e.sales = '1') AND (NOT e.id = ?)|;
+              WHERE (e.sales = '1') AND (NOT e.id = ?)
+              ORDER BY name|;
   $self->{all_employees} = selectall_hashref_query($self, $dbh, $query, $self->{employee_id});
 
   # this is for self
@@ -2549,11 +2561,6 @@ sub all_vc {
        { id   => $self->{employee_id},
          name => $self->{employee} });
 
-  # sort the whole thing
-  @{ $self->{all_employees} } =
-    sort { $a->{name} cmp $b->{name} } @{ $self->{all_employees} };
-
-
     # prepare query for departments
     $query = qq|SELECT id, description
                 FROM department
@@ -2860,7 +2867,7 @@ sub create_links {
     if ($self->{"$self->{vc}_id"}) {
 
       # only setup currency
-      ($self->{currency}) = split(/:/, $self->{currencies});
+      ($self->{currency}) = split(/:/, $self->{currencies}) if !$self->{currency};
 
     } else {
 
@@ -3568,6 +3575,30 @@ sub reformat_numbers {
   $::myconfig{numberformat} = $saved_numberformat;
 }
 
+sub layout {
+  my ($self) = @_;
+  $::lxdebug->enter_sub;
+
+  my %style_to_script_map = (
+    v3  => 'v3',
+    neu => 'new',
+    v4  => 'v4',
+  );
+
+  my $menu_script = $style_to_script_map{$::myconfig{menustyle}} || '';
+
+  package main;
+  require "bin/mozilla/menu$menu_script.pl";
+  package Form;
+  require SL::Controller::FrameHeader;
+
+
+  my $layout = SL::Controller::FrameHeader->new->action_header . ::render();
+
+  $::lxdebug->leave_sub;
+  return $layout;
+}
+
 1;
 
 __END__