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