Merge branch 'after-262'
authorMoritz Bunkus <m.bunkus@linet-services.de>
Wed, 9 Feb 2011 12:58:23 +0000 (13:58 +0100)
committerMoritz Bunkus <m.bunkus@linet-services.de>
Wed, 9 Feb 2011 12:58:23 +0000 (13:58 +0100)
Conflicts:
scripts/dbupgrade2_tool.pl

1  2 
DEBIAN/DEBIAN/postinst
DEBIAN/mk_erp_deb.sh
SL/DO.pm
SL/Form.pm
bin/mozilla/admin.pl
bin/mozilla/oe.pl
doc/INSTALL.texi
doc/INSTALL.txt
locale/de_DE/all
scripts/dbupgrade2_tool.pl
templates/webpages/oe/form_header.html

diff --combined DEBIAN/DEBIAN/postinst
@@@ -115,10 -115,10 +115,10 @@@ set_lx_office_erp_authentication_db_use
  set_user_rights() {
        chown -R www-data:www-data /usr/lib/lx-office-erp/users
        chown -R www-data:www-data /usr/lib/lx-office-erp/templates
-       chown www-data:www-data /etc/lx-office-erp/lx-erp.conf
+       chown www-data:www-data /etc/lx-office-erp/lx_office.conf
        chown www-data:www-data /usr/lib/lx-office-erp/menu.ini
        chown www-data:www-data /etc/lx-office-erp/authentication.pl
-       chmod 0600 /etc/lx-office-erp/lx-erp.conf
+       chmod 0600 /etc/lx-office-erp/lx_office.conf
        chmod 0600 /etc/lx-office-erp/authentication.pl
  }
  
@@@ -135,63 -135,49 +135,57 @@@ disable_ipv6_on_lo_interface() 
  
  }
  mk_new_menu() {
-     if [ -e /usr/lib/lx-office-crm ] ; then 
+     if [ -e /usr/lib/lx-office-crm ] ; then
          #crm vorhanden, dann die menu.ini mit der höchsten VersNr nehmen
-         for i in `ls -1 /usr/lib/lx-office-crm/update/menu*ini` ; do 
+         for i in `ls -1 /usr/lib/lx-office-crm/update/menu*ini` ; do
              cat $i > /usr/lib/lx-office-erp/menu.ini
          done;
          cat /usr/lib/lx-office-erp/menu.default >> /usr/lib/lx-office-erp/menu.ini
      else
          cp /usr/lib/lx-office-erp/menu.default /usr/lib/lx-office-erp/menu.ini
      fi
- }  
+ }
  
  mk_new_config() {
-     if ! [ -f /etc/lx-office-erp/lx-erp.conf ] ; then
-         cp /etc/lx-office-erp/lx-erp.conf.default /etc/lx-office-erp/lx-erp.conf
-     fi
-     if ! [ -f /etc/lx-office-erp/console.conf ] ; then
-         cp /etc/lx-office-erp/console.conf.default /etc/lx-office-erp/console.conf
+     if ! [ -f /etc/lx-office-erp/lx_office.conf ] ; then
+         cp /etc/lx-office-erp/lx_office.conf.default /etc/lx-office-erp/lx_office.conf
      fi
- }  
+ }
  
  mk_links() {
      if ! [ -f /usr/lib/lx-office-erp/config/authentication.pl ] ; then
          ln -s /etc/lx-office-erp/authentication.pl /usr/lib/lx-office-erp/config/authentication.pl
      fi;
-     if ! [ -f /usr/lib/lx-office-erp/config/lx-erp.conf ] ; then
-         ln -s /etc/lx-office-erp/lx-erp.conf /usr/lib/lx-office-erp/config/lx-erp.conf
+     if ! [ -f /usr/lib/lx-office-erp/config/lx_office.conf ] ; then
+         ln -s /etc/lx-office-erp/lx_office.conf /usr/lib/lx-office-erp/config/lx_office.conf
      fi;
-     if ! [ -f /usr/lib/lx-office-erp/config/console.conf ] ; then
-         ln -s /etc/lx-office-erp/console.conf /usr/lib/lx-office-erp/config/console.conf
-     fi;
-     if [ -e /etc/apache2 ] ; then 
+     if [ -e /etc/apache2 ] ; then
          if ! [ -f /etc/apache2/conf.d/lx-office-erp.apache2.conf ] ; then
              ln -s /etc/lx-office-erp/lx-office-erp.apache2.conf /etc/apache2/conf.d/lx-office-erp.apache2.conf
          fi;
      fi;
-     if [ -e /etc/cherokee/sites-available ] ; then 
+     if [ -e /etc/cherokee/sites-available ] ; then
          if ! [ -f /etc/cherokee/sites-available/lx-office-erp.cherokee ] ; then
              cat /etc/lx-office-erp/lx-office-erp.cherokee.handler >> /etc/cherokee/sites-available/default
              ln -s /etc/lx-office-erp/lx-office-erp.cherokee /etc/cherokee/sites-available/lx-office-erp.cherokee
          fi;
      fi;
 +    if [ -e /etc/lighttpd ] ; then 
 +        if ! [ -f /etc/lighttpd/conf-enabled/lx-office-erp.lighttpd ] ; then
 +            ln -s /etc/lx-office-erp/lx-office-erp.lighttpd /etc/lighttpf/conf-enabled/10-lx-office-erp
 +        fi;
 +    fi;
  }
  reload_web_server() {
-     if [ -f /etc/init.d/apache* ] ; then 
+     if [ -f /etc/init.d/apache* ] ; then
              /etc/init.d/apache* reload
      fi
-     if [ -f /etc/init.d/cherokee ] ; then 
+     if [ -f /etc/init.d/cherokee ] ; then
              /etc/init.d/cherokee reload
      fi
 +    if [ -f /etc/init.d/lighttpd ] ; then 
 +            /etc/init.d/lighttpd reload
 +    fi
  }
  case "$1" in
  
  
      install|configure)
          echo " ! "`date`" $1 !" >> /tmp/lxo-erp.log
-         
          mk_new_menu
          mk_new_config
          config_postgresql_factory_script
diff --combined DEBIAN/mk_erp_deb.sh
@@@ -15,7 -15,7 +15,7 @@@ DST=/tmp/packag
  ################################################
  
  VER=`cat VERSION`
 -DEST=$DST/lx-office-erp_$VER-$NR-all
 +DEST=$DST/lx-office-erp_$VER-$NR$1-all
  
  
  mkdir -p $DEST
@@@ -25,13 -25,6 +25,13 @@@ cd $DES
  cp -a $SRC/DEBIAN/DEBIAN .
  tar xzf $SRC/DEBIAN/struktur.tgz
  
 +#Für Hardy + co Sonderbehandlung
 +if [ "$1#" == "-older#" ]; then
 +    mv DEBIAN/control.older DEBIAN/control
 +else
 +    rm DEBIAN/control.older
 +fi
 +
  #Dateien kopieren:
  #aber keine fertigen Konfigurationen, nur *.default
  cp -a $SRC/SL usr/lib/lx-office-erp
@@@ -46,8 -39,7 +46,7 @@@ cp -a $SRC/t usr/lib/lx-office-er
  cp -a $SRC/*.pl usr/lib/lx-office-erp
  cp $SRC/VERSION usr/lib/lx-office-erp
  cp $SRC/index.html usr/lib/lx-office-erp
- cp $SRC/config/lx-erp.conf  etc/lx-office-erp/lx-erp.conf.default
- cp $SRC/config/console.conf.default etc/lx-office-erp/
+ cp $SRC/config/lx_office.conf.default etc/lx-office-erp/lx_office.conf.default
  cp $SRC/config/authentication.pl.default etc/lx-office-erp/
  cp $SRC/menu.ini usr/lib/lx-office-erp/menu.default
  cp -a $SRC/css var/lib/lx-office-erp
@@@ -58,11 -50,6 +57,11 @@@ cp -a $SRC/xslt var/lib/lx-office-er
  cp -a $SRC/doc/* usr/share/doc/lx-office-erp/
  cp -a $SRC/image/* usr/share/lx-office-erp/
  
 +#Ist nicht im Repository. Liegt bei sf
 +if [ "$1#" == "-older#" ]; then
 +    tar xzf $SRC/DEBIAN/lx-erp-perl-libs-compat-v2.tar.gz
 +fi
 +
  #Git- und dummy-files löschen
  find . -name ".git*" -exec rm -rf {} \;
  find . -name ".dummy" -exec rm -rf {} \;
@@@ -89,6 -76,6 +88,6 @@@ mv DEBIAN/1.tmp DEBIAN/contro
  
  #Paket bauen:
  cd ..
 -dpkg-deb --build lx-office-erp_$VER-$NR-all
 +dpkg-deb --build lx-office-erp_$VER-$NR$1-all
  
  echo "Done"
diff --combined SL/DO.pm
+++ b/SL/DO.pm
@@@ -383,7 -383,7 +383,7 @@@ sub save 
  
    $form->{saved_donumber} = $form->{donumber};
  
-   Common::webdav_folder($form) if ($main::webdav);
+   Common::webdav_folder($form);
  
    $main::lxdebug->leave_sub();
  
@@@ -491,7 -491,7 +491,7 @@@ sub delete 
  
    my $myconfig = \%main::myconfig;
    my $form     = $main::form;
-   my $spool    = $main::spool;
+   my $spool    = $::lx_office_conf{paths}->{spool};
  
    # connect to database
    my $dbh = $form->get_standard_dbh($myconfig);
@@@ -712,7 -712,7 +712,7 @@@ sub retrieve 
      $sth->finish();
    }
  
-   Common::webdav_folder($form) if ($main::webdav);
+   Common::webdav_folder($form);
  
    $main::lxdebug->leave_sub();
  
@@@ -939,7 -939,7 +939,7 @@@ sub unpack_stock_information 
  }
  
  sub get_item_availability {
 -  $main::lxdebug->enter_sub();
 +  $::lxdebug->enter_sub;
  
    my $self     = shift;
    my %params   = @_;
    Common::check_params(\%params, qw(parts_id));
  
    my @parts_ids = 'ARRAY' eq ref $params{parts_id} ? @{ $params{parts_id} } : ($params{parts_id});
 -  my $form      = $main::form;
 -  my $myconfig  = \%main::myconfig;
  
    my $query     =
      qq|SELECT i.warehouse_id, i.bin_id, i.chargenumber, i.bestbefore, SUM(qty) AS qty, i.parts_id,
         HAVING SUM(qty) > 0
         ORDER BY LOWER(w.description), LOWER(b.description), LOWER(i.chargenumber), i.bestbefore
  |;
 -  my $contents = selectall_hashref_query($form, $form->get_standard_dbh($myconfig), $query, @parts_ids);
 +  my $contents = selectall_hashref_query($::form, $::form->get_standard_dbh, $query, @parts_ids);
  
 -  $main::lxdebug->leave_sub();
 +  $::lxdebug->leave_sub;
  
    return @{ $contents };
  }
diff --combined SL/Form.pm
@@@ -43,21 -43,28 +43,28 @@@ use CGI
  use CGI::Ajax;
  use Cwd;
  use Encode;
+ use File::Copy;
  use IO::File;
  use SL::Auth;
  use SL::Auth::DB;
  use SL::Auth::LDAP;
  use SL::AM;
  use SL::Common;
+ use SL::CVar;
+ use SL::DB;
  use SL::DBUtils;
+ use SL::DO;
+ use SL::IC;
+ use SL::IS;
  use SL::Mailer;
  use SL::Menu;
+ use SL::OE;
  use SL::Template;
  use SL::User;
  use Template;
  use URI;
  use List::Util qw(first max min sum);
- use List::MoreUtils qw(any apply);
+ use List::MoreUtils qw(all any apply);
  
  use strict;
  
@@@ -255,7 -262,7 +262,7 @@@ sub new 
      $self->_request_to_hash($content);
    }
  
-   my $db_charset   = $main::dbcharset;
+   my $db_charset   = $::lx_office_conf{system}->{dbcharset};
    $db_charset    ||= Common::DEFAULT_CHARSET;
  
    my $encoding     = $self->{INPUT_ENCODING} || $db_charset;
@@@ -378,7 -385,7 +385,7 @@@ sub escape 
    my ($self, $str) = @_;
  
    $str =  Encode::encode('utf-8-strict', $str) if $::locale->is_utf8;
 -  $str =~ s/([^a-zA-Z0-9_.-])/sprintf("%%%02x", ord($1))/ge;
 +  $str =~ s/([^a-zA-Z0-9_.:-])/sprintf("%%%02x", ord($1))/ge;
  
    $main::lxdebug->leave_sub(2);
  
@@@ -394,7 -401,6 +401,7 @@@ sub unescape 
    $str =~ s/\\$//;
  
    $str =~ s/%([0-9a-fA-Z]{2})/pack("c",hex($1))/eg;
 +  $str =  Encode::decode('utf-8-strict', $str) if $::locale->is_utf8;
  
    $main::lxdebug->leave_sub(2);
  
@@@ -442,13 -448,23 +449,23 @@@ sub hide_form 
    $main::lxdebug->leave_sub();
  }
  
+ sub throw_on_error {
+   my ($self, $code) = @_;
+   local $self->{__ERROR_HANDLER} = sub { die({ error => $_[0] }) };
+   $code->();
+ }
  sub error {
    $main::lxdebug->enter_sub();
  
    $main::lxdebug->show_backtrace();
  
    my ($self, $msg) = @_;
-   if ($ENV{HTTP_USER_AGENT}) {
+   if ($self->{__ERROR_HANDLER}) {
+     $self->{__ERROR_HANDLER}->($msg);
+   } elsif ($ENV{HTTP_USER_AGENT}) {
      $msg =~ s/\n/<br>/g;
      $self->show_generic_error($msg);
  
@@@ -620,7 -636,7 +637,7 @@@ sub header 
    # 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, $extra_code) = @_;
-   my $db_charset = $::dbcharset || Common::DEFAULT_CHARSET;
+   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}++;
@@@ -704,7 -720,7 +721,7 @@@ sub ajax_response_header 
  
    my ($self) = @_;
  
-   my $db_charset = $main::dbcharset ? $main::dbcharset : Common::DEFAULT_CHARSET;
+   my $db_charset = $::lx_office_conf{system}->{dbcharset} || Common::DEFAULT_CHARSET;
    my $cgi        = $main::cgi || CGI->new('');
    my $output     = $cgi->header('-charset' => $db_charset);
  
@@@ -745,7 -761,7 +762,7 @@@ sub _prepare_html_template 
    my $language;
  
    if (!%::myconfig || !$::myconfig{"countrycode"}) {
-     $language = $main::language;
+     $language = $::lx_office_conf{system}->{language};
    } else {
      $language = $main::myconfig{"countrycode"};
    }
      map { $additional_params->{"myconfig_${_}"} = $main::myconfig{$_}; } keys %::myconfig;
    }
  
-   $additional_params->{"conf_dbcharset"}              = $::dbcharset;
-   $additional_params->{"conf_webdav"}                 = $::webdav;
-   $additional_params->{"conf_lizenzen"}               = $::lizenzen;
-   $additional_params->{"conf_latex_templates"}        = $::latex;
-   $additional_params->{"conf_opendocument_templates"} = $::opendocument_templates;
-   $additional_params->{"conf_vertreter"}              = $::vertreter;
-   $additional_params->{"conf_show_best_before"}       = $::show_best_before;
-   $additional_params->{"conf_parts_image_css"}        = $::parts_image_css;
-   $additional_params->{"conf_parts_listing_images"}   = $::parts_listing_images;
-   $additional_params->{"conf_parts_show_image"}       = $::parts_show_image;
+   $additional_params->{"conf_dbcharset"}              = $::lx_office_conf{system}->{dbcharset};
+   $additional_params->{"conf_webdav"}                 = $::lx_office_conf{system}->{webdav};
+   $additional_params->{"conf_lizenzen"}               = $::lx_office_conf{system}->{lizenzen};
+   $additional_params->{"conf_latex_templates"}        = $::lx_office_conf{print_templates}->{latex};
+   $additional_params->{"conf_opendocument_templates"} = $::lx_office_conf{print_templates}->{opendocument};
+   $additional_params->{"conf_vertreter"}              = $::lx_office_conf{system}->{vertreter};
+   $additional_params->{"conf_show_best_before"}       = $::lx_office_conf{system}->{show_best_before};
+   $additional_params->{"conf_parts_image_css"}        = $::lx_office_conf{features}->{parts_image_css};
+   $additional_params->{"conf_parts_listing_images"}   = $::lx_office_conf{features}->{parts_listing_images};
+   $additional_params->{"conf_parts_show_image"}       = $::lx_office_conf{features}->{parts_show_image};
  
    if (%main::debug_options) {
      map { $additional_params->{'DEBUG_' . uc($_)} = $main::debug_options{$_} } keys %main::debug_options;
@@@ -845,7 -861,7 +862,7 @@@ sub init_template 
       'PLUGIN_BASE'  => 'SL::Template::Plugin',
       'INCLUDE_PATH' => '.:templates/webpages',
       'COMPILE_EXT'  => '.tcc',
-      'COMPILE_DIR'  => $::userspath . '/templates-cache',
+      'COMPILE_DIR'  => $::lx_office_conf{paths}->{userspath} . '/templates-cache',
    })) || die;
  }
  
@@@ -860,6 -876,12 +877,12 @@@ sub show_generic_error 
  
    my ($self, $error, %params) = @_;
  
+   if ($self->{__ERROR_HANDLER}) {
+     $self->{__ERROR_HANDLER}->($error);
+     $main::lxdebug->leave_sub();
+     return;
+   }
    my $add_params = {
      'title_error' => $params{title},
      'label_error' => $error,
@@@ -1169,11 -1191,13 +1192,13 @@@ sub round_amount 
  sub parse_template {
    $main::lxdebug->enter_sub();
  
-   my ($self, $myconfig, $userspath) = @_;
+   my ($self, $myconfig) = @_;
    my $out;
  
    local (*IN, *OUT);
  
+   my $userspath = $::lx_office_conf{paths}->{userspath};
    $self->{"cwd"} = getcwd();
    $self->{"tmpdir"} = $self->{cwd} . "/${userspath}";
  
    }
  
    map { $self->{"${_}"} = $myconfig->{$_}; } qw(co_ustid);
+   map { $self->{"myconfig_${_}"} = $myconfig->{$_} } grep { $_ ne 'dbpasswd' } keys %{ $myconfig };
  
    $self->{copies} = 1 if (($self->{copies} *= 1) <= 0);
  
      $self->error("$self->{IN} : " . $template->get_error());
    }
  
+   if ($self->{media} eq 'file') {
+     copy(join('/', $self->{cwd}, $userspath, $self->{tmpfile}), $out =~ m|^/| ? $out : join('/', $self->{cwd}, $out)) if $template->uses_temp_file;
+     $self->cleanup;
+     chdir("$self->{cwd}");
+     $::lxdebug->leave_sub();
+     return;
+   }
    if ($template->uses_temp_file() || $self->{media} eq 'email') {
  
      if ($self->{media} eq 'email') {
  
        map { $mail->{$_} = $self->{$_} }
          qw(cc bcc subject message version format);
-       $mail->{charset} = $main::dbcharset ? $main::dbcharset : Common::DEFAULT_CHARSET;
+       $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.";
@@@ -1473,7 -1508,7 +1509,7 @@@ sub cleanup 
      close(FH);
    }
  
-   if ($self->{tmpfile} && ! $::keep_temp_files) {
+   if ($self->{tmpfile} && !($::lx_office_conf{debug} && $::lx_office_conf{debug}->{keep_temp_files})) {
      $self->{tmpfile} =~ s|.*/||g;
      # strip extension
      $self->{tmpfile} =~ s/\.\w+$//g;
@@@ -1581,7 -1616,7 +1617,7 @@@ sub get_standard_dbh 
      undef $standard_dbh;
    }
  
-   $standard_dbh ||= $self->dbconnect_noauto($myconfig);
+   $standard_dbh ||= SL::DB::create->dbh;
  
    $main::lxdebug->leave_sub(2);
  
@@@ -3497,6 -3532,163 +3533,163 @@@ sub restore_vars 
    $main::lxdebug->leave_sub();
  }
  
+ sub prepare_for_printing {
+   my ($self) = @_;
+   $self->{templates} ||= $::myconfig{templates};
+   $self->{formname}  ||= $self->{type};
+   $self->{media}     ||= 'email';
+   die "'media' other than 'email', 'file', 'printer' is not supported yet" unless $self->{media} =~ m/^(?:email|file|printer)$/;
+   # set shipto from billto unless set
+   my $has_shipto = any { $self->{"shipto$_"} } qw(name street zipcode city country contact);
+   if (!$has_shipto && ($self->{type} =~ m/^(?:purchase_order|request_quotation)$/)) {
+     $self->{shiptoname}   = $::myconfig{company};
+     $self->{shiptostreet} = $::myconfig{address};
+   }
+   my $language = $self->{language} ? '_' . $self->{language} : '';
+   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;
+   }
+   # Retrieve accounts for tax calculation.
+   IC->retrieve_accounts(\%::myconfig, $self, map { $_ => $self->{"id_$_"} } 1 .. $self->{rowcount});
+   if ($self->{type} =~ /_delivery_order$/) {
+     DO->order_details();
+   } elsif ($self->{type} =~ /sales_order|sales_quotation|request_quotation|purchase_order/) {
+     OE->order_details(\%::myconfig, $self);
+   } else {
+     IS->invoice_details(\%::myconfig, $self, $::locale);
+   }
+   # Chose extension & set source file name
+   my $extension = 'html';
+   if ($self->{format} eq 'postscript') {
+     $self->{postscript}   = 1;
+     $extension            = 'tex';
+   } elsif ($self->{"format"} =~ /pdf/) {
+     $self->{pdf}          = 1;
+     $extension            = $self->{'format'} =~ m/opendocument/i ? 'odt' : 'tex';
+   } elsif ($self->{"format"} =~ /opendocument/) {
+     $self->{opendocument} = 1;
+     $extension            = 'odt';
+   } elsif ($self->{"format"} =~ /excel/) {
+     $self->{excel}        = 1;
+     $extension            = 'xls';
+   }
+   my $printer_code    = '_' . $self->{printer_code} if $self->{printer_code};
+   my $email_extension = '_email' if -f "$self->{templates}/$self->{formname}_email${language}${printer_code}.${extension}";
+   $self->{IN}         = "$self->{formname}${email_extension}${language}${printer_code}.${extension}";
+   # Format dates.
+   $self->format_dates($output_dateformat, $output_longdates,
+                       qw(invdate orddate quodate pldate duedate reqdate transdate shippingdate deliverydate validitydate paymentdate datepaid
+                          transdate_oe deliverydate_oe employee_startdate employee_enddate),
+                       grep({ /^(?:datepaid|transdate_oe|reqdate|deliverydate|deliverydate_oe|transdate)_\d+$/ } keys(%{$self})));
+   $self->reformat_numbers($output_numberformat, 2,
+                           qw(invtotal ordtotal quototal subtotal linetotal listprice sellprice netprice discount tax taxbase total paid),
+                           grep({ /^(?:linetotal|listprice|sellprice|netprice|taxbase|discount|paid|subtotal|total|tax)_\d+$/ } keys(%{$self})));
+   $self->reformat_numbers($output_numberformat, undef, qw(qty price_factor), grep({ /^qty_\d+$/} keys(%{$self})));
+   my ($cvar_date_fields, $cvar_number_fields) = CVar->get_field_format_list('module' => 'CT', 'prefix' => 'vc_');
+   if (scalar @{ $cvar_date_fields }) {
+     $self->format_dates($output_dateformat, $output_longdates, @{ $cvar_date_fields });
+   }
+   while (my ($precision, $field_list) = each %{ $cvar_number_fields }) {
+     $self->reformat_numbers($output_numberformat, $precision, @{ $field_list });
+   }
+   return $self;
+ }
+ sub format_dates {
+   my ($self, $dateformat, $longformat, @indices) = @_;
+   $dateformat ||= $::myconfig{dateformat};
+   foreach my $idx (@indices) {
+     if ($self->{TEMPLATE_ARRAYS} && (ref($self->{TEMPLATE_ARRAYS}->{$idx}) eq "ARRAY")) {
+       for (my $i = 0; $i < scalar(@{ $self->{TEMPLATE_ARRAYS}->{$idx} }); $i++) {
+         $self->{TEMPLATE_ARRAYS}->{$idx}->[$i] = $::locale->reformat_date(\%::myconfig, $self->{TEMPLATE_ARRAYS}->{$idx}->[$i], $dateformat, $longformat);
+       }
+     }
+     next unless defined $self->{$idx};
+     if (!ref($self->{$idx})) {
+       $self->{$idx} = $::locale->reformat_date(\%::myconfig, $self->{$idx}, $dateformat, $longformat);
+     } elsif (ref($self->{$idx}) eq "ARRAY") {
+       for (my $i = 0; $i < scalar(@{ $self->{$idx} }); $i++) {
+         $self->{$idx}->[$i] = $::locale->reformat_date(\%::myconfig, $self->{$idx}->[$i], $dateformat, $longformat);
+       }
+     }
+   }
+ }
+ sub reformat_numbers {
+   my ($self, $numberformat, $places, @indices) = @_;
+   return if !$numberformat || ($numberformat eq $::myconfig{numberformat});
+   foreach my $idx (@indices) {
+     if ($self->{TEMPLATE_ARRAYS} && (ref($self->{TEMPLATE_ARRAYS}->{$idx}) eq "ARRAY")) {
+       for (my $i = 0; $i < scalar(@{ $self->{TEMPLATE_ARRAYS}->{$idx} }); $i++) {
+         $self->{TEMPLATE_ARRAYS}->{$idx}->[$i] = $self->parse_amount(\%::myconfig, $self->{TEMPLATE_ARRAYS}->{$idx}->[$i]);
+       }
+     }
+     next unless defined $self->{$idx};
+     if (!ref($self->{$idx})) {
+       $self->{$idx} = $self->parse_amount(\%::myconfig, $self->{$idx});
+     } elsif (ref($self->{$idx}) eq "ARRAY") {
+       for (my $i = 0; $i < scalar(@{ $self->{$idx} }); $i++) {
+         $self->{$idx}->[$i] = $self->parse_amount(\%::myconfig, $self->{$idx}->[$i]);
+       }
+     }
+   }
+   my $saved_numberformat    = $::myconfig{numberformat};
+   $::myconfig{numberformat} = $numberformat;
+   foreach my $idx (@indices) {
+     if ($self->{TEMPLATE_ARRAYS} && (ref($self->{TEMPLATE_ARRAYS}->{$idx}) eq "ARRAY")) {
+       for (my $i = 0; $i < scalar(@{ $self->{TEMPLATE_ARRAYS}->{$idx} }); $i++) {
+         $self->{TEMPLATE_ARRAYS}->{$idx}->[$i] = $self->format_amount(\%::myconfig, $self->{TEMPLATE_ARRAYS}->{$idx}->[$i], $places);
+       }
+     }
+     next unless defined $self->{$idx};
+     if (!ref($self->{$idx})) {
+       $self->{$idx} = $self->format_amount(\%::myconfig, $self->{$idx}, $places);
+     } elsif (ref($self->{$idx}) eq "ARRAY") {
+       for (my $i = 0; $i < scalar(@{ $self->{$idx} }); $i++) {
+         $self->{$idx}->[$i] = $self->format_amount(\%::myconfig, $self->{$idx}->[$i], $places);
+       }
+     }
+   }
+   $::myconfig{numberformat} = $saved_numberformat;
+ }
  1;
  
  __END__
diff --combined bin/mozilla/admin.pl
@@@ -42,6 -42,7 +42,7 @@@ use POSIX qw(strftime)
  use Sys::Hostname;
  
  use SL::Auth;
+ use SL::Auth::PasswordPolicy;
  use SL::Form;
  use SL::Iconv;
  use SL::Mailer;
@@@ -143,10 -144,11 +144,11 @@@ sub check_auth_db_and_tables 
      ::end_of_request();
    }
  
-   if (-f $main::memberfile) {
+   my $memberfile = $::lx_office_conf{paths}->{memberfile};
+   if (-f $memberfile) {
      my $memberdir = "";
  
-     if ($main::memberfile =~ m|^.*/|) {
+     if ($memberfile =~ m|^.*/|) {
        $memberdir = $&;
      }
  
  
      $form->{title} = $locale->text('User data migration');
      $form->header();
-     print $form->parse_html_template('admin/user_migration', { 'memberfile' => $main::memberfile,
+     print $form->parse_html_template('admin/user_migration', { 'memberfile' => $memberfile,
                                                                 'backupdir'  => $backupdir });
  
      ::end_of_request();
@@@ -178,7 -180,8 +180,8 @@@ sub create_auth_tables 
    $main::auth->set_session_value('rpw', $form->{rpw});
    $main::auth->create_or_refresh_session();
  
-   if (!-f $main::memberfile) {
+   my $memberfile = $::lx_office_conf{paths}->{memberfile};
+   if (!-f $memberfile) {
      # New installation -- create a standard group with full access
      my %members;
      my $group = {
      $main::auth->save_group($group);
    }
  
 +  _apply_dbupgrade_scripts();
    login();
  }
  
@@@ -203,7 -205,8 +206,8 @@@ sub migrate_users 
  
    my $memberdir = "";
  
-   if ($main::memberfile =~ m|^.*/|) {
+   my $memberfile = $::lx_office_conf{paths}->{memberfile};
+   if ($memberfile =~ m|^.*/|) {
      $memberdir = $&;
    }
  
      $form->error(sprintf($locale->text('The directory "%s" could not be created:\n%s'), $backupdir, $!));
    }
  
-   copy $main::memberfile, "users/member-file-migration/members";
+   copy $memberfile, "users/member-file-migration/members";
  
-   my $in = IO::File->new($main::memberfile, "r");
+   my $in = IO::File->new($memberfile, "r");
  
    $form->error($locale->text('Could not open the old memberfile.')) if (!$in);
  
      }
    }
  
-   unlink $main::memberfile;
+   unlink $memberfile;
  
    my @member_list = sort { lc $a->{login} cmp lc $b->{login} } values %members;
  
@@@ -341,7 -344,7 +345,7 @@@ sub list_users 
    map { $_->{templates} =~ s|.*/||; } values %members;
  
    $form->{title}   = "Lx-Office ERP " . $locale->text('Administration');
-   $form->{LOCKED}  = -e "$main::userspath/nologin";
+   $form->{LOCKED}  = -e _nologin_file_name();
    $form->{MEMBERS} = [ @members{sort { lc $a cmp lc $b } keys %members} ];
  
    $form->header();
@@@ -411,14 -414,14 +415,14 @@@ sub edit_user_form 
    }
  
    # is there a templates basedir
-   if (!-d "$main::templates") {
-     $form->error(sprintf($locale->text("The directory %s does not exist."), $main::templates));
+   if (!-d $::lx_office_conf{paths}->{templates}) {
+     $form->error(sprintf($locale->text("The directory %s does not exist."), $::lx_office_conf{paths}->{templates}));
    }
  
-   opendir TEMPLATEDIR, "$main::templates/." or $form->error("$main::templates : $ERRNO");
+   opendir TEMPLATEDIR, $::lx_office_conf{paths}->{templates} or $form->error($::lx_office_conf{paths}->{templates} . " : $ERRNO");
    my @all     = readdir(TEMPLATEDIR);
-   my @alldir  = sort grep { -d "$main::templates/$_" && !/^\.\.?$/ } @all;
-   my @allhtml = sort grep { -f "$main::templates/$_" && /\.html$/ } @all;
+   my @alldir  = sort grep { -d ($::lx_office_conf{paths}->{templates} . "/$_") && !/^\.\.?$/ } @all;
+   my @allhtml = sort grep { -f ($::lx_office_conf{paths}->{templates} . "/$_") &&  /\.html$/ } @all;
    closedir TEMPLATEDIR;
  
    @alldir = grep !/\.(html|tex|sty|odt|xml|txb)$/, @alldir;
@@@ -497,13 -500,13 +501,13 @@@ sub save_user 
    }
  
    # is there a basedir
-   if (!-d "$main::templates") {
-     $form->error(sprintf($locale->text("The directory %s does not exist."), $main::templates));
+   if (!-d $::lx_office_conf{paths}->{templates}) {
+     $form->error(sprintf($locale->text("The directory %s does not exist."), $::lx_office_conf{paths}->{templates}));
    }
  
    # add base directory to $form->{templates}
    $form->{templates} =~ s|.*/||;
-   $form->{templates} =  "$main::templates/$form->{templates}";
+   $form->{templates} =  $::lx_office_conf{paths}->{templates} . "/$form->{templates}";
  
    my $myconfig = new User($form->{login});
  
  
    $myconfig->save_member();
  
-   if ($main::auth->can_change_password()
-       && defined $form->{new_password}
-       && ($form->{new_password} ne '********')) {
-     $main::auth->change_password($form->{login}, $form->{new_password});
-   }
    $form->{templates}       =~ s|.*/||;
-   $form->{templates}       =  "$main::templates/$form->{templates}";
+   $form->{templates}       =  $::lx_office_conf{paths}->{templates} . "/$form->{templates}";
    $form->{mastertemplates} =~ s|.*/||;
  
    # create user template directory and copy master files
        umask(007);
  
        # copy templates to the directory
-       opendir TEMPLATEDIR, "$main::templates/." or $form->error("$main::templates : $ERRNO");
+       opendir TEMPLATEDIR, $::lx_office_conf{paths}->{templates} or $form->error($::lx_office_conf{paths}->{templates} . " : $ERRNO");
        my @templates = grep /$form->{mastertemplates}.*?\.(html|tex|sty|odt|xml|txb)$/,
          readdir TEMPLATEDIR;
        closedir TEMPLATEDIR;
  
        foreach my $file (@templates) {
-         open(TEMP, "$main::templates/$file")
-           or $form->error("$main::templates/$file : $ERRNO");
+         open(TEMP, $::lx_office_conf{paths}->{templates} . "/$file")
+           or $form->error($::lx_office_conf{paths}->{templates} . "/$file : $ERRNO");
  
          $file =~ s/\Q$form->{mastertemplates}\E-//;
          open(NEW, ">$form->{templates}/$file")
      }
    }
  
-   $form->redirect($locale->text('User saved!'));
+   if ($main::auth->can_change_password()
+       && defined $form->{new_password}
+       && ($form->{new_password} ne '********')) {
+     my $verifier = SL::Auth::PasswordPolicy->new;
+     my $result   = $verifier->verify($form->{new_password}, 1);
+     if ($result != SL::Auth::PasswordPolicy->OK()) {
+       $form->error($::locale->text('The settings were saved, but the password was not changed.') . ' ' . join(' ', $verifier->errors($result)));
+     }
  
+     $main::auth->change_password($form->{login}, $form->{new_password});
+   }
+   $form->redirect($locale->text('User saved!'));
  }
  
  sub save_user_as_new {
@@@ -758,12 -767,12 +768,12 @@@ sub create_dataset 
    }
    closedir SQLDIR;
  
-   my $default_charset = $main::dbcharset;
+   my $default_charset = $::lx_office_conf{system}->{dbcharset};
    $default_charset ||= Common::DEFAULT_CHARSET;
  
    my $cluster_encoding = User->dbclusterencoding($form);
    if ($cluster_encoding && ($cluster_encoding =~ m/^(?:UTF-?8|UNICODE)$/i)) {
-     if ($main::dbcharset !~ m/^UTF-?8$/i) {
+     if ($::lx_office_conf{system}->{dbcharset} !~ m/^UTF-?8$/i) {
        $form->show_generic_error($locale->text('The selected  PostgreSQL installation uses UTF-8 as its encoding. ' .
                                                'Therefore you have to configure Lx-Office to use UTF-8 as well.'),
                                  'back_button' => 1);
@@@ -836,8 -845,8 +846,8 @@@ sub backup_dataset 
  
    $form->{title} = "Lx-Office ERP " . $locale->text('Database Administration') . " / " . $locale->text('Backup Dataset');
  
-   if ("$main::pg_dump_exe" eq "DISABLED") {
-     $form->error($locale->text('Database backups and restorations are disabled in lx-erp.conf.'));
+   if ($::lx_office_conf{applications}->{pg_dump} eq "DISABLED") {
+     $form->error($locale->text('Database backups and restorations are disabled in the configuration.'));
    }
  
    my @dbsources         = sort User->dbsources($form);
@@@ -858,10 -867,10 +868,10 @@@ sub backup_dataset_start 
  
    $form->{title} = "Lx-Office ERP " . $locale->text('Database Administration') . " / " . $locale->text('Backup Dataset');
  
-   $main::pg_dump_exe ||= "pg_dump";
+   my $pg_dump_exe = $::lx_office_conf{applications}->{pg_dump} || "pg_dump";
  
-   if ("$main::pg_dump_exe" eq "DISABLED") {
-     $form->error($locale->text('Database backups and restorations are disabled in lx-erp.conf.'));
+   if ("$pg_dump_exe" eq "DISABLED") {
+     $form->error($locale->text('Database backups and restorations are disabled in the configuration.'));
    }
  
    $form->isblank("dbname", $locale->text('The dataset name is missing.'));
    push @args, ("-p", $form->{dbport}) if ($form->{dbport});
    push @args, $form->{dbname};
  
-   my $cmd  = "$main::pg_dump_exe " . join(" ", map { s/\\/\\\\/g; s/\"/\\\"/g; $_ } @args);
+   my $cmd  = "$pg_dump_exe " . join(" ", map { s/\\/\\\\/g; s/\"/\\\"/g; $_ } @args);
    my $name = "dataset_backup_$form->{dbname}_" . strftime("%Y%m%d", localtime()) . ".tar";
  
    if ($form->{destination} ne "email") {
  
      map { $mail->{$_} = $form->{$_} } qw(from to cc subject message);
  
-     $mail->{charset}     = $main::dbcharset ? $main::dbcharset : Common::DEFAULT_CHARSET;
+     $mail->{charset}     = $::lx_office_conf{system}->{dbcharset} || Common::DEFAULT_CHARSET;
      $mail->{attachments} = [ { "filename" => $tmp, "name" => $name } ];
      $mail->send();
  
@@@ -945,11 -954,11 +955,11 @@@ sub restore_dataset 
  
    $form->{title} = "Lx-Office ERP " . $locale->text('Database Administration') . " / " . $locale->text('Restore Dataset');
  
-   if ("$main::pg_restore_exe" eq "DISABLED") {
-     $form->error($locale->text('Database backups and restorations are disabled in lx-erp.conf.'));
+   if ($::lx_office_conf{applications}->{pg_restore} eq "DISABLED") {
+     $form->error($locale->text('Database backups and restorations are disabled in the configuration.'));
    }
  
-   my $default_charset   = $main::dbcharset;
+   my $default_charset   = $::lx_office_conf{system}->{dbcharset};
    $default_charset    ||= Common::DEFAULT_CHARSET;
  
    $form->{DBENCODINGS}  = [];
@@@ -970,10 -979,10 +980,10 @@@ sub restore_dataset_start 
  
    $form->{title} = "Lx-Office ERP " . $locale->text('Database Administration') . " / " . $locale->text('Restore Dataset');
  
-   $main::pg_restore_exe ||= "pg_restore";
+   my $pg_restore_exe = $::lx_office_conf{applications}->{pg_restore} || "pg_restore";
  
-   if ("$main::pg_restore_exe" eq "DISABLED") {
-     $form->error($locale->text('Database backups and restorations are disabled in lx-erp.conf.'));
+   if ("$pg_restore_exe" eq "DISABLED") {
+     $form->error($locale->text('Database backups and restorations are disabled in the configuration.'));
    }
  
    $form->isblank("new_dbname", $locale->text('The dataset name is missing.'));
    push @args, ("-p", $form->{dbport}) if ($form->{dbport});
    push @args, $tmp;
  
-   my $cmd = "$main::pg_restore_exe " . join(" ", map { s/\\/\\\\/g; s/\"/\\\"/g; $_ } @args);
+   my $cmd = "$pg_restore_exe " . join(" ", map { s/\\/\\\\/g; s/\"/\\\"/g; $_ } @args);
  
    my $in = IO::File->new("$cmd 2>&1 |");
  
@@@ -1091,7 -1100,7 +1101,7 @@@ sub unlock_system 
    my $form   = $main::form;
    my $locale = $main::locale;
  
-   unlink "$main::userspath/nologin";
+   unlink _nologin_file_name();;
  
    $form->{callback} = "admin.pl?action=list_users";
  
@@@ -1103,7 -1112,7 +1113,7 @@@ sub lock_system 
    my $form   = $main::form;
    my $locale = $main::locale;
  
-   open(FH, ">$main::userspath/nologin")
+   open(FH, ">" . _nologin_file_name())
      or $form->error($locale->text('Cannot create Lock!'));
    close(FH);
  
@@@ -1174,4 -1183,8 +1184,8 @@@ sub _apply_dbupgrade_scripts 
    ::end_of_request() if SL::DBUpgrade2->new(form => $::form, dbdriver => 'Pg', auth => 1)->apply_admin_dbupgrade_scripts(1);
  }
  
+ sub _nologin_file_name {
+   return $::lx_office_conf{paths}->{userspath} . '/nologin';
+ }
  1;
diff --combined bin/mozilla/oe.pl
@@@ -41,6 -41,7 +41,7 @@@ use SL::IS
  use SL::MoreCommon qw(ary_diff);
  use SL::PE;
  use SL::ReportGenerator;
+ use List::MoreUtils qw(any none);
  use List::Util qw(max reduce sum);
  use Data::Dumper;
  
@@@ -221,7 -222,7 +222,7 @@@ sub order_links 
    $form->all_vc(\%myconfig, $form->{vc}, ($form->{vc} eq 'customer') ? "AR" : "AP");
  
    # retrieve order/quotation
-   $form->{webdav}   = $main::webdav;
+   $form->{webdav}   = $::lx_office_conf{system}->{webdav};
    $form->{jsscript} = 1;
  
    my $editing = $form->{id};
@@@ -396,6 -397,16 +397,16 @@@ sub form_header 
    $onload .= qq|;setupPoints('|.   $myconfig{numberformat} .qq|', '|. $locale->text("wrongformat") .qq|')|;
    $TMPL_VAR{onload} = $onload;
  
+   if ($form->{type} eq 'sales_order') {
+     if (!$form->{periodic_invoices_config}) {
+       $form->{periodic_invoices_status} = $locale->text('not configured');
+     } else {
+       my $config                        = YAML::Load($form->{periodic_invoices_config});
+       $form->{periodic_invoices_status} = $config->{active} ? $locale->text('active') : $locale->text('inactive');
+     }
+   }
    $form->{javascript} .= qq|<script type="text/javascript" src="js/show_form_details.js"></script>|;
    $form->{javascript} .= qq|<script type="text/javascript" src="js/show_history.js"></script>|;
    $form->{javascript} .= qq|<script type="text/javascript" src="js/show_vc_details.js"></script>|;
@@@ -487,7 -498,7 +498,7 @@@ sub form_footer 
  
    print $form->parse_html_template("oe/form_footer", {
       %TMPL_VAR,
-      webdav          => $main::webdav,
+      webdav          => $::lx_office_conf{system}->{webdav},
       print_options   => print_options(inline => 1),
       label_edit      => $locale->text("Edit the $form->{type}"),
       label_workflow  => $locale->text("Workflow $form->{type}"),
@@@ -747,7 -758,8 +758,8 @@@ sub orders 
      "salesman",
      "shipvia",                 "globalprojectnumber",
      "transaction_description", "open",
-     "delivered", "marge_total", "marge_percent",
+     "delivered",               "periodic_invoices",
+     "marge_total",             "marge_percent",
      "vcnumber",                "ustid",
      "country",
    );
      unshift @columns, "ids";
    }
  
-   $form->{l_open}      = $form->{l_closed} = "Y" if ($form->{open}      && $form->{closed});
-   $form->{l_delivered} = "Y"                     if ($form->{delivered} && $form->{notdelivered});
+   $form->{l_open}              = $form->{l_closed} = "Y" if ($form->{open}      && $form->{closed});
+   $form->{l_delivered}         = "Y"                     if ($form->{delivered} && $form->{notdelivered});
+   $form->{l_periodic_invoices} = "Y"                     if ($form->{periodic_invoices_active} && $form->{periodic_invoices_inactive});
  
    my $attachment_basename;
    if ($form->{vc} eq 'vendor') {
    my @hidden_variables = map { "l_${_}" } @columns;
    push @hidden_variables, "l_subtotal", $form->{vc}, qw(l_closed l_notdelivered open closed delivered notdelivered ordnumber quonumber
                                                          transaction_description transdatefrom transdateto type vc employee_id salesman_id
-                                                         reqdatefrom reqdateto projectnumber project_id);
+                                                         reqdatefrom reqdateto projectnumber project_id periodic_invoices_active periodic_invoices_inactive);
  
    my $href = build_std_url('action=orders', grep { $form->{$_} } @hidden_variables);
  
      'vcnumber'                => { 'text' => $form->{vc} eq 'customer' ? $locale->text('Customer Number') : $locale->text('Vendor Number'), },
      'country'                 => { 'text' => $locale->text('Country'), },
      'ustid'                   => { 'text' => $locale->text('USt-IdNr.'), },
+     'periodic_invoices'       => { 'text' => $locale->text('Per. Inv.'), },
    );
  
    foreach my $name (qw(id transdate reqdate quonumber ordnumber name employee salesman shipvia transaction_description)) {
    push @options, $locale->text('Closed')                                                                  if $form->{closed};
    push @options, $locale->text('Delivered')                                                               if $form->{delivered};
    push @options, $locale->text('Not delivered')                                                           if $form->{notdelivered};
+   push @options, $locale->text('Periodic invoices active')                                                if $form->{periodic_invoices_actibe};
  
    $report->set_options('top_info_text'        => join("\n", @options),
                         'raw_top_info_text'    => $form->parse_html_template('oe/orders_top'),
    foreach my $oe (@{ $form->{OE} }) {
      map { $oe->{$_} *= $oe->{exchangerate} } @subtotal_columns;
  
-     $oe->{tax}       = $oe->{amount} - $oe->{netamount};
-     $oe->{open}      = $oe->{closed}    ? $locale->text('No')  : $locale->text('Yes');
-     $oe->{delivered} = $oe->{delivered} ? $locale->text('Yes') : $locale->text('No');
+     $oe->{tax}               = $oe->{amount} - $oe->{netamount};
+     $oe->{open}              = $oe->{closed}            ? $locale->text('No')  : $locale->text('Yes');
+     $oe->{delivered}         = $oe->{delivered}         ? $locale->text('Yes') : $locale->text('No');
+     $oe->{periodic_invoices} = $oe->{periodic_invoices} ? $locale->text('On')  : $locale->text('Off');
  
      map { $subtotals{$_} += $oe->{$_};
            $totals{$_}    += $oe->{$_} } @subtotal_columns;
@@@ -1255,7 -1271,7 +1271,7 @@@ sub delete_order_quotation 
      $msg = $locale->text('Quotation deleted!');
      $err = $locale->text('Cannot delete quotation!');
    }
-   if (OE->delete(\%myconfig, \%$form, $main::spool)){
+   if (OE->delete(\%myconfig, \%$form)){
      # saving the history
      if(!exists $form->{addition}) {
        $form->{snumbers} = qq|ordnumber_| . $form->{ordnumber};
@@@ -1908,7 -1924,7 +1924,7 @@@ sub display_form 
  
    $form->language_payment(\%myconfig);
  
-   Common::webdav_folder($form) if ($main::webdav);
+   Common::webdav_folder($form);
  
    &form_header;
  
@@@ -1940,15 -1956,80 +1956,78 @@@ sub report_for_todo_list 
    return $content;
  }
  
+ sub edit_periodic_invoices_config {
+   $::lxdebug->enter_sub();
+   $::form->{type} = 'sales_order';
+   check_oe_access();
+   my $config;
+   $config = YAML::Load($::form->{periodic_invoices_config}) if $::form->{periodic_invoices_config};
+   if ('HASH' ne ref $config) {
+     $config =  { periodicity             => 'y',
+                  start_date_as_date      => $::form->{transdate},
+                  extend_automatically_by => 12,
+                  active                  => 1,
+                };
+   }
+   $config->{periodicity} = 'm' if none { $_ eq $config->{periodicity} } qw(m q y);
+   $::form->get_lists(printers => "ALL_PRINTERS",
+                      charts   => { key       => 'ALL_CHARTS',
+                                    transdate => 'current_date' });
+   $::form->{AR}    = [ grep { $_->{link} =~ m/(?:^|:)AR(?::|$)/ } @{ $::form->{ALL_CHARTS} } ];
+   $::form->{title} = $::locale->text('Edit the configuration for periodic invoices');
+   $::form->header();
+   print $::form->parse_html_template('oe/edit_periodic_invoices_config', $config);
+   $::lxdebug->leave_sub();
+ }
+ sub save_periodic_invoices_config {
+   $::lxdebug->enter_sub();
+   $::form->{type} = 'sales_order';
+   check_oe_access();
+   $::form->isblank('start_date_as_date', $::locale->text('The start date is missing.'));
+   my $config = { active                  => $::form->{active}     ? 1 : 0,
+                  terminated              => $::form->{terminated} ? 1 : 0,
+                  periodicity             => (any { $_ eq $::form->{periodicity} } qw(m q y)) ? $::form->{periodicity} : 'm',
+                  start_date_as_date      => $::form->{start_date_as_date},
+                  end_date_as_date        => $::form->{end_date_as_date},
+                  print                   => $::form->{print} ? 1 : 0,
+                  printer_id              => $::form->{print} ? $::form->{printer_id} * 1 : undef,
+                  copies                  => $::form->{copies} * 1 ? $::form->{copies} : 1,
+                  extend_automatically_by => $::form->{extend_automatically_by} * 1 || undef,
+                  ar_chart_id             => $::form->{ar_chart_id} * 1,
+                };
+   $::form->{periodic_invoices_config} = YAML::Dump($config);
+   $::form->{title} = $::locale->text('Edit the configuration for periodic invoices');
+   $::form->header;
+   print $::form->parse_html_template('oe/save_periodic_invoices_config', $config);
+   $::lxdebug->leave_sub();
+ }
  sub dispatcher {
 -  my $form     = $main::form;
 -  my $locale   = $main::locale;
 -
    foreach my $action (qw(delete delivery_order e_mail invoice print purchase_order purchase_order quotation
                           request_for_quotation sales_order sales_order save save_and_close save_as_new ship_to update)) {
 -    if ($form->{"action_${action}"}) {
 +    if ($::form->{"action_${action}"}) {
 +      $::form->{dispatched_action} = $action;
        call_sub($action);
        return;
      }
    }
  
 -  $form->error($locale->text('No action defined.'));
 +  $::form->error($::locale->text('No action defined.'));
  }
diff --combined doc/INSTALL.texi
@@@ -114,10 -114,6 +114,10 @@@ Zur Installation das Paket in das entpa
  
  @code{tar xzf lx-erp-perl-libs-compat-v2.tar.gz /path/to/lx-office/}
  
 +Zusätzlich müssen dann noch die folgenden Pakete installiert weerden
 +
 +@code{libbit-vector-perl libsub-exporter-perl libclone-perl libclass-factory-util-perl}
 +
  Danach sollte der Installationscheck (@pxref{Pakete}) die enthaltenen Pakete erkennen.
  
  @node Pakete
@@@ -624,9 -620,10 +624,10 @@@ ist dies @samp{lxoffice})
  Wenn Sie für die Lx-Office-Installation nicht den europäischen
  Schriftsatz ISO-8859-15 sondern UTF-8 (Unicode) benutzen wollen, so
  müssen Sie vor dem Anlegen der Datenbank in der Datei
- @code{config/lx-erp.conf} die Variable @code{$dbcharset} auf den Wert
- @samp{UTF-8} setzen. Zusätzlich muss beim Anlegen der Datenbank
- @samp{UTF-8 Unicode} als Schriftsatz ausgewählt werden.
+ @code{config/lx_office.conf} die Variable @code{dbcharset} im
+ Abschnitt @code{system} auf den Wert @samp{UTF-8} setzen. Zusätzlich
+ muss beim Anlegen der Datenbank @samp{UTF-8 Unicode} als Schriftsatz
+ ausgewählt werden.
  
  Bitte beachten Sie, dass alle Datenbanken den selben Zeichensatz
  verwenden müssen, da diese Einstellungen momentan global in Lx-Office
@@@ -711,13 -708,14 +712,14 @@@ OpenDocument-Format, wie es OpenOffice.
  erzeugt. Lx-Office kann dabei sowohl neue OpenDocument-Dokumente als
  auch aus diesen direkt PDF-Dateien erzeugen.  Um die Unterstützung von
  OpenDocument-Vorlagen zu aktivieren muss in der Datei
- @code{config/lx-erp.conf} die Variable @code{$opendocument_templates}
- auf @samp{1} stehen.  Dieses ist die Standardeinstellung.
+ @code{config/lx_office.conf} die Variable @code{opendocument} im
+ Abschnitt @code{print_templates} auf @samp{1} stehen.  Dieses ist die
+ Standardeinstellung.
  
- Weiterhin muss in der Datei @code{config/lx-erp.conf} die Variable
- @code{$dbcharset} auf die Zeichenkodierung gesetzt werden, die auch
- bei der Speicherung der Daten in der Datenbank verwendet wird. Diese
- ist in den meisten Fällen "UTF-8".
+ Weiterhin muss in der Datei @code{config/lx_office.conf} die Variable
+ @code{dbcharset} im Abschnitt @code{system} auf die Zeichenkodierung
+ gesetzt werden, die auch bei der Speicherung der Daten in der
Datenbank verwendet wird. Diese ist in den meisten Fällen "UTF-8".
  
  Während die Erzeugung von reinen OpenDocument-Dateien keinerlei
  weitere Software benötigt, wird zur Umwandlung dieser Dateien in PDF
@@@ -726,11 -724,11 +728,11 @@@ neben OpenOffice.org ab Version 2 auch 
  (xvfb) installiert werden.  Bei Debian ist er im Paket ``xvfb''
  enthalten. Andere Distributionen enthalten ihn in anderen Paketen.
  
- Nach der Installation müssen in der Datei @code{config/lx-erp.conf}
- zwei weitere Variablen angepasst werden:
- @code{$openofficeorg_writer_bin} muss den vollständigen Pfad zur
- OpenOffice.org Writer-Anwendung enthalten.  @code{$xvfb_bin} muss den
Pfad zum ``X virtual frame buffer'' enthalten.
+ Nach der Installation müssen in der Datei @code{config/lx_config.conf}
+ zwei weitere Variablen angepasst werden: @code{openofficeorg_writer}
+ muss den vollständigen Pfad zur OpenOffice.org Writer-Anwendung
+ enthalten. @code{xvfb} muss den Pfad zum ``X virtual frame buffer''
enthalten. Beide stehen im Abschnitt @code{applications}.
  
  Zusätzlich gibt es zwei verschiedene Arten, wie Lx-Office mit
  OpenOffice kommuniziert. Die erste Variante, die benutzt wird, wenn
diff --combined doc/INSTALL.txt
@@@ -103,11 -103,6 +103,11 @@@ entpacken
  
     `tar xzf lx-erp-perl-libs-compat-v2.tar.gz /path/to/lx-office/'
  
 +   Zusätzlich müssen dann noch die folgenden Pakete installiert weerden
 +
 +   `libbit-vector-perl libsub-exporter-perl libclone-perl
 +libclass-factory-util-perl'
 +
     Danach sollte der Installationscheck (*note Pakete::) die
  enthaltenen Pakete erkennen.
  
@@@ -577,9 -572,9 +577,9 @@@ ist dies `lxoffice')
     Wenn Sie für die Lx-Office-Installation nicht den europäischen
  Schriftsatz ISO-8859-15 sondern UTF-8 (Unicode) benutzen wollen, so
  müssen Sie vor dem Anlegen der Datenbank in der Datei
- `config/lx-erp.conf' die Variable `$dbcharset' auf den Wert `UTF-8'
- setzen. Zusätzlich muss beim Anlegen der Datenbank `UTF-8 Unicode' als
- Schriftsatz ausgewählt werden.
+ `config/lx_office.conf' die Variable `dbcharset' im Abschnitt `system'
+ auf den Wert `UTF-8' setzen. Zusätzlich muss beim Anlegen der Datenbank
`UTF-8 Unicode' als Schriftsatz ausgewählt werden.
  
     Bitte beachten Sie, dass alle Datenbanken den selben Zeichensatz
  verwenden müssen, da diese Einstellungen momentan global in Lx-Office
@@@ -658,13 -653,13 +658,13 @@@ OpenDocument-Format, wie es OpenOffice.
  Lx-Office kann dabei sowohl neue OpenDocument-Dokumente als auch aus
  diesen direkt PDF-Dateien erzeugen.  Um die Unterstützung von
  OpenDocument-Vorlagen zu aktivieren muss in der Datei
- `config/lx-erp.conf' die Variable `$opendocument_templates' auf `1'
- stehen.  Dieses ist die Standardeinstellung.
+ `config/lx_office.conf' die Variable `opendocument' im Abschnitt
`print_templates' auf `1' stehen.  Dieses ist die Standardeinstellung.
  
-    Weiterhin muss in der Datei `config/lx-erp.conf' die Variable
- `$dbcharset' auf die Zeichenkodierung gesetzt werden, die auch bei der
- Speicherung der Daten in der Datenbank verwendet wird. Diese ist in den
- meisten Fällen "UTF-8".
+    Weiterhin muss in der Datei `config/lx_office.conf' die Variable
+ `dbcharset' im Abschnitt `system' auf die Zeichenkodierung gesetzt
+ werden, die auch bei der Speicherung der Daten in der Datenbank
verwendet wird. Diese ist in den meisten Fällen "UTF-8".
  
     Während die Erzeugung von reinen OpenDocument-Dateien keinerlei
  weitere Software benötigt, wird zur Umwandlung dieser Dateien in PDF
@@@ -673,10 -668,11 +673,11 @@@ neben OpenOffice.org ab Version 2 auch 
  (xvfb) installiert werden.  Bei Debian ist er im Paket "xvfb"
  enthalten. Andere Distributionen enthalten ihn in anderen Paketen.
  
-    Nach der Installation müssen in der Datei `config/lx-erp.conf' zwei
weitere Variablen angepasst werden: `$openofficeorg_writer_bin' muss
+    Nach der Installation müssen in der Datei `config/lx_config.conf'
zwei weitere Variablen angepasst werden: `openofficeorg_writer' muss
  den vollständigen Pfad zur OpenOffice.org Writer-Anwendung enthalten.
- `$xvfb_bin' muss den Pfad zum "X virtual frame buffer" enthalten.
+ `xvfb' muss den Pfad zum "X virtual frame buffer" enthalten. Beide
+ stehen im Abschnitt `applications'.
  
     Zusätzlich gibt es zwei verschiedene Arten, wie Lx-Office mit
  OpenOffice kommuniziert. Die erste Variante, die benutzt wird, wenn die
diff --combined locale/de_DE/all
@@@ -38,9 -38,12 +38,12 @@@ $self->{texts} = 
    '4. Quarter'                  => '4. Quartal',
    '<b>What</b> do you want to look for?' => '<b>Wonach</b> wollen Sie suchen?',
    'A Buchungsgruppe consists of a descriptive name and the account numbers for the income and expense accounts for those four tax zones as well as the inventory account number.' => 'Eine Buchungsgruppe besteht aus einem deskriptiven Namen, den Erl&ouml;s- und Aufwandskonten f&uuml;r diese vier Steuerzonen sowie aus einem Inventarkonto.',
+   'A digit is required.'        => '',
    'A group named &quot;Full Access&quot; has been created.' => 'Eine Gruppe namens &quot;Vollzugriff&quot; wurde angelegt.',
    'A group with that name does already exist.' => 'Eine Gruppe mit diesem Namen gibt es bereits.',
    'A lot of the usability of Lx-Office has been enhanced with javascript. Although it is currently possible to use every aspect of Lx-Office without javascript, we strongly recommend it. In a future version this may change and javascript may be necessary to access advanced features.' => 'Die Bedienung von Lx-Office wurde an vielen Stellen mit Javascript verbessert. Obwohl es derzeit möglich ist, jeden Aspekt von Lx-Office auch ohne Javascript zu benutzen, empfehlen wir es. In einer zukünftigen Version wird Javascript eventuell notwendig sein um weitergehende Features zu benutzen.',
+   'A lower-case character is required.' => '',
+   'A special character is required (valid characters: #1).' => '',
    'A temporary directory could not be created:' => 'Ein tempor&auml;res Verzeichnis konnte nicht erstellt werden:',
    'A temporary file could not be created. Please verify that the directory "#1" is writeable by the webserver.' => 'Eine temporäre Datei konnte nicht angelegt werden. Bitte stellen Sie sicher, dass das Verzeichnis "#1" vom Webserver beschrieben werden darf.',
    'A temporary file could not be created:' => 'Eine tempor&auml;re Datei konnte nicht erstellt werden:',
    'Amount'                      => 'Betrag',
    'Amount Due'                  => 'Betrag fällig',
    'Amount has to be greater then zero! Wrong row number: ' => '"Betrag" muss größer Null sein. Fehlerhafte Zeile: ',
+   'An invalid character was used (invalid characters: #1).' => '',
+   'An invalid character was used (valid characters: #1).' => '',
+   'An upper-case character is required.' => '',
    'Annotations'                 => 'Hilfe',
    'Another user with the login #1 does already exist.' => 'Es existiert bereits ein anderer Benutzer mit diesem Login.',
    'Ap aging on %s'              => 'Offene Verbindlichkeiten zum %s',
    'BOM'                         => 'Stückliste',
    'BWA'                         => 'BWA',
    'Back'                        => 'Zurück',
 +  'Back to the login page'      => 'Zurück zur Loginseite',
    'Backup Dataset'              => 'Datenbank sichern',
    'Backup file'                 => 'Sicherungsdatei',
    'Backup of dataset'           => 'Sicherung der Datenbank',
    'Company Name'                => 'Firmenname',
    'Compare to'                  => 'Gegenüberstellen zu',
    'Configuration of individual TODO items' => 'Konfiguration f&uuml;r die einzelnen Aufgabenlistenpunkte',
+   'Configure'                   => '',
    'Confirm'                     => 'Best&auml;tigen',
    'Confirm!'                    => 'Bestätigen Sie!',
    'Confirmation'                => 'Auftragsbestätigung',
    'Database Host'               => 'Datenbankcomputer',
    'Database User'               => 'Datenbankbenutzer',
    'Database User missing!'      => 'Datenbankbenutzer fehlt!',
-   'Database backups and restorations are disabled in lx-erp.conf.' => 'Datenbanksicherungen und -wiederherstellungen sind in der lx-erp.conf deaktiviert.',
+   'Database backups and restorations are disabled in the configuration.' => 'Datenbanksicherungen und -wiederherstellungen sind in der Konfiguration deaktiviert.',
    'Database name'               => 'Datenbankname',
    'Database template'           => 'Datenbankvorlage',
    'Database update error:'      => 'Fehler beim Datenbankupgrade:',
    'Edit rights'                 => 'Rechte bearbeiten',
    'Edit templates'              => 'Vorlagen bearbeiten',
    'Edit the Delivery Order'     => 'Lieferschein bearbeiten',
+   'Edit the configuration for periodic invoices' => '',
    'Edit the membership of all users in all groups:' => 'Bearbeiten der Mitgliedschaft aller Benutzer in allen Gruppen:',
    'Edit the purchase_order'     => 'Bearbeiten des Lieferantenauftrags',
    'Edit the request_quotation'  => 'Bearbeiten der Preisanfrage',
    'Element disabled'            => 'Element deaktiviert',
    'Employee'                    => 'Bearbeiter',
    'Empty transaction!'          => 'Buchung ist leer!',
+   'End date'                    => '',
    'Enter a description for this new draft.' => 'Geben Sie eine Beschreibung f&uuml;r diesen Entwurf ein.',
    'Enter longdescription'       => 'Langtext eingeben',
    'Enter the requested execution date or leave empty for the quickest possible execution:' => 'Geben Sie das jeweils gewünschte Ausführungsdatum an, oder lassen Sie das Feld leer für die schnellstmögliche Ausführung:',
    'Export date'                 => 'Exportdatum',
    'Export date from'            => 'Exportdatum von',
    'Export date to'              => 'Exportdatum bis',
+   'Extend automatically by n months' => '',
    'Extended'                    => 'Gesamt',
    'Extension Of Time'           => 'Dauerfristverlängerung',
    'Factor'                      => 'Faktor',
    'Help Template Variables'     => 'Hilfe zu Dokumenten-Variablen',
    'Here\'s an example command line:' => 'Hier ist eine Kommandozeile, die als Beispiel dient:',
    'Hide by default'             => 'Standardm&auml;&szlig;ig verstecken',
-   'History'                     => 'Historie',
    'History Search'              => 'Historien Suche',
    'History Search Engine'       => 'Historien Suchmaschine',
    'Homepage'                    => 'Homepage',
    'Payment posted!'             => 'Zahlung gebucht!',
    'Payment terms deleted!'      => 'Zahlungskonditionen gelöscht!',
    'Payments'                    => 'Zahlungsausgänge',
+   'Per. Inv.'                   => '',
    'Period'                      => 'Zeitraum',
    'Period:'                     => 'Zeitraum:',
+   'Periodic Invoices'           => '',
+   'Periodic invoices active'    => '',
+   'Periodic invoices inactive'  => '',
+   'Periodicity'                 => '',
    'Personal settings'           => 'Meine Daten',
    'Pg Database Administration'  => 'Datenbankadministration',
    'Phone'                       => 'Telefon',
    'Pricegroups'                 => 'Preisgruppen',
    'Print'                       => 'Drucken',
    'Print and Post'              => 'Drucken und Buchen',
+   'Print automatically'         => '',
    'Print dunnings'              => 'Mahnungen drucken',
    'Print list'                  => 'Liste ausdrucken',
    'Print options'               => 'Drucken',
    'Spoolfile'                   => 'Druckdatei',
    'Start Dunning Process'       => 'Neue Mahnung',
    'Start analysis'              => 'Analyse beginnen',
+   'Start date'                  => '',
    'Start the correction assistant' => 'Korrekturassistenten starten',
    'Startdate_coa'               => 'Gültig ab',
    'Starting Balance'            => 'Eröffnungsbilanzwerte',
    'Statement Balance'           => 'Sammelrechnungsbilanz',
    'Statement sent to'           => 'Sammelrechnung verschickt an',
    'Statements sent to printer!' => 'Sammelrechnungen an Drucker geschickt!',
+   'Status'                      => '',
    'Step 1 of 3: Parts'          => 'Schritt 1 von 3: Waren',
    'Step 2'                      => 'Schritt 2',
    'Step 2 of 3: Services'       => 'Schritt 2 von 3: Dienstleistungen',
    'The dunning process started' => 'Der Mahnprozess ist gestartet.',
    'The dunnings have been printed.' => 'Die Mahnung(en) wurden gedruckt.',
    'The email address is missing.' => 'Die Emailadresse fehlt.',
+   'The end date is the last day for which invoices will possibly be created.' => '',
    'The factor is missing in row %d.' => 'Der Faktor fehlt in Zeile %d.',
    'The factor is missing.'      => 'Der Faktor fehlt.',
    'The first reason is that Lx-Office contained a bug which resulted in the wrong taxkeys being recorded for transactions in which two entries are posted for the same chart with different taxkeys.' => 'Zum Einen gab es einen Bug in Lx-Office, der dazu führte, dass bei Buchungen mit verschiedenen Steuerschlüssel auf ein Konto teilweise falsche Steuerschlüssel gespeichert wurden.',
    'The group has been saved.'   => 'Die Gruppe wurde gespeichert.',
    'The group memberships have been saved.' => 'Die Gruppenmitgliedschaften wurden gespeichert.',
    'The group name is missing.'  => 'Der Gruppenname fehlt.',
-   'The licensing module has been deactivated in lx-erp.conf.' => 'Das Lizenzverwaltungsmodul wurde in lx-erp.conf deaktiviert.',
+   'The licensing module has been deactivated in the configuration.' => 'Das Lizenzverwaltungsmodul wurde in der Konfiguration deaktiviert.',
    'The list has been printed.'  => 'Die Liste wurde ausgedruckt.',
    'The login is missing.'       => 'Das Login fehlt.',
    'The name in row %d has already been used before.' => 'Der Name in Zeile %d wurde vorher bereits benutzt.',
    'The parts have been removed.' => 'Die Waren wurden aus dem Lager entnommen.',
    'The parts have been stocked.' => 'Die Artikel wurden eingelagert.',
    'The parts have been transferred.' => 'Die Waren wurden umgelagert.',
+   'The password is too long (maximum length: #1).' => '',
+   'The password is too short (minimum length: #1).' => '',
+   'The password is weak (e.g. it can be found in a dictionary).' => '',
    'The payments have been posted.' => 'Die Zahlungen wurden gebucht.',
    'The pg_dump process could not be started.' => 'Der pg_dump-Prozess konnte nicht gestartet werden.',
    'The pg_restore process could not be started.' => 'Der pg_restore-Prozess konnte nicht gestartet werden.',
    'The selected bin does not exist.' => 'Der ausgew&auml;hlte Lagerplatz existiert nicht.',
    'The selected exports have been closed.' => 'Die ausgewählten Exporte wurden abgeschlossen.',
    'The selected warehouse does not exist.' => 'Das ausgew&auml;hlte Lager existiert nicht.',
 -  'The selected warehouse is empty.' => 'Das ausgew&auml;hlte Lager ist leer.',
 +  'The selected warehouse is empty, or no stocked items where found that match the filter settings.' => 'Das ausgewählte Lager ist leer, oder die Suche ergab keine Übereinstimmungen.',
    'The session is invalid or has expired.' => 'Sie sind von Lx-Office abgemeldet.',
+   'The settings were saved, but the password was not changed.' => '',
    'The source warehouse does not contain any bins.' => 'Das Quelllager enth&auml;lt keine Lagerpl&auml;tze.',
+   'The start date is missing.'  => '',
    'The subject is missing.'     => 'Der Betreff fehlt.',
    'The tables for user management and authentication do not exist. They will be created in the next step in the following database:' => 'Die Tabellen zum Speichern der Benutzerdaten und zur Benutzerauthentifizierung wurden nicht gefunden. Sie werden in der folgenden Datenbank angelegt:',
    'The tabulator character'     => 'Das Tabulator-Symbol',
    '[email]'                     => '[email]',
    'account_description'         => 'Beschreibung',
    'accrual'                     => 'Bilanzierung (Soll-Versteuerung)',
+   'active'                      => '',
    'all entries'                 => 'alle Einträge',
    'ap_aging_list'               => 'liste_offene_verbindlichkeiten',
    'ar_aging_list'               => 'liste_offene_forderungen',
    'general_ledger_list'         => 'buchungsjournal',
    'history'                     => 'Historie',
    'history search engine'       => 'Historien Suchmaschine',
+   'inactive'                    => '',
    'invoice'                     => 'Rechnung',
    'invoice_list'                => 'debitorenbuchungsliste',
    'lead deleted!'               => 'Kundenquelle gelöscht',
    'mark as paid'                => 'als bezahlt markieren',
    'missing'                     => 'Fehlbestand',
    'month'                       => 'Monatliche Abgabe',
+   'monthly'                     => '',
    'new Window'                  => 'neues Fenster',
    'no'                          => 'nein',
    'no bestbefore'               => 'keine Mindesthaltbarkeit',
    'no chargenumber'             => 'keine Chargennummer',
    'none (pricegroup)'           => 'keine',
+   'not configured'              => '',
    'not executed'                => 'nicht ausgeführt',
    'not transferred in yet'      => 'noch nicht eingelagert',
    'not transferred out yet'     => 'noch nicht ausgelagert',
    'purchase_order'              => 'Auftrag',
    'purchase_order_list'         => 'lieferantenauftragsliste',
    'quarter'                     => 'Vierteljährliche (quartalsweise) Abgabe',
+   'quarterly'                   => '',
    'quotation_list'              => 'angebotsliste',
    'release_material'            => 'Materialausgabebe',
    'report_generator_dispatch_to is not defined.' => 'report_generator_dispatch_to ist nicht definiert.',
    'tax_taxdescription'          => 'Steuername',
    'tax_taxkey'                  => 'Steuerschlüssel',
    'taxnumber'                   => 'Automatikkonto',
+   'terminated'                  => '',
    'to (date)'                   => 'bis',
    'to (time)'                   => 'bis',
    'transfer'                    => 'Umlagerung',
    'warehouse_journal_list'      => 'lagerbuchungsliste',
    'warehouse_report_list'       => 'lagerbestandsliste',
    'wrongformat'                 => 'Falsches Format',
+   'yearly'                      => '',
    'yes'                         => 'ja',
  };
  
@@@ -12,18 -12,20 +12,21 @@@ BEGIN 
  
  
  use strict;
 +use warnings;
  
  use utf8;
  use English '-no_match_vars';
  
+ use Config::Std;
  use DBI;
  use Data::Dumper;
  use Getopt::Long;
  use Text::Iconv;
  
  use SL::LXDebug;
+ use SL::LxOfficeConf;
  
+ SL::LxOfficeConf->read;
  our $lxdebug = LXDebug->new();
  
  use SL::Auth;
@@@ -42,7 -44,7 +45,7 @@@ my ($opt_list, $opt_tree, $opt_rtree, $
  my ($opt_user, $opt_apply, $opt_applied, $opt_format, $opt_test_utf8);
  my ($opt_dbhost, $opt_dbport, $opt_dbname, $opt_dbuser, $opt_dbpassword);
  
 -our (%myconfig, $form, $user, $auth, $locale, $controls);
 +our (%myconfig, $form, $user, $auth, $locale, $controls, $dbupgrader);
  
  sub show_help {
    my $help_text = <<"END_HELP"
@@@ -109,7 -111,7 +112,7 @@@ sub calc_rev_depends 
  }
  
  sub dump_list {
 -  my @sorted_controls = sort_dbupdate_controls($controls);
 +  my @sorted_controls = $dbupgrader->sort_dbupdate_controls;
  
    print "LIST VIEW\n\n" .
      "number tag depth priority\n";
@@@ -138,7 -140,7 +141,7 @@@ sub dump_tree 
  
    calc_rev_depends();
  
 -  my @sorted_controls = sort_dbupdate_controls($controls);
 +  my @sorted_controls = $dbupgrader->sort_dbupdate_controls;
  
    foreach my $control (@sorted_controls) {
      dump_node($control->{tag}, "") unless (@{ $control->{rev_depends} });
@@@ -162,7 -164,7 +165,7 @@@ sub dump_tree_reverse 
  
    calc_rev_depends();
  
 -  my @sorted_controls = sort_dbupdate_controls($controls);
 +  my @sorted_controls = $dbupgrader->sort_dbupdate_controls;
  
    foreach my $control (@sorted_controls) {
      last if ($control->{depth} > 1);
@@@ -195,8 -197,7 +198,8 @@@ sub dump_graphviz 
    foreach my $c (values %{ $controls }) {
      $ranks{$c->{depth}} ||= [];
  
 -    my ($pre, $post) = ('node [fillcolor=lightgray] ', 'node [fillcolor=white] ') if (!scalar @{ $c->{rev_depends} });
 +    my ($pre, $post) = @{ $c->{rev_depends} } ? ('')x2 :
 +      (map "node [fillcolor=$_] ", qw(lightgray white));
  
      push @{ $ranks{$c->{"depth"}} }, qq|${pre}"$c->{tag}"; ${post}|;
    }
    }
  
    foreach my $c (values %{ $controls }) {
 -    print OUT "$c->{tag};\n";
 +    print OUT qq|"$c->{tag}";\n|;
  
      foreach my $d (@{ $c->{depends} }) {
 -      print OUT "$c->{tag} -> $d;\n";
 +      print OUT qq|"$c->{tag}" -> "$d";\n|;
      }
    }
  
@@@ -274,9 -275,9 +277,9 @@@ sub apply_upgrade 
      print "Applying upgrade $control->{file}\n";
  
      if ($file_type eq "sql") {
 -      $user->process_query($form, $dbh, "sql/$form->{dbdriver}-upgrade2/$control->{file}", $control);
 +      $dbupgrader->process_query($dbh, "sql/$form->{dbdriver}-upgrade2/$control->{file}", $control);
      } else {
 -      $user->process_perl_script($form, $dbh, "sql/$form->{dbdriver}-upgrade2/$control->{file}", $control);
 +      $dbupgrader->process_perl_script($dbh, "sql/$form->{dbdriver}-upgrade2/$control->{file}", $control);
      }
    }
  
@@@ -358,11 -359,8 +361,8 @@@ sub build_upgrade_order 
  #######
  #######
  
- eval { require "config/lx-erp.conf"; };
- eval { require "config/lx-erp-local.conf"; } if (-f "config/lx-erp-local.conf");
- $locale = Locale->new($::language);
--$form = Form->new();
 -$locale = Locale->new("de");
++$locale = Locale->new;
++$form   = Form->new;
  
  #######
  #######
@@@ -388,8 -386,7 +388,8 @@@ GetOptions("list"         => \$opt_list
  
  show_help() if ($opt_help);
  
 -$controls = parse_dbupdate_controls($form, "Pg");
 +$dbupgrader = SL::DBUpgrade2->new(form => $form, dbdriver => 'Pg');
 +$controls   = $dbupgrader->parse_dbupdate_controls->{all_controls};
  
  dump_list()                                 if ($opt_list);
  dump_tree()                                 if ($opt_tree);
@@@ -12,6 -12,9 +12,9 @@@
      <script type="text/javascript" src="js/calculate_qty.js"></script>
      <script type="text/javascript" src="js/customer_or_vendor_selection.js"></script>
      <script type="text/javascript" src="js/follow_up.js"></script>
+     [%- IF is_sales_ord %]
+      [% L.javascript_tag("js/edit_periodic_invoices_config") %]
+     [%- END %]
  
  [%- FOREACH row = HIDDENS %]
     <input type="hidden" name="[% HTML.escape(row.name) %]" value="[% HTML.escape(row.value) %]" >
      <input type="hidden" name="follow_up_trans_type_1" value="[% HTML.escape(type) %]">
      <input type="hidden" name="follow_up_trans_info_1" value="[% HTML.escape(follow_up_trans_info) %]">
      <input type="hidden" name="follow_up_rowcount" value="1">
 +[%- IF resubmit %]
 +[%# in case of resubmits, restore enough information for dispatcher to work %]
 +    [% L.hidden_tag('action_' _ dispatched_action, 1) %]
 +[%- END %]
  
      <div class="listtop">[% title %]</div>