From cf3d8f440677e81545fee330907920ff3a16028b Mon Sep 17 00:00:00 2001 From: Bill Erickson Date: Wed, 9 Jul 2014 15:00:44 -0400 Subject: [PATCH] initial hold pull list; hold grid action consolidation Signed-off-by: Bill Erickson --- Open-ILS/src/templates/staff/circ/holds/t_pull.tt2 | 26 ++++ .../src/templates/staff/circ/holds/t_pull_list.tt2 | 71 +++++++++ .../src/templates/staff/circ/holds/t_shelf.tt2 | 6 + .../templates/staff/circ/holds/t_shelf_list.tt2 | 28 ++-- .../templates/staff/circ/patron/t_holds_list.tt2 | 28 ++-- Open-ILS/src/templates/staff/navbar.tt2 | 6 + Open-ILS/web/js/ui/default/staff/circ/holds/app.js | 166 +++++++++++---------- .../web/js/ui/default/staff/circ/patron/holds.js | 78 ++-------- .../web/js/ui/default/staff/circ/services/holds.js | 137 +++++++++++++++-- 9 files changed, 360 insertions(+), 186 deletions(-) create mode 100644 Open-ILS/src/templates/staff/circ/holds/t_pull.tt2 create mode 100644 Open-ILS/src/templates/staff/circ/holds/t_pull_list.tt2 diff --git a/Open-ILS/src/templates/staff/circ/holds/t_pull.tt2 b/Open-ILS/src/templates/staff/circ/holds/t_pull.tt2 new file mode 100644 index 0000000000..de2988b571 --- /dev/null +++ b/Open-ILS/src/templates/staff/circ/holds/t_pull.tt2 @@ -0,0 +1,26 @@ +
+
+ [% l('Holds Pull List') %] +
+
+ +
+ +
+[% INCLUDE 'staff/circ/holds/t_pull_list.tt2' %] +
+ + +
+
+
+ +
+
+
+ + +
diff --git a/Open-ILS/src/templates/staff/circ/holds/t_pull_list.tt2 b/Open-ILS/src/templates/staff/circ/holds/t_pull_list.tt2 new file mode 100644 index 0000000000..b7e7ba1aa7 --- /dev/null +++ b/Open-ILS/src/templates/staff/circ/holds/t_pull_list.tt2 @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + {{item.hold.current_copy().barcode()}} + + + + + + + + + + + + {{item.mvr.title()}} + + + + + + + + + + + + + + diff --git a/Open-ILS/src/templates/staff/circ/holds/t_shelf.tt2 b/Open-ILS/src/templates/staff/circ/holds/t_shelf.tt2 index a3c4005276..b0a4aafbca 100644 --- a/Open-ILS/src/templates/staff/circ/holds/t_shelf.tt2 +++ b/Open-ILS/src/templates/staff/circ/holds/t_shelf.tt2 @@ -1,3 +1,9 @@ +
+
+ [% l('Holds Shelf') %] +
+
+
diff --git a/Open-ILS/src/templates/staff/circ/holds/t_shelf_list.tt2 b/Open-ILS/src/templates/staff/circ/holds/t_shelf_list.tt2 index 1ccf6b6ca7..33e53200ec 100644 --- a/Open-ILS/src/templates/staff/circ/holds/t_shelf_list.tt2 +++ b/Open-ILS/src/templates/staff/circ/holds/t_shelf_list.tt2 @@ -20,35 +20,35 @@ - - - - - - - - - - - - - - diff --git a/Open-ILS/src/templates/staff/circ/patron/t_holds_list.tt2 b/Open-ILS/src/templates/staff/circ/patron/t_holds_list.tt2 index 6e0d065e3b..d3bbae0a29 100644 --- a/Open-ILS/src/templates/staff/circ/patron/t_holds_list.tt2 +++ b/Open-ILS/src/templates/staff/circ/patron/t_holds_list.tt2 @@ -7,35 +7,35 @@ - - - - - - - - - - - - - - diff --git a/Open-ILS/src/templates/staff/navbar.tt2 b/Open-ILS/src/templates/staff/navbar.tt2 index 917672f394..3ce1a65dc5 100644 --- a/Open-ILS/src/templates/staff/navbar.tt2 +++ b/Open-ILS/src/templates/staff/navbar.tt2 @@ -80,6 +80,12 @@
  • + + + [% l('Pull List for Hold Requests') %] + +
  • +
  • [% l('Renew Items') %] diff --git a/Open-ILS/web/js/ui/default/staff/circ/holds/app.js b/Open-ILS/web/js/ui/default/staff/circ/holds/app.js index 20915c78ba..bf0bc5b5fa 100644 --- a/Open-ILS/web/js/ui/default/staff/circ/holds/app.js +++ b/Open-ILS/web/js/ui/default/staff/circ/holds/app.js @@ -20,18 +20,37 @@ angular.module('egHoldsApp', resolve : resolver }); + $routeProvider.when('/circ/holds/pull', { + templateUrl: './circ/holds/t_pull', + controller: 'HoldsPullListCtrl', + resolve : resolver + }); + + $routeProvider.when('/circ/holds/pull/:hold_id', { + templateUrl: './circ/holds/t_pull', + controller: 'HoldsPullListCtrl', + resolve : resolver + }); + $routeProvider.otherwise({redirectTo : '/circ/holds/shelf'}); }) +.factory('holdUiSvc', function() { + return { + holds : [] // cache + } +}) + .controller('HoldsShelfCtrl', - ['$scope','$q','$routeParams','$window','$location','egCore','egHolds','egCirc','egGridDataProvider', -function($scope , $q , $routeParams , $window , $location , egCore , egHolds , egCirc , egGridDataProvider) { + ['$scope','$q','$routeParams','$window','$location','egCore','egHolds','egHoldGridActions','egCirc','egGridDataProvider', +function($scope , $q , $routeParams , $window , $location , egCore , egHolds , egHoldGridActions , egCirc , egGridDataProvider) { $scope.detail_hold_id = $routeParams.hold_id; var hold_ids = []; var holds = []; var clear_mode = false; $scope.gridControls = {}; + $scope.grid_actions = egHoldGridActions; function fetch_holds(offset, count) { var ids = hold_ids.slice(offset, offset + count); @@ -46,6 +65,14 @@ function($scope , $q , $routeParams , $window , $location , egCore , egHolds , e var provider = egGridDataProvider.instance({}); $scope.gridDataProvider = provider; + function refresh_page() { + holds = []; + hold_ids = []; + provider.refresh(); + } + // called after any egHoldGridActions action occurs + $scope.grid_actions.refresh = refresh_page; + provider.get = function(offset, count) { // see if we have the requested range cached @@ -84,13 +111,6 @@ function($scope , $q , $routeParams , $window , $location , egCore , egHolds , e return deferred.promise; } - function refresh_page() { - holds = []; - hold_ids = []; - provider.refresh(); - } - - // re-draw the grid when user changes the org selector $scope.pickup_ou = egCore.org.get(egCore.auth.user().ws_ou()); $scope.$watch('pickup_ou', function(newVal, oldVal) { @@ -123,74 +143,6 @@ function($scope , $q , $routeParams , $window , $location , egCore , egHolds , e $scope.show_active = function() { clear_mode = false; refresh_page() } $scope.disable_clear = function() { return clearing || !clear_mode } - // action handlers - // TODO: These are copied directly from patron/holds.js. - // Consider refactoring / consolidating. - $scope.cancel_hold = function(items) { - var hold_ids = items.filter(function(item) { - return !item.hold.cancel_time(); - }).map(function(item) {return item.hold.id()}); - - return egHolds.cancel_holds(hold_ids).then(refresh_page); - } - - // jump to circ list for either 1) the targeted copy or - // 2) the hold target copy for copy-level holds - $scope.show_recent_circs = function(items) { - if (items.length && (copy = items[0].copy)) { - var url = $location.path( - '/cat/item/' + copy.id() + '/circ_list').absUrl(); - $window.open(url, '_blank').focus(); - } - } - - function generic_update(items, action) { - if (!items.length) return $q.when(); - var hold_ids = items.map(function(item) {return item.hold.id()}); - return egHolds[action](hold_ids).then(refresh_page); - } - - $scope.set_copy_quality = function(items) { - generic_update(items, 'set_copy_quality'); } - $scope.edit_pickup_lib = function(items) { - generic_update(items, 'edit_pickup_lib'); } - $scope.edit_notify_prefs = function(items) { - generic_update(items, 'edit_notify_prefs'); } - $scope.edit_dates = function(items) { - generic_update(items, 'edit_dates'); } - $scope.suspend = function(items) { - generic_update(items, 'suspend_holds'); } - $scope.activate = function(items) { - generic_update(items, 'activate_holds'); } - $scope.set_top_of_queue = function(items) { - generic_update(items, 'set_top_of_queue'); } - $scope.clear_top_of_queue = function(items) { - generic_update(items, 'clear_top_of_queue'); } - $scope.transfer_to_marked_title = function(items) { - generic_update(items, 'transfer_to_marked_title'); } - - $scope.mark_damaged = function(items) { - var copy_ids = items - .filter(function(item) { return Boolean(item.copy) }) - .map(function(item) { return item.copy.id() }); - if (copy_ids.length) - egCirc.mark_damaged(copy_ids).then(refresh_page); - } - - $scope.mark_missing = function(items) { - var copy_ids = items - .filter(function(item) { return Boolean(item.copy) }) - .map(function(item) { return item.copy.id() }); - if (copy_ids.length) - egCirc.mark_missing(copy_ids).then(refresh_page); - } - - $scope.retarget = function(items) { - var hold_ids = items.map(function(item) { return item.hold.id() }); - egHolds.retarget(hold_ids).then(refresh_page); - } - - // udpate the in-grid hold with the clear-shelf cached response info. function handle_clear_cache_resp(resp) { if (!angular.isArray(resp)) resp = [resp]; @@ -252,5 +204,63 @@ function($scope , $q , $routeParams , $window , $location , egCore , egHolds , e refresh_page(); -}]); +}]) + +.controller('HoldsPullListCtrl', + ['$scope','$q','$routeParams','$window','$location','egCore','egHolds','egCirc','egGridDataProvider','egHoldGridActions','holdUiSvc', +function($scope , $q , $routeParams , $window , $location , egCore , egHolds , egCirc , egGridDataProvider , egHoldGridActions , holdUiSvc) { + $scope.detail_hold_id = $routeParams.hold_id; + + var provider = egGridDataProvider.instance({}); + $scope.gridDataProvider = provider; + + $scope.grid_actions = egHoldGridActions; + $scope.grid_actions.refresh = function() { + holdUiSvc.holds = []; + provider.refresh(); + } + + provider.get = function(offset, count) { + + if (holdUiSvc.holds[offset]) { + return provider.arrayNotifier(holdUiSvc.holds, offset, count); + } + + var deferred = $q.defer(); + var recv_index = 0; + + // fetch the IDs + egCore.net.request( + 'open-ils.circ', + 'open-ils.circ.hold_pull_list.fleshed.stream', + egCore.auth.token(), count, offset + ).then( + deferred.resolve, null, + function(hold_data) { + egHolds.local_flesh(hold_data); + holdUiSvc.holds[offset + recv_index++] = hold_data; + deferred.notify(hold_data); + } + ); + + return deferred.promise; + } + + $scope.detail_view = function(action, user_data, items) { + if (h = items[0]) { + $location.path('/circ/holds/pull/' + h.hold.id()); + } + } + + $scope.list_view = function(items) { + $location.path('/circ/holds/pull'); + } + + // when the detail hold is fetched (and updated), update the bib + // record summary display record id. + $scope.set_hold = function(hold_data) { + $scope.detail_hold_record_id = hold_data.mvr.doc_id(); + } + +}]) diff --git a/Open-ILS/web/js/ui/default/staff/circ/patron/holds.js b/Open-ILS/web/js/ui/default/staff/circ/patron/holds.js index 9933c4261c..d49c15a7aa 100644 --- a/Open-ILS/web/js/ui/default/staff/circ/patron/holds.js +++ b/Open-ILS/web/js/ui/default/staff/circ/patron/holds.js @@ -5,13 +5,14 @@ angular.module('egPatronApp').controller('PatronHoldsCtrl', ['$scope','$q','$routeParams','egCore','egUser','patronSvc', - 'egGridDataProvider','egHolds','$window','$location','egCirc', + 'egGridDataProvider','egHolds','$window','$location','egCirc','egHoldGridActions', function($scope, $q, $routeParams, egCore, egUser, patronSvc, - egGridDataProvider , egHolds , $window , $location , egCirc) { + egGridDataProvider , egHolds , $window , $location , egCirc, egHoldGridActions) { $scope.initTab('holds', $routeParams.id); $scope.holds_display = 'main'; $scope.detail_hold_id = $routeParams.hold_id; + $scope.grid_actions = egHoldGridActions; function refresh_all() { patronSvc.refreshPrimary(); @@ -19,6 +20,7 @@ function($scope, $q, $routeParams, egCore, egUser, patronSvc, patronSvc.hold_ids = []; provider.refresh() } + $scope.grid_actions.refresh = refresh_all; $scope.show_main_list = function() { // don't need a full reset_page() to swap tabs @@ -41,10 +43,11 @@ function($scope, $q, $routeParams, egCore, egUser, patronSvc, function fetchHolds(offset, count) { var ids = patronSvc.hold_ids.slice(offset, offset + count); - return egHolds.fetch_holds(ids).then( - function() { $scope.loading = false; }, - null, - function(hold_data) { patronSvc.holds.push(hold_data) } + return egHolds.fetch_holds(ids).then(null, null, + function(hold_data) { + patronSvc.holds.push(hold_data); + return hold_data; + } ); } @@ -82,69 +85,6 @@ function($scope, $q, $routeParams, egCore, egUser, patronSvc, return deferred.promise; } - $scope.cancel_hold = function(items) { - var hold_ids = items.filter(function(item) { - return !item.hold.cancel_time(); - }).map(function(item) {return item.hold.id()}); - - return egHolds.cancel_holds(hold_ids).then(refresh_all); - } - - // jump to circ list for either 1) the targeted copy or - // 2) the hold target copy for copy-level holds - $scope.show_recent_circs = function(items) { - if (items.length && (copy = items[0].copy)) { - var url = $location.path( - '/cat/item/' + copy.id() + '/circ_list').absUrl(); - $window.open(url, '_blank').focus(); - } - } - - function generic_update(items, action) { - if (!items.length) return $q.when(); - var hold_ids = items.map(function(item) {return item.hold.id()}); - return egHolds[action](hold_ids).then(refresh_all); - } - - $scope.set_copy_quality = function(items) { - generic_update(items, 'set_copy_quality'); } - $scope.edit_pickup_lib = function(items) { - generic_update(items, 'edit_pickup_lib'); } - $scope.edit_notify_prefs = function(items) { - generic_update(items, 'edit_notify_prefs'); } - $scope.edit_dates = function(items) { - generic_update(items, 'edit_dates'); } - $scope.suspend = function(items) { - generic_update(items, 'suspend_holds'); } - $scope.activate = function(items) { - generic_update(items, 'activate_holds'); } - $scope.set_top_of_queue = function(items) { - generic_update(items, 'set_top_of_queue'); } - $scope.clear_top_of_queue = function(items) { - generic_update(items, 'clear_top_of_queue'); } - $scope.transfer_to_marked_title = function(items) { - generic_update(items, 'transfer_to_marked_title'); } - - $scope.mark_damaged = function(items) { - var copy_ids = items - .filter(function(item) { return Boolean(item.copy) }) - .map(function(item) { return item.copy.id() }); - if (copy_ids.length) - egCirc.mark_damaged(copy_ids).then(refresh_all); - } - $scope.mark_missing = function(items) { - var copy_ids = items - .filter(function(item) { return Boolean(item.copy) }) - .map(function(item) { return item.copy.id() }); - if (copy_ids.length) - egCirc.mark_missing(copy_ids).then(refresh_all); - } - - $scope.retarget = function(items) { - var hold_ids = items.map(function(item) { return item.hold.id() }); - egHolds.retarget(hold_ids).then(refresh_all); - } - $scope.print = function() { var holds = []; angular.forEach(patronSvc.holds, function(item) { diff --git a/Open-ILS/web/js/ui/default/staff/circ/services/holds.js b/Open-ILS/web/js/ui/default/staff/circ/services/holds.js index 6ae72fd840..161722c6a7 100644 --- a/Open-ILS/web/js/ui/default/staff/circ/services/holds.js +++ b/Open-ILS/web/js/ui/default/staff/circ/services/holds.js @@ -12,18 +12,47 @@ function($modal , $q , egCore , egAlertDialog , egConfirmDialog , egAlertDialog) var service = {}; service.fetch_holds = function(hold_ids) { + var deferred = $q.defer(); - return egCore.net.request( - 'open-ils.circ', - 'open-ils.circ.hold.details.batch.retrieve.authoritative', - egCore.auth.token(), hold_ids - - ).then(null, null, function(hold_data) { - var hold = hold_data.hold; - hold_data.id = hold.id(); - service.local_flesh(hold_data); - return hold_data; - }); + // FIXME: large batches using .authoritative result in many + // stranded cstore backends on the server. Needs investigation. + // For now, collect holds in a series of small batches. + // Fetch them serially both to avoid the above problem and + // to maintain order. + var batch_size = 5; + var index = 0; + + function one_batch() { + var ids = hold_ids.slice(index, index + batch_size) + .filter(function(id) {return Boolean(id)}) // avoid nulls + + console.debug('egHolds.fetch_holds => ' + ids); + index += batch_size; + + if (!ids.length) { + deferred.resolve(); + return; + } + + egCore.net.request( + 'open-ils.circ', + 'open-ils.circ.hold.details.batch.retrieve.authoritative', + egCore.auth.token(), ids + + ).then( + one_batch, // kick off the next batch + null, + function(hold_data) { + var hold = hold_data.hold; + hold_data.id = hold.id(); + service.local_flesh(hold_data); + deferred.notify(hold_data); + } + ); + } + + one_batch(); // kick it off + return deferred.promise; } @@ -335,6 +364,7 @@ function($modal , $q , egCore , egAlertDialog , egConfirmDialog , egAlertDialog) var hold = hold_data.hold; hold.pickup_lib(egCore.org.get(hold.pickup_lib())); hold.current_shelf_lib(egCore.org.get(hold.current_shelf_lib())); + hold_data.id = hold.id(); // current_copy is not always fleshed in the API if (hold.current_copy() && typeof hold.current_copy() != 'object') @@ -344,6 +374,91 @@ function($modal , $q , egCore , egAlertDialog , egConfirmDialog , egAlertDialog) return service; }]) +/** + * Action handlers for the common Hold grid UI. + * These generally scrub the data for valid input then pass the + * holds / copies / etc. off to the relevant action in egHolds or egCirc. + * + * Caller must apply a reset_page function, which is called after + * most actionis are performed. + */ +.factory('egHoldGridActions', + ['$window','$location','egCore','egHolds','egCirc', +function($window , $location , egCore , egHolds , egCirc) { + + var service = {}; + + service.refresh = function() { + console.error('egHoldGridActions.refresh not defined!'); + } + + service.cancel_hold = function(items) { + var hold_ids = items.filter(function(item) { + return !item.hold.cancel_time(); + }).map(function(item) {return item.hold.id()}); + + return egHolds.cancel_holds(hold_ids).then(service.refresh); + } + + // jump to circ list for either 1) the targeted copy or + // 2) the hold target copy for copy-level holds + service.show_recent_circs = function(items) { + if (items.length && (copy = items[0].copy)) { + var url = $location.path( + '/cat/item/' + copy.id() + '/circ_list').absUrl(); + $window.open(url, '_blank').focus(); + } + } + + function generic_update(items, action) { + if (!items.length) return $q.when(); + var hold_ids = items.map(function(item) {return item.hold.id()}); + return egHolds[action](hold_ids).then(service.refresh); + } + + service.set_copy_quality = function(items) { + generic_update(items, 'set_copy_quality'); } + service.edit_pickup_lib = function(items) { + generic_update(items, 'edit_pickup_lib'); } + service.edit_notify_prefs = function(items) { + generic_update(items, 'edit_notify_prefs'); } + service.edit_dates = function(items) { + generic_update(items, 'edit_dates'); } + service.suspend = function(items) { + generic_update(items, 'suspend_holds'); } + service.activate = function(items) { + generic_update(items, 'activate_holds'); } + service.set_top_of_queue = function(items) { + generic_update(items, 'set_top_of_queue'); } + service.clear_top_of_queue = function(items) { + generic_update(items, 'clear_top_of_queue'); } + service.transfer_to_marked_title = function(items) { + generic_update(items, 'transfer_to_marked_title'); } + + service.mark_damaged = function(items) { + var copy_ids = items + .filter(function(item) { return Boolean(item.copy) }) + .map(function(item) { return item.copy.id() }); + if (copy_ids.length) + egCirc.mark_damaged(copy_ids).then(service.refresh); + } + + service.mark_missing = function(items) { + var copy_ids = items + .filter(function(item) { return Boolean(item.copy) }) + .map(function(item) { return item.copy.id() }); + if (copy_ids.length) + egCirc.mark_missing(copy_ids).then(service.refresh); + } + + service.retarget = function(items) { + var hold_ids = items.map(function(item) { return item.hold.id() }); + egHolds.retarget(hold_ids).then(service.refresh); + } + + return service; +}]) + /** * Hold details interface */ -- 2.11.0