]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/web/js/ui/default/staff/circ/services/holds.js
LP#1402797 Supply top-level method for uncanceling holds
[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.uncancel_holds = function(hold_ids) {
111        
112         return $modal.open({
113             templateUrl : './circ/share/t_uncancel_hold_dialog',
114             controller : 
115                 ['$scope', '$modalInstance',
116                 function($scope, $modalInstance) {
117                     $scope.args = {
118                         num_holds : hold_ids.length
119                     };
120                     
121                     $scope.cancel = function($event) {
122                         $modalInstance.dismiss();
123                         $event.preventDefault();
124                     }
125
126                     $scope.ok = function() {
127
128                         function uncancel_one() {
129                             var hold_id = hold_ids.pop();
130                             if (!hold_id) {
131                                 $modalInstance.close();
132                                 return;
133                             }
134                             egCore.net.request(
135                                 'open-ils.circ', 'open-ils.circ.hold.uncancel',
136                                 egCore.auth.token(), hold_id
137                             ).then(function(resp) {
138                                 if (evt = egCore.evt.parse(resp)) {
139                                     console.error('unable to uncancel hold: ' 
140                                         + evt.toString());
141                                 }
142                                 uncancel_one();
143                             });
144                         }
145
146                         uncancel_one();
147                     }
148                 }
149             ]
150         }).result;
151     }
152
153     service.get_cancel_reasons = function() {
154         if (egCore.env.ahrcc) return $q.when(egCore.env.ahrcc.list);
155         return egCore.pcrud.retrieveAll('ahrcc', {}, {atomic : true})
156         .then(function(list) { return egCore.env.absorbList(list, 'ahrcc').list });
157     }
158
159     // Updates a batch of holds, notifies on each response.
160     // new_values = array of hashes describing values to change,
161     // including the id of the hold to change.
162     // e.g. {id : 1, mint_condition : true}
163     service.update_holds = function(new_values) {
164         return egCore.net.request(
165             'open-ils.circ',
166             'open-ils.circ.hold.update.batch',
167             egCore.auth.token(), null, new_values);
168     }
169
170     service.set_copy_quality = function(hold_ids) {
171         if (!hold_ids.length) return $q.when();
172         return $modal.open({
173             templateUrl : './circ/share/t_hold_copy_quality_dialog',
174             controller : 
175                 ['$scope', '$modalInstance',
176                 function($scope, $modalInstance) {
177
178                     function update(val) {
179                         var vals = hold_ids.map(function(hold_id) {
180                             return {id : hold_id, mint_condition : val}})
181                         service.update_holds(vals).finally(function() {
182                             $modalInstance.close();
183                         });
184                     }
185                     $scope.good = function() { update(true) }
186                     $scope.any = function() { update(false) }
187                     $scope.cancel = function() { $modalInstance.dismiss() }
188                 }
189             ]
190         }).result;
191     }
192
193     service.edit_pickup_lib = function(hold_ids) {
194         if (!hold_ids.length) return $q.when();
195         return $modal.open({
196             templateUrl : './circ/share/t_hold_edit_pickup_lib',
197             controller : 
198                 ['$scope', '$modalInstance',
199                 function($scope, $modalInstance) {
200                     $scope.args = {}
201                     $scope.ok = function() { 
202                         var vals = hold_ids.map(function(hold_id) {
203                             return {
204                                 id : hold_id, 
205                                 pickup_lib : $scope.args.org_unit.id()
206                             }
207                         });
208                         service.update_holds(vals).finally(function() {
209                             $modalInstance.close();
210                         });
211                     }
212                     $scope.cancel = function() { $modalInstance.dismiss() }
213                 }
214             ]
215         }).result;
216     }
217
218     service.get_sms_carriers = function() {
219         if (egCore.env.csc) return $q.when(egCore.env.csc.list);
220         return egCore.pcrud.retrieveAll('csc', {}, {atomic : true})
221         .then(function(list) { return egCore.env.absorbList(list, 'csc').list });
222     }
223
224     service.edit_notify_prefs = function(hold_ids) {
225         if (!hold_ids.length) return $q.when();
226         return $modal.open({
227             templateUrl : './circ/share/t_hold_notification_prefs',
228             controller : 
229                 ['$scope', '$modalInstance', 'sms_carriers',
230                 function($scope, $modalInstance, sms_carriers) {
231                     $scope.args = {}
232                     $scope.sms_carriers = sms_carriers;
233                     $scope.num_holds = hold_ids.length;
234                     $scope.ok = function() { 
235
236                         var vals = hold_ids.map(function(hold_id) {
237                             var val = {id : hold_id};
238                             angular.forEach(
239                                 ['email', 'phone', 'sms'],
240                                 function(type) {
241                                     var key = type + '_notify';
242                                     if ($scope.args['update_' + key]) 
243                                         val[key] = $scope.args[key];
244                                 }
245                             );
246                             if ($scope.args.update_sms_carrier)
247                                 val.sms_carrier = $scope.args.sms_carrier.id();
248                             return val;
249                         });
250
251                         service.update_holds(vals).finally(function() {
252                             $modalInstance.close();
253                         });
254                     }
255                     $scope.cancel = function() { $modalInstance.dismiss() }
256                 }
257             ],
258             resolve : {
259                 sms_carriers : service.get_sms_carriers
260             }
261         }).result;
262     }
263
264     service.edit_dates = function(hold_ids) {
265         if (!hold_ids.length) return $q.when();
266
267         // collects the fields from the dialog the user wishes to modify
268         function relay_to_update(modal_scope) {
269             var vals = hold_ids.map(function(hold_id) {
270                 var val = {id : hold_id};
271                 angular.forEach(
272                     ['thaw_date', 'request_time', 'expire_time', 'shelf_expire_time'], 
273                     function(field) {
274                         if (modal_scope.args['modify_' + field]) 
275                             val[field] = modal_scope.args[field].toISOString();
276                     }
277                 );
278
279                 return val;
280             });
281
282             console.log(JSON.stringify(vals,null,2));
283             return service.update_holds(vals);
284         }
285
286         return $modal.open({
287             templateUrl : './circ/share/t_hold_dates',
288             controller : 
289                 ['$scope', '$modalInstance',
290                 function($scope, $modalInstance) {
291                     var today = new Date();
292                     $scope.args = {
293                         thaw_date : today,
294                         request_time : today,
295                         expire_time : today,
296                         shelf_expire_time : today
297                     }
298                     $scope.num_holds = hold_ids.length;
299                     $scope.ok = function() { 
300                         relay_to_update($scope).then($modalInstance.close);
301                     }
302                     $scope.cancel = function() { $modalInstance.dismiss() }
303                 }
304             ],
305         }).result;
306     }
307
308     service.update_field_with_confirm = function(hold_ids, msg_key, field, value) {
309         if (!hold_ids.length) return $q.when();
310
311         return egConfirmDialog.open(
312             egCore.strings[msg_key], '', {num_holds : hold_ids.length})
313         .result.then(function() {
314
315             var vals = hold_ids.map(function(hold_id) {
316                 val = {id : hold_id};
317                 val[field] = value;
318                 return val;
319             });
320             return service.update_holds(vals);
321         });
322     }
323
324     service.suspend_holds = function(hold_ids) {
325         return service.update_field_with_confirm(
326             hold_ids, 'SUSPEND_HOLDS', 'frozen', true);
327     }
328
329     service.activate_holds = function(hold_ids) {
330         return service.update_field_with_confirm(
331             hold_ids, 'ACTIVATE_HOLDS', 'frozen', false);
332     }
333
334     service.set_top_of_queue = function(hold_ids) {
335         return service.update_field_with_confirm(
336             hold_ids, 'SET_TOP_OF_QUEUE', 'cut_in_line', true);
337     }
338
339     service.clear_top_of_queue = function(hold_ids) {
340         return service.update_field_with_confirm(
341             hold_ids, 'CLEAR_TOP_OF_QUEUE', 'cut_in_line', null);
342     }
343
344     service.transfer_to_marked_title = function(hold_ids) {
345         if (!hold_ids.length) return $q.when();
346
347         var bib_id = egCore.hatch.getLocalItem(
348             'eg.circ.hold.title_transfer_target');
349
350         if (!bib_id) {
351             // no target marked
352             return egAlertDialog.open(
353                 egCore.strings.NO_HOLD_TRANSFER_TITLE_MARKED).result;
354         }
355
356         return egConfirmDialog.open(
357             egCore.strings.TRANSFER_HOLD_TO_TITLE, '', {
358                 num_holds : hold_ids.length,
359                 bib_id : bib_id
360             }
361         ).result.then(function() {
362             return egCore.net.request(
363                 'open-ils.circ',
364                 'open-ils.circ.hold.change_title.specific_holds',
365                 egCore.auth.token(), bib_id, hold_ids);
366         });
367     }
368
369     // serially retargets each hold
370     service.retarget = function(hold_ids) {
371         if (!hold_ids.length) return $q.when();
372         var deferred = $q.defer();
373
374         egConfirmDialog.open(
375             egCore.strings.RETARGET_HOLDS, '', 
376             {hold_ids : hold_ids.join(',')}
377
378         ).result.then(function() {
379
380             function do_one() {
381                 var hold_id = hold_ids.pop();
382                 if (!hold_id) {
383                     deferred.resolve();
384                     return;
385                 }
386
387                 egCore.net.request(
388                     'open-ils.circ',
389                     'open-ils.circ.hold.reset',
390                     egCore.auth.token(), hold_id).finally(do_one);
391             }
392
393             do_one(); // kick it off
394         });
395
396         return deferred.promise;
397     }
398
399     // fleshes orgs, etc. for hold data blobs retrieved from
400     // open-ils.circ.hold.details[.batch].retrieve
401     service.local_flesh = function(hold_data) {
402
403         hold_data.status_string = 
404             egCore.strings['HOLD_STATUS_' + hold_data.status] 
405             || hold_data.status;
406
407         var hold = hold_data.hold;
408         hold.pickup_lib(egCore.org.get(hold.pickup_lib()));
409         hold.current_shelf_lib(egCore.org.get(hold.current_shelf_lib()));
410         hold_data.id = hold.id();
411
412         if (hold.requestor() && typeof hold.requestor() != 'object')
413             hold.requestor(egUser.get(hold.requestor()));
414
415         if (hold.usr() && typeof hold.usr() != 'object')
416             hold.usr(egUser.get(hold.usr()));
417
418         // current_copy is not always fleshed in the API
419         if (hold.current_copy() && typeof hold.current_copy() != 'object')
420             hold.current_copy(hold_data.copy);
421     }
422
423     return service;
424 }])
425
426 /**  
427  * Action handlers for the common Hold grid UI.
428  * These generally scrub the data for valid input then pass the
429  * holds / copies / etc. off to the relevant action in egHolds or egCirc.
430  *
431  * Caller must apply a reset_page function, which is called after 
432  * most actionis are performed.
433  */
434 .factory('egHoldGridActions', 
435        ['$window','$location','egCore','egHolds','egCirc',
436 function($window , $location , egCore , egHolds , egCirc) {
437     
438     var service = {};
439
440     service.refresh = function() {
441         console.error('egHoldGridActions.refresh not defined!');
442     }
443
444     service.cancel_hold = function(items) {
445         var hold_ids = items.filter(function(item) {
446             return !item.hold.cancel_time();
447         }).map(function(item) {return item.hold.id()});
448
449         return egHolds.cancel_holds(hold_ids).then(service.refresh);
450     }
451
452     service.uncancel_hold = function(items) {
453         var hold_ids = items.filter(function(item) {
454             return item.hold.cancel_time();
455         }).map(function(item) {return item.hold.id()});
456
457         return egHolds.uncancel_holds(hold_ids).then(service.refresh);
458     }
459
460     // jump to circ list for either 1) the targeted copy or
461     // 2) the hold target copy for copy-level holds
462     service.show_recent_circs = function(items) {
463         if (items.length && (copy = items[0].copy)) {
464             var url = $location.path(
465                 '/cat/item/' + copy.id() + '/circ_list').absUrl();
466             $window.open(url, '_blank').focus();
467         }
468     }
469
470     function generic_update(items, action) {
471         if (!items.length) return $q.when();
472         var hold_ids = items.map(function(item) {return item.hold.id()});
473         return egHolds[action](hold_ids).then(service.refresh);
474     }
475
476     service.set_copy_quality = function(items) {
477         generic_update(items, 'set_copy_quality'); }
478     service.edit_pickup_lib = function(items) {
479         generic_update(items, 'edit_pickup_lib'); }
480     service.edit_notify_prefs = function(items) {
481         generic_update(items, 'edit_notify_prefs'); }
482     service.edit_dates = function(items) {
483         generic_update(items, 'edit_dates'); }
484     service.suspend = function(items) {
485         generic_update(items, 'suspend_holds'); }
486     service.activate = function(items) {
487         generic_update(items, 'activate_holds'); }
488     service.set_top_of_queue = function(items) {
489         generic_update(items, 'set_top_of_queue'); }
490     service.clear_top_of_queue = function(items) {
491         generic_update(items, 'clear_top_of_queue'); }
492     service.transfer_to_marked_title = function(items) {
493         generic_update(items, 'transfer_to_marked_title'); }
494
495     service.mark_damaged = function(items) {
496         var copy_ids = items
497             .filter(function(item) { return Boolean(item.copy) })
498             .map(function(item) { return item.copy.id() });
499         if (copy_ids.length) 
500             egCirc.mark_damaged(copy_ids).then(service.refresh);
501     }
502
503     service.mark_missing = function(items) {
504         var copy_ids = items
505             .filter(function(item) { return Boolean(item.copy) })
506             .map(function(item) { return item.copy.id() });
507         if (copy_ids.length) 
508             egCirc.mark_missing(copy_ids).then(service.refresh);
509     }
510
511     service.retarget = function(items) {
512         var hold_ids = items.map(function(item) { return item.hold.id() });
513         egHolds.retarget(hold_ids).then(service.refresh);
514     }
515
516     return service;
517 }])
518
519 /**
520  * Hold details interface 
521  */
522 .directive('egHoldDetails', function() {
523     return {
524         restrict : 'AE',
525         templateUrl : './circ/share/t_hold_details',
526         scope : {
527             holdId : '=',
528             // if set, called whenever hold details are retrieved.  The
529             // argument is the hold blob returned from hold.details.retrieve
530             holdRetrieved : '=',
531             showPatron : '='
532         },
533         controller : [
534                     '$scope','$modal','egCore','egHolds','egCirc',
535             function($scope , $modal , egCore , egHolds , egCirc) {
536
537                 function draw() {
538                     if (!$scope.holdId) return;
539
540                     egCore.net.request(
541                         'open-ils.circ',
542                         'open-ils.circ.hold.details.retrieve.authoritative',
543                         egCore.auth.token(), $scope.holdId
544
545                     ).then(function(hold_data) { 
546                         egHolds.local_flesh(hold_data);
547     
548                         angular.forEach(hold_data, 
549                             function(val, key) { $scope[key] = val });
550
551                         // fetch + flesh the cancel_cause if needed
552                         if ($scope.hold.cancel_time()) {
553                             egHolds.get_cancel_reasons().then(function() {
554                                 // egHolds caches the causes in egEnv
555                                 $scope.hold.cancel_cause(
556                                     egCore.env.ahrcc.map[$scope.hold.cancel_cause()]);
557                             })
558                         }
559
560                         if ($scope.hold.current_copy()) {
561                             egCirc.flesh_copy_location($scope.hold.current_copy());
562                         }
563
564                         if ($scope.holdRetrieved)
565                             $scope.holdRetrieved(hold_data);
566
567                     });
568                 }
569
570                 $scope.show_notify_tab = function() {
571                     $scope.detail_tab = 'notify';
572                     egCore.pcrud.search('ahn',
573                         {hold : $scope.hold.id()}, 
574                         {flesh : 1, flesh_fields : {ahn : ['notify_staff']}}, 
575                         {atomic : true}
576                     ).then(function(nots) {
577                         $scope.hold.notifications(nots);
578                     });
579                 }
580
581                 $scope.delete_note = function(note) {
582                     egCore.pcrud.remove(note).then(function() {
583                         // remove the deleted note from the locally fleshed notes
584                         $scope.hold.notes(
585                             $scope.hold.notes().filter(function(n) {
586                                 return n.id() != note.id()
587                             })
588                         );
589                     });
590                 }
591
592                 $scope.new_note = function() {
593                     return $modal.open({
594                         templateUrl : './circ/share/t_hold_note_dialog',
595                         controller : 
596                             ['$scope', '$modalInstance',
597                             function($scope, $modalInstance) {
598                                 $scope.args = {};
599                                 $scope.ok = function() {
600                                     $modalInstance.close($scope.args)
601                                 },
602                                 $scope.cancel = function($event) {
603                                     $modalInstance.dismiss();
604                                     $event.preventDefault();
605                                 }
606                             }
607                         ]
608                     }).result.then(function(args) {
609                         var note = new egCore.idl.ahrn();
610                         note.hold($scope.hold.id());
611                         note.staff(true);
612                         note.slip(args.slip);
613                         note.pub(args.pub); 
614                         note.title(args.title);
615                         note.body(args.body);
616                         return egCore.pcrud.create(note).then(function(n) {
617                             $scope.hold.notes().push(n);
618                         });
619                     });
620                 }
621
622                 $scope.new_notification = function() {
623                     return $modal.open({
624                         templateUrl : './circ/share/t_hold_notification_dialog',
625                         controller : 
626                             ['$scope', '$modalInstance',
627                             function($scope, $modalInstance) {
628                                 $scope.args = {};
629                                 $scope.ok = function() {
630                                     $modalInstance.close($scope.args)
631                                 },
632                                 $scope.cancel = function($event) {
633                                     $modalInstance.dismiss();
634                                     $event.preventDefault();
635                                 }
636                             }
637                         ]
638                     }).result.then(function(args) {
639                         var note = new egCore.idl.ahn();
640                         note.hold($scope.hold.id());
641                         note.method(args.method);
642                         note.note(args.note);
643                         note.notify_staff(egCore.auth.user().id());
644                         note.notify_time('now');
645                         return egCore.pcrud.create(note).then(function(n) {
646                             n.notify_staff(egCore.auth.user());
647                             $scope.hold.notifications().push(n);
648                         });
649                     });
650                 }
651
652                 $scope.$watch('holdId', function(newVal, oldVal) {
653                     if (newVal != oldVal) draw();
654                 });
655
656                 draw();
657             }
658         ]
659     }
660 })
661
662