]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/web/js/ui/default/staff/circ/services/holds.js
lp1749502 Holds Pull List Print Order
[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         if (volume) {
521             //Call number affixes are not always fleshed in the API
522             if (volume.prefix() && typeof volume.prefix() != 'object') {
523                 console.debug('fetching call number prefix');
524                 egCore.pcrud.retrieve('acnp',volume.prefix()).then(function(p) {volume.prefix(p)});
525             }
526             if (volume.suffix() && typeof volume.suffix() != 'object') {
527                 console.debug('fetching call number prefix');
528                 egCore.pcrud.retrieve('acns',volume.suffix()).then(function(s) {volume.suffix(s)});
529             }
530         }
531     }
532
533     return service;
534 }])
535
536 /**  
537  * Action handlers for the common Hold grid UI.
538  * These generally scrub the data for valid input then pass the
539  * holds / copies / etc. off to the relevant action in egHolds or egCirc.
540  *
541  * Caller must apply a reset_page function, which is called after 
542  * most actionis are performed.
543  */
544 .factory('egHoldGridActions', 
545        ['$window','$location','$timeout','egCore','egHolds','egCirc',
546 function($window , $location , $timeout , egCore , egHolds , egCirc) {
547     
548     var service = {};
549
550     service.refresh = function() {
551         console.error('egHoldGridActions.refresh not defined!');
552     }
553
554     service.cancel_hold = function(items) {
555         var hold_ids = items.filter(function(item) {
556             return !item.hold.cancel_time();
557         }).map(function(item) {return item.hold.id()});
558
559         return egHolds.cancel_holds(hold_ids).then(service.refresh);
560     }
561
562     service.cancel_wide_hold = function(items) {
563         var hold_ids = items.filter(function(item) {
564             return !item.hold.cancel_time;
565         }).map(function(item) {return item.hold.id});
566
567         return egHolds.cancel_holds(hold_ids).then(service.refresh);
568     }
569
570     service.uncancel_hold = function(items) {
571         var hold_ids = items.filter(function(item) {
572             return item.hold.cancel_time();
573         }).map(function(item) {return item.hold.id()});
574
575         return egHolds.uncancel_holds(hold_ids).then(service.refresh);
576     }
577
578     service.uncancel_wide_hold = function(items) {
579         var hold_ids = items.filter(function(item) {
580             return item.hold.cancel_time;
581         }).map(function(item) {return item.hold.id});
582
583         return egHolds.uncancel_holds(hold_ids).then(service.refresh);
584     }
585
586     // jump to circ list for either 1) the targeted copy or
587     // 2) the hold target copy for copy-level holds
588     service.show_recent_circs = function(items) {
589         var focus = items.length == 1;
590         angular.forEach(items, function(item) {
591             if (item.copy) {
592                 var url = egCore.env.basePath +
593                           '/cat/item/' +
594                           item.copy.id() +
595                           '/circ_list';
596                 $timeout(function() { var x = $window.open(url, '_blank'); if (focus) x.focus() });
597             }
598         });
599     }
600
601     // jump to circ list for either 1) the targeted copy or
602     // 2) the hold target copy for copy-level holds
603     service.show_recent_circs_wide = function(items) {
604         var focus = items.length == 1;
605         angular.forEach(items, function(item) {
606             if (item.hold.cp_id) {
607                 var url = egCore.env.basePath +
608                           '/cat/item/' +
609                           item.hold.cp_id +
610                           '/circ_list';
611                 $timeout(function() { var x = $window.open(url, '_blank'); if (focus) x.focus() });
612             }
613         });
614     }
615
616     service.show_patrons = function(items) {
617         var focus = items.length == 1;
618         angular.forEach(items, function(item) {
619             var url = egCore.env.basePath +
620                       'circ/patron/' +
621                       item.hold.usr().id() +
622                       '/holds';
623             $timeout(function() { var x = $window.open(url, '_blank'); if (focus) x.focus() });
624         });
625     }
626
627     service.show_patrons_wide = function(items) {
628         var focus = items.length == 1;
629         angular.forEach(items, function(item) {
630             var url = egCore.env.basePath +
631                       'circ/patron/' +
632                       item.hold.usr_id +
633                       '/holds';
634             $timeout(function() { var x = $window.open(url, '_blank'); if (focus) x.focus() });
635         });
636     }
637
638     service.show_holds_for_title = function(items) {
639         var focus = items.length == 1;
640         angular.forEach(items, function(item) {
641             var url = egCore.env.basePath +
642                       'cat/catalog/record/' +
643                       item.mvr.doc_id() +
644                       '/holds';
645             $timeout(function() { var x = $window.open(url, '_blank'); if (focus) x.focus() });
646         });
647     }
648
649     service.show_holds_for_title_wide = function(items) {
650         var focus = items.length == 1;
651         angular.forEach(items, function(item) {
652             var url = egCore.env.basePath +
653                       'cat/catalog/record/' +
654                       item.hold.record_id +
655                       '/holds';
656             $timeout(function() { var x = $window.open(url, '_blank'); if (focus) x.focus() });
657         });
658     }
659
660
661     function generic_update(items, action) {
662         if (!items.length) return $q.when();
663         var hold_ids = items.map(function(item) {return item.hold.id()});
664         return egHolds[action](hold_ids).then(service.refresh);
665     }
666
667     function generic_update_wide(items, action) {
668         if (!items.length) return $q.when();
669         var hold_ids = items.map(function(item) {return item.hold.id});
670         return egHolds[action](hold_ids).then(service.refresh);
671     }
672
673     service.set_copy_quality = function(items) {
674         generic_update(items, 'set_copy_quality'); }
675     service.edit_pickup_lib = function(items) {
676         generic_update(items, 'edit_pickup_lib'); }
677     service.edit_notify_prefs = function(items) {
678         generic_update(items, 'edit_notify_prefs'); }
679     service.edit_dates = function(items) {
680         generic_update(items, 'edit_dates'); }
681     service.suspend = function(items) {
682         generic_update(items, 'suspend_holds'); }
683     service.activate = function(items) {
684         generic_update(items, 'activate_holds'); }
685     service.set_top_of_queue = function(items) {
686         generic_update(items, 'set_top_of_queue'); }
687     service.clear_top_of_queue = function(items) {
688         generic_update(items, 'clear_top_of_queue'); }
689     service.transfer_to_marked_title = function(items) {
690         generic_update(items, 'transfer_to_marked_title'); }
691
692     service.set_copy_quality_wide = function(items) {
693         generic_update_wide(items, 'set_copy_quality'); }
694     service.edit_pickup_lib_wide = function(items) {
695         generic_update_wide(items, 'edit_pickup_lib'); }
696     service.edit_notify_prefs_wide = function(items) {
697         generic_update_wide(items, 'edit_notify_prefs'); }
698     service.edit_dates_wide = function(items) {
699         generic_update_wide(items, 'edit_dates'); }
700     service.suspend_wide = function(items) {
701         generic_update_wide(items, 'suspend_holds'); }
702     service.activate_wide = function(items) {
703         generic_update_wide(items, 'activate_holds'); }
704     service.set_top_of_queue_wide = function(items) {
705         generic_update_wide(items, 'set_top_of_queue'); }
706     service.clear_top_of_queue_wide = function(items) {
707         generic_update_wide(items, 'clear_top_of_queue'); }
708     service.transfer_to_marked_title_wide = function(items) {
709         generic_update_wide(items, 'transfer_to_marked_title'); }
710
711     service.mark_damaged = function(items) {
712         angular.forEach(items, function(item) {
713             if (item.copy) {
714                 egCirc.mark_damaged({
715                     id: item.copy.id(),
716                     barcode: item.copy.barcode()
717                 }).then(service.refresh);
718             }
719         });
720     }
721
722     service.mark_damaged_wide = function(items) {
723         angular.forEach(items, function(item) {
724             if (item.copy) {
725                 egCirc.mark_damaged({
726                     id: item.hold.cp_id,
727                     barcode: item.hold.cp_barcode
728                 }).then(service.refresh);
729             }
730         });
731     }
732
733     service.mark_missing = function(items) {
734         var copy_ids = items
735             .filter(function(item) { return Boolean(item.copy) })
736             .map(function(item) { return item.copy.id() });
737         if (copy_ids.length) 
738             egCirc.mark_missing(copy_ids).then(service.refresh);
739     }
740
741     service.mark_missing_wide = function(items) {
742         var copy_ids = items
743             .filter(function(item) { return Boolean(item.hold.cp_id) })
744             .map(function(item) { return item.hold.cp_id });
745         if (copy_ids.length) 
746             egCirc.mark_missing(copy_ids).then(service.refresh);
747     }
748
749     service.retarget = function(items) {
750         var hold_ids = items.map(function(item) { return item.hold.id() });
751         egHolds.retarget(hold_ids).then(service.refresh);
752     }
753
754     service.retarget_wide = function(items) {
755         var hold_ids = items.map(function(item) { return item.hold.id });
756         egHolds.retarget(hold_ids).then(service.refresh);
757     }
758
759     return service;
760 }])
761
762 /**
763  * Hold details interface 
764  */
765 .directive('egHoldDetails', function() {
766     return {
767         restrict : 'AE',
768         templateUrl : './circ/share/t_hold_details',
769         scope : {
770             holdId : '=',
771             // if set, called whenever hold details are retrieved.  The
772             // argument is the hold blob returned from hold.details.retrieve
773             holdRetrieved : '=',
774             showPatron : '='
775         },
776         controller : [
777                     '$scope','$uibModal','egCore','egHolds','egCirc',
778             function($scope , $uibModal , egCore , egHolds , egCirc) {
779
780                 function draw() {
781                     if (!$scope.holdId) return;
782
783                     egCore.net.request(
784                         'open-ils.circ',
785                         'open-ils.circ.hold.details.retrieve.authoritative',
786                         egCore.auth.token(), $scope.holdId, {
787                             include_current_copy : true,
788                             include_usr          : true,
789                             include_cancel_cause : true,
790                             include_requestor    : true
791                         }
792                     ).then(function(hold_data) { 
793                         egHolds.local_flesh(hold_data);
794     
795                         angular.forEach(hold_data, 
796                             function(val, key) { $scope[key] = val });
797
798                         // fetch + flesh the cancel_cause if needed
799                         if ($scope.hold.cancel_time()) {
800                             egHolds.get_cancel_reasons().then(function() {
801                                 // egHolds caches the causes in egEnv
802                                 $scope.hold.cancel_cause(
803                                     egCore.env.ahrcc.map[$scope.hold.cancel_cause()]);
804                             })
805                         }
806
807                         if ($scope.hold.current_copy()) {
808                             egCirc.flesh_copy_location($scope.hold.current_copy());
809                         }
810
811                         if ($scope.holdRetrieved)
812                             $scope.holdRetrieved(hold_data);
813
814                     });
815                 }
816
817                 $scope.show_notify_tab = function() {
818                     $scope.detail_tab = 'notify';
819                     egCore.pcrud.search('ahn',
820                         {hold : $scope.hold.id()}, 
821                         {flesh : 1, flesh_fields : {ahn : ['notify_staff']}}, 
822                         {atomic : true}
823                     ).then(function(nots) {
824                         $scope.hold.notifications(nots);
825                     });
826                 }
827
828                 $scope.delete_note = function(note) {
829                     egCore.pcrud.remove(note).then(function() {
830                         // remove the deleted note from the locally fleshed notes
831                         $scope.hold.notes(
832                             $scope.hold.notes().filter(function(n) {
833                                 return n.id() != note.id()
834                             })
835                         );
836                     });
837                 }
838
839                 $scope.new_note = function() {
840                     return $uibModal.open({
841                         templateUrl : './circ/share/t_hold_note_dialog',
842                         backdrop: 'static',
843                         controller : 
844                             ['$scope', '$uibModalInstance',
845                             function($scope, $uibModalInstance) {
846                                 $scope.args = {};
847                                 $scope.ok = function() {
848                                     $uibModalInstance.close($scope.args)
849                                 },
850                                 $scope.cancel = function($event) {
851                                     $uibModalInstance.dismiss();
852                                     $event.preventDefault();
853                                 }
854                             }
855                         ]
856                     }).result.then(function(args) {
857                         var note = new egCore.idl.ahrn();
858                         note.hold($scope.hold.id());
859                         note.staff(true);
860                         note.slip(args.slip);
861                         note.pub(args.pub); 
862                         note.title(args.title);
863                         note.body(args.body);
864                         return egCore.pcrud.create(note).then(function(n) {
865                             $scope.hold.notes().push(n);
866                         });
867                     });
868                 }
869
870                 $scope.new_notification = function() {
871                     return $uibModal.open({
872                         templateUrl : './circ/share/t_hold_notification_dialog',
873                         backdrop: 'static',
874                         controller : 
875                             ['$scope', '$uibModalInstance',
876                             function($scope, $uibModalInstance) {
877                                 $scope.args = {};
878                                 $scope.ok = function() {
879                                     $uibModalInstance.close($scope.args)
880                                 },
881                                 $scope.cancel = function($event) {
882                                     $uibModalInstance.dismiss();
883                                     $event.preventDefault();
884                                 }
885                             }
886                         ]
887                     }).result.then(function(args) {
888                         var note = new egCore.idl.ahn();
889                         note.hold($scope.hold.id());
890                         note.method(args.method);
891                         note.note(args.note);
892                         note.notify_staff(egCore.auth.user().id());
893                         note.notify_time('now');
894                         return egCore.pcrud.create(note).then(function(n) {
895                             n.notify_staff(egCore.auth.user());
896                             $scope.hold.notifications().push(n);
897                         });
898                     });
899                 }
900
901                 $scope.$watch('holdId', function(newVal, oldVal) {
902                     if (newVal != oldVal) draw();
903                 });
904
905                 draw();
906             }
907         ]
908     }
909 })
910
911