Merge branch 'b-3.6.1' of ../kivitendo-erp_20220811
[kivitendo-erp.git] / SL / DB / Project.pm
index 4b93c5a..5497535 100644 (file)
@@ -2,9 +2,111 @@ package SL::DB::Project;
 
 use strict;
 
+use List::MoreUtils qw(any);
+
 use SL::DB::MetaSetup::Project;
+use SL::DB::Manager::Project;
+
+use SL::DB::Helper::CustomVariables(
+  module      => 'Projects',
+  cvars_alias => 1,
+);
+
+__PACKAGE__->meta->add_relationship(
+  employee_invoice_permissions  => {
+    type       => 'many to many',
+    map_class  => 'SL::DB::EmployeeProjectInvoices',
+  },
+);
+
+__PACKAGE__->meta->initialize;
+
+sub validate {
+  my ($self) = @_;
+
+  my @errors;
+  push @errors, $::locale->text('The project number is missing.')        if !$self->projectnumber;
+  push @errors, $::locale->text('The project number is already in use.') if !$self->is_projectnumber_unique;
+  push @errors, $::locale->text('The description is missing.')           if !$self->description;
+
+  return @errors;
+}
+
+sub is_used {
+  my ($self) = @_;
+
+  # Unsaved projects are never referenced.
+  return 0 unless $self->id;
+
+  return any {
+    my $column = $SL::DB::Manager::Project::project_id_column_prefixes{$_} . 'project_id';
+    $self->db->dbh->selectrow_arrayref(qq|SELECT EXISTS(SELECT * FROM ${_} WHERE ${column} = ?)|, undef, $self->id)->[0]
+  } @SL::DB::Manager::Project::tables_with_project_id_cols;
+}
+
+sub is_projectnumber_unique {
+  my ($self) = @_;
+
+  return 1 unless $self->projectnumber;
+
+  my @filter = (projectnumber => $self->projectnumber);
+  @filter    = (and => [ @filter, '!id' => $self->id ]) if $self->id;
+
+  return !SL::DB::Manager::Project->get_first(where => \@filter);
+}
+
+sub displayable_name {
+  my ($self) = @_;
+
+  return join ' ', grep $_, $self->projectnumber, $self->description;
+}
+
+sub full_description {
+  my ($self, %params) = @_;
+
+  $params{style} ||= 'both';
+  my $description;
+
+  if ($params{style} =~ m/number/) {
+    $description = $self->projectnumber;
+
+  } elsif ($params{style} =~ m/description/) {
+    $description = $self->description;
+
+  } elsif (($params{style} =~ m/full/) && $self->customer) {
+    $description = $self->projectnumber;
+    if ($self->description && do { my $desc = quotemeta $self->description; $self->projectnumber !~ m/$desc/ }) {
+      $description .= ' ' . $self->description;
+    }
+
+    $description = $self->customer->name . " (${description})";
+
+  } else {
+    $description = $self->projectnumber;
+    if ($self->description && do { my $desc = quotemeta $self->description; $self->projectnumber !~ m/$desc/ }) {
+      $description .= ' (' . $self->description . ')';
+    }
+  }
+
+  return $description;
+}
+
+sub may_employee_view_project_invoices {
+  my ($self, $employee) = @_;
 
-__PACKAGE__->meta->make_manager_class;
+  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;
 
@@ -22,7 +124,66 @@ This is a standard Rose::DB::Object based model and can be used as one.
 
 =head1 FUNCTIONS
 
-None so far.
+=over 4
+
+=item C<validate>
+
+Checks whether or not all fields are set to valid values so that the
+object can be saved. If valid returns an empty list. Returns an array
+of translated error message otherwise.
+
+=item C<is_used>
+
+Checks whether or not the project is referenced from any other
+database table. Returns a boolean value.
+
+=item C<is_projectnumber_unique>
+
+Returns trueish if the project number is not used for any other
+project in the database. Also returns trueish if no project number has
+been set yet.
+
+=item C<displayable_name>
+
+Returns a human-readable description of the project, consisting of projectnumber
+and description.
+
+=item C<full_description %params>
+
+Returns a full description for the project which can consist of the
+project number, its description or both. This is determined by the
+parameter C<style> which defaults to C<both>:
+
+=over 2
+
+=item C<both>
+
+Returns the project's number followed by its description in
+parenthesis (e.g. "12345 (Secret Combinations)"). If the project's
+description is already part of the project's number then it will not
+be appended.
+
+=item C<projectnumber> (or simply C<number>)
+
+Returns only the project's number.
+
+=item C<projectdescription> (or simply C<description>)
+
+Returns only the project's description.
+
+=item C<full>
+
+Returns the customer name followed by the project number and project
+description in parenthesis (e.g. "Evil Corp (12345 World
+domination)"). If the project's description is already part of the
+project's number then it will not be appended.
+
+If this project isn't linked to a customer then the style C<both> is
+used instead.
+
+=back
+
+=back
 
 =head1 AUTHOR