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 // FIXME: large batches using .authoritative result in many
18 // stranded cstore backends on the server. Needs investigation.
19 // For now, collect holds in a series of small batches.
20 // Fetch them serially both to avoid the above problem and
25 function one_batch() {
26 var ids = hold_ids.slice(index, index + batch_size)
27 .filter(function(id) {return Boolean(id)}) // avoid nulls
29 console.debug('egHolds.fetch_holds => ' + ids);
39 'open-ils.circ.hold.details.batch.retrieve.authoritative',
40 egCore.auth.token(), ids
43 one_batch, // kick off the next batch
46 var hold = hold_data.hold;
47 hold_data.id = hold.id();
48 service.local_flesh(hold_data);
49 deferred.notify(hold_data);
54 one_batch(); // kick it off
55 return deferred.promise;
59 service.cancel_holds = function(hold_ids) {
61 return $uibModal.open({
62 templateUrl : './circ/share/t_cancel_hold_dialog',
64 ['$scope', '$uibModalInstance', 'cancel_reasons',
65 function($scope, $uibModalInstance, cancel_reasons) {
68 cancel_reasons : cancel_reasons,
69 num_holds : hold_ids.length
72 $scope.cancel = function($event) {
73 $uibModalInstance.dismiss();
74 $event.preventDefault();
77 $scope.ok = function() {
79 function cancel_one() {
80 var hold_id = hold_ids.pop();
82 $uibModalInstance.close();
86 'open-ils.circ', 'open-ils.circ.hold.cancel',
87 egCore.auth.token(), hold_id,
88 $scope.args.cancel_reason,
90 ).then(function(resp) {
91 if (evt = egCore.evt.parse(resp)) {
93 'warning.hold.cancel_failed');
94 console.error('unable to cancel hold: '
106 cancel_reasons : function() {
107 return service.get_cancel_reasons();
113 service.uncancel_holds = function(hold_ids) {
115 return $uibModal.open({
116 templateUrl : './circ/share/t_uncancel_hold_dialog',
118 ['$scope', '$uibModalInstance',
119 function($scope, $uibModalInstance) {
121 num_holds : hold_ids.length
124 $scope.cancel = function($event) {
125 $uibModalInstance.dismiss();
126 $event.preventDefault();
129 $scope.ok = function() {
131 function uncancel_one() {
132 var hold_id = hold_ids.pop();
134 $uibModalInstance.close();
138 'open-ils.circ', 'open-ils.circ.hold.uncancel',
139 egCore.auth.token(), hold_id
140 ).then(function(resp) {
141 if (evt = egCore.evt.parse(resp)) {
143 'warning.hold.uncancel_failed');
144 console.error('unable to uncancel hold: '
158 service.get_cancel_reasons = function() {
159 if (egCore.env.ahrcc) return $q.when(egCore.env.ahrcc.list);
160 return egCore.pcrud.retrieveAll('ahrcc', {}, {atomic : true})
161 .then(function(list) { return egCore.env.absorbList(list, 'ahrcc').list });
164 // Updates a batch of holds, notifies on each response.
165 // new_values = array of hashes describing values to change,
166 // including the id of the hold to change.
167 // e.g. {id : 1, mint_condition : true}
168 service.update_holds = function(new_values) {
169 return egCore.net.request(
171 'open-ils.circ.hold.update.batch',
172 egCore.auth.token(), null, new_values).then(
174 if (evt = egCore.evt.parse(resp)) {
176 'warning.hold.batch_update');
177 console.error('unable to batch update holds: '
181 'success.hold.batch_update');
187 service.set_copy_quality = function(hold_ids) {
188 if (!hold_ids.length) return $q.when();
189 return $uibModal.open({
190 templateUrl : './circ/share/t_hold_copy_quality_dialog',
192 ['$scope', '$uibModalInstance',
193 function($scope, $uibModalInstance) {
195 function update(val) {
196 var vals = hold_ids.map(function(hold_id) {
197 return {id : hold_id, mint_condition : val}})
198 service.update_holds(vals).finally(function() {
199 $uibModalInstance.close();
202 $scope.good = function() { update(true) }
203 $scope.any = function() { update(false) }
204 $scope.cancel = function() { $uibModalInstance.dismiss() }
210 service.edit_pickup_lib = function(hold_ids) {
211 if (!hold_ids.length) return $q.when();
212 return $uibModal.open({
213 templateUrl : './circ/share/t_hold_edit_pickup_lib',
215 ['$scope', '$uibModalInstance',
216 function($scope, $uibModalInstance) {
217 $scope.cant_be_pickup = function (id) { return !egCore.org.CanHaveUsers(id); };
219 $scope.ok = function() {
220 var vals = hold_ids.map(function(hold_id) {
223 pickup_lib : $scope.args.org_unit.id()
226 service.update_holds(vals).finally(function() {
227 $uibModalInstance.close();
230 $scope.cancel = function() { $uibModalInstance.dismiss() }
236 service.get_sms_carriers = function() {
237 if (egCore.env.csc) return $q.when(egCore.env.csc.list);
238 return egCore.pcrud.retrieveAll('csc', {}, {atomic : true})
239 .then(function(list) { return egCore.env.absorbList(list, 'csc').list });
242 service.edit_notify_prefs = function(hold_ids) {
243 if (!hold_ids.length) return $q.when();
244 return $uibModal.open({
245 templateUrl : './circ/share/t_hold_notification_prefs',
247 ['$scope', '$uibModalInstance', 'sms_carriers',
248 function($scope, $uibModalInstance, sms_carriers) {
250 $scope.sms_carriers = sms_carriers;
251 $scope.num_holds = hold_ids.length;
252 $scope.ok = function() {
254 var vals = hold_ids.map(function(hold_id) {
255 var val = {id : hold_id};
257 ['email', 'phone', 'sms'],
259 var key = type + '_notify';
260 if ($scope.args['update_' + key])
261 val[key] = $scope.args[key];
264 if ($scope.args.update_sms_carrier)
265 val.sms_carrier = $scope.args.sms_carrier.id();
269 service.update_holds(vals).finally(function() {
270 $uibModalInstance.close();
273 $scope.cancel = function() { $uibModalInstance.dismiss() }
277 sms_carriers : service.get_sms_carriers
282 service.edit_dates = function(hold_ids) {
283 if (!hold_ids.length) return $q.when();
285 // collects the fields from the dialog the user wishes to modify
286 function relay_to_update(modal_scope) {
287 var vals = hold_ids.map(function(hold_id) {
288 var val = {id : hold_id};
290 ['thaw_date', 'request_time', 'expire_time', 'shelf_expire_time'],
292 if (modal_scope.args['modify_' + field])
293 val[field] = modal_scope.args[field].toISOString();
300 console.log(JSON.stringify(vals,null,2));
301 return service.update_holds(vals);
304 return $uibModal.open({
305 templateUrl : './circ/share/t_hold_dates',
307 ['$scope', '$uibModalInstance',
308 function($scope, $uibModalInstance) {
309 var today = new Date();
312 request_time : today,
314 shelf_expire_time : today
316 $scope.num_holds = hold_ids.length;
317 $scope.ok = function() {
318 relay_to_update($scope).then($uibModalInstance.close);
320 $scope.cancel = function() { $uibModalInstance.dismiss() }
326 service.update_field_with_confirm = function(hold_ids, msg_key, field, value) {
327 if (!hold_ids.length) return $q.when();
329 return egConfirmDialog.open(
330 egCore.strings[msg_key], '', {num_holds : hold_ids.length})
331 .result.then(function() {
333 var vals = hold_ids.map(function(hold_id) {
334 val = {id : hold_id};
338 return service.update_holds(vals);
342 service.suspend_holds = function(hold_ids) {
343 return service.update_field_with_confirm(
344 hold_ids, 'SUSPEND_HOLDS', 'frozen', true);
347 service.activate_holds = function(hold_ids) {
348 return service.update_field_with_confirm(
349 hold_ids, 'ACTIVATE_HOLDS', 'frozen', false);
352 service.set_top_of_queue = function(hold_ids) {
353 return service.update_field_with_confirm(
354 hold_ids, 'SET_TOP_OF_QUEUE', 'cut_in_line', true);
357 service.clear_top_of_queue = function(hold_ids) {
358 return service.update_field_with_confirm(
359 hold_ids, 'CLEAR_TOP_OF_QUEUE', 'cut_in_line', null);
362 service.transfer_to_marked_title = function(hold_ids) {
363 if (!hold_ids.length) return $q.when();
365 var bib_id = egCore.hatch.getLocalItem(
366 'eg.circ.hold.title_transfer_target');
370 return egAlertDialog.open(
371 egCore.strings.NO_HOLD_TRANSFER_TITLE_MARKED).result;
374 return egConfirmDialog.open(
375 egCore.strings.TRANSFER_HOLD_TO_TITLE, '', {
376 num_holds : hold_ids.length,
379 ).result.then(function() {
380 return egCore.net.request(
382 'open-ils.circ.hold.change_title.specific_holds',
383 egCore.auth.token(), bib_id, hold_ids);
387 service.transfer_all_bib_holds_to_marked_title = function(bib_ids) {
388 if (!bib_ids.length) return $q.when();
390 var target_bib_id = egCore.hatch.getLocalItem(
391 'eg.circ.hold.title_transfer_target');
393 if (!target_bib_id) {
395 return egAlertDialog.open(
396 egCore.strings.NO_HOLD_TRANSFER_TITLE_MARKED).result;
399 return egConfirmDialog.open(
400 egCore.strings.TRANSFER_ALL_BIB_HOLDS_TO_TITLE, '', {
401 num_bibs : bib_ids.length,
402 bib_id : target_bib_id
404 ).result.then(function() {
405 return egCore.net.request(
407 'open-ils.circ.hold.change_title',
408 egCore.auth.token(), target_bib_id, bib_ids);
412 // serially retargets each hold
413 service.retarget = function(hold_ids) {
414 if (!hold_ids.length) return $q.when();
415 var deferred = $q.defer();
417 egConfirmDialog.open(
418 egCore.strings.RETARGET_HOLDS, '',
419 {hold_ids : hold_ids.join(',')}
421 ).result.then(function() {
424 var hold_id = hold_ids.pop();
432 'open-ils.circ.hold.reset',
433 egCore.auth.token(), hold_id).finally(do_one);
436 do_one(); // kick it off
439 return deferred.promise;
442 // fleshes orgs, etc. for hold data blobs retrieved from
443 // open-ils.circ.hold.details[.batch].retrieve
444 service.local_flesh = function(hold_data) {
446 hold_data.status_string =
447 egCore.strings['HOLD_STATUS_' + hold_data.status]
450 var hold = hold_data.hold;
451 hold.pickup_lib(egCore.org.get(hold.pickup_lib()));
452 hold.current_shelf_lib(egCore.org.get(hold.current_shelf_lib()));
453 hold_data.id = hold.id();
455 if (hold.requestor() && typeof hold.requestor() != 'object')
456 egCore.pcrud.retrieve('au',hold.requestor()).then(function(u) { hold.requestor(u) });
458 if (hold.cancel_cause() && typeof hold.cancel_cause() != 'object')
459 egCore.pcrud.retrieve('ahrcc',hold.cancel_cause()).then(function(c) { hold.cancel_cause(c) });
461 if (hold.usr() && typeof hold.usr() != 'object')
462 egCore.pcrud.retrieve('au',hold.usr()).then(function(u) { hold.usr(u) });
464 // current_copy is not always fleshed in the API
465 if (hold.current_copy() && typeof hold.current_copy() != 'object') {
466 hold.current_copy(hold_data.copy);
468 // likewise, current_copy's status isn't fleshed in the API
469 if(hold.current_copy().status() !== null &&
470 typeof hold.current_copy().status() != 'object')
471 egCore.pcrud.retrieve('ccs',hold.current_copy().status()
472 ).then(function(c) { hold.current_copy().status(c) });
480 * Action handlers for the common Hold grid UI.
481 * These generally scrub the data for valid input then pass the
482 * holds / copies / etc. off to the relevant action in egHolds or egCirc.
484 * Caller must apply a reset_page function, which is called after
485 * most actionis are performed.
487 .factory('egHoldGridActions',
488 ['$window','$location','$timeout','egCore','egHolds','egCirc',
489 function($window , $location , $timeout , egCore , egHolds , egCirc) {
493 service.refresh = function() {
494 console.error('egHoldGridActions.refresh not defined!');
497 service.cancel_hold = function(items) {
498 var hold_ids = items.filter(function(item) {
499 return !item.hold.cancel_time();
500 }).map(function(item) {return item.hold.id()});
502 return egHolds.cancel_holds(hold_ids).then(service.refresh);
505 service.uncancel_hold = function(items) {
506 var hold_ids = items.filter(function(item) {
507 return item.hold.cancel_time();
508 }).map(function(item) {return item.hold.id()});
510 return egHolds.uncancel_holds(hold_ids).then(service.refresh);
513 // jump to circ list for either 1) the targeted copy or
514 // 2) the hold target copy for copy-level holds
515 service.show_recent_circs = function(items) {
516 var focus = items.length == 1;
517 angular.forEach(items, function(item) {
519 var url = egCore.env.basePath +
523 $timeout(function() { var x = $window.open(url, '_blank'); if (focus) x.focus() });
528 service.show_patrons = function(items) {
529 var focus = items.length == 1;
530 angular.forEach(items, function(item) {
531 var url = egCore.env.basePath +
533 item.hold.usr().id() +
535 $timeout(function() { var x = $window.open(url, '_blank'); if (focus) x.focus() });
539 service.show_holds_for_title = function(items) {
540 var focus = items.length == 1;
541 angular.forEach(items, function(item) {
542 var url = egCore.env.basePath +
543 'cat/catalog/record/' +
546 $timeout(function() { var x = $window.open(url, '_blank'); if (focus) x.focus() });
551 function generic_update(items, action) {
552 if (!items.length) return $q.when();
553 var hold_ids = items.map(function(item) {return item.hold.id()});
554 return egHolds[action](hold_ids).then(service.refresh);
557 service.set_copy_quality = function(items) {
558 generic_update(items, 'set_copy_quality'); }
559 service.edit_pickup_lib = function(items) {
560 generic_update(items, 'edit_pickup_lib'); }
561 service.edit_notify_prefs = function(items) {
562 generic_update(items, 'edit_notify_prefs'); }
563 service.edit_dates = function(items) {
564 generic_update(items, 'edit_dates'); }
565 service.suspend = function(items) {
566 generic_update(items, 'suspend_holds'); }
567 service.activate = function(items) {
568 generic_update(items, 'activate_holds'); }
569 service.set_top_of_queue = function(items) {
570 generic_update(items, 'set_top_of_queue'); }
571 service.clear_top_of_queue = function(items) {
572 generic_update(items, 'clear_top_of_queue'); }
573 service.transfer_to_marked_title = function(items) {
574 generic_update(items, 'transfer_to_marked_title'); }
576 service.mark_damaged = function(items) {
578 .filter(function(item) { return Boolean(item.copy) })
579 .map(function(item) { return item.copy.id() });
581 egCirc.mark_damaged(copy_ids).then(service.refresh);
584 service.mark_missing = function(items) {
586 .filter(function(item) { return Boolean(item.copy) })
587 .map(function(item) { return item.copy.id() });
589 egCirc.mark_missing(copy_ids).then(service.refresh);
592 service.retarget = function(items) {
593 var hold_ids = items.map(function(item) { return item.hold.id() });
594 egHolds.retarget(hold_ids).then(service.refresh);
601 * Hold details interface
603 .directive('egHoldDetails', function() {
606 templateUrl : './circ/share/t_hold_details',
609 // if set, called whenever hold details are retrieved. The
610 // argument is the hold blob returned from hold.details.retrieve
615 '$scope','$uibModal','egCore','egHolds','egCirc',
616 function($scope , $uibModal , egCore , egHolds , egCirc) {
619 if (!$scope.holdId) return;
623 'open-ils.circ.hold.details.retrieve.authoritative',
624 egCore.auth.token(), $scope.holdId
626 ).then(function(hold_data) {
627 egHolds.local_flesh(hold_data);
629 angular.forEach(hold_data,
630 function(val, key) { $scope[key] = val });
632 // fetch + flesh the cancel_cause if needed
633 if ($scope.hold.cancel_time()) {
634 egHolds.get_cancel_reasons().then(function() {
635 // egHolds caches the causes in egEnv
636 $scope.hold.cancel_cause(
637 egCore.env.ahrcc.map[$scope.hold.cancel_cause()]);
641 if ($scope.hold.current_copy()) {
642 egCirc.flesh_copy_location($scope.hold.current_copy());
645 if ($scope.holdRetrieved)
646 $scope.holdRetrieved(hold_data);
651 $scope.show_notify_tab = function() {
652 $scope.detail_tab = 'notify';
653 egCore.pcrud.search('ahn',
654 {hold : $scope.hold.id()},
655 {flesh : 1, flesh_fields : {ahn : ['notify_staff']}},
657 ).then(function(nots) {
658 $scope.hold.notifications(nots);
662 $scope.delete_note = function(note) {
663 egCore.pcrud.remove(note).then(function() {
664 // remove the deleted note from the locally fleshed notes
666 $scope.hold.notes().filter(function(n) {
667 return n.id() != note.id()
673 $scope.new_note = function() {
674 return $uibModal.open({
675 templateUrl : './circ/share/t_hold_note_dialog',
677 ['$scope', '$uibModalInstance',
678 function($scope, $uibModalInstance) {
680 $scope.ok = function() {
681 $uibModalInstance.close($scope.args)
683 $scope.cancel = function($event) {
684 $uibModalInstance.dismiss();
685 $event.preventDefault();
689 }).result.then(function(args) {
690 var note = new egCore.idl.ahrn();
691 note.hold($scope.hold.id());
693 note.slip(args.slip);
695 note.title(args.title);
696 note.body(args.body);
697 return egCore.pcrud.create(note).then(function(n) {
698 $scope.hold.notes().push(n);
703 $scope.new_notification = function() {
704 return $uibModal.open({
705 templateUrl : './circ/share/t_hold_notification_dialog',
707 ['$scope', '$uibModalInstance',
708 function($scope, $uibModalInstance) {
710 $scope.ok = function() {
711 $uibModalInstance.close($scope.args)
713 $scope.cancel = function($event) {
714 $uibModalInstance.dismiss();
715 $event.preventDefault();
719 }).result.then(function(args) {
720 var note = new egCore.idl.ahn();
721 note.hold($scope.hold.id());
722 note.method(args.method);
723 note.note(args.note);
724 note.notify_staff(egCore.auth.user().id());
725 note.notify_time('now');
726 return egCore.pcrud.create(note).then(function(n) {
727 n.notify_staff(egCore.auth.user());
728 $scope.hold.notifications().push(n);
733 $scope.$watch('holdId', function(newVal, oldVal) {
734 if (newVal != oldVal) draw();