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',
66 ['$scope', '$uibModalInstance', 'cancel_reasons',
67 function($scope, $uibModalInstance, cancel_reasons) {
70 cancel_reasons : cancel_reasons,
71 num_holds : hold_ids.length
74 $scope.cancel = function($event) {
75 $uibModalInstance.dismiss();
76 $event.preventDefault();
79 $scope.ok = function() {
81 function cancel_one() {
82 var hold_id = hold_ids.pop();
84 $uibModalInstance.close();
88 'open-ils.circ', 'open-ils.circ.hold.cancel',
89 egCore.auth.token(), hold_id,
90 $scope.args.cancel_reason,
92 ).then(function(resp) {
93 if (evt = egCore.evt.parse(resp)) {
95 'warning.hold.cancel_failed');
96 console.error('unable to cancel hold: '
108 cancel_reasons : function() {
109 return service.get_cancel_reasons();
115 service.uncancel_holds = function(hold_ids) {
117 return $uibModal.open({
118 templateUrl : './circ/share/t_uncancel_hold_dialog',
121 ['$scope', '$uibModalInstance',
122 function($scope, $uibModalInstance) {
124 num_holds : hold_ids.length
127 $scope.cancel = function($event) {
128 $uibModalInstance.dismiss();
129 $event.preventDefault();
132 $scope.ok = function() {
134 function uncancel_one() {
135 var hold_id = hold_ids.pop();
137 $uibModalInstance.close();
141 'open-ils.circ', 'open-ils.circ.hold.uncancel',
142 egCore.auth.token(), hold_id
143 ).then(function(resp) {
144 if (evt = egCore.evt.parse(resp)) {
146 'warning.hold.uncancel_failed');
147 console.error('unable to uncancel hold: '
161 service.get_cancel_reasons = function() {
162 if (egCore.env.ahrcc) return $q.when(egCore.env.ahrcc.list);
163 return egCore.pcrud.retrieveAll('ahrcc', {}, {atomic : true})
164 .then(function(list) { return egCore.env.absorbList(list, 'ahrcc').list });
167 // Updates a batch of holds, notifies on each response.
168 // new_values = array of hashes describing values to change,
169 // including the id of the hold to change.
170 // e.g. {id : 1, mint_condition : true}
171 service.update_holds = function(new_values) {
172 return egCore.net.request(
174 'open-ils.circ.hold.update.batch',
175 egCore.auth.token(), null, new_values).then(
177 if (evt = egCore.evt.parse(resp)) {
179 'warning.hold.batch_update');
180 console.error('unable to batch update holds: '
184 'success.hold.batch_update');
190 service.set_copy_quality = function(hold_ids) {
191 if (!hold_ids.length) return $q.when();
192 return $uibModal.open({
193 templateUrl : './circ/share/t_hold_copy_quality_dialog',
196 ['$scope', '$uibModalInstance',
197 function($scope, $uibModalInstance) {
199 function update(val) {
200 var vals = hold_ids.map(function(hold_id) {
201 return {id : hold_id, mint_condition : val}})
202 service.update_holds(vals).finally(function() {
203 $uibModalInstance.close();
206 $scope.good = function() { update(true) }
207 $scope.any = function() { update(false) }
208 $scope.cancel = function() { $uibModalInstance.dismiss() }
214 service.edit_pickup_lib = function(hold_ids) {
215 if (!hold_ids.length) return $q.when();
216 return $uibModal.open({
217 templateUrl : './circ/share/t_hold_edit_pickup_lib',
220 ['$scope', '$uibModalInstance',
221 function($scope, $uibModalInstance) {
222 $scope.cant_be_pickup = function (id) { return !egCore.org.CanHaveUsers(id); };
224 $scope.ok = function() {
225 var vals = hold_ids.map(function(hold_id) {
228 pickup_lib : $scope.args.org_unit.id()
231 service.update_holds(vals).finally(function() {
232 $uibModalInstance.close();
235 $scope.cancel = function() { $uibModalInstance.dismiss() }
241 service.get_sms_carriers = function() {
242 if (egCore.env.csc) return $q.when(egCore.env.csc.list);
243 return egCore.pcrud.retrieveAll('csc', {}, {atomic : true})
244 .then(function(list) { return egCore.env.absorbList(list, 'csc').list });
247 service.edit_notify_prefs = function(hold_ids) {
248 if (!hold_ids.length) return $q.when();
249 return $uibModal.open({
250 templateUrl : './circ/share/t_hold_notification_prefs',
253 ['$scope', '$uibModalInstance', 'sms_carriers',
254 function($scope, $uibModalInstance, sms_carriers) {
256 $scope.sms_carriers = sms_carriers;
257 $scope.num_holds = hold_ids.length;
258 $scope.ok = function() {
260 var vals = hold_ids.map(function(hold_id) {
261 var val = {id : hold_id};
263 ['email', 'phone', 'sms'],
265 var key = type + '_notify';
266 if ($scope.args['update_' + key])
267 val[key] = $scope.args[key];
270 if ($scope.args.update_sms_carrier)
271 val.sms_carrier = $scope.args.sms_carrier.id();
275 service.update_holds(vals).finally(function() {
276 $uibModalInstance.close();
279 $scope.cancel = function() { $uibModalInstance.dismiss() }
283 sms_carriers : service.get_sms_carriers
288 service.edit_dates = function(hold_ids) {
289 if (!hold_ids.length) return $q.when();
291 // collects the fields from the dialog the user wishes to modify
292 function relay_to_update(modal_scope) {
293 var vals = hold_ids.map(function(hold_id) {
294 var val = {id : hold_id};
296 ['thaw_date', 'request_time', 'expire_time', 'shelf_expire_time'],
298 if (modal_scope.args['modify_' + field])
299 val[field] = modal_scope.args[field].toISOString();
306 console.log(JSON.stringify(vals,null,2));
307 return service.update_holds(vals);
310 return $uibModal.open({
311 templateUrl : './circ/share/t_hold_dates',
314 ['$scope', '$uibModalInstance',
315 function($scope, $uibModalInstance) {
316 var today = new Date();
319 request_time : today,
321 shelf_expire_time : today
323 $scope.num_holds = hold_ids.length;
324 $scope.ok = function() {
325 relay_to_update($scope).then($uibModalInstance.close);
327 $scope.cancel = function() { $uibModalInstance.dismiss() }
333 service.update_field_with_confirm = function(hold_ids, msg_key, field, value) {
334 if (!hold_ids.length) return $q.when();
336 return egConfirmDialog.open(
337 egCore.strings[msg_key], '', {num_holds : hold_ids.length})
338 .result.then(function() {
340 var vals = hold_ids.map(function(hold_id) {
341 val = {id : hold_id};
345 return service.update_holds(vals);
349 service.suspend_holds = function(hold_ids) {
350 return service.update_field_with_confirm(
351 hold_ids, 'SUSPEND_HOLDS', 'frozen', true);
354 service.activate_holds = function(hold_ids) {
355 return service.update_field_with_confirm(
356 hold_ids, 'ACTIVATE_HOLDS', 'frozen', false);
359 service.set_top_of_queue = function(hold_ids) {
360 return service.update_field_with_confirm(
361 hold_ids, 'SET_TOP_OF_QUEUE', 'cut_in_line', true);
364 service.clear_top_of_queue = function(hold_ids) {
365 return service.update_field_with_confirm(
366 hold_ids, 'CLEAR_TOP_OF_QUEUE', 'cut_in_line', null);
369 service.transfer_to_marked_title = function(hold_ids) {
370 if (!hold_ids.length) return $q.when();
372 var bib_id = egCore.hatch.getLocalItem(
373 'eg.circ.hold.title_transfer_target');
377 return egAlertDialog.open(
378 egCore.strings.NO_HOLD_TRANSFER_TITLE_MARKED).result;
381 return egConfirmDialog.open(
382 egCore.strings.TRANSFER_HOLD_TO_TITLE, '', {
383 num_holds : hold_ids.length,
386 ).result.then(function() {
387 return egCore.net.request(
389 'open-ils.circ.hold.change_title.specific_holds',
390 egCore.auth.token(), bib_id, hold_ids);
394 service.transfer_all_bib_holds_to_marked_title = function(bib_ids) {
395 if (!bib_ids.length) return $q.when();
397 var target_bib_id = egCore.hatch.getLocalItem(
398 'eg.circ.hold.title_transfer_target');
400 if (!target_bib_id) {
402 return egAlertDialog.open(
403 egCore.strings.NO_HOLD_TRANSFER_TITLE_MARKED).result;
406 return egConfirmDialog.open(
407 egCore.strings.TRANSFER_ALL_BIB_HOLDS_TO_TITLE, '', {
408 num_bibs : bib_ids.length,
409 bib_id : target_bib_id
411 ).result.then(function() {
412 return egCore.net.request(
414 'open-ils.circ.hold.change_title',
415 egCore.auth.token(), target_bib_id, bib_ids);
419 // serially retargets each hold
420 service.retarget = function(hold_ids) {
421 if (!hold_ids.length) return $q.when();
422 var deferred = $q.defer();
424 egConfirmDialog.open(
425 egCore.strings.RETARGET_HOLDS, '',
426 {hold_ids : hold_ids.join(',')}
428 ).result.then(function() {
431 var hold_id = hold_ids.pop();
439 'open-ils.circ.hold.reset',
440 egCore.auth.token(), hold_id).finally(do_one);
443 do_one(); // kick it off
446 return deferred.promise;
449 // fleshes orgs, etc. for hold data blobs retrieved from
450 // open-ils.circ.hold.details[.batch].retrieve
451 service.local_flesh = function(hold_data) {
453 hold_data.status_string =
454 egCore.strings['HOLD_STATUS_' + hold_data.status]
457 var hold = hold_data.hold;
458 var volume = hold_data.volume;
459 hold.pickup_lib(egCore.org.get(hold.pickup_lib()));
460 hold.current_shelf_lib(egCore.org.get(hold.current_shelf_lib()));
461 hold_data.id = hold.id();
463 // TODO: LP#1697954 fleshing calls below are deprecated in favor
466 if (hold.requestor() && typeof hold.requestor() != 'object') {
467 console.debug('fetching hold requestor');
468 egCore.pcrud.retrieve('au',hold.requestor()).then(function(u) { hold.requestor(u) });
471 if (hold.cancel_cause() && typeof hold.cancel_cause() != 'object') {
472 console.debug('fetching hold cancel cause');
473 egCore.pcrud.retrieve('ahrcc',hold.cancel_cause()).then(function(c) { hold.cancel_cause(c) });
476 if (hold.usr() && typeof hold.usr() != 'object') {
477 console.debug('fetching hold user');
478 egCore.pcrud.retrieve('au',hold.usr()).then(function(u) { hold.usr(u) });
481 // current_copy is not always fleshed in the API
482 if (hold.current_copy() && typeof hold.current_copy() != 'object') {
483 hold.current_copy(hold_data.copy);
485 // likewise, current_copy's status isn't fleshed in the API
486 if(hold.current_copy().status() !== null &&
487 typeof hold.current_copy().status() != 'object')
488 egCore.pcrud.retrieve('ccs',hold.current_copy().status()
489 ).then(function(c) { hold.current_copy().status(c) });
492 // current_copy's shelving location position isn't always accessible
493 if (hold.current_copy().location()) {
494 console.debug('fetching hold copy location order');
496 if (typeof hold.current_copy().location() != 'object') {
497 location_id = hold.current_copy().location();
499 location_id = hold.current_copy().location().id();
503 {location: location_id, org: egCore.auth.user().ws_ou()},
506 ).then(function(orders) {
507 hold_data.hold._copy_location_position = orders[0].position();
512 //Call number affixes are not always fleshed in the API
513 if (volume.prefix() && typeof volume.prefix() != 'object') {
514 console.debug('fetching call number prefix');
515 egCore.pcrud.retrieve('acnp',volume.prefix()).then(function(p) {volume.prefix(p)});
517 if (volume.suffix() && typeof volume.suffix() != 'object') {
518 console.debug('fetching call number prefix');
519 egCore.pcrud.retrieve('acns',volume.suffix()).then(function(s) {volume.suffix(s)});
528 * Action handlers for the common Hold grid UI.
529 * These generally scrub the data for valid input then pass the
530 * holds / copies / etc. off to the relevant action in egHolds or egCirc.
532 * Caller must apply a reset_page function, which is called after
533 * most actionis are performed.
535 .factory('egHoldGridActions',
536 ['$window','$location','$timeout','egCore','egHolds','egCirc',
537 function($window , $location , $timeout , egCore , egHolds , egCirc) {
541 service.refresh = function() {
542 console.error('egHoldGridActions.refresh not defined!');
545 service.cancel_hold = function(items) {
546 var hold_ids = items.filter(function(item) {
547 return !item.hold.cancel_time();
548 }).map(function(item) {return item.hold.id()});
550 return egHolds.cancel_holds(hold_ids).then(service.refresh);
553 service.uncancel_hold = function(items) {
554 var hold_ids = items.filter(function(item) {
555 return item.hold.cancel_time();
556 }).map(function(item) {return item.hold.id()});
558 return egHolds.uncancel_holds(hold_ids).then(service.refresh);
561 // jump to circ list for either 1) the targeted copy or
562 // 2) the hold target copy for copy-level holds
563 service.show_recent_circs = function(items) {
564 var focus = items.length == 1;
565 angular.forEach(items, function(item) {
567 var url = egCore.env.basePath +
571 $timeout(function() { var x = $window.open(url, '_blank'); if (focus) x.focus() });
576 service.show_patrons = function(items) {
577 var focus = items.length == 1;
578 angular.forEach(items, function(item) {
579 var url = egCore.env.basePath +
581 item.hold.usr().id() +
583 $timeout(function() { var x = $window.open(url, '_blank'); if (focus) x.focus() });
587 service.show_holds_for_title = function(items) {
588 var focus = items.length == 1;
589 angular.forEach(items, function(item) {
590 var url = egCore.env.basePath +
591 'cat/catalog/record/' +
594 $timeout(function() { var x = $window.open(url, '_blank'); if (focus) x.focus() });
599 function generic_update(items, action) {
600 if (!items.length) return $q.when();
601 var hold_ids = items.map(function(item) {return item.hold.id()});
602 return egHolds[action](hold_ids).then(service.refresh);
605 service.set_copy_quality = function(items) {
606 generic_update(items, 'set_copy_quality'); }
607 service.edit_pickup_lib = function(items) {
608 generic_update(items, 'edit_pickup_lib'); }
609 service.edit_notify_prefs = function(items) {
610 generic_update(items, 'edit_notify_prefs'); }
611 service.edit_dates = function(items) {
612 generic_update(items, 'edit_dates'); }
613 service.suspend = function(items) {
614 generic_update(items, 'suspend_holds'); }
615 service.activate = function(items) {
616 generic_update(items, 'activate_holds'); }
617 service.set_top_of_queue = function(items) {
618 generic_update(items, 'set_top_of_queue'); }
619 service.clear_top_of_queue = function(items) {
620 generic_update(items, 'clear_top_of_queue'); }
621 service.transfer_to_marked_title = function(items) {
622 generic_update(items, 'transfer_to_marked_title'); }
624 service.mark_damaged = function(items) {
625 angular.forEach(items, function(item) {
627 egCirc.mark_damaged({
629 barcode: item.copy.barcode()
630 }).then(service.refresh);
635 service.mark_missing = function(items) {
637 .filter(function(item) { return Boolean(item.copy) })
638 .map(function(item) { return item.copy.id() });
640 egCirc.mark_missing(copy_ids).then(service.refresh);
643 service.retarget = function(items) {
644 var hold_ids = items.map(function(item) { return item.hold.id() });
645 egHolds.retarget(hold_ids).then(service.refresh);
652 * Hold details interface
654 .directive('egHoldDetails', function() {
657 templateUrl : './circ/share/t_hold_details',
660 // if set, called whenever hold details are retrieved. The
661 // argument is the hold blob returned from hold.details.retrieve
666 '$scope','$uibModal','egCore','egHolds','egCirc',
667 function($scope , $uibModal , egCore , egHolds , egCirc) {
670 if (!$scope.holdId) return;
674 'open-ils.circ.hold.details.retrieve.authoritative',
675 egCore.auth.token(), $scope.holdId, {
676 include_current_copy : true,
678 include_cancel_cause : true,
679 include_requestor : true
681 ).then(function(hold_data) {
682 egHolds.local_flesh(hold_data);
684 angular.forEach(hold_data,
685 function(val, key) { $scope[key] = val });
687 // fetch + flesh the cancel_cause if needed
688 if ($scope.hold.cancel_time()) {
689 egHolds.get_cancel_reasons().then(function() {
690 // egHolds caches the causes in egEnv
691 $scope.hold.cancel_cause(
692 egCore.env.ahrcc.map[$scope.hold.cancel_cause()]);
696 if ($scope.hold.current_copy()) {
697 egCirc.flesh_copy_location($scope.hold.current_copy());
700 if ($scope.holdRetrieved)
701 $scope.holdRetrieved(hold_data);
706 $scope.show_notify_tab = function() {
707 $scope.detail_tab = 'notify';
708 egCore.pcrud.search('ahn',
709 {hold : $scope.hold.id()},
710 {flesh : 1, flesh_fields : {ahn : ['notify_staff']}},
712 ).then(function(nots) {
713 $scope.hold.notifications(nots);
717 $scope.delete_note = function(note) {
718 egCore.pcrud.remove(note).then(function() {
719 // remove the deleted note from the locally fleshed notes
721 $scope.hold.notes().filter(function(n) {
722 return n.id() != note.id()
728 $scope.new_note = function() {
729 return $uibModal.open({
730 templateUrl : './circ/share/t_hold_note_dialog',
733 ['$scope', '$uibModalInstance',
734 function($scope, $uibModalInstance) {
736 $scope.ok = function() {
737 $uibModalInstance.close($scope.args)
739 $scope.cancel = function($event) {
740 $uibModalInstance.dismiss();
741 $event.preventDefault();
745 }).result.then(function(args) {
746 var note = new egCore.idl.ahrn();
747 note.hold($scope.hold.id());
749 note.slip(args.slip);
751 note.title(args.title);
752 note.body(args.body);
753 return egCore.pcrud.create(note).then(function(n) {
754 $scope.hold.notes().push(n);
759 $scope.new_notification = function() {
760 return $uibModal.open({
761 templateUrl : './circ/share/t_hold_notification_dialog',
764 ['$scope', '$uibModalInstance',
765 function($scope, $uibModalInstance) {
767 $scope.ok = function() {
768 $uibModalInstance.close($scope.args)
770 $scope.cancel = function($event) {
771 $uibModalInstance.dismiss();
772 $event.preventDefault();
776 }).result.then(function(args) {
777 var note = new egCore.idl.ahn();
778 note.hold($scope.hold.id());
779 note.method(args.method);
780 note.note(args.note);
781 note.notify_staff(egCore.auth.user().id());
782 note.notify_time('now');
783 return egCore.pcrud.create(note).then(function(n) {
784 n.notify_staff(egCore.auth.user());
785 $scope.hold.notifications().push(n);
790 $scope.$watch('holdId', function(newVal, oldVal) {
791 if (newVal != oldVal) draw();