From 725e14ea540f63f4345f8c758e5c240d7c35691f Mon Sep 17 00:00:00 2001 From: Jason Etheridge Date: Mon, 12 Mar 2018 18:02:47 -0400 Subject: [PATCH] toward acq requests Signed-off-by: Jason Etheridge --- Open-ILS/examples/fm_IDL.xml | 82 ++++ .../perlmods/lib/OpenILS/Application/Acq/Order.pm | 136 +++++- Open-ILS/src/sql/Pg/200.schema.acq.sql | 22 +- Open-ILS/src/sql/Pg/950.data.seed-values.sql | 20 +- .../XXXX.data.schema.acq.patron_requests.sql | 82 ++++ .../src/templates/staff/acq/requests/index.tt2 | 26 ++ .../src/templates/staff/acq/requests/t_cancel.tt2 | 31 ++ .../src/templates/staff/acq/requests/t_clear.tt2 | 25 ++ .../src/templates/staff/acq/requests/t_edit.tt2 | 230 ++++++++++ .../src/templates/staff/acq/requests/t_list.tt2 | 81 ++++ .../templates/staff/acq/requests/t_set_no_hold.tt2 | 25 ++ .../staff/acq/requests/t_set_yes_hold.tt2 | 25 ++ Open-ILS/src/templates/staff/circ/patron/index.tt2 | 5 + Open-ILS/src/templates/staff/navbar.tt2 | 2 +- Open-ILS/web/js/ui/default/acq/common/li_table.js | 12 +- .../web/js/ui/default/acq/picklist/brief_record.js | 6 +- .../web/js/ui/default/staff/acq/requests/list.js | 239 ++++++++++ .../js/ui/default/staff/acq/services/requests.js | 486 +++++++++++++++++++++ 18 files changed, 1521 insertions(+), 14 deletions(-) create mode 100644 Open-ILS/src/sql/Pg/upgrade/XXXX.data.schema.acq.patron_requests.sql create mode 100644 Open-ILS/src/templates/staff/acq/requests/index.tt2 create mode 100644 Open-ILS/src/templates/staff/acq/requests/t_cancel.tt2 create mode 100644 Open-ILS/src/templates/staff/acq/requests/t_clear.tt2 create mode 100644 Open-ILS/src/templates/staff/acq/requests/t_edit.tt2 create mode 100644 Open-ILS/src/templates/staff/acq/requests/t_list.tt2 create mode 100644 Open-ILS/src/templates/staff/acq/requests/t_set_no_hold.tt2 create mode 100644 Open-ILS/src/templates/staff/acq/requests/t_set_yes_hold.tt2 create mode 100644 Open-ILS/web/js/ui/default/staff/acq/requests/list.js create mode 100644 Open-ILS/web/js/ui/default/staff/acq/services/requests.js diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml index b272b9ffab..8820cc7683 100644 --- a/Open-ILS/examples/fm_IDL.xml +++ b/Open-ILS/examples/fm_IDL.xml @@ -6085,6 +6085,7 @@ SELECT usr, + @@ -6103,6 +6104,7 @@ SELECT usr, + @@ -8147,6 +8149,7 @@ SELECT usr, + @@ -8174,6 +8177,85 @@ 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 3eae8602e3..54a30fca04 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]; @@ -3570,6 +3582,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) = @_; @@ -3602,7 +3629,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; } } @@ -3610,6 +3644,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 { @@ -3625,6 +3660,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/sql/Pg/200.schema.acq.sql b/Open-ILS/src/sql/Pg/200.schema.acq.sql index 60088d19b4..f956abcae0 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 @@ -970,9 +988,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 7f7687d734..0a2709bb70 100644 --- a/Open-ILS/src/sql/Pg/950.data.seed-values.sql +++ b/Open-ILS/src/sql/Pg/950.data.seed-values.sql @@ -1903,7 +1903,9 @@ INSERT INTO permission.perm_list ( id, code, description ) VALUES ( 605, 'UPDATE_COPY_ALERT', oils_i18n_gettext( 605, 'Update copy alerts', 'ppl', 'description' )), ( 606, 'DELETE_COPY_ALERT', oils_i18n_gettext( 606, - 'Delete copy alerts', 'ppl', 'description' )) + 'Delete copy alerts', 'ppl', 'description' )), + ( 607, 'CLEAR_PURCHASE_REQUEST', oils_i18n_gettext(607, + 'Clear Completed User Purchase Requests', 'ppl', 'description')) ; SELECT SETVAL('permission.perm_list_id_seq'::TEXT, 1000); @@ -2578,6 +2580,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', @@ -2951,12 +2954,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 @@ -3471,19 +3475,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) @@ -11895,6 +11899,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..1ba02b512c --- /dev/null +++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.data.schema.acq.patron_requests.sql @@ -0,0 +1,82 @@ +BEGIN; + +--SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version); + +ALTER TABLE acq.user_request ADD COLUMN cancel_time TIMESTAMPTZ; +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 + ( 607, 'CLEAR_PURCHASE_REQUEST', oils_i18n_gettext(607, + '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); + +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..b1efd40492 --- /dev/null +++ b/Open-ILS/src/templates/staff/acq/requests/t_edit.tt2 @@ -0,0 +1,230 @@ +[% 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..1bc19566e3 --- /dev/null +++ b/Open-ILS/src/templates/staff/acq/requests/t_list.tt2 @@ -0,0 +1,81 @@ +
+
+ [% 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 94849f203e..e4e8acf051 100644 --- a/Open-ILS/src/templates/staff/circ/patron/index.tt2 +++ b/Open-ILS/src/templates/staff/circ/patron/index.tt2 @@ -207,6 +207,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 a6a65ad58c..2621973313 100644 --- a/Open-ILS/src/templates/staff/navbar.tt2 +++ b/Open-ILS/src/templates/staff/navbar.tt2 @@ -353,7 +353,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 7c19fef1cf..70d1c2ffe8 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..0af28e7a72 --- /dev/null +++ b/Open-ILS/web/js/ui/default/staff/acq/services/requests.js @@ -0,0 +1,486 @@ +/** + * AcqRequests, yo + */ + +angular.module('egCoreMod') + +.factory('egAcqRequests', + + ['$uibModal','$q','egCore','ngToast', +function($uibModal , $q , egCore , 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'] + } + }; + + 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'] + } + }; + + 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 = { + "1": [aur_obj.title(), aur_obj.article_title(), aur_obj.volume()].join(' '), + "2": aur_obj.author(), + "4": aur_obj.article_pages(), + "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','request_types','request_status_types', + function($m_scope , $uibModalInstance , egCore , + request , request_types , request_status_types ) { + 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 = {}; + $m_scope.extra.user_obj = request.usr; + angular.forEach(['hold', 'email_notify'], function(field) { + request[field] = request[field] == 't'; + }); + 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 = context_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' : [ + 'mailing_address' + ,'billing_address' + ,'home_ou' + ] + } + }, + { atomic : true } + ).then(function(usr) { + $m_scope.extra.user_obj = + egCore.idl.toHash(usr[0]); + $m_scope.request.usr = + $m_scope.extra.user_obj.id; + }); + }); + } + $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 : function() { + if (mode=='create') { + var aur_obj = egCore.idl.toHash(new egCore.idl.aurs()); + if (row['usr']) { + aur_obj.usr = row['usr']; + } + return aur_obj; + } else { + return egCore.pcrud.search('aurs', { + id : row['id'] + }, aurs_fleshing, { + atomic : true + } + ).then(function(requests) { + return egCore.idl.toHash(requests[0]); + }); + } + } + ,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 (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) { + console.log('obj',obj); + if (callback) { + callback(obj); + } + }); + }).catch(function(e) { + console.log('caught',e); + }); + } + + return service; +}]) +; -- 2.11.0