From bdc135fc47bfe0ca6fac69510c7f4ed83d86f419 Mon Sep 17 00:00:00 2001 From: senator Date: Wed, 3 Mar 2010 19:18:55 +0000 Subject: [PATCH] Acq: middle-layer support for unified lineitem/purchase order/picklist search git-svn-id: svn://svn.open-ils.org/ILS/trunk@15679 dcc99617-32d9-48b4-a31d-7c20da2025e4 --- Open-ILS/src/perlmods/OpenILS/Application/Acq.pm | 1 + .../perlmods/OpenILS/Application/Acq/Lineitem.pm | 20 +- .../perlmods/OpenILS/Application/Acq/Picklist.pm | 7 + .../src/perlmods/OpenILS/Application/Acq/Search.pm | 251 +++++++++++++++++++++ 4 files changed, 275 insertions(+), 4 deletions(-) create mode 100644 Open-ILS/src/perlmods/OpenILS/Application/Acq/Search.pm diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Acq.pm b/Open-ILS/src/perlmods/OpenILS/Application/Acq.pm index e1da288a24..0e89d8c418 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/Acq.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/Acq.pm @@ -8,5 +8,6 @@ use OpenILS::Application::Acq::Provider; use OpenILS::Application::Acq::Lineitem; use OpenILS::Application::Acq::Order; use OpenILS::Application::Acq::EDI; +use OpenILS::Application::Acq::Search; 1; diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Acq/Lineitem.pm b/Open-ILS/src/perlmods/OpenILS/Application/Acq/Lineitem.pm index acb9d65cc1..6ebc9f327e 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/Acq/Lineitem.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/Acq/Lineitem.pm @@ -84,12 +84,15 @@ sub retrieve_lineitem { my($self, $conn, $auth, $li_id, $options) = @_; my $e = new_editor(authtoken=>$auth); return $e->die_event unless $e->checkauth; + return retrieve_lineitem_impl($e, $li_id, $options); +} + +sub retrieve_lineitem_impl { + my ($e, $li_id, $options) = @_; $options ||= {}; # XXX finer grained perms... - my $li; - my $flesh = {}; if($$options{flesh_attrs} or $$options{flesh_notes}) { $flesh = {flesh => 2, flesh_fields => {jub => []}}; @@ -100,7 +103,7 @@ sub retrieve_lineitem { push(@{$flesh->{flesh_fields}->{jub}}, 'attributes') if $$options{flesh_attrs}; } - $li = $e->retrieve_acq_lineitem([$li_id, $flesh]); + my $li = $e->retrieve_acq_lineitem([$li_id, $flesh]); if($$options{flesh_li_details}) { my $ops = { @@ -117,7 +120,16 @@ sub retrieve_lineitem { $li->item_count(scalar(@$details)); } - if($li->picklist) { + if($li->purchase_order) { + my $purchase_order = + $e->retrieve_acq_purchase_order($li->purchase_order) + or return $e->event; + + if($purchase_order->owner != $e->requestor->id) { + return $e->event unless + $e->allowed('VIEW_PURCHASE_ORDER', undef, $purchase_order); + } + } elsif($li->picklist) { my $picklist = $e->retrieve_acq_picklist($li->picklist) or return $e->event; diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Acq/Picklist.pm b/Open-ILS/src/perlmods/OpenILS/Application/Acq/Picklist.pm index 3004982bb5..7de39369e5 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/Acq/Picklist.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/Acq/Picklist.pm @@ -104,6 +104,13 @@ sub retrieve_picklist { my $e = new_editor(authtoken=>$auth); return $e->event unless $e->checkauth; + return retrieve_picklist_impl($e, $picklist_id, $options); +} + +sub retrieve_picklist_impl { + my ($e, $picklist_id, $options) = @_; + $options ||= {}; + my $picklist = $e->retrieve_acq_picklist($picklist_id) or return $e->event; diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Acq/Search.pm b/Open-ILS/src/perlmods/OpenILS/Application/Acq/Search.pm new file mode 100644 index 0000000000..9b275689ad --- /dev/null +++ b/Open-ILS/src/perlmods/OpenILS/Application/Acq/Search.pm @@ -0,0 +1,251 @@ +package OpenILS::Application::Acq::Search; +use base "OpenILS::Application"; + +use strict; +use warnings; + +use OpenILS::Event; +use OpenILS::Utils::CStoreEditor q/:funcs/; +use OpenILS::Utils::Fieldmapper; +use OpenILS::Application::Acq::Lineitem; +use OpenILS::Application::Acq::Financials; +use OpenILS::Application::Acq::Picklist; + +my %RETRIEVERS = ( + "lineitem" => + \&{"OpenILS::Application::Acq::Lineitem::retrieve_lineitem_impl"}, + "picklist" => + \&{"OpenILS::Application::Acq::Picklist::retrieve_picklist_impl"}, + "purchase_order" => \&{ + "OpenILS::Application::Acq::Financials::retrieve_purchase_order_impl" + } +); + +sub F { $Fieldmapper::fieldmap->{"Fieldmapper::" . $_[0]}; } + +# This subroutine returns 1 if the argument is a) a scalar OR +# b) an array of ONLY scalars. Otherwise it returns 0. +sub check_1d_max { + my ($o) = @_; + return 1 unless ref $o; + if (ref($o) eq "ARRAY") { + foreach (@$o) { return 0 if ref $_; } + return 1; + } + 0; +} + +# Returns 1 if and only if argument is an array of exactly two scalars. +sub could_be_range { + my ($o) = @_; + if (ref $o eq "ARRAY") { + return 1 if (scalar(@$o) == 2 && (!ref $o->[0] && !ref $o->[1])); + } + 0; +} + +sub prepare_acqlia_search_and { + my ($acqlia) = @_; + + my @phrases = (); + foreach my $unit (@{$acqlia}) { + my $something = 0; + my $subquery = { + "select" => {"acqlia" => ["id"]}, + "from" => "acqlia", + "where" => {"-and" => [{"lineitem" => {"=" => {"+jub" => "id"}}}]} + }; + + while (my ($k, $v) = each %$unit) { + my $point = $subquery->{"where"}->{"-and"}; + if ($k !~ /^__/) { + push @$point, {"definition" => $k}; + $something++; + + if ($unit->{"__fuzzy"} and not ref $v) { + push @$point, {"attr_value" => {"ilike" => "%" . $v . "%"}}; + } elsif ($unit->{"__between"} and could_be_range($v)) { + push @$point, {"attr_value" => {"between" => $v}}; + } elsif (check_1d_max($v)) { + push @$point, {"attr_value" => $v}; + } else { + $something--; + } + } + } + push @phrases, {"-exists" => $subquery} if $something; + } + @phrases; +} + +sub prepare_acqlia_search_or { + my ($acqlia) = @_; + + my $point = []; + 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} + }; + } else { + next; + } + last; + } + } + } + $result; +} + +sub prepare_terms { + my ($terms, $is_and) = @_; + + my $conj = $is_and ? "-and" : "-or"; + my $outer_clause = {}; + + foreach my $class (qw/acqpo acqpl jub/) { + next if not exists $terms->{$class}; + + 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}; + } + } + } + } + push @{$outer_clause->{$conj}}, {"+" . $class => $clause}; + } + + if ($terms->{"acqlia"}) { + push @{$outer_clause->{$conj}}, + $is_and ? prepare_acqlia_search_and($terms->{"acqlia"}) : + prepare_acqlia_search_or($terms->{"acqlia"}); + } + + return undef unless scalar keys %$outer_clause; + $outer_clause; +} + +__PACKAGE__->register_method( + method => "grand_search", + api_name => "open-ils.acq.lineitem.grand_search", + signature => { + desc => q/Returns lineitems based on flexible search terms./, + params => [ + {desc => "Authentication token", type => "string"}, + {desc => "Field/value pairs for AND'ing", type => "object"}, + {desc => "Field/value pairs for OR'ing", type => "object"}, + {desc => "Conjunction between AND pairs and OR pairs " . + "(can be 'and' or 'or')", type => "string"}, + {desc => "Retrieval options (clear_marc, flesh_notes, etc) " . + "- XXX detail all the options", + type => "object"} + ], + return => {desc => "A stream of LIs on success, Event on failure"} + } +); + +__PACKAGE__->register_method( + method => "grand_search", + api_name => "open-ils.acq.purchase_order.grand_search", + signature => { + desc => q/Returns purchase orders based on flexible search terms. + See open-ils.acq.lineitem.grand_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", + signature => { + desc => q/Returns pick lists based on flexible search terms. + See open-ils.acq.lineitem.grand_search/, + return => {desc => "A stream of PLs on success, Event on failure"} + } +); + +sub grand_search { + my ($self, $conn, $auth, $and_terms, $or_terms, $conj, $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 $retriever = $RETRIEVERS{$ret_type}; + + my $query = { + "select" => { + F("acq::$ret_type")->{"hint"} => + [{"column" => "id", "transform" => "distinct"}] + }, + "from" => { + "jub" => { + "acqpo" => { + "type" => "full", + "field" => "id", + "fkey" => "purchase_order" + }, + "acqpl" => { + "type" => "full", + "field" => "id", + "fkey" => "picklist" + } + } + } + }; + + $and_terms = prepare_terms($and_terms, 1); + $or_terms = prepare_terms($or_terms, 0) and do { + $query->{"from"}->{"jub"}->{"acqlia"} = { + "type" => "left", "field" => "lineitem", "fkey" => "id", + }; + }; + + if ($and_terms and $or_terms) { + $query->{"where"} = { + "-" . (lc $conj eq "or" ? "or" : "and") => [$and_terms, $or_terms] + }; + } elsif ($and_terms) { + $query->{"where"} = $and_terms; + } elsif ($or_terms) { + $query->{"where"} = $or_terms; + } else { + $e->disconnect; + return new OpenILS::Event("BAD_PARAMS", "desc" => "No usable terms"); + } + + my $results = $e->json_query($query) or return $e->die_event; + $conn->respond($retriever->($e, $_->{"id"}, $options)) foreach (@$results); + $e->disconnect; + undef; +} + +1; -- 2.11.0