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_requestor : true
53 one_batch, // kick off the next batch
56 var hold = hold_data.hold;
57 hold_data.id = hold.id();
58 service.local_flesh(hold_data);
59 deferred.notify(hold_data);
64 one_batch(); // kick it off
65 return deferred.promise;
69 service.cancel_holds = function(hold_ids) {
71 return $uibModal.open({
72 templateUrl : './circ/share/t_cancel_hold_dialog',
75 ['$scope', '$uibModalInstance', 'cancel_reasons',
76 function($scope, $uibModalInstance, cancel_reasons) {
79 cancel_reasons : cancel_reasons,
80 num_holds : hold_ids.length
83 $scope.cancel = function($event) {
84 $uibModalInstance.dismiss();
85 $event.preventDefault();
88 $scope.ok = function() {
90 function cancel_one() {
91 var hold_id = hold_ids.pop();
93 $uibModalInstance.close();
97 'open-ils.circ', 'open-ils.circ.hold.cancel',
98 egCore.auth.token(), hold_id,
99 $scope.args.cancel_reason,
101 ).then(function(resp) {
102 if (evt = egCore.evt.parse(resp)) {
104 'warning.hold.cancel_failed');
105 console.error('unable to cancel hold: '
117 cancel_reasons : function() {
118 return service.get_cancel_reasons();
124 service.uncancel_holds = function(hold_ids) {
126 return $uibModal.open({
127 templateUrl : './circ/share/t_uncancel_hold_dialog',
130 ['$scope', '$uibModalInstance',
131 function($scope, $uibModalInstance) {
133 num_holds : hold_ids.length
136 $scope.cancel = function($event) {
137 $uibModalInstance.dismiss();
138 $event.preventDefault();
141 $scope.ok = function() {
143 function uncancel_one() {
144 var hold_id = hold_ids.pop();
146 $uibModalInstance.close();
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)) {
155 'warning.hold.uncancel_failed');
156 console.error('unable to uncancel hold: '
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 });
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(
183 'open-ils.circ.hold.update.batch',
184 egCore.auth.token(), null, new_values).then(
186 if (evt = egCore.evt.parse(resp)) {
188 'warning.hold.batch_update');
189 console.error('unable to batch update holds: '
193 'success.hold.batch_update');
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',
205 ['$scope', '$uibModalInstance',
206 function($scope, $uibModalInstance) {
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();
215 $scope.good = function() { update(true) }
216 $scope.any = function() { update(false) }
217 $scope.cancel = function() { $uibModalInstance.dismiss() }
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',
229 ['$scope', '$uibModalInstance',
230 function($scope, $uibModalInstance) {
231 $scope.cant_be_pickup = function (id) { return !egCore.org.CanHaveUsers(id); };
233 $scope.ok = function() {
234 var vals = hold_ids.map(function(hold_id) {
237 pickup_lib : $scope.args.org_unit.id()
240 service.update_holds(vals).finally(function() {
241 $uibModalInstance.close();
244 $scope.cancel = function() { $uibModalInstance.dismiss() }
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 });
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',
262 ['$scope', '$uibModalInstance', 'sms_carriers',
263 function($scope, $uibModalInstance, sms_carriers) {
265 $scope.sms_carriers = sms_carriers;
266 $scope.num_holds = hold_ids.length;
267 $scope.ok = function() {
269 var vals = hold_ids.map(function(hold_id) {
270 var val = {id : hold_id};
272 ['email', 'phone', 'sms'],
274 var key = type + '_notify';
275 if ($scope.args['update_' + key])
276 val[key] = $scope.args[key];
279 if ($scope.args.update_sms_carrier)
280 val.sms_carrier = $scope.args.sms_carrier.id();
284 service.update_holds(vals).finally(function() {
285 $uibModalInstance.close();
288 $scope.cancel = function() { $uibModalInstance.dismiss() }
292 sms_carriers : service.get_sms_carriers
297 service.edit_dates = function(hold_ids) {
298 if (!hold_ids.length) return $q.when();
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};
305 ['thaw_date', 'request_time', 'expire_time', 'shelf_expire_time'],
307 if (modal_scope.args['modify_' + field])
308 val[field] = modal_scope.args[field].toISOString();
315 console.log(JSON.stringify(vals,null,2));
316 return service.update_holds(vals);
319 return $uibModal.open({
320 templateUrl : './circ/share/t_hold_dates',
323 ['$scope', '$uibModalInstance',
324 function($scope, $uibModalInstance) {
325 var today = new Date();
328 request_time : today,
330 shelf_expire_time : today
332 $scope.num_holds = hold_ids.length;
333 $scope.ok = function() {
334 relay_to_update($scope).then($uibModalInstance.close);
336 $scope.cancel = function() { $uibModalInstance.dismiss() }
342 service.update_field_with_confirm = function(hold_ids, msg_key, field, value) {
343 if (!hold_ids.length) return $q.when();
345 return egConfirmDialog.open(
346 egCore.strings[msg_key], '', {num_holds : hold_ids.length})
347 .result.then(function() {
349 var vals = hold_ids.map(function(hold_id) {
350 val = {id : hold_id};
354 return service.update_holds(vals);
358 service.suspend_holds = function(hold_ids) {
359 return service.update_field_with_confirm(
360 hold_ids, 'SUSPEND_HOLDS', 'frozen', true);
363 service.activate_holds = function(hold_ids) {
364 return service.update_field_with_confirm(
365 hold_ids, 'ACTIVATE_HOLDS', 'frozen', false);
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);
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);
378 service.transfer_to_marked_title = function(hold_ids) {
379 if (!hold_ids.length) return $q.when();
381 var bib_id = egCore.hatch.getLocalItem(
382 'eg.circ.hold.title_transfer_target');
386 return egAlertDialog.open(
387 egCore.strings.NO_HOLD_TRANSFER_TITLE_MARKED).result;
390 return egConfirmDialog.open(
391 egCore.strings.TRANSFER_HOLD_TO_TITLE, '', {
392 num_holds : hold_ids.length,
395 ).result.then(function() {
396 return egCore.net.request(
398 'open-ils.circ.hold.change_title.specific_holds',
399 egCore.auth.token(), bib_id, hold_ids);
403 service.transfer_all_bib_holds_to_marked_title = function(bib_ids) {
404 if (!bib_ids.length) return $q.when();
406 var target_bib_id = egCore.hatch.getLocalItem(
407 'eg.circ.hold.title_transfer_target');
409 if (!target_bib_id) {
411 return egAlertDialog.open(
412 egCore.strings.NO_HOLD_TRANSFER_TITLE_MARKED).result;
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
420 ).result.then(function() {
421 return egCore.net.request(
423 'open-ils.circ.hold.change_title',
424 egCore.auth.token(), target_bib_id, bib_ids);
428 // serially retargets each hold
429 service.retarget = function(hold_ids) {
430 if (!hold_ids.length) return $q.when();
431 var deferred = $q.defer();
433 egConfirmDialog.open(
434 egCore.strings.RETARGET_HOLDS, '',
435 {hold_ids : hold_ids.join(',')}
437 ).result.then(function() {
440 var hold_id = hold_ids.pop();
448 'open-ils.circ.hold.reset',
449 egCore.auth.token(), hold_id).finally(do_one);
452 do_one(); // kick it off
455 return deferred.promise;
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) {
462 hold_data.status_string =
463 egCore.strings['HOLD_STATUS_' + hold_data.status]
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();
472 // TODO: LP#1697954 fleshing calls below are deprecated in favor
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) });
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) });
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) });
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);
495 if (hold.current_copy()) {
496 // likewise, current_copy's status isn't fleshed in the API
497 if(hold.current_copy().status() !== null &&
498 typeof hold.current_copy().status() != 'object')
499 egCore.pcrud.retrieve('ccs',hold.current_copy().status()
500 ).then(function(c) { hold.current_copy().status(c) });
502 // current_copy's shelving location position isn't always accessible
503 if (hold.current_copy().location()) {
504 //console.debug('fetching hold copy location order');
506 if (typeof hold.current_copy().location() != 'object') {
507 location_id = hold.current_copy().location();
509 location_id = hold.current_copy().location().id();
513 {location: location_id, org: egCore.auth.user().ws_ou()},
516 ).then(function(orders) {
518 hold_data.hold._copy_location_position = orders[0].position();
520 hold_data.hold._copy_location_position = 999;
525 //Call number affixes are not always fleshed in the API
526 if (hold_data.volume.prefix) {
527 //console.debug('fetching call number prefix');
528 //console.log(hold_data.volume.prefix());
529 egCore.pcrud.retrieve('acnp',hold_data.volume.prefix())
530 .then(function(p) {hold_data.volume.prefix = p.label(); hold_data.volume.prefix_sortkey = p.label_sortkey()});
532 if (hold_data.volume.suffix) {
533 //console.debug('fetching call number suffix');
534 //console.log(hold_data.volume.suffix());
535 egCore.pcrud.retrieve('acns',hold_data.volume.suffix())
536 .then(function(s) {hold_data.volume.suffix = s.label(); hold_data.volume.suffix_sortkey = s.label_sortkey()});
545 * Action handlers for the common Hold grid UI.
546 * These generally scrub the data for valid input then pass the
547 * holds / copies / etc. off to the relevant action in egHolds or egCirc.
549 * Caller must apply a reset_page function, which is called after
550 * most actionis are performed.
552 .factory('egHoldGridActions',
553 ['$window','$location','$timeout','egCore','egHolds','egCirc',
554 function($window , $location , $timeout , egCore , egHolds , egCirc) {
558 service.refresh = function() {
559 console.error('egHoldGridActions.refresh not defined!');
562 service.cancel_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()});
567 return egHolds.cancel_holds(hold_ids).then(service.refresh);
570 service.cancel_wide_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});
575 return egHolds.cancel_holds(hold_ids).then(service.refresh);
578 service.uncancel_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()});
583 return egHolds.uncancel_holds(hold_ids).then(service.refresh);
586 service.uncancel_wide_hold = function(items) {
587 var hold_ids = items.filter(function(item) {
588 return item.hold.cancel_time;
589 }).map(function(item) {return item.hold.id});
591 return egHolds.uncancel_holds(hold_ids).then(service.refresh);
594 // jump to circ list for either 1) the targeted copy or
595 // 2) the hold target copy for copy-level holds
596 service.show_recent_circs = function(items) {
597 var focus = items.length == 1;
598 angular.forEach(items, function(item) {
600 var url = egCore.env.basePath +
604 $timeout(function() { var x = $window.open(url, '_blank'); if (focus) x.focus() });
609 // jump to circ list for either 1) the targeted copy or
610 // 2) the hold target copy for copy-level holds
611 service.show_recent_circs_wide = function(items) {
612 var focus = items.length == 1;
613 angular.forEach(items, function(item) {
614 if (item.hold.cp_id) {
615 var url = egCore.env.basePath +
619 $timeout(function() { var x = $window.open(url, '_blank'); if (focus) x.focus() });
624 service.show_patrons = function(items) {
625 var focus = items.length == 1;
626 angular.forEach(items, function(item) {
627 var url = egCore.env.basePath +
629 item.hold.usr().id() +
631 $timeout(function() { var x = $window.open(url, '_blank'); if (focus) x.focus() });
635 service.show_patrons_wide = function(items) {
636 var focus = items.length == 1;
637 angular.forEach(items, function(item) {
638 var url = egCore.env.basePath +
642 $timeout(function() { var x = $window.open(url, '_blank'); if (focus) x.focus() });
646 service.show_holds_for_title = function(items) {
647 var focus = items.length == 1;
648 angular.forEach(items, function(item) {
649 var url = egCore.env.basePath +
650 'cat/catalog/record/' +
653 $timeout(function() { var x = $window.open(url, '_blank'); if (focus) x.focus() });
657 service.show_holds_for_title_wide = function(items) {
658 var focus = items.length == 1;
659 angular.forEach(items, function(item) {
660 var url = egCore.env.basePath +
661 'cat/catalog/record/' +
662 item.hold.record_id +
664 $timeout(function() { var x = $window.open(url, '_blank'); if (focus) x.focus() });
669 function generic_update(items, action) {
670 if (!items.length) return $q.when();
671 var hold_ids = items.map(function(item) {return item.hold.id()});
672 return egHolds[action](hold_ids).then(service.refresh);
675 function generic_update_wide(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 service.set_copy_quality = function(items) {
682 generic_update(items, 'set_copy_quality'); }
683 service.edit_pickup_lib = function(items) {
684 generic_update(items, 'edit_pickup_lib'); }
685 service.edit_notify_prefs = function(items) {
686 generic_update(items, 'edit_notify_prefs'); }
687 service.edit_dates = function(items) {
688 generic_update(items, 'edit_dates'); }
689 service.suspend = function(items) {
690 generic_update(items, 'suspend_holds'); }
691 service.activate = function(items) {
692 generic_update(items, 'activate_holds'); }
693 service.set_top_of_queue = function(items) {
694 generic_update(items, 'set_top_of_queue'); }
695 service.clear_top_of_queue = function(items) {
696 generic_update(items, 'clear_top_of_queue'); }
697 service.transfer_to_marked_title = function(items) {
698 generic_update(items, 'transfer_to_marked_title'); }
700 service.set_copy_quality_wide = function(items) {
701 generic_update_wide(items, 'set_copy_quality'); }
702 service.edit_pickup_lib_wide = function(items) {
703 generic_update_wide(items, 'edit_pickup_lib'); }
704 service.edit_notify_prefs_wide = function(items) {
705 generic_update_wide(items, 'edit_notify_prefs'); }
706 service.edit_dates_wide = function(items) {
707 generic_update_wide(items, 'edit_dates'); }
708 service.suspend_wide = function(items) {
709 generic_update_wide(items, 'suspend_holds'); }
710 service.activate_wide = function(items) {
711 generic_update_wide(items, 'activate_holds'); }
712 service.set_top_of_queue_wide = function(items) {
713 generic_update_wide(items, 'set_top_of_queue'); }
714 service.clear_top_of_queue_wide = function(items) {
715 generic_update_wide(items, 'clear_top_of_queue'); }
716 service.transfer_to_marked_title_wide = function(items) {
717 generic_update_wide(items, 'transfer_to_marked_title'); }
719 service.mark_damaged = function(items) {
720 angular.forEach(items, function(item) {
722 egCirc.mark_damaged({
724 barcode: item.copy.barcode()
725 }).then(service.refresh);
730 service.mark_damaged_wide = function(items) {
731 angular.forEach(items, function(item) {
733 egCirc.mark_damaged({
735 barcode: item.hold.cp_barcode
736 }).then(service.refresh);
741 service.mark_missing = function(items) {
743 .filter(function(item) { return Boolean(item.copy) })
744 .map(function(item) { return item.copy.id() });
746 egCirc.mark_missing(copy_ids).then(service.refresh);
749 service.mark_missing_wide = function(items) {
751 .filter(function(item) { return Boolean(item.hold.cp_id) })
752 .map(function(item) { return item.hold.cp_id });
754 egCirc.mark_missing(copy_ids).then(service.refresh);
757 service.retarget = function(items) {
758 var hold_ids = items.map(function(item) { return item.hold.id() });
759 egHolds.retarget(hold_ids).then(service.refresh);
762 service.retarget_wide = function(items) {
763 var hold_ids = items.map(function(item) { return item.hold.id });
764 egHolds.retarget(hold_ids).then(service.refresh);
771 * Hold details interface
773 .directive('egHoldDetails', function() {
776 templateUrl : './circ/share/t_hold_details',
779 // if set, called whenever hold details are retrieved. The
780 // argument is the hold blob returned from hold.details.retrieve
785 '$scope','$uibModal','egCore','egHolds','egCirc',
786 function($scope , $uibModal , egCore , egHolds , egCirc) {
789 if (!$scope.holdId) return;
793 'open-ils.circ.hold.details.retrieve.authoritative',
794 egCore.auth.token(), $scope.holdId, {
795 include_current_copy : true,
797 include_cancel_cause : true,
798 include_requestor : true
800 ).then(function(hold_data) {
801 egHolds.local_flesh(hold_data);
803 angular.forEach(hold_data,
804 function(val, key) { $scope[key] = val });
806 // fetch + flesh the cancel_cause if needed
807 if ($scope.hold.cancel_time()) {
808 egHolds.get_cancel_reasons().then(function() {
809 // egHolds caches the causes in egEnv
810 $scope.hold.cancel_cause(
811 egCore.env.ahrcc.map[$scope.hold.cancel_cause()]);
815 if ($scope.hold.current_copy()) {
816 egCirc.flesh_copy_location($scope.hold.current_copy());
819 if ($scope.holdRetrieved)
820 $scope.holdRetrieved(hold_data);
825 $scope.show_notify_tab = function() {
826 $scope.detail_tab = 'notify';
827 egCore.pcrud.search('ahn',
828 {hold : $scope.hold.id()},
829 {flesh : 1, flesh_fields : {ahn : ['notify_staff']}},
831 ).then(function(nots) {
832 $scope.hold.notifications(nots);
836 $scope.delete_note = function(note) {
837 egCore.pcrud.remove(note).then(function() {
838 // remove the deleted note from the locally fleshed notes
840 $scope.hold.notes().filter(function(n) {
841 return n.id() != note.id()
847 $scope.new_note = function() {
848 return $uibModal.open({
849 templateUrl : './circ/share/t_hold_note_dialog',
852 ['$scope', '$uibModalInstance',
853 function($scope, $uibModalInstance) {
855 $scope.ok = function() {
856 $uibModalInstance.close($scope.args)
858 $scope.cancel = function($event) {
859 $uibModalInstance.dismiss();
860 $event.preventDefault();
864 }).result.then(function(args) {
865 var note = new egCore.idl.ahrn();
866 note.hold($scope.hold.id());
868 note.slip(args.slip);
870 note.title(args.title);
871 note.body(args.body);
872 return egCore.pcrud.create(note).then(function(n) {
873 $scope.hold.notes().push(n);
878 $scope.new_notification = function() {
879 return $uibModal.open({
880 templateUrl : './circ/share/t_hold_notification_dialog',
883 ['$scope', '$uibModalInstance',
884 function($scope, $uibModalInstance) {
886 $scope.ok = function() {
887 $uibModalInstance.close($scope.args)
889 $scope.cancel = function($event) {
890 $uibModalInstance.dismiss();
891 $event.preventDefault();
895 }).result.then(function(args) {
896 var note = new egCore.idl.ahn();
897 note.hold($scope.hold.id());
898 note.method(args.method);
899 note.note(args.note);
900 note.notify_staff(egCore.auth.user().id());
901 note.notify_time('now');
902 return egCore.pcrud.create(note).then(function(n) {
903 n.notify_staff(egCore.auth.user());
904 $scope.hold.notifications().push(n);
909 $scope.$watch('holdId', function(newVal, oldVal) {
910 if (newVal != oldVal) draw();