5 angular.module('egCoreMod')
9 ['$uibModal','$q','egCore','egConfirmDialog','egAlertDialog',
10 function($uibModal , $q , egCore , egConfirmDialog , egAlertDialog) {
14 service.fetch_wide_holds = function(restrictions, order_by, limit, offset) {
15 return egCore.net.request(
17 'open-ils.circ.hold.wide_hash.stream',
19 restrictions, order_by, limit, offset
23 service.fetch_holds = function(hold_ids) {
24 var deferred = $q.defer();
26 // Fetch hold details in batches for better UI responsiveness.
30 function one_batch() {
31 var ids = hold_ids.slice(index, index + batch_size)
32 .filter(function(id) {return Boolean(id)}) // avoid nulls
34 console.debug('egHolds.fetch_holds => ' + ids);
44 'open-ils.circ.hold.details.batch.retrieve.authoritative',
45 egCore.auth.token(), ids, {
46 include_current_copy : true,
48 include_cancel_cause : true,
49 include_sms_carrier : true,
50 include_requestor : true
54 one_batch, // kick off the next batch
57 var hold = hold_data.hold;
58 hold_data.id = hold.id();
59 service.local_flesh(hold_data);
60 deferred.notify(hold_data);
65 one_batch(); // kick it off
66 return deferred.promise;
70 service.cancel_holds = function(hold_ids) {
72 return $uibModal.open({
73 templateUrl : './circ/share/t_cancel_hold_dialog',
76 ['$scope', '$uibModalInstance', 'cancel_reasons',
77 function($scope, $uibModalInstance, cancel_reasons) {
80 cancel_reasons : cancel_reasons,
81 num_holds : hold_ids.length
84 $scope.cancel = function($event) {
85 $uibModalInstance.dismiss();
86 $event.preventDefault();
89 $scope.ok = function() {
91 function cancel_one() {
92 var hold_id = hold_ids.pop();
94 $uibModalInstance.close();
98 'open-ils.circ', 'open-ils.circ.hold.cancel',
99 egCore.auth.token(), hold_id,
100 $scope.args.cancel_reason,
102 ).then(function(resp) {
103 if (evt = egCore.evt.parse(resp)) {
105 'warning.hold.cancel_failed');
106 console.error('unable to cancel hold: '
118 cancel_reasons : function() {
119 return service.get_cancel_reasons();
125 service.uncancel_holds = function(hold_ids) {
127 return $uibModal.open({
128 templateUrl : './circ/share/t_uncancel_hold_dialog',
131 ['$scope', '$uibModalInstance',
132 function($scope, $uibModalInstance) {
134 num_holds : hold_ids.length
137 $scope.cancel = function($event) {
138 $uibModalInstance.dismiss();
139 $event.preventDefault();
142 $scope.ok = function() {
144 function uncancel_one() {
145 var hold_id = hold_ids.pop();
147 $uibModalInstance.close();
151 'open-ils.circ', 'open-ils.circ.hold.uncancel',
152 egCore.auth.token(), hold_id
153 ).then(function(resp) {
154 if (evt = egCore.evt.parse(resp)) {
156 'warning.hold.uncancel_failed');
157 console.error('unable to uncancel hold: '
171 service.get_cancel_reasons = function() {
172 if (egCore.env.ahrcc) return $q.when(egCore.env.ahrcc.list);
173 return egCore.pcrud.retrieveAll('ahrcc', {}, {atomic : true})
174 .then(function(list) { return egCore.env.absorbList(list, 'ahrcc').list });
177 // Updates a batch of holds, notifies on each response.
178 // new_values = array of hashes describing values to change,
179 // including the id of the hold to change.
180 // e.g. {id : 1, mint_condition : true}
181 service.update_holds = function(new_values) {
182 return egCore.net.request(
184 'open-ils.circ.hold.update.batch',
185 egCore.auth.token(), null, new_values).then(
187 if (evt = egCore.evt.parse(resp)) {
189 'warning.hold.batch_update');
190 console.error('unable to batch update holds: '
194 'success.hold.batch_update');
200 service.set_copy_quality = function(hold_ids) {
201 if (!hold_ids.length) return $q.when();
202 return $uibModal.open({
203 templateUrl : './circ/share/t_hold_copy_quality_dialog',
206 ['$scope', '$uibModalInstance',
207 function($scope, $uibModalInstance) {
209 function update(val) {
210 var vals = hold_ids.map(function(hold_id) {
211 return {id : hold_id, mint_condition : val}})
212 service.update_holds(vals).finally(function() {
213 $uibModalInstance.close();
216 $scope.good = function() { update(true) }
217 $scope.any = function() { update(false) }
218 $scope.cancel = function() { $uibModalInstance.dismiss() }
224 service.edit_pickup_lib = function(hold_ids) {
225 if (!hold_ids.length) return $q.when();
226 return $uibModal.open({
227 templateUrl : './circ/share/t_hold_edit_pickup_lib',
230 ['$scope', '$uibModalInstance',
231 function($scope, $uibModalInstance) {
232 $scope.cant_be_pickup = function (id) { return !egCore.org.CanHaveUsers(id); };
234 $scope.ok = function() {
235 var vals = hold_ids.map(function(hold_id) {
238 pickup_lib : $scope.args.org_unit.id()
241 service.update_holds(vals).finally(function() {
242 $uibModalInstance.close();
245 $scope.cancel = function() { $uibModalInstance.dismiss() }
251 service.get_sms_carriers = function() {
252 if (egCore.env.csc) return $q.when(egCore.env.csc.list);
253 return egCore.pcrud.retrieveAll('csc', {}, {atomic : true})
254 .then(function(list) { return egCore.env.absorbList(list, 'csc').list });
257 service.edit_notify_prefs = function(hold_ids) {
258 if (!hold_ids.length) return $q.when();
259 return $uibModal.open({
260 templateUrl : './circ/share/t_hold_notification_prefs',
263 ['$scope', '$uibModalInstance', 'sms_carriers',
264 function($scope, $uibModalInstance, sms_carriers) {
266 $scope.sms_carriers = sms_carriers;
267 $scope.num_holds = hold_ids.length;
268 $scope.ok = function() {
270 var vals = hold_ids.map(function(hold_id) {
271 var val = {id : hold_id};
273 ['email', 'phone', 'sms'],
275 var key = type + '_notify';
276 if ($scope.args['update_' + key])
277 val[key] = $scope.args[key];
280 if ($scope.args.update_sms_carrier)
281 val.sms_carrier = $scope.args.sms_carrier.id();
285 service.update_holds(vals).finally(function() {
286 $uibModalInstance.close();
289 $scope.cancel = function() { $uibModalInstance.dismiss() }
293 sms_carriers : service.get_sms_carriers
298 service.edit_dates = function(hold_ids) {
299 if (!hold_ids.length) return $q.when();
301 // collects the fields from the dialog the user wishes to modify
302 function relay_to_update(modal_scope) {
303 var vals = hold_ids.map(function(hold_id) {
304 var val = {id : hold_id};
306 ['thaw_date', 'request_time', 'expire_time', 'shelf_expire_time'],
308 if (modal_scope.args['modify_' + field])
309 val[field] = modal_scope.args[field].toISOString();
316 console.log(JSON.stringify(vals,null,2));
317 return service.update_holds(vals);
320 return $uibModal.open({
321 templateUrl : './circ/share/t_hold_dates',
324 ['$scope', '$uibModalInstance',
325 function($scope, $uibModalInstance) {
326 var today = new Date();
329 request_time : today,
331 shelf_expire_time : today
333 $scope.num_holds = hold_ids.length;
334 $scope.ok = function() {
335 relay_to_update($scope).then($uibModalInstance.close);
337 $scope.cancel = function() { $uibModalInstance.dismiss() }
343 service.update_field_with_confirm = function(hold_ids, msg_key, field, value) {
344 if (!hold_ids.length) return $q.when();
346 return egConfirmDialog.open(
347 egCore.strings[msg_key], '', {num_holds : hold_ids.length})
348 .result.then(function() {
350 var vals = hold_ids.map(function(hold_id) {
351 val = {id : hold_id};
355 return service.update_holds(vals);
359 service.suspend_holds = function(hold_ids) {
360 return service.update_field_with_confirm(
361 hold_ids, 'SUSPEND_HOLDS', 'frozen', true);
364 service.activate_holds = function(hold_ids) {
365 return service.update_field_with_confirm(
366 hold_ids, 'ACTIVATE_HOLDS', 'frozen', false);
369 service.set_top_of_queue = function(hold_ids) {
370 return service.update_field_with_confirm(
371 hold_ids, 'SET_TOP_OF_QUEUE', 'cut_in_line', true);
374 service.clear_top_of_queue = function(hold_ids) {
375 return service.update_field_with_confirm(
376 hold_ids, 'CLEAR_TOP_OF_QUEUE', 'cut_in_line', null);
379 service.transfer_to_marked_title = function(hold_ids) {
380 if (!hold_ids.length) return $q.when();
382 var bib_id = egCore.hatch.getLocalItem(
383 'eg.circ.hold.title_transfer_target');
387 return egAlertDialog.open(
388 egCore.strings.NO_HOLD_TRANSFER_TITLE_MARKED).result;
391 return egConfirmDialog.open(
392 egCore.strings.TRANSFER_HOLD_TO_TITLE, '', {
393 num_holds : hold_ids.length,
396 ).result.then(function() {
397 return egCore.net.request(
399 'open-ils.circ.hold.change_title.specific_holds',
400 egCore.auth.token(), bib_id, hold_ids);
404 service.transfer_all_bib_holds_to_marked_title = function(bib_ids) {
405 if (!bib_ids.length) return $q.when();
407 var target_bib_id = egCore.hatch.getLocalItem(
408 'eg.circ.hold.title_transfer_target');
410 if (!target_bib_id) {
412 return egAlertDialog.open(
413 egCore.strings.NO_HOLD_TRANSFER_TITLE_MARKED).result;
416 return egConfirmDialog.open(
417 egCore.strings.TRANSFER_ALL_BIB_HOLDS_TO_TITLE, '', {
418 num_bibs : bib_ids.length,
419 bib_id : target_bib_id
421 ).result.then(function() {
422 return egCore.net.request(
424 'open-ils.circ.hold.change_title',
425 egCore.auth.token(), target_bib_id, bib_ids);
429 // serially retargets each hold
430 service.retarget = function(hold_ids) {
431 if (!hold_ids.length) return $q.when();
432 var deferred = $q.defer();
434 egConfirmDialog.open(
435 egCore.strings.RETARGET_HOLDS, '',
436 {hold_ids : hold_ids.join(',')}
438 ).result.then(function() {
441 var hold_id = hold_ids.pop();
449 'open-ils.circ.hold.reset',
450 egCore.auth.token(), hold_id).finally(do_one);
453 do_one(); // kick it off
456 return deferred.promise;
459 // fleshes orgs, etc. for hold data blobs retrieved from
460 // open-ils.circ.hold.details[.batch].retrieve
461 service.local_flesh = function(hold_data) {
463 hold_data.status_string =
464 egCore.strings['HOLD_STATUS_' + hold_data.status]
467 var hold = hold_data.hold;
468 var volume = hold_data.volume;
469 hold.pickup_lib(egCore.org.get(hold.pickup_lib()));
470 hold.current_shelf_lib(egCore.org.get(hold.current_shelf_lib()));
471 hold_data.id = hold.id();
473 // TODO: LP#1697954 fleshing calls below are deprecated in favor
476 if (hold.requestor() && typeof hold.requestor() != 'object') {
477 console.debug('fetching hold requestor');
478 egCore.pcrud.retrieve('au',hold.requestor()).then(function(u) { hold.requestor(u) });
481 if (hold.cancel_cause() && typeof hold.cancel_cause() != 'object') {
482 console.debug('fetching hold cancel cause');
483 egCore.pcrud.retrieve('ahrcc',hold.cancel_cause()).then(function(c) { hold.cancel_cause(c) });
486 if (hold.usr() && typeof hold.usr() != 'object') {
487 console.debug('fetching hold user');
488 egCore.pcrud.retrieve('au',hold.usr()).then(function(u) { hold.usr(u) });
491 if (hold.sms_carrier() && typeof hold.sms_carrier() != 'object') {
492 console.debug('fetching sms carrier');
493 egCore.pcrud.retrieve('csc',hold.sms_carrier()).then(function(c) { hold.sms_carrier(c) });
496 // current_copy is not always fleshed in the API
497 if (hold.current_copy() && typeof hold.current_copy() != 'object') {
498 hold.current_copy(hold_data.copy);
501 if (hold.current_copy()) {
502 // likewise, current_copy's status isn't fleshed in the API
503 if(hold.current_copy().status() !== null &&
504 typeof hold.current_copy().status() != 'object')
505 egCore.pcrud.retrieve('ccs',hold.current_copy().status()
506 ).then(function(c) { hold.current_copy().status(c) });
508 // current_copy's shelving location position isn't always accessible
509 if (hold.current_copy().location()) {
510 //console.debug('fetching hold copy location order');
512 if (typeof hold.current_copy().location() != 'object') {
513 location_id = hold.current_copy().location();
515 location_id = hold.current_copy().location().id();
519 {location: location_id, org: egCore.auth.user().ws_ou()},
522 ).then(function(orders) {
524 hold_data.hold._copy_location_position = orders[0].position();
526 hold_data.hold._copy_location_position = 999;
531 //Call number affixes are not always fleshed in the API
532 if (hold_data.volume.prefix) {
533 //console.debug('fetching call number prefix');
534 //console.log(hold_data.volume.prefix());
535 egCore.pcrud.retrieve('acnp',hold_data.volume.prefix())
536 .then(function(p) {hold_data.volume.prefix = p.label(); hold_data.volume.prefix_sortkey = p.label_sortkey()});
538 if (hold_data.volume.suffix) {
539 //console.debug('fetching call number suffix');
540 //console.log(hold_data.volume.suffix());
541 egCore.pcrud.retrieve('acns',hold_data.volume.suffix())
542 .then(function(s) {hold_data.volume.suffix = s.label(); hold_data.volume.suffix_sortkey = s.label_sortkey()});
551 * Action handlers for the common Hold grid UI.
552 * These generally scrub the data for valid input then pass the
553 * holds / copies / etc. off to the relevant action in egHolds or egCirc.
555 * Caller must apply a reset_page function, which is called after
556 * most actionis are performed.
558 .factory('egHoldGridActions',
559 ['$window','$location','$timeout','egCore','egHolds','egCirc',
560 function($window , $location , $timeout , egCore , egHolds , egCirc) {
564 service.refresh = function() {
565 console.error('egHoldGridActions.refresh not defined!');
568 service.cancel_hold = function(items) {
569 var hold_ids = items.filter(function(item) {
570 return !item.hold.cancel_time();
571 }).map(function(item) {return item.hold.id()});
573 return egHolds.cancel_holds(hold_ids).then(service.refresh);
576 service.cancel_wide_hold = function(items) {
577 var hold_ids = items.filter(function(item) {
578 return !item.hold.cancel_time;
579 }).map(function(item) {return item.hold.id});
581 return egHolds.cancel_holds(hold_ids).then(service.refresh);
584 service.uncancel_hold = function(items) {
585 var hold_ids = items.filter(function(item) {
586 return item.hold.cancel_time();
587 }).map(function(item) {return item.hold.id()});
589 return egHolds.uncancel_holds(hold_ids).then(service.refresh);
592 service.uncancel_wide_hold = function(items) {
593 var hold_ids = items.filter(function(item) {
594 return item.hold.cancel_time;
595 }).map(function(item) {return item.hold.id});
597 return egHolds.uncancel_holds(hold_ids).then(service.refresh);
600 // jump to circ list for either 1) the targeted copy or
601 // 2) the hold target copy for copy-level holds
602 service.show_recent_circs = function(items) {
603 var focus = items.length == 1;
604 angular.forEach(items, function(item) {
606 var url = egCore.env.basePath +
610 $timeout(function() { var x = $window.open(url, '_blank'); if (focus) x.focus() });
615 // jump to circ list for either 1) the targeted copy or
616 // 2) the hold target copy for copy-level holds
617 service.show_recent_circs_wide = function(items) {
618 var focus = items.length == 1;
619 angular.forEach(items, function(item) {
620 if (item.hold.cp_id) {
621 var url = egCore.env.basePath +
625 $timeout(function() { var x = $window.open(url, '_blank'); if (focus) x.focus() });
630 service.show_patrons = function(items) {
631 var focus = items.length == 1;
632 angular.forEach(items, function(item) {
633 var url = egCore.env.basePath +
635 item.hold.usr().id() +
637 $timeout(function() { var x = $window.open(url, '_blank'); if (focus) x.focus() });
641 service.show_patrons_wide = function(items) {
642 var focus = items.length == 1;
643 angular.forEach(items, function(item) {
644 var url = egCore.env.basePath +
648 $timeout(function() { var x = $window.open(url, '_blank'); if (focus) x.focus() });
652 service.show_holds_for_title = function(items) {
653 var focus = items.length == 1;
654 angular.forEach(items, function(item) {
655 var url = egCore.env.basePath +
656 'cat/catalog/record/' +
659 $timeout(function() { var x = $window.open(url, '_blank'); if (focus) x.focus() });
663 service.show_holds_for_title_wide = function(items) {
664 var focus = items.length == 1;
665 angular.forEach(items, function(item) {
666 var url = egCore.env.basePath +
667 'cat/catalog/record/' +
668 item.hold.record_id +
670 $timeout(function() { var x = $window.open(url, '_blank'); if (focus) x.focus() });
675 function generic_update(items, action) {
676 if (!items.length) return $q.when();
677 var hold_ids = items.map(function(item) {return item.hold.id()});
678 return egHolds[action](hold_ids).then(service.refresh);
681 function generic_update_wide(items, action) {
682 if (!items.length) return $q.when();
683 var hold_ids = items.map(function(item) {return item.hold.id});
684 return egHolds[action](hold_ids).then(service.refresh);
687 service.set_copy_quality = function(items) {
688 generic_update(items, 'set_copy_quality'); }
689 service.edit_pickup_lib = function(items) {
690 generic_update(items, 'edit_pickup_lib'); }
691 service.edit_notify_prefs = function(items) {
692 generic_update(items, 'edit_notify_prefs'); }
693 service.edit_dates = function(items) {
694 generic_update(items, 'edit_dates'); }
695 service.suspend = function(items) {
696 generic_update(items, 'suspend_holds'); }
697 service.activate = function(items) {
698 generic_update(items, 'activate_holds'); }
699 service.set_top_of_queue = function(items) {
700 generic_update(items, 'set_top_of_queue'); }
701 service.clear_top_of_queue = function(items) {
702 generic_update(items, 'clear_top_of_queue'); }
703 service.transfer_to_marked_title = function(items) {
704 generic_update(items, 'transfer_to_marked_title'); }
706 service.set_copy_quality_wide = function(items) {
707 generic_update_wide(items, 'set_copy_quality'); }
708 service.edit_pickup_lib_wide = function(items) {
709 generic_update_wide(items, 'edit_pickup_lib'); }
710 service.edit_notify_prefs_wide = function(items) {
711 generic_update_wide(items, 'edit_notify_prefs'); }
712 service.edit_dates_wide = function(items) {
713 generic_update_wide(items, 'edit_dates'); }
714 service.suspend_wide = function(items) {
715 generic_update_wide(items, 'suspend_holds'); }
716 service.activate_wide = function(items) {
717 generic_update_wide(items, 'activate_holds'); }
718 service.set_top_of_queue_wide = function(items) {
719 generic_update_wide(items, 'set_top_of_queue'); }
720 service.clear_top_of_queue_wide = function(items) {
721 generic_update_wide(items, 'clear_top_of_queue'); }
722 service.transfer_to_marked_title_wide = function(items) {
723 generic_update_wide(items, 'transfer_to_marked_title'); }
725 service.mark_damaged = function(items) {
726 angular.forEach(items, function(item) {
728 egCirc.mark_damaged({
730 barcode: item.copy.barcode()
731 }).then(service.refresh);
736 service.mark_damaged_wide = function(items) {
737 angular.forEach(items, function(item) {
739 egCirc.mark_damaged({
741 barcode: item.hold.cp_barcode
742 }).then(service.refresh);
747 service.mark_missing = function(items) {
749 .filter(function(item) { return Boolean(item.copy) })
750 .map(function(item) { return item.copy.id() });
752 egCirc.mark_missing(copy_ids).then(service.refresh);
755 service.mark_missing_wide = function(items) {
757 .filter(function(item) { return Boolean(item.hold.cp_id) })
758 .map(function(item) { return item.hold.cp_id });
760 egCirc.mark_missing(copy_ids).then(service.refresh);
763 service.retarget = function(items) {
764 var hold_ids = items.map(function(item) { return item.hold.id() });
765 egHolds.retarget(hold_ids).then(service.refresh);
768 service.retarget_wide = function(items) {
769 var hold_ids = items.map(function(item) { return item.hold.id });
770 egHolds.retarget(hold_ids).then(service.refresh);
777 * Hold details interface
779 .directive('egHoldDetails', function() {
782 templateUrl : './circ/share/t_hold_details',
785 // if set, called whenever hold details are retrieved. The
786 // argument is the hold blob returned from hold.details.retrieve
791 '$scope','$uibModal','egCore','egHolds','egCirc',
792 function($scope , $uibModal , egCore , egHolds , egCirc) {
795 if (!$scope.holdId) return;
799 'open-ils.circ.hold.details.retrieve.authoritative',
800 egCore.auth.token(), $scope.holdId, {
801 include_current_copy : true,
803 include_cancel_cause : true,
804 include_sms_carrier : true,
805 include_requestor : true
807 ).then(function(hold_data) {
808 egHolds.local_flesh(hold_data);
810 angular.forEach(hold_data,
811 function(val, key) { $scope[key] = val });
813 // fetch + flesh the cancel_cause if needed
814 if ($scope.hold.cancel_time()) {
815 egHolds.get_cancel_reasons().then(function() {
816 // egHolds caches the causes in egEnv
817 $scope.hold.cancel_cause(
818 egCore.env.ahrcc.map[$scope.hold.cancel_cause()]);
822 if ($scope.hold.current_copy()) {
823 egCirc.flesh_copy_location($scope.hold.current_copy());
826 if ($scope.holdRetrieved)
827 $scope.holdRetrieved(hold_data);
832 $scope.show_notify_tab = function() {
833 $scope.detail_tab = 'notify';
834 egCore.pcrud.search('ahn',
835 {hold : $scope.hold.id()},
836 {flesh : 1, flesh_fields : {ahn : ['notify_staff']}},
838 ).then(function(nots) {
839 $scope.hold.notifications(nots);
843 $scope.delete_note = function(note) {
844 egCore.pcrud.remove(note).then(function() {
845 // remove the deleted note from the locally fleshed notes
847 $scope.hold.notes().filter(function(n) {
848 return n.id() != note.id()
854 $scope.new_note = function() {
855 return $uibModal.open({
856 templateUrl : './circ/share/t_hold_note_dialog',
859 ['$scope', '$uibModalInstance',
860 function($scope, $uibModalInstance) {
862 $scope.ok = function() {
863 $uibModalInstance.close($scope.args)
865 $scope.cancel = function($event) {
866 $uibModalInstance.dismiss();
867 $event.preventDefault();
871 }).result.then(function(args) {
872 var note = new egCore.idl.ahrn();
873 note.hold($scope.hold.id());
875 note.slip(args.slip);
877 note.title(args.title);
878 note.body(args.body);
879 return egCore.pcrud.create(note).then(function(n) {
880 $scope.hold.notes().push(n);
885 $scope.new_notification = function() {
886 return $uibModal.open({
887 templateUrl : './circ/share/t_hold_notification_dialog',
890 ['$scope', '$uibModalInstance',
891 function($scope, $uibModalInstance) {
893 $scope.ok = function() {
894 $uibModalInstance.close($scope.args)
896 $scope.cancel = function($event) {
897 $uibModalInstance.dismiss();
898 $event.preventDefault();
902 }).result.then(function(args) {
903 var note = new egCore.idl.ahn();
904 note.hold($scope.hold.id());
905 note.method(args.method);
906 note.note(args.note);
907 note.notify_staff(egCore.auth.user().id());
908 note.notify_time('now');
909 return egCore.pcrud.create(note).then(function(n) {
910 n.notify_staff(egCore.auth.user());
911 $scope.hold.notifications().push(n);
916 $scope.$watch('holdId', function(newVal, oldVal) {
917 if (newVal != oldVal) draw();