]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/web/js/ui/default/staff/circ/services/holds.js
LP1869898 Make Angular staff catalog default
[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        ['$uibModal','$q','egCore','egConfirmDialog','egAlertDialog',
10 function($uibModal , $q , egCore , egConfirmDialog , egAlertDialog) {
11
12     var service = {};
13
14     service.fetch_wide_holds = function(restrictions, order_by, limit, offset) {
15         return egCore.net.request(
16             'open-ils.circ',
17             'open-ils.circ.hold.wide_hash.stream',
18             egCore.auth.token(),
19             restrictions, order_by, limit, offset
20         );
21     }
22
23     service.fetch_holds = function(hold_ids) {
24         var deferred = $q.defer();
25
26         // Fetch hold details in batches for better UI responsiveness.
27         var batch_size = 5;
28         var index = 0;
29
30         function one_batch() {
31             var ids = hold_ids.slice(index, index + batch_size)
32                 .filter(function(id) {return Boolean(id)}) // avoid nulls
33
34             console.debug('egHolds.fetch_holds => ' + ids);
35             index += batch_size;
36
37             if (!ids.length) {
38                 deferred.resolve();
39                 return;
40             }
41
42             egCore.net.request(
43                 'open-ils.circ',
44                 'open-ils.circ.hold.details.batch.retrieve.authoritative',
45                 egCore.auth.token(), ids, {
46                     include_current_copy : true,
47                     include_usr          : true,
48                     include_cancel_cause : true,
49                     include_sms_carrier  : true,
50                     include_requestor    : true
51                 }
52
53             ).then(
54                 one_batch,  // kick off the next batch
55                 null, 
56                 function(hold_data) {
57                     var hold = hold_data.hold;
58                     hold_data.id = hold.id();
59                     service.local_flesh(hold_data);
60                     deferred.notify(hold_data);
61                 }
62             );
63         }
64
65         one_batch(); // kick it off
66         return deferred.promise;
67     }
68
69
70     service.cancel_holds = function(hold_ids) {
71        
72         return $uibModal.open({
73             templateUrl : './circ/share/t_cancel_hold_dialog',
74             backdrop: 'static',
75             controller : 
76                 ['$scope', '$uibModalInstance', 'cancel_reasons',
77                 function($scope, $uibModalInstance, cancel_reasons) {
78                     $scope.args = {
79                         cancel_reason : 5,
80                         cancel_reasons : cancel_reasons,
81                         num_holds : hold_ids.length
82                     };
83                     
84                     $scope.cancel = function($event) {
85                         $uibModalInstance.dismiss();
86                         $event.preventDefault();
87                     }
88
89                     $scope.ok = function() {
90
91                         function cancel_one() {
92                             var hold_id = hold_ids.pop();
93                             if (!hold_id) {
94                                 $uibModalInstance.close();
95                                 return;
96                             }
97                             egCore.net.request(
98                                 'open-ils.circ', 'open-ils.circ.hold.cancel',
99                                 egCore.auth.token(), hold_id,
100                                 $scope.args.cancel_reason,
101                                 $scope.args.note
102                             ).then(function(resp) {
103                                 if (evt = egCore.evt.parse(resp)) {
104                                     egCore.audio.play(
105                                         'warning.hold.cancel_failed');
106                                     console.error('unable to cancel hold: ' 
107                                         + evt.toString());
108                                 }
109                                 cancel_one();
110                             });
111                         }
112
113                         cancel_one();
114                     }
115                 }
116             ],
117             resolve : {
118                 cancel_reasons : function() {
119                     return service.get_cancel_reasons();
120                 }
121             }
122         }).result;
123     }
124
125     service.uncancel_holds = function(hold_ids) {
126        
127         return $uibModal.open({
128             templateUrl : './circ/share/t_uncancel_hold_dialog',
129             backdrop: 'static',
130             controller : 
131                 ['$scope', '$uibModalInstance',
132                 function($scope, $uibModalInstance) {
133                     $scope.args = {
134                         num_holds : hold_ids.length
135                     };
136                     
137                     $scope.cancel = function($event) {
138                         $uibModalInstance.dismiss();
139                         $event.preventDefault();
140                     }
141
142                     $scope.ok = function() {
143
144                         function uncancel_one() {
145                             var hold_id = hold_ids.pop();
146                             if (!hold_id) {
147                                 $uibModalInstance.close();
148                                 return;
149                             }
150                             egCore.net.request(
151                                 'open-ils.circ', 'open-ils.circ.hold.uncancel',
152                                 egCore.auth.token(), hold_id
153                             ).then(function(resp) {
154                                 if (evt = egCore.evt.parse(resp)) {
155                                     egCore.audio.play(
156                                         'warning.hold.uncancel_failed');
157                                     console.error('unable to uncancel hold: ' 
158                                         + evt.toString());
159                                 }
160                                 uncancel_one();
161                             });
162                         }
163
164                         uncancel_one();
165                     }
166                 }
167             ]
168         }).result;
169     }
170
171     service.get_cancel_reasons = function() {
172         if (egCore.env.ahrcc) return $q.when(egCore.env.ahrcc.list);
173         return egCore.pcrud.retrieveAll('ahrcc', {}, {atomic : true})
174         .then(function(list) { return egCore.env.absorbList(list, 'ahrcc').list });
175     }
176
177     // Updates a batch of holds, notifies on each response.
178     // new_values = array of hashes describing values to change,
179     // including the id of the hold to change.
180     // e.g. {id : 1, mint_condition : true}
181     service.update_holds = function(new_values) {
182         return egCore.net.request(
183             'open-ils.circ',
184             'open-ils.circ.hold.update.batch',
185             egCore.auth.token(), null, new_values).then(
186             function(resp) {
187                 if (evt = egCore.evt.parse(resp)) {
188                     egCore.audio.play(
189                         'warning.hold.batch_update');
190                     console.error('unable to batch update holds: '
191                         + evt.toString());
192                 } else {
193                     egCore.audio.play(
194                         'success.hold.batch_update');
195                 }
196             }
197         );
198     }
199
200     service.set_copy_quality = function(hold_ids) {
201         if (!hold_ids.length) return $q.when();
202         return $uibModal.open({
203             templateUrl : './circ/share/t_hold_copy_quality_dialog',
204             backdrop: 'static',
205             controller : 
206                 ['$scope', '$uibModalInstance',
207                 function($scope, $uibModalInstance) {
208
209                     function update(val) {
210                         var vals = hold_ids.map(function(hold_id) {
211                             return {id : hold_id, mint_condition : val}})
212                         service.update_holds(vals).finally(function() {
213                             $uibModalInstance.close();
214                         });
215                     }
216                     $scope.good = function() { update(true) }
217                     $scope.any = function() { update(false) }
218                     $scope.cancel = function() { $uibModalInstance.dismiss() }
219                 }
220             ]
221         }).result;
222     }
223
224     service.edit_pickup_lib = function(hold_ids) {
225         if (!hold_ids.length) return $q.when();
226         return $uibModal.open({
227             templateUrl : './circ/share/t_hold_edit_pickup_lib',
228             backdrop: 'static',
229             controller : 
230                 ['$scope', '$uibModalInstance',
231                 function($scope, $uibModalInstance) {
232                     $scope.cant_be_pickup = function (id) { return !egCore.org.CanHaveUsers(id); };
233                     $scope.args = {};
234                     $scope.ok = function() { 
235                         var vals = hold_ids.map(function(hold_id) {
236                             return {
237                                 id : hold_id, 
238                                 pickup_lib : $scope.args.org_unit.id()
239                             }
240                         });
241                         service.update_holds(vals).finally(function() {
242                             $uibModalInstance.close();
243                         });
244                     }
245                     $scope.cancel = function() { $uibModalInstance.dismiss() }
246                 }
247             ]
248         }).result;
249     }
250
251     service.get_sms_carriers = function() {
252         if (egCore.env.csc) return $q.when(egCore.env.csc.list);
253         return egCore.pcrud.retrieveAll('csc', {}, {atomic : true})
254         .then(function(list) { return egCore.env.absorbList(list, 'csc').list });
255     }
256
257     service.edit_notify_prefs = function(hold_ids) {
258         if (!hold_ids.length) return $q.when();
259         return $uibModal.open({
260             templateUrl : './circ/share/t_hold_notification_prefs',
261             backdrop: 'static',
262             controller : 
263                 ['$scope', '$uibModalInstance', 'sms_carriers',
264                 function($scope, $uibModalInstance, sms_carriers) {
265                     $scope.args = {}
266                     $scope.sms_carriers = sms_carriers;
267                     $scope.num_holds = hold_ids.length;
268                     $scope.ok = function() { 
269
270                         var vals = hold_ids.map(function(hold_id) {
271                             var val = {id : hold_id};
272                             angular.forEach(
273                                 ['email', 'phone', 'sms'],
274                                 function(type) {
275                                     var key = type + '_notify';
276                                     if ($scope.args['update_' + key]) 
277                                         val[key] = $scope.args[key];
278                                 }
279                             );
280                             if ($scope.args.update_sms_carrier)
281                                 val.sms_carrier = $scope.args.sms_carrier.id();
282                             return val;
283                         });
284
285                         service.update_holds(vals).finally(function() {
286                             $uibModalInstance.close();
287                         });
288                     }
289                     $scope.cancel = function() { $uibModalInstance.dismiss() }
290                 }
291             ],
292             resolve : {
293                 sms_carriers : service.get_sms_carriers
294             }
295         }).result;
296     }
297
298     service.edit_dates = function(hold_ids) {
299         if (!hold_ids.length) return $q.when();
300
301         // collects the fields from the dialog the user wishes to modify
302         function relay_to_update(modal_scope) {
303             var vals = hold_ids.map(function(hold_id) {
304                 var val = {id : hold_id};
305                 angular.forEach(
306                     ['thaw_date', 'request_time', 'expire_time', 'shelf_expire_time'], 
307                     function(field) {
308                         if (modal_scope.args['modify_' + field]) { 
309                             val[field] = modal_scope.args[field].toISOString();
310                             if (field === 'thaw_date') {
311                             //If we are setting the thaw_date, freeze the hold.
312                                 val['frozen'] = true;
313                             }
314                         }
315                     }
316                 );
317
318                 return val;
319             });
320
321             console.log(JSON.stringify(vals,null,2));
322             return service.update_holds(vals);
323         }
324
325         return $uibModal.open({
326             templateUrl : './circ/share/t_hold_dates',
327             backdrop: 'static',
328             controller : 
329                 ['$scope', '$uibModalInstance',
330                 function($scope, $uibModalInstance) {
331                     var today = new Date();
332                     $scope.args = {
333                         thaw_date : today,
334                         request_time : today,
335                         expire_time : today,
336                         shelf_expire_time : today
337                     }
338                     $scope.num_holds = hold_ids.length;
339                     $scope.ok = function() { 
340                         relay_to_update($scope).then($uibModalInstance.close);
341                     }
342                     $scope.cancel = function() { $uibModalInstance.dismiss() }
343                     $scope.minDate = new Date();
344                     //watch for changes to the hold dates, and perform validations
345                     $scope.$watch('args', function(newValue,oldValue,scope) {
346                         if (newValue['thaw_date'] && newValue['thaw_date'] < today) {
347                             $scope.args['thaw_date'] = today;
348                             $scope.args.thaw_date_error = true;
349                         }
350                         if (newValue['thaw_date'] && newValue['thaw_date'] > today) {
351                             $scope.args.thaw_date_error = false;
352                         }
353                     }, true);
354                 }
355             ],
356         }).result;
357     }
358
359     service.update_field_with_confirm = function(hold_ids, msg_key, field, value) {
360         if (!hold_ids.length) return $q.when();
361
362         return egConfirmDialog.open(
363             egCore.strings[msg_key], '', {num_holds : hold_ids.length})
364         .result.then(function() {
365
366             var vals = hold_ids.map(function(hold_id) {
367                 val = {id : hold_id};
368                 val[field] = value;
369                 return val;
370             });
371             return service.update_holds(vals);
372         });
373     }
374
375     service.suspend_holds = function(hold_ids) {
376         return service.update_field_with_confirm(
377             hold_ids, 'SUSPEND_HOLDS', 'frozen', true);
378     }
379
380     service.activate_holds = function(hold_ids) {
381         return service.update_field_with_confirm(
382             hold_ids, 'ACTIVATE_HOLDS', 'frozen', false);
383     }
384
385     service.set_top_of_queue = function(hold_ids) {
386         return service.update_field_with_confirm(
387             hold_ids, 'SET_TOP_OF_QUEUE', 'cut_in_line', true);
388     }
389
390     service.clear_top_of_queue = function(hold_ids) {
391         return service.update_field_with_confirm(
392             hold_ids, 'CLEAR_TOP_OF_QUEUE', 'cut_in_line', null);
393     }
394
395     service.transfer_to_marked_title = function(hold_ids) {
396         if (!hold_ids.length) return $q.when();
397
398         var bib_id = egCore.hatch.getLocalItem(
399             'eg.circ.hold.title_transfer_target');
400
401         if (!bib_id) {
402             // no target marked
403             return egAlertDialog.open(
404                 egCore.strings.NO_HOLD_TRANSFER_TITLE_MARKED).result;
405         }
406
407         return egConfirmDialog.open(
408             egCore.strings.TRANSFER_HOLD_TO_TITLE, '', {
409                 num_holds : hold_ids.length,
410                 bib_id : bib_id
411             }
412         ).result.then(function() {
413             return egCore.net.request(
414                 'open-ils.circ',
415                 'open-ils.circ.hold.change_title.specific_holds',
416                 egCore.auth.token(), bib_id, hold_ids);
417         });
418     }
419
420     service.transfer_all_bib_holds_to_marked_title = function(bib_ids) {
421         if (!bib_ids.length) return $q.when();
422
423         var target_bib_id = egCore.hatch.getLocalItem(
424             'eg.circ.hold.title_transfer_target');
425
426         if (!target_bib_id) {
427             // no target marked
428             return egAlertDialog.open(
429                 egCore.strings.NO_HOLD_TRANSFER_TITLE_MARKED).result;
430         }
431
432         return egConfirmDialog.open(
433             egCore.strings.TRANSFER_ALL_BIB_HOLDS_TO_TITLE, '', {
434                 num_bibs : bib_ids.length,
435                 bib_id : target_bib_id
436             }
437         ).result.then(function() {
438             return egCore.net.request(
439                 'open-ils.circ',
440                 'open-ils.circ.hold.change_title',
441                 egCore.auth.token(), target_bib_id, bib_ids);
442         });
443     }
444
445     // serially retargets each hold
446     service.retarget = function(hold_ids) {
447         if (!hold_ids.length) return $q.when();
448         var deferred = $q.defer();
449
450         egConfirmDialog.open(
451             egCore.strings.RETARGET_HOLDS, '', 
452             {hold_ids : hold_ids.join(',')}
453
454         ).result.then(function() {
455
456             function do_one() {
457                 var hold_id = hold_ids.pop();
458                 if (!hold_id) {
459                     deferred.resolve();
460                     return;
461                 }
462
463                 egCore.net.request(
464                     'open-ils.circ',
465                     'open-ils.circ.hold.reset',
466                     egCore.auth.token(), hold_id).finally(do_one);
467             }
468
469             do_one(); // kick it off
470         });
471
472         return deferred.promise;
473     }
474
475     // fleshes orgs, etc. for hold data blobs retrieved from
476     // open-ils.circ.hold.details[.batch].retrieve
477     service.local_flesh = function(hold_data) {
478
479         hold_data.status_string = 
480             egCore.strings['HOLD_STATUS_' + hold_data.status] 
481             || hold_data.status;
482
483         var hold = hold_data.hold;
484         var volume = hold_data.volume;
485         hold.pickup_lib(egCore.org.get(hold.pickup_lib()));
486         hold.current_shelf_lib(egCore.org.get(hold.current_shelf_lib()));
487         hold_data.id = hold.id();
488
489         // TODO: LP#1697954 fleshing calls below are deprecated in favor
490         // of API fleshing.
491
492         if (hold.requestor() && typeof hold.requestor() != 'object') {
493             console.debug('fetching hold requestor');
494             egCore.pcrud.retrieve('au',hold.requestor()).then(function(u) { hold.requestor(u) });
495         }
496
497         if (hold.cancel_cause() && typeof hold.cancel_cause() != 'object') {
498             console.debug('fetching hold cancel cause');
499             egCore.pcrud.retrieve('ahrcc',hold.cancel_cause()).then(function(c) { hold.cancel_cause(c) });
500         }
501
502         if (hold.usr() && typeof hold.usr() != 'object') {
503             console.debug('fetching hold user');
504             egCore.pcrud.retrieve('au',hold.usr()).then(function(u) { hold.usr(u) });
505         }
506
507         if (hold.sms_carrier() && typeof hold.sms_carrier() != 'object') {
508             console.debug('fetching sms carrier');
509             egCore.pcrud.retrieve('csc',hold.sms_carrier()).then(function(c) { hold.sms_carrier(c) });
510         }
511
512         // current_copy is not always fleshed in the API
513         if (hold.current_copy() && typeof hold.current_copy() != 'object') {
514             hold.current_copy(hold_data.copy);
515         }
516
517         if (hold.current_copy()) {
518             // likewise, current_copy's status isn't fleshed in the API
519             if(hold.current_copy().status() !== null &&
520                typeof hold.current_copy().status() != 'object')
521                 egCore.pcrud.retrieve('ccs',hold.current_copy().status()
522                     ).then(function(c) { hold.current_copy().status(c) });
523         
524             // current_copy's shelving location position isn't always accessible
525             if (hold.current_copy().location()) {
526                 //console.debug('fetching hold copy location order');
527                 var location_id;
528                 if (typeof hold.current_copy().location() != 'object') {
529                     location_id = hold.current_copy().location();
530                 } else {
531                     location_id = hold.current_copy().location().id();
532                 }
533                 egCore.pcrud.search(
534                     'acplo',
535                     {location: location_id, org: egCore.auth.user().ws_ou()},
536                     null,
537                     {atomic:true}
538                 ).then(function(orders) {
539                     if(orders[0]){
540                         hold_data.hold._copy_location_position = orders[0].position();
541                     } else {
542                         hold_data.hold._copy_location_position = 999;
543                     }
544                 });
545             }
546
547             //Call number affixes are not always fleshed in the API
548             if (hold_data.volume.prefix) {
549                 //console.debug('fetching call number prefix');
550                 //console.log(hold_data.volume.prefix());
551                 egCore.pcrud.retrieve('acnp',hold_data.volume.prefix())
552                 .then(function(p) {hold_data.volume.prefix = p.label(); hold_data.volume.prefix_sortkey = p.label_sortkey()});
553             }
554             if (hold_data.volume.suffix) {
555                 //console.debug('fetching call number suffix');
556                 //console.log(hold_data.volume.suffix());
557                 egCore.pcrud.retrieve('acns',hold_data.volume.suffix())
558                 .then(function(s) {hold_data.volume.suffix = s.label(); hold_data.volume.suffix_sortkey = s.label_sortkey()});
559             }
560         }
561     }
562
563     return service;
564 }])
565
566 /**  
567  * Action handlers for the common Hold grid UI.
568  * These generally scrub the data for valid input then pass the
569  * holds / copies / etc. off to the relevant action in egHolds or egCirc.
570  *
571  * Caller must apply a reset_page function, which is called after 
572  * most actionis are performed.
573  */
574 .factory('egHoldGridActions', 
575        ['$window','$location','$timeout','egCore','egHolds','egCirc',
576 function($window , $location , $timeout , egCore , egHolds , egCirc) {
577     
578     var service = {};
579
580     service.refresh = function() {
581         console.error('egHoldGridActions.refresh not defined!');
582     }
583
584     service.cancel_hold = function(items) {
585         var hold_ids = items.filter(function(item) {
586             return !item.hold.cancel_time();
587         }).map(function(item) {return item.hold.id()});
588
589         return egHolds.cancel_holds(hold_ids).then(service.refresh);
590     }
591
592     service.cancel_wide_hold = function(items) {
593         var hold_ids = items.filter(function(item) {
594             return !item.hold.cancel_time;
595         }).map(function(item) {return item.hold.id});
596
597         return egHolds.cancel_holds(hold_ids).then(service.refresh);
598     }
599
600     service.uncancel_hold = function(items) {
601         var hold_ids = items.filter(function(item) {
602             return item.hold.cancel_time();
603         }).map(function(item) {return item.hold.id()});
604
605         return egHolds.uncancel_holds(hold_ids).then(service.refresh);
606     }
607
608     service.uncancel_wide_hold = function(items) {
609         var hold_ids = items.filter(function(item) {
610             return item.hold.cancel_time;
611         }).map(function(item) {return item.hold.id});
612
613         return egHolds.uncancel_holds(hold_ids).then(service.refresh);
614     }
615
616     // jump to circ list for either 1) the targeted copy or
617     // 2) the hold target copy for copy-level holds
618     service.show_recent_circs = function(items) {
619         var focus = items.length == 1;
620         angular.forEach(items, function(item) {
621             if (item.copy) {
622                 var url = egCore.env.basePath +
623                           '/cat/item/' +
624                           item.copy.id() +
625                           '/circ_list';
626                 $timeout(function() { var x = $window.open(url, '_blank'); if (focus) x.focus() });
627             }
628         });
629     }
630
631     // jump to circ list for either 1) the targeted copy or
632     // 2) the hold target copy for copy-level holds
633     service.show_recent_circs_wide = function(items) {
634         var focus = items.length == 1;
635         angular.forEach(items, function(item) {
636             if (item.hold.cp_id) {
637                 var url = egCore.env.basePath +
638                           '/cat/item/' +
639                           item.hold.cp_id +
640                           '/circ_list';
641                 $timeout(function() { var x = $window.open(url, '_blank'); if (focus) x.focus() });
642             }
643         });
644     }
645
646     service.show_patrons = function(items) {
647         var focus = items.length == 1;
648         angular.forEach(items, function(item) {
649             var url = egCore.env.basePath +
650                       'circ/patron/' +
651                       item.hold.usr().id() +
652                       '/holds';
653             $timeout(function() { var x = $window.open(url, '_blank'); if (focus) x.focus() });
654         });
655     }
656
657     service.show_patrons_wide = function(items) {
658         var focus = items.length == 1;
659         angular.forEach(items, function(item) {
660             var url = egCore.env.basePath +
661                       'circ/patron/' +
662                       item.hold.usr_id +
663                       '/holds';
664             $timeout(function() { var x = $window.open(url, '_blank'); if (focus) x.focus() });
665         });
666     }
667
668     service.show_holds_for_title = function(items) {
669         var focus = items.length == 1;
670         angular.forEach(items, function(item) {
671             var url = '/eg2/staff/catalog/record/' + item.mvr.doc_id() + '/holds';
672             $timeout(function() { var x = $window.open(url, '_blank'); if (focus) x.focus() });
673         });
674     }
675
676     service.show_holds_for_title_wide = function(items) {
677         var focus = items.length == 1;
678         angular.forEach(items, function(item) {
679             var url = '/eg2/staff/catalog/record/' + item.hold.record_id + '/holds';
680             $timeout(function() { var x = $window.open(url, '_blank'); if (focus) x.focus() });
681         });
682     }
683
684
685     function generic_update(items, action) {
686         if (!items.length) return $q.when();
687         var hold_ids = items.map(function(item) {return item.hold.id()});
688         return egHolds[action](hold_ids).then(service.refresh);
689     }
690
691     function generic_update_wide(items, action) {
692         if (!items.length) return $q.when();
693         var hold_ids = items.map(function(item) {return item.hold.id});
694         return egHolds[action](hold_ids).then(service.refresh);
695     }
696
697     service.set_copy_quality = function(items) {
698         generic_update(items, 'set_copy_quality'); }
699     service.edit_pickup_lib = function(items) {
700         generic_update(items, 'edit_pickup_lib'); }
701     service.edit_notify_prefs = function(items) {
702         generic_update(items, 'edit_notify_prefs'); }
703     service.edit_dates = function(items) {
704         generic_update(items, 'edit_dates'); }
705     service.suspend = function(items) {
706         generic_update(items, 'suspend_holds'); }
707     service.activate = function(items) {
708         generic_update(items, 'activate_holds'); }
709     service.set_top_of_queue = function(items) {
710         generic_update(items, 'set_top_of_queue'); }
711     service.clear_top_of_queue = function(items) {
712         generic_update(items, 'clear_top_of_queue'); }
713     service.transfer_to_marked_title = function(items) {
714         generic_update(items, 'transfer_to_marked_title'); }
715
716     service.set_copy_quality_wide = function(items) {
717         generic_update_wide(items, 'set_copy_quality'); }
718     service.edit_pickup_lib_wide = function(items) {
719         generic_update_wide(items, 'edit_pickup_lib'); }
720     service.edit_notify_prefs_wide = function(items) {
721         generic_update_wide(items, 'edit_notify_prefs'); }
722     service.edit_dates_wide = function(items) {
723         generic_update_wide(items, 'edit_dates'); }
724     service.suspend_wide = function(items) {
725         generic_update_wide(items, 'suspend_holds'); }
726     service.activate_wide = function(items) {
727         generic_update_wide(items, 'activate_holds'); }
728     service.set_top_of_queue_wide = function(items) {
729         generic_update_wide(items, 'set_top_of_queue'); }
730     service.clear_top_of_queue_wide = function(items) {
731         generic_update_wide(items, 'clear_top_of_queue'); }
732     service.transfer_to_marked_title_wide = function(items) {
733         generic_update_wide(items, 'transfer_to_marked_title'); }
734
735     service.mark_damaged = function(items) {
736         angular.forEach(items, function(item) {
737             if (item.copy) {
738                 egCirc.mark_damaged({
739                     id: item.copy.id(),
740                     barcode: item.copy.barcode()
741                 }).then(service.refresh);
742             }
743         });
744     }
745
746     service.mark_damaged_wide = function(items) {
747         angular.forEach(items, function(item) {
748             if (item.copy) {
749                 egCirc.mark_damaged({
750                     id: item.hold.cp_id,
751                     barcode: item.hold.cp_barcode
752                 }).then(service.refresh);
753             }
754         });
755     }
756
757     service.mark_discard = function(items) {
758         var copies = items
759             .filter(function(item) { return Boolean(item.copy) })
760             .map(function(item) {
761                 return {id: item.copy.id(), barcode: item.copy.barcode()}
762             });
763         if (copies.length)
764             egCirc.mark_discard(copies).then(service.refresh);
765     }
766
767     service.mark_missing = function(items) {
768         var copies = items
769             .filter(function(item) { return Boolean(item.copy) })
770             .map(function(item) {
771                 return {id: item.copy.id(), barcode: item.copy.barcode()}
772             });
773         if (copies.length)
774             egCirc.mark_missing(copies).then(service.refresh);
775     }
776
777     service.mark_missing_wide = function(items) {
778         var copies = items
779             .filter(function(item) { return Boolean(item.hold.cp_id) })
780             .map(function(item) { return {id: item.hold.cp_id, barcode: item.hold.cp_barcode}; });
781         if (copies.length)
782             egCirc.mark_missing(copies).then(service.refresh);
783     }
784
785     service.mark_discard_wide = function(items) {
786         var copies = items
787             .filter(function(item) { return Boolean(item.hold.cp_id) })
788             .map(function(item) { return {id: item.hold.cp_id, barcode: item.hold.cp_barcode}; });
789         if (copies.length)
790             egCirc.mark_discard(copies).then(service.refresh);
791     }
792
793     service.retarget = function(items) {
794         var hold_ids = items.map(function(item) { return item.hold.id() });
795         egHolds.retarget(hold_ids).then(service.refresh);
796     }
797
798     service.retarget_wide = function(items) {
799         var hold_ids = items.map(function(item) { return item.hold.id });
800         egHolds.retarget(hold_ids).then(service.refresh);
801     }
802
803     return service;
804 }])
805
806 /**
807  * Hold details interface 
808  */
809 .directive('egHoldDetails', function() {
810     return {
811         restrict : 'AE',
812         templateUrl : './circ/share/t_hold_details',
813         scope : {
814             holdId : '=',
815             // if set, called whenever hold details are retrieved.  The
816             // argument is the hold blob returned from hold.details.retrieve
817             holdRetrieved : '=',
818             showPatron : '='
819         },
820         controller : [
821                     '$scope','$uibModal','egCore','egHolds','egCirc',
822             function($scope , $uibModal , egCore , egHolds , egCirc) {
823
824                 function draw() {
825                     if (!$scope.holdId) return;
826
827                     egCore.net.request(
828                         'open-ils.circ',
829                         'open-ils.circ.hold.details.retrieve.authoritative',
830                         egCore.auth.token(), $scope.holdId, {
831                             include_current_copy : true,
832                             include_usr          : true,
833                             include_cancel_cause : true,
834                             include_sms_carrier  : true,
835                             include_requestor    : true
836                         }
837                     ).then(function(hold_data) { 
838                         egHolds.local_flesh(hold_data);
839     
840                         angular.forEach(hold_data, 
841                             function(val, key) { $scope[key] = val });
842
843                         // fetch + flesh the cancel_cause if needed
844                         if ($scope.hold.cancel_time()) {
845                             egHolds.get_cancel_reasons().then(function() {
846                                 // egHolds caches the causes in egEnv
847                                 $scope.hold.cancel_cause(
848                                     egCore.env.ahrcc.map[$scope.hold.cancel_cause()]);
849                             })
850                         }
851
852                         if ($scope.hold.current_copy()) {
853                             egCirc.flesh_copy_location($scope.hold.current_copy());
854                         }
855
856                         if ($scope.holdRetrieved)
857                             $scope.holdRetrieved(hold_data);
858
859                     });
860                 }
861
862                 $scope.show_notify_tab = function() {
863                     $scope.detail_tab = 'notify';
864                     egCore.pcrud.search('ahn',
865                         {hold : $scope.hold.id()}, 
866                         {flesh : 1, flesh_fields : {ahn : ['notify_staff']}}, 
867                         {atomic : true}
868                     ).then(function(nots) {
869                         $scope.hold.notifications(nots);
870                     });
871                 }
872
873                 $scope.delete_note = function(note) {
874                     egCore.pcrud.remove(note).then(function() {
875                         // remove the deleted note from the locally fleshed notes
876                         $scope.hold.notes(
877                             $scope.hold.notes().filter(function(n) {
878                                 return n.id() != note.id()
879                             })
880                         );
881                     });
882                 }
883
884                 $scope.new_note = function() {
885                     return $uibModal.open({
886                         templateUrl : './circ/share/t_hold_note_dialog',
887                         backdrop: 'static',
888                         controller : 
889                             ['$scope', '$uibModalInstance',
890                             function($scope, $uibModalInstance) {
891                                 $scope.args = {};
892                                 $scope.ok = function() {
893                                     $uibModalInstance.close($scope.args)
894                                 },
895                                 $scope.cancel = function($event) {
896                                     $uibModalInstance.dismiss();
897                                     $event.preventDefault();
898                                 }
899                             }
900                         ]
901                     }).result.then(function(args) {
902                         var note = new egCore.idl.ahrn();
903                         note.hold($scope.hold.id());
904                         note.staff(true);
905                         note.slip(args.slip);
906                         note.pub(args.pub); 
907                         note.title(args.title);
908                         note.body(args.body);
909                         return egCore.pcrud.create(note).then(function(n) {
910                             $scope.hold.notes().push(n);
911                         });
912                     });
913                 }
914
915                 $scope.new_notification = function() {
916                     return $uibModal.open({
917                         templateUrl : './circ/share/t_hold_notification_dialog',
918                         backdrop: 'static',
919                         controller : 
920                             ['$scope', '$uibModalInstance',
921                             function($scope, $uibModalInstance) {
922                                 $scope.args = {};
923                                 $scope.ok = function() {
924                                     $uibModalInstance.close($scope.args)
925                                 },
926                                 $scope.cancel = function($event) {
927                                     $uibModalInstance.dismiss();
928                                     $event.preventDefault();
929                                 }
930                             }
931                         ]
932                     }).result.then(function(args) {
933                         var note = new egCore.idl.ahn();
934                         note.hold($scope.hold.id());
935                         note.method(args.method);
936                         note.note(args.note);
937                         note.notify_staff(egCore.auth.user().id());
938                         note.notify_time('now');
939                         return egCore.pcrud.create(note).then(function(n) {
940                             n.notify_staff(egCore.auth.user());
941                             $scope.hold.notifications().push(n);
942                         });
943                     });
944                 }
945
946                 $scope.$watch('holdId', function(newVal, oldVal) {
947                     if (newVal != oldVal) draw();
948                 });
949
950                 draw();
951             }
952         ]
953     }
954 })
955
956