From: senator Date: Fri, 12 Mar 2010 20:37:04 +0000 (+0000) Subject: Acq: unified search: big improvement X-Git-Url: https://old-git.evergreen-ils.org/?a=commitdiff_plain;h=11fbfa1cf813eada597150b3e45f9f1f5a19077d;p=evergreen%2Ftadl.git Acq: unified search: big improvement You can now search any field linked to au by string matching name, username, alias, or barcode, not just user ID (which was fairly useless). git-svn-id: svn://svn.open-ils.org/ILS/trunk@15836 dcc99617-32d9-48b4-a31d-7c20da2025e4 --- diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Acq/Search.pm b/Open-ILS/src/perlmods/OpenILS/Application/Acq/Search.pm index 9b275689ad..b76fc47f11 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/Acq/Search.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/Acq/Search.pm @@ -85,34 +85,108 @@ sub prepare_acqlia_search_or { my $result = {"+acqlia" => {"-or" => $point}}; foreach my $unit (@$acqlia) { - while (my ($k, $v) = each %$unit) { - if ($k !~ /^__/) { - if ($unit->{"__fuzzy"} and not ref $v) { - push @$point, { - "-and" => { - "definition" => $k, - "attr_value" => {"ilike" => "%" . $v . "%"} - } - }; - } elsif ($unit->{"__between"} and could_be_range($v)) { - push @$point, { - "-and" => { - "definition" => $k, - "attr_value" => {"between" => $v} - } - }; - } elsif (check_1d_max($v)) { - push @$point, { - "-and" => {"definition" => $k, "attr_value" => $v} - }; + my ($k, $v, $fuzzy, $between) = breakdown_term($unit); + if ($fuzzy and not ref $v) { + push @$point, { + "-and" => { + "definition" => $k, + "attr_value" => {"ilike" => "%" . $v . "%"} + } + }; + } elsif ($between and could_be_range($v)) { + push @$point, { + "-and" => { + "definition" => $k, "attr_value" => {"between" => $v} + } + }; + } elsif (check_1d_max($v)) { + push @$point, { + "-and" => {"definition" => $k, "attr_value" => $v} + }; + } + } + $result; +} + +sub breakdown_term { + my ($term) = @_; + + my $key = (grep { !/^__/ } keys %$term)[0]; + ( + $key, $term->{$key}, + $term->{"__fuzzy"} ? 1 : 0, + $term->{"__between"} ? 1 : 0 + ); +} + +sub get_fm_links_by_hint { + my ($hint) = @_; + foreach my $field (values %{$Fieldmapper::fieldmap}) { + return $field->{"links"} if $field->{"hint"} eq $hint; + } + undef; +} + +sub gen_au_term { + my ($value, $n) = @_; + +{ + "-or" => { + "+au$n" => { + "-or" => { + "usrname" => $value, + "alias" => $value, + "first_given_name" => $value, + "second_given_name" => $value, + "family_name" => $value + } + }, + "+ac$n" => {"barcode" => $value} + } + }; +} + +# go through the terms hash, find keys that correspond to fields links +# to actor.usr, and rewrite the search as one that searches not by +# actor.usr.id but by any of these user properties: card barcode, username, +# alias, given names and family name. +sub prepare_au_terms { + my ($terms, $join_num) = @_; + my @joins = (); + $join_num ||= 0; + + foreach my $conj (qw/-and -or/) { + next unless exists $terms->{$conj}; + + my @new_outer_terms = (); + foreach my $hint_unit (@{$terms->{$conj}}) { + my $hint = (keys %$hint_unit)[0]; + (my $plain_hint = $hint) =~ y/+//d; + + if (my $links = get_fm_links_by_hint($plain_hint) and + $plain_hint ne "acqlia") { + my @new_terms = (); + foreach my $pair (@{$hint_unit->{$hint}}) { + my ($attr, $value) = breakdown_term($pair); + if ($links->{$attr} and + $links->{$attr}->{"class"} eq "au") { + push @joins, [$plain_hint, $attr, $join_num]; + push @new_outer_terms, gen_au_term($value, $join_num); + $join_num++; + } else { + push @new_terms, $pair; + } + } + if (@new_terms) { + $hint_unit->{$hint} = [ @new_terms ]; } else { - next; + delete $hint_unit->{$hint}; } - last; } + push @new_outer_terms, $hint_unit if scalar keys %$hint_unit; } + $terms->{$conj} = [ @new_outer_terms ]; } - $result; + @joins; } sub prepare_terms { @@ -127,16 +201,13 @@ sub prepare_terms { my $clause = []; $outer_clause->{$conj} = [] unless $outer_clause->{$conj}; foreach my $unit (@{$terms->{$class}}) { - while (my ($k, $v) = each %$unit) { - if ($k !~ /^__/) { - if ($unit->{"__fuzzy"} and not ref $v) { - push @$clause, {$k => {"ilike" => "%" . $v . "%"}}; - } elsif ($unit->{"__between"} and could_be_range($v)) { - push @$clause, {$k => {"between" => $v}}; - } elsif (check_1d_max($v)) { - push @$clause, {$k => $v}; - } - } + my ($k, $v, $fuzzy, $between) = breakdown_term($unit); + if ($fuzzy and not ref $v) { + push @$clause, {$k => {"ilike" => "%" . $v . "%"}}; + } elsif ($between and could_be_range($v)) { + push @$clause, {$k => {"between" => $v}}; + } elsif (check_1d_max($v)) { + push @$clause, {$k => $v}; } } push @{$outer_clause->{$conj}}, {"+" . $class => $clause}; @@ -152,9 +223,42 @@ sub prepare_terms { $outer_clause; } +sub add_au_joins { + my ($from) = shift; + + my $n = 0; + foreach my $join (@_) { + my ($hint, $attr, $num) = @$join; + my $start = $hint eq "jub" ? $from->{$hint} : $from->{"jub"}->{$hint}; + my $clause = { + "class" => "au", + "type" => "left", + "field" => "id", + "fkey" => $attr, + "join" => { + "ac$num" => { + "class" => "ac", + "type" => "left", + "field" => "id", + "fkey" => "card" + } + } + }; + if ($hint eq "jub") { + $start->{"au$num"} = $clause; + } else { + $start->{"join"} ||= {}; + $start->{"join"}->{"au$num"} = $clause; + } + $n++; + } + $n; +} + __PACKAGE__->register_method( - method => "grand_search", - api_name => "open-ils.acq.lineitem.grand_search", + method => "unified_search", + api_name => "open-ils.acq.lineitem.unified_search", + stream => 1, signature => { desc => q/Returns lineitems based on flexible search terms./, params => [ @@ -172,38 +276,43 @@ __PACKAGE__->register_method( ); __PACKAGE__->register_method( - method => "grand_search", - api_name => "open-ils.acq.purchase_order.grand_search", + method => "unified_search", + api_name => "open-ils.acq.purchase_order.unified_search", + stream => 1, signature => { desc => q/Returns purchase orders based on flexible search terms. - See open-ils.acq.lineitem.grand_search/, + See open-ils.acq.lineitem.unified_search/, return => {desc => "A stream of POs on success, Event on failure"} } ); __PACKAGE__->register_method( - method => "grand_search", - api_name => "open-ils.acq.picklist.grand_search", + method => "unified_search", + api_name => "open-ils.acq.picklist.unified_search", + stream => 1, signature => { desc => q/Returns pick lists based on flexible search terms. - See open-ils.acq.lineitem.grand_search/, + See open-ils.acq.lineitem.unified_search/, return => {desc => "A stream of PLs on success, Event on failure"} } ); -sub grand_search { +sub unified_search { my ($self, $conn, $auth, $and_terms, $or_terms, $conj, $options) = @_; + $options ||= {}; + my $e = new_editor("authtoken" => $auth); return $e->die_event unless $e->checkauth; # What kind of object are we returning? Important: (\w+) had better be # a legit acq classname particle, so don't register any crazy api_names. - my $ret_type = ($self->api_name =~ /cq.(\w+).gr/)[0]; + my $ret_type = ($self->api_name =~ /cq.(\w+).un/)[0]; my $retriever = $RETRIEVERS{$ret_type}; + my $hint = F("acq::$ret_type")->{"hint"}; my $query = { "select" => { - F("acq::$ret_type")->{"hint"} => + $hint => [{"column" => "id", "transform" => "distinct"}] }, "from" => { @@ -219,9 +328,13 @@ sub grand_search { "fkey" => "picklist" } } - } + }, + "order_by" => { $hint => {"id" => {}}}, + "offset" => ($options->{"offset"} || 0) }; + $query->{"limit"} = $options->{"limit"} if $options->{"limit"}; + $and_terms = prepare_terms($and_terms, 1); $or_terms = prepare_terms($or_terms, 0) and do { $query->{"from"}->{"jub"}->{"acqlia"} = { @@ -229,6 +342,9 @@ sub grand_search { }; }; + my $offset = add_au_joins($query->{"from"}, prepare_au_terms($and_terms)); + add_au_joins($query->{"from"}, prepare_au_terms($or_terms, $offset)); + if ($and_terms and $or_terms) { $query->{"where"} = { "-" . (lc $conj eq "or" ? "or" : "and") => [$and_terms, $or_terms]