1 angular.module('egHoldsApp',
2 ['ngRoute', 'ui.bootstrap', 'egCoreMod', 'egUiMod', 'egGridMod'])
4 .config(function($routeProvider, $locationProvider, $compileProvider) {
5 $locationProvider.html5Mode(true);
6 $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|blob):/); // grid export
8 var resolver = {delay :
9 ['egStartup', function(egStartup) {return egStartup.go()}]}
11 $routeProvider.when('/circ/holds/shelf', {
12 templateUrl: './circ/holds/t_shelf',
13 controller: 'HoldsShelfCtrl',
17 $routeProvider.when('/circ/holds/shelf/:hold_id', {
18 templateUrl: './circ/holds/t_shelf',
19 controller: 'HoldsShelfCtrl',
23 $routeProvider.when('/circ/holds/pull', {
24 templateUrl: './circ/holds/t_pull',
25 controller: 'HoldsPullListCtrl',
29 $routeProvider.when('/circ/holds/pull/:hold_id', {
30 templateUrl: './circ/holds/t_pull',
31 controller: 'HoldsPullListCtrl',
35 $routeProvider.otherwise({redirectTo : '/circ/holds/shelf'});
39 .controller('HoldsShelfCtrl',
40 ['$scope','$q','$routeParams','$window','$location','egCore','egHolds','egHoldGridActions','egCirc','egGridDataProvider','egProgressDialog',
41 function($scope , $q , $routeParams , $window , $location , egCore , egHolds , egHoldGridActions , egCirc , egGridDataProvider , egProgressDialog) {
42 $scope.detail_hold_id = $routeParams.hold_id;
45 var clear_mode = false;
46 $scope.gridControls = {};
47 $scope.grid_actions = egHoldGridActions;
49 var provider = egGridDataProvider.instance({});
50 $scope.gridDataProvider = provider;
52 function refresh_page() {
58 // called after any egHoldGridActions action occurs
59 $scope.grid_actions.refresh = refresh_page;
61 provider.get = function(offset, count) {
63 // if in clear mode...
64 if (clear_mode && holds.length) {
65 if (!all_holds.length) all_holds = holds;
66 holds = holds.filter(function(h) { return h.hold.clear_me });
67 hold_count = holds.length;
68 return provider.arrayNotifier(holds, offset, count);
69 } else if (all_holds.length) {
71 hold_count = holds.length;
75 // see if we have the requested range cached
77 return provider.arrayNotifier(holds, offset, count);
83 is_staff_request : 'true',
84 last_captured_hold: 'true',
85 capture_time : { not : null },
86 cs_id : 8, // on holds shelf
88 fulfillment_time : null,
89 current_shelf_lib : $scope.pickup_ou.id()
92 var order_by = [{ shelf_expire_time : null }];
94 // NOTE: Server sorting is currently disabled entirely by the
95 // first clause in this 'if'. This is perfectly fine because
96 // clientsort always runs inside the arrayNotifier implementation
97 // in the egGrid code. However, in order to retain the memory
98 // of sorting constraints placed on us by the current server-side
99 // code, an initial "cannot sort these" array and test is added
100 // here. An alternate implementation might be to map fields to
101 // query positions, thus allowing positional ORDER BY clauses.
102 // With as many fields as the wide hold object has, this is
103 // non-trivial at the moment.
104 if (false && provider.sort && provider.sort.length) {
105 // A list of fields we can't sort on the server side. That's ok, because
106 // the grid is marked clientsort, so it always re-sorts in the browser.
108 'relative_queue_position',
109 'default_estimated_wait',
110 'min_estimated_wait',
114 'notification_count',
115 'last_notification_time',
117 'copy_location_order_position',
120 'usr_alias_or_display_name',
122 'usr_alias_or_first_given_name'
126 angular.forEach(provider.sort, function (c) {
127 if (!angular.isObject(c)) {
128 if (c.match(/^hold\./)) {
129 var i = c.replace('hold.','');
130 if (cannot_sort.includes(i)) return;
136 var i = Object.keys(c)[0];
137 var direction = c[i];
138 if (i.match(/^hold\./)) {
139 i = i.replace('hold.','');
140 if (cannot_sort.includes(i)) return;
142 ob[i] = {dir:direction};
149 egProgressDialog.open({max : 1, value : 0});
151 return egHolds.fetch_wide_holds(
155 return provider.arrayNotifier(holds, offset, count);
158 function(hold_data) {
160 hold_count = hold_data;
162 egProgressDialog.update({max:hold_count});
164 egProgressDialog.increment();
165 var new_item = { id : hold_data.id, hold : hold_data };
166 new_item.status_string =
167 egCore.strings['HOLD_STATUS_' + hold_data.hold_status]
168 || hold_data.hold_status;
171 if (hold_data.clear_me) holds.push(new_item);
172 all_holds.push(new_item);
174 holds.push(new_item);
178 ).finally(egProgressDialog.close);
181 // re-draw the grid when user changes the org selector
182 $scope.pickup_ou = egCore.org.get(egCore.auth.user().ws_ou());
183 $scope.$watch('pickup_ou', function(newVal, oldVal) {
184 if (newVal && newVal != oldVal)
188 $scope.detail_view = function(action, user_data, items) {
190 $location.path('/circ/holds/shelf/' + h.hold.id);
194 $scope.list_view = function(items) {
195 $location.path('/circ/holds/shelf');
198 // when the detail hold is fetched (and updated), update the bib
199 // record summary display record id.
200 $scope.set_hold = function(hold_data) {
201 $scope.detail_hold_record_id = hold_data.bre_id;
204 // manage active vs. clearable holds display
205 var clearing = false; // true if actively clearing holds (below)
206 $scope.is_clearing = function() { return clearing };
207 $scope.active_mode = function() {return !clear_mode}
208 $scope.clear_mode = function() {return clear_mode}
209 $scope.show_clearable = function() { clear_mode = true; provider.refresh() }
210 $scope.show_active = function() { clear_mode = false; provider.refresh() }
211 $scope.disable_clear = function() { return clearing || !clear_mode }
213 // udpate the in-grid hold with the clear-shelf cached response info.
214 function handle_clear_cache_resp(resp) {
215 if (!angular.isArray(resp)) resp = [resp];
216 angular.forEach(resp, function(info) {
218 var grid_item = holds.filter(function(item) {
219 return item.hold.id == info.hold_details.id
222 var all_hold_item = all_holds.filter(function(item) {
223 return item.hold.id == info.hold_details.id
226 // there will be no grid item if the hold is off-page
228 grid_item.post_clear =
229 egCore.strings['CLEAR_SHELF_ACTION_' + info.action];
230 all_hold_item.post_clear =
231 egCore.strings['CLEAR_SHELF_ACTION_' + info.action];
237 $scope.clear_holds = function() {
239 $scope.clear_progress = {max : 0, value : 0};
241 // we want to see all processed holds, so (effectively) remove
243 $scope.gridControls.setLimit(1000, true);
245 // initiate clear shelf and grab cache key
248 'open-ils.circ.hold.clear_shelf.process',
249 egCore.auth.token(), $scope.pickup_ou.id(),
252 // request responses from the clear shelf cache
255 // clear shelf done; fetch the cached results.
260 'open-ils.circ.hold.clear_shelf.get_cache',
261 egCore.auth.token(), resp.cache_key, 1
262 ).then(null, null, handle_clear_cache_resp);
267 // handle streamed clear_shelf progress updates
270 $scope.clear_progress.max = resp.maximum;
272 $scope.clear_progress.value = resp.progress;
278 function map_prefix_to_subhash (h,pf) {
280 angular.forEach(Object.keys(h), function (k) {
281 if (k.startsWith(pf)) {
282 var nk = k.substr(pf.length);
289 $scope.print_shelf_list = function() {
290 var print_holds = [];
291 angular.forEach(holds, function(hold_data) {
293 print_holds.push(phold);
295 phold.status_string = hold_data.status_string;
297 phold.patron_first = hold_data.hold.usr_first_given_name;
298 phold.patron_last = hold_data.hold.usr_family_name;
299 phold.patron_alias = hold_data.hold.usr_alias;
300 phold.patron_barcode = hold_data.hold.ucard_barcode;
302 phold.title = hold_data.hold.title;
303 phold.author = hold_data.hold.author;
305 phold.hold = hold_data.hold;
306 phold.copy = map_prefix_to_subhash(hold_data.hold, 'cp_');
307 phold.volume = map_prefix_to_subhash(hold_data.hold, 'cn_');
308 phold.part = map_prefix_to_subhash(hold_data.hold, 'p_');
311 console.log(print_holds);
313 return egCore.print.print({
315 template : 'hold_shelf_list',
316 scope : {holds : print_holds}
324 .controller('HoldsPullListCtrl',
325 ['$scope','$q','$routeParams','$window','$location','egCore',
326 'egHolds','egCirc','egHoldGridActions','egProgressDialog',
327 function($scope , $q , $routeParams , $window , $location , egCore ,
328 egHolds , egCirc , egHoldGridActions , egProgressDialog) {
330 $scope.detail_hold_id = $routeParams.hold_id;
332 var cached_details = {};
333 var details_needed = {};
335 egCore.strings.setPageTitle(egCore.strings['PULL_LIST_TITLE']);
337 function current_query() {
338 var org_id = $scope.org_unit ? $scope.org_unit.id() :
339 egCore.auth.user().ws_ou();
340 return {'copy_circ_lib_id' : org_id};
343 $scope.gridControls = {
344 setQuery : current_query,
345 setSort : function() {
346 return ['copy_location_order_position','call_number_sort_key']
348 collectStarted : function(offset) {
349 // Launch an indeterminate -> semi-determinate progress
350 // modal. Using a determinate modal that starts counting
351 // on the post-grid holds data retrieval results in a modal
352 // that's stuck at 0% for most of its life, which is aggravating.
353 egProgressDialog.open();
355 itemRetrieved : function(item) {
356 egProgressDialog.increment();
357 if (!cached_details[item.id]) {
358 details_needed[item.id] = item;
361 allItemsRetrieved : function() {
362 flesh_holds().finally(egProgressDialog.close);
367 // Fetches hold detail data for each hold in the grid and links
368 // the detail data to the related grid item so egHoldGridActions
369 // and friends have access to holds data they understand.
370 // Only fetch not-yet-cached data.
371 function flesh_holds() {
372 egProgressDialog.increment();
374 // Start by fleshing hold details from our cached data.
375 var items = $scope.gridControls.allItems();
376 angular.forEach(items, function(item) {
377 if (!cached_details[item.id]) return $q.when();
378 angular.forEach(cached_details[item.id],
379 function(val, key) { item[key] = val })
382 // Exit if all needed details were already cached
383 if (Object.keys(details_needed).length == 0) return $q.when();
385 return egCore.net.request(
387 'open-ils.circ.hold.details.batch.retrieve.authoritative',
388 egCore.auth.token(), Object.keys(details_needed), {
392 ).then(null, null, function(hold_info) {
393 egProgressDialog.increment();
395 // check if this is a staff-created hold
396 // i.e., requestor is not the same as the user
397 hold_info['_is_staff_hold'] = hold_info.hold.requestor() != hold_info.hold.usr().id();
399 var hold_id = hold_info.hold.id();
400 cached_details[hold_id] = hold_info;
401 var item = details_needed[hold_id];
402 delete details_needed[hold_id];
404 // flesh the grid item from the blob of hold data.
405 angular.forEach(hold_info,
406 function(val, key) { item[key] = val });
411 $scope.grid_actions = egHoldGridActions;
412 $scope.grid_actions.refresh = function() {
413 cached_details = {}; // un-cache details after edit actions.
414 $scope.gridControls.refresh();
417 $scope.detail_view = function(action, user_data, items) {
419 $location.path('/circ/holds/pull/' + h.hold.id());
423 $scope.list_view = function(items) {
424 $location.path('/circ/holds/pull');
427 // when the detail hold is fetched (and updated), update the bib
428 // record summary display record id.
429 $scope.set_hold = function(hold_data) {
430 $scope.detail_hold_record_id = hold_data.mvr.doc_id();
433 // By default, this action is hidded from the UI, but leaving it
434 // here in case it's needed in the future
435 $scope.print_list_alt = function() {
436 var url = '/opac/extras/circ/alt_holds_print.html';
437 var win = $window.open(url, '_blank');
438 win.ses = function() {return egCore.auth.token()};
443 $scope.print_full_list = function() {
444 var print_holds = [];
445 egProgressDialog.open({value : 0});
447 // collect the full list of holds
450 'open-ils.circ.hold_pull_list.fleshed.stream',
451 egCore.auth.token(), 10000, 0
454 console.debug('printing ' + print_holds.length + ' holds');
456 // holds fetched, send to print
459 template : 'hold_pull_list',
460 scope : {holds : print_holds}
464 function(hold_data) {
465 egProgressDialog.increment();
466 egHolds.local_flesh(hold_data);
467 print_holds.push(hold_data);
468 hold_data.title = hold_data.mvr.title();
469 hold_data.author = hold_data.mvr.author();
470 hold_data.hold = egCore.idl.toHash(hold_data.hold);
471 hold_data.copy = egCore.idl.toHash(hold_data.copy);
472 hold_data.volume = egCore.idl.toHash(hold_data.volume);
473 hold_data.part = egCore.idl.toHash(hold_data.part);
475 ).finally(egProgressDialog.close);
478 $scope.update_org_unit = function (org) {
479 $scope.org_unit = org;
480 $scope.gridControls.setQuery(current_query());
481 $scope.gridControls.refresh();
484 $scope.cant_have_volumes =
485 function (id) { return !egCore.org.CanHaveVolumes(id); };