]> wagnertech.de Git - mfinanz.git/blobdiff - bin/mozilla/ap.pl
PDF::Table: Zusätzliche header richtigrum anzeigen
[mfinanz.git] / bin / mozilla / ap.pl
index 038c5741f829b13eb5c0e71f549033523b4f8fb7..49f6f7a638b2a6019f46ed604c87a4e25c6f02d6 100644 (file)
@@ -43,6 +43,8 @@ use SL::Helper::Flash qw(flash);
 use SL::IR;
 use SL::IS;
 use SL::ReportGenerator;
+use SL::DB::BankTransactionAccTrans;
+use SL::DB::Chart;
 use SL::DB::Currency;
 use SL::DB::Default;
 use SL::DB::PurchaseInvoice;
@@ -88,6 +90,20 @@ use strict;
 # $locale->text('Nov')
 # $locale->text('Dec')
 
+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 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 load_record_template {
   $::auth->assert('ap_transactions');
 
@@ -154,6 +170,12 @@ sub load_record_template {
   $::form->{$_} = $form_defaults->{$_} for keys %{ $form_defaults // {} };
 
   flash('info', $::locale->text("The record template '#1' has been loaded.", $template->template_name));
+  flash('info', $::locale->text("Payment bookings disallowed. After the booking this record may be " .
+                                "suggested with the amount of '#1' or otherwise has to be choosen manually." .
+                                " No automatic payment booking will be done to chart '#2'.",
+                                  $form_defaults->{paid_1_suggestion},
+                                  $form_defaults->{AP_paid_1_suggestion},
+                                )) if $::form->{no_payment_bookings};
 
   update(
     keep_rows_without_amount => 1,
@@ -241,9 +263,11 @@ sub add {
 sub edit {
   $main::lxdebug->enter_sub();
 
-  my $form     = $main::form;
+  # Delay access check to after the invoice's been loaded in
+  # "create_links" so that project-specific invoice rights can be
+  # evaluated.
 
-  $main::auth->assert('ap_transactions');
+  my $form     = $main::form;
 
   $form->{title} = "Edit";
 
@@ -256,9 +280,9 @@ sub edit {
 sub display_form {
   $main::lxdebug->enter_sub();
 
-  my $form     = $main::form;
+  _assert_access();
 
-  $main::auth->assert('ap_transactions');
+  my $form     = $main::form;
 
   # get all files stored in the webdav folder
   if ($form->{invnumber} && $::instance_conf->get_webdav) {
@@ -281,14 +305,18 @@ sub display_form {
 sub create_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 %params   = @_;
 
   my $form     = $main::form;
   my %myconfig = %main::myconfig;
 
-  $main::auth->assert('ap_transactions');
-
   $form->create_links("AP", \%myconfig, "vendor");
+
+  _assert_access();
+
   my %saved;
   if (!$params{dont_save}) {
     %saved = map { ($_ => $form->{$_}) } qw(direct_debit taxincluded);
@@ -301,7 +329,7 @@ sub create_links {
 
   $form->{$_}        = $saved{$_} for keys %saved;
   $form->{rowcount}  = 1;
-  $form->{AP_chart_id} = $form->{acc_trans} && $form->{acc_trans}->{AP} ? $form->{acc_trans}->{AP}->[0]->{chart_id} : $form->{AP_links}->{AP}->[0]->{chart_id};
+  $form->{AP_chart_id} = $form->{acc_trans} && $form->{acc_trans}->{AP} ? $form->{acc_trans}->{AP}->[0]->{chart_id} : $::instance_conf->get_ap_chart_id || $form->{AP_links}->{AP}->[0]->{chart_id};
 
   # build the popup menus
   $form->{taxincluded} = ($form->{id}) ? $form->{taxincluded} : "checked";
@@ -340,13 +368,13 @@ sub _sort_payments {
 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('ap_transactions');
-
   $::form->{invoice_obj} = SL::DB::PurchaseInvoice->new(id => $::form->{id})->load if $::form->{id};
 
   $form->{initial_focus} = !($form->{amount_1} * 1) ? 'vendor_id' : 'row_' . $form->{rowcount};
@@ -407,7 +435,7 @@ sub form_header {
     @{ $form->{ALL_CHARTS} }
   );
 
-  $form->{ALL_DEPARTMENTS} = SL::DB::Manager::Department->get_all;
+  $form->{ALL_DEPARTMENTS} = SL::DB::Manager::Department->get_all_sorted;
 
   my %project_labels = ();
   foreach my $item (@{ $form->{"ALL_PROJECTS"} }) {
@@ -431,10 +459,15 @@ sub form_header {
   my $follow_up_vc         = $form->{vendor_id} ? SL::DB::Vendor->load_cached($form->{vendor_id})->name : '';
   my $follow_up_trans_info =  "$form->{invnumber} ($follow_up_vc)";
 
-  $::request->layout->add_javascripts("autocomplete_chart.js", "autocomplete_customer.js", "show_vc_details.js", "show_history.js", "follow_up.js", "kivi.Draft.js", "kivi.GL.js", "kivi.RecordTemplate.js", "kivi.File.js", "kivi.AP.js");
+  $::request->layout->add_javascripts("autocomplete_chart.js", "show_vc_details.js", "show_history.js", "follow_up.js", "kivi.Draft.js", "kivi.GL.js", "kivi.RecordTemplate.js", "kivi.File.js", "kivi.AP.js", "kivi.CustomerVendor.js", "kivi.Validator.js");
   my $transdate = $::form->{transdate} ? DateTime->from_kivitendo($::form->{transdate}) : DateTime->today_local;
   my $first_taxchart;
 
+  # $form->{totalpaid} is used by the action bar setup to determine
+  # whether or not canceling is allowed. Therefore it must be
+  # calculated prior to the action bar setup.
+  $form->{totalpaid} = sum map { $form->{"paid_${_}"} } (1..$form->{paidaccounts});
+
   setup_ap_display_form_action_bar();
 
   $form->header();
@@ -446,9 +479,8 @@ sub form_header {
     $form->{"tax_$i"} = $form->format_amount(\%myconfig, $form->{"tax_$i"}, 2);
 
     my ($default_taxchart, $taxchart_to_use);
-    my $amount_chart_id   = $form->{"AP_amount_chart_id_$i"} || $default_ap_amount_chart_id;
-    my $chart_has_changed = $::form->{"previous_AP_amount_chart_id_$i"} && ($amount_chart_id != $::form->{"previous_AP_amount_chart_id_$i"});
-    my @taxcharts         = GL->get_active_taxes_for_chart($amount_chart_id, $transdate);
+    my $amount_chart_id = $form->{"AP_amount_chart_id_$i"} || $default_ap_amount_chart_id;
+    my @taxcharts       = GL->get_active_taxes_for_chart($amount_chart_id, $transdate);
 
     foreach my $item (@taxcharts) {
       my $key             = $item->id . "--" . $item->rate;
@@ -457,7 +489,7 @@ sub form_header {
       $taxchart_to_use    = $item if $key eq $form->{"taxchart_$i"};
     }
 
-    $taxchart_to_use                 = $default_taxchart // $first_taxchart if $chart_has_changed || !$taxchart_to_use;
+    $taxchart_to_use               //= $default_taxchart // $first_taxchart;
     my $selected_taxchart            = $taxchart_to_use->id . '--' . $taxchart_to_use->rate;
     $form->{"selected_taxchart_$i"}  = $selected_taxchart;
     $form->{"AP_amount_chart_id_$i"} = $amount_chart_id;
@@ -483,8 +515,6 @@ sub form_header {
   $form->{invtotal_unformatted} = $form->{invtotal};
   $form->{invtotal} = $form->format_amount(\%myconfig, $form->{invtotal}, 2);
 
-  $form->{totalpaid} = 0;
-
   _sort_payments();
 
   if ( $form->{'paid_'. $form->{paidaccounts}} ) {
@@ -495,8 +525,6 @@ sub form_header {
   $form->{accno_arap} = IS->get_standard_accno_current_assets(\%myconfig, \%$form);
 
   for my $i (1 .. $form->{paidaccounts}) {
-    $form->{totalpaid} += $form->{"paid_$i"};
-
     # format amounts
     if ($form->{"paid_$i"}) {
       $form->{"paid_$i"} = $form->format_amount(\%myconfig, $form->{"paid_$i"}, 2);
@@ -526,6 +554,10 @@ sub form_header {
     $form->{'paidaccount_changeable_'. $i} = $changeable;
 
     $form->{'labelpaid_project_id_'. $i} = $project_labels{$form->{'paid_project_id_'. $i}};
+    # accno and description as info text
+    $form->{'AP_paid_readonly_desc_' . $i} =  $form->{'AP_paid_' . $i} ?
+       $form->{'AP_paid_' . $i} . " " . SL::DB::Manager::Chart->find_by(accno => $form->{'AP_paid_' . $i})->description
+     : '';
   }
 
   $form->{paid_missing} = $form->{invtotal_unformatted} - $form->{totalpaid};
@@ -540,7 +572,8 @@ sub form_header {
 
 sub form_footer {
   $::lxdebug->enter_sub;
-  $::auth->assert('ap_transactions');
+
+  _assert_access();
 
   my $num_due;
   my $num_follow_ups;
@@ -633,6 +666,10 @@ sub update {
 
   if (($form->{previous_vendor_id} || $form->{vendor_id}) != $form->{vendor_id}) {
     IR->get_vendor(\%::myconfig, $form);
+    if (($form->{rowcount} == 1) && ($form->{amount_1} == 0)) {
+      my $last_used_ap_chart = SL::DB::Vendor->load_cached($form->{vendor_id})->last_used_ap_chart;
+      $form->{"AP_amount_chart_id_1"} = $last_used_ap_chart->id if $last_used_ap_chart;
+    }
   }
 
   $form->{rowcount} = $count + ($params{dont_add_new_row} ? 0 : 1);
@@ -734,10 +771,11 @@ sub post {
   my ($inline) = @_;
 
   # check if there is a vendor, invoice, due date and invnumber
-  $form->isblank("transdate", $locale->text("Invoice Date missing!"));
-  $form->isblank("duedate",   $locale->text("Due Date missing!"));
-  $form->isblank("vendor_id", $locale->text('Vendor missing!'));
-  $form->isblank("invnumber", $locale->text('Invoice Number missing!'));
+  $form->isblank("transdate",   $locale->text("Invoice Date missing!"));
+  $form->isblank("duedate",     $locale->text("Due Date missing!"));
+  $form->isblank("vendor_id",   $locale->text('Vendor missing!'));
+  $form->isblank("invnumber",   $locale->text('Invoice Number missing!'));
+  $form->isblank("AP_chart_id", $locale->text('No contra account selected!'));
 
   if ($myconfig{mandatory_departments} && !$form->{department_id}) {
     $form->{saved_message} = $::locale->text('You have to specify a department.');
@@ -813,9 +851,14 @@ sub post {
       $form->{what_done} = "invoice";
       $form->save_history;
     }
-    # /saving the history
-    # Dieser Text wird niemals ausgegeben: Probleme beim redirect?
-    $form->redirect($locale->text('AP transaction posted.')) unless $inline;
+    # no restore_from_session_id needed. we like to have a newly generated
+    # list of invoices for bank transactions
+    print $form->redirect_header($form->{callback}) if ($form->{callback} =~ /BankTransaction/);
+    $form->redirect($locale->text('AP transaction posted.') . ' ' . $locale->text('ID') . ': ' . $form->{id}) unless $inline;
+    # TODO Add callback/return flag in myconfig
+    # With version 3.5 we can add documents, but only after posting. there should be a flag in myconfig for the user
+    # $form->{callback} ||= 'ap.pl?action=edit&id=' . $form->{id} if $myconfig{no_reset_arap};
+
   } else {
     $form->error($locale->text('Cannot post transaction!'));
   }
@@ -898,13 +941,11 @@ sub delete {
 sub search {
   $main::lxdebug->enter_sub();
 
-  $main::auth->assert('vendor_invoice_edit');
-
   my $form     = $main::form;
   my %myconfig = %main::myconfig;
   my $locale   = $main::locale;
 
-  $form->{title}    = $locale->text('AP Transactions');
+  $form->{title} = $locale->text('Vendor Invoices & AP Transactions');
 
   $form->get_lists(projects => { "key" => "ALL_PROJECTS", "all" => 1 });
 
@@ -914,6 +955,8 @@ sub search {
 
   $::request->layout->add_javascripts("autocomplete_project.js");
 
+  setup_ap_search_action_bar();
+
   $form->header;
   print $form->parse_html_template('ap/search', { %myconfig });
 
@@ -948,13 +991,11 @@ sub ap_transactions {
   my %myconfig = %main::myconfig;
   my $locale   = $main::locale;
 
-  $main::auth->assert('vendor_invoice_edit');
-
   report_generator_set_default_sort('transdate', 1);
 
   AP->ap_transactions(\%myconfig, \%$form);
 
-  $form->{title} = $locale->text('AP Transactions');
+  $form->{title} = $locale->text('Vendor Invoices & AP Transactions');
 
   my $report = SL::ReportGenerator->new(\%myconfig, $form);
 
@@ -992,7 +1033,7 @@ sub ap_transactions {
     'ustid'                   => { 'text' => $locale->text('USt-IdNr.'), },
     'taxzone'                 => { 'text' => $locale->text('Tax rate'), },
     'payment_terms'           => { 'text' => $locale->text('Payment Terms'), },
-    'charts'                  => { 'text' => $locale->text('Buchungskonto'), },
+    'charts'                  => { 'text' => $locale->text('Chart'), },
     'direct_debit'            => { 'text' => $locale->text('direct debit'), },
   );
 
@@ -1029,7 +1070,6 @@ sub ap_transactions {
   push @options, $locale->text('Closed')                                                                 if ($form->{closed});
 
   $report->set_options('top_info_text'        => join("\n", @options),
-                       'raw_bottom_info_text' => $form->parse_html_template('ap/ap_transactions_bottom'),
                        'output_format'        => 'HTML',
                        'title'                => $form->{title},
                        'attachment_basename'  => $locale->text('vendor_invoice_list') . strftime('_%Y%m%d', localtime time),
@@ -1104,6 +1144,7 @@ sub ap_transactions {
   $report->add_separator();
   $report->add_data(create_subtotal_row(\%totals, \@columns, \%column_alignment, \@subtotal_columns, 'listtotal'));
 
+  setup_ap_transactions_action_bar();
   $report->generate_with_headers();
 
   $main::lxdebug->leave_sub();
@@ -1142,46 +1183,112 @@ sub storno {
   $main::lxdebug->leave_sub();
 }
 
+sub setup_ap_search_action_bar {
+  my %params = @_;
+
+  for my $bar ($::request->layout->get('actionbar')) {
+    $bar->add(
+      action => [
+        $::locale->text('Search'),
+        submit    => [ '#form', { action => "ap_transactions" } ],
+        checks    => [ 'kivi.validate_form' ],
+        accesskey => 'enter',
+      ],
+    );
+  }
+  $::request->layout->add_javascripts('kivi.Validator.js');
+}
+
+sub setup_ap_transactions_action_bar {
+  my %params          = @_;
+  my $may_edit_create = $::auth->assert('ap_transactions', 1);
+
+  for my $bar ($::request->layout->get('actionbar')) {
+    $bar->add(
+      combobox => [
+        action => [ t8('Add') ],
+        link => [
+          t8('Purchase Invoice'),
+          link     => [ 'ir.pl?action=add' ],
+          disabled => !$may_edit_create ? t8('You do not have the permissions to access this function.') : undef,
+
+        ],
+        link => [
+          t8('AP Transaction'),
+          link     => [ 'ap.pl?action=add' ],
+          disabled => !$may_edit_create ? t8('You do not have the permissions to access this function.') : undef,
+        ],
+      ], # end of combobox "Add"
+    );
+  }
+}
+
 sub setup_ap_display_form_action_bar {
   my $transdate               = $::form->datetonum($::form->{transdate}, \%::myconfig);
   my $closedto                = $::form->datetonum($::form->{closedto},  \%::myconfig);
   my $is_closed               = $transdate <= $closedto;
 
-  my $change_never            = $::instance_conf->get_ar_changeable == 0;
-  my $change_on_same_day_only = $::instance_conf->get_ar_changeable == 2 && ($::form->current_date(\%::myconfig) ne $::form->{gldate});
+  my $change_never            = $::instance_conf->get_ap_changeable == 0;
+  my $change_on_same_day_only = $::instance_conf->get_ap_changeable == 2 && ($::form->current_date(\%::myconfig) ne $::form->{gldate});
 
   my $is_storno               = IS->is_storno(\%::myconfig, $::form, 'ap', $::form->{id});
   my $has_storno              = IS->has_storno(\%::myconfig, $::form, 'ap');
 
+  my $may_edit_create         = $::auth->assert('ap_transactions', 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;
+  }
+
   for my $bar ($::request->layout->get('actionbar')) {
     $bar->add(
       action => [
         t8('Update'),
         submit    => [ '#form', { action => "update" } ],
         id        => 'update_button',
+        checks    => [ 'kivi.validate_form' ],
         accesskey => 'enter',
+        disabled  => !$may_edit_create ? t8('You must not change this AP transaction.') : undef,
       ],
 
       combobox => [
         action => [
           t8('Post'),
           submit   => [ '#form', { action => "post" } ],
-          checks   => [ 'kivi.AP.check_fields_before_posting' ],
-          disabled => $is_closed                                  ? t8('The billing period has already been locked.')
+          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 AP transaction.')
+                    : $is_closed                                  ? t8('The billing period has already been locked.')
                     : $is_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,
         ],
         action => [
           t8('Post Payment'),
           submit   => [ '#form', { action => "post_payment" } ],
-          disabled => !$::form->{id} ? t8('This invoice has not been posted yet.') : undef,
+          checks   => [ 'kivi.validate_form' ],
+          disabled => !$may_edit_create           ? t8('You must not change this AP transaction.')
+                    : !$::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" } ],
           confirm  => t8('This will remove the invoice from showing as unpaid even if the unpaid amount does not match the amount. Proceed?'),
-          disabled => !$::form->{id} ? t8('This invoice has not been posted yet.') : undef,
+          disabled => !$may_edit_create ? t8('You must not change this AP transaction.')
+                    : !$::form->{id}    ? t8('This invoice has not been posted yet.')
+                    :                     undef,
           only_if  => $::instance_conf->get_is_show_mark_as_paid,
         ],
       ], # end of combobox "Post"
@@ -1189,23 +1296,28 @@ sub setup_ap_display_form_action_bar {
       combobox => [
         action => [ t8('Storno'),
           submit   => [ '#form', { action => "storno" } ],
-          checks   => [ 'kivi.AP.check_fields_before_posting' ],
+          checks   => [ 'kivi.validate_form', 'kivi.AP.check_fields_before_posting' ],
           confirm  => t8('Do you really want to cancel this invoice?'),
-          disabled => !$::form->{id}         ? t8('This invoice has not been posted yet.')
-                      : $has_storno          ? t8('This invoice has been canceled already.')
-                      : $is_storno           ? t8('Reversal invoices cannot be canceled.')
-                      : $::form->{totalpaid} ? t8('Invoices with payments cannot be canceled.')
-                      :                        undef,
+          disabled => !$may_edit_create    ? t8('You must not change this AP transaction.')
+                    : !$::form->{id}       ? t8('This invoice has not been posted yet.')
+                    : $has_storno          ? t8('This invoice has been canceled already.')
+                    : $is_storno           ? t8('Reversal invoices cannot be canceled.')
+                    : $::form->{totalpaid} ? t8('Invoices with payments cannot be canceled.')
+                    : $has_sepa_exports    ? t8('This invoice has been linked with a sepa export, undo this first.')
+                    :                        undef,
         ],
         action => [ t8('Delete'),
           submit   => [ '#form', { action => "delete" } ],
           confirm  => t8('Do you really want to delete this object?'),
-          disabled => !$::form->{id}           ? t8('This invoice has not been posted yet.')
-                    : $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_storno              ? t8('This invoice has been canceled already.')
-                    : $is_closed               ? t8('The billing period has already been locked.')
-                    :                            undef,
+          disabled => !$may_edit_create           ? t8('You must not change this AP transaction.')
+                    : !$::form->{id}              ? t8('This invoice has not been posted yet.')
+                    : $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_storno                 ? t8('This invoice has been canceled already.')
+                    : $is_closed                  ? t8('The billing period has already been locked.')
+                    : $has_sepa_exports           ? t8('This invoice has been linked with a sepa export, undo this first.')
+                    : $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"
 
@@ -1216,7 +1328,10 @@ sub setup_ap_display_form_action_bar {
         action => [
           t8('Use As New'),
           submit   => [ '#form', { action => "use_as_new" } ],
-          disabled => !$::form->{id} ? t8('This invoice has not been posted yet.') : undef,
+          checks   => [ 'kivi.validate_form' ],
+          disabled => !$may_edit_create ? t8('You must not change this AP transaction.')
+                    : !$::form->{id}    ? t8('This invoice has not been posted yet.')
+                    :                     undef,
         ],
       ], # end of combobox "Workflow"
 
@@ -1234,16 +1349,19 @@ sub setup_ap_display_form_action_bar {
         ],
         action => [
           t8('Record templates'),
-          call => [ 'kivi.RecordTemplate.popup', 'ap_transaction' ],
+          call     => [ 'kivi.RecordTemplate.popup', 'ap_transaction' ],
+          disabled => !$may_edit_create ? t8('You must not change this AP transaction.') : undef,
         ],
         action => [
           t8('Drafts'),
           call     => [ 'kivi.Draft.popup', 'ap', 'invoice', $::form->{draft_id}, $::form->{draft_description} ],
-          disabled => $::form->{id} ? t8('This invoice has already been posted.')
-                    : $is_closed    ? t8('The billing period has already been locked.')
-                    :                 undef,
+          disabled => !$may_edit_create ? t8('You must not change this AP transaction.')
+                    : $::form->{id}     ? t8('This invoice has already been posted.')
+                    : $is_closed        ? t8('The billing period has already been locked.')
+                    :                     undef,
         ],
       ], # end of combobox "more"
     );
   }
+  $::request->layout->add_javascripts('kivi.Validator.js');
 }