X-Git-Url: http://wagnertech.de/git?a=blobdiff_plain;f=SL%2FDB%2FHelper%2FLinkedRecords.pm;h=3fb594848164cf17c2c55bb63966a327bbd56a03;hb=a341d959ab798890746200efd0d5983c66f67db7;hp=6dad81f2ea2a1d89436e90555343666a079705dc;hpb=eb0d6c3aa89cac579f3e65ac1390105f2553cf8d;p=kivitendo-erp.git diff --git a/SL/DB/Helper/LinkedRecords.pm b/SL/DB/Helper/LinkedRecords.pm index 6dad81f2e..3fb594848 100644 --- a/SL/DB/Helper/LinkedRecords.pm +++ b/SL/DB/Helper/LinkedRecords.pm @@ -19,14 +19,14 @@ sub linked_records { dir => delete($params{sort_dir}) ); my $filter = delete $params{filter}; - my $records = linked_records_implementation($self, %params); + my $records = _linked_records_implementation($self, %params); $records = filter_linked_records($self, $filter, @{ $records }) if $filter; $records = sort_linked_records($self, $sort_spec{by}, $sort_spec{dir}, @{ $records }) if $sort_spec{by}; return $records; } -sub linked_records_implementation { +sub _linked_records_implementation { my $self = shift; my %params = @_; @@ -37,43 +37,74 @@ sub linked_records_implementation { my %from_to = ( from => delete($params{from}) || $both, to => delete($params{to}) || $both); - my @records = (@{ linked_records_implementation($self, %params, direction => 'from', from => $from_to{from}) }, - @{ linked_records_implementation($self, %params, direction => 'to', to => $from_to{to} ) }); + my @records = (@{ _linked_records_implementation($self, %params, direction => 'from', from => $from_to{from}) }, + @{ _linked_records_implementation($self, %params, direction => 'to', to => $from_to{to} ) }); my %record_map = map { ( ref($_) . $_->id => $_ ) } @records; return [ values %record_map ]; } - my $myself = $wanted eq 'from' ? 'to' : $wanted eq 'to' ? 'from' : croak("Invalid parameter `direction'"); + if ($params{via}) { + croak("Cannot use 'via' without '${wanted}_table'") if !$params{$wanted}; + croak("Cannot use 'via' with '${wanted}_table' being an array") if ref $params{$wanted}; + } - my $my_table = SL::DB::Helper::Mappings::get_table_for_package(ref($self)); + my $myself = $wanted eq 'from' ? 'to' : $wanted eq 'to' ? 'from' : croak("Invalid parameter `direction'"); + my $my_table = SL::DB::Helper::Mappings::get_table_for_package(ref($self)); - my @query = ( "${myself}_table" => $my_table, - "${myself}_id" => $self->id ); + my $sub_wanted_table = "${wanted}_table"; + my $sub_wanted_id = "${wanted}_id"; + my ($wanted_classes, $wanted_tables); if ($params{$wanted}) { - my $wanted_classes = ref($params{$wanted}) eq 'ARRAY' ? $params{$wanted} : [ $params{$wanted} ]; - my $wanted_tables = [ map { SL::DB::Helper::Mappings::get_table_for_package($_) || croak("Invalid parameter `${wanted}'") } @{ $wanted_classes } ]; - push @query, ("${wanted}_table" => $wanted_tables); + $wanted_classes = ref($params{$wanted}) eq 'ARRAY' ? $params{$wanted} : [ $params{$wanted} ]; + $wanted_tables = [ map { SL::DB::Helper::Mappings::get_table_for_package($_) || croak("Invalid parameter `${wanted}'") } @{ $wanted_classes } ]; } - my $links = SL::DB::Manager::RecordLink->get_all(query => [ and => \@query ]); + my @get_objects_query = ref($params{query}) eq 'ARRAY' ? @{ $params{query} } : (); + my $get_objects = sub { + my $manager_class = SL::DB::Helper::Mappings::get_manager_package_for_table($_[0]->$sub_wanted_table); + my $object_class = SL::DB::Helper::Mappings::get_package_for_table($_[0]->$sub_wanted_table); + eval "require " . $object_class . "; 1;"; + return @{ $manager_class->get_all(query => [ id => $_[0]->$sub_wanted_id, @get_objects_query ]) }; + }; - my $sub_wanted_table = "${wanted}_table"; - my $sub_wanted_id = "${wanted}_id"; + # If no 'via' is given then use a simple(r) method for querying the wanted objects. + if (!$params{via}) { + my @query = ( "${myself}_table" => $my_table, + "${myself}_id" => $self->id ); + push @query, ( "${wanted}_table" => $wanted_tables ) if $wanted_tables; - my $records = []; - @query = ref($params{query}) eq 'ARRAY' ? @{ $params{query} } : (); + return [ map { $get_objects->($_) } @{ SL::DB::Manager::RecordLink->get_all(query => [ and => \@query ]) } ]; + } - foreach my $link (@{ $links }) { - my $manager_class = SL::DB::Helper::Mappings::get_manager_package_for_table($link->$sub_wanted_table); - my $object_class = SL::DB::Helper::Mappings::get_package_for_table($link->$sub_wanted_table); - eval "require " . $object_class . "; 1;"; - push @{ $records }, @{ $manager_class->get_all(query => [ id => $link->$sub_wanted_id, @query ]) }; + # More complex handling for the 'via' case. + my @sources = ( $self ); + my @targets = map { SL::DB::Helper::Mappings::get_table_for_package($_) } @{ ref($params{via}) ? $params{via} : [ $params{via} ] }; + push @targets, @{ $wanted_tables } if $wanted_tables; + + my %seen = map { ($_->meta->table . $_->id => 1) } @sources; + + while (@targets) { + my @new_sources = @sources; + foreach my $src (@sources) { + my @query = ( "${myself}_table" => $src->meta->table, + "${myself}_id" => $src->id, + "${wanted}_table" => \@targets ); + push @new_sources, + map { $get_objects->($_) } + grep { !$seen{$_->$sub_wanted_table . $_->$sub_wanted_id} } + @{ SL::DB::Manager::RecordLink->get_all(query => [ and => \@query ]) }; + } + + @sources = @new_sources; + %seen = map { ($_->meta->table . $_->id => 1) } @sources; + shift @targets; } - return $records; + my %wanted_tables_map = map { ($_ => 1) } @{ $wanted_tables }; + return [ grep { $wanted_tables_map{$_->meta->table} } @sources ]; } sub link_to_record { @@ -210,23 +241,41 @@ from C<$self> (for C = C). For C all records linked from or to C<$self> are returned. The optional parameter C or C (same as C) -contains the package names of Rose models for table limitation. It can -be a single model name as a single scalar or multiple model names in -an array reference in which case all links matching any of the model -names will be returned. +contains the package names of Rose models for table limitation (the +prefix C is optional). It can be a single model name as a +single scalar or multiple model names in an array reference in which +case all links matching any of the model names will be returned. + +The optional parameter C can be used to retrieve all documents +that may have intermediate documents inbetween. It is an array +reference of Rose package names for the models that may be +intermediate link targets. One example is retrieving all invoices for +a given quotation no matter whether or not orders and delivery orders +have been created. If C is given then C or C (depending +on C) must be given as well, and it must then not be an +array reference. -If you only need invoices created from an order C<$order> then the -call could look like this: +Examples: + +If you only need invoices created directly from an order C<$order> (no +delivery orders inbetween) then the call could look like this: my $invoices = $order->linked_records(direction => 'to', - to => 'SL::DB::Invoice'); + to => 'Invoice'); + +Retrieving all invoices from a quotation no matter whether or not +orders or delivery orders where created: + + my $invoices = $quotation->linked_records(direction => 'to', + to => 'Invoice', + via => [ 'Order', 'DeliveryOrder' ]); The optional parameter C can be used to limit the records returned. The following call limits the earlier example to invoices created today: my $invoices = $order->linked_records(direction => 'to', - to => 'SL::DB::Invoice', + to => 'Invoice', query => [ transdate => DateTime->today_local ]); The optional parameters C<$params{sort_by}> and C<$params{sort_dir}>