Merge branch 'b-3.6.1' of ../kivitendo-erp_20220811
[kivitendo-erp.git] / bin / mozilla / ir.pl
index 6bcba9b..b0df30c 100644 (file)
@@ -24,7 +24,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.
 #======================================================================
 #
 # Inventory received module
 #======================================================================
 
 use SL::FU;
+use SL::Helper::Flash qw(flash_later);
+use SL::Helper::UserPreferences::DisplayPreferences;
 use SL::IR;
 use SL::IS;
-use SL::PE;
+use SL::DB::BankTransactionAccTrans;
 use SL::DB::Default;
+use SL::DB::Department;
+use SL::DB::Project;
 use SL::DB::PurchaseInvoice;
+use SL::DB::Vendor;
+use List::MoreUtils qw(uniq);
 use List::Util qw(max sum);
 use List::UtilsBy qw(sort_by);
 
 require "bin/mozilla/io.pl";
-require "bin/mozilla/invoice_io.pl";
-require "bin/mozilla/arap.pl";
 require "bin/mozilla/common.pl";
-require "bin/mozilla/drafts.pl";
 
 use strict;
 
@@ -52,6 +56,21 @@ use strict;
 
 # end of main
 
+sub _may_view_or_edit_this_invoice {
+  return 1 if  $::auth->assert('ap_transactions', 1);       # may edit all invoices
+  return 0 if !$::form->{id};                               # creating new invoices isn't allowed without invoice_edit
+  return 1 if  $::auth->assert('purchase_invoice_view', 1); # viewing is allowed with this right
+  return 0 if !$::form->{globalproject_id};                 # existing records without a project ID are not allowed
+  return SL::DB::Project->new(id => $::form->{globalproject_id})->load->may_employee_view_project_invoices(SL::DB::Manager::Employee->current);
+}
+
+sub _assert_access {
+  my $cache = $::request->cache('ap.pl::_assert_access');
+
+  $cache->{_may_view_or_edit_this_invoice} = _may_view_or_edit_this_invoice()                              if !exists $cache->{_may_view_or_edit_this_invoice};
+  $::form->show_generic_error($::locale->text("You do not have the permissions to access this function.")) if !       $cache->{_may_view_or_edit_this_invoice};
+}
+
 sub add {
   $main::lxdebug->enter_sub();
 
@@ -64,7 +83,7 @@ sub add {
     $::form->show_generic_error($::locale->text("You do not have the permissions to access this function."));
   }
 
-  return $main::lxdebug->leave_sub() if (load_draft_maybe());
+  $form->{show_details} = $::myconfig{show_form_details};
 
   $form->{title} = $locale->text('Record Vendor Invoice');
 
@@ -78,10 +97,14 @@ sub add {
 sub edit {
   $main::lxdebug->enter_sub();
 
+  # Delay access check to after the invoice's been loaded in
+  # "create_links" so that project-specific invoice rights can be
+  # evaluated.
+
   my $form     = $main::form;
   my $locale   = $main::locale;
 
-  $main::auth->assert('vendor_invoice_edit');
+  $form->{show_details} = $::myconfig{show_form_details};
 
   # show history button
   $form->{javascript} = qq|<script type=text/javascript src=js/show_history.js></script>|;
@@ -99,28 +122,18 @@ sub edit {
 sub invoice_links {
   $main::lxdebug->enter_sub();
 
+  # Delay access check to after the invoice's been loaded so that
+  # project-specific invoice rights can be evaluated.
+
   my $form     = $main::form;
   my %myconfig = %main::myconfig;
 
-  $main::auth->assert('vendor_invoice_edit');
-
   $form->{vc} = 'vendor';
 
   # create links
-  $form->{webdav}   = $::instance_conf->get_webdav;
-
   $form->create_links("AP", \%myconfig, "vendor");
 
-  #quote all_vendor Bug 133
-  foreach my $ref (@{ $form->{all_vendor} }) {
-    $ref->{name} = $form->quote($ref->{name});
-  }
-
-  if ($form->{all_vendor}) {
-    unless ($form->{vendor_id}) {
-      $form->{vendor_id} = $form->{all_vendor}->[0]->{id};
-    }
-  }
+  _assert_access();
 
   $form->backup_vars(qw(payment_id language_id taxzone_id
                         currency delivery_term_id intnotes cp_id));
@@ -134,25 +147,6 @@ sub invoice_links {
   my @curr = $form->get_all_currencies();
   map { $form->{selectcurrency} .= "<option>$_\n" } @curr;
 
-  $form->{oldvendor} = "$form->{vendor}--$form->{vendor_id}";
-
-  # build vendor/customer drop down comatibility... don't ask
-  if (@{ $form->{"all_vendor"} || [] }) {
-    $form->{"selectvendor"} = 1;
-    $form->{vendor}         = qq|$form->{vendor}--$form->{vendor_id}|;
-  }
-
-  # departments
-  if ($form->{all_departments}) {
-    $form->{selectdepartment} = "<option>\n";
-    $form->{department}       = "$form->{department}--$form->{department_id}";
-
-    map {
-      $form->{selectdepartment} .=
-        "<option>$_->{description}--$_->{id}\n"
-    } (@{ $form->{all_departments} || [] });
-  }
-
   # forex
   $form->{forex} = $form->{exchangerate};
   my $exchangerate = ($form->{exchangerate}) ? $form->{exchangerate} : 1;
@@ -204,11 +198,11 @@ sub invoice_links {
 sub prepare_invoice {
   $main::lxdebug->enter_sub();
 
+  _assert_access();
+
   my $form     = $main::form;
   my %myconfig = %main::myconfig;
 
-  $main::auth->assert('vendor_invoice_edit');
-
   $form->{type}     = "purchase_invoice";
 
   if ($form->{id}) {
@@ -247,37 +241,189 @@ sub prepare_invoice {
   $main::lxdebug->leave_sub();
 }
 
+sub setup_ir_action_bar {
+  my $form                    = $::form;
+  my $change_never            = $::instance_conf->get_ir_changeable == 0;
+  my $change_on_same_day_only = $::instance_conf->get_ir_changeable == 2 && ($form->current_date(\%::myconfig) ne $form->{gldate});
+  my $has_storno              = ($::form->{storno} && !$::form->{storno_id});
+  my $payments_balanced       = ($::form->{oldtotalpaid} == 0);
+  my $may_edit_create         = $::auth->assert('vendor_invoice_edit', 1);
+
+  my $has_sepa_exports;
+  if ($form->{id}) {
+    my $invoice = SL::DB::Manager::PurchaseInvoice->find_by(id => $form->{id});
+    $has_sepa_exports = 1 if ($invoice->find_sepa_export_items()->[0]);
+  }
+
+  my $is_linked_bank_transaction;
+  if ($::form->{id}
+      && SL::DB::Default->get->payments_changeable != 0
+      && SL::DB::Manager::BankTransactionAccTrans->find_by(ap_id => $::form->{id})) {
+
+    $is_linked_bank_transaction = 1;
+  }
+
+  my $create_post_action = sub {
+    # $_[0]: description
+    # $_[1]: after_action
+    action => [
+      $_[0],
+      submit   => [ '#form', { action => "post", after_action => $_[1] } ],
+      checks   => [ 'kivi.validate_form' ],
+      checks   => [ 'kivi.validate_form', 'kivi.AP.check_fields_before_posting', 'kivi.AP.check_duplicate_invnumber' ],
+      disabled => !$may_edit_create                         ? t8('You must not change this invoice.')
+                : $form->{locked}                           ? t8('The billing period has already been locked.')
+                : $form->{storno}                           ? t8('A canceled invoice cannot be posted.')
+                : ($form->{id} && $change_never)            ? t8('Changing invoices has been disabled in the configuration.')
+                : ($form->{id} && $change_on_same_day_only) ? t8('Invoices can only be changed on the day they are posted.')
+                : $is_linked_bank_transaction               ? t8('This transaction is linked with a bank transaction. Please undo and redo the bank transaction booking if needed.')
+                :                                             undef,
+    ],
+  };
+
+  my @post_entries;
+  if ($::instance_conf->get_ir_add_doc && $::instance_conf->get_doc_storage) {
+    @post_entries = ( $create_post_action->(t8('Post'), 'doc-tab') );
+  } elsif ($::instance_conf->get_doc_storage) {
+    @post_entries = ( $create_post_action->(t8('Post')),
+                      $create_post_action->(t8('Post and upload document'), 'doc-tab') );
+  } else {
+    @post_entries = ( $create_post_action->(t8('Post')) );
+  }
+
+  for my $bar ($::request->layout->get('actionbar')) {
+    $bar->add(
+      action => [
+        t8('Update'),
+        submit    => [ '#form', { action => "update" } ],
+        id        => 'update_button',
+        accesskey => 'enter',
+        disabled  => !$may_edit_create ? t8('You must not change this invoice.') : undef,
+      ],
+      combobox => [
+        @post_entries,
+        action => [
+          t8('Post Payment'),
+          submit   => [ '#form', { action => "post_payment" } ],
+          checks   => [ 'kivi.validate_form' ],
+          disabled => !$may_edit_create           ? t8('You must not change this invoice.')
+                    : !$form->{id}                ? t8('This invoice has not been posted yet.')
+                    : $is_linked_bank_transaction ? t8('This transaction is linked with a bank transaction. Please undo and redo the bank transaction booking if needed.')
+                    :                               undef,
+        ],
+        action => [
+          t8('Mark as paid'),
+          submit   => [ '#form', { action => "mark_as_paid" } ],
+          checks   => [ 'kivi.validate_form' ],
+          confirm  => t8('This will remove the invoice from showing as unpaid even if the unpaid amount does not match the amount. Proceed?'),
+          disabled => !$may_edit_create ? t8('You must not change this invoice.')
+                    : !$form->{id}      ? t8('This invoice has not been posted yet.')
+                    :                     undef,
+          only_if  => $::instance_conf->get_ir_show_mark_as_paid,
+        ],
+      ], # end of combobox "Post"
+
+      combobox => [
+        action => [ t8('Storno'),
+          submit   => [ '#form', { action => "storno" } ],
+          checks   => [ 'kivi.validate_form' ],
+          confirm  => t8('Do you really want to cancel this invoice?'),
+          disabled => !$may_edit_create   ? t8('You must not change this invoice.')
+                    : !$form->{id}        ? t8('This invoice has not been posted yet.')
+                    : $has_sepa_exports   ? t8('This invoice has been linked with a sepa export, undo this first.')
+                    : !$payments_balanced ? t8('Cancelling is disallowed. Either undo or balance the current payments until the open amount matches the invoice amount')
+                    : undef,
+        ],
+        action => [ t8('Delete'),
+          submit   => [ '#form', { action => "delete" } ],
+          checks   => [ 'kivi.validate_form' ],
+          confirm  => t8('Do you really want to delete this object?'),
+          disabled => !$may_edit_create           ? t8('You must not change this invoice.')
+                    : !$form->{id}                ? t8('This invoice has not been posted yet.')
+                    : $form->{locked}             ? t8('The billing period has already been locked.')
+                    : $change_never               ? t8('Changing invoices has been disabled in the configuration.')
+                    : $change_on_same_day_only    ? t8('Invoices can only be changed on the day they are posted.')
+                    : $has_sepa_exports           ? t8('This invoice has been linked with a sepa export, undo this first.')
+                    : $has_storno                 ? t8('Can only delete the "Storno zu" part of the cancellation pair.')
+                    : $is_linked_bank_transaction ? t8('This transaction is linked with a bank transaction. Please undo and redo the bank transaction booking if needed.')
+                    :                            undef,
+        ],
+      ], # end of combobox "Storno"
+
+      'separator',
+
+      combobox => [
+        action => [ t8('Workflow') ],
+        action => [
+          t8('Use As New'),
+          submit   => [ '#form', { action => "use_as_new" } ],
+          checks   => [ 'kivi.validate_form' ],
+          disabled => !$may_edit_create ? t8('You must not change this invoice.')
+                    : !$form->{id}      ? t8('This invoice has not been posted yet.')
+                    :                     undef,
+        ],
+       ], # end of combobox "Workflow"
+
+      combobox => [
+        action => [ t8('more') ],
+        action => [
+          t8('History'),
+          call     => [ 'set_history_window', $::form->{id} * 1, 'glid' ],
+          disabled => !$form->{id} ? t8('This invoice has not been posted yet.') : undef,
+        ],
+        action => [
+          t8('Follow-Up'),
+          call     => [ 'follow_up_window' ],
+          disabled => !$form->{id} ? t8('This invoice has not been posted yet.') : undef,
+        ],
+        action => [
+          t8('Drafts'),
+          call     => [ 'kivi.Draft.popup', 'ir', 'invoice', $::form->{draft_id}, $::form->{draft_description} ],
+          disabled => !$may_edit_create ? t8('You must not change this invoice.')
+                    : $form->{id}       ? t8('This invoice has already been posted.')
+                    : $form->{locked}   ? t8('The billing period has already been locked.')
+                    :                     undef,
+        ],
+      ], # end of combobox "more"
+    );
+  }
+  $::request->layout->add_javascripts('kivi.Validator.js', 'kivi.AP.js');
+
+}
+
 sub form_header {
   $main::lxdebug->enter_sub();
 
+  _assert_access();
+
   my $form     = $main::form;
   my %myconfig = %main::myconfig;
   my $locale   = $main::locale;
   my $cgi      = $::request->{cgi};
 
-  $main::auth->assert('vendor_invoice_edit');
-
   my %TMPL_VAR = ();
   my @custom_hiddens;
 
-  $TMPL_VAR{invoice_obj} = SL::DB::PurchaseInvoice->new(id => $form->{id})->load if $form->{id};
-  $form->{employee_id} = $form->{old_employee_id} if $form->{old_employee_id};
-  $form->{salesman_id} = $form->{old_salesman_id} if $form->{old_salesman_id};
+  $TMPL_VAR{invoice_obj} = SL::DB::PurchaseInvoice->load_cached($form->{id}) if $form->{id};
+  $TMPL_VAR{vendor_obj}  = SL::DB::Vendor->load_cached($form->{vendor_id})   if $form->{vendor_id};
+  my $current_employee   = SL::DB::Manager::Employee->current;
+  $form->{employee_id}   = $form->{old_employee_id} if $form->{old_employee_id};
+  $form->{salesman_id}   = $form->{old_salesman_id} if $form->{old_salesman_id};
+  $form->{employee_id} ||= $current_employee->id;
+  $form->{salesman_id} ||= $current_employee->id;
 
   $form->{defaultcurrency} = $form->get_default_currency(\%myconfig);
 
-  my @old_project_ids = ($form->{"globalproject_id"});
-  map { push @old_project_ids, $form->{"project_id_$_"} if $form->{"project_id_$_"}; } 1..$form->{"rowcount"};
+  my @old_project_ids     = uniq grep { $_ } map { $_ * 1 } ($form->{"globalproject_id"}, map { $form->{"project_id_$_"} } 1..$form->{"rowcount"});
+  my @conditions          = @old_project_ids ? (id => \@old_project_ids) : ();
+  $TMPL_VAR{ALL_PROJECTS} = SL::DB::Manager::Project->get_all_sorted(query => [ or => [ active => 1, @conditions ]]);
+  $form->{ALL_PROJECTS}   = $TMPL_VAR{ALL_PROJECTS}; # make projects available for second row drop-down in io.pl
 
-  $form->get_lists("projects"      => { "key"    => "ALL_PROJECTS",
-                                        "all"    => 0,
-                                        "old_id" => \@old_project_ids },
-                   "taxzones"      => "ALL_TAXZONES",
+  $form->get_lists("taxzones"      => ($form->{id} ? "ALL_TAXZONES" : "ALL_ACTIVE_TAXZONES"),
                    "currencies"    => "ALL_CURRENCIES",
-                   "vendors"       => "ALL_VENDORS",
-                   "departments"   => "all_departments",
                    "price_factors" => "ALL_PRICE_FACTORS");
 
+  $TMPL_VAR{ALL_DEPARTMENTS}       = SL::DB::Manager::Department->get_all_sorted;
   $TMPL_VAR{ALL_EMPLOYEES}         = SL::DB::Manager::Employee->get_all_sorted(query => [ or => [ id => $::form->{employee_id},  deleted => 0 ] ]);
   $TMPL_VAR{ALL_CONTACTS}          = SL::DB::Manager::Contact->get_all_sorted(query => [
     or => [
@@ -288,15 +434,6 @@ sub form_header {
       ]
     ]
   ]);
-  $TMPL_VAR{department_labels}     = sub { "$_[0]->{description}--$_[0]->{id}" };
-
-  # customer
-  $TMPL_VAR{vc_keys} = sub { "$_[0]->{name}--$_[0]->{id}" };
-  $TMPL_VAR{vclimit} = $myconfig{vclimit};
-  $TMPL_VAR{vc_select} = "customer_or_vendor_selection_window('vendor', '', 1, 0)";
-  push @custom_hiddens, "vendor_id";
-  push @custom_hiddens, "oldvendor";
-  push @custom_hiddens, "selectvendor";
 
   # currencies and exchangerate
   my @values = map { $_       } @{ $form->{ALL_CURRENCIES} };
@@ -314,36 +451,34 @@ sub form_header {
   $TMPL_VAR{creditwarning} = ($form->{creditlimit} != 0) && ($form->{creditremaining} < 0) && !$form->{update};
   $TMPL_VAR{is_credit_remaining_negativ} = $form->{creditremaining} =~ /-/;
 
-  my $follow_up_vc         =  $form->{vendor};
-  $follow_up_vc            =~ s/--\d*\s*$//;
-  $TMPL_VAR{vendor_name} = $follow_up_vc;
-
 # set option selected
   foreach my $item (qw(AP)) {
     $form->{"select$item"} =~ s/ selected//;
     $form->{"select$item"} =~ s/option>\Q$form->{$item}\E/option selected>$form->{$item}/;
   }
 
-  $TMPL_VAR{is_type_credit_note} = $form->{type}   eq "credit_note";
-  $TMPL_VAR{is_format_html}      = $form->{format} eq 'html';
-  $TMPL_VAR{dateformat}          = $myconfig{dateformat};
-  $TMPL_VAR{numberformat}        = $myconfig{numberformat};
+  $TMPL_VAR{is_format_html}                         = $form->{format} eq 'html';
+  $TMPL_VAR{dateformat}                             = $myconfig{dateformat};
+  $TMPL_VAR{numberformat}                           = $myconfig{numberformat};
+  $TMPL_VAR{longdescription_dialog_size_percentage} = SL::Helper::UserPreferences::DisplayPreferences->new()->get_longdescription_dialog_size_percentage();
 
   # hiddens
   $TMPL_VAR{HIDDENS} = [qw(
-    id action type media format queued printed emailed title vc discount
+    id type queued printed emailed title vc discount
     title creditlimit creditremaining tradediscount business closedto locked shipped storno storno_id
     max_dunning_level dunning_amount
-    shiptoname shiptostreet shiptozipcode shiptocity shiptocountry  shiptocontact shiptophone shiptofax
+    shiptoname shiptostreet shiptozipcode shiptocity shiptocountry shiptogln shiptocontact shiptophone shiptofax
     shiptoemail shiptodepartment_1 shiptodepartment_2 message email subject cc bcc taxaccounts cursor_fokus
-    convert_from_do_ids convert_from_oe_ids show_details gldate useasnew
+    convert_from_do_ids convert_from_oe_ids convert_from_ap_ids show_details gldate useasnew
   ), @custom_hiddens,
-  map { $_.'_rate', $_.'_description', $_.'_taxnumber' } split / /, $form->{taxaccounts}];
+  map { $_.'_rate', $_.'_description', $_.'_taxnumber', $_.'_tax_id' } split / /, $form->{taxaccounts}];
 
   $TMPL_VAR{payment_terms_obj} = get_payment_terms_for_invoice();
-  $form->{duedate}             = $TMPL_VAR{payment_terms_obj}->calc_date(reference_date => $form->{invdate}, due_date => $form->{due_due})->to_kivitendo if $TMPL_VAR{payment_terms_obj};
+  $form->{duedate}             = $TMPL_VAR{payment_terms_obj}->calc_date(reference_date => $form->{invdate}, due_date => $form->{duedate})->to_kivitendo if $TMPL_VAR{payment_terms_obj};
+
+  $::request->{layout}->use_javascript(map { "${_}.js" } qw(kivi.Draft kivi.File kivi.SalesPurchase kivi.Part kivi.CustomerVendor kivi.Validator ckeditor/ckeditor ckeditor/adapters/jquery kivi.io autocomplete_project client_js));
 
-  $::request->{layout}->use_javascript(map { "${_}.js" } qw(kivi.SalesPurchase ckeditor/ckeditor ckeditor/adapters/jquery kivi.io autocomplete_customer autocomplete_part client_js));
+  setup_ir_action_bar();
 
   $form->header();
 
@@ -353,7 +488,7 @@ sub form_header {
 }
 
 sub _sort_payments {
-  my @fields   = qw(acc_trans_id gldate datepaid source memo paid AR_paid);
+  my @fields   = qw(acc_trans_id gldate datepaid source memo paid AP_paid);
   my @payments =
     grep { $_->{paid} != 0 }
     map  {
@@ -374,21 +509,15 @@ sub _sort_payments {
 sub form_footer {
   $main::lxdebug->enter_sub();
 
+  _assert_access();
+
   my $form     = $main::form;
   my %myconfig = %main::myconfig;
   my $locale   = $main::locale;
 
-  $main::auth->assert('vendor_invoice_edit');
-
   $form->{invtotal}    = $form->{invsubtotal};
   $form->{oldinvtotal} = $form->{invtotal};
 
-  # note rows
-  $form->{rows} = max 2,
-    $form->numtextrows($form->{notes},    26, 8),
-    $form->numtextrows($form->{intnotes}, 35, 8);
-
-
   # tax, total and subtotal calculations
   my ($tax, $subtotal);
   $form->{taxaccounts_array} = [ split / /, $form->{taxaccounts} ];
@@ -408,7 +537,7 @@ sub form_footer {
 
   # follow ups
   if ($form->{id}) {
-    $form->{follow_ups}            = FU->follow_ups('trans_id' => $form->{id}) || [];
+    $form->{follow_ups}            = FU->follow_ups('trans_id' => $form->{id}, 'not_done' => 1) || [];
     $form->{follow_ups_unfinished} = ( sum map { $_->{due} * 1 } @{ $form->{follow_ups} } ) || 0;
   }
 
@@ -433,6 +562,14 @@ sub form_footer {
                                   ($form->current_date(\%myconfig) eq $form->{"gldate_$i"}));
     }
 
+    $form->error($locale->text('Cannot post transaction above the maximum future booking date!'))
+      if ($form->date_max_future($form->{"datepaid_$i"}, \%myconfig));
+
+    #deaktivieren von Zahlungen ausserhalb der Bücherkontrolle
+    if ($form->date_closed($form->{"gldate_$i"})) {
+      $form->{"changeable_$i"} = 0;
+    }
+
     $form->{"selectAP_paid_$i"} = $form->{selectAP_paid};
     if (!$form->{"AP_paid_$i"}) {
       $form->{"selectAP_paid_$i"} =~ s/option>$accno_arap--(.*?)>/option selected>$accno_arap--$1>/;
@@ -446,7 +583,6 @@ sub form_footer {
   $form->{ALL_DELIVERY_TERMS} = SL::DB::Manager::DeliveryTerm->get_all_sorted();
 
   print $form->parse_html_template('ir/form_footer', {
-    is_type_credit_note => ($form->{type} eq "credit_note"),
     totalpaid           => $totalpaid,
     paid_missing        => $form->{invtotal} - $totalpaid,
     show_storno         => $form->{id} && !$form->{storno} && !IS->has_storno(\%myconfig, $form, "ap") && !$totalpaid,
@@ -462,16 +598,15 @@ sub form_footer {
 }
 
 sub mark_as_paid {
-  $main::lxdebug->enter_sub();
-
-  my $form     = $main::form;
-  my %myconfig = %main::myconfig;
+  $::auth->assert('vendor_invoice_edit');
 
-  $main::auth->assert('vendor_invoice_edit');
+  SL::DB::PurchaseInvoice->new(id => $::form->{id})->load->mark_as_paid;
 
-  &mark_as_paid_common(\%myconfig,"ap");
+  $::form->redirect($::locale->text("Marked as paid"));
+}
 
-  $main::lxdebug->leave_sub();
+sub show_draft {
+  update();
 }
 
 sub update {
@@ -482,7 +617,9 @@ sub update {
 
   $main::auth->assert('vendor_invoice_edit');
 
-  &check_name('vendor');
+  if (($form->{previous_vendor_id} || $form->{vendor_id}) != $form->{vendor_id}) {
+    IR->get_vendor(\%myconfig, $form);
+  }
 
   if (!$form->{forex}) {        # read exchangerate from input field (not hidden)
     $form->{exchangerate} = $form->parse_amount(\%myconfig, $form->{exchangerate});
@@ -524,7 +661,7 @@ sub update {
       if ($rows > 1) {
 
         select_item(mode => 'IR', pre_entered_qty => $form->{"qty_$i"});
-        ::end_of_request();
+        $::dispatcher->end_request;
 
       } else {
 
@@ -620,7 +757,8 @@ sub storno {
   $form->{paidaccounts} = 0;
   map { my $key = $_; delete $form->{$key} if grep { $key =~ /^$_/ } qw(datepaid_ gldate_ acc_trans_id_ source_ memo_ paid_ exchangerate_ AR_paid_) } keys %{ $form };
   # set new ids for storno invoice
-  delete $form->{"invoice_id_$_"} for 1 .. $form->{"rowcount"};
+  # set new persistent ids for storno invoice items
+  $form->{"converted_from_invoice_id_$_"} = delete $form->{"invoice_id_$_"} for 1 .. $form->{"rowcount"};
 
   # saving the history
   if(!exists $form->{addition} && $form->{id} ne "") {
@@ -631,6 +769,8 @@ sub storno {
   }
   # /saving the history
 
+  # record link invoice to storno
+  $form->{convert_from_ap_ids} = $form->{id};
   $form->{storno_id} = $form->{id};
   $form->{storno} = 1;
   $form->{id} = "";
@@ -680,8 +820,13 @@ sub post_payment {
 
       $form->isblank("datepaid_$i", $locale->text('Payment date missing!'));
 
+      $form->error($locale->text('Cannot post transaction above the maximum future booking date!'))
+        if ($form->date_max_future($form->{"datepaid_$i"}, \%myconfig));
+
+      #Zusätzlich noch das Buchungsdatum in die Bücherkontrolle einbeziehen
+      # (Dient zur Prüfung ob ZE oder ZA geprüft werden soll)
       $form->error($locale->text('Cannot post payment for a closed period!'))
-        if ($form->date_closed($form->{"datepaid_$i"}, \%myconfig));
+        if ($form->date_closed($form->{"datepaid_$i"})  && !$form->date_closed($form->{"gldate_$i"}, \%myconfig));
 
       if ($form->{currency} ne $form->{defaultcurrency}) {
 #        $form->{"exchangerate_$i"} = $form->{exchangerate} if ($invdate == $datepaid); # invdate isn't set here
@@ -736,16 +881,16 @@ sub post {
   $form->{defaultcurrency} = $form->get_default_currency(\%myconfig);
 
   $form->isblank("invdate",   $locale->text('Invoice Date missing!'));
-  $form->isblank("vendor",    $locale->text('Vendor missing!'));
+  $form->isblank("vendor_id", $locale->text('Vendor missing!'));
   $form->isblank("invnumber", $locale->text('Invnumber missing!'));
 
   $form->{invnumber} =~ s/^\s*//g;
   $form->{invnumber} =~ s/\s*$//g;
 
   # if the vendor changed get new values
-  if (&check_name('vendor')) {
+  if (($form->{previous_vendor_id} || $form->{vendor_id}) != $form->{vendor_id}) {
     &update;
-    ::end_of_request();
+    $::dispatcher->end_request;
   }
 
   if ($myconfig{mandatory_departments} && !$form->{department_id}) {
@@ -776,8 +921,13 @@ sub post {
 
       $form->isblank("datepaid_$i", $locale->text('Payment date missing!'));
 
+      $form->error($locale->text('Cannot post transaction above the maximum future booking date!'))
+        if ($form->date_max_future($form->{"datepaid_$i"}, \%myconfig));
+
+      #Zusätzlich noch das Buchungsdatum in die Bücherkontrolle einbeziehen
+      # (Dient zur Prüfung ob ZE oder ZA geprüft werden soll)
       $form->error($locale->text('Cannot post payment for a closed period!'))
-        if ($form->date_closed($form->{"datepaid_$i"}, \%myconfig));
+        if ($form->date_closed($form->{"datepaid_$i"})  && !$form->date_closed($form->{"gldate_$i"}, \%myconfig));
 
       if ($form->{currency} ne $form->{defaultcurrency}) {
         $form->{"exchangerate_$i"} = $form->{exchangerate}
@@ -805,10 +955,21 @@ sub post {
       $form->save_history;
     }
     # /saving the history
-    remove_draft() if $form->{remove_draft};
-    $form->redirect(  $locale->text('Invoice')
-                  . " $form->{invnumber} "
-                  . $locale->text('posted!'));
+
+    my $redirect_url;
+    if ('doc-tab' eq $form->{after_action}) {
+      $redirect_url = build_std_url("script=ir.pl", 'action=edit', 'id=' . E($form->{id}), 'fragment=ui-tabs-docs');
+    } else {
+      $redirect_url = build_std_url("script=ir.pl", 'action=edit', 'id=' . E($form->{id}));
+    }
+    SL::Helper::Flash::flash_later('info',
+                                   $locale->text('Invoice')
+                                   . " $form->{invnumber} "
+                                   . ", " . $locale->text('ID')
+                                   . ': ' . $form->{id} . ' '
+                                   . $locale->text('posted!'));
+    print $form->redirect_header($redirect_url);
+    $::dispatcher->end_request;
   }
   $form->error($locale->text('Cannot post invoice!'));
 
@@ -852,6 +1013,27 @@ sub delete {
   $main::lxdebug->leave_sub();
 }
 
+sub display_form {
+  $::lxdebug->enter_sub;
+
+  _assert_access();
+
+  relink_accounts();
+
+  my $new_rowcount = $::form->{"rowcount"} * 1 + 1;
+  $::form->{"project_id_${new_rowcount}"} = $::form->{"globalproject_id"};
+
+  $::form->language_payment(\%::myconfig);
+
+  Common::webdav_folder($::form);
+
+  form_header();
+  display_row(++$::form->{rowcount});
+  form_footer();
+
+  $::lxdebug->leave_sub;
+}
+
 sub yes {
   $main::lxdebug->enter_sub();