From 894ac4cac779a9993350dc14534d01ab04e4a281 Mon Sep 17 00:00:00 2001 From: Moritz Bunkus Date: Thu, 14 Feb 2019 12:14:02 +0100 Subject: [PATCH] =?utf8?q?Berechtigung,=20Einkaufsrechnungen=20pers=C3=B6n?= =?utf8?q?lich=20zugeordneter=20Projekte=20einzusehen?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Betrifft sowohl Einkaufsrechnungen als auch Kreditorenbuchungen. --- SL/AP.pm | 35 ++++++++++++--- bin/mozilla/ap.pl | 97 ++++++++++++++++++++++++++++-------------- bin/mozilla/ir.pl | 78 ++++++++++++++++++++++----------- menus/user/00-erp.yaml | 1 - 4 files changed, 150 insertions(+), 61 deletions(-) diff --git a/SL/AP.pm b/SL/AP.pm index 5b4386e17..caae2bb73 100644 --- 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; } diff --git a/bin/mozilla/ap.pl b/bin/mozilla/ap.pl index 53dd785ed..187322afe 100644 --- a/bin/mozilla/ap.pl +++ b/bin/mozilla/ap.pl @@ -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" ); diff --git a/bin/mozilla/ir.pl b/bin/mozilla/ir.pl index 6d157fc8f..79aa473dc 100644 --- a/bin/mozilla/ir.pl +++ b/bin/mozilla/ir.pl @@ -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(); diff --git a/menus/user/00-erp.yaml b/menus/user/00-erp.yaml index 91cbb2449..8c69e4b1b 100644 --- a/menus/user/00-erp.yaml +++ b/menus/user/00-erp.yaml @@ -477,7 +477,6 @@ id: ap_reports_vendor_invoices_ap_transactions name: Vendor Invoices & AP Transactions order: 400 - access: vendor_invoice_edit module: ap.pl params: action: search -- 2.20.1