Berechtigung, Einkaufsrechnungen persönlich zugeordneter Projekte einzusehen
authorMoritz Bunkus <m.bunkus@linet-services.de>
Thu, 14 Feb 2019 11:14:02 +0000 (12:14 +0100)
committerMoritz Bunkus <m.bunkus@linet-services.de>
Thu, 14 Feb 2019 15:40:29 +0000 (16:40 +0100)
Betrifft sowohl Einkaufsrechnungen als auch Kreditorenbuchungen.

SL/AP.pm
bin/mozilla/ap.pl
bin/mozilla/ir.pl
menus/user/00-erp.yaml

index 5b4386e..caae2bb 100644 (file)
--- a/SL/AP.pm
+++ b/SL/AP.pm
@@ -444,12 +444,37 @@ sub ap_transactions {
 
   my $where = '';
 
-  unless ( $::auth->assert('show_ap_transactions', 1) ) {
-    $where .= " AND NOT invoice = 'f' ";  # remove ap transactions from Sales -> Reports -> Invoices
-  };
-
   my @values;
 
+  # Permissions:
+  # - Always return invoices & AP transactions for projects the employee has "view invoices" permissions for, no matter what the other rules say.
+  # - Exclude AP transactions if no permissions for them exist.
+  # - Filter by employee if requested.
+  my (@permission_where, @permission_values);
+
+  if ($::auth->assert('vendor_invoice_edit', 1)) {
+    if (!$::auth->assert('show_ap_transactions', 1)) {
+      push @permission_where, "NOT invoice = 'f'"; # remove ap transactions from Purchase -> Reports -> Invoices
+    }
+
+    if ($form->{employee_id}) {
+      push @permission_where,  "a.employee_id = ?";
+      push @permission_values, conv_i($form->{employee_id});
+    }
+  }
+
+  if (@permission_where || !$::auth->assert('vendor_invoice_edit', 1)) {
+    my $permission_where_str = @permission_where ? "OR (" . join(" AND ", map { "($_)" } @permission_where) . ")" : "";
+    $where .= qq|
+      AND (   (a.globalproject_id IN (
+               SELECT epi.project_id
+               FROM employee_project_invoices epi
+               WHERE epi.employee_id = ?))
+           $permission_where_str)
+    |;
+    push @values, SL::DB::Manager::Employee->current->id, @permission_values;
+  }
+
   if ($form->{vendor}) {
     $where .= " AND v.name ILIKE ?";
     push(@values, like($form->{vendor}));
@@ -529,7 +554,7 @@ SQL
   }
 
   if ($where) {
-    substr($where, 0, 4, " WHERE ");
+    $where  =~ s{\s*AND\s*}{ WHERE };
     $query .= $where;
   }
 
index 53dd785..187322a 100644 (file)
@@ -88,6 +88,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');
 
@@ -247,9 +261,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";
 
@@ -262,9 +278,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) {
@@ -287,14 +303,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);
@@ -346,13 +366,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};
@@ -546,7 +566,8 @@ sub form_header {
 
 sub form_footer {
   $::lxdebug->enter_sub;
-  $::auth->assert('ap_transactions');
+
+  _assert_access();
 
   my $num_due;
   my $num_follow_ups;
@@ -914,8 +935,6 @@ 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;
@@ -966,8 +985,6 @@ 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);
@@ -1177,7 +1194,8 @@ sub setup_ap_search_action_bar {
 }
 
 sub setup_ap_transactions_action_bar {
-  my %params = @_;
+  my %params          = @_;
+  my $may_edit_create = $::auth->assert('vendor_invoice_edit', 1);
 
   for my $bar ($::request->layout->get('actionbar')) {
     $bar->add(
@@ -1185,11 +1203,14 @@ sub setup_ap_transactions_action_bar {
         action => [ t8('Add') ],
         link => [
           t8('Purchase Invoice'),
-          link => [ 'ir.pl?action=add' ],
+          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' ],
+          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"
     );
@@ -1207,6 +1228,8 @@ sub setup_ap_display_form_action_bar {
   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('vendor_invoice_edit', 1);
+
   my $has_sepa_exports;
 
   if ($::form->{id}) {
@@ -1222,6 +1245,7 @@ sub setup_ap_display_form_action_bar {
         id        => 'update_button',
         checks    => [ 'kivi.validate_form' ],
         accesskey => 'enter',
+        disabled  => !$may_edit_create ? t8('You must not change this AP transaction.') : undef,
       ],
 
       combobox => [
@@ -1229,7 +1253,8 @@ sub setup_ap_display_form_action_bar {
           t8('Post'),
           submit   => [ '#form', { action => "post" } ],
           checks   => [ 'kivi.validate_form', 'kivi.AP.check_fields_before_posting', 'kivi.AP.check_duplicate_invnumber' ],
-          disabled => $is_closed                                  ? t8('The billing period has already been locked.')
+          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.')
@@ -1239,12 +1264,16 @@ sub setup_ap_display_form_action_bar {
           t8('Post Payment'),
           submit   => [ '#form', { action => "post_payment" } ],
           checks   => [ 'kivi.validate_form' ],
-          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,
         ],
         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"
@@ -1254,17 +1283,19 @@ sub setup_ap_display_form_action_bar {
           submit   => [ '#form', { action => "storno" } ],
           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.')
-                      : $has_sepa_exports    ? t8('This invoice has been linked with a sepa export, undo this first.')
-                      :                        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.')
+          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.')
@@ -1282,7 +1313,9 @@ sub setup_ap_display_form_action_bar {
           t8('Use As New'),
           submit   => [ '#form', { action => "use_as_new" } ],
           checks   => [ 'kivi.validate_form' ],
-          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,
         ],
       ], # end of combobox "Workflow"
 
@@ -1300,14 +1333,16 @@ 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"
     );
index 6d157fc..79aa473 100644 (file)
@@ -51,6 +51,20 @@ 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 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();
 
@@ -77,11 +91,13 @@ 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
@@ -100,16 +116,19 @@ 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->create_links("AP", \%myconfig, "vendor");
 
+  _assert_access();
+
   $form->backup_vars(qw(payment_id language_id taxzone_id
                         currency delivery_term_id intnotes cp_id));
 
@@ -173,11 +192,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}) {
@@ -222,6 +241,7 @@ sub setup_ir_action_bar {
   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;
 
@@ -238,6 +258,7 @@ sub setup_ir_action_bar {
         checks    => [ 'kivi.validate_form' ],
         id        => 'update_button',
         accesskey => 'enter',
+        disabled  => !$may_edit_create ? t8('You must not change this invoice.') : undef,
       ],
 
       combobox => [
@@ -246,8 +267,8 @@ sub setup_ir_action_bar {
           submit   => [ '#form', { action => "post" } ],
           checks   => [ 'kivi.validate_form' ],
           checks   => [ 'kivi.validate_form', 'kivi.AP.check_fields_before_posting', 'kivi.AP.check_duplicate_invnumber' ],
-
-          disabled => $form->{locked}                           ? t8('The billing period has already been locked.')
+          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.')
@@ -257,14 +278,18 @@ sub setup_ir_action_bar {
           t8('Post Payment'),
           submit   => [ '#form', { action => "post_payment" } ],
           checks   => [ 'kivi.validate_form' ],
-          disabled => !$form->{id} ? t8('This invoice has not been posted yet.') : undef,
+          disabled => !$may_edit_create ? t8('You must not change this invoice.')
+                    : !$form->{id}      ? t8('This invoice has not been posted yet.')
+                    :                     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 => !$form->{id} ? t8('This invoice has not been posted yet.') : undef,
+          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"
@@ -274,16 +299,18 @@ sub setup_ir_action_bar {
           submit   => [ '#form', { action => "storno" } ],
           checks   => [ 'kivi.validate_form' ],
           confirm  => t8('Do you really want to cancel this invoice?'),
-          disabled => !$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,
+          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 => !$form->{id}             ? t8('This invoice has not been posted yet.')
+          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.')
@@ -301,7 +328,9 @@ sub setup_ir_action_bar {
           t8('Use As New'),
           submit   => [ '#form', { action => "use_as_new" } ],
           checks   => [ 'kivi.validate_form' ],
-          disabled => !$form->{id} ? t8('This invoice has not been posted yet.') : undef,
+          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"
 
@@ -320,9 +349,10 @@ sub setup_ir_action_bar {
         action => [
           t8('Drafts'),
           call     => [ 'kivi.Draft.popup', 'ir', 'invoice', $::form->{draft_id}, $::form->{draft_description} ],
-          disabled => $form->{id}     ? t8('This invoice has already been posted.')
-                    : $form->{locked} ? t8('The billing period has already been locked.')
-                    :                   undef,
+          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"
     );
@@ -334,13 +364,13 @@ sub setup_ir_action_bar {
 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;
 
@@ -446,12 +476,12 @@ 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};
 
@@ -950,7 +980,7 @@ sub delete {
 sub display_form {
   $::lxdebug->enter_sub;
 
-  $::auth->assert('vendor_invoice_edit');
+  _assert_access();
 
   relink_accounts();
 
index 91cbb24..8c69e4b 100644 (file)
   id: ap_reports_vendor_invoices_ap_transactions
   name: Vendor Invoices & AP Transactions
   order: 400
-  access: vendor_invoice_edit
   module: ap.pl
   params:
     action: search