LP#1792621: Ignore deleted items on hold shelf
[Evergreen.git] / Open-ILS / web / js / ui / default / staff / circ / holds / app.js
1 angular.module('egHoldsApp', 
2     ['ngRoute', 'ui.bootstrap', 'egCoreMod', 'egUiMod', 'egGridMod'])
3
4 .config(function($routeProvider, $locationProvider, $compileProvider) {
5     $locationProvider.html5Mode(true);
6     $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|blob):/); // grid export
7
8     var resolver = {delay : 
9         ['egStartup', function(egStartup) {return egStartup.go()}]}
10
11     $routeProvider.when('/circ/holds/shelf', {
12         templateUrl: './circ/holds/t_shelf',
13         controller: 'HoldsShelfCtrl',
14         resolve : resolver
15     });
16
17     $routeProvider.when('/circ/holds/shelf/:hold_id', {
18         templateUrl: './circ/holds/t_shelf',
19         controller: 'HoldsShelfCtrl',
20         resolve : resolver
21     });
22
23     $routeProvider.when('/circ/holds/pull', {
24         templateUrl: './circ/holds/t_pull',
25         controller: 'HoldsPullListCtrl',
26         resolve : resolver
27     });
28
29     $routeProvider.when('/circ/holds/pull/:hold_id', {
30         templateUrl: './circ/holds/t_pull',
31         controller: 'HoldsPullListCtrl',
32         resolve : resolver
33     });
34
35     $routeProvider.otherwise({redirectTo : '/circ/holds/shelf'});
36 })
37
38
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;
43
44     var holds = [];
45     var clear_mode = false;
46     $scope.gridControls = {};
47     $scope.grid_actions = egHoldGridActions;
48
49     var provider = egGridDataProvider.instance({});
50     $scope.gridDataProvider = provider;
51
52     function refresh_page() {
53         hold_count = 0;
54         holds = [];
55         all_holds = [];
56         provider.refresh();
57     }
58     // called after any egHoldGridActions action occurs
59     $scope.grid_actions.refresh = refresh_page;
60
61     provider.get = function(offset, count) {
62
63         // if in clear mode...
64         if (clear_mode && holds.length) {
65             if (!all_holds.legnth) 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) {
70             holds = all_holds;
71             hold_count = holds.length;
72             all_holds = [];
73         }
74
75         // see if we have the requested range cached
76         if (holds[offset]) {
77             return provider.arrayNotifier(holds, offset, count);
78         }
79
80         hold_count = 0;
81         holds = [];
82         var restrictions = {
83                 is_staff_request  : 'true',
84                 last_captured_hold: 'true',
85                 capture_time      : { not : null },
86                 cs_id             : 8, // on holds shelf
87                 cp_deleted        : 'f',
88                 fulfillment_time  : null,
89                 current_shelf_lib : $scope.pickup_ou.id()
90         };
91
92         var order_by = [{ shelf_expire_time : null }];
93         if (provider.sort && provider.sort.length) {
94             order_by = [];
95             angular.forEach(provider.sort, function (c) {
96                 if (!angular.isObject(c)) {
97                     if (c.match(/^hold\./)) {
98                         var i = c.replace('hold.','');
99                         var ob = {};
100                         ob[i] = null;
101                         order_by.push(ob);
102                     }
103                 } else {
104                     var i = Object.keys(c)[0];
105                     var direction = c[i];
106                     if (i.match(/^hold\./)) {
107                         i = i.replace('hold.','');
108                         var ob = {}
109                         ob[i] = {dir:direction};
110                         order_by.push(ob);
111                     }
112                 }
113             });
114         }
115
116         egProgressDialog.open({max : 1, value : 0});
117         var first = true;
118         return egHolds.fetch_wide_holds(
119             restrictions,
120             order_by
121         ).then(function () {
122                 return provider.arrayNotifier(holds, offset, count);
123             },
124             null,
125             function(hold_data) { 
126                 if (first) {
127                     hold_count = hold_data;
128                     first = false;
129                     egProgressDialog.update({max:hold_count});
130                 } else {
131                     egProgressDialog.increment();
132                     var new_item = { id : hold_data.id, hold : hold_data };
133                     new_item.status_string =
134                         egCore.strings['HOLD_STATUS_' + hold_data.hold_status]
135                         || hold_data.hold_status;
136
137                     if (clear_mode) {
138                         if (hold_data.clear_me) holds.push(new_item);
139                         all_holds.push(new_item);
140                     } else {
141                         holds.push(new_item);
142                     }
143                 }
144             }
145         ).finally(egProgressDialog.close);
146     }
147
148     // re-draw the grid when user changes the org selector
149     $scope.pickup_ou = egCore.org.get(egCore.auth.user().ws_ou());
150     $scope.$watch('pickup_ou', function(newVal, oldVal) {
151         if (newVal && newVal != oldVal) 
152             refresh_page();
153     });
154
155     $scope.detail_view = function(action, user_data, items) {
156         if (h = items[0]) {
157             $location.path('/circ/holds/shelf/' + h.hold.id);
158         }
159     }
160
161     $scope.list_view = function(items) {
162         $location.path('/circ/holds/shelf');
163     }
164
165     // when the detail hold is fetched (and updated), update the bib
166     // record summary display record id.
167     $scope.set_hold = function(hold_data) {
168         $scope.detail_hold_record_id = hold_data.hold.record_id;
169     }
170
171     // manage active vs. clearable holds display
172     var clearing = false; // true if actively clearing holds (below)
173     $scope.is_clearing = function() { return clearing };
174     $scope.active_mode = function() {return !clear_mode}
175     $scope.clear_mode = function() {return clear_mode}
176     $scope.show_clearable = function() { clear_mode = true; provider.refresh() }
177     $scope.show_active = function() { clear_mode = false; provider.refresh() }
178     $scope.disable_clear = function() { return clearing || !clear_mode }
179
180     // udpate the in-grid hold with the clear-shelf cached response info.
181     function handle_clear_cache_resp(resp) {
182         if (!angular.isArray(resp)) resp = [resp];
183         angular.forEach(resp, function(info) {
184             if (info.action) {
185                 var grid_item = holds.filter(function(item) {
186                     return item.hold.id == info.hold_details.id
187                 })[0];
188
189                 var all_hold_item = all_holds.filter(function(item) {
190                     return item.hold.id == info.hold_details.id
191                 })[0];
192
193                 // there will be no grid item if the hold is off-page
194                 if (grid_item) {
195                     grid_item.post_clear = 
196                         egCore.strings['CLEAR_SHELF_ACTION_' + info.action];
197                     all_hold_item.post_clear = 
198                         egCore.strings['CLEAR_SHELF_ACTION_' + info.action];
199                 }
200             }
201         });
202     }
203
204     $scope.clear_holds = function() {
205         clearing = true;
206         $scope.clear_progress = {max : 0, value : 0};
207
208         // we want to see all processed holds, so (effectively) remove
209         // the grid limit.
210         $scope.gridControls.setLimit(1000, true); 
211
212         // initiate clear shelf and grab cache key
213         egCore.net.request(
214             'open-ils.circ',
215             'open-ils.circ.hold.clear_shelf.process',
216             egCore.auth.token(), $scope.pickup_ou.id(),
217             null, 1
218
219         // request responses from the clear shelf cache
220         ).then(
221             
222             // clear shelf done; fetch the cached results.
223             function(resp) {
224                 clearing = false;
225                 egCore.net.request(
226                     'open-ils.circ',
227                     'open-ils.circ.hold.clear_shelf.get_cache',
228                     egCore.auth.token(), resp.cache_key, 1
229                 ).then(null, null, handle_clear_cache_resp);
230             }, 
231
232             null,
233
234             // handle streamed clear_shelf progress updates
235             function(resp) {
236                 if (resp.maximum) 
237                     $scope.clear_progress.max = resp.maximum;
238                 if (resp.progress)
239                     $scope.clear_progress.value = resp.progress;
240             }
241
242         );
243     }
244
245     function map_prefix_to_subhash (h,pf) {
246         var newhash = {};
247         angular.forEach(Object.keys(h), function (k) {
248             if (k.startsWith(pf)) {
249                 var nk = k.substr(pf.length);
250                 newhash[nk] = h[k];
251             }
252         });
253         return newhash;
254     }
255
256     $scope.print_shelf_list = function() {
257         var print_holds = [];
258         angular.forEach(holds, function(hold_data) {
259             var phold = {};
260             print_holds.push(phold);
261
262             phold.status_string = hold_data.status_string;
263
264             phold.patron_first = hold_data.hold.usr_first_given_name;
265             phold.patron_last = hold_data.hold.usr_family_name;
266             phold.patron_alias = hold_data.hold.usr_alias;
267             phold.patron_barcode = hold_data.hold.ucard_barcode;
268
269             phold.title = hold_data.hold.title;
270             phold.author = hold_data.hold.author;
271
272             phold.hold = hold_data.hold;
273             phold.copy = map_prefix_to_subhash(hold_data.hold, 'cp_');
274             phold.volume = map_prefix_to_subhash(hold_data.hold, 'cn_');
275             phold.part = map_prefix_to_subhash(hold_data.hold, 'p_');
276         });
277
278         console.log(print_holds);
279
280         return egCore.print.print({
281             context : 'default', 
282             template : 'hold_shelf_list', 
283             scope : {holds : print_holds}
284         });
285     }
286
287     refresh_page();
288
289 }])
290
291 .controller('HoldsPullListCtrl',
292        ['$scope','$q','$routeParams','$window','$location','egCore',
293         'egHolds','egCirc','egHoldGridActions','egProgressDialog',
294 function($scope , $q , $routeParams , $window , $location , egCore , 
295          egHolds , egCirc , egHoldGridActions , egProgressDialog) {
296
297     $scope.detail_hold_id = $routeParams.hold_id;
298
299     var cached_details = {};
300     var details_needed = {};
301
302     $scope.gridControls = {
303         setQuery : function() {
304             return {'copy_circ_lib_id' : egCore.auth.user().ws_ou()}
305         },
306         setSort : function() {
307             return ['copy_location_order_position','call_number_sort_key']
308         },
309         collectStarted : function(offset) {
310             // Launch an indeterminate -> semi-determinate progress
311             // modal.  Using a determinate modal that starts counting
312             // on the post-grid holds data retrieval results in a modal
313             // that's stuck at 0% for most of its life, which is aggravating.
314             egProgressDialog.open();
315         },
316         itemRetrieved : function(item) {
317             egProgressDialog.increment();
318             if (!cached_details[item.id]) {
319                 details_needed[item.id] = item;
320             }
321         },
322         allItemsRetrieved : function() {
323             flesh_holds().finally(egProgressDialog.close);
324         }
325     }
326
327
328     // Fetches hold detail data for each hold in the grid and links
329     // the detail data to the related grid item so egHoldGridActions 
330     // and friends have access to holds data they understand.
331     // Only fetch not-yet-cached data.
332     function flesh_holds() {
333         egProgressDialog.increment();
334
335         // Start by fleshing hold details from our cached data.
336         var items = $scope.gridControls.allItems();
337         angular.forEach(items, function(item) {
338             if (!cached_details[item.id]) return $q.when();
339             angular.forEach(cached_details[item.id], 
340                 function(val, key) { item[key] = val })
341         });
342
343         // Exit if all needed details were already cached
344         if (Object.keys(details_needed).length == 0) return $q.when();
345
346         return egCore.net.request(
347             'open-ils.circ',
348             'open-ils.circ.hold.details.batch.retrieve.authoritative',
349             egCore.auth.token(), Object.keys(details_needed), {
350                 include_usr : true
351             }
352
353         ).then(null, null, function(hold_info) {
354             egProgressDialog.increment();
355
356             // check if this is a staff-created hold
357             // i.e., requestor is not the same as the user
358             hold_info['_is_staff_hold'] = hold_info.hold.requestor() != hold_info.hold.usr().id();
359
360             var hold_id = hold_info.hold.id();
361             cached_details[hold_id] = hold_info;
362             var item = details_needed[hold_id];
363             delete details_needed[hold_id];
364
365             // flesh the grid item from the blob of hold data.
366             angular.forEach(hold_info, 
367                 function(val, key) { item[key] = val });
368
369         });
370     }
371
372     $scope.grid_actions = egHoldGridActions;
373     $scope.grid_actions.refresh = function() {
374         cached_details = {}; // un-cache details after edit actions.
375         $scope.gridControls.refresh();
376     }
377
378     $scope.detail_view = function(action, user_data, items) {
379         if (h = items[0]) {
380             $location.path('/circ/holds/pull/' + h.hold.id());
381         }
382     }
383
384     $scope.list_view = function(items) {
385         $location.path('/circ/holds/pull');
386     }
387
388     // when the detail hold is fetched (and updated), update the bib
389     // record summary display record id.
390     $scope.set_hold = function(hold_data) {
391         $scope.detail_hold_record_id = hold_data.mvr.doc_id();
392     }
393
394     // By default, this action is hidded from the UI, but leaving it
395     // here in case it's needed in the future
396     $scope.print_list_alt = function() {
397         var url = '/opac/extras/circ/alt_holds_print.html';
398         var win = $window.open(url, '_blank');
399         win.ses = function() {return egCore.auth.token()};
400         win.open();
401         win.focus();
402     }
403
404     $scope.print_full_list = function() {
405         var print_holds = [];
406         egProgressDialog.open({value : 0});
407
408         // collect the full list of holds
409         egCore.net.request(
410             'open-ils.circ',
411             'open-ils.circ.hold_pull_list.fleshed.stream',
412             egCore.auth.token(), 10000, 0
413         ).then(
414             function() {
415                 console.debug('printing ' + print_holds.length + ' holds');
416
417                 // holds fetched, send to print
418                 egCore.print.print({
419                     context : 'default', 
420                     template : 'hold_pull_list', 
421                     scope : {holds : print_holds}
422                 });
423             },
424             null, 
425             function(hold_data) {
426                 egProgressDialog.increment();
427                 egHolds.local_flesh(hold_data);
428                 print_holds.push(hold_data);
429                 hold_data.title = hold_data.mvr.title();
430                 hold_data.author = hold_data.mvr.author();
431                 hold_data.hold = egCore.idl.toHash(hold_data.hold);
432                 hold_data.copy = egCore.idl.toHash(hold_data.copy);
433                 hold_data.volume = egCore.idl.toHash(hold_data.volume);
434                 hold_data.part = egCore.idl.toHash(hold_data.part);
435             }
436         ).finally(egProgressDialog.close);
437     }
438
439 }])
440