my $where = "1 = 1";
- unless ( $::auth->assert('show_ar_transactions', 1) ) {
- $where .= " AND NOT invoice = 'f' "; # remove ar transactions from Sales -> Reports -> Invoices
- };
+ # Permissions:
+ # - Always return invoices & AR transactions for projects the employee has "view invoices" permissions for, no matter what the other rules say.
+ # - Exclude AR transactions if no permissions for them exist.
+ # - Limit to own invoices unless may edit all invoices.
+ # - If may edit all, allow filtering by employee/salesman.
+ my (@permission_where, @permission_values);
+
+ if ($::auth->assert('invoice_edit', 1)) {
+ if (!$::auth->assert('show_ar_transactions', 1) ) {
+ push @permission_where, "NOT invoice = 'f'"; # remove ar transactions from Sales -> Reports -> Invoices
+ }
+
+ if (!$::auth->assert('sales_all_edit', 1)) {
+ # only show own invoices
+ push @permission_where, "a.employee_id = ?";
+ push @permission_values, SL::DB::Manager::Employee->current->id;
+
+ } else {
+ if ($form->{employee_id}) {
+ push @permission_where, "a.employee_id = ?";
+ push @permission_values, conv_i($form->{employee_id});
+ }
+ if ($form->{salesman_id}) {
+ push @permission_where, "a.salesman_id = ?";
+ push @permission_values, conv_i($form->{salesman_id});
+ }
+ }
+ }
+
+ if (@permission_where || !$::auth->assert('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->{customer}) {
$where .= " AND c.name ILIKE ?";
}
}
- if (!$main::auth->assert('sales_all_edit', 1)) {
- # only show own invoices
- $where .= " AND a.employee_id = (select id from employee where login= ?)";
- push (@values, $::myconfig{login});
- } else {
- if ($form->{employee_id}) {
- $where .= " AND a.employee_id = ?";
- push @values, conv_i($form->{employee_id});
- }
- if ($form->{salesman_id}) {
- $where .= " AND a.salesman_id = ?";
- push @values, conv_i($form->{salesman_id});
- }
- };
-
if ($form->{parts_partnumber}) {
$where .= <<SQL;
AND EXISTS (
use SL::CVar;
use SL::DB::Customer;
use SL::DB::DeliveryOrder;
+use SL::DB::Employee;
use SL::DB::Invoice;
use SL::DB::Order;
use SL::DB::Project;
use Rose::Object::MakeMethods::Generic
(
scalar => [ qw(project) ],
- 'scalar --get_set_init' => [ qw(models customers project_types project_statuses projects linked_records) ],
+ 'scalar --get_set_init' => [ qw(models customers project_types project_statuses projects linked_records employees may_edit_invoice_permissions) ],
);
__PACKAGE__->run_before('check_auth', except => [ qw(ajax_autocomplete) ]);
__PACKAGE__->run_before('load_project', only => [ qw(edit update destroy) ]);
+__PACKAGE__->run_before('use_multiselect_js', only => [ qw(new create edit update) ]);
#
# actions
sub init_project_statuses { SL::DB::Manager::ProjectStatus->get_all_sorted }
sub init_project_types { SL::DB::Manager::ProjectType->get_all_sorted }
+sub init_employees { SL::DB::Manager::Employee->get_all_sorted }
+sub init_may_edit_invoice_permissions { $::auth->assert('project_edit_view_invoices_permission', 1) }
sub init_linked_records {
my ($self) = @_;
return SL::DB::Manager::Customer->get_all_sorted(where => [ or => [ obsolete => 0, obsolete => undef, @customer_id ]]);
}
+sub use_multiselect_js {
+ $::request->layout->use_javascript("${_}.js") for qw(jquery.selectboxes jquery.multiselect2side);
+}
+
sub display_form {
my ($self, %params) = @_;
my $is_new = !$self->project->id;
my $params = delete($::form->{project}) || { };
+ if (!$self->may_edit_invoice_permissions) {
+ delete $params->{employee_invoice_permissions};
+ } elsif (!$params->{employee_invoice_permissions}) {
+ $params->{employee_invoice_permissions} = [];
+ }
+
delete $params->{id};
$self->project->assign_attributes(%{ $params });
use SL::DB::MetaSetup::Employee;
use SL::DB::Manager::Employee;
+__PACKAGE__->meta->add_relationship(
+ project_invoice_permissions => {
+ type => 'many to many',
+ map_class => 'SL::DB::EmployeeProjectInvoices',
+ },
+);
+
__PACKAGE__->meta->initialize;
sub has_right {
--- /dev/null
+package SL::DB::EmployeeProjectInvoices;
+
+use strict;
+
+use SL::DB::MetaSetup::EmployeeProjectInvoices;
+use SL::DB::Manager::EmployeeProjectInvoices;
+
+__PACKAGE__->meta->initialize;
+
+1;
use SL::DB::EmailJournal;
use SL::DB::EmailJournalAttachment;
use SL::DB::Employee;
+use SL::DB::EmployeeProjectInvoices;
use SL::DB::Exchangerate;
use SL::DB::File;
use SL::DB::Finanzamt;
email_journal => 'EmailJournal',
email_journal_attachments => 'EmailJournalAttachment',
employee => 'employee',
+ employee_project_invoices => 'EmployeeProjectInvoices',
exchangerate => 'exchangerate',
files => 'file',
finanzamt => 'finanzamt',
--- /dev/null
+package SL::DB::Manager::EmployeeProjectInvoices;
+
+use strict;
+
+use parent qw(SL::DB::Helper::Manager);
+
+sub object_class { 'SL::DB::EmployeeProjectInvoices' }
+
+__PACKAGE__->make_manager_methods;
+
+1;
--- /dev/null
+# This file has been auto-generated. Do not modify it; it will be overwritten
+# by rose_auto_create_model.pl automatically.
+package SL::DB::EmployeeProjectInvoices;
+
+use strict;
+
+use parent qw(SL::DB::Object);
+
+__PACKAGE__->meta->table('employee_project_invoices');
+
+__PACKAGE__->meta->columns(
+ employee_id => { type => 'integer', not_null => 1 },
+ project_id => { type => 'integer', not_null => 1 },
+);
+
+__PACKAGE__->meta->primary_key_columns([ 'employee_id', 'project_id' ]);
+
+__PACKAGE__->meta->foreign_keys(
+ employee => {
+ class => 'SL::DB::Employee',
+ key_columns => { employee_id => 'id' },
+ },
+
+ project => {
+ class => 'SL::DB::Project',
+ key_columns => { project_id => 'id' },
+ },
+);
+
+1;
+;
cvars_alias => 1,
);
+__PACKAGE__->meta->add_relationship(
+ employee_invoice_permissions => {
+ type => 'many to many',
+ map_class => 'SL::DB::EmployeeProjectInvoices',
+ },
+);
+
__PACKAGE__->meta->initialize;
sub validate {
return $description;
}
+sub may_employee_view_project_invoices {
+ my ($self, $employee) = @_;
+
+ return undef if !$self->id;
+
+ my $employee_id = ref($employee) ? $employee->id : $employee * 1;
+ my $query = <<EOSQL;
+ SELECT project_id
+ FROM employee_project_invoices
+ WHERE (employee_id = ?)
+ AND (project_id = ?)
+ LIMIT 1
+EOSQL
+
+ return !!$self->db->dbh->selectrow_arrayref($query, undef, $employee_id, $self->id)->[0];
+}
+
1;
__END__
# $locale->text('Nov')
# $locale->text('Dec')
+sub _may_view_or_edit_this_invoice {
+ return 1 if $::auth->assert('ar_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('ar.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('ar_transactions');
sub edit {
$main::lxdebug->enter_sub();
- $main::auth->assert('ar_transactions');
+ # 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;
sub display_form {
$main::lxdebug->enter_sub();
- $main::auth->assert('ar_transactions');
+ _assert_access();
my $form = $main::form;
sub create_links {
$main::lxdebug->enter_sub();
- $main::auth->assert('ar_transactions');
+ # 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;
$form->create_links("AR", \%myconfig, "customer");
$form->{invoice_obj} = _retrieve_invoice_object();
+ _assert_access();
+
my %saved;
if (!$params{dont_save}) {
%saved = map { ($_ => $form->{$_}) } qw(direct_debit id taxincluded);
sub form_header {
$main::lxdebug->enter_sub();
- $main::auth->assert('ar_transactions');
+ _assert_access();
my $form = $main::form;
my %myconfig = %main::myconfig;
sub form_footer {
$main::lxdebug->enter_sub();
- $main::auth->assert('ar_transactions');
+ _assert_access();
my $form = $main::form;
my %myconfig = %main::myconfig;
}
sub setup_ar_transactions_action_bar {
- my %params = @_;
+ my %params = @_;
+ my $may_edit_create = $::auth->assert('invoice_edit', 1);
for my $bar ($::request->layout->get('actionbar')) {
$bar->add(
action => [
$::locale->text('Print'),
call => [ 'kivi.MassInvoiceCreatePrint.showMassPrintOptionsOrDownloadDirectly' ],
- disabled => !$params{num_rows} ? $::locale->text('The report doesn\'t contain entries.') : undef,
+ disabled => !$may_edit_create ? t8('You do not have the permissions to access this function.')
+ : !$params{num_rows} ? $::locale->text('The report doesn\'t contain entries.')
+ : undef,
],
combobox => [
action => [ $::locale->text('Create new') ],
action => [
$::locale->text('AR Transaction'),
- submit => [ '#create_new_form', { action => 'ar_transaction' } ],
+ submit => [ '#create_new_form', { action => 'ar_transaction' } ],
+ disabled => !$may_edit_create ? t8('You do not have the permissions to access this function.') : undef,
],
action => [
$::locale->text('Sales Invoice'),
- submit => [ '#create_new_form', { action => 'sales_invoice' } ],
+ submit => [ '#create_new_form', { action => 'sales_invoice' } ],
+ disabled => !$may_edit_create ? t8('You do not have the permissions to access this function.') : undef,
],
], # end of combobox "Create new"
);
sub search {
$main::lxdebug->enter_sub();
- $main::auth->assert('invoice_edit');
-
my $form = $main::form;
my %myconfig = %main::myconfig;
my $locale = $main::locale;
sub ar_transactions {
$main::lxdebug->enter_sub();
- $main::auth->assert('invoice_edit');
-
my $form = $main::form;
my %myconfig = %main::myconfig;
my $locale = $main::locale;
my $is_storno = IS->is_storno(\%::myconfig, $::form, 'ar', $::form->{id});
my $has_storno = IS->has_storno(\%::myconfig, $::form, 'ar');
+ my $may_edit_create = $::auth->assert('ar_transactions', 1);
for my $bar ($::request->layout->get('actionbar')) {
$bar->add(
submit => [ '#form', { action => "update" } ],
id => 'update_button',
checks => [ 'kivi.validate_form' ],
+ disabled => !$may_edit_create ? t8('You must not change this AR transaction.') : undef,
accesskey => 'enter',
],
t8('Post'),
submit => [ '#form', { action => "post" } ],
checks => [ 'kivi.validate_form', 'kivi.AR.check_fields_before_posting' ],
- disabled => $is_closed ? t8('The billing period has already been locked.')
+ disabled => !$may_edit_create ? t8('You must not change this AR 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.')
action => [
t8('Post Payment'),
submit => [ '#form', { action => "post_payment" } ],
- disabled => !$::form->{id} ? t8('This invoice has not been posted yet.') : undef,
+ disabled => !$may_edit_create ? t8('You must not change this AR 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 AR 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"
submit => [ '#form', { action => "storno" } ],
checks => [ 'kivi.validate_form', 'kivi.AR.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 AR 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.')
+ : 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 AR 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.')
: $is_closed ? t8('The billing period has already been locked.')
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 AR transaction.')
+ : !$::form->{id} ? t8('This invoice has not been posted yet.')
+ : undef,
],
], # end of combobox "Workflow"
],
action => [
t8('Record templates'),
- call => [ 'kivi.RecordTemplate.popup', 'ar_transaction' ],
+ call => [ 'kivi.RecordTemplate.popup', 'ar_transaction' ],
+ disabled => !$may_edit_create ? t8('You must not change this AR transaction.') : undef,
],
action => [
t8('Drafts'),
call => [ 'kivi.Draft.popup', 'ar', '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 AR 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"
);
sub display_row {
$main::lxdebug->enter_sub();
- _check_io_auth();
-
my $form = $main::form;
my %myconfig = %main::myconfig;
my $locale = $main::locale;
my $form = $main::form;
my %myconfig = %main::myconfig;
- _check_io_auth();
-
$form->{"taxaccounts"} =~ s/\s*$//;
$form->{"taxaccounts"} =~ s/^\s*//;
foreach my $accno (split(/\s*/, $form->{"taxaccounts"})) {
# end of main
+sub _may_view_or_edit_this_invoice {
+ return 1 if $::auth->assert('invoice_edit', 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('is.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();
sub edit {
$main::lxdebug->enter_sub();
+ # Delay access check to after the invoice's been loaded in
+ # "invoice_links" so that project-specific invoice rights can be
+ # evaluated.
+
my $form = $main::form;
my $locale = $main::locale;
- $main::auth->assert('invoice_edit');
-
$form->{show_details} = $::myconfig{show_form_details};
$form->{taxincluded_changed_by_user} = 1;
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('invoice_edit');
-
$form->{vc} = 'customer';
# create links
$form->create_links("AR", \%myconfig, "customer");
+ _assert_access();
+
my $editing = $form->{id};
$form->backup_vars(qw(payment_id language_id taxzone_id salesman_id
sub prepare_invoice {
$main::lxdebug->enter_sub();
+ _assert_access();
+
my $form = $main::form;
my %myconfig = %main::myconfig;
- $main::auth->assert('invoice_edit');
-
if ($form->{type} eq "credit_note") {
$form->{type} = "credit_note";
$form->{formname} = "credit_note";
my $change_on_same_day_only = $::instance_conf->get_is_changeable == 2 && ($form->current_date(\%::myconfig) ne $form->{gldate});
my $payments_balanced = ($::form->{oldtotalpaid} == 0);
my $has_storno = ($::form->{storno} && !$::form->{storno_id});
+ my $may_edit_create = $::auth->assert('invoice_edit', 1);
for my $bar ($::request->layout->get('actionbar')) {
$bar->add(
action => [
t8('Update'),
submit => [ '#form', { action => "update" } ],
- disabled => $form->{locked} ? t8('The billing period has already been locked.') : undef,
+ disabled => !$may_edit_create ? t8('You must not change this invoice.')
+ : $form->{locked} ? t8('The billing period has already been locked.')
+ : undef,
id => 'update_button',
checks => [ 'kivi.validate_form' ],
accesskey => 'enter',
t8('Post'),
submit => [ '#form', { action => "post" } ],
checks => [ 'kivi.validate_form' ],
- 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.')
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" } ],
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_is_show_mark_as_paid,
],
], # end of combobox "Post"
submit => [ '#form', { action => "storno" } ],
confirm => t8('Do you really want to cancel this invoice?'),
checks => [ 'kivi.validate_form' ],
- 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.')
: !$payments_balanced ? t8('Cancelling is disallowed. Either undo or balance the current payments until the open amount matches the invoice amount')
: undef,
],
submit => [ '#form', { action => "delete" } ],
confirm => t8('Do you really want to delete this object?'),
checks => [ 'kivi.validate_form' ],
- 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.')
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,
],
action => [
t8('Credit Note'),
submit => [ '#form', { action => "credit_note" } ],
checks => [ 'kivi.validate_form' ],
- disabled => $form->{type} eq "credit_note" ? t8('Credit notes cannot be converted into other credit notes.')
+ disabled => !$may_edit_create ? t8('You must not change this invoice.')
+ : $form->{type} eq "credit_note" ? t8('Credit notes cannot be converted into other credit notes.')
: !$form->{id} ? t8('This invoice has not been posted yet.')
: undef,
],
($form->{id} ? t8('Print') : t8('Preview')),
call => [ 'kivi.SalesPurchase.show_print_dialog', $form->{id} ? 'print' : 'preview' ],
checks => [ 'kivi.validate_form' ],
- disabled => !$form->{id} && $form->{locked} ? t8('The billing period has already been locked.') : undef,
+ disabled => !$may_edit_create ? t8('You must not print this invoice.')
+ : !$form->{id} && $form->{locked} ? t8('The billing period has already been locked.')
+ : undef,
],
action => [ t8('Print and Post'),
call => [ 'kivi.SalesPurchase.show_print_dialog', $form->{id} ? 'print' : 'print_and_post' ],
checks => [ 'kivi.validate_form' ],
- disabled => $form->{id} ? t8('This invoice has already been posted.') : undef,,
+ disabled => !$may_edit_create ? t8('You must not print this invoice.')
+ : $form->{id} ? t8('This invoice has already been posted.')
+ : undef,,
],
action => [ t8('E Mail'),
call => [ 'kivi.SalesPurchase.show_email_dialog' ],
checks => [ 'kivi.validate_form' ],
- disabled => !$form->{id} ? t8('This invoice has not been posted yet.') : undef,
+ disabled => !$may_edit_create ? t8('You must not print this invoice.')
+ : !$form->{id} ? t8('This invoice has not been posted yet.')
+ : undef,
],
], # end of combobox "Export"
action => [
t8('Drafts'),
call => [ 'kivi.Draft.popup', 'is', '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"
);
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('invoice_edit');
-
my %TMPL_VAR = ();
my @custom_hiddens;
sub form_footer {
$main::lxdebug->enter_sub();
+ _assert_access();
+
my $form = $main::form;
my %myconfig = %main::myconfig;
my $locale = $main::locale;
- $main::auth->assert('invoice_edit');
-
$form->{invtotal} = $form->{invsubtotal};
# note rows
sub update {
$main::lxdebug->enter_sub();
+ _assert_access();
+
my $form = $main::form;
my %myconfig = %main::myconfig;
- $main::auth->assert('invoice_edit');
-
my ($recursive_call) = @_;
$form->{print_and_post} = 0 if $form->{second_run};
sub display_form {
$::lxdebug->enter_sub;
- $::auth->assert('invoice_edit');
+ _assert_access();
relink_accounts();
'All as list' => 'Alle als Liste',
'All changes in that file have been reverted.' => 'Alle Änderungen in dieser Datei wurden rückgängig gemacht.',
'All clients' => 'Alle Mandanten',
+ 'All employees' => 'Alle Angestellten',
'All general ledger entries' => 'Alle Hauptbucheinträge',
'All groups' => 'Alle Gruppen',
'All modules' => 'Alle Module',
'Employee (database ID)' => 'Bearbeiter (Datenbank-ID)',
'Employee from the original invoice' => 'Mitarbeiter der Ursprungs-Rechnung',
'Employees' => 'Benutzer',
+ 'Employees with read access to the project\'s invoices' => 'Angestellte mit Leserechten auf die Projektrechnungen',
'Empty selection for warehouse will not be added, even if the old bin is still visible (use back and forth to edit again).' => 'Leere Lager-Auswahl wird ignoriert, selbst wenn noch ein Lagerplatz ausgewählt ist. Alle Daten können durch zurück und vorwärts korrigiert werden.',
'Empty transaction!' => 'Buchung ist leer!',
'Enabled Quick Searched' => 'Aktivierte Schnellsuchen',
'Periodic inventory' => 'Aufwandsmethode',
'Periodic invoices active' => 'Wiederkehrende Rechnungen aktiv',
'Periodic invoices inactive' => 'Wiederkehrende Rechnungen inaktiv',
+ 'Permissions for invoices' => 'Ansehrechte für Rechnungen',
'Perpetual inventory' => 'Bestandsmethode',
'Personal settings' => 'Persönliche Einstellungen',
'Phone' => 'Telefon',
'Project type' => 'Projekttyp',
'Project types' => 'Projekttypen',
'Projects' => 'Projekte',
+ 'Projects: edit the list of employees allowed to view invoices' => 'Projekte: Liste der Angestellten bearbeiten, die Projektrechnungen ansehen dürfen',
'Projecttransactions' => 'Projektbuchungen',
'Proposal' => 'Vorschlag',
'Proposals' => 'Vorschläge',
'You have to specify an execution date for each antry.' => 'Sie müssen für jeden zu buchenden Eintrag ein Ausführungsdatum angeben.',
'You must chose a user.' => 'Sie müssen einen Benutzer auswählen.',
'You must enter a name for your new print templates.' => 'Sie müssen einen Namen für die neuen Druckvorlagen angeben.',
+ 'You must not change this AR transaction.' => 'Sie dürfen diese Debitorenbuchung nicht verändern.',
+ 'You must not change this invoice.' => 'Sie dürfen diese Rechnung nicht verändern.',
+ 'You must not print this invoice.' => 'Sie dürfen diese Rechnung nicht drucken.',
'You must select existing print templates or create a new set.' => 'Sie müssen vorhandene Druckvorlagen auswählen oder einen neuen Satz anlegen.',
'You should create a backup of the database before proceeding because the backup might not be reversible.' => 'Sie sollten eine Sicherungskopie der Datenbank erstellen, bevor Sie fortfahren, da die Aktualisierung unter Umständen nicht umkehrbar ist.',
'You\'re not editing a file.' => 'Sie bearbeiten momentan keine Datei.',
name: Invoices, Credit Notes & AR Transactions
icon: invoices_report
order: 500
- access: invoice_edit
module: ar.pl
params:
action: search
--- /dev/null
+-- @tag: rights_for_viewing_project_specific_invoices
+-- @description: Rechte zum Anzeigen von Rechnungen, die zu Projekten gehören
+-- @depends: release_3_5_3
+-- @locales: Projects: edit the list of employees allowed to view invoices
+INSERT INTO auth.master_rights (position, name, description, category)
+VALUES (
+ (SELECT position + 2
+ FROM auth.master_rights
+ WHERE name = 'project_edit'),
+ 'project_edit_view_invoices_permission',
+ 'Projects: edit the list of employees allowed to view invoices',
+ false
+);
+
+INSERT INTO auth.group_rights (group_id, "right", granted)
+SELECT id, 'project_edit_view_invoices_permission', true
+FROM auth.group
+WHERE name = 'Vollzugriff';
--- /dev/null
+-- @tag: add_emloyee_project_assignment_for_viewing_invoices
+-- @description: Mitarbeiter*innen Projekten zuweisen können, damit diese Projektrechnungen anschauen dürfen
+-- @depends: release_3_5_3
+CREATE TABLE employee_project_invoices (
+ employee_id INTEGER NOT NULL,
+ project_id INTEGER NOT NULL,
+
+ CONSTRAINT employee_project_invoices_pkey PRIMARY KEY (employee_id, project_id),
+ CONSTRAINT employee_project_invoices_employee_id_fkey FOREIGN KEY (employee_id) REFERENCES employee (id) ON DELETE CASCADE,
+ CONSTRAINT employee_project_invoices_project_id_fkey FOREIGN KEY (project_id) REFERENCES project (id) ON DELETE CASCADE
+);
--- /dev/null
+[%- USE LxERP -%][%- USE L -%]<div class="clearfix">
+ [% L.select_tag("project.employee_invoice_permissions[]", SELF.employees, id="employee_invoice_permissions", title_key="safe_name", default=SELF.project.employee_invoice_permissions, default_value_key='id', multiple=1) %]
+ [% L.multiselect2side("employee_invoice_permissions", labelsx => LxERP.t8("All employees"), labeldx => LxERP.t8("Employees with read access to the project's invoices")) %]
+</div>
[%- IF CUSTOM_VARIABLES.size %]
<li><a href="#custom_variables">[% 'Custom Variables' | $T8 %]</a></li>
[%- END %]
+ [%- IF SELF.may_edit_invoice_permissions %]
+ <li><a href="#invoice_permissions">[% 'Permissions for invoices' | $T8 %]</a></li>
+ [%- END %]
[%- IF SELF.project.id and AUTH.assert('record_links', 1) %]
<li><a href="#linked_records">[% 'Linked Records' | $T8 %]</a></li>
[%- END %]
</div>
[%- END %]
+ [%- IF SELF.may_edit_invoice_permissions %]
+ <div id="invoice_permissions">
+ [%- PROCESS 'project/_invoice_permissions.html' %]
+ </div>
+ [%- END %]
+
[%- IF SELF.project.id and AUTH.assert('record_links', 1) %]
<div id="linked_records">
[%- PROCESS 'project/_linked_records.html' records=SELF.linked_records %]