5 angular.module('egItemStatus',
6 ['ngRoute', 'ui.bootstrap', 'egCoreMod', 'egUiMod', 'egGridMod', 'egUserMod'])
8 .filter('boolText', function(){
14 .config(function($routeProvider, $locationProvider, $compileProvider) {
15 $locationProvider.html5Mode(true);
16 $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|blob):/); // grid export
18 var resolver = {delay : function(egStartup) {return egStartup.go()}};
20 // search page shows the list view by default
21 $routeProvider.when('/cat/item/search', {
22 templateUrl: './cat/item/t_list',
23 controller: 'ListCtrl',
27 // search page shows the list view by default
28 $routeProvider.when('/cat/item/search/:idList', {
29 templateUrl: './cat/item/t_list',
30 controller: 'ListCtrl',
34 $routeProvider.when('/cat/item/:id', {
35 templateUrl: './cat/item/t_view',
36 controller: 'ViewCtrl',
40 $routeProvider.when('/cat/item/:id/:tab', {
41 templateUrl: './cat/item/t_view',
42 controller: 'ViewCtrl',
46 // default page / bucket view
47 $routeProvider.otherwise({redirectTo : '/cat/item/search'});
51 ['egCore','egCirc','$uibModal','$q','$timeout','$window','egConfirmDialog','egAlertDialog',
52 function(egCore , egCirc , $uibModal , $q , $timeout , $window , egConfirmDialog, egAlertDialog ) {
55 copies : [], // copy barcode search results
56 index : 0 // search grid index
62 acp : ['call_number','location','status','location','floating','circ_modifier',
63 'age_protect','circ_lib'],
64 acn : ['record','prefix','suffix','label_class'],
65 bre : ['simple_record','creator','editor']
68 // avoid fleshing MARC on the bre
69 // note: don't add simple_record.. not sure why
70 bre : ['id','tcn_value','creator','editor'],
80 'checkin_workstation',
88 order_by : {circ : 'xact_start desc'},
92 //Retrieve separate copy, aacs, and accs information
93 service.getCopy = function(barcode, id) {
95 // handle barcode completion
96 return egCirc.handle_barcode_completion(barcode)
97 .then(function(actual_barcode) {
98 return egCore.pcrud.search(
99 'acp', {barcode : actual_barcode, deleted : 'f'},
100 service.flesh).then(function(copy) {return copy});
104 return egCore.pcrud.retrieve( 'acp', id, service.flesh)
105 .then(function(copy) {return copy});
108 service.getCirc = function(id) {
109 return egCore.pcrud.search('aacs', { target_copy : id },
110 service.circFlesh).then(function(circ) {return circ});
113 service.getSummary = function(id) {
114 return circ_summary = egCore.net.request(
116 'open-ils.circ.renewal_chain.retrieve_by_circ.summary',
117 egCore.auth.token(), id).then(
118 function(circ_summary) {return circ_summary});
121 //Combine copy, circ, and accs information
122 service.retrieveCopyData = function(barcode, id) {
125 var fetchCopy = function(barcode, id) {
126 return service.getCopy(barcode, id)
127 .then(function(copy) {
128 copyData.copy = copy;
132 var fetchCirc = function(copy) {
133 return service.getCirc(copy.id())
134 .then(function(circ) {
135 copyData.circ = circ;
139 var fetchSummary = function(circ) {
140 return service.getSummary(circ.id())
141 .then(function(summary) {
142 copyData.circ_summary = summary;
146 return fetchCopy(barcode, id).then(function(res) {
147 return fetchCirc(copyData.copy).then(function(res) {
149 return fetchSummary(copyData.circ).then(function() {
160 // resolved with the last received copy
161 service.fetch = function(barcode, id, noListDupes) {
167 return service.retrieveCopyData(barcode, id)
168 .then(function(copyData) {
169 //Make sure we're getting a completed copyData - no plain acp or circ objects
171 // flesh circ_lib locally
172 copyData.circ.circ_lib(egCore.org.get(copyData.circ.circ_lib()));
173 copyData.circ.checkin_workstation(
174 egCore.org.get(copyData.circ.checkin_workstation()));
179 // use the existing copy if possible
180 flatCopy = service.copies.filter(
181 function(c) {return c.id == copyData.copy.id()})[0];
185 flatCopy = egCore.idl.toHash(copyData.copy, true);
188 flatCopy._circ = egCore.idl.toHash(copyData.circ, true);
189 flatCopy._circ_summary = egCore.idl.toHash(copyData.circ_summary, true);
190 flatCopy._circ_lib = copyData.circ.circ_lib();
191 flatCopy._duration = copyData.circ.duration();
193 flatCopy.index = service.index++;
194 service.copies.unshift(flatCopy);
197 //Get in-house use count
198 egCore.pcrud.search('aihu',
199 {item : flatCopy.id}, {}, {idlist : true, atomic : true})
200 .then(function(uses) {
201 flatCopy._inHouseUseCount = uses.length;
202 copyData.copy._inHouseUseCount = uses.length;
206 copy : copyData.copy,
207 index : flatCopy.index
214 service.add_copies_to_bucket = function(copy_list) {
215 if (copy_list.length == 0) return;
217 return $uibModal.open({
218 templateUrl: './cat/catalog/t_add_to_bucket',
222 ['$scope','$uibModalInstance',
223 function($scope , $uibModalInstance) {
225 $scope.bucket_id = 0;
226 $scope.newBucketName = '';
227 $scope.allBuckets = [];
231 'open-ils.actor.container.retrieve_by_class.authoritative',
232 egCore.auth.token(), egCore.auth.user().id(),
233 'copy', 'staff_client'
234 ).then(function(buckets) { $scope.allBuckets = buckets; });
236 $scope.add_to_bucket = function() {
238 angular.forEach(copy_list, function (cp) {
239 var item = new egCore.idl.ccbi()
240 item.bucket($scope.bucket_id);
241 item.target_copy(cp);
245 'open-ils.actor.container.item.create',
246 egCore.auth.token(), 'copy', item
250 return $q.all(promises).then(function() {
251 $uibModalInstance.close();
256 $scope.add_to_new_bucket = function() {
257 var bucket = new egCore.idl.ccb();
258 bucket.owner(egCore.auth.user().id());
259 bucket.name($scope.newBucketName);
260 bucket.description('');
261 bucket.btype('staff_client');
263 return egCore.net.request(
265 'open-ils.actor.container.create',
266 egCore.auth.token(), 'copy', bucket
267 ).then(function(bucket) {
268 $scope.bucket_id = bucket;
269 $scope.add_to_bucket();
273 $scope.cancel = function() {
274 $uibModalInstance.dismiss();
280 service.make_copies_bookable = function(items) {
282 var copies_by_record = {};
283 var record_list = [];
287 var record_id = item['call_number.record.id'];
288 if (typeof copies_by_record[ record_id ] == 'undefined') {
289 copies_by_record[ record_id ] = [];
290 record_list.push( record_id );
292 copies_by_record[ record_id ].push(item.id);
297 var combined_results = [];
298 angular.forEach(record_list, function(record_id) {
302 'open-ils.booking.resources.create_from_copies',
304 copies_by_record[record_id]
305 ).then(function(results) {
306 if (results && results['brsrc']) {
307 combined_results = combined_results.concat(results['brsrc']);
313 $q.all(promises).then(function() {
314 if (combined_results.length > 0) {
316 template: '<eg-embed-frame url="booking_admin_url" handlers="funcs"></eg-embed-frame>',
320 ['$scope','$location','egCore','$uibModalInstance',
321 function($scope , $location , egCore , $uibModalInstance) {
324 ses : egCore.auth.token(),
325 resultant_brsrc : combined_results.map(function(o) { return o[0]; })
328 var booking_path = '/eg/conify/global/booking/resource';
330 $scope.booking_admin_url =
331 $location.absUrl().replace(/\/eg\/staff.*/, booking_path);
338 service.book_copies_now = function(items) {
339 var copies_by_record = {};
340 var record_list = [];
344 var record_id = item['call_number.record.id'];
345 if (typeof copies_by_record[ record_id ] == 'undefined') {
346 copies_by_record[ record_id ] = [];
347 record_list.push( record_id );
349 copies_by_record[ record_id ].push(item.id);
354 var combined_brt = [];
355 var combined_brsrc = [];
356 angular.forEach(record_list, function(record_id) {
360 'open-ils.booking.resources.create_from_copies',
362 copies_by_record[record_id]
363 ).then(function(results) {
364 if (results && results['brt']) {
365 combined_brt = combined_brt.concat(results['brt']);
367 if (results && results['brsrc']) {
368 combined_brsrc = combined_brsrc.concat(results['brsrc']);
374 $q.all(promises).then(function() {
375 if (combined_brt.length > 0 || combined_brsrc.length > 0) {
377 template: '<eg-embed-frame url="booking_admin_url" handlers="funcs"></eg-embed-frame>',
381 ['$scope','$location','egCore','$uibModalInstance',
382 function($scope , $location , egCore , $uibModalInstance) {
385 ses : egCore.auth.token(),
386 bresv_interface_opts : {
389 ,brsrc : combined_brsrc
394 var booking_path = '/eg/booking/reservation';
396 $scope.booking_admin_url =
397 $location.absUrl().replace(/\/eg\/staff.*/, booking_path);
405 service.requestItems = function(copy_list) {
406 if (copy_list.length == 0) return;
408 return $uibModal.open({
409 templateUrl: './cat/catalog/t_request_items',
412 ['$scope','$uibModalInstance','egUser',
413 function($scope , $uibModalInstance , egUser) {
415 $scope.first_user_fetch = true;
419 copy_list : copy_list,
420 pickup_lib: egCore.org.get(egCore.auth.user().ws_ou()),
421 user : egCore.auth.user().id()
424 egUser.get( $scope.hold_data.user ).then(function(u) {
426 $scope.barcode = u.card().barcode();
427 $scope.user_name = egUser.format_name(u);
428 $scope.hold_data.user = u.id();
431 $scope.user_name = '';
433 $scope.$watch('barcode', function (n) {
434 if (!$scope.first_user_fetch) {
435 egUser.getByBarcode(n).then(function(u) {
437 $scope.user_name = egUser.format_name(u);
438 $scope.hold_data.user = u.id();
441 $scope.user_name = '';
442 delete $scope.hold_data.user;
445 $scope.first_user_fetch = false;
448 $scope.ok = function(h) {
451 hold_type : h.hold_type,
452 pickup_lib: h.pickup_lib.id(),
458 'open-ils.circ.holds.test_and_create.batch.override',
459 egCore.auth.token(), args, h.copy_list
462 $uibModalInstance.close();
465 $scope.cancel = function($event) {
466 $uibModalInstance.dismiss();
467 $event.preventDefault();
473 service.attach_to_peer_bib = function(items) {
474 if (items.length == 0) return;
476 egCore.hatch.getItem('eg.cat.marked_conjoined_record').then(function(target_record) {
477 if (!target_record) return;
479 return $uibModal.open({
480 templateUrl: './cat/catalog/t_conjoined_selector',
483 ['$scope','$uibModalInstance',
484 function($scope , $uibModalInstance) {
485 $scope.update = false;
487 $scope.peer_type = null;
488 $scope.peer_type_list = [];
490 get_peer_types = function() {
492 return $q.when(egCore.env.bpt.list);
494 return egCore.pcrud.retrieveAll('bpt', null, {atomic : true})
495 .then(function(list) {
496 egCore.env.absorbList(list, 'bpt');
501 get_peer_types().then(function(list){
502 $scope.peer_type_list = list;
505 $scope.ok = function(type) {
508 angular.forEach(items, function (cp) {
509 var n = new egCore.idl.bpbcm();
511 n.peer_record(target_record);
512 n.target_copy(cp.id);
514 promises.push(egCore.pcrud.create(n).then(function(){service.add_barcode_to_list(cp.barcode)}));
517 return $q.all(promises).then(function(){$uibModalInstance.close()});
520 $scope.cancel = function($event) {
521 $uibModalInstance.dismiss();
522 $event.preventDefault();
529 service.selectedHoldingsCopyDelete = function (items) {
530 if (items.length == 0) return;
532 var copy_objects = [];
533 egCore.pcrud.search('acp',
534 {deleted : 'f', id : items.map(function(el){return el.id;}) },
535 { flesh : 1, flesh_fields : { acp : ['call_number'] } }
536 ).then(function(copy) {
537 copy_objects.push(copy);
541 var perCnCopies = {};
551 var cn_id = cp.call_number().id();
552 if (!cnHash[cn_id]) {
553 cnHash[cn_id] = cp.call_number();
554 perCnCopies[cn_id] = [cp];
556 perCnCopies[cn_id].push(cp);
558 cp.call_number(cn_id); // prevent loops in JSON-ification
562 angular.forEach(perCnCopies, function (v, k) {
567 angular.forEach(cnHash, function (v, k) {
571 if (cnList.length == 0) return;
575 egConfirmDialog.open(
576 egCore.strings.CONFIRM_DELETE_COPIES_VOLUMES,
577 egCore.strings.CONFIRM_DELETE_COPIES_VOLUMES_MESSAGE,
578 {copies : cp_count, volumes : cn_count}
579 ).result.then(function() {
582 'open-ils.cat.asset.volume.fleshed.batch.update.override',
583 egCore.auth.token(), cnList, 1, flags
585 angular.forEach(items, function(cp){service.add_barcode_to_list(cp.barcode)});
591 service.checkin = function (items) {
592 angular.forEach(items, function (cp) {
593 egCirc.checkin({copy_barcode:cp.barcode}).then(
594 function() { service.add_barcode_to_list(cp.barcode) }
599 service.renew = function (items) {
600 angular.forEach(items, function (cp) {
601 egCirc.renew({copy_barcode:cp.barcode}).then(
602 function() { service.add_barcode_to_list(cp.barcode) }
607 service.cancel_transit = function (items) {
608 angular.forEach(items, function(cp) {
609 egCirc.find_copy_transit(null, {copy_barcode:cp.barcode})
610 .then(function(t) { return egCirc.abort_transit(t.id()) })
611 .then(function() { return service.add_barcode_to_list(cp.barcode) });
615 service.selectedHoldingsDamaged = function (items) {
616 egCirc.mark_damaged(items.map(function(el){return el.id;})).then(function(){
617 angular.forEach(items, function(cp){service.add_barcode_to_list(cp.barcode)});
621 service.selectedHoldingsMissing = function (items) {
622 egCirc.mark_missing(items.map(function(el){return el.id;})).then(function(){
623 angular.forEach(items, function(cp){service.add_barcode_to_list(cp.barcode)});
627 service.gatherSelectedRecordIds = function (items) {
632 if (rid_list.indexOf(item['call_number.record.id']) == -1)
633 rid_list.push(item['call_number.record.id'])
639 service.gatherSelectedVolumeIds = function (items,rid) {
644 if (rid && item['call_number.record.id'] != rid) return;
645 if (cn_id_list.indexOf(item['call_number.id']) == -1)
646 cn_id_list.push(item['call_number.id'])
652 service.gatherSelectedHoldingsIds = function (items,rid) {
657 if (rid && item['call_number.record.id'] != rid) return;
658 cp_id_list.push(item.id)
664 service.spawnHoldingsAdd = function (items,use_vols,use_copies){
665 angular.forEach(service.gatherSelectedRecordIds(items), function (r) {
667 if (use_copies) { // just a copy on existing volumes
668 angular.forEach(service.gatherSelectedVolumeIds(items,r), function (v) {
669 raw.push( {callnumber : v} );
671 } else if (use_vols) {
673 service.gatherSelectedHoldingsIds(items,r),
675 angular.forEach(items, function(item) {
676 if (i == item.id) raw.push({owner : item['call_number.owning_lib']});
682 if (raw.length == 0) raw.push({});
686 'open-ils.actor.anon_cache.set_value',
687 null, 'edit-these-copies', {
693 ).then(function(key) {
695 var url = egCore.env.basePath + 'cat/volcopy/' + key;
696 $timeout(function() { $window.open(url, '_blank') });
698 alert('Could not create anonymous cache key!');
704 service.spawnHoldingsEdit = function (items,hide_vols,hide_copies){
705 angular.forEach(service.gatherSelectedRecordIds(items), function (r) {
708 'open-ils.actor.anon_cache.set_value',
709 null, 'edit-these-copies', {
711 copies: service.gatherSelectedHoldingsIds(items,r),
713 hide_vols : hide_vols,
714 hide_copies : hide_copies
716 ).then(function(key) {
718 var url = egCore.env.basePath + 'cat/volcopy/' + key;
719 $timeout(function() { $window.open(url, '_blank') });
721 alert('Could not create anonymous cache key!');
727 service.replaceBarcodes = function(items) {
728 angular.forEach(items, function (cp) {
730 templateUrl: './cat/share/t_replace_barcode',
733 ['$scope','$uibModalInstance',
734 function($scope , $uibModalInstance) {
735 $scope.isModal = true;
736 $scope.focusBarcode = false;
737 $scope.focusBarcode2 = true;
738 $scope.barcode1 = cp.barcode;
740 $scope.updateBarcode = function() {
741 $scope.copyNotFound = false;
742 $scope.updateOK = false;
744 egCore.pcrud.search('acp',
745 {deleted : 'f', barcode : $scope.barcode1})
746 .then(function(copy) {
749 $scope.focusBarcode = true;
750 $scope.copyNotFound = true;
754 $scope.copyId = copy.id();
755 copy.barcode($scope.barcode2);
757 egCore.pcrud.update(copy).then(function(stat) {
758 $scope.updateOK = stat;
759 $scope.focusBarcode = true;
760 if (stat) service.add_barcode_to_list(copy.barcode());
764 $uibModalInstance.close();
767 $scope.cancel = function($event) {
768 $uibModalInstance.dismiss();
769 $event.preventDefault();
777 // this "transfers" selected copies to a new owning library,
778 // auto-creating volumes and deleting unused volumes as required.
779 service.changeItemOwningLib = function(items) {
780 var xfer_target = egCore.hatch.getLocalItem('eg.cat.volume_transfer_target');
781 if (!xfer_target || !items.length) {
784 var vols_to_move = {};
785 var copies_to_move = {};
786 angular.forEach(items, function(item) {
787 if (item['call_number.owning_lib'] != xfer_target) {
788 if (item['call_number.id'] in vols_to_move) {
789 copies_to_move[item['call_number.id']].push(item.id);
791 vols_to_move[item['call_number.id']] = {
792 label : item['call_number.label'],
793 label_class : item['call_number.label_class'],
794 record : item['call_number.record.id'],
795 prefix : item['call_number.prefix.id'],
796 suffix : item['call_number.suffix.id']
798 copies_to_move[item['call_number.id']] = new Array;
799 copies_to_move[item['call_number.id']].push(item.id);
805 angular.forEach(vols_to_move, function(vol) {
806 promises.push(egCore.net.request(
808 'open-ils.cat.call_number.find_or_create',
816 ).then(function(resp) {
817 var evt = egCore.evt.parse(resp);
819 return egCore.net.request(
821 'open-ils.cat.transfer_copies_to_volume',
824 copies_to_move[vol.id]
833 function(){ service.add_barcode_to_list(cp.barcode) }
840 service.transferItems = function (items){
841 var xfer_target = egCore.hatch.getLocalItem('eg.cat.item_transfer_target');
842 var copy_ids = service.gatherSelectedHoldingsIds(items);
843 if (xfer_target && copy_ids.length > 0) {
846 'open-ils.cat.transfer_copies_to_volume',
851 function(resp) { // oncomplete
852 var evt = egCore.evt.parse(resp);
854 egConfirmDialog.open(
855 egCore.strings.OVERRIDE_TRANSFER_COPIES_TO_MARKED_VOLUME_TITLE,
856 egCore.strings.OVERRIDE_TRANSFER_COPIES_TO_MARKED_VOLUME_BODY,
858 ).result.then(function() {
861 'open-ils.cat.transfer_copies_to_volume.override',
865 { events: ['TITLE_LAST_COPY', 'COPY_DELETE_WARNING'] }
868 angular.forEach(items, function(cp){service.add_barcode_to_list(cp.barcode)});
871 angular.forEach(items, function(cp){service.add_barcode_to_list(cp.barcode)});
881 service.mark_missing_pieces = function(copy) {
882 var b = copy.barcode();
883 var t = egCore.idl.toHash(copy.call_number()).record.title;
884 egConfirmDialog.open(
885 egCore.strings.CONFIRM_MARK_MISSING_TITLE,
886 egCore.strings.CONFIRM_MARK_MISSING_BODY,
887 { barcode : b, title : t }
888 ).result.then(function() {
890 // kick off mark missing
891 return egCore.net.request(
893 'open-ils.circ.mark_item_missing_pieces',
894 egCore.auth.token(), copy.id()
897 }).then(function(resp) {
898 var evt = egCore.evt.parse(resp); // should always produce event
900 if (evt.textcode == 'ACTION_CIRCULATION_NOT_FOUND') {
901 return egAlertDialog.open(
902 egCore.strings.CIRC_NOT_FOUND, {barcode : copy.barcode()});
905 var payload = evt.payload;
907 // TODO: open copy editor inline? new tab?
909 // print the missing pieces slip
910 var promise = $q.when();
912 // wait for completion, since it may spawn a confirm dialog
913 promise = egCore.print.print({
915 content_type : 'text/html',
916 content : payload.slip.template_output().data()
920 if (payload.letter) {
921 $scope.letter = payload.letter.template_output().data();
924 // apply patron penalty
926 promise.then(function() {
927 egCirc.create_penalty(payload.circ.usr())
938 * Search bar along the top of the page.
939 * Parent scope for list and detail views
941 .controller('SearchCtrl',
942 ['$scope','$location','$timeout','egCore','egGridDataProvider','itemSvc',
943 function($scope , $location , $timeout , egCore , egGridDataProvider , itemSvc) {
944 $scope.args = {}; // search args
946 // sub-scopes (search / detail-view) apply their version
947 // of retrieval function to $scope.context.search
948 // and display toggling via $scope.context.toggleDisplay
953 $scope.toggleView = function($event) {
954 $scope.context.toggleDisplay();
955 $event.preventDefault(); // avoid form submission
958 // The functions that follow in this controller are never called
959 // when the List View is active, only the Detail View.
961 // In this context, we're only ever dealing with 1 item, so
962 // we can simply refresh the page. These various itemSvc
963 // functions used to live in the ListCtrl, but they're now
964 // shared between SearchCtrl (for Actions for the Detail View)
965 // and ListCtrl (Actions in the egGrid)
966 itemSvc.add_barcode_to_list = function(b) {
967 //console.log('SearchCtrl: add_barcode_to_list',b);
968 // timeout so audible can happen upon checkin
969 $timeout(function() { location.href = location.href; }, 1000);
972 $scope.add_copies_to_bucket = function() {
973 itemSvc.add_copies_to_bucket([$scope.args.copyId]);
976 $scope.make_copies_bookable = function() {
977 itemSvc.make_copies_bookable([{
978 id : $scope.args.copyId,
979 'call_number.record.id' : $scope.args.recordId
983 $scope.book_copies_now = function() {
984 itemSvc.book_copies_now([{
985 id : $scope.args.copyId,
986 'call_number.record.id' : $scope.args.recordId
990 $scope.requestItems = function() {
991 itemSvc.requestItems([$scope.args.copyId]);
994 $scope.attach_to_peer_bib = function() {
995 itemSvc.attach_to_peer_bib([{
996 id : $scope.args.copyId,
997 barcode : $scope.args.copyBarcode
1001 $scope.selectedHoldingsCopyDelete = function () {
1002 itemSvc.selectedHoldingsCopyDelete([{
1003 id : $scope.args.copyId,
1004 barcode : $scope.args.copyBarcode
1008 $scope.checkin = function () {
1010 id : $scope.args.copyId,
1011 barcode : $scope.args.copyBarcode
1015 $scope.renew = function () {
1017 id : $scope.args.copyId,
1018 barcode : $scope.args.copyBarcode
1022 $scope.cancel_transit = function () {
1023 itemSvc.cancel_transit([{
1024 id : $scope.args.copyId,
1025 barcode : $scope.args.copyBarcode
1029 $scope.selectedHoldingsDamaged = function () {
1030 itemSvc.selectedHoldingsDamaged([{
1031 id : $scope.args.copyId,
1032 barcode : $scope.args.copyBarcode
1036 $scope.selectedHoldingsMissing = function () {
1037 itemSvc.selectedHoldingsMissing([{
1038 id : $scope.args.copyId,
1039 barcode : $scope.args.copyBarcode
1043 $scope.selectedHoldingsVolCopyAdd = function () {
1044 itemSvc.spawnHoldingsAdd([{
1045 id : $scope.args.copyId,
1046 'call_number.owning_lib' : $scope.args.cnOwningLib,
1047 'call_number.record.id' : $scope.args.recordId,
1048 barcode : $scope.args.copyBarcode
1051 $scope.selectedHoldingsCopyAdd = function () {
1052 itemSvc.spawnHoldingsAdd([{
1053 id : $scope.args.copyId,
1054 'call_number.id' : $scope.args.cnId,
1055 'call_number.owning_lib' : $scope.args.cnOwningLib,
1056 'call_number.record.id' : $scope.args.recordId,
1057 barcode : $scope.args.copyBarcode
1061 $scope.selectedHoldingsVolCopyEdit = function () {
1062 itemSvc.spawnHoldingsEdit([{
1063 id : $scope.args.copyId,
1064 'call_number.id' : $scope.args.cnId,
1065 'call_number.owning_lib' : $scope.args.cnOwningLib,
1066 'call_number.record.id' : $scope.args.recordId,
1067 barcode : $scope.args.copyBarcode
1070 $scope.selectedHoldingsVolEdit = function () {
1071 itemSvc.spawnHoldingsEdit([{
1072 id : $scope.args.copyId,
1073 'call_number.id' : $scope.args.cnId,
1074 'call_number.owning_lib' : $scope.args.cnOwningLib,
1075 'call_number.record.id' : $scope.args.recordId,
1076 barcode : $scope.args.copyBarcode
1079 $scope.selectedHoldingsCopyEdit = function () {
1080 itemSvc.spawnHoldingsEdit([{
1081 id : $scope.args.copyId,
1082 'call_number.id' : $scope.args.cnId,
1083 'call_number.owning_lib' : $scope.args.cnOwningLib,
1084 'call_number.record.id' : $scope.args.recordId,
1085 barcode : $scope.args.copyBarcode
1089 $scope.replaceBarcodes = function() {
1090 itemSvc.replaceBarcodes([{
1091 id : $scope.args.copyId,
1092 barcode : $scope.args.copyBarcode
1096 $scope.changeItemOwningLib = function() {
1097 itemSvc.changeItemOwningLib([{
1098 id : $scope.args.copyId,
1099 'call_number.id' : $scope.args.cnId,
1100 'call_number.owning_lib' : $scope.args.cnOwningLib,
1101 'call_number.record.id' : $scope.args.recordId,
1102 'call_number.label' : $scope.args.cnLabel,
1103 'call_number.label_class' : $scope.args.cnLabelClass,
1104 'call_number.prefix.id' : $scope.args.cnPrefixId,
1105 'call_number.suffix.id' : $scope.args.cnSuffixId,
1106 barcode : $scope.args.copyBarcode
1110 $scope.transferItems = function (){
1111 itemSvc.transferItems([{
1112 id : $scope.args.copyId,
1113 barcode : $scope.args.copyBarcode
1120 * List view - grid stuff
1122 .controller('ListCtrl',
1123 ['$scope','$q','$routeParams','$location','$timeout','$window','egCore','egGridDataProvider','itemSvc','egUser','$uibModal','egCirc','egConfirmDialog',
1124 function($scope , $q , $routeParams , $location , $timeout , $window , egCore , egGridDataProvider , itemSvc , egUser , $uibModal , egCirc , egConfirmDialog) {
1126 var cp_list = $routeParams.idList;
1128 copyId = cp_list.split(',');
1131 $scope.context.page = 'list';
1134 var provider = egGridDataProvider.instance();
1135 provider.get = function(offset, count) {
1139 $scope.gridDataProvider = egGridDataProvider.instance({
1140 get : function(offset, count) {
1141 //return provider.arrayNotifier(itemSvc.copies, offset, count);
1142 return this.arrayNotifier(itemSvc.copies, offset, count);
1146 // If a copy was just displayed in the detail view, ensure it's
1147 // focused in the list view.
1148 var selected = false;
1149 var copyGrid = $scope.gridControls = {
1150 itemRetrieved : function(item) {
1151 if (selected || !itemSvc.copy) return;
1152 if (itemSvc.copy.id() == item.id) {
1153 copyGrid.selectItems([item.index]);
1159 $scope.$watch('barcodesFromFile', function(newVal, oldVal) {
1160 if (newVal && newVal != oldVal) {
1161 $scope.args.barcode = '';
1164 angular.forEach(newVal.split(/\n/), function(line) {
1166 // scrub any trailing spaces or commas from the barcode
1167 line = line.replace(/(.*?)($|\s.*|,.*)/,'$1');
1168 barcodes.push(line);
1171 if (barcodes.length > 0) {
1173 angular.forEach(barcodes, function (b) {
1174 promises.push(itemSvc.fetch(b));
1177 $q.all(promises).then(
1180 copyGrid.selectItems([itemSvc.copies[0].index]);
1187 $scope.context.search = function(args) {
1188 if (!args.barcode) return;
1189 $scope.context.itemNotFound = false;
1190 itemSvc.fetch(args.barcode).then(function(res) {
1193 copyGrid.selectItems([res.index]);
1194 $scope.args.barcode = '';
1196 $scope.context.itemNotFound = true;
1197 egCore.audio.play('warning.item_status.itemNotFound');
1199 $scope.context.selectBarcode = true;
1203 var add_barcode_to_list = function (b) {
1204 //console.log('listCtrl: add_barcode_to_list',b);
1205 $scope.context.search({barcode:b});
1207 itemSvc.add_barcode_to_list = add_barcode_to_list;
1209 $scope.context.toggleDisplay = function() {
1210 var item = copyGrid.selectedItems()[0];
1212 $location.path('/cat/item/' + item.id);
1215 $scope.context.show_triggered_events = function() {
1216 var item = copyGrid.selectedItems()[0];
1218 $location.path('/cat/item/' + item.id + '/triggered_events');
1221 function gatherSelectedRecordIds () {
1224 copyGrid.selectedItems(),
1226 if (rid_list.indexOf(item['call_number.record.id']) == -1)
1227 rid_list.push(item['call_number.record.id'])
1233 function gatherSelectedVolumeIds (rid) {
1234 var cn_id_list = [];
1236 copyGrid.selectedItems(),
1238 if (rid && item['call_number.record.id'] != rid) return;
1239 if (cn_id_list.indexOf(item['call_number.id']) == -1)
1240 cn_id_list.push(item['call_number.id'])
1246 function gatherSelectedHoldingsIds (rid) {
1247 var cp_id_list = [];
1249 copyGrid.selectedItems(),
1251 if (rid && item['call_number.record.id'] != rid) return;
1252 cp_id_list.push(item.id)
1258 $scope.add_copies_to_bucket = function() {
1259 var copy_list = gatherSelectedHoldingsIds();
1260 itemSvc.add_copies_to_bucket(copy_list);
1263 $scope.need_one_selected = function() {
1264 var items = $scope.gridControls.selectedItems();
1265 if (items.length == 1) return false;
1269 $scope.make_copies_bookable = function() {
1270 itemSvc.make_copies_bookable(copyGrid.selectedItems());
1273 $scope.book_copies_now = function() {
1274 itemSvc.book_copies_now(copyGrid.selectedItems());
1277 $scope.requestItems = function() {
1278 var copy_list = gatherSelectedHoldingsIds();
1279 itemSvc.requestItems(copy_list);
1282 $scope.replaceBarcodes = function() {
1283 itemSvc.replaceBarcodes(copyGrid.selectedItems());
1286 $scope.attach_to_peer_bib = function() {
1287 itemSvc.attach_to_peer_bib(copyGrid.selectedItems());
1290 $scope.selectedHoldingsCopyDelete = function () {
1291 itemSvc.selectedHoldingsCopyDelete(copyGrid.selectedItems());
1294 $scope.selectedHoldingsItemStatusTgrEvt= function() {
1295 var item = copyGrid.selectedItems()[0];
1297 $location.path('/cat/item/' + item.id + '/triggered_events');
1300 $scope.selectedHoldingsItemStatusHolds= function() {
1301 var item = copyGrid.selectedItems()[0];
1303 $location.path('/cat/item/' + item.id + '/holds');
1306 $scope.cancel_transit = function () {
1307 itemSvc.cancel_transit(copyGrid.selectedItems());
1310 $scope.selectedHoldingsDamaged = function () {
1311 itemSvc.selectedHoldingsDamaged(copyGrid.selectedItems());
1314 $scope.selectedHoldingsMissing = function () {
1315 itemSvc.selectedHoldingsMissing(copyGrid.selectedItems());
1318 $scope.checkin = function () {
1319 itemSvc.checkin(copyGrid.selectedItems());
1322 $scope.renew = function () {
1323 itemSvc.renew(copyGrid.selectedItems());
1326 $scope.selectedHoldingsVolCopyAdd = function () {
1327 itemSvc.spawnHoldingsAdd(copyGrid.selectedItems(),true,false);
1329 $scope.selectedHoldingsCopyAdd = function () {
1330 itemSvc.spawnHoldingsAdd(copyGrid.selectedItems(),false,true);
1333 $scope.showBibHolds = function () {
1334 angular.forEach(gatherSelectedRecordIds(), function (r) {
1335 var url = egCore.env.basePath + 'cat/catalog/record/' + r + '/holds';
1336 $timeout(function() { $window.open(url, '_blank') });
1340 $scope.selectedHoldingsVolCopyEdit = function () {
1341 itemSvc.spawnHoldingsEdit(copyGrid.selectedItems(),false,false);
1343 $scope.selectedHoldingsVolEdit = function () {
1344 itemSvc.spawnHoldingsEdit(copyGrid.selectedItems(),false,true);
1346 $scope.selectedHoldingsCopyEdit = function () {
1347 itemSvc.spawnHoldingsEdit(copyGrid.selectedItems(),true,false);
1350 $scope.changeItemOwningLib = function() {
1351 itemSvc.changeItemOwningLib(copyGrid.selectedItems());
1354 $scope.transferItems = function (){
1355 itemSvc.transferItems(copyGrid.selectedItems());
1358 $scope.print_labels = function() {
1361 'open-ils.actor.anon_cache.set_value',
1362 null, 'print-labels-these-copies', {
1363 copies : gatherSelectedHoldingsIds()
1365 ).then(function(key) {
1367 var url = egCore.env.basePath + 'cat/printlabels/' + key;
1368 $timeout(function() { $window.open(url, '_blank') });
1370 alert('Could not create anonymous cache key!');
1375 $scope.print_list = function() {
1376 var print_data = { copies : copyGrid.allItems() };
1378 if (print_data.copies.length == 0) return $q.when();
1380 return egCore.print.print({
1381 template : 'item_status',
1386 if (copyId.length > 0) {
1387 itemSvc.fetch(null,copyId).then(
1397 * Detail view -- shows one copy
1399 .controller('ViewCtrl',
1400 ['$scope','$q','$location','$routeParams','$timeout','$window','egCore','itemSvc','egBilling',
1401 function($scope , $q , $location , $routeParams , $timeout , $window , egCore , itemSvc , egBilling) {
1402 var copyId = $routeParams.id;
1403 $scope.args.copyId = copyId;
1404 $scope.tab = $routeParams.tab || 'summary';
1405 $scope.context.page = 'detail';
1406 $scope.summaryRecord = null;
1408 $scope.edit = false;
1409 if ($scope.tab == 'edit') {
1410 $scope.tab = 'summary';
1415 // use the cached record info
1417 $scope.recordId = itemSvc.copy.call_number().record().id();
1418 $scope.args.recordId = $scope.recordId;
1419 $scope.args.cnId = itemSvc.copy.call_number().id();
1420 $scope.args.cnOwningLib = itemSvc.copy.call_number().owning_lib();
1421 $scope.args.cnLabel = itemSvc.copy.call_number().label();
1422 $scope.args.cnLabelClass = itemSvc.copy.call_number().label_class();
1423 $scope.args.cnPrefixId = itemSvc.copy.call_number().prefix().id();
1424 $scope.args.cnSuffixId = itemSvc.copy.call_number().suffix().id();
1425 $scope.args.copyBarcode = itemSvc.copy.barcode();
1428 function loadCopy(barcode) {
1429 $scope.context.itemNotFound = false;
1431 // Avoid re-fetching the same copy while jumping tabs.
1432 // In addition to being quicker, this helps to avoid flickering
1433 // of the top panel which is always visible in the detail view.
1435 // 'barcode' represents the loading of a new item - refetch it
1436 // regardless of whether it matches the current item.
1437 if (!barcode && itemSvc.copy && itemSvc.copy.id() == copyId) {
1438 $scope.copy = itemSvc.copy;
1439 $scope.recordId = itemSvc.copy.call_number().record().id();
1440 $scope.args.recordId = $scope.recordId;
1441 $scope.args.cnId = itemSvc.copy.call_number().id();
1442 $scope.args.cnOwningLib = itemSvc.copy.call_number().owning_lib();
1443 $scope.args.cnLabel = itemSvc.copy.call_number().label();
1444 $scope.args.cnLabelClass = itemSvc.copy.call_number().label_class();
1445 $scope.args.cnPrefixId = itemSvc.copy.call_number().prefix().id();
1446 $scope.args.cnSuffixId = itemSvc.copy.call_number().suffix().id();
1447 $scope.args.copyBarcode = itemSvc.copy.barcode();
1452 delete itemSvc.copy;
1454 var deferred = $q.defer();
1455 itemSvc.fetch(barcode, copyId, true).then(function(res) {
1456 $scope.context.selectBarcode = true;
1460 $scope.context.itemNotFound = true;
1461 egCore.audio.play('warning.item_status.itemNotFound');
1462 deferred.reject(); // avoid propagation of data fetch calls
1466 var copy = res.copy;
1467 itemSvc.copy = copy;
1471 $scope.recordId = copy.call_number().record().id();
1472 $scope.args.recordId = $scope.recordId;
1473 $scope.args.cnId = itemSvc.copy.call_number().id();
1474 $scope.args.cnOwningLib = itemSvc.copy.call_number().owning_lib();
1475 $scope.args.cnLabel = itemSvc.copy.call_number().label();
1476 $scope.args.cnLabelClass = itemSvc.copy.call_number().label_class();
1477 $scope.args.cnPrefixId = itemSvc.copy.call_number().prefix().id();
1478 $scope.args.cnSuffixId = itemSvc.copy.call_number().suffix().id();
1479 $scope.args.copyBarcode = copy.barcode();
1480 $scope.args.barcode = '';
1482 // locally flesh org units
1483 copy.circ_lib(egCore.org.get(copy.circ_lib()));
1484 copy.call_number().owning_lib(
1485 egCore.org.get(copy.call_number().owning_lib()));
1487 var r = copy.call_number().record();
1488 if (r.owner()) r.owner(egCore.org.get(r.owner()));
1490 // make boolean for auto-magic true/false display
1492 ['ref','opac_visible','holdable','circulate'],
1493 function(field) { copy[field](Boolean(copy[field]() == 't')) }
1496 // finally, if this is a different copy, redirect.
1497 // Note that we flesh first since the copy we just
1498 // fetched will be used after the redirect.
1499 if (copyId && copyId != copy.id()) {
1500 // if a new barcode is scanned in the detail view,
1501 // update the url to match the ID of the new copy
1502 $location.path('/cat/item/' + copy.id() + '/' + $scope.tab);
1503 deferred.reject(); // avoid propagation of data fetch calls
1511 return deferred.promise;
1514 // if loadPrev load the two most recent circulations
1515 function loadCurrentCirc(loadPrev) {
1517 delete $scope.circ_summary;
1518 delete $scope.prev_circ_summary;
1519 delete $scope.prev_circ_usr;
1520 if (!copyId) return;
1522 egCore.pcrud.search('aacs',
1523 {target_copy : copyId},
1529 'checkin_workstation',
1532 'recurring_fine_rule'
1536 order_by : {aacs : 'xact_start desc'},
1540 ).then(null, null, function(circ) {
1543 // load the chain for this circ
1546 'open-ils.circ.renewal_chain.retrieve_by_circ.summary',
1547 egCore.auth.token(), $scope.circ.id()
1548 ).then(function(summary) {
1549 $scope.circ_summary = summary;
1552 if (!loadPrev) return;
1554 // load the chain for the previous circ, plus the user
1557 'open-ils.circ.prev_renewal_chain.retrieve_by_circ.summary',
1558 egCore.auth.token(), $scope.circ.id()
1560 ).then(null, null, function(summary) {
1561 $scope.prev_circ_summary = summary.summary;
1563 if (summary.usr) { // aged circs have no 'usr'.
1564 egCore.pcrud.retrieve('au', summary.usr,
1565 {flesh : 1, flesh_fields : {au : ['card']}})
1567 .then(function(user) { $scope.prev_circ_usr = user });
1574 function fetchMaxCircHistory() {
1575 if (maxHistory) return $q.when(maxHistory);
1576 return egCore.org.settings(
1577 'circ.item_checkout_history.max')
1578 .then(function(set) {
1579 maxHistory = set['circ.item_checkout_history.max'] || 4;
1584 $scope.addBilling = function(circ) {
1585 egBilling.showBillDialog({
1586 xact_id : circ.id(),
1591 $scope.retrieveAllPatrons = function() {
1592 var users = new Set();
1593 angular.forEach($scope.circ_list.map(function(circ) { return circ.usr(); }),function(usr) {
1594 // aged circs have no 'usr'.
1595 if (usr) users.add(usr);
1597 users.forEach(function(usr) {
1598 $timeout(function() {
1599 var url = $location.absUrl().replace(
1601 '/circ/patron/' + usr.id() + '/checkout');
1602 $window.open(url, '_blank')
1607 function loadCircHistory() {
1608 $scope.circ_list = [];
1611 itemSvc.copy.call_number().id() == -1 ?
1612 itemSvc.copy.circ_lib().id() :
1613 itemSvc.copy.call_number().owning_lib().id()
1615 // there is an extra layer of permissibility over circ
1617 egCore.perm.hasPermAt('VIEW_COPY_CHECKOUT_HISTORY', true)
1618 .then(function(orgIds) {
1620 if (orgIds.indexOf(copy_org) == -1) {
1621 console.log('User is not allowed to view circ history');
1625 return fetchMaxCircHistory();
1627 }).then(function(count) {
1629 egCore.pcrud.search('aacs',
1630 {target_copy : copyId},
1636 'checkin_workstation',
1637 'recurring_fine_rule'
1641 order_by : {aacs : 'xact_start desc'},
1645 ).then(null, null, function(circ) {
1647 // flesh circ_lib locally
1648 circ.circ_lib(egCore.org.get(circ.circ_lib()));
1649 circ.checkin_lib(egCore.org.get(circ.checkin_lib()));
1650 $scope.circ_list.push(circ);
1656 function loadCircCounts() {
1658 delete $scope.circ_counts;
1659 $scope.total_circs = 0;
1660 $scope.total_circs_this_year = 0;
1661 $scope.total_circs_prev_year = 0;
1662 if (!copyId) return;
1664 egCore.pcrud.search('circbyyr',
1665 {copy : copyId}, null, {atomic : true})
1667 .then(function(counts) {
1668 $scope.circ_counts = counts;
1670 angular.forEach(counts, function(count) {
1671 $scope.total_circs += Number(count.count());
1674 var this_year = counts.filter(function(c) {
1675 return c.year() == new Date().getFullYear();
1678 $scope.total_circs_this_year =
1679 this_year.length ? this_year[0].count() : 0;
1681 var prev_year = counts.filter(function(c) {
1682 return c.year() == new Date().getFullYear() - 1;
1685 $scope.total_circs_prev_year =
1686 prev_year.length ? prev_year[0].count() : 0;
1691 function loadHolds() {
1693 if (!copyId) return;
1695 egCore.pcrud.search('ahr',
1696 { current_copy : copyId,
1698 fulfillment_time : null,
1699 capture_time : {'<>' : null}
1703 ahr : ['requestor', 'usr'],
1707 ).then(null, null, function(hold) {
1709 hold.pickup_lib(egCore.org.get(hold.pickup_lib()));
1710 if (hold.current_shelf_lib()) {
1711 hold.current_shelf_lib(
1712 egCore.org.get(hold.current_shelf_lib()));
1714 hold.behind_desk(Boolean(hold.behind_desk() == 't'));
1718 function loadTransits() {
1719 delete $scope.transit;
1720 delete $scope.hold_transit;
1721 if (!copyId) return;
1723 egCore.pcrud.search('atc',
1724 {target_copy : copyId},
1725 {order_by : {atc : 'source_send_time DESC'}}
1727 ).then(null, null, function(transit) {
1728 $scope.transit = transit;
1729 transit.source(egCore.org.get(transit.source()));
1730 transit.dest(egCore.org.get(transit.dest()));
1735 // we don't need all data on all tabs, so fetch what's needed when needed.
1736 function loadTabData() {
1737 switch($scope.tab) {
1744 loadCurrentCirc(true);
1756 case 'triggered_events':
1757 var url = $location.absUrl().replace(/\/staff.*/, '/actor/user/event_log');
1758 url += '?copy_id=' + encodeURIComponent(copyId);
1759 $scope.triggered_events_url = url;
1766 'open-ils.actor.anon_cache.set_value',
1767 null, 'edit-these-copies', {
1768 record_id: $scope.recordId,
1773 ).then(function(key) {
1775 var url = egCore.env.basePath + 'cat/volcopy/' + key;
1776 $window.location.href = url;
1778 alert('Could not create anonymous cache key!');
1786 $scope.context.toggleDisplay = function() {
1787 $location.path('/cat/item/search');
1790 // handle the barcode scan box, which will replace our current copy
1791 $scope.context.search = function(args) {
1792 loadCopy(args.barcode).then(loadTabData);
1795 $scope.context.show_triggered_events = function() {
1796 $location.path('/cat/item/' + copyId + '/triggered_events');
1799 loadCopy().then(loadTabData);