X-Git-Url: http://wagnertech.de/git?a=blobdiff_plain;f=SL%2FDB%2FHelper%2FActsAsList.pm;h=abe11c06338ccf15c8f989b1e8832dc0c97c7115;hb=0ebb8f829ac2d23b65e8d993c7d03aa0b172b637;hp=5734c2e6da2a693d3ea871c3de69ccb5ca3550aa;hpb=0fc82c30071666a29a96a706119dff8609e16ef1;p=kivitendo-erp.git diff --git a/SL/DB/Helper/ActsAsList.pm b/SL/DB/Helper/ActsAsList.pm index 5734c2e6d..abe11c063 100644 --- a/SL/DB/Helper/ActsAsList.pm +++ b/SL/DB/Helper/ActsAsList.pm @@ -3,9 +3,11 @@ package SL::DB::Helper::ActsAsList; use strict; use parent qw(Exporter); -our @EXPORT = qw(move_position_up move_position_down 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; +use SL::X; my %list_spec; @@ -13,12 +15,13 @@ sub import { my ($class, @params) = @_; my $importing = caller(); + configure_acts_as_list($importing, @params); + $importing->before_save( sub { SL::DB::Helper::ActsAsList::set_position(@_) }); $importing->before_delete(sub { SL::DB::Helper::ActsAsList::remove_position(@_) }); - # Use 'goto' so that Exporter knows which module to import into via - # 'caller()'. - goto &Exporter::import; + # Don't 'goto' to Exporters import, it would try to parse @params + __PACKAGE__->export_to_level(1, $class, @EXPORT); } # @@ -35,6 +38,102 @@ sub move_position_down { do_move($self, 'down'); } +sub remove_from_list { + my ($self) = @_; + + return $self->db->with_transaction(sub { + remove_position($self); + + # 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; + my $column = column_name($self); + my $primary_key_col = ($self->meta->primary_key)[0]; + my $sql = <db->dbh->do($sql, undef, $self->$primary_key_col); + $self->$column(undef); + }); +} + +sub add_to_list { + my ($self, %params) = @_; + + 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; + return; + } + + my $table = $self->meta->table; + 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 $new_position; + + if ($params{position} eq 'first') { + $new_position = 1; + + } else { + # Can only be 'before' or 'after' -- 'last' has been checked above + # already. + + my $reference = $params{reference}; + croak "Missing parameter 'reference'" if !$reference; + + my $reference_pos; + if (ref $reference) { + $reference_pos = $reference->$column; + } else { + ($reference_pos) = $self->db->dbh->selectrow_array(qq|SELECT ${column} FROM ${table} WHERE ${primary_key_col} = ?|, undef, $reference); + } + + $new_position = $params{position} eq 'before' ? $reference_pos : $reference_pos + 1; + } + + my $query = < ?) + ${group_by} +SQL + + return $self->db->with_transaction(sub { + $self->db->dbh->do($query, undef, $new_position - 1, @values); + $self->update_attributes($column => $new_position); + }); +} + +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) = @_; @@ -42,15 +141,17 @@ sub reorder_list { my $self = ref($class_or_self) ? $class_or_self : $class_or_self->new; my $column = column_name($self); - my $result = $self->db->do_transaction(sub { + my $result = $self->db->with_transaction(sub { my $query = qq|UPDATE | . $self->meta->table . qq| SET ${column} = ? WHERE id = ?|; my $sth = $self->db->dbh->prepare($query) || die $self->db->dbh->errstr; foreach my $new_position (1 .. scalar(@ids)) { - $sth->execute($new_position, $ids[$new_position - 1]) || die $sth->errstr; + $sth->execute($new_position, $ids[$new_position - 1]) || die SL::X::DBUtilsError->new(error => $sth->errstr); } $sth->finish; + + 1; }); return $result; @@ -75,27 +176,34 @@ sub get_group_by_where { my $group_by = get_spec(ref $self, 'group_by') || []; $group_by = [ $group_by ] if $group_by && !ref $group_by; - my @where = map { my $value = $self->$_; defined($value) ? "(${_} = " . $value . ")" : "(${_} IS NULL)" } @{ $group_by }; + my (@where, @values); + foreach my $column (@{ $group_by }) { + my $value = $self->$column; + push @values, $value if defined $value; + push @where, defined($value) ? "(${column} = ?)" : "(${column} IS NULL)"; + } - return join ' AND ', @where; + return (join(' AND ', @where), @values); } 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 $where = get_group_by_where($self); - $where = " WHERE ${where}" if $where; - my $sql = <meta->table; + my ($group_by, @values) = get_group_by_where($self); + $group_by = " AND ${group_by}" if $group_by; + my $sql = < -1) + ${group_by} SQL - my $max_position = $self->db->dbh->selectrow_arrayref($sql)->[0]; + my $max_position = $self->db->dbh->selectrow_arrayref($sql, undef, @values)->[0]; $self->$column($max_position + 1); return 1; @@ -106,58 +214,85 @@ 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 = get_group_by_where($self); - $group_by = ' AND ' . $group_by if $group_by; - my $sql = <meta->table; + my ($group_by, @values) = get_group_by_where($self); + $group_by = ' AND ' . $group_by if $group_by; + my $sql = < ${value}) ${group_by} + WHERE (${column} > ?) + ${group_by} SQL - $self->db->dbh->do($sql); + $self->db->dbh->do($sql, undef, $value, @values); return 1; } 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 = get_group_by_where($self); + 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 = < -1) + AND (${column} ${comp_sel} ?) ${group_by} SQL - my $new_position = $self->db->dbh->selectrow_arrayref($sql)->[0]; + my $new_position = $self->db->dbh->selectrow_arrayref($sql, undef, $old_position, @values)->[0]; return undef unless defined $new_position; $sql = <db->dbh->do($sql); + $self->db->dbh->do($sql, undef, $old_position, $new_position, @values); $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 = <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'); @@ -187,7 +322,7 @@ column =head1 SYNOPSIS package SL::DB::SomeObject; - use SL::DB::Helper::ActsAsList; + use SL::DB::Helper::ActsAsList [ PARAMS ]; package SL::Controller::SomeController; ... @@ -212,7 +347,8 @@ in the table plus one. When the object is deleted all positions greater than the object's old position are decreased by one. -The column name to use can be configured via L. +C will be given to L and can be used to +set the column name. =head1 CLASS FUNCTIONS @@ -220,8 +356,8 @@ The column name to use can be configured via L. =item C -Configures the mixin's behaviour. C<%params> can contain the following -values: +Configures the mixin's behaviour. Will get called automatically with the +include parameters. C<%params> can contain the following values: =over 2 @@ -263,6 +399,47 @@ regarding their sort order by exchanging their C values. Swaps the object with the object one step below the current one regarding their sort order by exchanging their C values. +=item C + +Adds this item to the list. The parameter C is required and +can be one of C, C, C and C. With C +the item is inserted as the first item in the list and all other +item's positions are shifted up by one. For C the +item is inserted at the end of the list. + +For C and C an additional parameter C is +required. This is either a Rose model instance or the primary key of +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 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 + +Sets this items positional column to C<-1>, saves it and moves all +following items up by 1. + +=item C + +Fetches the previous item in the list. Returns C if C<$self> is +already the first one. + +=item C + +Fetches the next item in the list. Returns C if C<$self> is +already the last one. + +=item C + +Fetches all items in the same list as C<$self> and returns them as an +array reference. + =item C Re-orders the objects given in C<@ids> by their position in C<@ids> by