5 angular.module('egItemStatus',
6 ['ngRoute', 'ui.bootstrap', 'egCoreMod', 'egUiMod', 'egGridMod'])
8 .config(function($routeProvider, $locationProvider, $compileProvider) {
9 $locationProvider.html5Mode(true);
10 $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|blob):/); // grid export
12 var resolver = {delay : function(egStartup) {return egStartup.go()}};
14 // search page shows the list view by default
15 $routeProvider.when('/cat/item/search', {
16 templateUrl: './cat/item/t_list',
17 controller: 'ListCtrl',
21 $routeProvider.when('/cat/item/:id', {
22 templateUrl: './cat/item/t_view',
23 controller: 'ViewCtrl',
27 $routeProvider.when('/cat/item/:id/:tab', {
28 templateUrl: './cat/item/t_view',
29 controller: 'ViewCtrl',
33 // default page / bucket view
34 $routeProvider.otherwise({redirectTo : '/cat/item/search'});
42 copies : [], // copy barcode search results
43 index : 0 // search grid index
49 acp : ['call_number','location','status','location'],
50 acn : ['record','prefix','suffix'],
51 bre : ['simple_record','creator','editor']
54 // avoid fleshing MARC on the bre
55 // note: don't add simple_record.. not sure why
56 bre : ['id','tcn_value','creator','editor'],
60 // resolved with the last received copy
61 service.fetch = function(barcode, id, noListDupes) {
65 promise = egCore.pcrud.search('acp',
66 {barcode : barcode, deleted : 'f'}, service.flesh);
68 promise = egCore.pcrud.retrieve('acp', id, service.flesh);
73 function() {return lastRes},
76 // notify reads the stream of copies, one at a time.
81 // use the existing copy if possible
82 flatCopy = service.copies.filter(
83 function(c) {return c.id == copy.id()})[0];
87 flatCopy = egCore.idl.toHash(copy, true);
88 flatCopy.index = service.index++;
89 service.copies.unshift(flatCopy);
94 index : flatCopy.index
104 * Search bar along the top of the page.
105 * Parent scope for list and detail views
107 .controller('SearchCtrl',
108 ['$scope','$location','egCore','egGridDataProvider','itemSvc',
109 function($scope , $location , egCore , egGridDataProvider , itemSvc) {
110 $scope.args = {}; // search args
112 // sub-scopes (search / detail-view) apply their version
113 // of retrieval function to $scope.context.search
114 // and display toggling via $scope.context.toggleDisplay
119 $scope.toggleView = function($event) {
120 $scope.context.toggleDisplay();
121 $event.preventDefault(); // avoid form submission
126 * List view - grid stuff
128 .controller('ListCtrl',
129 ['$scope','$q','$location','$timeout','egCore','egGridDataProvider','itemSvc',
130 function($scope , $q , $location , $timeout , egCore , egGridDataProvider , itemSvc) {
131 $scope.context.page = 'list';
134 var provider = egGridDataProvider.instance();
135 provider.get = function(offset, count) {
139 $scope.gridDataProvider = egGridDataProvider.instance({
140 get : function(offset, count) {
141 //return provider.arrayNotifier(itemSvc.copies, offset, count);
142 return this.arrayNotifier(itemSvc.copies, offset, count);
146 // If a copy was just displayed in the detail view, ensure it's
147 // focused in the list view.
148 var selected = false;
149 var copyGrid = $scope.gridControls = {
150 itemRetrieved : function(item) {
151 if (selected || !itemSvc.copy) return;
152 if (itemSvc.copy.id() == item.id) {
153 copyGrid.selectItems([item.index]);
159 $scope.$watch('barcodesFromFile', function(newVal, oldVal) {
160 if (newVal && newVal != oldVal) {
161 $scope.args.barcode = '';
164 angular.forEach(newVal.split(/\n/), function(line) {
166 // scrub any trailing spaces or commas from the barcode
167 line = line.replace(/(.*?)($|\s.*|,.*)/,'$1');
171 itemSvc.fetch(barcodes).then(
174 copyGrid.selectItems([itemSvc.copies[0].index]);
180 $scope.context.search = function(args) {
181 if (!args.barcode) return;
182 $scope.context.itemNotFound = false;
183 itemSvc.fetch(args.barcode).then(function(res) {
186 copyGrid.selectItems([res.index]);
187 $scope.args.barcode = '';
189 $scope.context.itemNotFound = true;
191 $scope.context.selectBarcode = true;
195 $scope.context.toggleDisplay = function() {
196 var item = copyGrid.selectedItems()[0];
198 $location.path('/cat/item/' + item.id);
201 $scope.context.show_triggered_events = function() {
202 var item = copyGrid.selectedItems()[0];
204 $location.path('/cat/item/' + item.id + '/triggered_events');
210 * Detail view -- shows one copy
212 .controller('ViewCtrl',
213 ['$scope','$q','$location','$routeParams','egCore','itemSvc','egBilling',
214 function($scope , $q , $location , $routeParams , egCore , itemSvc , egBilling) {
215 var copyId = $routeParams.id;
216 $scope.tab = $routeParams.tab || 'summary';
217 $scope.context.page = 'detail';
219 // use the cached record info
221 $scope.summaryRecord = itemSvc.copy.call_number().record();
223 function loadCopy(barcode) {
224 $scope.context.itemNotFound = false;
226 // Avoid re-fetching the same copy while jumping tabs.
227 // In addition to being quicker, this helps to avoid flickering
228 // of the top panel which is always visible in the detail view.
230 // 'barcode' represents the loading of a new item - refetch it
231 // regardless of whether it matches the current item.
232 if (!barcode && itemSvc.copy && itemSvc.copy.id() == copyId) {
233 $scope.copy = itemSvc.copy;
234 $scope.recordId = itemSvc.copy.call_number().record().id();
241 var deferred = $q.defer();
242 itemSvc.fetch(barcode, copyId, true).then(function(res) {
243 $scope.context.selectBarcode = true;
247 $scope.context.itemNotFound = true;
248 deferred.reject(); // avoid propagation of data fetch calls
257 $scope.recordId = copy.call_number().record().id();
258 $scope.summaryRecord = itemSvc.copy.call_number().record();
259 $scope.args.barcode = '';
261 // locally flesh org units
262 copy.circ_lib(egCore.org.get(copy.circ_lib()));
263 copy.call_number().owning_lib(
264 egCore.org.get(copy.call_number().owning_lib()));
266 var r = copy.call_number().record();
267 if (r.owner()) r.owner(egCore.org.get(r.owner()));
269 // make boolean for auto-magic true/false display
271 ['ref','opac_visible','holdable','floating','circulate'],
272 function(field) { copy[field](Boolean(copy[field]() == 't')) }
275 // finally, if this is a different copy, redirect.
276 // Note that we flesh first since the copy we just
277 // fetched will be used after the redirect.
278 if (copyId && copyId != copy.id()) {
279 // if a new barcode is scanned in the detail view,
280 // update the url to match the ID of the new copy
281 $location.path('/cat/item/' + copy.id() + '/' + $scope.tab);
282 deferred.reject(); // avoid propagation of data fetch calls
290 return deferred.promise;
293 // if loadPrev load the two most recent circulations
294 function loadCurrentCirc(loadPrev) {
296 delete $scope.circ_summary;
297 delete $scope.prev_circ_summary;
300 egCore.pcrud.search('circ',
301 {target_copy : copyId},
307 'checkin_workstation',
310 'recurring_fine_rule'
314 order_by : {circ : 'xact_start desc'},
318 ).then(null, null, function(circ) {
321 // load the chain for this circ
324 'open-ils.circ.renewal_chain.retrieve_by_circ.summary',
325 egCore.auth.token(), $scope.circ.id()
326 ).then(function(summary) {
327 $scope.circ_summary = summary.summary;
330 if (!loadPrev) return;
332 // load the chain for the previous circ, plus the user
335 'open-ils.circ.prev_renewal_chain.retrieve_by_circ.summary',
336 egCore.auth.token(), $scope.circ.id()
338 ).then(null, null, function(summary) {
339 $scope.prev_circ_summary = summary.summary;
341 egCore.pcrud.retrieve('au', summary.usr,
342 {flesh : 1, flesh_fields : {au : ['card']}})
344 .then(function(user) {
345 $scope.prev_circ_usr = user;
352 function fetchMaxCircHistory() {
353 if (maxHistory) return $q.when(maxHistory);
354 return egCore.org.settings(
355 'circ.item_checkout_history.max')
356 .then(function(set) {
357 maxHistory = set['circ.item_checkout_history.max'] || 4;
362 $scope.addBilling = function(circ) {
363 egBilling.showBillDialog({
369 function loadCircHistory() {
370 $scope.circ_list = [];
373 itemSvc.copy.call_number().id() == -1 ?
374 itemSvc.copy.circ_lib().id() :
375 itemSvc.copy.call_number().owning_lib().id()
377 // there is an extra layer of permissibility over circ
379 egCore.perm.hasPermAt('VIEW_COPY_CHECKOUT_HISTORY', true)
380 .then(function(orgIds) {
382 if (orgIds.indexOf(copy_org) == -1) {
383 console.log('User is not allowed to view circ history');
387 return fetchMaxCircHistory();
389 }).then(function(count) {
391 egCore.pcrud.search('circ',
392 {target_copy : copyId},
398 'checkin_workstation',
399 'recurring_fine_rule'
403 order_by : {circ : 'xact_start desc'},
407 ).then(null, null, function(circ) {
409 // flesh circ_lib locally
410 circ.circ_lib(egCore.org.get(circ.circ_lib()));
411 circ.checkin_lib(egCore.org.get(circ.checkin_lib()));
412 $scope.circ_list.push(circ);
418 function loadCircCounts() {
420 delete $scope.circ_counts;
421 $scope.total_circs = 0;
422 $scope.total_circs_this_year = 0;
423 $scope.total_circs_prev_year = 0;
426 egCore.pcrud.search('circbyyr',
427 {copy : copyId}, null, {atomic : true})
429 .then(function(counts) {
430 $scope.circ_counts = counts;
432 angular.forEach(counts, function(count) {
433 $scope.total_circs += Number(count.count());
436 var this_year = counts.filter(function(c) {
437 return c.year() == new Date().getFullYear();
440 $scope.total_circs_this_year =
441 this_year.length ? this_year[0].count() : 0;
443 var prev_year = counts.filter(function(c) {
444 return c.year() == new Date().getFullYear() - 1;
447 $scope.total_circs_prev_year =
448 prev_year.length ? prev_year[0].count() : 0;
453 function loadHolds() {
457 egCore.pcrud.search('ahr',
458 { current_copy : copyId,
460 fulfillment_time : null,
461 capture_time : {'<>' : null}
465 ahr : ['requestor', 'usr'],
469 ).then(null, null, function(hold) {
471 hold.pickup_lib(egCore.org.get(hold.pickup_lib()));
472 if (hold.current_shelf_lib()) {
473 hold.current_shelf_lib(
474 egCore.org.get(hold.current_shelf_lib()));
476 hold.behind_desk(Boolean(hold.behind_desk() == 't'));
480 function loadTransits() {
481 delete $scope.transit;
482 delete $scope.hold_transit;
485 egCore.pcrud.search('atc',
486 {target_copy : copyId},
487 {order_by : {atc : 'source_send_time DESC'}}
489 ).then(null, null, function(transit) {
490 $scope.transit = transit;
491 transit.source(egCore.org.get(transit.source()));
492 transit.dest(egCore.org.get(transit.dest()));
497 // we don't need all data on all tabs, so fetch what's needed when needed.
498 function loadTabData() {
506 loadCurrentCirc(true);
518 case 'triggered_events':
519 var url = $location.absUrl().replace(/\/staff.*/, '/actor/user/event_log');
520 url += '?copy_id=' + encodeURIComponent(copyId);
521 $scope.triggered_events_url = url;
526 $scope.context.toggleDisplay = function() {
527 $location.path('/cat/item/search');
530 // handle the barcode scan box, which will replace our current copy
531 $scope.context.search = function(args) {
532 loadCopy(args.barcode).then(loadTabData);
535 $scope.context.show_triggered_events = function() {
536 $location.path('/cat/item/' + copyId + '/triggered_events');
539 loadCopy().then(loadTabData);