angular.module('egPatronApp').controller('PatronCheckoutCtrl',
['$scope','$q','$modal','$routeParams','egCore','egUser','patronSvc',
- 'egGridDataProvider','$location',
+ 'egGridDataProvider','$location','egCirc',
function($scope , $q , $modal , $routeParams , egCore , egUser , patronSvc ,
- egGridDataProvider , $location) {
+ egGridDataProvider , $location , egCirc) {
$scope.initTab('checkout', $routeParams.id);
$scope.focusMe = true;
$scope.checkouts = patronSvc.checkouts;
$scope.checkoutArgs = {noncat_type : 'barcode'};
+ $scope.gridDataProvider = egGridDataProvider.instance({
+ get : function(offset, count) {
+ return this.arrayNotifier($scope.checkouts, offset, count);
+ }
+ });
- // Grid Provider -------------------
- var provider = egGridDataProvider.instance({});
- provider.get = function(offset, count) {
- return provider.arrayNotifier($scope.checkouts, offset, count);
- }
-
- function addCheckout(co) {
- $scope.checkouts.push(co);
- provider.refresh();
- }
+ egCirc.get_circ_mods().then(function(list) {
+ $scope.circModifiers = list;
+ });
- $scope.gridDataProvider = provider;
- // -----------------------------
+ egCirc.get_noncat_types().then(function(list) {
+ $scope.nonCatTypes = list;
+ });
$scope.selectedNcType = function() {
if (!egCore.env.cnct) return null; // too soon
return type ? type.name() : null;
}
- if (egCore.env.cnct) {
- $scope.nonCatTypes = egCore.env.cnct.list;
- } else {
- egCore.pcrud.search('cnct',
- {owning_lib : egCore.org.fullPath(egCore.auth.user().ws_ou(), true)},
- null, {atomic : true}
- ).then(function(list) {
- egCore.env.absorbList(list, 'cnct');
- $scope.nonCatTypes = list
- });
- }
-
- egCore.pcrud.retrieveAll('ccm', null, {atomic : true}).then(
- function(list) { $scope.circModifiers = list });
-
- // TODO: apply correct response order
$scope.checkout = function(args) {
- var type = args.type;
- var coArgs = angular.copy(args);
-
- if (coArgs.noncat_type == 'barcode') {
+ var params = angular.copy(args);
+ params.patron_id = patronSvc.current.id();
+ if (params.noncat_type == 'barcode') {
if (!args.copy_barcode) return;
- args.copy_barcode = ''; // reset UI
- delete coArgs.noncat_type;
- performCheckout(coArgs);
+ args.copy_barcode = ''; // reset UI input
+ params.noncat_type = ''; // "barcode"
+ send_checkout(params);
} else {
- console.debug('noncat..');
- openNoncatDialog(coArgs);
+ egCirc.noncat_dialog(params).then(function() {
+ send_checkout(params)
+ });
}
$scope.focusMe; // return focus to barcode input
}
- var index = 0;
- function performCheckout(args, override) {
- console.debug('checkout: ' + js2JSON(args));
-
- var method = 'open-ils.circ.checkout.full';
- if (override) method += '.override';
-
- args.patron_id = $scope.patron_id;
-
- egCore.net.request(
- 'open-ils.circ', method, egCore.auth.token(), args
- ).then(function(evt) {
-
- if (!evt) {
- console.error('no checkout response received');
- return;
- }
-
- // TODO: how best to handle multiple response events?
- if (angular.isArray(evt)) evt = evt[0];
- evt.id = index++;
- evt.copy_barcode = args.copy_barcode;
- handleCheckoutResponse(evt, args, override)
- });
- }
-
- function handleCheckoutResponse(evt, args, override) {
-
- if (evt.payload) {
- if (args.precat) {
- evt.payload.record = {
- title : args.dummy_title,
- author : args.dummy_author,
- isbn : args.dummy_isbn
- };
- } else if (args.noncat) {
- evt.payload.record = {
- title : egCore.env.cnct.map[args.noncat_type].name()
- };
- evt.noncat_count = args.noncat_count;
- evt.payload.circ = new egCore.idl.circ();
- evt.payload.circ.due_date(evt.payload.noncat_circ.duedate());
- }
- }
-
- console.debug('checkout: ' + JSON.stringify(evt, null, 2));
-
- switch (evt.textcode) {
- case 'SUCCESS':
- // keep the global patron object in sync with reality
- addCheckout(evt);
- if (!args.noncat)
- patronSvc.patron_stats.checkouts.out++;
- break;
-
- case 'ITEM_NOT_CATALOGED':
- openPrecatDialog(evt.copy_barcode);
- break;
-
- case 'PATRON_EXCEEDS_FINES':
- case 'PATRON_EXCEEDS_CHECKOUT_COUNT':
- if (!override) {
- if (patronSvc.checkout_overrides[evt.textcode]) {
- performCheckout(args, true);
- } else {
- openOverrideConfirmDialog(evt, args);
- }
- }
- break;
-
- case 'OPEN_CIRCULATION_EXISTS':
- openCircExistsDialog(args, evt);
- break;
-
- /* stuff to consider
- PERM_FAILURE
- PATRON_EXCEEDS_OVERDUE_COUNT
- PATRON_BARRED
- CIRC_EXCEEDS_COPY_RANGE
- PATRON_ACCOUNT_EXPIRED
- ITEM_DEPOSIT_REQUIRED
- ITEM_RENTAL_FEE_REQUIRED
- ITEM_DEPOSIT_PAID
- PATRON_EXCEEDS_LOST_COUNT
- ACTION_CIRCULATION_NOT_FOUND
- PATRON_EXCEEDS_CHECKOUT_COUNT
- COPY_CIRC_NOT_ALLOWED
- COPY_NOT_AVAILABLE
- COPY_IS_REFERENCE
- COPY_NEEDED_FOR_HOLD
- MAX_RENEWALS_REACHED
- CIRC_CLAIMS_RETURNED
- COPY_ALERT_MESSAGE
- PATRON_EXCEEDS_FINES
- */
-
- default:
- console.warn('unhandled circ response : ' + evt.textcode);
- // push it on the list so the user can at least see
- // something happened.
- addCheckout(evt);
- }
- }
-
- // define our modal dialogs
-
- function openNoncatDialog(coArgs) {
- coArgs.noncat = true;
- var type = egCore.env.cnct.map[coArgs.noncat_type];
-
- $modal.open({
- templateUrl: './circ/patron/t_noncat_dialog',
- controller:
- ['$scope', '$modalInstance',
- function($scope, $modalInstance) {
- $scope.focusMe = true;
- $scope.type = type;
- $scope.count = 1;
- $scope.ok = function(count) { $modalInstance.close(count) }
- $scope.cancel = function () { $modalInstance.dismiss() }
- }],
- }).result.then(
- function(count) {
- $scope.focusMe = true; // main barcode input
- if (count) {
- // TODO: sanity check
- coArgs.noncat_count = count;
- performCheckout(coArgs);
- }
- }
- );
- }
-
- function openCircExistsDialog(coArgs, evt) {
- $modal.open({
- templateUrl: './circ/patron/t_circ_exists_dialog',
- controller:
- ['$scope','$modalInstance','openCirc',
- function($scope , $modalInstance , openCirc) {
- $scope.circDate = openCirc.xact_start();
- $scope.ok = function() { $modalInstance.close() }
- $scope.cancel = function($event) {
- $modalInstance.dismiss();
- $event.preventDefault(); // form: prevent ok() from firing
- }
- }],
- resolve : {
- openCirc : function() {
- return egCore.pcrud.search('circ',
- {target_copy : evt.payload.copy.id()},
- {order_by : {circ : 'xact_start desc' }, limit : 1}
- );
- }
- }
- }).result.then(
- function() {
- egCore.net.request(
- 'open-ils.circ',
- 'open-ils.circ.checkin',
- egCore.auth.token(),
- {barcode :coArgs.copy_barcode, noop : true}
-
- ).then(function(resp) {
-
- if (evt = egCore.evt.parse(resp)) {
- if (evt.textcode == 'SUCCESS') {
- performCheckout(coArgs);
- } else {
- alert(evt); // FIXME
- }
- }
- })
- }
- );
- }
-
-
- function openPrecatDialog(copy_barcode) {
- $modal.open({
- templateUrl: './circ/patron/t_precat_dialog',
- controller:
- ['$scope', '$modalInstance', 'circMods',
- function($scope, $modalInstance, circMods) {
- $scope.focusMe = true;
- $scope.precatArgs = {
- copy_barcode : copy_barcode,
- circ_modifier : circMods.length ? circMods[0].code() : null
- };
- $scope.circModifiers = circMods;
- $scope.ok = function(args) { $modalInstance.close(args) }
- $scope.cancel = function () { $modalInstance.dismiss() }
- }],
- // pass the circ mod list into the modal environment
- // the angular way.
- resolve : {
- circMods : function() { return $scope.circModifiers }
- }
- }).result.then(
- function(args) {
- $scope.focusMe = true; // main barcode input
- if (!args || !args.dummy_title) return;
- args.precat = true;
- performCheckout(args);
+ function send_checkout(params) {
+
+ params.noncat_type = params.noncat ? params.noncat_type : '';
+
+ // populate the grid row before we send the request so that the
+ // order of actions is maintained and so the user gets an
+ // immediate reaction to their barcode input action.
+ var row_item = {
+ index : $scope.checkouts.length,
+ copy_barcode : params.copy_barcode,
+ noncat_type : params.noncat_type
+ };
+
+ $scope.checkouts.unshift(row_item);
+ $scope.gridDataProvider.refresh();
+
+ egCirc.checkout(params).then(
+ function(co_resp) {
+ // update stats locally so we don't have to fetch them w/
+ // each checkout.
+ patronSvc.patron_stats.checkouts.out++
+
+ munge_checkout_resp(co_resp);
+
+ // copy the response event into the original grid row item
+ angular.copy(co_resp.evt, row_item);
},
function() {
- // dialog was closed without action
- $scope.focusMe = true;
+ // Circ was rejected somewhere along the way.
+ // Remove the copy from the grid since there was no action.
+ $scope.checkouts.splice(row_item.index, 1);
+ $scope.gridDataProvider.refresh();
}
);
}
- function openOverrideConfirmDialog(evt, args) {
- $modal.open({
- templateUrl: './circ/patron/t_event_override_dialog',
- controller:
- ['$scope', '$modalInstance',
- function($scope, $modalInstance) {
- $scope.evt = evt;
- $scope.ok = function() { $modalInstance.close() }
- $scope.cancel = function () { $modalInstance.dismiss() }
- }]
- }).result.then(
- function() {
- $scope.focusMe = true; // main barcode input
- patronSvc.checkout_overrides[evt.textcode] = true;
- performCheckout(args, true);
- },
- function() {
- // dialog was closed without action
- $scope.focusMe = true;
- }
- );
+ // move some stuff around so it will play nice w/ the template
+ function munge_checkout_resp(co_resp) {
+ var payload = co_resp.evt.payload;
+ var params = co_resp.params;
+
+ if (!payload) return;
+
+ if (params.precat) {
+ payload.record = {
+ title : params.dummy_title,
+ author : params.dummy_author,
+ isbn : params.dummy_isbn
+ };
+ } else if (params.noncat) {
+ payload.record = {
+ title : egCore.env.cnct.map[params.noncat_type].name()
+ };
+ co_resp.evt.noncat_count = params.noncat_count;
+ payload.circ = new egCore.idl.circ();
+ payload.circ.due_date(payload.noncat_circ.duedate());
+ }
}
-
}])
).then(function(evt) {
if (angular.isArray(evt)) evt = evt[0];
- return service.handle_checkout_resp(evt, args, override);
+ return service.handle_checkout_resp(evt, params, options);
});
}
).then(function(evt) {
if (angular.isArray(evt)) evt = evt[0];
- return service.handle_checkout_resp(evt, args, options);
+ return service.handle_checkout_resp(evt, params, options);
});
}
}
}
- // fetch the list of circ modifiers, from cache if available
+ // returns a promise resolved with the list of circ mods
service.get_circ_mods = function() {
- if (egCore.env.ccm) {
+ if (egCore.env.ccm)
return $q.when(egCore.env.ccm.list);
- } else {
- return egCore.pcrud.retrieveAll('ccm', null, {atomic : true})
- .then(function(list) {
- egCore.env.absorbList(list, 'ccm');
- return list;
- });
- }
+
+ return egCore.pcrud.retrieveAll('ccm', null, {atomic : true})
+ .then(function(list) {
+ egCore.env.absorbList(list, 'ccm');
+ return list;
+ });
};
+ // returns a promise resolved with the list of noncat types
+ service.get_noncat_types = function() {
+ if (egCore.env.cnct)
+ return $q.when(egCore.env.cnct.list);
+
+ return egCore.pcrud.search('cnct',
+ {owning_lib :
+ egCore.org.fullPath(egCore.auth.user().ws_ou(), true)},
+ null, {atomic : true}
+ ).then(function(list) {
+ egCore.env.absorbList(list, 'cnct');
+ return list;
+ });
+ }
+
+
// fetch/cache for org unit addresses
service.get_org_addr = function(org_id, addr_type) {
if (service.org_addr_cache[org_id]) {
function($scope, $modalInstance) {
$scope.evt = evt;
$scope.ok = function() { $modalInstance.close() }
- $scope.cancel = function () { $modalInstance.dismiss() }
+ $scope.cancel = function ($event) {
+ $modalInstance.dismiss();
+ $event.preventDefault();
+ }
}]
}).result.then(
function() {
);
}
+ // Opens a dialog allowing the user to fill in the desired non-cat count.
+ // Unlike other dialogs, which kickoff circ actions internally
+ // as a result of events, this dialog does not kick off any circ
+ // actions. It just collects the count and and resolves the promise.
+ //
+ // This assumes the caller has already handled the noncat-type
+ // selection and just needs to collect the count info.
+ service.noncat_dialog = function(params, options) {
+
+ // the caller should presumably have fetched the noncat_types via
+ // our API already, but fetch them again (from cache) to be safe.
+ return service.get_noncat_types().then(function() {
+
+ params.noncat = true;
+ var type = egCore.env.cnct.map[params.noncat_type];
+
+ return $modal.open({
+ templateUrl: './circ/share/t_noncat_dialog',
+ controller:
+ ['$scope', '$modalInstance',
+ function($scope, $modalInstance) {
+ $scope.focusMe = true;
+ $scope.type = type;
+ $scope.count = 1;
+ $scope.ok = function(count) { $modalInstance.close(count) }
+ $scope.cancel = function () { $modalInstance.dismiss() }
+ }],
+ }).result.then(
+ function(count) {
+ if (count && count > 0 /* && count < x (TODO) */) {
+ params.noncat_count = count;
+ return $q.when(params);
+ } else {
+ return $q.reject();
+ }
+ }
+ );
+ });
+ }
- // opens a dialog allowing the user to fill in pre-cat copy info
+ // Opens a dialog allowing the user to fill in pre-cat copy info.
service.precat_dialog = function(params, options) {
return $modal.open({
}
service.circ_exists_dialog = function(evt, params, options) {
- $modal.open({
+ return $modal.open({
templateUrl: './circ/share/t_circ_exists_dialog',
controller:
['$scope','$modalInstance','openCirc',
function($scope , $modalInstance , openCirc) {
$scope.circDate = openCirc.xact_start();
$scope.ok = function() { $modalInstance.close() }
- $scope.cancel = function() { $modalInstance.dismiss() }
+ $scope.cancel = function($event) {
+ $modalInstance.dismiss();
+ $event.preventDefault(); // form, avoid calling ok();
+ }
}],
resolve : {
// fetch the conflicting open circulation