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_requestor : true
44 one_batch, // kick off the next batch
47 var hold = hold_data.hold;
48 hold_data.id = hold.id();
49 service.local_flesh(hold_data);
50 deferred.notify(hold_data);
55 one_batch(); // kick it off
56 return deferred.promise;
60 service.cancel_holds = function(hold_ids) {
62 return $uibModal.open({
63 templateUrl : './circ/share/t_cancel_hold_dialog',
65 ['$scope', '$uibModalInstance', 'cancel_reasons',
66 function($scope, $uibModalInstance, cancel_reasons) {
69 cancel_reasons : cancel_reasons,
70 num_holds : hold_ids.length
73 $scope.cancel = function($event) {
74 $uibModalInstance.dismiss();
75 $event.preventDefault();
78 $scope.ok = function() {
80 function cancel_one() {
81 var hold_id = hold_ids.pop();
83 $uibModalInstance.close();
87 'open-ils.circ', 'open-ils.circ.hold.cancel',
88 egCore.auth.token(), hold_id,
89 $scope.args.cancel_reason,
91 ).then(function(resp) {
92 if (evt = egCore.evt.parse(resp)) {
94 'warning.hold.cancel_failed');
95 console.error('unable to cancel hold: '
107 cancel_reasons : function() {
108 return service.get_cancel_reasons();
114 service.uncancel_holds = function(hold_ids) {
116 return $uibModal.open({
117 templateUrl : './circ/share/t_uncancel_hold_dialog',
119 ['$scope', '$uibModalInstance',
120 function($scope, $uibModalInstance) {
122 num_holds : hold_ids.length
125 $scope.cancel = function($event) {
126 $uibModalInstance.dismiss();
127 $event.preventDefault();
130 $scope.ok = function() {
132 function uncancel_one() {
133 var hold_id = hold_ids.pop();
135 $uibModalInstance.close();
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)) {
144 'warning.hold.uncancel_failed');
145 console.error('unable to uncancel hold: '
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 });
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(
172 'open-ils.circ.hold.update.batch',
173 egCore.auth.token(), null, new_values).then(
175 if (evt = egCore.evt.parse(resp)) {
177 'warning.hold.batch_update');
178 console.error('unable to batch update holds: '
182 'success.hold.batch_update');
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',
193 ['$scope', '$uibModalInstance',
194 function($scope, $uibModalInstance) {
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();
203 $scope.good = function() { update(true) }
204 $scope.any = function() { update(false) }
205 $scope.cancel = function() { $uibModalInstance.dismiss() }
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',
216 ['$scope', '$uibModalInstance',
217 function($scope, $uibModalInstance) {
218 $scope.cant_be_pickup = function (id) { return !egCore.org.CanHaveUsers(id); };
220 $scope.ok = function() {
221 var vals = hold_ids.map(function(hold_id) {
224 pickup_lib : $scope.args.org_unit.id()
227 service.update_holds(vals).finally(function() {
228 $uibModalInstance.close();
231 $scope.cancel = function() { $uibModalInstance.dismiss() }
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 });
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',
248 ['$scope', '$uibModalInstance', 'sms_carriers',
249 function($scope, $uibModalInstance, sms_carriers) {
251 $scope.sms_carriers = sms_carriers;
252 $scope.num_holds = hold_ids.length;
253 $scope.ok = function() {
255 var vals = hold_ids.map(function(hold_id) {
256 var val = {id : hold_id};
258 ['email', 'phone', 'sms'],
260 var key = type + '_notify';
261 if ($scope.args['update_' + key])
262 val[key] = $scope.args[key];
265 if ($scope.args.update_sms_carrier)
266 val.sms_carrier = $scope.args.sms_carrier.id();
270 service.update_holds(vals).finally(function() {
271 $uibModalInstance.close();
274 $scope.cancel = function() { $uibModalInstance.dismiss() }
278 sms_carriers : service.get_sms_carriers
283 service.edit_dates = function(hold_ids) {
284 if (!hold_ids.length) return $q.when();
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};
291 ['thaw_date', 'request_time', 'expire_time', 'shelf_expire_time'],
293 if (modal_scope.args['modify_' + field])
294 val[field] = modal_scope.args[field].toISOString();
301 console.log(JSON.stringify(vals,null,2));
302 return service.update_holds(vals);
305 return $uibModal.open({
306 templateUrl : './circ/share/t_hold_dates',
308 ['$scope', '$uibModalInstance',
309 function($scope, $uibModalInstance) {
310 var today = new Date();
313 request_time : today,
315 shelf_expire_time : today
317 $scope.num_holds = hold_ids.length;
318 $scope.ok = function() {
319 relay_to_update($scope).then($uibModalInstance.close);
321 $scope.cancel = function() { $uibModalInstance.dismiss() }
327 service.update_field_with_confirm = function(hold_ids, msg_key, field, value) {
328 if (!hold_ids.length) return $q.when();
330 return egConfirmDialog.open(
331 egCore.strings[msg_key], '', {num_holds : hold_ids.length})
332 .result.then(function() {
334 var vals = hold_ids.map(function(hold_id) {
335 val = {id : hold_id};
339 return service.update_holds(vals);
343 service.suspend_holds = function(hold_ids) {
344 return service.update_field_with_confirm(
345 hold_ids, 'SUSPEND_HOLDS', 'frozen', true);
348 service.activate_holds = function(hold_ids) {
349 return service.update_field_with_confirm(
350 hold_ids, 'ACTIVATE_HOLDS', 'frozen', false);
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);
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);
363 service.transfer_to_marked_title = function(hold_ids) {
364 if (!hold_ids.length) return $q.when();
366 var bib_id = egCore.hatch.getLocalItem(
367 'eg.circ.hold.title_transfer_target');
371 return egAlertDialog.open(
372 egCore.strings.NO_HOLD_TRANSFER_TITLE_MARKED).result;
375 return egConfirmDialog.open(
376 egCore.strings.TRANSFER_HOLD_TO_TITLE, '', {
377 num_holds : hold_ids.length,
380 ).result.then(function() {
381 return egCore.net.request(
383 'open-ils.circ.hold.change_title.specific_holds',
384 egCore.auth.token(), bib_id, hold_ids);
388 service.transfer_all_bib_holds_to_marked_title = function(bib_ids) {
389 if (!bib_ids.length) return $q.when();
391 var target_bib_id = egCore.hatch.getLocalItem(
392 'eg.circ.hold.title_transfer_target');
394 if (!target_bib_id) {
396 return egAlertDialog.open(
397 egCore.strings.NO_HOLD_TRANSFER_TITLE_MARKED).result;
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
405 ).result.then(function() {
406 return egCore.net.request(
408 'open-ils.circ.hold.change_title',
409 egCore.auth.token(), target_bib_id, bib_ids);
413 // serially retargets each hold
414 service.retarget = function(hold_ids) {
415 if (!hold_ids.length) return $q.when();
416 var deferred = $q.defer();
418 egConfirmDialog.open(
419 egCore.strings.RETARGET_HOLDS, '',
420 {hold_ids : hold_ids.join(',')}
422 ).result.then(function() {
425 var hold_id = hold_ids.pop();
433 'open-ils.circ.hold.reset',
434 egCore.auth.token(), hold_id).finally(do_one);
437 do_one(); // kick it off
440 return deferred.promise;
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) {
447 hold_data.status_string =
448 egCore.strings['HOLD_STATUS_' + hold_data.status]
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();
456 // TODO: LP#1697954 fleshing calls below are deprecated in favor
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) });
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) });
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) });
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);
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) });
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.
494 * Caller must apply a reset_page function, which is called after
495 * most actionis are performed.
497 .factory('egHoldGridActions',
498 ['$window','$location','$timeout','egCore','egHolds','egCirc',
499 function($window , $location , $timeout , egCore , egHolds , egCirc) {
503 service.refresh = function() {
504 console.error('egHoldGridActions.refresh not defined!');
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()});
512 return egHolds.cancel_holds(hold_ids).then(service.refresh);
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()});
520 return egHolds.uncancel_holds(hold_ids).then(service.refresh);
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) {
529 var url = egCore.env.basePath +
533 $timeout(function() { var x = $window.open(url, '_blank'); if (focus) x.focus() });
538 service.show_patrons = function(items) {
539 var focus = items.length == 1;
540 angular.forEach(items, function(item) {
541 var url = egCore.env.basePath +
543 item.hold.usr().id() +
545 $timeout(function() { var x = $window.open(url, '_blank'); if (focus) x.focus() });
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/' +
556 $timeout(function() { var x = $window.open(url, '_blank'); if (focus) x.focus() });
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);
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'); }
586 service.mark_damaged = function(items) {
588 .filter(function(item) { return Boolean(item.copy) })
589 .map(function(item) { return item.copy.id() });
591 egCirc.mark_damaged(copy_ids).then(service.refresh);
594 service.mark_missing = function(items) {
596 .filter(function(item) { return Boolean(item.copy) })
597 .map(function(item) { return item.copy.id() });
599 egCirc.mark_missing(copy_ids).then(service.refresh);
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);
611 * Hold details interface
613 .directive('egHoldDetails', function() {
616 templateUrl : './circ/share/t_hold_details',
619 // if set, called whenever hold details are retrieved. The
620 // argument is the hold blob returned from hold.details.retrieve
625 '$scope','$uibModal','egCore','egHolds','egCirc',
626 function($scope , $uibModal , egCore , egHolds , egCirc) {
629 if (!$scope.holdId) return;
633 'open-ils.circ.hold.details.retrieve.authoritative',
634 egCore.auth.token(), $scope.holdId, {
635 include_current_copy : true,
637 include_cancel_cause : true,
638 include_requestor : true
640 ).then(function(hold_data) {
641 egHolds.local_flesh(hold_data);
643 angular.forEach(hold_data,
644 function(val, key) { $scope[key] = val });
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()]);
655 if ($scope.hold.current_copy()) {
656 egCirc.flesh_copy_location($scope.hold.current_copy());
659 if ($scope.holdRetrieved)
660 $scope.holdRetrieved(hold_data);
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']}},
671 ).then(function(nots) {
672 $scope.hold.notifications(nots);
676 $scope.delete_note = function(note) {
677 egCore.pcrud.remove(note).then(function() {
678 // remove the deleted note from the locally fleshed notes
680 $scope.hold.notes().filter(function(n) {
681 return n.id() != note.id()
687 $scope.new_note = function() {
688 return $uibModal.open({
689 templateUrl : './circ/share/t_hold_note_dialog',
691 ['$scope', '$uibModalInstance',
692 function($scope, $uibModalInstance) {
694 $scope.ok = function() {
695 $uibModalInstance.close($scope.args)
697 $scope.cancel = function($event) {
698 $uibModalInstance.dismiss();
699 $event.preventDefault();
703 }).result.then(function(args) {
704 var note = new egCore.idl.ahrn();
705 note.hold($scope.hold.id());
707 note.slip(args.slip);
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);
717 $scope.new_notification = function() {
718 return $uibModal.open({
719 templateUrl : './circ/share/t_hold_notification_dialog',
721 ['$scope', '$uibModalInstance',
722 function($scope, $uibModalInstance) {
724 $scope.ok = function() {
725 $uibModalInstance.close($scope.args)
727 $scope.cancel = function($event) {
728 $uibModalInstance.dismiss();
729 $event.preventDefault();
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);
747 $scope.$watch('holdId', function(newVal, oldVal) {
748 if (newVal != oldVal) draw();