ActsAsList: add_to_list() auch aufrufen können, wenn 'position' noch gesetzt ist
[kivitendo-erp.git] / SL / DB / Helper / ActsAsList.pm
index 86ee2ec..8f3333e 100644 (file)
@@ -3,7 +3,8 @@ package SL::DB::Helper::ActsAsList;
 use strict;
 
 use parent qw(Exporter);
-our @EXPORT = qw(move_position_up move_position_down add_to_list remove_from_list reorder_list configure_acts_as_list);
+our @EXPORT = qw(move_position_up move_position_down add_to_list remove_from_list reorder_list configure_acts_as_list
+                 get_previous_in_list get_next_in_list get_full_list);
 
 use Carp;
 
@@ -41,7 +42,7 @@ sub remove_from_list {
   my $worker = sub {
     remove_position($self);
 
-    # Set to NULL manually because $self->update_attributes() would
+    # Set to -1 manually because $self->update_attributes() would
     # trigger the before_save() hook from this very plugin assigning a
     # number at the end of the list again.
     my $table           = $self->meta->table;
@@ -49,7 +50,7 @@ sub remove_from_list {
     my $primary_key_col = ($self->meta->primary_key)[0];
     my $sql             = <<SQL;
       UPDATE ${table}
-      SET ${column} = NULL
+      SET ${column} = -1
       WHERE ${primary_key_col} = ?
 SQL
     $self->db->dbh->do($sql, undef, $self->$primary_key_col);
@@ -64,6 +65,10 @@ sub add_to_list {
 
   croak "Invalid parameter 'position'" unless ($params{position} || '') =~ m/^ (?: before | after | first | last ) $/x;
 
+  my $column = column_name($self);
+
+  $self->remove_from_list if ($self->$column // -1) != -1;
+
   if ($params{position} eq 'last') {
     set_position($self);
     $self->save;
@@ -72,7 +77,6 @@ sub add_to_list {
 
   my $table               = $self->meta->table;
   my $primary_key_col     = ($self->meta->primary_key)[0];
-  my $column              = column_name($self);
   my ($group_by, @values) = get_group_by_where($self);
   $group_by               = " AND ${group_by}" if $group_by;
   my $new_position;
@@ -112,6 +116,26 @@ SQL
   return $self->db->in_transaction ? $worker->() : $self->db->do_transaction($worker);
 }
 
+sub get_next_in_list {
+  my ($self) = @_;
+  return get_previous_or_next($self, 'next');
+}
+
+sub get_previous_in_list {
+  my ($self) = @_;
+  return get_previous_or_next($self, 'previous');
+}
+
+sub get_full_list {
+  my ($self) = @_;
+
+  my $group_by = get_spec(ref $self, 'group_by') || [];
+  $group_by    = [ $group_by ] if $group_by && !ref $group_by;
+  my @where    = map { ($_ => $self->$_) } @{ $group_by };
+
+  return $self->_get_manager_class->get_all(where => \@where, sort_by => column_name($self) . ' ASC');
+}
+
 sub reorder_list {
   my ($class_or_self, @ids) = @_;
 
@@ -165,16 +189,18 @@ sub get_group_by_where {
 sub set_position {
   my ($self) = @_;
   my $column = column_name($self);
+  my $value  = $self->$column;
 
-  return 1 if defined $self->$column;
+  return 1 if defined($value) && ($value != -1);
 
   my $table               = $self->meta->table;
   my ($group_by, @values) = get_group_by_where($self);
-  my $where               = $group_by ? " WHERE ${group_by}" : '';
+  $group_by               = " AND ${group_by}" if $group_by;
   my $sql                 = <<SQL;
     SELECT COALESCE(MAX(${column}), 0)
     FROM ${table}
-    ${where}
+    WHERE (${column} <> -1)
+      ${group_by}
 SQL
 
   my $max_position = $self->db->dbh->selectrow_arrayref($sql, undef, @values)->[0];
@@ -188,10 +214,10 @@ sub remove_position {
   my $column = column_name($self);
 
   $self->load;
-  return 1 unless defined $self->$column;
+  my $value = $self->$column;
+  return 1 unless defined($value) && ($value != -1);
 
   my $table               = $self->meta->table;
-  my $value               = $self->$column;
   my ($group_by, @values) = get_group_by_where($self);
   $group_by               = ' AND ' . $group_by if $group_by;
   my $sql                 = <<SQL;
@@ -208,20 +234,22 @@ SQL
 
 sub do_move {
   my ($self, $direction) = @_;
-  my $column             = column_name($self);
 
   croak "Object has not been saved yet" unless $self->id;
-  croak "No position set yet"           unless defined $self->$column;
+
+  my $column       = column_name($self);
+  my $old_position = $self->$column;
+  croak "No position set yet" unless defined($old_position) && ($old_position != -1);
 
   my $table                                        = $self->meta->table;
-  my $old_position                                 = $self->$column;
   my ($comp_sel, $comp_upd, $min_max, $plus_minus) = $direction eq 'up' ? ('<', '>=', 'MAX', '+') : ('>', '<=', 'MIN', '-');
   my ($group_by, @values)                          = get_group_by_where($self);
   $group_by                                        = ' AND ' . $group_by if $group_by;
   my $sql                                          = <<SQL;
     SELECT ${min_max}(${column})
     FROM ${table}
-    WHERE (${column} ${comp_sel} ?)
+    WHERE (${column} <>          -1)
+      AND (${column} ${comp_sel} ?)
       ${group_by}
 SQL
 
@@ -241,6 +269,30 @@ SQL
   $self->update_attributes($column => $new_position);
 }
 
+sub get_previous_or_next {
+  my ($self, $direction)  = @_;
+
+  my $asc_desc            = $direction eq 'next' ? 'ASC' : 'DESC';
+  my $comparator          = $direction eq 'next' ? '>'   : '<';
+  my $table               = $self->meta->table;
+  my $column              = column_name($self);
+  my $primary_key_col     = ($self->meta->primary_key)[0];
+  my ($group_by, @values) = get_group_by_where($self);
+  $group_by               = " AND ${group_by}" if $group_by;
+  my $sql                 = <<SQL;
+    SELECT ${primary_key_col}
+    FROM ${table}
+    WHERE (${column} ${comparator} ?)
+      ${group_by}
+    ORDER BY ${column} ${asc_desc}
+    LIMIT 1
+SQL
+
+  my $id = ($self->db->dbh->selectrow_arrayref($sql, undef, $self->$column, @values) || [])->[0];
+
+  return $id ? $self->_get_manager_class->find_by(id => $id) : undef;
+}
+
 sub column_name {
   my ($self) = @_;
   my $column = get_spec(ref $self, 'column_name');
@@ -360,14 +412,33 @@ one. The current item will then be inserted either before or after the
 referenced item by shifting all the appropriate item positions up by
 one.
 
+If C<$self>'s positional column is already set when this function is
+called then L</remove_from_list> will be called first before anything
+else is done.
+
 After this function C<$self>'s positional column has been set and
 saved to the database.
 
 =item C<remove_from_list>
 
-Sets this items positional column to C<undef>, saves it and moves all
+Sets this items positional column to C<-1>, saves it and moves all
 following items up by 1.
 
+=item C<get_previous_in_list>
+
+Fetches the previous item in the list. Returns C<undef> if C<$self> is
+already the first one.
+
+=item C<get_next_in_list>
+
+Fetches the next item in the list. Returns C<undef> if C<$self> is
+already the last one.
+
+=item C<get_full_list>
+
+Fetches all items in the same list as C<$self> and returns them as an
+array reference.
+
 =item C<reorder_list @ids>
 
 Re-orders the objects given in C<@ids> by their position in C<@ids> by