angular.module('egPatronApp')
.controller('PatronItemsOutCtrl',
- ['$scope','$q','$routeParams','$timeout','egCore','egUser','patronSvc','$location',
- 'egGridDataProvider','$uibModal','egCirc','egConfirmDialog','egBilling','$window',
-function($scope, $q, $routeParams, $timeout, egCore , egUser, patronSvc , $location,
- egGridDataProvider , $uibModal , egCirc , egConfirmDialog , egBilling , $window) {
+ ['$scope','$q','$routeParams','$timeout','egCore','egUser','patronSvc',
+ '$location','egGridDataProvider','$uibModal','egCirc','egConfirmDialog',
+ 'egProgressDialog','egBilling','$window','egBibDisplay',
+function($scope , $q , $routeParams , $timeout , egCore , egUser , patronSvc ,
+ $location , egGridDataProvider , $uibModal , egCirc , egConfirmDialog ,
+ egProgressDialog , egBilling , $window , egBibDisplay) {
// list of noncatatloged circulations. Define before initTab to
// avoid any possibility of race condition, since they are loaded
// list of alt circs (lost, etc.) and/or check-in with fines circs
$scope.alt_list = [];
+
+ egCore.org.settings([
+ 'ui.circ.suppress_checkin_popups' // add other settings as needed
+ ]).then(function(set) {
+ $scope.suppress_popups = set['ui.circ.suppress_checkin_popups'];
+ });
// these are fetched during startup (i.e. .configure())
// By default, show lost/lo/cr items in the alt list
}
$scope.items_out_display = 'main';
- $scope.show_main_list = function() {
+ $scope.show_main_list = function(refresh_grid) {
// don't need a full reset_page() to swap tabs
$scope.items_out_display = 'main';
patronSvc.items_out = [];
- provider.refresh();
+ // only refresh the grid when navigating from a tab that
+ // shares the same grid.
+ if (refresh_grid) provider.refresh();
}
- $scope.show_alt_list = function() {
+ $scope.show_alt_list = function(refresh_grid) {
// don't need a full reset_page() to swap tabs
$scope.items_out_display = 'alt';
patronSvc.items_out = [];
- provider.refresh();
+ // only refresh the grid when navigating from a tab that
+ // shares the same grid.
+ if (refresh_grid) provider.refresh();
}
$scope.show_noncat_list = function() {
// don't need a full reset_page() to swap tabs
$scope.items_out_display = 'noncat';
patronSvc.items_out = [];
- provider.refresh();
+ // Grid refresh is not necessary because switching to the
+ // noncat_list always involves instantiating a new grid.
+ }
+
+ $scope.colorizeItemsOutList = {
+ apply: function(item) {
+ var duedate = item.due_date();
+ if (duedate && duedate < new Date().toISOString()) {
+ return 'overdue-row';
+ }
+ }
}
// Reload the user to pick up changes in items out, fines, etc.
patronSvc.items_out = [];
$scope.main_list = [];
$scope.alt_list = [];
- provider.refresh()
+ $timeout(provider.refresh); // allow scope changes to propagate
}
var provider = egGridDataProvider.instance({});
$scope.gridDataProvider = provider;
function fetch_circs(id_list, offset, count) {
- if (!id_list.length) return $q.when();
+ if (!id_list.length || id_list.length < offset + 1) return $q.when();
+
+ var deferred = $q.defer();
+ var rendered = 0;
+
+ egProgressDialog.open();
// fetch the lot of circs and stream the results back via notify
- return egCore.pcrud.search('circ', {id : id_list},
+ egCore.pcrud.search('circ', {id : id_list},
{ flesh : 4,
flesh_fields : {
circ : ['target_copy', 'workstation', 'checkin_workstation'],
- acp : ['call_number', 'holds_count', 'status'],
- acn : ['record'],
- bre : ['simple_record']
+ acp : ['call_number', 'holds_count', 'status', 'circ_lib', 'location', 'floating', 'age_protect', 'parts'],
+ acpm : ['part'],
+ acn : ['record', 'owning_lib', 'prefix', 'suffix'],
+ bre : ['wide_display_entry']
},
// avoid fetching the MARC blob by specifying which
// fields on the bre to select. More may be needed.
// note that fleshed fields are explicitly selected.
select : { bre : ['id'] },
+ // TODO: LP#1697954 Fetch all circs on grid render
+ // to support client-side sorting. Migrate to server-side
+ // sorting to avoid the need for fetching all items.
//limit : count,
//offset : offset,
// we need an order-by to support paging
}).then(null, null, function(circ) {
circ.circ_lib(egCore.org.get(circ.circ_lib())); // local fleshing
+ // Translate bib display field JSON blobs to JS.
+ // Collapse multi/array fields down to comma-separated strings.
+ egBibDisplay.mwdeJSONToJS(
+ circ.target_copy().call_number().record().wide_display_entry(), true);
+
if (circ.target_copy().call_number().id() == -1) {
// dummy-up a record for precat items
- circ.target_copy().call_number().record().simple_record({
+ circ.target_copy().call_number().record().wide_display_entry({
title : function() {return circ.target_copy().dummy_title()},
author : function() {return circ.target_copy().dummy_author()},
isbn : function() {return circ.target_copy().dummy_isbn()}
})
}
+ circ._parts = circ.target_copy().parts().map(function(part) {
+ return part.label()
+ }).join(',');
+
+ patronSvc.items_out.push(circ);
+
+ }).then(function() {
+
+ var circIds = patronSvc.items_out.map(function(circ) { return circ.id() });
+
+ egCore.net.request(
+ 'open-ils.actor',
+ 'open-ils.actor.user.itemsout.notices',
+ egCore.auth.token(), circIds
+
+ ).then(deferred.resolve, null, function(notice) {
- patronSvc.items_out.push(circ); // toss it into the cache
- return circ;
+ var circ = patronSvc.items_out.filter(
+ function(circ) {return circ.id() == notice.circ_id})[0];
+
+ if (notice.numNotices) {
+ circ.action_trigger_event_count = notice.numNotices;
+ circ.action_trigger_latest_event_date = notice.lastDt;
+ }
+
+ if (rendered++ >= offset && rendered <= count) {
+ egProgressDialog.close();
+ deferred.notify(circ);
+ };
+ });
});
+
+ return deferred.promise;
}
function fetch_noncat_circs(id_list, offset, count) {
if (!id_list.length) return $q.when();
- return egCore.pcrud.search('ancc', {id : id_list},
+ var deferred = $q.defer();
+ var rendered = 0;
+
+ egCore.pcrud.search('ancc', {id : id_list},
{ flesh : 1,
flesh_fields : {ancc : ['item_type','staff']},
+ // TODO: LP#1697954 Fetch all circs on grid render
+ // to support client-side sorting. Migrate to server-side
+ // sorting to avoid the need for fetching all items.
//limit : count,
//offset : offset,
// we need an order-by to support paging
order_by : {circ : ['circ_time']}
- }).then(null, null, function(noncat_circ) {
+ }).then(deferred.resolve, null, function(noncat_circ) {
// calculate the virtual due date from the item type duration
var seconds = egCore.date.intervalToSeconds(
noncat_circ.circ_lib(egCore.org.get(noncat_circ.circ_lib()));
patronSvc.items_out.push(noncat_circ); // cache it
- return noncat_circ;
+
+ // We fetch all noncat circs for client-side sorting, but
+ // only notify the caller for the page of requested circs.
+ if (rendered++ >= offset && rendered <= count)
+ deferred.notify(noncat_circ);
});
+
+ return deferred.promise;
}
var id_list = $scope[$scope.items_out_display + '_list'];
// see if we have the requested range cached
+ // Note this items_out list is reset w/ each items-out tab change
if (patronSvc.items_out[offset]) {
return provider.arrayNotifier(
patronSvc.items_out, offset, count);
get_circ_ids().then(function() {
id_list = $scope[$scope.items_out_display + '_list'];
-
+ $scope.gridDataProvider.grid.totalCount = id_list.length;
// relay the notified circs back to the grid through our promise
fetch_circs(id_list, offset, count).then(
deferred.resolve, null, deferred.notify);
$uibModal.open({
templateUrl : './circ/patron/t_edit_due_date_dialog',
+ backdrop: 'static',
controller : [
'$scope','$uibModalInstance',
function($scope , $uibModalInstance) {
// Fire off the due-date updater for each circ.
// When all is done, close the dialog
$scope.ok = function(args) {
- // toISOString gives us Zulu time, so
- // adjust for that before truncating to date
- var adjust_date = new Date( $scope.args.due_date );
- adjust_date.setMinutes(
- $scope.args.due_date.getMinutes() - adjust_date.getTimezoneOffset()
- );
- var due = adjust_date.toISOString().replace(/T.*/,'');
+ var due = $scope.args.due_date.toISOString();
console.debug("applying due date of " + due);
+ egProgressDialog.open();
- var promises = [];
+ var promise = $q.when();
angular.forEach(items, function(circ) {
- promises.push(
- egCore.net.request(
+ promise = promise.then(function() {
+ return egCore.net.request(
'open-ils.circ',
'open-ils.circ.circulation.due_date.update',
egCore.auth.token(), circ.id(), due
// date from the modified circulation.
circ.due_date(new_circ.due_date());
})
- );
+ });
});
- $q.all(promises).then(function() {
+ promise.finally(function() {
+ egProgressDialog.close();
$uibModalInstance.close();
provider.refresh();
});
$scope.print_receipt = function(items) {
if (items.length == 0) return $q.when();
- var print_data = {circulations : []}
+ var print_data = {circulations : []};
+ var cusr = patronSvc.current;
- angular.forEach(patronSvc.items_out, function(circ) {
+ angular.forEach(items, function(circ) {
print_data.circulations.push({
circ : egCore.idl.toHash(circ),
copy : egCore.idl.toHash(circ.target_copy()),
call_number : egCore.idl.toHash(circ.target_copy().call_number()),
- title : circ.target_copy().call_number().record().simple_record().title(),
- author : circ.target_copy().call_number().record().simple_record().author(),
+ title : circ.target_copy().call_number().record().wide_display_entry().title(),
+ author : circ.target_copy().call_number().record().wide_display_entry().author()
})
});
+ print_data.patron = {
+ prefix : cusr.prefix(),
+ first_given_name : cusr.first_given_name(),
+ second_given_name : cusr.second_given_name(),
+ family_name : cusr.family_name(),
+ suffix : cusr.suffix(),
+ pref_prefix : cusr.pref_prefix(),
+ pref_first_given_name : cusr.pref_first_given_name(),
+ pref_second_given_name : cusr.pref_second_given_name(),
+ pref_family_name : cusr.pref_family_name(),
+ pref_suffix : cusr.pref_suffix(),
+ card : { barcode : cusr.card().barcode() },
+ money_summary : patronSvc.patron_stats.fines,
+ expire_date : cusr.expire_date(),
+ alias : cusr.alias(),
+ has_email : Boolean(patronSvc.current.email() && patronSvc.current.email().match(/.*@.*/)),
+ has_phone : Boolean(cusr.day_phone() || cusr.evening_phone() || cusr.other_phone()),
+ juvenile : cusr.juvenile()
+ };
+
return egCore.print.print({
context : 'default',
template : 'items_out',
});
}
+ function batch_action_with_flat_copies(items, action) {
+ if (!items.length) return;
+ var copies = items.map(function(circ)
+ { return egCore.idl.toHash(circ.target_copy()) });
+ action(copies).then(reset_page);
+ }
function batch_action_with_barcodes(items, action) {
if (!items.length) return;
var barcodes = items.map(function(circ)
{ return circ.target_copy().barcode() });
action(barcodes).then(reset_page);
}
+ $scope.mark_damaged = function(items) {
+ if (items.length == 0) return;
+
+ angular.forEach(items, function(circ) {
+ egCirc.mark_damaged({
+ id: circ.target_copy().id(),
+ barcode: circ.target_copy().barcode(),
+ circ_lib: circ.target_copy().circ_lib().id()
+ }).then(() => $timeout(reset_page,1000)) // reset after each, because rejecting one stops the $q.all() chain
+ });
+ }
+ $scope.mark_missing = function(items) {
+ batch_action_with_flat_copies(items, egCirc.mark_missing);
+ }
$scope.mark_lost = function(items) {
batch_action_with_barcodes(items, egCirc.mark_lost);
}
$scope.show_triggered_events = function(items) {
var focus = items.length == 1;
angular.forEach(items, function(item) {
- var url = egCore.env.basePath +
- '/cat/item/' +
- item.target_copy().id() +
- '/triggered_events';
+ var url = '/eg2/staff/circ/item/event-log/' +
+ item.target_copy().id();
$timeout(function() { var x = $window.open(url, '_blank'); if (focus) x.focus() });
+
});
}
return egConfirmDialog.open(msg, barcodes.join(' '), {}).result
.then(function() {
+ window.oils_cancel_batch = false;
+ window.oils_inside_batch = true;
+ function batch_cleanup() {
+ if (window.oils_inside_batch && window.oils_op_change_within_batch) {
+ window.oils_op_change_undo_func();
+ }
+ window.oils_inside_batch = false;
+ window.oils_op_change_within_batch = false;
+ reset_page();
+ }
function do_one() {
var bc = barcodes.pop();
- if (!bc) { reset_page(); return }
+ if (!bc) {
+ batch_cleanup();
+ return;
+ }
+ if (window.oils_op_change_within_batch) {
+ window.oils_op_change_toast_func();
+ }
// finally -> continue even when one fails
- egCirc.renew({copy_barcode : bc}).finally(do_one);
+ egCirc.renew({copy_barcode : bc}).finally(function() {
+ if (!window.oils_cancel_batch) {
+ do_one();
+ } else {
+ console.log('batch cancelled');
+ batch_cleanup();
+ return;
+ }
+ });
}
do_one();
});
{ return circ.target_copy().barcode() });
return $uibModal.open({
- templateUrl : './circ/patron/t_edit_due_date_dialog',
templateUrl : './circ/patron/t_renew_with_date_dialog',
+ backdrop: 'static',
controller : [
'$scope','$uibModalInstance',
function($scope , $uibModalInstance) {
+ var now = new Date();
+ $scope.outOfRange = false;
+ $scope.minDate = new Date(now);
$scope.args = {
barcodes : barcodes,
- date : new Date()
+ date : new Date(now)
}
$scope.cancel = function() {$uibModalInstance.dismiss()}
$scope.checkin = function(items) {
if (!items.length) return;
- var barcodes = items.map(function(circ)
- { return circ.target_copy().barcode() });
-
- return egConfirmDialog.open(
- egCore.strings.CHECK_IN_CONFIRM, barcodes.join(' '), {
-
- }).result.then(function() {
- function do_one() {
- if (bc = barcodes.pop()) {
- egCirc.checkin({copy_barcode : bc})
- .finally(do_one);
- } else {
- reset_page();
- }
+ var copies = items.map(function(circ) { return circ.target_copy() });
+ var barcodes = copies.map(function(copy) { return copy.barcode() });
+
+ var copy;
+ function do_one() {
+ if (copy = copies.pop()) {
+ // Checkin expects a barcode, but will pass other
+ // parameters too. Passing the copy ID allows
+ // for the checkin of deleted copies on the server.
+ egCirc.checkin(
+ {copy_barcode: copy.barcode(), copy_id: copy.id()},
+ {suppress_popups: $scope.suppress_popups})
+ .finally(do_one);
+ } else {
+ reset_page();
}
- do_one(); // kick it off
- });
+ }
+ if ($scope.suppress_popups) {
+ do_one();
+ } else {
+ return egConfirmDialog.open(
+ egCore.strings.CHECK_IN_CONFIRM, barcodes.join(' '), {
+
+ }).result.then(function() {
+ do_one(); // kick it off
+ });
+ }
}
$scope.add_billing = function(items) {