5 angular.module('egCoreMod')
9 ['$uibModal','$q','egCore','egConfirmDialog','egAlertDialog',
10 function($uibModal , $q , egCore , egConfirmDialog , egAlertDialog) {
14 service.fetch_holds = function(hold_ids) {
15 var deferred = $q.defer();
17 // Fetch hold details in batches for better UI responsiveness.
21 function one_batch() {
22 var ids = hold_ids.slice(index, index + batch_size)
23 .filter(function(id) {return Boolean(id)}) // avoid nulls
25 console.debug('egHolds.fetch_holds => ' + ids);
35 'open-ils.circ.hold.details.batch.retrieve.authoritative',
36 egCore.auth.token(), ids, {
37 include_current_copy : true,
39 include_cancel_cause : true,
40 include_sms_carrier : true,
41 include_requestor : true
45 one_batch, // kick off the next batch
48 var hold = hold_data.hold;
49 hold_data.id = hold.id();
50 service.local_flesh(hold_data);
51 deferred.notify(hold_data);
56 one_batch(); // kick it off
57 return deferred.promise;
61 service.cancel_holds = function(hold_ids) {
63 return $uibModal.open({
64 templateUrl : './circ/share/t_cancel_hold_dialog',
67 ['$scope', '$uibModalInstance', 'cancel_reasons',
68 function($scope, $uibModalInstance, cancel_reasons) {
71 cancel_reasons : cancel_reasons,
72 num_holds : hold_ids.length
75 $scope.cancel = function($event) {
76 $uibModalInstance.dismiss();
77 $event.preventDefault();
80 $scope.ok = function() {
82 function cancel_one() {
83 var hold_id = hold_ids.pop();
85 $uibModalInstance.close();
89 'open-ils.circ', 'open-ils.circ.hold.cancel',
90 egCore.auth.token(), hold_id,
91 $scope.args.cancel_reason,
93 ).then(function(resp) {
94 if (evt = egCore.evt.parse(resp)) {
96 'warning.hold.cancel_failed');
97 console.error('unable to cancel hold: '
109 cancel_reasons : function() {
110 return service.get_cancel_reasons();
116 service.uncancel_holds = function(hold_ids) {
118 return $uibModal.open({
119 templateUrl : './circ/share/t_uncancel_hold_dialog',
122 ['$scope', '$uibModalInstance',
123 function($scope, $uibModalInstance) {
125 num_holds : hold_ids.length
128 $scope.cancel = function($event) {
129 $uibModalInstance.dismiss();
130 $event.preventDefault();
133 $scope.ok = function() {
135 function uncancel_one() {
136 var hold_id = hold_ids.pop();
138 $uibModalInstance.close();
142 'open-ils.circ', 'open-ils.circ.hold.uncancel',
143 egCore.auth.token(), hold_id
144 ).then(function(resp) {
145 if (evt = egCore.evt.parse(resp)) {
147 'warning.hold.uncancel_failed');
148 console.error('unable to uncancel hold: '
162 service.get_cancel_reasons = function() {
163 if (egCore.env.ahrcc) return $q.when(egCore.env.ahrcc.list);
164 return egCore.pcrud.retrieveAll('ahrcc', {}, {atomic : true})
165 .then(function(list) { return egCore.env.absorbList(list, 'ahrcc').list });
168 // Updates a batch of holds, notifies on each response.
169 // new_values = array of hashes describing values to change,
170 // including the id of the hold to change.
171 // e.g. {id : 1, mint_condition : true}
172 service.update_holds = function(new_values) {
173 return egCore.net.request(
175 'open-ils.circ.hold.update.batch',
176 egCore.auth.token(), null, new_values).then(
178 if (evt = egCore.evt.parse(resp)) {
180 'warning.hold.batch_update');
181 console.error('unable to batch update holds: '
185 'success.hold.batch_update');
191 service.set_copy_quality = function(hold_ids) {
192 if (!hold_ids.length) return $q.when();
193 return $uibModal.open({
194 templateUrl : './circ/share/t_hold_copy_quality_dialog',
197 ['$scope', '$uibModalInstance',
198 function($scope, $uibModalInstance) {
200 function update(val) {
201 var vals = hold_ids.map(function(hold_id) {
202 return {id : hold_id, mint_condition : val}})
203 service.update_holds(vals).finally(function() {
204 $uibModalInstance.close();
207 $scope.good = function() { update(true) }
208 $scope.any = function() { update(false) }
209 $scope.cancel = function() { $uibModalInstance.dismiss() }
215 service.edit_pickup_lib = function(hold_ids) {
216 if (!hold_ids.length) return $q.when();
217 return $uibModal.open({
218 templateUrl : './circ/share/t_hold_edit_pickup_lib',
221 ['$scope', '$uibModalInstance',
222 function($scope, $uibModalInstance) {
223 $scope.cant_be_pickup = function (id) { return !egCore.org.CanHaveUsers(id); };
225 $scope.ok = function() {
226 var vals = hold_ids.map(function(hold_id) {
229 pickup_lib : $scope.args.org_unit.id()
232 service.update_holds(vals).finally(function() {
233 $uibModalInstance.close();
236 $scope.cancel = function() { $uibModalInstance.dismiss() }
242 service.get_sms_carriers = function() {
243 if (egCore.env.csc) return $q.when(egCore.env.csc.list);
244 return egCore.pcrud.retrieveAll('csc', {}, {atomic : true})
245 .then(function(list) { return egCore.env.absorbList(list, 'csc').list });
248 service.edit_notify_prefs = function(hold_ids) {
249 if (!hold_ids.length) return $q.when();
250 return $uibModal.open({
251 templateUrl : './circ/share/t_hold_notification_prefs',
254 ['$scope', '$uibModalInstance', 'sms_carriers',
255 function($scope, $uibModalInstance, sms_carriers) {
257 $scope.sms_carriers = sms_carriers;
258 $scope.num_holds = hold_ids.length;
259 $scope.ok = function() {
261 var vals = hold_ids.map(function(hold_id) {
262 var val = {id : hold_id};
264 ['email', 'phone', 'sms'],
266 var key = type + '_notify';
267 if ($scope.args['update_' + key])
268 val[key] = $scope.args[key];
271 if ($scope.args.update_sms_carrier)
272 val.sms_carrier = $scope.args.sms_carrier.id();
276 service.update_holds(vals).finally(function() {
277 $uibModalInstance.close();
280 $scope.cancel = function() { $uibModalInstance.dismiss() }
284 sms_carriers : service.get_sms_carriers
289 service.edit_dates = function(hold_ids) {
290 if (!hold_ids.length) return $q.when();
292 // collects the fields from the dialog the user wishes to modify
293 function relay_to_update(modal_scope) {
294 var vals = hold_ids.map(function(hold_id) {
295 var val = {id : hold_id};
297 ['thaw_date', 'request_time', 'expire_time', 'shelf_expire_time'],
299 if (modal_scope.args['modify_' + field])
300 val[field] = modal_scope.args[field].toISOString();
307 console.log(JSON.stringify(vals,null,2));
308 return service.update_holds(vals);
311 return $uibModal.open({
312 templateUrl : './circ/share/t_hold_dates',
315 ['$scope', '$uibModalInstance',
316 function($scope, $uibModalInstance) {
317 var today = new Date();
320 request_time : today,
322 shelf_expire_time : today
324 $scope.num_holds = hold_ids.length;
325 $scope.ok = function() {
326 relay_to_update($scope).then($uibModalInstance.close);
328 $scope.cancel = function() { $uibModalInstance.dismiss() }
334 service.update_field_with_confirm = function(hold_ids, msg_key, field, value) {
335 if (!hold_ids.length) return $q.when();
337 return egConfirmDialog.open(
338 egCore.strings[msg_key], '', {num_holds : hold_ids.length})
339 .result.then(function() {
341 var vals = hold_ids.map(function(hold_id) {
342 val = {id : hold_id};
346 return service.update_holds(vals);
350 service.suspend_holds = function(hold_ids) {
351 return service.update_field_with_confirm(
352 hold_ids, 'SUSPEND_HOLDS', 'frozen', true);
355 service.activate_holds = function(hold_ids) {
356 return service.update_field_with_confirm(
357 hold_ids, 'ACTIVATE_HOLDS', 'frozen', false);
360 service.set_top_of_queue = function(hold_ids) {
361 return service.update_field_with_confirm(
362 hold_ids, 'SET_TOP_OF_QUEUE', 'cut_in_line', true);
365 service.clear_top_of_queue = function(hold_ids) {
366 return service.update_field_with_confirm(
367 hold_ids, 'CLEAR_TOP_OF_QUEUE', 'cut_in_line', null);
370 service.transfer_to_marked_title = function(hold_ids) {
371 if (!hold_ids.length) return $q.when();
373 var bib_id = egCore.hatch.getLocalItem(
374 'eg.circ.hold.title_transfer_target');
378 return egAlertDialog.open(
379 egCore.strings.NO_HOLD_TRANSFER_TITLE_MARKED).result;
382 return egConfirmDialog.open(
383 egCore.strings.TRANSFER_HOLD_TO_TITLE, '', {
384 num_holds : hold_ids.length,
387 ).result.then(function() {
388 return egCore.net.request(
390 'open-ils.circ.hold.change_title.specific_holds',
391 egCore.auth.token(), bib_id, hold_ids);
395 service.transfer_all_bib_holds_to_marked_title = function(bib_ids) {
396 if (!bib_ids.length) return $q.when();
398 var target_bib_id = egCore.hatch.getLocalItem(
399 'eg.circ.hold.title_transfer_target');
401 if (!target_bib_id) {
403 return egAlertDialog.open(
404 egCore.strings.NO_HOLD_TRANSFER_TITLE_MARKED).result;
407 return egConfirmDialog.open(
408 egCore.strings.TRANSFER_ALL_BIB_HOLDS_TO_TITLE, '', {
409 num_bibs : bib_ids.length,
410 bib_id : target_bib_id
412 ).result.then(function() {
413 return egCore.net.request(
415 'open-ils.circ.hold.change_title',
416 egCore.auth.token(), target_bib_id, bib_ids);
420 // serially retargets each hold
421 service.retarget = function(hold_ids) {
422 if (!hold_ids.length) return $q.when();
423 var deferred = $q.defer();
425 egConfirmDialog.open(
426 egCore.strings.RETARGET_HOLDS, '',
427 {hold_ids : hold_ids.join(',')}
429 ).result.then(function() {
432 var hold_id = hold_ids.pop();
440 'open-ils.circ.hold.reset',
441 egCore.auth.token(), hold_id).finally(do_one);
444 do_one(); // kick it off
447 return deferred.promise;
450 // fleshes orgs, etc. for hold data blobs retrieved from
451 // open-ils.circ.hold.details[.batch].retrieve
452 service.local_flesh = function(hold_data) {
454 hold_data.status_string =
455 egCore.strings['HOLD_STATUS_' + hold_data.status]
458 var hold = hold_data.hold;
459 var volume = hold_data.volume;
460 hold.pickup_lib(egCore.org.get(hold.pickup_lib()));
461 hold.current_shelf_lib(egCore.org.get(hold.current_shelf_lib()));
462 hold_data.id = hold.id();
464 // TODO: LP#1697954 fleshing calls below are deprecated in favor
467 if (hold.requestor() && typeof hold.requestor() != 'object') {
468 console.debug('fetching hold requestor');
469 egCore.pcrud.retrieve('au',hold.requestor()).then(function(u) { hold.requestor(u) });
472 if (hold.cancel_cause() && typeof hold.cancel_cause() != 'object') {
473 console.debug('fetching hold cancel cause');
474 egCore.pcrud.retrieve('ahrcc',hold.cancel_cause()).then(function(c) { hold.cancel_cause(c) });
477 if (hold.usr() && typeof hold.usr() != 'object') {
478 console.debug('fetching hold user');
479 egCore.pcrud.retrieve('au',hold.usr()).then(function(u) { hold.usr(u) });
482 if (hold.sms_carrier() && typeof hold.sms_carrier() != 'object') {
483 console.debug('fetching sms carrier');
484 egCore.pcrud.retrieve('csc',hold.sms_carrier()).then(function(c) { hold.sms_carrier(c) });
487 // current_copy is not always fleshed in the API
488 if (hold.current_copy() && typeof hold.current_copy() != 'object') {
489 hold.current_copy(hold_data.copy);
492 if (hold.current_copy()) {
493 // likewise, current_copy's status isn't fleshed in the API
494 if(hold.current_copy().status() !== null &&
495 typeof hold.current_copy().status() != 'object')
496 egCore.pcrud.retrieve('ccs',hold.current_copy().status()
497 ).then(function(c) { hold.current_copy().status(c) });
499 // current_copy's shelving location position isn't always accessible
500 if (hold.current_copy().location()) {
501 //console.debug('fetching hold copy location order');
503 if (typeof hold.current_copy().location() != 'object') {
504 location_id = hold.current_copy().location();
506 location_id = hold.current_copy().location().id();
510 {location: location_id, org: egCore.auth.user().ws_ou()},
513 ).then(function(orders) {
515 hold_data.hold._copy_location_position = orders[0].position();
517 hold_data.hold._copy_location_position = 999;
522 //Call number affixes are not always fleshed in the API
523 if (hold_data.volume.prefix) {
524 //console.debug('fetching call number prefix');
525 //console.log(hold_data.volume.prefix());
526 egCore.pcrud.retrieve('acnp',hold_data.volume.prefix())
527 .then(function(p) {hold_data.volume.prefix = p.label(); hold_data.volume.prefix_sortkey = p.label_sortkey()});
529 if (hold_data.volume.suffix) {
530 //console.debug('fetching call number suffix');
531 //console.log(hold_data.volume.suffix());
532 egCore.pcrud.retrieve('acns',hold_data.volume.suffix())
533 .then(function(s) {hold_data.volume.suffix = s.label(); hold_data.volume.suffix_sortkey = s.label_sortkey()});
542 * Action handlers for the common Hold grid UI.
543 * These generally scrub the data for valid input then pass the
544 * holds / copies / etc. off to the relevant action in egHolds or egCirc.
546 * Caller must apply a reset_page function, which is called after
547 * most actionis are performed.
549 .factory('egHoldGridActions',
550 ['$window','$location','$timeout','egCore','egHolds','egCirc',
551 function($window , $location , $timeout , egCore , egHolds , egCirc) {
555 service.refresh = function() {
556 console.error('egHoldGridActions.refresh not defined!');
559 service.cancel_hold = function(items) {
560 var hold_ids = items.filter(function(item) {
561 return !item.hold.cancel_time();
562 }).map(function(item) {return item.hold.id()});
564 return egHolds.cancel_holds(hold_ids).then(service.refresh);
567 service.uncancel_hold = function(items) {
568 var hold_ids = items.filter(function(item) {
569 return item.hold.cancel_time();
570 }).map(function(item) {return item.hold.id()});
572 return egHolds.uncancel_holds(hold_ids).then(service.refresh);
575 // jump to circ list for either 1) the targeted copy or
576 // 2) the hold target copy for copy-level holds
577 service.show_recent_circs = function(items) {
578 var focus = items.length == 1;
579 angular.forEach(items, function(item) {
581 var url = egCore.env.basePath +
585 $timeout(function() { var x = $window.open(url, '_blank'); if (focus) x.focus() });
590 service.show_patrons = function(items) {
591 var focus = items.length == 1;
592 angular.forEach(items, function(item) {
593 var url = egCore.env.basePath +
595 item.hold.usr().id() +
597 $timeout(function() { var x = $window.open(url, '_blank'); if (focus) x.focus() });
601 service.show_holds_for_title = function(items) {
602 var focus = items.length == 1;
603 angular.forEach(items, function(item) {
604 var url = egCore.env.basePath +
605 'cat/catalog/record/' +
608 $timeout(function() { var x = $window.open(url, '_blank'); if (focus) x.focus() });
613 function generic_update(items, action) {
614 if (!items.length) return $q.when();
615 var hold_ids = items.map(function(item) {return item.hold.id()});
616 return egHolds[action](hold_ids).then(service.refresh);
619 service.set_copy_quality = function(items) {
620 generic_update(items, 'set_copy_quality'); }
621 service.edit_pickup_lib = function(items) {
622 generic_update(items, 'edit_pickup_lib'); }
623 service.edit_notify_prefs = function(items) {
624 generic_update(items, 'edit_notify_prefs'); }
625 service.edit_dates = function(items) {
626 generic_update(items, 'edit_dates'); }
627 service.suspend = function(items) {
628 generic_update(items, 'suspend_holds'); }
629 service.activate = function(items) {
630 generic_update(items, 'activate_holds'); }
631 service.set_top_of_queue = function(items) {
632 generic_update(items, 'set_top_of_queue'); }
633 service.clear_top_of_queue = function(items) {
634 generic_update(items, 'clear_top_of_queue'); }
635 service.transfer_to_marked_title = function(items) {
636 generic_update(items, 'transfer_to_marked_title'); }
638 service.mark_damaged = function(items) {
639 angular.forEach(items, function(item) {
641 egCirc.mark_damaged({
643 barcode: item.copy.barcode()
644 }).then(service.refresh);
649 service.mark_missing = function(items) {
651 .filter(function(item) { return Boolean(item.copy) })
652 .map(function(item) { return item.copy.id() });
654 egCirc.mark_missing(copy_ids).then(service.refresh);
657 service.retarget = function(items) {
658 var hold_ids = items.map(function(item) { return item.hold.id() });
659 egHolds.retarget(hold_ids).then(service.refresh);
666 * Hold details interface
668 .directive('egHoldDetails', function() {
671 templateUrl : './circ/share/t_hold_details',
674 // if set, called whenever hold details are retrieved. The
675 // argument is the hold blob returned from hold.details.retrieve
680 '$scope','$uibModal','egCore','egHolds','egCirc',
681 function($scope , $uibModal , egCore , egHolds , egCirc) {
684 if (!$scope.holdId) return;
688 'open-ils.circ.hold.details.retrieve.authoritative',
689 egCore.auth.token(), $scope.holdId, {
690 include_current_copy : true,
692 include_cancel_cause : true,
693 include_sms_carrier : true,
694 include_requestor : true
696 ).then(function(hold_data) {
697 egHolds.local_flesh(hold_data);
699 angular.forEach(hold_data,
700 function(val, key) { $scope[key] = val });
702 // fetch + flesh the cancel_cause if needed
703 if ($scope.hold.cancel_time()) {
704 egHolds.get_cancel_reasons().then(function() {
705 // egHolds caches the causes in egEnv
706 $scope.hold.cancel_cause(
707 egCore.env.ahrcc.map[$scope.hold.cancel_cause()]);
711 if ($scope.hold.current_copy()) {
712 egCirc.flesh_copy_location($scope.hold.current_copy());
715 if ($scope.holdRetrieved)
716 $scope.holdRetrieved(hold_data);
721 $scope.show_notify_tab = function() {
722 $scope.detail_tab = 'notify';
723 egCore.pcrud.search('ahn',
724 {hold : $scope.hold.id()},
725 {flesh : 1, flesh_fields : {ahn : ['notify_staff']}},
727 ).then(function(nots) {
728 $scope.hold.notifications(nots);
732 $scope.delete_note = function(note) {
733 egCore.pcrud.remove(note).then(function() {
734 // remove the deleted note from the locally fleshed notes
736 $scope.hold.notes().filter(function(n) {
737 return n.id() != note.id()
743 $scope.new_note = function() {
744 return $uibModal.open({
745 templateUrl : './circ/share/t_hold_note_dialog',
748 ['$scope', '$uibModalInstance',
749 function($scope, $uibModalInstance) {
751 $scope.ok = function() {
752 $uibModalInstance.close($scope.args)
754 $scope.cancel = function($event) {
755 $uibModalInstance.dismiss();
756 $event.preventDefault();
760 }).result.then(function(args) {
761 var note = new egCore.idl.ahrn();
762 note.hold($scope.hold.id());
764 note.slip(args.slip);
766 note.title(args.title);
767 note.body(args.body);
768 return egCore.pcrud.create(note).then(function(n) {
769 $scope.hold.notes().push(n);
774 $scope.new_notification = function() {
775 return $uibModal.open({
776 templateUrl : './circ/share/t_hold_notification_dialog',
779 ['$scope', '$uibModalInstance',
780 function($scope, $uibModalInstance) {
782 $scope.ok = function() {
783 $uibModalInstance.close($scope.args)
785 $scope.cancel = function($event) {
786 $uibModalInstance.dismiss();
787 $event.preventDefault();
791 }).result.then(function(args) {
792 var note = new egCore.idl.ahn();
793 note.hold($scope.hold.id());
794 note.method(args.method);
795 note.note(args.note);
796 note.notify_staff(egCore.auth.user().id());
797 note.notify_time('now');
798 return egCore.pcrud.create(note).then(function(n) {
799 n.notify_staff(egCore.auth.user());
800 $scope.hold.notifications().push(n);
805 $scope.$watch('holdId', function(newVal, oldVal) {
806 if (newVal != oldVal) draw();