TopQuickSearch für den Benutzer konfigurierbar gemacht.
[kivitendo-erp.git] / SL / Form.pm
index 5c7d062..0c21ae2 100644 (file)
@@ -1,4 +1,4 @@
-#========= ===========================================================
+#=====================================================================
 # LX-Office ERP
 # Copyright (C) 2004
 # Based on SQL-Ledger Version 2.1.9
@@ -27,7 +27,8 @@
 # GNU General Public License for more details.
 # You should have received a copy of the GNU General Public License
 # along with this program; if not, write to the Free Software
-# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA 02110-1335, USA.
 #======================================================================
 # Utilities for parsing forms
 # and supporting routines for linking account numbers
@@ -48,6 +49,7 @@ use Encode;
 use File::Copy;
 use IO::File;
 use Math::BigInt;
+use POSIX qw(strftime);
 use SL::Auth;
 use SL::Auth::DB;
 use SL::Auth::LDAP;
@@ -62,6 +64,7 @@ use SL::DB::Default;
 use SL::DB::PaymentTerm;
 use SL::DB::Vendor;
 use SL::DO;
+use SL::Helper::Flash qw();
 use SL::IC;
 use SL::IS;
 use SL::Layout::Dispatcher;
@@ -76,24 +79,20 @@ use SL::Request;
 use SL::Template;
 use SL::User;
 use SL::Util;
+use SL::Version;
 use SL::X;
 use Template;
 use URI;
 use List::Util qw(first max min sum);
 use List::MoreUtils qw(all any apply);
 use SL::DB::Tax;
+use SL::Helper::File qw(:all);
+use SL::Helper::CreatePDF qw(merge_pdfs);
 
 use strict;
 
 sub read_version {
-  my ($self) = @_;
-
-  open VERSION_FILE, "VERSION";                 # New but flexible code reads version from VERSION-file
-  my $version =  <VERSION_FILE>;
-  $version    =~ s/[^0-9A-Za-z\.\_\-]//g; # only allow numbers, letters, points, underscores and dashes. Prevents injecting of malicious code.
-  close VERSION_FILE;
-
-  return $version;
+  SL::Version->get_version;
 }
 
 sub new {
@@ -111,8 +110,6 @@ sub new {
 
   bless $self, $type;
 
-  $self->{version} = $self->read_version;
-
   $main::lxdebug->leave_sub();
 
   return $self;
@@ -184,7 +181,7 @@ sub flatten_standard_variables {
   $main::lxdebug->enter_sub(2);
 
   my $self      = shift;
-  my %skip_keys = map { $_ => 1 } (qw(login password header stylesheet titlebar version), @_);
+  my %skip_keys = map { $_ => 1 } (qw(login password header stylesheet titlebar), @_);
 
   my @variables;
 
@@ -197,36 +194,6 @@ sub flatten_standard_variables {
   return @variables;
 }
 
-sub debug {
-  $main::lxdebug->enter_sub();
-
-  my ($self) = @_;
-
-  print "\n";
-
-  map { print "$_ = $self->{$_}\n" } (sort keys %{$self});
-
-  $main::lxdebug->leave_sub();
-}
-
-sub dumper {
-  $main::lxdebug->enter_sub(2);
-
-  my $self          = shift;
-  my $password      = $self->{password};
-
-  $self->{password} = 'X' x 8;
-
-  local $Data::Dumper::Sortkeys = 1;
-  my $output                    = Dumper($self);
-
-  $self->{password} = $password;
-
-  $main::lxdebug->leave_sub(2);
-
-  return $output;
-}
-
 sub escape {
   my ($self, $str) = @_;
 
@@ -342,13 +309,12 @@ sub numtextrows {
 }
 
 sub dberror {
-  $main::lxdebug->enter_sub();
-
   my ($self, $msg) = @_;
 
-  $self->error("$msg\n" . $DBI::errstr);
-
-  $main::lxdebug->leave_sub();
+  die SL::X::DBError->new(
+    msg   => $msg,
+    error => $DBI::errstr,
+  );
 }
 
 sub isblank {
@@ -373,7 +339,7 @@ sub _get_request_uri {
   return URI->new($ENV{HTTP_REFERER})->canonical() if $ENV{HTTP_X_FORWARDED_FOR};
   return URI->new                                  if !$ENV{REQUEST_URI}; # for testing
 
-  my $scheme =  $ENV{HTTPS} && (lc $ENV{HTTPS} eq 'on') ? 'https' : 'http';
+  my $scheme =  $::request->is_https ? 'https' : 'http';
   my $port   =  $ENV{SERVER_PORT};
   $port      =  undef if (($scheme eq 'http' ) && ($port == 80))
                       || (($scheme eq 'https') && ($port == 443));
@@ -423,7 +389,7 @@ sub create_http_response {
       $session_cookie = $cgi->cookie('-name'   => $main::auth->get_session_cookie_name(),
                                      '-value'  => $session_cookie_value,
                                      '-path'   => $uri->path,
-                                     '-secure' => $ENV{HTTPS});
+                                     '-secure' => $::request->is_https);
     }
   }
 
@@ -468,11 +434,12 @@ sub header {
     jquery jquery-ui jquery.cookie jquery.checkall jquery.download
     jquery/jquery.form jquery/fixes client_js
     jquery/jquery.tooltipster.min
+    jquery.multiselect2side
     common part_selection
   ), "jquery/ui/i18n/jquery.ui.datepicker-$::myconfig{countrycode}");
 
   $self->{favicon} ||= "favicon.ico";
-  $self->{titlebar} = join ' - ', grep $_, $self->{title}, $self->{login}, $::myconfig{dbname}, $self->{version} if $self->{title} || !$self->{titlebar};
+  $self->{titlebar} = join ' - ', grep $_, $self->{title}, $self->{login}, $::myconfig{dbname}, $self->read_version if $self->{title} || !$self->{titlebar};
 
   # build includes
   if ($self->{refresh_url} || $self->{refresh_time}) {
@@ -566,7 +533,7 @@ sub set_standard_title {
   $::lxdebug->enter_sub;
   my $self = shift;
 
-  $self->{titlebar}  = "kivitendo " . $::locale->text('Version') . " $self->{version}";
+  $self->{titlebar}  = "kivitendo " . $::locale->text('Version') . " " . $self->read_version;
   $self->{titlebar} .= "- $::myconfig{name}"   if $::myconfig{name};
   $self->{titlebar} .= "- $::myconfig{dbname}" if $::myconfig{name};
 
@@ -618,7 +585,7 @@ sub parse_html_template {
   $additional_params ||= { };
 
   my $real_file = $self->_prepare_html_template($file, $additional_params);
-  my $template  = $self->template || $self->init_template;
+  my $template  = $self->template;
 
   map { $additional_params->{$_} ||= $self->{$_} } keys %{ $self };
 
@@ -630,32 +597,7 @@ sub parse_html_template {
   return $output;
 }
 
-sub init_template {
-  my $self = shift;
-
-  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,
-     'ABSOLUTE'     => 1,
-     'CACHE_SIZE'   => 0,
-     'PLUGIN_BASE'  => 'SL::Template::Plugin',
-     'INCLUDE_PATH' => '.:templates/webpages',
-     'COMPILE_EXT'  => '.tcc',
-     'COMPILE_DIR'  => $::lx_office_conf{paths}->{userspath} . '/templates-cache',
-     'ERROR'        => 'templates/webpages/generic/exception.html',
-     'ENCODING'     => 'utf8',
-  })) || die;
-}
-
-sub template {
-  my $self = shift;
-  $self->{template_object} = shift if @_;
-  return $self->{template_object};
-}
+sub template { $::request->presenter->get_template }
 
 sub show_generic_error {
   $main::lxdebug->enter_sub();
@@ -680,22 +622,18 @@ sub show_generic_error {
     'label_error' => $error,
   };
 
-  if ($params{action}) {
-    my @vars;
-
-    map { delete($self->{$_}); } qw(action);
-    map { push @vars, { "name" => $_, "value" => $self->{$_} } if (!ref($self->{$_})); } keys %{ $self };
-
-    $add_params->{SHOW_BUTTON}  = 1;
-    $add_params->{BUTTON_LABEL} = $params{label} || $params{action};
-    $add_params->{VARIABLES}    = \@vars;
+  $self->{title} = $params{title} if $params{title};
 
-  } elsif ($params{back_button}) {
-    $add_params->{SHOW_BACK_BUTTON} = 1;
+  for my $bar ($::request->layout->get('actionbar')) {
+    $bar->add(
+      action => [
+        t8('Back'),
+        call      => [ 'kivi.history_back' ],
+        accesskey => 'enter',
+      ],
+    );
   }
 
-  $self->{title} = $params{title} if $params{title};
-
   $self->header();
   print $self->parse_html_template("generic/error", $add_params);
 
@@ -745,6 +683,7 @@ sub redirect {
     $self->info($msg);
 
   } else {
+    SL::Helper::Flash::flash_later('info', $msg) if $msg;
     $self->_store_redirect_info_in_session;
     print $::form->redirect_header($self->{callback});
   }
@@ -1047,16 +986,19 @@ sub parse_template {
 
   # OUT is used for the media, screen, printer, email
   # for postscript we store a copy in a temporary file
+  my $keep_temp_files = $::lx_office_conf{debug} && $::lx_office_conf{debug}->{keep_temp_files};
+
   my ($temp_fh, $suffix);
   $suffix =  $self->{IN};
   $suffix =~ s/.*\.//;
   ($temp_fh, $self->{tmpfile}) = File::Temp::tempfile(
-    'kivitendo-printXXXXXX',
+    strftime('kivitendo-print-%Y%m%d%H%M%S-XXXXXX', localtime()),
     SUFFIX => '.' . ($suffix || 'tex'),
     DIR    => $userspath,
-    UNLINK => ($::lx_office_conf{debug} && $::lx_office_conf{debug}->{keep_temp_files})? 0 : 1,
+    UNLINK => $keep_temp_files ? 0 : 1,
   );
   close $temp_fh;
+  chmod 0644, $self->{tmpfile} if $keep_temp_files;
   (undef, undef, $self->{template_meta}{tmpfile}) = File::Spec->splitpath( $self->{tmpfile} );
 
   $out              = $self->{OUT};
@@ -1086,11 +1028,20 @@ sub parse_template {
   close OUT if $self->{OUT};
   # check only one flag (webdav_documents)
   # therefore copy to webdav, even if we do not have the webdav feature enabled (just archive)
-  my $copy_to_webdav =  $::instance_conf->get_webdav_documents && !$self->{preview} && $self->{tmpdir} && $self->{tmpfile} && $self->{type};
-
+  my $copy_to_webdav =  $::instance_conf->get_webdav_documents && !$self->{preview} && $self->{tmpdir} && $self->{tmpfile} && $self->{type}
+                        && $self->{type} ne 'statement';
+  if ( $ext_for_format eq 'pdf' && $self->doc_storage_enabled ) {
+    $self->append_general_pdf_attachments(filepath =>  $self->{tmpdir}."/".$self->{tmpfile},
+                                          type     =>  $self->{type});
+  }
   if ($self->{media} eq 'file') {
     copy(join('/', $self->{cwd}, $userspath, $self->{tmpfile}), $out =~ m|^/| ? $out : join('/', $self->{cwd}, $out)) if $template->uses_temp_file;
     Common::copy_file_to_webdav_folder($self)                                                                         if $copy_to_webdav;
+    if (!$self->{preview} && $self->doc_storage_enabled)
+    {
+      $self->{attachment_filename} ||= $self->generate_attachment_filename;
+      $self->store_pdf($self);
+    }
     $self->cleanup;
     chdir("$self->{cwd}");
 
@@ -1101,98 +1052,179 @@ sub parse_template {
 
   Common::copy_file_to_webdav_folder($self) if $copy_to_webdav;
 
+  if ( !$self->{preview} && $ext_for_format eq 'pdf' && $self->doc_storage_enabled) {
+    $self->{attachment_filename} ||= $self->generate_attachment_filename;
+    my $file_obj = $self->store_pdf($self);
+    $self->{print_file_id} = $file_obj->id if $file_obj;
+  }
   if ($self->{media} eq 'email') {
+    if ( getcwd() eq $self->{"tmpdir"} ) {
+      # in the case of generating pdf we are in the tmpdir, but WHY ???
+      $self->{tmpfile} = $userspath."/".$self->{tmpfile};
+      chdir("$self->{cwd}");
+    }
+    $self->send_email(\%::myconfig,$ext_for_format);
+  }
+  else {
+    $self->{OUT}      = $out;
+    $self->{OUT_MODE} = $out_mode;
+    $self->output_file($template->get_mime_type,$command_formatter);
+  }
+  delete $self->{print_file_id};
 
-    my $mail = Mailer->new;
-
-    map { $mail->{$_} = $self->{$_} }
-      qw(cc bcc subject message version format);
-    $mail->{to} = $self->{EMAIL_RECIPIENT} ? $self->{EMAIL_RECIPIENT} : $self->{email};
-    $mail->{from}   = qq|"$myconfig->{name}" <$myconfig->{email}>|;
-    $mail->{fileid} = time() . '.' . $$ . '.';
-    my $full_signature     =  $self->create_email_signature();
-    $full_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;
-      $full_signature         =~ s/\n/<br>\n/g;
-      $mail->{message}       .=  $full_signature;
-
-      open(IN, "<:encoding(UTF-8)", $self->{tmpfile})
-        or $self->error($self->cleanup . "$self->{tmpfile} : $!");
-      $mail->{message} .= $_ while <IN>;
-      close(IN);
+  $self->cleanup;
 
-    } else {
+  chdir("$self->{cwd}");
+  $main::lxdebug->leave_sub();
+}
 
-      if (!$self->{"do_not_attach"}) {
-        my $attachment_name  =  $self->{attachment_filename} || $self->{tmpfile};
-        $attachment_name     =~ s/\.(.+?)$/.${ext_for_format}/ if ($ext_for_format);
-        $mail->{attachments} =  [{ "filename" => $self->{tmpfile},
-                                   "name"     => $attachment_name }];
-      }
+sub get_bcc_defaults {
+  my ($self, $myconfig, $mybcc) = @_;
+  if (SL::DB::Default->get->bcc_to_login) {
+    $mybcc .= ", " if $mybcc;
+    $mybcc .= $myconfig->{email};
+  }
+  my $otherbcc = SL::DB::Default->get->global_bcc;
+  if ($otherbcc) {
+    $mybcc .= ", " if $mybcc;
+    $mybcc .= $otherbcc;
+  }
+  return $mybcc;
+}
 
-      $mail->{message} .= $full_signature;
-    }
+sub send_email {
+  $main::lxdebug->enter_sub();
+  my ($self, $myconfig, $ext_for_format) = @_;
+  my $mail = Mailer->new;
 
-    my $err = $mail->send();
-    $self->error($self->cleanup . "$err") if ($err);
+  map { $mail->{$_} = $self->{$_} }
+    qw(cc subject message format);
 
-  } else {
+  $mail->{bcc}    = $self->get_bcc_defaults($myconfig, $self->{bcc});
+  $mail->{to}     = $self->{EMAIL_RECIPIENT} ? $self->{EMAIL_RECIPIENT} : $self->{email};
+  $mail->{from}   = qq|"$myconfig->{name}" <$myconfig->{email}>|;
+  $mail->{fileid} = time() . '.' . $$ . '.';
+  my $full_signature     =  $self->create_email_signature();
+  $full_signature        =~ s/\r//g;
 
-    $self->{OUT}      = $out;
-    $self->{OUT_MODE} = $out_mode;
+  $mail->{attachments} =  [];
+  my @attfiles;
+  # if we send html or plain text inline
+  if (($self->{format} eq 'html') && ($self->{sendmode} eq 'inline')) {
+    $mail->{content_type}   =  "text/html";
+    $mail->{message}        =~ s/\r//g;
+    $mail->{message}        =~ s/\n/<br>\n/g;
+    $full_signature         =~ s/\n/<br>\n/g;
+    $mail->{message}       .=  $full_signature;
 
-    my $numbytes = (-s $self->{tmpfile});
     open(IN, "<", $self->{tmpfile})
       or $self->error($self->cleanup . "$self->{tmpfile} : $!");
-    binmode IN;
+    $mail->{message} .= $_ while <IN>;
+    close(IN);
 
-    $self->{copies} = 1 unless $self->{media} eq 'printer';
+  } elsif (($self->{attachment_policy} // '') ne 'no_file') {
+    my $attachment_name  =  $self->{attachment_filename}  || $self->{tmpfile};
+    $attachment_name     =~ s/\.(.+?)$/.${ext_for_format}/ if ($ext_for_format);
 
-    chdir("$self->{cwd}");
-    #print(STDERR "Kopien $self->{copies}\n");
-    #print(STDERR "OUT $self->{OUT}\n");
-    for my $i (1 .. $self->{copies}) {
-      if ($self->{OUT}) {
-        $self->{OUT} = $command_formatter->($self->{OUT_MODE}, $self->{OUT});
+    if (($self->{attachment_policy} // '') eq 'old_file') {
+      my ( $attfile ) = SL::File->get_all(object_id   => $self->{id},
+                                          object_type => $self->{formname},
+                                          file_type   => 'document');
 
-        open  OUT, $self->{OUT_MODE}, $self->{OUT} or $self->error($self->cleanup . "$self->{OUT} : $!");
-        print OUT $_ while <IN>;
-        close OUT;
-        seek  IN, 0, 0;
+      if ($attfile) {
+        $attfile->{override_file_name} = $attachment_name if $attachment_name;
+        push @attfiles, $attfile;
+      }
 
-      } else {
-        my %headers = ('-type'       => $template->get_mime_type,
-                       '-connection' => 'close',
-                       '-charset'    => 'UTF-8');
-
-        $self->{attachment_filename} ||= $self->generate_attachment_filename;
-
-        if ($self->{attachment_filename}) {
-          %headers = (
-            %headers,
-            '-attachment'     => $self->{attachment_filename},
-            '-content-length' => $numbytes,
-            '-charset'        => '',
-          );
-        }
+    } else {
+      push @{ $mail->{attachments} }, { path => $self->{tmpfile},
+                                        id   => $self->{print_file_id},
+                                        type => "application/pdf",
+                                        name => $attachment_name };
+    }
+  }
 
-        print $::request->cgi->header(%headers);
+  push @attfiles,
+    grep { $_ }
+    map  { SL::File->get(id => $_) }
+    @{ $self->{attach_file_ids} // [] };
 
-        $::locale->with_raw_io(\*STDOUT, sub { print while <IN> });
-      }
-    }
+  foreach my $attfile ( @attfiles ) {
+    push @{ $mail->{attachments} }, {
+      path    => $attfile->get_file,
+      id      => $attfile->id,
+      type    => $attfile->mime_type,
+      name    => $attfile->{override_file_name} // $attfile->file_name,
+      content => $attfile->get_content ? ${ $attfile->get_content } : undef,
+    };
+  }
 
-    close(IN);
+  $mail->{message}  =~ s/\r//g;
+  $mail->{message} .= $full_signature;
+  $self->{emailerr} = $mail->send();
+
+  if ($self->{emailerr}) {
+    $self->cleanup;
+    $self->error($::locale->text('The email was not sent due to the following error: #1.', $self->{emailerr}));
   }
 
-  $self->cleanup;
+  $self->{email_journal_id} = $mail->{journalentry};
+  $self->{snumbers}  = "emailjournal" . "_" . $self->{email_journal_id};
+  $self->{what_done} = $::form->{type};
+  $self->{addition}  = "MAILED";
+  $self->save_history;
+
+  #write back for message info and mail journal
+  $self->{cc}  = $mail->{cc};
+  $self->{bcc} = $mail->{bcc};
+  $self->{email} = $mail->{to};
+
+  $main::lxdebug->leave_sub();
+}
+
+sub output_file {
+  $main::lxdebug->enter_sub();
+
+  my ($self,$mimeType,$command_formatter) = @_;
+  my $numbytes = (-s $self->{tmpfile});
+  open(IN, "<", $self->{tmpfile})
+    or $self->error($self->cleanup . "$self->{tmpfile} : $!");
+  binmode IN;
+
+  $self->{copies} = 1 unless $self->{media} eq 'printer';
 
   chdir("$self->{cwd}");
+  for my $i (1 .. $self->{copies}) {
+    if ($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;
+
+    } else {
+      my %headers = ('-type'       => $mimeType,
+                     '-connection' => 'close',
+                     '-charset'    => 'UTF-8');
+
+      $self->{attachment_filename} ||= $self->generate_attachment_filename;
+
+      if ($self->{attachment_filename}) {
+        %headers = (
+          %headers,
+          '-attachment'     => $self->{attachment_filename},
+          '-content-length' => $numbytes,
+          '-charset'        => '',
+        );
+      }
+
+      print $::request->cgi->header(%headers);
+
+      $::locale->with_raw_io(\*STDOUT, sub { print while <IN> });
+    }
+  }
+  close(IN);
   $main::lxdebug->leave_sub();
 }
 
@@ -1219,8 +1251,13 @@ sub get_formname_translation {
     sales_delivery_order    => $main::locale->text('Delivery Order'),
     purchase_delivery_order => $main::locale->text('Delivery Order'),
     dunning                 => $main::locale->text('Dunning'),
+    dunning1                => $main::locale->text('Payment Reminder'),
+    dunning2                => $main::locale->text('Dunning'),
+    dunning3                => $main::locale->text('Last Dunning'),
+    dunning_invoice         => $main::locale->text('Dunning Invoice'),
     letter                  => $main::locale->text('Letter'),
     ic_supply               => $main::locale->text('Intra-Community supply'),
+    statement               => $main::locale->text('Statement'),
   );
 
   $main::lxdebug->leave_sub();
@@ -1306,6 +1343,38 @@ sub generate_email_subject {
   return $subject;
 }
 
+sub generate_email_body {
+  $main::lxdebug->enter_sub();
+  my ($self) = @_;
+  # simple german and english will work grammatically (most european languages as well)
+  # Dear Mr Alan Greenspan:
+  # Sehr geehrte Frau Meyer,
+  # A l’attention de Mme Villeroy,
+  # Gentile Signora Ferrari,
+  my $body = '';
+
+  if ($self->{cp_id}) {
+    my $givenname = SL::DB::Contact->load_cached($self->{cp_id})->cp_givenname; # for qw(gender givename name);
+    my $name      = SL::DB::Contact->load_cached($self->{cp_id})->cp_name; # for qw(gender givename name);
+    my $gender    = SL::DB::Contact->load_cached($self->{cp_id})->cp_gender; # for qw(gender givename name);
+    my $mf = $gender eq 'f' ? 'female' : 'male';
+    $body  = GenericTranslations->get(translation_type => "salutation_$mf", language_id => $self->{language_id});
+    $body .= ' ' . $givenname . ' ' . $name if $body;
+  } else {
+    $body  = GenericTranslations->get(translation_type => "salutation_general", language_id => $self->{language_id});
+  }
+
+  return undef unless $body;
+
+  $body   .= GenericTranslations->get(translation_type =>"salutation_punctuation_mark", language_id => $self->{language_id}) . "\n";
+  $body   .= GenericTranslations->get(translation_type =>"preset_text_$self->{formname}", language_id => $self->{language_id});
+
+  $body = $main::locale->unquote_special_chars('HTML', $body);
+
+  $main::lxdebug->leave_sub();
+  return $body;
+}
+
 sub cleanup {
   $main::lxdebug->enter_sub();
 
@@ -2464,81 +2533,7 @@ sub get_name {
   return scalar(@{ $self->{name_list} });
 }
 
-# the selection sub is used in the AR, AP, IS, IR, DO and OE module
-#
-sub all_vc {
-  $main::lxdebug->enter_sub();
-
-  my ($self, $myconfig, $table, $module) = @_;
-
-  my $ref;
-  my $dbh = $self->get_standard_dbh;
-
-  $table = $table eq "customer" ? "customer" : "vendor";
-
-  # 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 = $self->{id} ? '' : "WHERE NOT obsolete";
-  my $query = qq|SELECT count(*) FROM $table $obsolete|;
-  my ($count) = selectrow_query($self, $dbh, $query);
-
-  if ($count <= $myconfig->{vclimit}) {
-    $query = qq|SELECT id, name, salesman_id
-                FROM $table $obsolete
-                ORDER BY name|;
-    $self->{"all_$table"} = selectall_hashref_query($self, $dbh, $query);
-  }
-
-  # get self
-  $self->get_employee($dbh);
-
-  # setup sales contacts
-  $query = qq|SELECT e.id, e.name
-              FROM employee e
-              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
-  push(@{ $self->{all_employees} },
-       { id   => $self->{employee_id},
-         name => $self->{employee} });
-
-    # prepare query for departments
-    $query = qq|SELECT id, description
-                FROM department
-                ORDER BY description|;
-
-  $self->{all_departments} = selectall_hashref_query($self, $dbh, $query);
-
-  # get languages
-  $query = qq|SELECT id, description
-              FROM language
-              ORDER BY id|;
-
-  $self->{languages} = selectall_hashref_query($self, $dbh, $query);
-
-  # get printer
-  $query = qq|SELECT printer_description, id
-              FROM printers
-              ORDER BY printer_description|;
-
-  $self->{printers} = selectall_hashref_query($self, $dbh, $query);
-
-  # get payment terms
-  $query = qq|SELECT id, description
-              FROM payment_terms
-              ORDER BY sortkey|;
-
-  $self->{payment_terms} = selectall_hashref_query($self, $dbh, $query);
-
-  $main::lxdebug->leave_sub();
-}
-
 sub new_lastmtime {
-  $main::lxdebug->enter_sub();
 
   my ($self, $table, $provided_dbh) = @_;
 
@@ -2550,9 +2545,7 @@ sub new_lastmtime {
   my $ref         = selectfirst_hashref_query($self, $dbh, $query, $self->{id});
   $ref->{mtime} ||= $ref->{itime};
   $self->{lastmtime} = $ref->{mtime};
-  $main::lxdebug->message(LXDebug->DEBUG2(),"new lastmtime=".$self->{lastmtime});
 
-  $main::lxdebug->leave_sub();
 }
 
 sub mtime_ischanged {
@@ -2574,6 +2567,9 @@ sub mtime_ischanged {
   }
 }
 
+# language_payment duplicates some of the functionality of all_vc (language,
+# printer, payment_terms), and at least in the case of sales invoices both
+# all_vc and language_payment are called when adding new invoices
 sub language_payment {
   $main::lxdebug->enter_sub();
 
@@ -2597,9 +2593,9 @@ sub language_payment {
   # get payment terms
   $query = qq|SELECT id, description
               FROM payment_terms
-              ORDER BY sortkey|;
-
-  $self->{payment_terms} = selectall_hashref_query($self, $dbh, $query);
+              WHERE ( obsolete IS FALSE OR id = ? )
+              ORDER BY sortkey |;
+  $self->{payment_terms} = selectall_hashref_query($self, $dbh, $query, $self->{payment_id} || undef);
 
   # get buchungsgruppen
   $query = qq|SELECT id, description
@@ -2643,8 +2639,6 @@ sub create_links {
     $arap = "ap";
   }
 
-  $self->all_vc($myconfig, $table, $module);
-
   # get last customers or vendors
   my ($query, $sth, $ref);
 
@@ -2659,15 +2653,8 @@ sub create_links {
     }
 
     # now get the account numbers
-#    $query = qq|SELECT c.accno, c.description, c.link, c.taxkey_id, tk.tax_id
-#                FROM chart c, taxkeys tk
-#                WHERE (c.link LIKE ?) AND (c.id = tk.chart_id) AND tk.id =
-#                  (SELECT id FROM taxkeys WHERE (taxkeys.chart_id = c.id) AND (startdate <= $transdate) ORDER BY startdate DESC LIMIT 1)
-#                ORDER BY c.accno|;
-
-#  same query as above, but without expensive subquery for each row. about 80% faster
     $query = qq|
-      SELECT c.accno, c.description, c.link, c.taxkey_id, tk2.tax_id
+      SELECT c.accno, c.description, c.link, c.taxkey_id, c.id AS chart_id, tk2.tax_id
         FROM chart c
         -- find newest entries in taxkeys
         INNER JOIN (
@@ -2697,6 +2684,7 @@ sub create_links {
 
           push @{ $self->{"${module}_links"}{$key} },
             { accno       => $ref->{accno},
+              chart_id    => $ref->{chart_id},
               description => $ref->{description},
               taxkey      => $ref->{taxkey_id},
               tax_id      => $ref->{tax_id} };
@@ -2750,7 +2738,7 @@ sub create_links {
     }
 
     # now get the account numbers
-    $query = qq|SELECT c.accno, c.description, c.link, c.taxkey_id, tk.tax_id
+    $query = qq|SELECT c.accno, c.description, c.link, c.taxkey_id, c.id AS chart_id, tk.tax_id
                 FROM chart c
                 LEFT JOIN taxkeys tk ON (tk.chart_id = c.id)
                 WHERE c.link LIKE ?
@@ -2772,6 +2760,7 @@ sub create_links {
 
           push @{ $self->{"${module}_links"}{$key} },
             { accno       => $ref->{accno},
+              chart_id    => $ref->{chart_id},
               description => $ref->{description},
               taxkey      => $ref->{taxkey_id},
               tax_id      => $ref->{tax_id} };
@@ -2786,7 +2775,7 @@ sub create_links {
     $query =
       qq|SELECT
            c.accno, c.description,
-           a.acc_trans_id, a.source, a.amount, a.memo, a.transdate, a.gldate, a.cleared, a.project_id, a.taxkey,
+           a.acc_trans_id, a.source, a.amount, a.memo, a.transdate, a.gldate, a.cleared, a.project_id, a.taxkey, a.chart_id,
            p.projectnumber,
            t.rate, t.id
          FROM acc_trans a
@@ -2923,6 +2912,22 @@ sub lastname_used {
   $main::lxdebug->leave_sub();
 }
 
+sub get_variable_content_types {
+  my %html_variables  = (
+      longdescription => 'html',
+      partnotes       => 'html',
+      notes           => 'html',
+      orignotes       => 'html',
+      notes1          => 'html',
+      notes2          => 'html',
+      notes3          => 'html',
+      notes4          => 'html',
+      header_text     => 'html',
+      footer_text     => 'html',
+  );
+  return \%html_variables;
+}
+
 sub current_date {
   $main::lxdebug->enter_sub();
 
@@ -3089,6 +3094,7 @@ sub save_status {
 
 #--- 4 locale ---#
 # $main::locale->text('SAVED')
+# $main::locale->text('SCREENED')
 # $main::locale->text('DELETED')
 # $main::locale->text('ADDED')
 # $main::locale->text('PAYMENT POSTED')
@@ -3101,6 +3107,8 @@ sub save_status {
 # $main::locale->text('MAILED')
 # $main::locale->text('SCREENED')
 # $main::locale->text('CANCELED')
+# $main::locale->text('IMPORT')
+# $main::locale->text('UNIMPORT')
 # $main::locale->text('invoice')
 # $main::locale->text('proforma')
 # $main::locale->text('sales_order')
@@ -3156,7 +3164,10 @@ sub get_history {
     while(my $hash_ref = $sth->fetchrow_hashref()) {
       $hash_ref->{addition} = $main::locale->text($hash_ref->{addition});
       $hash_ref->{what_done} = $main::locale->text($hash_ref->{what_done});
-      $hash_ref->{snumbers} =~ s/^.+_(.*)$/$1/g;
+      my ( $what, $number ) = split /_/, $hash_ref->{snumbers};
+      $hash_ref->{snumbers} = $number;
+      $hash_ref->{haslink}  = 'controller.pl?action=EmailJournal/show&id='.$number if $what eq 'emailjournal';
+      $hash_ref->{snumbers} = $main::locale->text("E-Mail").' '.$number if $what eq 'emailjournal';
       $tempArray[$i++] = $hash_ref;
     }
     $main::lxdebug->leave_sub() and return \@tempArray
@@ -3180,16 +3191,13 @@ sub get_partsgroup {
   my @values;
 
   if ($p->{searchitems} eq 'part') {
-    $query .= qq|WHERE p.inventory_accno_id > 0|;
+    $query .= qq|WHERE p.part_type = 'part'|;
   }
   if ($p->{searchitems} eq 'service') {
-    $query .= qq|WHERE p.inventory_accno_id IS NULL|;
+    $query .= qq|WHERE p.part_type = 'service'|;
   }
   if ($p->{searchitems} eq 'assembly') {
-    $query .= qq|WHERE p.assembly = '1'|;
-  }
-  if ($p->{searchitems} eq 'labor') {
-    $query .= qq|WHERE (p.inventory_accno_id > 0) AND (p.income_accno_id IS NULL)|;
+    $query .= qq|WHERE p.part_type = 'assembly'|;
   }
 
   $query .= qq|ORDER BY partsgroup|;
@@ -3578,29 +3586,6 @@ sub create_email_signature {
 
 };
 
-sub layout {
-  my ($self) = @_;
-  $::lxdebug->enter_sub;
-
-  my %style_to_script_map = (
-    v3  => 'v3',
-    neu => 'new',
-  );
-
-  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;
-}
-
 sub calculate_tax {
   # this function calculates the net amount and tax for the lines in ar, ap and
   # gl and is used for update as well as post. When used with update the return