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