From: Jason Etheridge Date: Mon, 12 Mar 2018 22:02:47 +0000 (-0400) Subject: lp1774277 Improvements to Patron Acquisition Request X-Git-Url: https://old-git.evergreen-ils.org/?a=commitdiff_plain;h=e9a9875da27e85e8437c082b6e40ad39ff9a1ba6;p=evergreen%2Ftadl.git lp1774277 Improvements to Patron Acquisition Request Squashed and rebased against master, this is an Angular reimplementation of the Patron Acquisition Request user interface with some improvements. It still reaches into the Dojo-based Acquisition interfaces. Signed-off-by: Jason Etheridge toward acq requests Signed-off-by: Jason Etheridge 4-status-not-updating-to-recieved-unless-all-items-in-order-are-recieved Change to acq patron request status logic, which now looks like this: If a cancel_reason is set on the patron request, then status = "Canceled" If there is an associated hold request that has fulfillment_time set, then status = 'Fulfilled" If there is an associated lineitem has a state of "received", then status = "Received" If there is an associated purchase order with a state of "on-order" and an associated hold request, then status = "Ordered, Hold Placed" If there is an associated purchase order with a state of "on-order" but no associated hold request (created through the automated process), then status = "Ordered, Hold Not Placed" If there is an associated lineitem (selection list), then status = "Pending" If there is no associated lineitem, then status = "New" Any other condition, which should be impossible (I should never say that), will give a status of "Error" Signed-off-by: Jason Etheridge 6-upc-not-on-patron-request-form Adds a UPC column to the patron acq request table Signed-off-by: Jason Etheridge 2-hold-request-fields-that-make-use-of-user-preferences For new requests (or edited requests when a user barcode is scanned), the user's preferences (if any) for hold notifications and pickup library will be used to set various fields in the request dialog. Signed-off-by: Jason Etheridge 5-pick-up-library-not-defaulting-to-patrons-home-library when creating new requests, given a user, default to the user's pickup library preference setting, or absent a preference, default to their home library. Absent a user, default to the pickup library selector value from the request list, if it's of an org type that can have volumes. Otherwise, default to the workstation library. Technically, the without-a-user behavior is going to be mooted whenever a user is chosen. Signed-off-by: Jason Etheridge 5-pick-up-library-not-defaulting-to-patrons-home-library Fix defaulting to patron home library in absense of user setting when creating acq patron request from user context Signed-off-by: Jason Etheridge misc fixes to the IDL and for the email_notify checkbox. some refactoring to avoid using foreign fields in the request object Signed-off-by: Jason Etheridge 7-retrieve-patron-fails-to-load-patron-record give the user_request.view permission some parity with VIEW_USER And some defensive programming if trying to create a request in the user already known context without adequate permission Signed-off-by: Jason Etheridge handle undefined values for email/hold checkboxes Signed-off-by: Jason Etheridge remove acq.holds.allow_holds_from_purchase_request This was added a long time ago but never actually used by the code. Signed-off-by: Jason Etheridge match pcrud perm for aur with aurs Signed-off-by: Jason Etheridge live_t/ test Signed-off-by: Jason Etheridge Signed-off-by: Bill Erickson --- diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml index 300c910f51..4a10387015 100644 --- a/Open-ILS/examples/fm_IDL.xml +++ b/Open-ILS/examples/fm_IDL.xml @@ -2332,7 +2332,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA - + @@ -2343,6 +2343,13 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + + + + + + + @@ -3691,7 +3698,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA - + @@ -6279,6 +6286,7 @@ SELECT usr, + @@ -6297,6 +6305,7 @@ SELECT usr, + @@ -6426,6 +6435,7 @@ SELECT usr, + @@ -6459,6 +6469,7 @@ SELECT usr, + @@ -6510,6 +6521,7 @@ SELECT usr, + @@ -6527,6 +6539,7 @@ SELECT usr, + @@ -6850,7 +6863,7 @@ SELECT usr, - + @@ -8378,6 +8391,7 @@ SELECT usr, + @@ -8389,6 +8403,7 @@ SELECT usr, + @@ -8416,6 +8431,88 @@ SELECT usr, + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Acq/Order.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Acq/Order.pm index 42fbebb918..feacb2f1e1 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Acq/Order.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Acq/Order.pm @@ -265,6 +265,17 @@ sub promote_lineitem_holds { next unless ($U->is_true( $request->hold )); + my $existing_hold = $mgr->editor->search_action_hold_request( + {acq_request => $request->id})->[0]; + if ($existing_hold) { + $logger->warn("Existing hold found where acq_request = $request->id"); + next; + } + if (! $li->eg_bib_id) { + $logger->error("Hold creation attempt for aur $request->id where li.eg_bib_id is null"); + next; + } + my $hold = Fieldmapper::action::hold_request->new; $hold->usr( $request->usr ); $hold->requestor( $request->usr ); @@ -275,6 +286,7 @@ sub promote_lineitem_holds { $hold->phone_notify( $request->phone_notify ); $hold->email_notify( $request->email_notify ); $hold->expire_time( $request->need_before ); + $hold->acq_request( $request->id ); if ($request->holdable_formats) { my $mrm = $mgr->editor->search_metabib_metarecord_source_map( { source => $li->eg_bib_id } )->[0]; @@ -3605,6 +3617,21 @@ __PACKAGE__->register_method ( } } ); +__PACKAGE__->register_method ( + method => 'update_user_request', + api_name => 'open-ils.acq.user_request.set_yes_hold.batch', + stream => 1, + signature => { + desc => 'Set hold to true for a user request or set of requests', + params => [ + { desc => 'Authentication token', type => 'string' }, + { desc => 'ID or array of IDs for the user requests to modify' } + ], + return => { + desc => 'progress object, event on error', + } + } +); sub update_user_request { my($self, $conn, $auth, $aur_ids, $cancel_reason) = @_; @@ -3637,7 +3664,14 @@ sub update_user_request { if($self->api_name =~ /set_no_hold/) { if ($U->is_true($aur_obj->hold)) { - $aur_obj->hold(0); + $aur_obj->hold(0); # FIXME - this is not really removing holds per the description + $e->update_acq_user_request($aur_obj) or return $e->die_event; + } + } + + if($self->api_name =~ /set_yes_hold/) { + if (!$U->is_true($aur_obj->hold)) { + $aur_obj->hold(1); $e->update_acq_user_request($aur_obj) or return $e->die_event; } } @@ -3645,6 +3679,7 @@ sub update_user_request { if($self->api_name =~ /cancel/) { if ( $cancel_reason ) { $aur_obj->cancel_reason( $cancel_reason ); + $aur_obj->cancel_time( 'now' ); $e->update_acq_user_request($aur_obj) or return $e->die_event; create_user_request_events( $e, [ $aur_obj ], 'aur.rejected' ); } else { @@ -3660,6 +3695,105 @@ sub update_user_request { } __PACKAGE__->register_method ( + method => 'clear_completed_user_requests', + api_name => 'open-ils.acq.clear_completed_user_requests', + stream => 1, + signature => { + desc => q/ + Auto-cancel the specified user requests if they are complete. + Completed is defined as having either a Request Status of Fulfilled + (which happens when the request is not Canceled and has an associated + hold request that has a fulfillment time), or having a Request Status + of Received (which happens when the request status is not Canceled or + Fulfilled and has an associated Purchase Order with a State of + Received) and a Place Hold value of False. + /, + params => [ + { desc => 'Authentication token', type => 'string' }, + { desc => 'ID for home library of user requests to auto-cancel.' } + ], + return => { + desc => 'progress object, event on error', + } + } +); + +sub clear_completed_user_requests { + my($self, $conn, $auth, $potential_aur_ids) = @_; + my $e = new_editor(xact => 1, authtoken => $auth); + return $e->die_event unless $e->checkauth; + my $rid = $e->requestor->id; + + my $potential_requests = $e->search_acq_user_request_status({ + id => $potential_aur_ids + ,'-or' => [ + { request_status => 6 }, # Fulfilled + { '-and' => [ { request_status => 5 }, { hold => 'f' } ] } # Received + ] + } + ); + my $aur_ids = []; + + my %perm_test = (); my %perm_test2 = (); + for my $request (@$potential_requests) { + if ($rid != $request->usr()) { + if (!defined $perm_test{ $request->home_ou() }) { + $perm_test{ $request->home_ou() } = + $e->allowed( ['user_request.view'], $request->home_ou() ); + } + if (!defined $perm_test2{ $request->home_ou() }) { + $perm_test2{ $request->home_ou() } = + $e->allowed( ['CLEAR_PURCHASE_REQUEST'], $request->home_ou() ); + } + if (!$perm_test{ $request->home_ou() }) { + next; # failed test + } + if (!$perm_test2{ $request->home_ou() }) { + next; # failed test + } + } + push @$aur_ids, $request->id(); + } + + my $x = 1; + my %perm_test3 = (); + for my $id (@$aur_ids) { + + my $aur_obj = $e->retrieve_acq_user_request([ + $id, + { flesh => 1, + flesh_fields => { "aur" => ['lineitem', 'usr'] } + } + ]) or return $e->die_event; + + my $context_org = $aur_obj->usr()->home_ou(); + $aur_obj->usr( $aur_obj->usr()->id() ); + + if ($rid != $aur_obj->usr) { + if (!defined $perm_test3{ $context_org }) { + $perm_test3{ $context_org } = $e->allowed( ['user_request.update'], $context_org ); + } + if (!$perm_test3{ $context_org }) { + next; # failed test + } + } + + $aur_obj->cancel_reason( 1015 ); # Canceled: Fulfilled + $aur_obj->cancel_time( 'now' ); + $e->update_acq_user_request($aur_obj) or return $e->die_event; + create_user_request_events( $e, [ $aur_obj ], 'aur.rejected' ); + # FIXME - hrmm, since this is a special type of "cancelation", should we not fire these + # events or should we put the burden on A/T to filter things based on cancel_reason if + # desired? I don't think anyone is actually using A/T for these in practice + + $conn->respond({maximum => scalar(@$aur_ids), progress => $x++}); + } + + $e->commit; + return {complete => 1}; +} + +__PACKAGE__->register_method ( method => 'new_user_request', api_name => 'open-ils.acq.user_request.create', signature => { diff --git a/Open-ILS/src/perlmods/live_t/22-acq-requests.t b/Open-ILS/src/perlmods/live_t/22-acq-requests.t new file mode 100644 index 0000000000..2fc3d89e9f --- /dev/null +++ b/Open-ILS/src/perlmods/live_t/22-acq-requests.t @@ -0,0 +1,340 @@ +#!perl +use strict; use warnings; +use Test::More tests => 26; +use OpenILS::Utils::TestUtils; +use OpenILS::Utils::CStoreEditor qw/:funcs/; +use OpenILS::Application::Acq::Order; + +diag("Tests ACQ purchase requests"); + +my $script = OpenILS::Utils::TestUtils->new(); +$script->bootstrap; + +$script->authenticate({ + username => 'admin', + password => 'demo123', + type => 'staff' +}); + +my $ses = $script->session('open-ils.storage'); +my $req = $ses->request('open-ils.storage.direct.actor.user.retrieve', 87); +if (my $resp = $req->recv) { + if (my $user = $resp->content) { +# ----------------------------------------------------------------------------- +# 1. We'll use Smith, Sarah (with usrname 99999303411 and home lib SL1) +# ----------------------------------------------------------------------------- + is( + $user->usrname, + '99999303411', + 'User with id = 87 is 99999303411' + ); + } +} + +# ----------------------------------------------------------------------------- +# 2. Check for auth +# ----------------------------------------------------------------------------- +ok($script->authtoken, 'Have an authtoken'); + +$req = $script->session('open-ils.pcrud')->request( + 'open-ils.pcrud.retrieve.acqcr', + $script->authtoken, 1015); +if (my $resp = $req->recv) { + if (my $new_cr = $resp->content) { +# ----------------------------------------------------------------------------- +# 3. Check for Canceled: Fulfilled +# ----------------------------------------------------------------------------- + is($new_cr->label,'Canceled: Fulfilled','New cancel reason for fulfilled requests'); + } +} + +$req = $script->session('open-ils.pcrud')->request( + 'open-ils.pcrud.retrieve.aurt', + $script->authtoken, 1); +if (my $resp = $req->recv) { + if (my $aurt = $resp->content) { +# ----------------------------------------------------------------------------- +# 4. Check for user request type Books +# ----------------------------------------------------------------------------- + is($aurt->label,'Books','Found user request type Books'); + } +} + +my $aur; +my $aur_hash = {}; +$aur_hash->{'request_type'} = 1; # Books +$aur_hash->{'usr'} = 87; # Smith +$aur_hash->{'pickup_lib'} = 8; # SL1 +$aur_hash->{'email_notify'} = 'f'; +$aur_hash->{'hold'} = 'f'; +$aur_hash->{'title'} = 'test'; + +$req = $script->session('open-ils.acq')->request( + 'open-ils.acq.user_request.create', + $script->authtoken, $aur_hash); +if (my $resp = $req->recv) { + if ($aur = $resp->content) { +# ----------------------------------------------------------------------------- +# 5. Check for created user request +# ----------------------------------------------------------------------------- + is(ref $aur, 'Fieldmapper::acq::user_request', 'User request created'); + diag('User Request ID = ' . $aur->id); + } +} + +$req = $script->session('open-ils.pcrud')->request( + 'open-ils.pcrud.retrieve.aurs', + $script->authtoken, $aur->id); +if (my $resp = $req->recv) { + if (my $aurs = $resp->content) { +# ----------------------------------------------------------------------------- +# 6,7,8. Check for status-enhanced user request +# ----------------------------------------------------------------------------- + is($aurs->id,$aur->id,'Found status-enhanced user request'); + is($aurs->request_status,1,'Request Status = New'); + is($aurs->home_ou,8,'Home Lib = SL1'); + } +} + +# open-ils.acq.picklist.create +# {"__c":"acqpl","__p":[null,1,"4","test",null,null,null,null,1,1]} +# {"__c":"acqpl","__p":[1,1,4,"test","2018-07-31T16:33:39-0400","now",null,null,1,1]} + +my $picklist_id; +my $picklist = Fieldmapper::acq::picklist->new; +$picklist->isnew(1); +$picklist->owner(1); # admin +$picklist->creator(1); # admin +$picklist->editor(1); # admin +$picklist->org_unit(8); # SL1 +$picklist->name( $script->authtoken ); # $picklist->name('22-acq-requests.t'); +$picklist->create_time('now'); +$picklist->edit_time('now'); + +$req = $script->session('open-ils.acq')->request( + 'open-ils.acq.picklist.create', + $script->authtoken, $picklist); +if (my $resp = $req->recv) { + if ($picklist_id = $resp->content) { +# ----------------------------------------------------------------------------- +# 9. Check for created picklist +# ----------------------------------------------------------------------------- + ok($picklist_id > 0,'Created picklist aka selection list'); + diag('Picklist ID = ' . $picklist_id); + } +} + +my $jub_id; +my $jub = Fieldmapper::acq::lineitem->new; +$jub->selector(1); # admin +$jub->picklist($picklist_id); +$jub->create_time('now'); +$jub->edit_time('now'); +$jub->marc('00000nam a22000007a 4500test '); +$jub->state('new'); +$jub->creator(1); # admin +$jub->editor(1); # admin +$jub->estimated_unit_price(1.00); +$jub->isnew(1); + +$req = $script->session('open-ils.acq')->request( + 'open-ils.acq.lineitem.create', + $script->authtoken, $jub); +if (my $resp = $req->recv) { + if ($jub_id = $resp->content) { +# ----------------------------------------------------------------------------- +# 10. Check for created lineitem +# ----------------------------------------------------------------------------- + ok($jub_id > 0,'Created lineitem'); + diag('Lineitem ID = ' . $jub_id); + } +} + +$req = $script->session('open-ils.pcrud')->request( + 'open-ils.pcrud.retrieve.aur', + $script->authtoken, $aur->id); +if (my $resp = $req->recv) { + if ($aur = $resp->content) { +# ----------------------------------------------------------------------------- +# 11. Retrieve bare user request +# ----------------------------------------------------------------------------- + is(ref $aur,'Fieldmapper::acq::user_request','Retrieved bare user request'); + } +} + +$aur->ischanged(1); +$aur->lineitem($jub_id); + +diag('Updating aur->lineitem'); +my $pcrud_ses = $script->session('open-ils.pcrud'); +$pcrud_ses->connect(); +my $xact = $pcrud_ses->request( + 'open-ils.pcrud.transaction.begin', + $script->authtoken +)->gather(1); +my $aur_id = $pcrud_ses->request( + 'open-ils.pcrud.update.aur', + $script->authtoken, + $aur +)->gather(1); +# ----------------------------------------------------------------------------- +# 12. Updated user request with lineitem +# ----------------------------------------------------------------------------- +is($aur_id,$aur->id,'Updated user request with lineitem'); + +$pcrud_ses->request( + 'open-ils.pcrud.transaction.commit', + $script->authtoken +)->gather(1); +$pcrud_ses->disconnect(); +undef($pcrud_ses); + +$req = $script->session('open-ils.acq')->request( + 'open-ils.acq.lineitem.batch_update', + $script->authtoken, { 'lineitems' => [$jub_id] }, { + "item_count" => 1, "location" => 118, "owning_lib" => 4, "fund" => 1}); +if (my $resp = $req->recv) { + if (my $return = $resp->content) { +# ----------------------------------------------------------------------------- +# 13. Check adding of copy to line +# ----------------------------------------------------------------------------- + is($return,$jub_id,'Added copy to lineitem'); + } +} + +$req = $script->session('open-ils.pcrud')->request( + 'open-ils.pcrud.retrieve.aurs', + $script->authtoken, $aur->id); +if (my $resp = $req->recv) { + if (my $aurs = $resp->content) { +# ----------------------------------------------------------------------------- +# 14,15,16. Check user request status and lineitem +# ----------------------------------------------------------------------------- + is($aurs->id,$aur->id,'Re-retrieved status-enhanced user request'); + is($aurs->request_status,2,'Request Status = Pending'); + is($aurs->lineitem,$jub_id,'Lineitem matches'); + } +} + +my $purchase_order_id; +my $purchase_order = Fieldmapper::acq::purchase_order->new; +$purchase_order->owner(1); # admin +$purchase_order->create_time('now'); +$purchase_order->edit_time('now'); +$purchase_order->provider(2); # BRODART +$purchase_order->state('pending'); +$purchase_order->ordering_agency(4); # BR1 +$purchase_order->creator(1); # admin +$purchase_order->editor(1); # admin +$purchase_order->name( $script->authtoken ); # $purchase_order->name('22-acq-requests.t'); +$purchase_order->isnew(1); + +$req = $script->session('open-ils.acq')->request( + 'open-ils.acq.purchase_order.create', + $script->authtoken, $purchase_order, { 'lineitems' => [$jub_id] }); +if (my $resp = $req->recv) { + if (my $return = $resp->content) { +#FIXME: open-ils.acq.purchase_order.create docs needs to be updated with correct return value +#FIXME: open-ils.acq.purchase_order.create docs needs to be updated for lineitem_ids argument +# ----------------------------------------------------------------------------- +# 17. Check for created purchase_order +# ----------------------------------------------------------------------------- + $purchase_order_id = $$return{'purchase_order'}->id; + ok($purchase_order_id > 0,'Created purchase_order'); + diag('Purchase Order ID = ' . $purchase_order_id); + } +} + +$req = $script->session('open-ils.pcrud')->request( + 'open-ils.pcrud.retrieve.aurs', + $script->authtoken, $aur->id); +if (my $resp = $req->recv) { + if (my $aurs = $resp->content) { +# ----------------------------------------------------------------------------- +# 18, 19. Check user request status is still Pending +# ----------------------------------------------------------------------------- + is($aurs->id,$aur->id,'Re-retrieved status-enhanced user request'); + is($aurs->request_status,2,'Request Status = Pending'); + } +} + + +# open-ils.acq.purchase_order.assets.create +my $vlArgs = { + 'vandelay' => { + 'auto_overlay_1match' => 0, + 'match_quality_ratio' => '0.0', + 'queue_name' => $script->authtoken, #'queue_name' => '22-acq-requests.t', + 'import_no_match' => 'on', + 'bib_source' => '', + 'fall_through_merge_profile' => '', + 'merge_profile' => '', + 'auto_overlay_best_match' => 0, + 'strip_field_groups' => [], + 'auto_overlay_exact' => 0, + 'existing_queue' => '', + 'match_set' => '' + } +}; +$req = $script->session('open-ils.acq')->request( + 'open-ils.acq.purchase_order.assets.create', + $script->authtoken, $purchase_order_id, $vlArgs); +if (my $resp = $req->recv) { + if (my $return = $resp->content) { +# ----------------------------------------------------------------------------- +# 20. Check for created assets +# ----------------------------------------------------------------------------- + is($return->{'complete'},1,'Assets created'); + } +} +$req = $script->session('open-ils.acq')->request( + 'open-ils.acq.purchase_order.activate', + $script->authtoken, $purchase_order_id, { + 'no_assets' => 0, 'zero_copy_activate' => 0}); +if (my $resp = $req->recv) { + if (my $return = $resp->content) { +# ----------------------------------------------------------------------------- +# 21. Check for activated purchase order +# ----------------------------------------------------------------------------- + is($return,1,'Purchase order activated'); + } +} + +$req = $script->session('open-ils.pcrud')->request( + 'open-ils.pcrud.retrieve.aurs', + $script->authtoken, $aur->id); +if (my $resp = $req->recv) { + if (my $aurs = $resp->content) { +# ----------------------------------------------------------------------------- +# 22, 23. Check user request status Ordered, No Hold Placed +# ----------------------------------------------------------------------------- + is($aurs->id,$aur->id,'Re-retrieved status-enhanced user request'); + is($aurs->request_status,3,'Request Status = Ordered, Hold Not Placed'); + } +} + +$req = $script->session('open-ils.acq')->request( + 'open-ils.acq.user_request.cancel.batch.atomic', + $script->authtoken, [ $aur_id ], 1015); # Canceled: Fulfilled +if (my $resp = $req->recv) { + if (my $return = $resp->content) { +# ----------------------------------------------------------------------------- +# 24. Check for activated purchase order +# ----------------------------------------------------------------------------- + is($return->[1]->{'complete'},1,'User request canceled with Canceled: Fulfilled'); + } +} + +$req = $script->session('open-ils.pcrud')->request( + 'open-ils.pcrud.retrieve.aurs', + $script->authtoken, $aur->id); +if (my $resp = $req->recv) { + if (my $aurs = $resp->content) { +# ----------------------------------------------------------------------------- +# 25, 26. Check user request status Ordered, No Hold Placed +# ----------------------------------------------------------------------------- + is($aurs->id,$aur->id,'Re-retrieved status-enhanced user request'); + is($aurs->request_status,7,'Request Status = Canceled'); + } +} + diff --git a/Open-ILS/src/sql/Pg/200.schema.acq.sql b/Open-ILS/src/sql/Pg/200.schema.acq.sql index c820e74409..68efce9bcc 100644 --- a/Open-ILS/src/sql/Pg/200.schema.acq.sql +++ b/Open-ILS/src/sql/Pg/200.schema.acq.sql @@ -942,6 +942,24 @@ CREATE TABLE acq.user_request_type ( label TEXT NOT NULL UNIQUE -- i18n-ize ); +CREATE TABLE acq.user_request_status_type ( + id SERIAL PRIMARY KEY + ,label TEXT +); + +INSERT INTO acq.user_request_status_type (id,label) VALUES + (0,oils_i18n_gettext(0,'Error','aurst','label')) + ,(1,oils_i18n_gettext(1,'New','aurst','label')) + ,(2,oils_i18n_gettext(2,'Pending','aurst','label')) + ,(3,oils_i18n_gettext(3,'Ordered, Hold Not Placed','aurst','label')) + ,(4,oils_i18n_gettext(4,'Ordered, Hold Placed','aurst','label')) + ,(5,oils_i18n_gettext(5,'Received','aurst','label')) + ,(6,oils_i18n_gettext(6,'Fulfilled','aurst','label')) + ,(7,oils_i18n_gettext(7,'Canceled','aurst','label')) +; + +SELECT SETVAL('acq.user_request_status_type_id_seq'::TEXT, 100); + CREATE TABLE acq.user_request ( id SERIAL PRIMARY KEY, usr INT NOT NULL REFERENCES actor.usr (id), -- requesting user @@ -959,6 +977,7 @@ CREATE TABLE acq.user_request ( request_type INT NOT NULL REFERENCES acq.user_request_type (id), isxn TEXT, + upc TEXT, title TEXT, volume TEXT, author TEXT, @@ -970,9 +989,11 @@ CREATE TABLE acq.user_request ( mentioned TEXT, other_info TEXT, cancel_reason INT REFERENCES acq.cancel_reason( id ) - DEFERRABLE INITIALLY DEFERRED + DEFERRABLE INITIALLY DEFERRED, + cancel_time TIMESTAMPTZ ); +ALTER TABLE action.hold_request ADD COLUMN acq_request INT REFERENCES acq.user_request (id); -- Functions diff --git a/Open-ILS/src/sql/Pg/950.data.seed-values.sql b/Open-ILS/src/sql/Pg/950.data.seed-values.sql index d9e75eb684..e0e02c66c9 100644 --- a/Open-ILS/src/sql/Pg/950.data.seed-values.sql +++ b/Open-ILS/src/sql/Pg/950.data.seed-values.sql @@ -1913,7 +1913,9 @@ INSERT INTO permission.perm_list ( id, code, description ) VALUES (608, 'APPLY_WORKSTATION_SETTING', oils_i18n_gettext(608, 'APPLY_WORKSTATION_SETTING', 'ppl', 'description')), ( 609, 'MANAGE_CUSTOM_PERM_GRP_TREE', oils_i18n_gettext( 609, - 'Allows a user to manage custom permission group lists.', 'ppl', 'description' )) + 'Allows a user to manage custom permission group lists.', 'ppl', 'description' )), + ( 610, 'CLEAR_PURCHASE_REQUEST', oils_i18n_gettext(610, + 'Clear Completed User Purchase Requests', 'ppl', 'description')) ; @@ -2591,6 +2593,7 @@ INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable) aout.name = 'Consortium' AND perm.code IN ( 'ALLOW_ALT_TCN', + 'CLEAR_PURCHASE_REQUEST', 'CREATE_BIB_IMPORT_QUEUE', 'CREATE_IMPORT_ITEM', 'CREATE_INVOICE', @@ -2964,12 +2967,13 @@ INSERT INTO config.settings_group (name, label) VALUES INSERT INTO acq.user_request_type (id,label) VALUES (1, oils_i18n_gettext('1', 'Books', 'aurt', 'label')); -INSERT INTO acq.user_request_type (id,label) VALUES (2, oils_i18n_gettext('2', 'Journal/Magazine & Newspaper Articles', 'aurt', 'label')); +INSERT INTO acq.user_request_type (id,label) VALUES (2, oils_i18n_gettext('2', 'Articles', 'aurt', 'label')); INSERT INTO acq.user_request_type (id,label) VALUES (3, oils_i18n_gettext('3', 'Audiobooks', 'aurt', 'label')); INSERT INTO acq.user_request_type (id,label) VALUES (4, oils_i18n_gettext('4', 'Music', 'aurt', 'label')); INSERT INTO acq.user_request_type (id,label) VALUES (5, oils_i18n_gettext('5', 'DVDs', 'aurt', 'label')); +INSERT INTO acq.user_request_type (id,label) VALUES (6, oils_i18n_gettext('6', 'Other', 'aurt', 'label')); -SELECT SETVAL('acq.user_request_type_id_seq'::TEXT, 6); +SELECT SETVAL('acq.user_request_type_id_seq'::TEXT, 7); -- org_unit setting types @@ -3028,15 +3032,6 @@ INSERT into config.org_unit_setting_type 'coust', 'description'), 'integer', null) -,( 'acq.holds.allow_holds_from_purchase_request', 'acq', - oils_i18n_gettext('acq.holds.allow_holds_from_purchase_request', - 'Allows patrons to create automatic holds from purchase requests.', - 'coust', 'label'), - oils_i18n_gettext('acq.holds.allow_holds_from_purchase_request', - 'Allows patrons to create automatic holds from purchase requests.', - 'coust', 'description'), - 'bool', null) - ,( 'acq.tmp_barcode_prefix', 'acq', oils_i18n_gettext('acq.tmp_barcode_prefix', 'Temporary barcode prefix', @@ -3484,19 +3479,19 @@ INSERT into config.org_unit_setting_type ,( 'circ.holds.canceled.display_age', 'holds', oils_i18n_gettext('circ.holds.canceled.display_age', - 'Canceled holds display age', + 'Canceled holds/requests display age', 'coust', 'label'), oils_i18n_gettext('circ.holds.canceled.display_age', - 'Show all canceled holds that were canceled within this amount of time', + 'Show all canceled entries in patron holds and patron acquisition requests interfaces that were canceled within this amount of time', 'coust', 'description'), 'interval', null) ,( 'circ.holds.canceled.display_count', 'holds', oils_i18n_gettext('circ.holds.canceled.display_count', - 'Canceled holds display count', + 'Canceled holds/requests display count', 'coust', 'label'), oils_i18n_gettext('circ.holds.canceled.display_count', - 'How many canceled holds to show in patron holds interfaces', + 'How many canceled entries to show in patron holds and patron acquisition requests interfaces', 'coust', 'description'), 'integer', null) @@ -11959,6 +11954,8 @@ INSERT INTO acq.cancel_reason (keep_debits, id, org_unit, label, description) VA oils_i18n_gettext(1007, 'This line item is not accepted by the seller.', 'acqcr', 'description')), ('f',( 10+1000), 1, oils_i18n_gettext(1010, 'Canceled: Not Found', 'acqcr', 'label'), oils_i18n_gettext(1010, 'This line item is not found in the referenced message.', 'acqcr', 'description')), +('f',( 15+1000), 1, oils_i18n_gettext(1015, 'Canceled: Fulfilled', 'acqcr', 'label'), + oils_i18n_gettext(1015, 'This acquisition request has been fulfilled.', 'acqcr', 'description')), ('t',( 24+1000), 1, oils_i18n_gettext(1024, 'Delayed: Accepted with amendment', 'acqcr', 'label'), oils_i18n_gettext(1024, 'Accepted with changes which require no confirmation.', 'acqcr', 'description')); diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.data.schema.acq.patron_requests.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.data.schema.acq.patron_requests.sql new file mode 100644 index 0000000000..5035008af7 --- /dev/null +++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.data.schema.acq.patron_requests.sql @@ -0,0 +1,88 @@ +BEGIN; + +--SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version); + +ALTER TABLE acq.user_request ADD COLUMN cancel_time TIMESTAMPTZ; +ALTER TABLE acq.user_request ADD COLUMN upc TEXT; +ALTER TABLE action.hold_request ADD COLUMN acq_request INT REFERENCES acq.user_request (id); + +UPDATE + config.org_unit_setting_type +SET + label = oils_i18n_gettext( + 'circ.holds.canceled.display_age', + 'Canceled holds/requests display age', + 'coust', 'label'), + description = oils_i18n_gettext( + 'circ.holds.canceled.display_age', + 'Show all canceled entries in patron holds and patron acquisition requests interfaces that were canceled within this amount of time', + 'coust', 'description') +WHERE + name = 'circ.holds.canceled.display_age' +; + +UPDATE + config.org_unit_setting_type +SET + label = oils_i18n_gettext( + 'circ.holds.canceled.display_count', + 'Canceled holds/requests display count', + 'coust', 'label'), + description = oils_i18n_gettext( + 'circ.holds.canceled.display_count', + 'How many canceled entries to show in patron holds and patron acquisition requests interfaces', + 'coust', 'description') +WHERE + name = 'circ.holds.canceled.display_count' +; + +INSERT INTO acq.cancel_reason (org_unit, keep_debits, id, label, description) + VALUES ( + 1, 'f', 1015, + oils_i18n_gettext(1015, 'Canceled: Fulfilled', 'acqcr', 'label'), + oils_i18n_gettext(1015, 'This acquisition request has been fulfilled.', 'acqcr', 'description') + ) +; + +UPDATE + acq.user_request_type +SET + label = oils_i18n_gettext('2', 'Articles', 'aurt', 'label') +WHERE + id = 2 +; + +INSERT INTO acq.user_request_type (id,label) + SELECT 6, oils_i18n_gettext('6', 'Other', 'aurt', 'label'); + +SELECT SETVAL('acq.user_request_type_id_seq'::TEXT, (SELECT MAX(id)+1 FROM acq.user_request_type)); + +INSERT INTO permission.perm_list ( id, code, description ) VALUES + ( 610, 'CLEAR_PURCHASE_REQUEST', oils_i18n_gettext(610, + 'Clear Completed User Purchase Requests', 'ppl', 'description')) +; + +CREATE TABLE acq.user_request_status_type ( + id SERIAL PRIMARY KEY + ,label TEXT +); + +INSERT INTO acq.user_request_status_type (id,label) VALUES + (0,oils_i18n_gettext(0,'Error','aurst','label')) + ,(1,oils_i18n_gettext(1,'New','aurst','label')) + ,(2,oils_i18n_gettext(2,'Pending','aurst','label')) + ,(3,oils_i18n_gettext(3,'Ordered, Hold Not Placed','aurst','label')) + ,(4,oils_i18n_gettext(4,'Ordered, Hold Placed','aurst','label')) + ,(5,oils_i18n_gettext(5,'Received','aurst','label')) + ,(6,oils_i18n_gettext(6,'Fulfilled','aurst','label')) + ,(7,oils_i18n_gettext(7,'Canceled','aurst','label')) +; + +SELECT SETVAL('acq.user_request_status_type_id_seq'::TEXT, 100); + +-- not used +DELETE FROM actor.org_unit_setting WHERE name = 'acq.holds.allow_holds_from_purchase_request'; +DELETE FROM config.org_unit_setting_type_log WHERE field_name = 'acq.holds.allow_holds_from_purchase_request'; +DELETE FROM config.org_unit_setting_type WHERE name = 'acq.holds.allow_holds_from_purchase_request'; + +COMMIT; diff --git a/Open-ILS/src/templates/staff/acq/requests/index.tt2 b/Open-ILS/src/templates/staff/acq/requests/index.tt2 new file mode 100644 index 0000000000..c6ae3d392d --- /dev/null +++ b/Open-ILS/src/templates/staff/acq/requests/index.tt2 @@ -0,0 +1,26 @@ +[% + WRAPPER "staff/base.tt2"; + ctx.page_title = l("Acquisition Patron Requests"); + ctx.page_app = "egAcqRequestsApp"; +%] + +[% BLOCK APP_JS %] + + + + + + + +[% END %] + +
+ +[% END %] diff --git a/Open-ILS/src/templates/staff/acq/requests/t_cancel.tt2 b/Open-ILS/src/templates/staff/acq/requests/t_cancel.tt2 new file mode 100644 index 0000000000..ba1db9d281 --- /dev/null +++ b/Open-ILS/src/templates/staff/acq/requests/t_cancel.tt2 @@ -0,0 +1,31 @@ +[% ctx.page_title = l("Cancel Selected Patron Requests"); %] + +
+
+ + + diff --git a/Open-ILS/src/templates/staff/acq/requests/t_clear.tt2 b/Open-ILS/src/templates/staff/acq/requests/t_clear.tt2 new file mode 100644 index 0000000000..b15206aa04 --- /dev/null +++ b/Open-ILS/src/templates/staff/acq/requests/t_clear.tt2 @@ -0,0 +1,25 @@ +[% ctx.page_title = l("Clear Completed Patron Requests"); %] + +
+
+ + + +
+
diff --git a/Open-ILS/src/templates/staff/acq/requests/t_edit.tt2 b/Open-ILS/src/templates/staff/acq/requests/t_edit.tt2 new file mode 100644 index 0000000000..a62a2281fc --- /dev/null +++ b/Open-ILS/src/templates/staff/acq/requests/t_edit.tt2 @@ -0,0 +1,240 @@ +[% ctx.page_title = l("Create/Edit/View patron Request"); %] + +
+
+ + + + +
+
diff --git a/Open-ILS/src/templates/staff/acq/requests/t_list.tt2 b/Open-ILS/src/templates/staff/acq/requests/t_list.tt2 new file mode 100644 index 0000000000..5c279b4faa --- /dev/null +++ b/Open-ILS/src/templates/staff/acq/requests/t_list.tt2 @@ -0,0 +1,82 @@ +
+
+ [% l('Acquisition Patron Requests') %] +
+
+ +
+
+
+ + + +   + + [% l('User ID: [_1]','{{context_user}}') %] + [% l('PO Line Item ID: [_1]','{{context_lineitem}}') %] +
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Open-ILS/src/templates/staff/acq/requests/t_set_no_hold.tt2 b/Open-ILS/src/templates/staff/acq/requests/t_set_no_hold.tt2 new file mode 100644 index 0000000000..77c5d4e39c --- /dev/null +++ b/Open-ILS/src/templates/staff/acq/requests/t_set_no_hold.tt2 @@ -0,0 +1,25 @@ +[% ctx.page_title = l('Set "No Hold" on Selected Patron Requests'); %] + +
+
+ + + +
+
diff --git a/Open-ILS/src/templates/staff/acq/requests/t_set_yes_hold.tt2 b/Open-ILS/src/templates/staff/acq/requests/t_set_yes_hold.tt2 new file mode 100644 index 0000000000..45acd4ef1f --- /dev/null +++ b/Open-ILS/src/templates/staff/acq/requests/t_set_yes_hold.tt2 @@ -0,0 +1,25 @@ +[% ctx.page_title = l('Set "Hold" on Selected Patron Requests'); %] + +
+
+ + + +
+
diff --git a/Open-ILS/src/templates/staff/circ/patron/index.tt2 b/Open-ILS/src/templates/staff/circ/patron/index.tt2 index 6724ca45d2..5ebbe0eb9b 100644 --- a/Open-ILS/src/templates/staff/circ/patron/index.tt2 +++ b/Open-ILS/src/templates/staff/circ/patron/index.tt2 @@ -209,6 +209,11 @@ angular.module('egCoreMod').run(['egStrings', function(s) {
  • + + [% l('Acquisition Patron Requests') %] + +
  • +
  • [% l('Booking: Create or Cancel Reservations') %] diff --git a/Open-ILS/src/templates/staff/navbar.tt2 b/Open-ILS/src/templates/staff/navbar.tt2 index d3033d7c7b..a5d988839a 100644 --- a/Open-ILS/src/templates/staff/navbar.tt2 +++ b/Open-ILS/src/templates/staff/navbar.tt2 @@ -359,7 +359,7 @@
  • - + [% l('Patron Requests') %] diff --git a/Open-ILS/web/js/ui/default/acq/common/li_table.js b/Open-ILS/web/js/ui/default/acq/common/li_table.js index 40347fc849..8c59f4e7c3 100644 --- a/Open-ILS/web/js/ui/default/acq/common/li_table.js +++ b/Open-ILS/web/js/ui/default/acq/common/li_table.js @@ -787,9 +787,15 @@ function AcqLiTable() { oilsBasePath + "/acq/lineitem/worksheet/" + li.id() + '?source=' + encodeURIComponent(location.pathname + location.search) - nodeByName("show_requests_link", row).href = - oilsBasePath + "/acq/picklist/user_request?lineitem=" + li.id() + - '?source=' + encodeURIComponent(location.pathname + location.search) + if (!IAMBROWSER) { + nodeByName("show_requests_link", row).href = + oilsBasePath + "/acq/picklist/user_request?lineitem=" + li.id() + + '?source=' + encodeURIComponent(location.pathname + location.search); + } else { + nodeByName("show_requests_link", row).href = + "/eg/staff/acq/requests/lineitem/" + li.id(); + nodeByName("show_requests_link", row).setAttribute('target','_top'); + } dojo.query('[attr=title]', row)[0].onclick = function() {self.drawInfo(li.id())}; dojo.query('[name=copieslink]', row)[0].onclick = function() {self.drawCopies(li.id())}; diff --git a/Open-ILS/web/js/ui/default/acq/picklist/brief_record.js b/Open-ILS/web/js/ui/default/acq/picklist/brief_record.js index f59b93b157..93d2a3f237 100644 --- a/Open-ILS/web/js/ui/default/acq/picklist/brief_record.js +++ b/Open-ILS/web/js/ui/default/acq/picklist/brief_record.js @@ -223,7 +223,11 @@ function compileBriefRecord(fields, editMarc) { pcrud.update( aur_obj, { 'oncomplete' : function(r, cudResults) { // Goes back to the list view - location.href = oilsBasePath + '/acq/picklist/user_request'; + if (!window.IAMBROWSER) { + location.href = oilsBasePath + '/acq/picklist/user_request'; + } else { + window.top.location.href = '/eg/staff/acq/requests/list'; + } } }); } else { diff --git a/Open-ILS/web/js/ui/default/staff/acq/requests/list.js b/Open-ILS/web/js/ui/default/staff/acq/requests/list.js new file mode 100644 index 0000000000..0191ef30d0 --- /dev/null +++ b/Open-ILS/web/js/ui/default/staff/acq/requests/list.js @@ -0,0 +1,239 @@ +angular.module('egAcqRequestsApp', + ['ngRoute', 'ui.bootstrap', 'egCoreMod', 'egUserMod', 'egUiMod', 'egGridMod']) + +.config(function($routeProvider, $locationProvider, $compileProvider) { + $locationProvider.html5Mode(true); + // grid export + $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|mailto|blob):/); + + var resolver = {delay : + ['egStartup', function(egStartup) {return egStartup.go()}]} + + $routeProvider.when('/acq/requests/list', { + templateUrl: './acq/requests/t_list', + controller: 'AcqRequestsCtrl', + resolve : resolver + }); + + $routeProvider.when('/acq/requests/user/:user', { + templateUrl: './acq/requests/t_list', + controller: 'AcqRequestsCtrl', + resolve : resolver + }); + + $routeProvider.when('/acq/requests/lineitem/:lineitem', { + templateUrl: './acq/requests/t_list', + controller: 'AcqRequestsCtrl', + resolve : resolver + }); + + $routeProvider.otherwise({redirectTo : '/acq/requests/list'}); +}) + +.controller('AcqRequestsCtrl', + ['$scope','$q','$routeParams','$window','egCore','egAcqRequests','egUser', + 'egGridDataProvider','$uibModal','$timeout', +function($scope , $q , $routeParams , $window , egCore , egAcqRequests , egUser , + egGridDataProvider , $uibModal , $timeout) { + + var cancel_age; + var cancel_count; + $scope.context_user = $routeParams.user; + $scope.context_lineitem = $routeParams.lineitem; + + egCore.startup.go().then(function() { + // org settings for constraining display of canceled requests + egCore.org.settings([ + 'circ.holds.canceled.display_age', + 'circ.holds.canceled.display_count' // FIXME Don't know how to use this with egGrid + ]).then(function(set) { + cancel_age = set['circ.holds.canceled.display_age']; + cancel_count = set['circ.holds.canceled.display_count']; + if (!cancel_age && !cancel_count) { + cancel_count = 10; // default to last 10 canceled requests + } + }); + }); + + $scope.need_one_selected = function() { + var requests = $scope.grid_controls.selectedItems(); + if (requests.length == 1) return false; + return true; + } + + $scope.need_one_uncanceled = function() { + var requests = $scope.grid_controls.selectedItems(); + if (requests.length == 1) { + return requests[0]['cancel_reason.label'] ? true : false; + } + return true; + } + + $scope.need_one_lineitem = function() { + var requests = $scope.grid_controls.selectedItems(); + if (requests.length == 1) { + return ! requests[0]['lineitem.id']; + } + return true; + } + + $scope.need_one_uncanceled_no_lineitem = function() { + var requests = $scope.grid_controls.selectedItems(); + if (requests.length == 1) { + if (! requests[0]['lineitem.id']) { + return requests[0]['cancel_reason.label'] ? true : false; + } + } + return true; + } + + $scope.need_one_and_all_uncanceled = function() { + var requests = $scope.grid_controls.selectedItems(); + if (requests.length == 0) return true; + var found_canceled = false; + angular.forEach(requests,function(v,k) { + if (v['cancel_reason.label']) { found_canceled = true; } + }); + return found_canceled; + } + + $scope.need_one_and_all_new_or_pending = function() { + var requests = $scope.grid_controls.selectedItems(); + if (requests.length == 0) return true; + var found_bad = false; + angular.forEach(requests,function(v,k) { + if (v['request_status.id'] != 2 // Pending + && v['request_status.id'] != 1) { // New + found_bad = true; + } + }); + return found_bad; + } + + $scope.create_request = function(rows) { + var row = {}; + if ($scope.context_user) { + row.usr = $scope.context_user; + } + egAcqRequests.handle_request(row,'create',$scope.context_ou,refresh_page); + } + + $scope.edit_request = function(rows) { + if (!rows) return; + if (!angular.isArray(rows)) rows = [rows]; + if (rows.length == 0) return; + egAcqRequests.handle_request(rows[0],'edit',$scope.context_ou,refresh_page); + } + + $scope.view_request = function(rows) { + if (!rows) return; + if (!angular.isArray(rows)) rows = [rows]; + if (rows.length == 0) return; + egAcqRequests.handle_request(rows[0],'view',$scope.context_ou,refresh_page); + } + + $scope.add_request_to_picklist = function(rows) { + if (!rows) return; + if (!angular.isArray(rows)) rows = [rows]; + if (rows.length == 0) return; + egAcqRequests.add_request_to_picklist(rows[0]); + } + + $scope.view_picklist = function(rows) { + if (!rows) return; + if (!angular.isArray(rows)) rows = [rows]; + if (rows.length == 0) return; + egAcqRequests.view_picklist(rows[0]); + } + + $scope.retrieve_user = function(rows) { + if (!rows) return; + if (!angular.isArray(rows)) rows = [rows]; + if (rows.length == 0) return; + location.href = "/eg/staff/circ/patron/" + rows[0]['usr.id'] + "/checkout"; + } + + $scope.clear_requests = function(rows) { + rows = $scope.grid_controls.selectedItems(); // remove this if we move the grid action into the menu + if (!rows) return; + if (!angular.isArray(rows)) rows = [rows]; + if (rows.length == 0) return; + egAcqRequests.clear_requests( rows, refresh_page ); + } + + $scope.set_no_hold_requests = function(rows) { + if (!rows) return; + if (!angular.isArray(rows)) rows = [rows]; + if (rows.length == 0) return; + egAcqRequests.set_no_hold_requests( rows, refresh_page ); + } + + $scope.set_yes_hold_requests = function(rows) { + if (!rows) return; + if (!angular.isArray(rows)) rows = [rows]; + if (rows.length == 0) return; + egAcqRequests.set_yes_hold_requests( rows, refresh_page ); + } + + $scope.cancel_requests = function(rows) { + if (!rows) return; + if (!angular.isArray(rows)) rows = [rows]; + if (rows.length == 0) return; + egAcqRequests.cancel_requests( rows, refresh_page ); + } + + $scope.canceled_requests_checkbox_handler = function (item) { + $scope.canceled_requests_cb_changed(item.checkbox,item.checked); + } + + $scope.canceled_requests_cb_changed = function(cb,newVal,norefresh) { + $scope[cb] = newVal; + egCore.hatch.setItem('eg.acq.' + cb, newVal); + if (!norefresh) { + refresh_page(); + } + } + + function current_query() { + var filter = {} + if ($scope.context_user) { + filter.usr = $scope.context_user; + } else if ($scope.context_lineitem) { + filter.lineitem = $scope.context_lineitem; + } else { + filter.home_ou = egCore.org.descendants($scope.context_ou.id(), true) + } + if ($scope['requests_show_canceled']) { + filter.cancel_reason = { '!=' : null }; + if (cancel_age) { + var seconds = egCore.date.intervalToSeconds(cancel_age); + var now_epoch = new Date().getTime(); + var cancel_date = new Date( + now_epoch - (seconds * 1000 /* milliseconds */) + ); + filter.cancel_time = { '>=' : cancel_date.toISOString() }; + } + + } else { + filter.cancel_reason = { '=' : null }; + } + return filter; + } + + $scope.grid_controls = { + activateItem : $scope.view_request, + setQuery : current_query + } + + function refresh_page() { + $scope.grid_controls.setQuery(current_query()); + $scope.grid_controls.refresh(); + } + + $scope.context_ou = egCore.org.get(egCore.auth.user().ws_ou()); + $scope.$watch('context_ou', function(newVal, oldVal) { + if (newVal && newVal != oldVal) refresh_page(); + }); + +}]) + diff --git a/Open-ILS/web/js/ui/default/staff/acq/services/requests.js b/Open-ILS/web/js/ui/default/staff/acq/services/requests.js new file mode 100644 index 0000000000..013b083598 --- /dev/null +++ b/Open-ILS/web/js/ui/default/staff/acq/services/requests.js @@ -0,0 +1,582 @@ +/** + * AcqRequests, yo + */ + +angular.module('egCoreMod') + +.factory('egAcqRequests', + + ['$uibModal','$q','egCore','egOrg','ngToast', +function($uibModal , $q , egCore , egOrg , ngToast) { + + var service = {}; + + var aur_fleshing = { + + flesh : 2, + // aur -> cancel_reason + // aur -> lineitem + // aur -> pickup_lib + // aur -> request_type + // aur -> usr + // aur -> usr -> card + + flesh_fields : { + 'aur' : [ + 'cancel_reason' + ,'lineitem' + ,'pickup_lib' + ,'request_type' + ,'usr' + ] + ,'au' : [ + 'card' + ,'home_ou' + ,'mailing_address' + ,'billing_address' + ,'settings' + ] + } + }; + + var aurs_fleshing = { + + flesh : 2, + // aurs -> cancel_reason + // aurs -> lineitem + // aurs -> pickup_lib + // aurs -> request_type + // aurs -> request_status + // aurs -> usr + // aurs -> usr -> card + + flesh_fields : { + 'aurs' : [ + 'cancel_reason' + ,'lineitem' + ,'pickup_lib' + ,'request_type' + ,'request_status' + ,'usr' + ] + ,'au' : [ + 'card' + ,'home_ou' + ,'mailing_address' + ,'billing_address' + ,'settings' + ] + } + }; + + service.aur_fleshing = function(newvalue) { + if (newvalue) { + aur_fleshing = newvalue; + } + return angular.copy(aur_fleshing); + } + + service.aurs_fleshing = function(newvalue) { + if (newvalue) { + aurs_fleshing = newvalue; + } + return angular.copy(aurs_fleshing); + } + + service.fetch_request = function(aur_id) { + var deferred = $q.defer(); + egCore.pcrud.search( + 'aur', { id : aur_id }, aur_fleshing, { atomic : true, authoritative : true } + ).then(function(requests) { + deferred.resolve(requests[0]); + }); + return deferred.promise; + } + + service.fetch_request_with_status = function(aur_id) { + var deferred = $q.defer(); + egCore.pcrud.search( + 'aurs', { id : aur_id }, aurs_fleshing, { atomic : true, authoritative : true } + ).then(function(requests) { + deferred.resolve(requests[0]); + }); + return deferred.promise; + } + + service.fetch_cancel_reasons = function() { + var deferred = $q.defer(); + egCore.pcrud.retrieveAll( + 'acqcr', {}, {atomic : true, authoritative : true} + ).then(function(cancel_reasons) { + deferred.resolve(cancel_reasons); + }); + return deferred.promise; + } + + service.fetch_request_types = function() { + var deferred = $q.defer(); + egCore.pcrud.retrieveAll( + 'aurt', {}, {atomic : true, authoritative : true} + ).then(function(request_types) { + deferred.resolve(request_types); + }); + return deferred.promise; + } + + service.fetch_request_status_types = function() { + var deferred = $q.defer(); + egCore.pcrud.retrieveAll( + 'aurst', {}, {atomic : true, authoritative : true} + ).then(function(request_status_types) { + deferred.resolve(request_status_types); + }); + return deferred.promise; + } + + service.add_request_to_picklist = function (row) { + egCore.pcrud.search('aurs', { + id : row['id'] + }, aurs_fleshing, { + atomic : true + } + ).then(function(requests) { + var aur_obj = requests[0]; + var prepop = { // based on acq.lineitem_marc_attr_definition + "1": [aur_obj.title(), aur_obj.article_title(), aur_obj.volume()].join(' '), + "2": aur_obj.author(), + "4": aur_obj.article_pages(), + "7": aur_obj.upc(), + "10": aur_obj.publisher(), + "11": aur_obj.pubdate() + } + if (aur_obj.request_type().id() == "2") { /* Articles */ + prepop["6"] = aur_obj.isxn(); + } else { + prepop["5"] = aur_obj.isxn(); + } + location.href = "/eg/staff/acq/legacy/picklist/brief_record?ur=" + + aur_obj.id() + "&prepop=" + encodeURIComponent(js2JSON(prepop)); + }); + } + + service.view_picklist = function (row) { + location.href = "/eg/staff/acq/legacy/picklist/view/" + row['lineitem.picklist']; + } + + service.handle_request = function(row,mode,context_ou,callback) { + if (mode!='create' && !row) { return; } + return $uibModal.open({ + templateUrl: './acq/requests/t_edit', + backdrop: 'static', + controller: ['$scope', '$uibModalInstance','egCore', + 'request_and_extra','request_types','request_status_types', + function($m_scope , $uibModalInstance , egCore , + request_and_extra , request_types , request_status_types ) { + var request = request_and_extra.request; + var extra = request_and_extra.extra || {}; + var today = new Date(); + today.setHours(0); + today.setMinutes(0); + today.setSeconds(0); + today.setMilliseconds(0); + $m_scope.minDate = today; + $m_scope.mode = mode; + $m_scope.request = request; + $m_scope.request_types = request_types; + $m_scope.extra = extra; + $m_scope.extra.user_obj = request.usr; + angular.forEach(['hold', 'email_notify'], function(field) { + if (request[field] == 't') { + request[field] = true; + } else if (request[field] == 'f' || typeof request[field] == 'undefined') { + request[field] = false; + } + }); + if (request.request_type) { + if (typeof request.request_type.id != 'undefined') { + request.request_type = request.request_type.id; + } + angular.forEach(request_types,function(v,k) { + if (v.id() == request.request_type) { + $m_scope.extra.selected_request_type = v; + } + }); + } + if (request.need_before) { + request.need_before = new Date(request.need_before); + } + if (request.pickup_lib) { + $m_scope.request.pickup_lib = + egCore.idl.fromHash('aou',request.pickup_lib); + } else { + $m_scope.request.pickup_lib = + egOrg.CanHaveVolumes(context_ou) + ? context_ou + : egOrg.get( egCore.auth.user().ws_ou() ); + } + if (request.cancel_reason) { + $m_scope.request.cancel_reason = + egCore.idl.fromHash('acqcr',request.cancel_reason); + $m_scope.mode = 'view'; // TODO: want explicit uncancel? + } + if (request.request_status && request.request_status.id != 1) { // New + $m_scope.mode = 'view'; + } + if (request.usr) { + if (typeof request.usr.id != 'undefined') { + $m_scope.extra.barcode = request.usr.card.barcode; + request.usr = request.usr.id; + } + } + $m_scope.cancel = function () { + $uibModalInstance.dismiss('canceled'); + } + $m_scope.ok = function(request2,extra2) { + $uibModalInstance.close({ + 'request':request2 + ,'extra':extra2 + }); + } + $m_scope.model_has_changed = false; + $m_scope.cant_have_vols = function (id) { + return !egCore.org.CanHaveVolumes(id); + } + $m_scope.find_user = function () { + + $m_scope.request.usr = null; + $m_scope.extra.user_obj = null; + if (!$m_scope.extra.barcode) return; + + egCore.net.request( + 'open-ils.actor', + 'open-ils.actor.get_barcodes', + egCore.auth.token(), egCore.auth.user().ws_ou(), + 'actor', $m_scope.extra.barcode) + + .then(function(resp) { // get_barcodes + + if (evt = egCore.evt.parse(resp)) { + console.error(evt.toString()); + return; + } + + if (!resp || !resp[0]) { + $m_scope.request.usr = null; + return; + } + + egCore.pcrud.search('au', { + id : resp[0].id + }, { + flesh : 1, + flesh_fields : { + 'au' : [ + 'card' + ,'home_ou' + ,'mailing_address' + ,'billing_address' + ,'settings' + ] + } + }, + { atomic : true } + ).then(function(users) { + var usr = egCore.idl.toHash(users[0]); + $m_scope.extra.user_obj = usr; + $m_scope.request.usr = usr.id; + $m_scope.request.pickup_lib = egOrg.get(usr.home_ou.id); + $m_scope.request.phone_notify = usr.day_phone; + angular.forEach(usr.settings, function(s) { + if (s.name == 'opac.hold_notify') { + if (s.value.match('phone')) { + $m_scope.extra.phone_notify = true; + } + if (s.value.match('email')) { + $m_scope.request.email_notify = true; + } + } + if (s.name == 'opac.default_phone') { + $m_scope.request.phone_notify = s.value.replace(/^"/,'').replace(/"$/,''); + } + if (s.name == 'opac.default_pickup_location') { + $m_scope.request.pickup_lib = + egOrg.get(s.value); + } + }); + return $m_scope.request; + }); + }); + } + $m_scope.$watch("extra.barcode", function(newVal, oldVal) { + if (newVal && newVal != oldVal) { + $m_scope.find_user(); + } + }); + $m_scope.$watch("extra.selected_request_type", + function(newVal, oldVal) { + if (newVal && newVal != oldVal) { + $m_scope.request.request_type = newVal.id(); + } + } + ); + }], + resolve : { + request_and_extra : function() { + if (mode=='create') { + var aur_obj = egCore.idl.toHash(new egCore.idl.aurs()); + var extra = {}; + if (row['usr']) { + return egCore.pcrud.search('au', { + id : row['usr'] + }, { + flesh : 1, + flesh_fields : { + 'au' : [ + 'card' + ,'home_ou' + ,'mailing_address' + ,'billing_address' + ,'settings' + ] + } + }, + { atomic : true } + ).then(function(users) { + if (users.length > 0) { + var usr = egCore.idl.toHash(users[0]); + aur_obj.usr = usr.id; + aur_obj.pickup_lib = egCore.idl.toHash( + egOrg.get(usr.home_ou.id) + ); + aur_obj.phone_notify = usr.day_phone; + angular.forEach(usr.settings, function(s) { + if (s.name == 'opac.hold_notify') { + if (s.value.match('phone')) { + extra.phone_notify = true; + } + if (s.value.match('email')) { + aur_obj.email_notify = true; + } + } + if (s.name == 'opac.default_phone') { + aur_obj.phone_notify = s.value.replace(/^"/,'').replace(/"$/,''); + } + if (s.name == 'opac.default_pickup_location') { + aur_obj.pickup_lib = egCore.idl.toHash( + egOrg.get(s.value) + ); + } + }); + } + return { 'request' : aur_obj, 'extra' : extra }; + }); + } else { + console.log('here'); + return { 'request' : aur_obj, 'extra': extra }; + } + } else { + return egCore.pcrud.search('aurs', { + id : row['id'] + }, aurs_fleshing, { + atomic : true + } + ).then(function(requests) { + var aur_obj = egCore.idl.toHash(requests[0]); + var extra = {}; + if (aur_obj.phone_notify) { + extra.phone_notify = true; + } + return { 'request' : aur_obj, 'extra' : extra }; + }); + } + } + ,request_types : function() { + return service.fetch_request_types(); + } + ,request_status_types : function() { + return service.fetch_request_status_types(); + } + } + }).result.then(function(data) { + delete data.request.request_status; + delete data.request.home_ou; + var aur_obj = new egCore.idl.fromHash('aur',data.request); + if (aur_obj.need_before() && typeof aur_obj.need_before() == 'object') { + aur_obj.need_before( aur_obj.need_before().toISOString() ); + } + if (!data.extra.phone_notify) { + aur_obj.phone_notify(null); + } + if (mode=='create') { + aur_obj.isnew('t'); + aur_obj.pickup_lib( aur_obj.pickup_lib().id() ); + return egCore.net.request( + 'open-ils.acq', + 'open-ils.acq.user_request.create', + egCore.auth.token(), egCore.idl.toHash(aur_obj) + ).then(function(resp) { + var evt = egCore.evt.parse(resp); + if (evt) { + ngToast.danger(egCore.strings.CREATE_USER_REQUEST_FAIL + ' : ' + evt.desc); + } else { + ngToast.success(egCore.strings.CREATE_USER_REQUEST_SUCCESS); + } + callback(resp); + }); + } else { + aur_obj.ischanged('t'); + return egCore.pcrud.apply(aur_obj).then(function(resp) { + var evt = egCore.evt.parse(resp); + if (evt) { + ngToast.danger(egCore.strings.EDIT_USER_REQUEST_FAIL + ' : ' + evt.desc); + } else { + ngToast.success(egCore.strings.EDIT_USER_REQUEST_SUCCESS); + } + callback(resp); + }); + } + }).catch(function(e) { + console.log('caught',e); + }); + } + + service.set_no_hold_requests = function(rows,callback) { + var ids = rows.map(function(v,i,a) { + return v.id; + }); + return $uibModal.open({ + templateUrl: './acq/requests/t_set_no_hold', + backdrop: 'static', + controller: ['$scope', '$uibModalInstance','egCore', + function($m_scope , $uibModalInstance , egCore ) { + $m_scope.ids = ids; + $m_scope.cancel = function () { + $uibModalInstance.dismiss('canceled'); + } + $m_scope.ok = function(doit) { + $uibModalInstance.close(doit); + } + }], + resolve : {} + }).result.then(function(cancel_reason) { + return egCore.net.request( + 'open-ils.acq', + 'open-ils.acq.user_request.set_no_hold.batch', + egCore.auth.token(), ids + ).then(function(obj) { + if (callback) { + callback(obj); + } + }); + }).catch(function(e) { + console.log('caught',e); + }); + } + + service.set_yes_hold_requests = function(rows,callback) { + var ids = rows.map(function(v,i,a) { + return v.id; + }); + return $uibModal.open({ + templateUrl: './acq/requests/t_set_yes_hold', + backdrop: 'static', + controller: ['$scope', '$uibModalInstance','egCore', + function($m_scope , $uibModalInstance , egCore ) { + $m_scope.ids = ids; + $m_scope.cancel = function () { + $uibModalInstance.dismiss('canceled'); + } + $m_scope.ok = function(doit) { + $uibModalInstance.close(doit); + } + }], + resolve : {} + }).result.then(function(cancel_reason) { + return egCore.net.request( + 'open-ils.acq', + 'open-ils.acq.user_request.set_yes_hold.batch', + egCore.auth.token(), ids + ).then(function(obj) { + if (callback) { + callback(obj); + } + }); + }).catch(function(e) { + console.log('caught',e); + }); + } + + service.cancel_requests = function(rows,callback) { + var ids = rows.map(function(v,i,a) { + return v.id; + }); + return $uibModal.open({ + templateUrl: './acq/requests/t_cancel', + backdrop: 'static', + controller: ['$scope', '$uibModalInstance','egCore','cancel_reasons', + function($m_scope , $uibModalInstance , egCore , cancel_reasons ) { + $m_scope.ids = ids; + $m_scope.cancel_reasons = cancel_reasons; + $m_scope.cancel = function () { + $uibModalInstance.dismiss('canceled'); + } + $m_scope.ok = function(cancel_reason) { + $uibModalInstance.close(cancel_reason); + } + }], + resolve : { + cancel_reasons : function() { + return service.fetch_cancel_reasons(); + } + } + }).result.then(function(cancel_reason) { + return egCore.net.request( + 'open-ils.acq', + 'open-ils.acq.user_request.cancel.batch.atomic', + egCore.auth.token(), ids, cancel_reason.id() + ).then(function(obj) { + if (callback) { + callback(obj); + } + }); + }).catch(function(e) { + console.log('caught',e); + }); + } + + service.clear_requests = function(rows,callback) { + var ids = rows.map(function(v,i,a) { + return v.id; + }); + return $uibModal.open({ + templateUrl: './acq/requests/t_clear', + backdrop: 'static', + controller: ['$scope', '$uibModalInstance','egCore', + function($m_scope , $uibModalInstance , egCore) { + $m_scope.ids = ids; + $m_scope.cancel = function () { + $uibModalInstance.dismiss('canceled'); + } + $m_scope.ok = function(cancel_reason) { + $uibModalInstance.close(true); + } + }], + resolve : {} + }).result.then(function(doit) { + return egCore.net.request( + 'open-ils.acq', + 'open-ils.acq.clear_completed_user_requests', + egCore.auth.token(), ids + ).then(function(obj) { + if (callback) { + callback(obj); + } + }); + }).catch(function(e) { + console.log('caught',e); + }); + } + + return service; +}]) +;