]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/web/js/ui/default/staff/circ/services/holds.js
LP#1402797 Add patron barcode and alias to hold lists where appropriate
[working/Evergreen.git] / Open-ILS / web / js / ui / default / staff / circ / services / holds.js
1 /**
2  * Holds, yo
3  */
4
5 angular.module('egCoreMod')
6
7 .factory('egHolds',
8
9        ['$modal','$q','egCore','egUser','egConfirmDialog','egAlertDialog',
10 function($modal , $q , egCore , egUser , egConfirmDialog , egAlertDialog) {
11
12     var service = {};
13
14     service.fetch_holds = function(hold_ids) {
15         var deferred = $q.defer();
16
17         // FIXME: large batches using .authoritative result in many 
18         // stranded cstore backends on the server.  Needs investigation.
19         // For now, collect holds in a series of small batches.
20         // Fetch them serially both to avoid the above problem and
21         // to maintain order.
22         var batch_size = 5;
23         var index = 0;
24
25         function one_batch() {
26             var ids = hold_ids.slice(index, index + batch_size)
27                 .filter(function(id) {return Boolean(id)}) // avoid nulls
28
29             console.debug('egHolds.fetch_holds => ' + ids);
30             index += batch_size;
31
32             if (!ids.length) {
33                 deferred.resolve();
34                 return;
35             }
36
37             egCore.net.request(
38                 'open-ils.circ',
39                 'open-ils.circ.hold.details.batch.retrieve.authoritative',
40                 egCore.auth.token(), ids
41
42             ).then(
43                 one_batch,  // kick off the next batch
44                 null, 
45                 function(hold_data) {
46                     var hold = hold_data.hold;
47                     hold_data.id = hold.id();
48                     service.local_flesh(hold_data);
49                     deferred.notify(hold_data);
50                 }
51             );
52         }
53
54         one_batch(); // kick it off
55         return deferred.promise;
56     }
57
58
59     service.cancel_holds = function(hold_ids) {
60        
61         return $modal.open({
62             templateUrl : './circ/share/t_cancel_hold_dialog',
63             controller : 
64                 ['$scope', '$modalInstance', 'cancel_reasons',
65                 function($scope, $modalInstance, cancel_reasons) {
66                     $scope.args = {
67                         cancel_reasons : cancel_reasons,
68                         num_holds : hold_ids.length
69                     };
70                     
71                     $scope.cancel = function($event) {
72                         $modalInstance.dismiss();
73                         $event.preventDefault();
74                     }
75
76                     $scope.ok = function() {
77
78                         function cancel_one() {
79                             var hold_id = hold_ids.pop();
80                             if (!hold_id) {
81                                 $modalInstance.close();
82                                 return;
83                             }
84                             egCore.net.request(
85                                 'open-ils.circ', 'open-ils.circ.hold.cancel',
86                                 egCore.auth.token(), hold_id,
87                                 $scope.args.cancel_reason.id(), 
88                                 $scope.args.note
89                             ).then(function(resp) {
90                                 if (evt = egCore.evt.parse(resp)) {
91                                     console.error('unable to cancel hold: ' 
92                                         + evt.toString());
93                                 }
94                                 cancel_one();
95                             });
96                         }
97
98                         cancel_one();
99                     }
100                 }
101             ],
102             resolve : {
103                 cancel_reasons : function() {
104                     return service.get_cancel_reasons();
105                 }
106             }
107         }).result;
108     }
109
110     service.get_cancel_reasons = function() {
111         if (egCore.env.ahrcc) return $q.when(egCore.env.ahrcc.list);
112         return egCore.pcrud.retrieveAll('ahrcc', {}, {atomic : true})
113         .then(function(list) { return egCore.env.absorbList(list, 'ahrcc').list });
114     }
115
116     // Updates a batch of holds, notifies on each response.
117     // new_values = array of hashes describing values to change,
118     // including the id of the hold to change.
119     // e.g. {id : 1, mint_condition : true}
120     service.update_holds = function(new_values) {
121         return egCore.net.request(
122             'open-ils.circ',
123             'open-ils.circ.hold.update.batch',
124             egCore.auth.token(), null, new_values);
125     }
126
127     service.set_copy_quality = function(hold_ids) {
128         if (!hold_ids.length) return $q.when();
129         return $modal.open({
130             templateUrl : './circ/share/t_hold_copy_quality_dialog',
131             controller : 
132                 ['$scope', '$modalInstance',
133                 function($scope, $modalInstance) {
134
135                     function update(val) {
136                         var vals = hold_ids.map(function(hold_id) {
137                             return {id : hold_id, mint_condition : val}})
138                         service.update_holds(vals).finally(function() {
139                             $modalInstance.close();
140                         });
141                     }
142                     $scope.good = function() { update(true) }
143                     $scope.any = function() { update(false) }
144                     $scope.cancel = function() { $modalInstance.dismiss() }
145                 }
146             ]
147         }).result;
148     }
149
150     service.edit_pickup_lib = function(hold_ids) {
151         if (!hold_ids.length) return $q.when();
152         return $modal.open({
153             templateUrl : './circ/share/t_hold_edit_pickup_lib',
154             controller : 
155                 ['$scope', '$modalInstance',
156                 function($scope, $modalInstance) {
157                     $scope.args = {}
158                     $scope.ok = function() { 
159                         var vals = hold_ids.map(function(hold_id) {
160                             return {
161                                 id : hold_id, 
162                                 pickup_lib : $scope.args.org_unit.id()
163                             }
164                         });
165                         service.update_holds(vals).finally(function() {
166                             $modalInstance.close();
167                         });
168                     }
169                     $scope.cancel = function() { $modalInstance.dismiss() }
170                 }
171             ]
172         }).result;
173     }
174
175     service.get_sms_carriers = function() {
176         if (egCore.env.csc) return $q.when(egCore.env.csc.list);
177         return egCore.pcrud.retrieveAll('csc', {}, {atomic : true})
178         .then(function(list) { return egCore.env.absorbList(list, 'csc').list });
179     }
180
181     service.edit_notify_prefs = function(hold_ids) {
182         if (!hold_ids.length) return $q.when();
183         return $modal.open({
184             templateUrl : './circ/share/t_hold_notification_prefs',
185             controller : 
186                 ['$scope', '$modalInstance', 'sms_carriers',
187                 function($scope, $modalInstance, sms_carriers) {
188                     $scope.args = {}
189                     $scope.sms_carriers = sms_carriers;
190                     $scope.num_holds = hold_ids.length;
191                     $scope.ok = function() { 
192
193                         var vals = hold_ids.map(function(hold_id) {
194                             var val = {id : hold_id};
195                             angular.forEach(
196                                 ['email', 'phone', 'sms'],
197                                 function(type) {
198                                     var key = type + '_notify';
199                                     if ($scope.args['update_' + key]) 
200                                         val[key] = $scope.args[key];
201                                 }
202                             );
203                             if ($scope.args.update_sms_carrier)
204                                 val.sms_carrier = $scope.args.sms_carrier.id();
205                             return val;
206                         });
207
208                         service.update_holds(vals).finally(function() {
209                             $modalInstance.close();
210                         });
211                     }
212                     $scope.cancel = function() { $modalInstance.dismiss() }
213                 }
214             ],
215             resolve : {
216                 sms_carriers : service.get_sms_carriers
217             }
218         }).result;
219     }
220
221     service.edit_dates = function(hold_ids) {
222         if (!hold_ids.length) return $q.when();
223
224         // collects the fields from the dialog the user wishes to modify
225         function relay_to_update(modal_scope) {
226             var vals = hold_ids.map(function(hold_id) {
227                 var val = {id : hold_id};
228                 angular.forEach(
229                     ['thaw_date', 'request_time', 'expire_time', 'shelf_expire_time'], 
230                     function(field) {
231                         if (modal_scope.args['modify_' + field]) 
232                             val[field] = modal_scope.args[field].toISOString();
233                     }
234                 );
235
236                 return val;
237             });
238
239             console.log(JSON.stringify(vals,null,2));
240             return service.update_holds(vals);
241         }
242
243         return $modal.open({
244             templateUrl : './circ/share/t_hold_dates',
245             controller : 
246                 ['$scope', '$modalInstance',
247                 function($scope, $modalInstance) {
248                     var today = new Date();
249                     $scope.args = {
250                         thaw_date : today,
251                         request_time : today,
252                         expire_time : today,
253                         shelf_expire_time : today
254                     }
255                     $scope.num_holds = hold_ids.length;
256                     $scope.ok = function() { 
257                         relay_to_update($scope).then($modalInstance.close);
258                     }
259                     $scope.cancel = function() { $modalInstance.dismiss() }
260                 }
261             ],
262         }).result;
263     }
264
265     service.update_field_with_confirm = function(hold_ids, msg_key, field, value) {
266         if (!hold_ids.length) return $q.when();
267
268         return egConfirmDialog.open(
269             egCore.strings[msg_key], '', {num_holds : hold_ids.length})
270         .result.then(function() {
271
272             var vals = hold_ids.map(function(hold_id) {
273                 val = {id : hold_id};
274                 val[field] = value;
275                 return val;
276             });
277             return service.update_holds(vals);
278         });
279     }
280
281     service.suspend_holds = function(hold_ids) {
282         return service.update_field_with_confirm(
283             hold_ids, 'SUSPEND_HOLDS', 'frozen', true);
284     }
285
286     service.activate_holds = function(hold_ids) {
287         return service.update_field_with_confirm(
288             hold_ids, 'ACTIVATE_HOLDS', 'frozen', false);
289     }
290
291     service.set_top_of_queue = function(hold_ids) {
292         return service.update_field_with_confirm(
293             hold_ids, 'SET_TOP_OF_QUEUE', 'cut_in_line', true);
294     }
295
296     service.clear_top_of_queue = function(hold_ids) {
297         return service.update_field_with_confirm(
298             hold_ids, 'CLEAR_TOP_OF_QUEUE', 'cut_in_line', null);
299     }
300
301     service.transfer_to_marked_title = function(hold_ids) {
302         if (!hold_ids.length) return $q.when();
303
304         var bib_id = egCore.hatch.getLocalItem(
305             'eg.circ.hold.title_transfer_target');
306
307         if (!bib_id) {
308             // no target marked
309             return egAlertDialog.open(
310                 egCore.strings.NO_HOLD_TRANSFER_TITLE_MARKED).result;
311         }
312
313         return egConfirmDialog.open(
314             egCore.strings.TRANSFER_HOLD_TO_TITLE, '', {
315                 num_holds : hold_ids.length,
316                 bib_id : bib_id
317             }
318         ).result.then(function() {
319             return egCore.net.request(
320                 'open-ils.circ',
321                 'open-ils.circ.hold.change_title.specific_holds',
322                 egCore.auth.token(), bib_id, hold_ids);
323         });
324     }
325
326     // serially retargets each hold
327     service.retarget = function(hold_ids) {
328         if (!hold_ids.length) return $q.when();
329         var deferred = $q.defer();
330
331         egConfirmDialog.open(
332             egCore.strings.RETARGET_HOLDS, '', 
333             {hold_ids : hold_ids.join(',')}
334
335         ).result.then(function() {
336
337             function do_one() {
338                 var hold_id = hold_ids.pop();
339                 if (!hold_id) {
340                     deferred.resolve();
341                     return;
342                 }
343
344                 egCore.net.request(
345                     'open-ils.circ',
346                     'open-ils.circ.hold.reset',
347                     egCore.auth.token(), hold_id).finally(do_one);
348             }
349
350             do_one(); // kick it off
351         });
352
353         return deferred.promise;
354     }
355
356     // fleshes orgs, etc. for hold data blobs retrieved from
357     // open-ils.circ.hold.details[.batch].retrieve
358     service.local_flesh = function(hold_data) {
359
360         hold_data.status_string = 
361             egCore.strings['HOLD_STATUS_' + hold_data.status] 
362             || hold_data.status;
363
364         var hold = hold_data.hold;
365         hold.pickup_lib(egCore.org.get(hold.pickup_lib()));
366         hold.current_shelf_lib(egCore.org.get(hold.current_shelf_lib()));
367         hold_data.id = hold.id();
368
369         if (hold.requestor() && typeof hold.requestor() != 'object')
370             hold.requestor(egUser.get(hold.requestor()));
371
372         if (hold.usr() && typeof hold.usr() != 'object')
373             hold.usr(egUser.get(hold.usr()));
374
375         // current_copy is not always fleshed in the API
376         if (hold.current_copy() && typeof hold.current_copy() != 'object')
377             hold.current_copy(hold_data.copy);
378     }
379
380     return service;
381 }])
382
383 /**  
384  * Action handlers for the common Hold grid UI.
385  * These generally scrub the data for valid input then pass the
386  * holds / copies / etc. off to the relevant action in egHolds or egCirc.
387  *
388  * Caller must apply a reset_page function, which is called after 
389  * most actionis are performed.
390  */
391 .factory('egHoldGridActions', 
392        ['$window','$location','egCore','egHolds','egCirc',
393 function($window , $location , egCore , egHolds , egCirc) {
394     
395     var service = {};
396
397     service.refresh = function() {
398         console.error('egHoldGridActions.refresh not defined!');
399     }
400
401     service.cancel_hold = function(items) {
402         var hold_ids = items.filter(function(item) {
403             return !item.hold.cancel_time();
404         }).map(function(item) {return item.hold.id()});
405
406         return egHolds.cancel_holds(hold_ids).then(service.refresh);
407     }
408
409     // jump to circ list for either 1) the targeted copy or
410     // 2) the hold target copy for copy-level holds
411     service.show_recent_circs = function(items) {
412         if (items.length && (copy = items[0].copy)) {
413             var url = $location.path(
414                 '/cat/item/' + copy.id() + '/circ_list').absUrl();
415             $window.open(url, '_blank').focus();
416         }
417     }
418
419     function generic_update(items, action) {
420         if (!items.length) return $q.when();
421         var hold_ids = items.map(function(item) {return item.hold.id()});
422         return egHolds[action](hold_ids).then(service.refresh);
423     }
424
425     service.set_copy_quality = function(items) {
426         generic_update(items, 'set_copy_quality'); }
427     service.edit_pickup_lib = function(items) {
428         generic_update(items, 'edit_pickup_lib'); }
429     service.edit_notify_prefs = function(items) {
430         generic_update(items, 'edit_notify_prefs'); }
431     service.edit_dates = function(items) {
432         generic_update(items, 'edit_dates'); }
433     service.suspend = function(items) {
434         generic_update(items, 'suspend_holds'); }
435     service.activate = function(items) {
436         generic_update(items, 'activate_holds'); }
437     service.set_top_of_queue = function(items) {
438         generic_update(items, 'set_top_of_queue'); }
439     service.clear_top_of_queue = function(items) {
440         generic_update(items, 'clear_top_of_queue'); }
441     service.transfer_to_marked_title = function(items) {
442         generic_update(items, 'transfer_to_marked_title'); }
443
444     service.mark_damaged = function(items) {
445         var copy_ids = items
446             .filter(function(item) { return Boolean(item.copy) })
447             .map(function(item) { return item.copy.id() });
448         if (copy_ids.length) 
449             egCirc.mark_damaged(copy_ids).then(service.refresh);
450     }
451
452     service.mark_missing = function(items) {
453         var copy_ids = items
454             .filter(function(item) { return Boolean(item.copy) })
455             .map(function(item) { return item.copy.id() });
456         if (copy_ids.length) 
457             egCirc.mark_missing(copy_ids).then(service.refresh);
458     }
459
460     service.retarget = function(items) {
461         var hold_ids = items.map(function(item) { return item.hold.id() });
462         egHolds.retarget(hold_ids).then(service.refresh);
463     }
464
465     return service;
466 }])
467
468 /**
469  * Hold details interface 
470  */
471 .directive('egHoldDetails', function() {
472     return {
473         restrict : 'AE',
474         templateUrl : './circ/share/t_hold_details',
475         scope : {
476             holdId : '=',
477             // if set, called whenever hold details are retrieved.  The
478             // argument is the hold blob returned from hold.details.retrieve
479             holdRetrieved : '=',
480             showPatron : '='
481         },
482         controller : [
483                     '$scope','$modal','egCore','egHolds','egCirc',
484             function($scope , $modal , egCore , egHolds , egCirc) {
485
486                 function draw() {
487                     if (!$scope.holdId) return;
488
489                     egCore.net.request(
490                         'open-ils.circ',
491                         'open-ils.circ.hold.details.retrieve.authoritative',
492                         egCore.auth.token(), $scope.holdId
493
494                     ).then(function(hold_data) { 
495                         egHolds.local_flesh(hold_data);
496     
497                         angular.forEach(hold_data, 
498                             function(val, key) { $scope[key] = val });
499
500                         // fetch + flesh the cancel_cause if needed
501                         if ($scope.hold.cancel_time()) {
502                             egHolds.get_cancel_reasons().then(function() {
503                                 // egHolds caches the causes in egEnv
504                                 $scope.hold.cancel_cause(
505                                     egCore.env.ahrcc.map[$scope.hold.cancel_cause()]);
506                             })
507                         }
508
509                         if ($scope.hold.current_copy()) {
510                             egCirc.flesh_copy_location($scope.hold.current_copy());
511                         }
512
513                         if ($scope.holdRetrieved)
514                             $scope.holdRetrieved(hold_data);
515
516                     });
517                 }
518
519                 $scope.show_notify_tab = function() {
520                     $scope.detail_tab = 'notify';
521                     egCore.pcrud.search('ahn',
522                         {hold : $scope.hold.id()}, 
523                         {flesh : 1, flesh_fields : {ahn : ['notify_staff']}}, 
524                         {atomic : true}
525                     ).then(function(nots) {
526                         $scope.hold.notifications(nots);
527                     });
528                 }
529
530                 $scope.delete_note = function(note) {
531                     egCore.pcrud.remove(note).then(function() {
532                         // remove the deleted note from the locally fleshed notes
533                         $scope.hold.notes(
534                             $scope.hold.notes().filter(function(n) {
535                                 return n.id() != note.id()
536                             })
537                         );
538                     });
539                 }
540
541                 $scope.new_note = function() {
542                     return $modal.open({
543                         templateUrl : './circ/share/t_hold_note_dialog',
544                         controller : 
545                             ['$scope', '$modalInstance',
546                             function($scope, $modalInstance) {
547                                 $scope.args = {};
548                                 $scope.ok = function() {
549                                     $modalInstance.close($scope.args)
550                                 },
551                                 $scope.cancel = function($event) {
552                                     $modalInstance.dismiss();
553                                     $event.preventDefault();
554                                 }
555                             }
556                         ]
557                     }).result.then(function(args) {
558                         var note = new egCore.idl.ahrn();
559                         note.hold($scope.hold.id());
560                         note.staff(true);
561                         note.slip(args.slip);
562                         note.pub(args.pub); 
563                         note.title(args.title);
564                         note.body(args.body);
565                         return egCore.pcrud.create(note).then(function(n) {
566                             $scope.hold.notes().push(n);
567                         });
568                     });
569                 }
570
571                 $scope.new_notification = function() {
572                     return $modal.open({
573                         templateUrl : './circ/share/t_hold_notification_dialog',
574                         controller : 
575                             ['$scope', '$modalInstance',
576                             function($scope, $modalInstance) {
577                                 $scope.args = {};
578                                 $scope.ok = function() {
579                                     $modalInstance.close($scope.args)
580                                 },
581                                 $scope.cancel = function($event) {
582                                     $modalInstance.dismiss();
583                                     $event.preventDefault();
584                                 }
585                             }
586                         ]
587                     }).result.then(function(args) {
588                         var note = new egCore.idl.ahn();
589                         note.hold($scope.hold.id());
590                         note.method(args.method);
591                         note.note(args.note);
592                         note.notify_staff(egCore.auth.user().id());
593                         note.notify_time('now');
594                         return egCore.pcrud.create(note).then(function(n) {
595                             n.notify_staff(egCore.auth.user());
596                             $scope.hold.notifications().push(n);
597                         });
598                     });
599                 }
600
601                 $scope.$watch('holdId', function(newVal, oldVal) {
602                     if (newVal != oldVal) draw();
603                 });
604
605                 draw();
606             }
607         ]
608     }
609 })
610
611