dojo.require('dojo.cookie');
dojo.require('dojo.date.stamp');
dojo.require('dijit.form.CheckBox');
+dojo.require('dijit.form.DateTextBox');
dojo.require('dijit.form.NumberSpinner');
dojo.require('openils.CGI');
dojo.require('openils.Util');
dojo.require('openils.User');
dojo.require('openils.Event');
+dojo.require('openils.PermaCrud');
dojo.require('openils.widget.ProgressDialog');
dojo.require('openils.widget.OrgUnitFilteringSelect');
// dict of org unit settings for "here"
this.orgSettings = {};
+ // true if we are exposing staff-only features (e.g. checkin)
+ this.staffMode = false;
+
+ // maps copy status IDs to status objects
+ this.copyStatusMap = {};
+
+ // maps org unit IDs to their mailing addresses
+ this.orgUnitAddrMap = {};
+
// Construct a mock checkout for debugging purposes
if(this.mockCheckouts = this.cgi.param('mock-circ')) {
this.authtoken = openils.User.authtoken;
}
+SelfCheckManager.prototype.logoutStaff = function() {
+ // remove the authtoken
+ dojo.cookie('ses', null, {expires:-1, path:'/'});
+ // reload the page, which displays the login dialog
+ location.href = location.href;
+}
+
+SelfCheckManager.prototype.activateStaffMode = function(permorgs) {
+ var self = this;
+
+ // make sure this staff account has the needed permission here
+ if (permorgs.indexOf(Number(this.staff.ws_ou())) == -1) return;
+
+ this.staffMode = true;
+
+ openils.Util.show(
+ dojo.byId('oils-selfck-nav-checkin').parentNode,
+ 'inline'
+ );
+
+ openils.Util.show(dojo.byId('oils-selfchk-staff-actions'));
+
+ dojo.byId('oils-selfchk-staff-logout').onclick = function() {
+ self.logoutStaff();
+ };
+}
/**
* Fetch the org-unit settings, initialize the display, etc.
*/
SelfCheckManager.prototype.init = function() {
+ var self = this;
this.setupStaffLogin();
this.loadOrgSettings();
+ this.loadCopyStatuses();
+
+ // are we in staff mode?
+ new openils.User().getPermOrgList(['SELFCHECK_STAFF_MODE'],
+ function(orglist) { self.activateStaffMode(orglist) },
+ true, true
+ );
this.circTbody = dojo.byId('oils-selfck-circ-tbody');
+ this.checkinTbody = dojo.byId('oils-selfck-checkin-tbody');
this.itemsOutTbody = dojo.byId('oils-selfck-circ-out-tbody');
+ this.itemsCheckinTbody = dojo.byId('oils-selfck-checkin-out-tbody');
// workstation is required but none provided
if(this.orgSettings[SET_WORKSTATION_REQUIRED] && !this.workstation) {
return;
}
- var self = this;
// connect onclick handlers to the various navigation links
var linkHandlers = {
'oils-selfck-hold-details-link' : function() { self.drawHoldsPage(); },
);
},
'oils-selfck-nav-home' : function() { self.drawCircPage(); },
+ 'oils-selfck-nav-checkin' : function() { self.drawCheckinPage(); },
'oils-selfck-nav-logout' : function() { self.logoutPatron(); },
'oils-selfck-nav-logout-print' : function() { self.logoutPatron(true); },
'oils-selfck-items-out-details-link' : function() { self.drawItemsOutPage(); },
}
+SelfCheckManager.prototype.loadCopyStatuses = function() {
+ var self = this;
+ var pcrud = new openils.PermaCrud();
+ pcrud.retrieveAll('ccs', {
+ async : true,
+ oncomplete : function(r) {
+ var list = openils.Util.readResponse(r);
+ dojo.forEach(list, function(stat) {
+ self.copyStatusMap[stat.id()] = stat;
+ });
+ }
+ });
+};
+
SelfCheckManager.prototype.getSelectedFinesTotal = function() {
var total = 0;
dojo.forEach(
// retrieve the fleshed user by id
this.patron = fieldmapper.standardRequest(
['open-ils.actor', 'open-ils.actor.user.fleshed.retrieve.authoritative'],
- {params : [this.authtoken, patron_id]}
+ {params : [this.authtoken, patron_id,
+ [ 'card',
+ 'cards',
+ 'addresses',
+ 'billing_address',
+ 'mailing_address',
+ 'profile'
+ ]
+ ]}
);
var evt = openils.Event.parse(this.patron);
} else {
this.handleAlert('', false, 'login-success');
- dojo.byId('oils-selfck-user-banner').innerHTML =
- dojo.string.substitute(localeStrings.WELCOME_BANNER, [this.patron.first_given_name()]);
+
+ if (this.staffMode) {
+ this.drawPatronInfo();
+
+ } else {
+ dojo.byId('oils-selfck-user-banner').innerHTML =
+ dojo.string.substitute(localeStrings.WELCOME_BANNER,
+ [this.patron.first_given_name()]);
+ }
+
+
this.drawCircPage();
}
}
+SelfCheckManager.prototype.drawPatronInfo = function() {
+
+ openils.Util.show('oils-selfck-user-details');
+
+ var patron = this.patron;
+
+ dojo.byId('oils-selfck-user-name').innerHTML =
+ openils.User.formalName(patron);
+
+ dojo.byId('oils-selfck-user-home-ou').innerHTML =
+ fieldmapper.aou.findOrgUnit(patron.home_ou()).shortname();
+
+ dojo.byId('oils-selfck-user-profile').innerHTML =
+ patron.profile().name();
+
+ var addr = patron.mailing_address() ||
+ patron.billing_address() ||
+ patron.addresses()[0];
+
+ if (addr) {
+
+ dojo.byId('oils-selfck-user-address').innerHTML =
+ dojo.string.substitute(localeStrings.ADDRESS, [
+ addr.street1(),
+ addr.street2() || '',
+ addr.city(),
+ addr.state(),
+ addr.post_code()
+ ]);
+
+ } else {
+ dojo.byId('oils-selfck-user-address').innerHTML = '';
+ }
+}
+
SelfCheckManager.prototype.handleAlert = function(message, shouldPopup, sound) {
console.log("Handling alert " + message);
var self = this;
this.updateScanBox({
- msg : 'Please enter an item barcode', // TODO i18n
- handler : function(barcode) { self.checkout(barcode); }
+ msg : dojo.string.substitute(localeStrings.CHECKOUT_PROMPT),
+ handler : function(barcode) {
+ // staffMode jumps straight to override
+ self.checkout(barcode, self.staffMode);
+ }
});
if(!this.circTemplate)
}
}
+/**
+ * Sets up the checkin page
+ */
+SelfCheckManager.prototype.drawCheckinPage = function() {
+ openils.Util.show('oils-selfck-checkin-tbody', 'table-row-group');
+ this.goToTab('checkin');
+
+ while(this.itemsCheckinTbody.childNodes[0])
+ this.itemsCheckinTbody.removeChild(
+ this.itemsCheckinTbody.childNodes[0]);
+
+ if(!this.checkinTemplate) {
+ this.checkinTemplate =
+ this.checkinTbody.removeChild(
+ dojo.byId('oils-selfck-checkin-row'));
+ }
+
+ var self = this;
+ this.updateScanBox({
+ msg : dojo.string.substitute(localeStrings.CHECKIN_PROMPT),
+ handler : function(barcode) { self.checkin(barcode); }
+ });
+};
+
+
+SelfCheckManager.prototype.checkin = function(barcode) {
+ var self = this;
+
+ // clear the box now so checkins can continue
+ this.updateScanBox();
+
+ var backdate = checkinBackdateInput.attr('value') || null;
+ if (backdate) backdate = dojo.date.stamp.toISOString(backdate);
+
+ var row = this.checkinTemplate.cloneNode(true);
+ this.byName(row, 'barcode').innerHTML = barcode;
+
+ // put new circs at the top of the list
+ var tbody = this.checkinTbody;
+ tbody.insertBefore(row, tbody.getElementsByTagName('tr')[0]);
+
+ this.checkinCopy({
+ barcode : barcode,
+ void_overdues : dojo.byId('oils-selfchk-amnesty-mode').checked,
+ backdate : backdate,
+ onload : function(evts) {
+ if (!evts.length) evts = [evts];
+ dojo.forEach(evts,
+ function(evt) {
+ self.handleCheckinResult(row, barcode, evt);
+ }
+ );
+ }
+ });
+};
+
SelfCheckManager.prototype.updateFinesSummary = function() {
var self = this;
openils.Util.hide('oils-selfck-payment-page');
openils.Util.hide('oils-selfck-holds-page');
openils.Util.hide('oils-selfck-circ-page');
+ openils.Util.hide('oils-selfck-checkin-page');
openils.Util.hide('oils-selfck-pay-fines-link');
-
+
+ dojo.removeClass('oils-selfck-nav-home', 'selected');
+ dojo.removeClass('oils-selfck-nav-checkin', 'selected');
+
+
switch(name) {
case 'checkout':
openils.Util.show('oils-selfck-circ-page');
+ dojo.addClass('oils-selfck-nav-home', 'selected');
+ break;
+ case 'checkin':
+ openils.Util.show('oils-selfck-checkin-page');
+ dojo.addClass('oils-selfck-nav-checkin', 'selected');
break;
case 'items_out':
openils.Util.show('oils-selfck-circ-page');
if(increment) {
// local checkout occurred. Add to the total and the session.
- this.circSummary.total += 1;
+ this.circSummary.total += increment;
this.circSummary.session += 1;
}
);
}
-SelfCheckManager.prototype.checkin = function(barcode, abortTransit) {
-
- var resp = fieldmapper.standardRequest(
- ['open-ils.circ', 'open-ils.circ.transit.abort'],
- {params : [this.authtoken, {barcode : barcode}]}
+/** top-level checkin handler */
+SelfCheckManager.prototype.checkinCopy = function(args) {
+ fieldmapper.standardRequest(
+ ['open-ils.circ', 'open-ils.circ.checkin.override'],
+ { async : true,
+ params : [
+ this.authtoken, {
+ copy_barcode : args.barcode,
+ backdate : args.backdate,
+ void_overdues : args.void_overdues
+ }
+ ],
+ oncomplete : function(r) {
+ var resp = openils.Util.readResponse(r, true);
+ args.onload(resp);
+ }
+ }
);
+};
- // resp == 1 on success
- if(openils.Event.parse(resp))
- return false;
+/** used for checkins required to fullfil a checkout */
+SelfCheckManager.prototype.inlineCheckinCopy = function(barcode, abortTransit) {
+
+ if (abortTransit) {
+ var resp = fieldmapper.standardRequest(
+ ['open-ils.circ', 'open-ils.circ.transit.abort'],
+ {params : [this.authtoken, {barcode : barcode}]}
+ );
+
+ // resp == 1 on success
+ if(openils.Event.parse(resp))
+ return false;
+ }
var resp = fieldmapper.standardRequest(
['open-ils.circ', 'open-ils.circ.checkin.override'],
}
}
+SelfCheckManager.prototype.displayCheckin = function(row, result, outcomeText) {
+ console.log('display checkin results ' + result);
+
+ var copy = result.payload.copy;
+ var record = result.payload.record;
+ var circ = result.payload.circ;
+
+ if(record.isbn()) {
+ this.byName(row, 'jacket').setAttribute('src',
+ '/opac/extras/ac/jacket/small/' + record.isbn());
+ }
+
+ this.byName(row, 'barcode').innerHTML = copy.barcode();
+ this.byName(row, 'title').innerHTML = record.title();
+ this.byName(row, 'author').innerHTML = record.author();
+ this.byName(row, 'status').innerHTML = this.copyStatusMap[copy.status()].name();
+ this.byName(row, 'outcome').innerHTML = outcomeText || result.textcode;
+
+ if (circ) {
+ var date = dojo.date.stamp.fromISOString(circ.due_date());
+ this.byName(row, 'due_date').innerHTML =
+ dojo.date.locale.format(date, {selector : 'date'});
+
+ // if a patron is loaded and we just checked an item
+ // in for this patron, decrement the items-out count by 1
+ if (this.patron && this.patron.id() == circ.usr())
+ this.updateCircSummary(-1);
+ }
+}
+
+SelfCheckManager.prototype.getOrgMailingAddress = function(orgId, callback) {
+ if (this.orgUnitAddrMap[orgId]) {
+ callback(this.orgUnitAddrMap[orgId]);
+ return;
+ }
+
+ var self = this;
+ var pcrud = new openils.PermaCrud();
+ pcrud.retrieve('aou', orgId, {
+ flesh : 1,
+ flesh_fields : {aou : ['mailing_address', 'billing_address']},
+ async : true,
+ oncomplete : function(r) {
+ var org = openils.Util.readResponse(r);
+ var addr = org.mailing_address() || org.billing_address();
+ self.orgUnitAddrMap[orgId] = addr;
+ callback(self.orgUnitAddrMap[orgId]);
+ }
+ });
+}
+
+SelfCheckManager.prototype.getUser = function(userId, callback) {
+ if (!userId || typeof userId == 'object')
+ callback(userId);
+
+ var self = this;
+ var pcrud = new openils.PermaCrud();
+ pcrud.retrieve('au', userId, {
+ flesh : 1,
+ flesh_fields : {au : ['card']},
+ async : true,
+ oncomplete : function(r) {
+ callback(openils.Util.readResponse(r));
+ }
+ });
+}
+
+// non-transit hold slip
+SelfCheckManager.prototype.printHoldSlip = function(item, result) {
+ if (!dojo.byId('oils-selfchk-print-hold-slip').checked) return;
+
+ var self = this;
+ var payload = result.payload;
+ var hold = payload.hold;
+
+ // in some cases, payload.patron is null even when there is a hold
+ this.getUser(hold.usr(), function(patron) {
+
+ var routeMsg = dojo.string.substitute(localeStrings.HOLD_SHELF, []);
+
+ var routeMsg = dojo.string.substitute(
+ localeStrings.ROUTE_MSG, [localeStrings.HOLD_SHELF]);
+
+ var holdMsg = dojo.string.substitute(
+ localeStrings.HOLD_SLIP, [
+ // checkin could be for a different user than this.patron
+ openils.User.formalName(payload.patron),
+ patron.card().barcode(),
+ openils.Util.timeStamp(hold.request_time())
+ ]
+ );
+
+ self.printData(
+ dojo.string.substitute(
+ localeStrings.TRANSIT_SLIP, [
+ routeMsg,
+ '', // destination address
+ item,
+ result.payload.record.title(),
+ result.payload.record.author(),
+ holdMsg,
+ dojo.date.locale.format(new Date()),
+ self.staff.usrname(),
+ fieldmapper.aou.findOrgUnit(self.staff.ws_ou()).shortname()
+ ]
+ ), 1
+ );
+ });
+}
+
+
+// hold transit slip
+SelfCheckManager.prototype.printHoldTransitSlip = function(item, result, dest) {
+ if (!dojo.byId('oils-selfchk-print-transit-slip').checked) return;
+
+ var self = this;
+ var payload = result.payload;
+ var transit = payload.transit;
+ var hold = payload.hold;
+
+ // in some cases, payload.patron is null even when there is a hold
+ this.getUser(hold.usr(), function(patron) {
+
+ var holdMsg = dojo.string.substitute(
+ localeStrings.HOLD_SLIP, [
+ // checkin could be for a different user than this.patron
+ openils.User.formalName(payload.patron),
+ patron.card().barcode(),
+ openils.Util.timeStamp(hold.request_time())
+ ]
+ );
+
+ // now we have hold recipient info,
+ // continue with regular slip generation
+ self.printTransitSlip(item, result, dest, holdMsg);
+ });
+}
+
+// if this is a hold transit, holdMsg should contain the hold info
+SelfCheckManager.prototype.printTransitSlip =
+ function(item, result, dest, holdMsg) {
+
+ if (!dojo.byId('oils-selfchk-print-transit-slip').checked) return;
+
+ var self = this;
+ var payload = result.payload;
+ var transit = payload.transit;
+ var hold = payload.hold;
+ holdMsg = holdMsg || '';
+
+ var routeMsg = dojo.string.substitute(
+ localeStrings.ROUTE_MSG, [dest.shortname()]);
+
+ // retrieve the address, then finish rendering the slip
+ this.getOrgMailingAddress(dest.id(), function(addr) {
+
+ var destAddr = dojo.string.substitute(
+ localeStrings.ORG_ADDRESS, [
+ dest.name(),
+ dojo.string.substitute(localeStrings.ADDRESS, [
+ addr.street1(),
+ addr.street2() || '',
+ addr.city(),
+ addr.state(),
+ addr.post_code()
+ ])
+ ]
+ );
+
+ self.printData(
+ dojo.string.substitute(
+ localeStrings.TRANSIT_SLIP, [
+ routeMsg,
+ destAddr,
+ item,
+ result.payload.record.title(),
+ result.payload.record.author(),
+ holdMsg,
+ dojo.date.locale.format(new Date()),
+ self.staff.usrname(),
+ fieldmapper.aou.findOrgUnit(self.staff.ws_ou()).shortname()
+ ]
+ ), 1
+ );
+ });
+}
+
+
+
+
+SelfCheckManager.prototype.handleCheckinResult = function(row, item, result) {
+ var displayText = '';
+ var popup = false;
+ var sound = ''; // sound file reference
+ var payload = result.payload || {};
+ var tc = result.textcode;
+
+ console.log('checkin resulted in ' + tc);
+
+ if (tc == 'NO_SESSION') {
+
+ return this.logoutStaff();
+
+ } else if (tc == 'SUCCESS') {
+
+ displayText = dojo.string.substitute(
+ localeStrings.CHECKIN_SUCCESS, [item]);
+
+ this.displayCheckin(row, result);
+ if (payload.hold)
+ this.printHoldSlip(item, result);
+
+ } else if (tc == 'NO_CHANGE') {
+
+ displayText = dojo.string.substitute(
+ localeStrings.CHECKIN_NO_CHANGE, [item]);
+
+ this.displayCheckin(row, result);
+ if (payload.hold)
+ this.printHoldSlip(item, result);
+
+ } else if (tc == 'ROUTE_ITEM') {
+
+ var dest = fieldmapper.aou.findOrgUnit(result.source.org);
+
+ displayText = dojo.string.substitute(
+ localeStrings.CHECKIN_ROUTE_ITEM, [item, dest.shortname()]);
+
+ this.displayCheckin(row, result, tc + ' => ' + dest.shortname());
+
+ if (result.payload.hold) {
+ this.printHoldTransitSlip(item, result, dest);
+ } else {
+ this.printTransitSlip(item, result, dest);
+ }
+
+ } else if (tc == 'ASSET_COPY_NOT_FOUND') {
+
+ // remove the in-progress row
+ row.parentNode.removeChild(row);
+
+ displayText = dojo.string.substitute(
+ localeStrings.ITEM_NOT_CATALOGED, [item]);
+
+ } else {
+
+ // remove the in-progress row
+ row.parentNode.removeChild(row);
+
+ displayText = dojo.string.substitute(
+ localeStrings.UNKNOWN_ERROR, [tc]);
+ }
+
+ this.handleAlert(displayText, popup, sound);
+ return {};
+}
+
SelfCheckManager.prototype.handleXactResult = function(action, item, result) {
var displayText = '';
this.updateHoldsSummary();
}
- this.updateCircSummary(true);
+ this.updateCircSummary(1);
} else if(action == 'renew') {
overrideEvents && overrideEvents.length &&
overrideEvents.indexOf('COPY_STATUS_LOST') != -1) {
- if(this.checkin(item)) {
+ if(this.inlineCheckinCopy(item)) {
return { doOver : true };
}
}
} else {
+
+ if (this.staffMode) {
+ // in staff mode we override everything. Transits, however,
+ // can't be overridden. They must first be aborted and checked in.
+
+ if(!result.length) result = [result];
+
+ for(var i = 0; i < result.length; i++) {
+ if(result[i].textcode == 'COPY_IN_TRANSIT') {
+ if(this.inlineCheckinCopy(item, true))
+ return { override : true };
+ }
+ }
+ }
if(overrideEvents && overrideEvents.length) {
if(result[i].textcode == 'COPY_IN_TRANSIT') {
// to override a transit, we have to abort the transit and check it in first
- if(this.checkin(item, true)) {
+ if(this.inlineCheckinCopy(item, true)) {
return { doOver : true };
} else {
override = false;