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