# Checking if the transfer page needs to be displayed
$template->param( display_transfer => 1 ) if ( ($flags->{'superlibrarian'} == 1) || (C4::Context->preference("IndependentBranches") == 0) );
+$template->{'VARS'}->{'AllowOfflineCirculation'} = C4::Context->preference('AllowOfflineCirculation');
output_html_with_http_headers $query, $cookie, $template->output;
--- /dev/null
+#!/usr/bin/perl
+
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
+# Suite 330, Boston, MA 02111-1307 USA
+#
+
+use Modern::Perl;
+use CGI;
+use C4::Auth;
+
+my $query = new CGI;
+my ($template, $loggedinuser, $cookie, $flags)
+= get_template_and_user({template_name => "circ/offline-mf.tt",
+ query => $query,
+ type => "intranet",
+ authnotrequired => 0,
+ flagsrequired => {circulate => "circulate_remaining_permissions"},
+ });
+
+$template->{'VARS'}->{'cookie'} = $cookie;
+print $query->header(-type => 'text/cache-manifest', cookie => $cookie);
+print $template->output;
--- /dev/null
+#!/usr/bin/perl
+
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
+# Suite 330, Boston, MA 02111-1307 USA
+#
+
+use Modern::Perl;
+use CGI;
+use C4::Auth;
+use C4::Output;
+use C4::Context;
+
+my $query = new CGI;
+my ($template, $loggedinuser, $cookie, $flags)
+= get_template_and_user({template_name => "circ/offline.tt",
+ query => $query,
+ type => "intranet",
+ authnotrequired => 0,
+ flagsrequired => {circulate => "circulate_remaining_permissions"},
+ });
+
+$template->{'VARS'}->{'AllowOfflineCirculation'} = C4::Context->preference('AllowOfflineCirculation');
+$template->{'VARS'}->{'maxoutstanding'} = C4::Context->preference('maxoutstanding') || 0;
+output_html_with_http_headers $query, $cookie, $template->output;
('AllowItemsOnHoldCheckout','0','','Do not generate RESERVE_WAITING and RESERVED warning when checking out items reserved to someone else. This allows self checkouts for those items.','YesNo'),
('AllowMultipleCovers','0','1','Allow multiple cover images to be attached to each bibliographic record.','YesNo'),
('AllowNotForLoanOverride','0','','If ON, Koha will allow the librarian to loan a not for loan item.','YesNo'),
+('AllowOfflineCirculation','0','','If on, enables HTML5 offline circulation functionality.','YesNo'),
('AllowOnShelfHolds','0','','Allow hold requests to be placed on items that are not on loan','YesNo'),
('AllowPKIAuth','None','None|Common Name|emailAddress','Use the field from a client-side SSL certificate to look a user in the Koha database','Choice'),
('AllowPurchaseSuggestionBranchChoice','0','1','Allow user to choose branch when making a purchase suggestion','YesNo'),
SetVersion($DBversion);
}
+$DBversion = "3.13.00.XXX";
+if ( CheckVersion($DBversion) ) {
+ $dbh->do("INSERT INTO systempreferences (variable,value,options,explanation,type) VALUES ('AllowOfflineCirculation','0','','If on, enables HTML5 offline circulation functionality.','YesNo')");
+ print "Upgrade to $DBversion done (Bug 10240: Add syspref AllowOfflineCirculation)\n";
+ SetVersion ($DBversion);
+}
+
=head1 FUNCTIONS
=head2 TableExists($table)
--- /dev/null
+(function($, undefined) {
+ var indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
+ var IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange;
+ var IDBCursor = window.IDBCursor || window.webkitIDBCursor;
+ IDBCursor.PREV = IDBCursor.PREV || "prev";
+ IDBCursor.NEXT = IDBCursor.NEXT || "next";
+
+ /**
+ * Best to use the constant IDBTransaction since older version support numeric types while the latest spec
+ * supports strings
+ */
+ var IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction;
+
+ function getDefaultTransaction(mode) {
+ var result = null;
+ switch (mode) {
+ case 0:
+ case 1:
+ case "readwrite":
+ case "readonly":
+ result = mode;
+ break;
+ default:
+ result = IDBTransaction.READ_WRITE || "readwrite";
+ }
+ return result;
+ }
+
+ $.extend({
+ /**
+ * The IndexedDB object used to open databases
+ * @param {Object} dbName - name of the database
+ * @param {Object} config - version, onupgradeneeded, onversionchange, schema
+ */
+ "indexedDB": function(dbName, config) {
+ if (config) {
+ // Parse the config argument
+ if (typeof config === "number") config = {
+ "version": config
+ };
+
+ var version = config.version;
+ if (config.schema && !version) {
+ var max = -1;
+ for (key in config.schema) {
+ max = max > key ? max : key;
+ }
+ version = config.version || max;
+ }
+ }
+
+
+ var wrap = {
+ "request": function(req, args) {
+ return $.Deferred(function(dfd) {
+ try {
+ var idbRequest = typeof req === "function" ? req(args) : req;
+ idbRequest.onsuccess = function(e) {
+
+ dfd.resolveWith(idbRequest, [idbRequest.result, e]);
+ };
+ idbRequest.onerror = function(e) {
+
+ dfd.rejectWith(idbRequest, [idbRequest.error, e]);
+ };
+ if (typeof idbRequest.onblocked !== "undefined" && idbRequest.onblocked === null) {
+ idbRequest.onblocked = function(e) {
+
+ var res;
+ try {
+ res = idbRequest.result;
+ } catch (e) {
+ res = null; // Required for Older Chrome versions, accessing result causes error
+ }
+ dfd.notifyWith(idbRequest, [res, e]);
+ };
+ }
+ if (typeof idbRequest.onupgradeneeded !== "undefined" && idbRequest.onupgradeneeded === null) {
+ idbRequest.onupgradeneeded = function(e) {
+
+ dfd.notifyWith(idbRequest, [idbRequest.result, e]);
+ };
+ }
+ } catch (e) {
+ e.name = "exception";
+ dfd.rejectWith(idbRequest, ["exception", e]);
+ }
+ });
+ },
+ // Wraps the IDBTransaction to return promises, and other dependent methods
+ "transaction": function(idbTransaction) {
+ return {
+ "objectStore": function(storeName) {
+ try {
+ return wrap.objectStore(idbTransaction.objectStore(storeName));
+ } catch (e) {
+ idbTransaction.readyState !== idbTransaction.DONE && idbTransaction.abort();
+ return wrap.objectStore(null);
+ }
+ },
+ "createObjectStore": function(storeName, storeParams) {
+ try {
+ return wrap.objectStore(idbTransaction.db.createObjectStore(storeName, storeParams));
+ } catch (e) {
+ idbTransaction.readyState !== idbTransaction.DONE && idbTransaction.abort();
+ }
+ },
+ "deleteObjectStore": function(storeName) {
+ try {
+ idbTransaction.db.deleteObjectStore(storeName);
+ } catch (e) {
+ idbTransaction.readyState !== idbTransaction.DONE && idbTransaction.abort();
+ }
+ },
+ "abort": function() {
+ idbTransaction.abort();
+ }
+ };
+ },
+ "objectStore": function(idbObjectStore) {
+ var result = {};
+ // Define CRUD operations
+ var crudOps = ["add", "put", "get", "delete", "clear", "count"];
+ for (var i = 0; i < crudOps.length; i++) {
+ result[crudOps[i]] = (function(op) {
+ return function() {
+ return wrap.request(function(args) {
+ return idbObjectStore[op].apply(idbObjectStore, args);
+ }, arguments);
+ };
+ })(crudOps[i]);
+ }
+
+ result.each = function(callback, range, direction) {
+ return wrap.cursor(function() {
+ if (direction) {
+ return idbObjectStore.openCursor(wrap.range(range), direction);
+ } else {
+ return idbObjectStore.openCursor(wrap.range(range));
+ }
+ }, callback);
+ };
+
+ result.index = function(name) {
+ return wrap.index(function() {
+ return idbObjectStore.index(name);
+ });
+ };
+
+ result.createIndex = function(prop, options, indexName) {
+ if (arguments.length === 2 && typeof options === "string") {
+ indexName = arguments[1];
+ options = null;
+ }
+ if (!indexName) {
+ indexName = prop;
+ }
+ return wrap.index(function() {
+ return idbObjectStore.createIndex(indexName, prop, options);
+ });
+ };
+
+ result.deleteIndex = function(indexName) {
+ return idbObjectStore.deleteIndex(indexName);
+ };
+
+ return result;
+ },
+
+ "range": function(r) {
+ if ($.isArray(r)) {
+ if (r.length === 1) {
+ return IDBKeyRange.only(r[0]);
+ } else {
+ return IDBKeyRange.bound(r[0], r[1], (typeof r[2] === 'undefined') ? true : r[2], (typeof r[3] === 'undefined') ? true : r[3]);
+ }
+ } else if (typeof r === "undefined") {
+ return null;
+ } else {
+ return r;
+ }
+ },
+
+ "cursor": function(idbCursor, callback) {
+ return $.Deferred(function(dfd) {
+ try {
+
+ var cursorReq = typeof idbCursor === "function" ? idbCursor() : idbCursor;
+ cursorReq.onsuccess = function(e) {
+
+ if (!cursorReq.result) {
+ dfd.resolveWith(cursorReq, [null, e]);
+ return;
+ }
+ var elem = {
+ // Delete, update do not move
+ "delete": function() {
+ return wrap.request(function() {
+ return cursorReq.result["delete"]();
+ });
+ },
+ "update": function(data) {
+ return wrap.request(function() {
+ return cursorReq.result["update"](data);
+ });
+ },
+ "next": function(key) {
+ this.data = key;
+ },
+ "key": cursorReq.result.key,
+ "value": cursorReq.result.value
+ };
+
+ dfd.notifyWith(cursorReq, [elem, e]);
+ var result = callback.apply(cursorReq, [elem]);
+
+ try {
+ if (result === false) {
+ dfd.resolveWith(cursorReq, [null, e]);
+ } else if (typeof result === "number") {
+ cursorReq.result["advance"].apply(cursorReq.result, [result]);
+ } else {
+ if (elem.data) cursorReq.result["continue"].apply(cursorReq.result, [elem.data]);
+ else cursorReq.result["continue"]();
+ }
+ } catch (e) {
+
+ dfd.rejectWith(cursorReq, [cursorReq.result, e]);
+ }
+ };
+ cursorReq.onerror = function(e) {
+
+ dfd.rejectWith(cursorReq, [cursorReq.result, e]);
+ };
+ } catch (e) {
+
+ e.type = "exception";
+ dfd.rejectWith(cursorReq, [null, e]);
+ }
+ });
+ },
+
+ "index": function(index) {
+ try {
+ var idbIndex = (typeof index === "function" ? index() : index);
+ } catch (e) {
+ idbIndex = null;
+ }
+
+ return {
+ "each": function(callback, range, direction) {
+ return wrap.cursor(function() {
+ if (direction) {
+ return idbIndex.openCursor(wrap.range(range), direction);
+ } else {
+ return idbIndex.openCursor(wrap.range(range));
+ }
+
+ }, callback);
+ },
+ "eachKey": function(callback, range, direction) {
+ return wrap.cursor(function() {
+ if (direction) {
+ return idbIndex.openKeyCursor(wrap.range(range), direction);
+ } else {
+ return idbIndex.openKeyCursor(wrap.range(range));
+ }
+ }, callback);
+ },
+ "get": function(key) {
+ if (typeof idbIndex.get === "function") {
+ return wrap.request(idbIndex.get(key));
+ } else {
+ return idbIndex.openCursor(wrap.range(key));
+ }
+ },
+ "count": function() {
+ if (typeof idbIndex.count === "function") {
+ return wrap.request(idbIndex.count());
+ } else {
+ throw "Count not implemented for cursors";
+ }
+ },
+ "getKey": function(key) {
+ if (typeof idbIndex.getKey === "function") {
+ return wrap.request(idbIndex.getKey(key));
+ } else {
+ return idbIndex.openKeyCursor(wrap.range(key));
+ }
+ }
+ };
+ }
+ };
+
+
+ // Start with opening the database
+ var dbPromise = wrap.request(function() {
+
+ return version ? indexedDB.open(dbName, parseInt(version)) : indexedDB.open(dbName);
+ });
+ dbPromise.then(function(db, e) {
+
+ db.onversionchange = function() {
+ // Try to automatically close the database if there is a version change request
+ if (!(config && config.onversionchange && config.onversionchange() !== false)) {
+ db.close();
+ }
+ };
+ }, function(error, e) {
+
+ // Nothing much to do if an error occurs
+ }, function(db, e) {
+ if (e && e.type === "upgradeneeded") {
+ if (config && config.schema) {
+ // Assuming that version is always an integer
+
+ for (var i = e.oldVersion + 1; i <= e.newVersion; i++) {
+ typeof config.schema[i] === "function" && config.schema[i].call(this, wrap.transaction(this.transaction));
+ }
+ }
+ if (config && typeof config.upgrade === "function") {
+ config.upgrade.call(this, wrap.transaction(this.transaction));
+ }
+ }
+ });
+
+ return $.extend(dbPromise, {
+ "cmp": function(key1, key2) {
+ return indexedDB.cmp(key1, key2);
+ },
+ "deleteDatabase": function() {
+ // Kinda looks ugly coz DB is opened before it needs to be deleted.
+ // Blame it on the API
+ return $.Deferred(function(dfd) {
+ dbPromise.then(function(db, e) {
+ db.close();
+ wrap.request(function() {
+ return indexedDB.deleteDatabase(dbName);
+ }).then(function(result, e) {
+ dfd.resolveWith(this, [result, e]);
+ }, function(error, e) {
+ dfd.rejectWith(this, [error, e]);
+ }, function(db, e) {
+ dfd.notifyWith(this, [db, e]);
+ });
+ }, function(error, e) {
+ dfd.rejectWith(this, [error, e]);
+ }, function(db, e) {
+ dfd.notifyWith(this, [db, e]);
+ });
+ });
+ },
+ "transaction": function(storeNames, mode) {
+ !$.isArray(storeNames) && (storeNames = [storeNames]);
+ mode = getDefaultTransaction(mode);
+ return $.Deferred(function(dfd) {
+ dbPromise.then(function(db, e) {
+ var idbTransaction;
+ try {
+
+ idbTransaction = db.transaction(storeNames, mode);
+
+ idbTransaction.onabort = idbTransaction.onerror = function(e) {
+ dfd.rejectWith(idbTransaction, [e]);
+ };
+ idbTransaction.oncomplete = function(e) {
+ dfd.resolveWith(idbTransaction, [e]);
+ };
+ } catch (e) {
+
+ e.type = "exception";
+ dfd.rejectWith(this, [e]);
+ return;
+ }
+ try {
+ dfd.notifyWith(idbTransaction, [wrap.transaction(idbTransaction)]);
+ } catch (e) {
+ e.type = "exception";
+ dfd.rejectWith(this, [e]);
+ }
+ }, function(err, e) {
+ dfd.rejectWith(this, [e, err]);
+ }, function(res, e) {
+
+ //dfd.notifyWith(this, ["", e]);
+ });
+
+ });
+ },
+ "objectStore": function(storeName, mode) {
+ var me = this,
+ result = {};
+
+ function op(callback) {
+ return $.Deferred(function(dfd) {
+ function onTransactionProgress(trans, callback) {
+ try {
+
+ callback(trans.objectStore(storeName)).then(function(result, e) {
+ dfd.resolveWith(this, [result, e]);
+ }, function(err, e) {
+ dfd.rejectWith(this, [err, e]);
+ });
+ } catch (e) {
+
+ e.name = "exception";
+ dfd.rejectWith(trans, [e, e]);
+ }
+ }
+ me.transaction(storeName, getDefaultTransaction(mode)).then(function() {
+
+ // Nothing to do when transaction is complete
+ }, function(err, e) {
+ // If transaction fails, CrudOp fails
+ if (err.code === err.NOT_FOUND_ERR && (mode === true || typeof mode === "object")) {
+
+ var db = this.result;
+ db.close();
+ dbPromise = wrap.request(function() {
+
+ return indexedDB.open(dbName, (parseInt(db.version, 10) || 1) + 1);
+ });
+ dbPromise.then(function(db, e) {
+
+ db.onversionchange = function() {
+ // Try to automatically close the database if there is a version change request
+ if (!(config && config.onversionchange && config.onversionchange() !== false)) {
+ db.close();
+ }
+ };
+ me.transaction(storeName, getDefaultTransaction(mode)).then(function() {
+
+ // Nothing much to do
+ }, function(err, e) {
+ dfd.rejectWith(this, [err, e]);
+ }, function(trans, e) {
+
+ onTransactionProgress(trans, callback);
+ });
+ }, function(err, e) {
+ dfd.rejectWith(this, [err, e]);
+ }, function(db, e) {
+ if (e.type === "upgradeneeded") {
+ try {
+
+ db.createObjectStore(storeName, mode === true ? {
+ "autoIncrement": true
+ } : mode);
+
+ } catch (ex) {
+
+ dfd.rejectWith(this, [ex, e]);
+ }
+ }
+ });
+ } else {
+ dfd.rejectWith(this, [err, e]);
+ }
+ }, function(trans) {
+
+ onTransactionProgress(trans, callback);
+ });
+ });
+ }
+
+ function crudOp(opName, args) {
+ return op(function(wrappedObjectStore) {
+ return wrappedObjectStore[opName].apply(wrappedObjectStore, args);
+ });
+ }
+
+ function indexOp(opName, indexName, args) {
+ return op(function(wrappedObjectStore) {
+ var index = wrappedObjectStore.index(indexName);
+ return index[opName].apply(index[opName], args);
+ });
+ }
+
+ var crud = ["add", "delete", "get", "put", "clear", "count", "each"];
+ for (var i = 0; i < crud.length; i++) {
+ result[crud[i]] = (function(op) {
+ return function() {
+ return crudOp(op, arguments);
+ };
+ })(crud[i]);
+ }
+
+ result.index = function(indexName) {
+ return {
+ "each": function(callback, range, direction) {
+ return indexOp("each", indexName, [callback, range, direction]);
+ },
+ "eachKey": function(callback, range, direction) {
+ return indexOp("eachKey", indexName, [callback, range, direction]);
+ },
+ "get": function(key) {
+ return indexOp("get", indexName, [key]);
+ },
+ "count": function() {
+ return indexOp("count", indexName, []);
+ },
+ "getKey": function(key) {
+ return indexOp("getKey", indexName, [key]);
+ }
+ };
+ };
+
+ return result;
+ }
+ });
+ }
+ });
+
+ $.indexedDB.IDBCursor = IDBCursor;
+ $.indexedDB.IDBTransaction = IDBTransaction;
+ $.idb = $.indexedDB;
+})(jQuery);
\ No newline at end of file
float: right;
padding-right: 1em;
}
+
+.loading-overlay {
+ background-color: #FFF;
+ cursor: wait;
+ height: 100%;
+ left: 0;
+ opacity: .7;
+ position: fixed;
+ top: 0;
+ width: 100%;
+ z-index: 1000;
+}
+.loading-overlay div {
+ background : transparent url(../../img/loading.gif) top left no-repeat;
+ font-size : 175%;
+ font-weight: bold;
+ height: 2em;
+ left: 50%;
+ margin: -1em 0 0 -2.5em;
+ padding-left : 50px;
+ position: absolute;
+ top: 50%;
+ width: 15em;
+}
--- /dev/null
+/* Copyright 2013 C & P Bibliography Services
+ *
+ * This file is part of Koha.
+ *
+ * Koha is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * Koha is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with Koha; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+(function( kohadb, $, undefined ) {
+ kohadb.settings = kohadb.settings || {};
+ kohadb.initialize = function (callback) {
+ $.indexedDB("koha", {
+ "version": 1,
+ "schema": {
+ "1": function(versionTransaction){
+ var patrons = versionTransaction.createObjectStore("patrons", {
+ "keyPath": "cardnumber"
+ });
+ var items = versionTransaction.createObjectStore("items", {
+ "keyPath": "barcode"
+ });
+ var issues = versionTransaction.createObjectStore("issues", {
+ "keyPath": "barcode"
+ });
+ issues.createIndex("cardnumber", { "multiEntry": true });
+ var transactions = versionTransaction.createObjectStore("transactions", {
+ "keyPath": "timestamp"
+ });
+ var settings = versionTransaction.createObjectStore("offline_settings", {
+ "keyPath": "key"
+ });
+ },
+ }
+ }).done(function(){
+ if (typeof callback === 'function') {
+ callback();
+ kohadb.loadSetting('userid');
+ kohadb.loadSetting('branchcode');
+ }
+ });
+ };
+ kohadb.loadSetting = function (key, callback) {
+ $.indexedDB("koha").transaction(["offline_settings"]).then(function(){
+ }, function(err, e){
+ }, function(transaction){
+ var settings = transaction.objectStore("offline_settings");
+ settings.get(key).done(function (item, error) {
+ if (typeof item !== 'undefined') {
+ kohadb.settings[key] = item.value;
+ }
+ if (typeof callback === 'function') {
+ callback(key, kohadb.settings[key]);
+ }
+ });
+ });
+ };
+ kohadb.saveSetting = function (key, value) {
+ $.indexedDB("koha").transaction(["offline_settings"]).then(function(){
+ }, function(err, e){
+ }, function(transaction){
+ var settings = transaction.objectStore("offline_settings");
+ settings.put({ "key" : key, "value" : value });
+ kohadb.settings[key] = value;
+ });
+ };
+ kohadb.recordTransaction = function (newtrans, callback) {
+ $.indexedDB("koha").transaction(["transactions"]).then(function(){
+ callback(newtrans);
+ }, function(err, e){
+ }, function(dbtransaction) {
+ var transactions = dbtransaction.objectStore("transactions");
+ transactions.put(newtrans);
+ });
+ };
+}( window.kohadb = window.bndb || {}, jQuery ));
+
+if ( !Date.prototype.toMySQLString ) {
+ ( function() {
+
+ function pad(number) {
+ var r = String(number);
+ if ( r.length === 1 ) {
+ r = '0' + r;
+ }
+ return r;
+ }
+
+ Date.prototype.toMySQLString = function() {
+ return this.getFullYear()
+ + '-' + pad( this.getMonth() + 1 )
+ + '-' + pad( this.getDate() )
+ + ' ' + pad( this.getHours() )
+ + ':' + pad( this.getMinutes() )
+ + ':' + pad( this.getSeconds() )
+ + '.' + String( (this.getMilliseconds()/1000).toFixed(3) ).slice( 2, 5 );
+ };
+
+ }() );
+}
- The following fields should be excluded from the patron checkout history CSV or iso2709 export
- pref: ExportRemoveFields
- (separate fields with space, e.g. 100a 200b 300c)
+ -
+ - pref: AllowOfflineCirculation
+ choices:
+ yes: Enable
+ no: "Do not enable"
+ - "offline circulation on regular circulation computers. (NOTE: This system preference does not affect the Firefox plugin or the desktop application)"
Checkout Policy:
-
<div class="yui-u">
<h5>Offline circulation</h5>
<ul>
- <li><a href="/cgi-bin/koha/offline_circ/process_koc.pl">Upload offline circulation file (.koc)</a></li>
- <li><a href="/cgi-bin/koha/offline_circ/list.pl">Pending offline circulation actions</a>
- <ul>
- <li><a href="http://kylehall.info/index.php/projects/koha/koha-offline-circulation/">Get desktop application</a></li>
- <li><a href="https://addons.mozilla.org/[% lang %]/firefox/addon/koct/">Get Firefox add-on</a></li>
- </ul>
+ [% IF (AllowOfflineCirculation) %]
+ <li><a href="/cgi-bin/koha/circ/offline.pl">Built-in offline circulation interface</a></li>
+ [% END %]
+ <li><a href="/cgi-bin/koha/offline_circ/process_koc.pl">Upload offline circulation file (.koc)</a></li>
+ <li><a href="/cgi-bin/koha/offline_circ/list.pl">Pending offline circulation actions</a>
+ <ul>
+ <li><a href="http://kylehall.info/index.php/projects/koha/koha-offline-circulation/">Get desktop application</a></li>
+ <li><a href="https://addons.mozilla.org/[% lang %]/firefox/addon/koct/">Get Firefox add-on</a></li>
+ </ul>
</ul>
</div>
</div>
--- /dev/null
+CACHE MANIFEST
+# [% cookie %]
+
+# Explicitly cached 'master entries'.
+CACHE:
+/cgi-bin/koha/circ/offline.pl
+/intranet-tmpl/lib/bootstrap/bootstrap.min.css
+/intranet-tmpl/lib/bootstrap/bootstrap.min.js
+/intranet-tmpl/lib/jquery/jquery-ui.css
+/intranet-tmpl/lib/jquery/jquery-ui.js
+/intranet-tmpl/lib/jquery/jquery.js
+/intranet-tmpl/lib/jquery/plugins/jquery.cookie.min.js
+/intranet-tmpl/lib/jquery/plugins/jquery.highlight-3.js
+/intranet-tmpl/lib/jquery/plugins/jquery.hotkeys.min.js
+/intranet-tmpl/lib/jquery/plugins/jquery.indexeddb.js
+/intranet-tmpl/lib/jquery/plugins/jquery.validate.min.js
+/intranet-tmpl/prog/en/css/print.css
+/intranet-tmpl/prog/en/css/staff-global.css
+/intranet-tmpl/prog/en/js/basket.js
+/intranet-tmpl/prog/en/js/offlinecirc.js
+/intranet-tmpl/prog/en/js/staff-global.js
+/intranet-tmpl/prog/en/lib/jquery/plugins/jquery-ui-timepicker-addon.js
+/intranet-tmpl/prog/en/lib/yui/button/button-min.js
+/intranet-tmpl/prog/en/lib/yui/container/container_core-min.js
+/intranet-tmpl/prog/en/lib/yui/menu/menu-min.js
+/intranet-tmpl/prog/en/lib/yui/reset-fonts-grids.css
+/intranet-tmpl/prog/en/lib/yui/skin.css
+/intranet-tmpl/prog/en/lib/yui/utilities/utilities.js
+/intranet-tmpl/prog/img/cart-small.gif
+/intranet-tmpl/prog/img/glyphicons-halflings-koha.png
+/intranet-tmpl/prog/img/koha-logo-medium.gif
+/intranet-tmpl/prog/img/loading.gif
+/intranet-tmpl/prog/sound/beep.ogg
+/intranet-tmpl/prog/sound/critical.ogg
+
+# Resources that require the user to be online.
+NETWORK:
+*
+
+# static.html will be served if main.py is inaccessible
+# offline.jpg will be served in place of all images in images/large/
+# offline.html will be served in place of all other .html files
+FALLBACK:
--- /dev/null
+<!DOCTYPE html>
+[% IF (AllowOfflineCirculation) %]
+[% SET manifestattr = 'manifest="/cgi-bin/koha/circ/offline-mf.pl"' %]
+[% END %]
+[% IF ( bidi ) %]<html lang="[% lang %]" dir="[% bidi %]" [% manifestattr %]>[% ELSE %]<html lang="[% lang %]" [% manifestattr %]>[% END %]
+<head>
+<title>Koha › Circulation</title>
+[% INCLUDE 'doc-head-close.inc' %]
+<script type="text/javascript" src="/intranet-tmpl/lib/jquery/plugins/jquery.indexeddb.js"></script>
+<script type="text/javascript" src="/intranet-tmpl/prog/en/js/offlinecirc.js"></script>
+<script type="text/javascript" src="[% themelang %]/lib/jquery/plugins/jquery-ui-timepicker-addon.js"></script>
+<script type="text/javascript">
+//<![CDATA[
+var ALERT_MATERIALS = _("Note about the accompanying materials: ");
+var ALERT_RESTRICTED = _("Patron is RESTRICTED");
+var ALERT_NO_MATCHING_ITEM = _("No item with barcode in offline database (transaction recorded anyway): ");
+var ALERT_NOT_CHECKED_OUT = _("Item not listed as checked out in offline database (transaction recorded anyway)");
+var ALERT_ITEM_WITHDRAWN = _("Item has been withdrawn (transaction recorded anyway)");
+var ALERT_ITEM_RESTRICTED = _("Item is restricted (transaction recorded anyway)");
+var ALERT_ITEM_LOST = _("Item is has been lost (transaction recorded anyway)");
+var ALERT_NO_MATCHING_PATRON = _("No patron cardnumber in offline database (proceeding anyway): ");
+var ALERT_PATRON_GONE_NO_ADDRESS = _("Patron's address is in doubt (transaction recorded anyway)");
+var ALERT_PATRON_CARD_LOST = _("Patron's card is lost");
+var ALERT_PATRON_EXPIRED = _("Patron's card is expired");
+var ALERT_PATRON_BLOCKED_TEMPORARY = _("Patron has had overdue items and is restricted for: ");
+var ALERT_PATRON_RESTRICTED = _("Patron is restricted");
+var ALERT_PATRON_FINE = _("Patron has outstanding fines: ");
+var ALERT_PATRON_FINE_OVER_LIMIT = _("Patron fines are over limit: ");
+
+var start;
+
+var dateformat = '[% IF ( dateformat_us ) %]mm/dd/yy[% ELSIF ( dateformat_metric ) %]dd/mm/yy[% ELSE %]yy-mm-dd[% END %]';
+
+function checkin(barcode, item, error) {
+ var alerts = checkAlerts(barcode, item);
+ if (typeof item === 'undefined') {
+ item = { };
+ }
+ item.title = item.title || _("(Unknown)");
+ item.author = item.author || _("(Unknown)");
+ item.homebranch = item.homebranch || "";
+ item.holdingbranch = item.holdingbranch || "";
+ item.callnumber = item.callnumber || "";
+ item.itemtype = item.itemtype || "";
+ item.barcode = item.barcode || barcode;
+ var trans = { "timestamp" : new Date().toMySQLString(),
+ "barcode" : barcode,
+ "action" : "return"
+ };
+ $('#alerts').empty();
+ $('#offline-home').hide();
+ $('#offline-returns').show();
+ kohadb.recordTransaction(trans, function () {
+ $('#already-checked-in tbody').prepend('<tr><td>' + item.title + '</td><td>' + item.author + '</td><td>' + barcode + '</td><td>' + item.homebranch + '</td><td>' + item.holdingbranch + '</td><td></td><td>' + item.callnumber + '</td><td>' + item.itemtype + '</td></tr>');
+ if (alerts.length > 0) {
+ $('#alerts').append('<div class="dialog alert"><h3>' + _("Check in message") + '</h3></div>');
+ for (var msg in alerts) {
+ $('#alerts .dialog').append('<p>' + alerts[msg] + '</p');
+ }
+ }
+ });
+}
+
+function checkAlerts(barcode, item) {
+ var alerts = [];
+ if (typeof item === 'undefined') {
+ alerts.push(ALERT_NO_MATCHING_ITEM + barcode);
+ } else {
+ if (typeof item.materials !== 'undefined' && item.materials != null) {
+ alerts.push(ALERT_MATERIALS + item.materials);
+ }
+ }
+ return alerts;
+}
+
+function synchronize() {
+ kohadb.saveSetting("userid", "[% loggedinusername %]");
+ kohadb.saveSetting("branchcode", "[% LoginBranchcode %]");
+ kohadb.loadSetting("item-timestamp", showTimestamp);
+ kohadb.loadSetting("patron-timestamp", showTimestamp);
+ kohadb.loadSetting("issue-timestamp", showTimestamp);
+ [% UNLESS (AllowOfflineCirculation) %]
+ reloadRecords();
+ [% END %]
+ $('#download-records').click(reloadRecords);
+ $('#upload-transactions').click(function () {
+ $('.loading-overlay div').text(_("Uploading transactions, please wait..."));
+ $('.loading-overlay').show();
+ var uploadIter = $.indexedDB("koha").objectStore("transactions").each(uploadTransaction);
+ uploadIter.done(function() {
+ $('.loading-overlay').hide();
+ });
+ });
+
+}
+
+function showTimestamp(key, value) {
+ if (typeof value !== 'undefined') {
+ var ts = new Date(value);
+ $('#' + key).text($.datepicker.formatDate(dateformat, ts) + ' ' + ts.toTimeString());
+ } else {
+ $('#' + key).text(_("(never)"));
+ }
+}
+
+function reloadRecords(ev) {
+ $(".loading-overlay div").text(_("Loading records, please wait..."));
+ $(".loading-overlay").show();
+ start = new Date();
+ $.indexedDB("koha").transaction(["patrons", "items", "issues"]).then(function(){
+ loadRecords(0);
+ }, function(err, e){
+ }, function(transaction){
+ transaction.objectStore("patrons").clear();
+ transaction.objectStore("items").clear();
+ transaction.objectStore("issues").clear();
+ });
+ if (typeof ev !== 'undefined') {
+ ev.stopPropagation();
+ }
+}
+
+function uploadTransaction(transaction) {
+ $.ajax({
+ type: "GET",
+ url: "/cgi-bin/koha/offline_circ/service.pl",
+ data: { "userid" : kohadb.settings.userid,
+ "branchcode" : kohadb.settings.branchcode,
+ "timestamp" : transaction.value.timestamp,
+ "action" : transaction.value.action,
+ "barcode" : transaction.value.barcode,
+ "cardnumber" : transaction.value.cardnumber,
+ "pending" : true,
+ },
+ }).done(function () {
+ transaction.delete();
+ });
+}
+
+function finishedLoading() {
+ kohadb.saveSetting('item-timestamp', start.toISOString())
+ kohadb.saveSetting('patron-timestamp', start.toISOString())
+ kohadb.saveSetting('issue-timestamp', start.toISOString())
+ showTimestamp('item-timestamp', start.toISOString());
+ showTimestamp('patron-timestamp', start.toISOString());
+ showTimestamp('issue-timestamp', start.toISOString());
+ $(".loading-overlay").hide();
+}
+
+function loadRecords(page) {
+[% IF (AllowOfflineCirculation) %]
+ $(".loading-overlay div").text(_("Loading page " + page + ", please wait..."));
+ $(".loading-overlay").show();
+ $.ajax({
+ type: "GET",
+ url: "/cgi-bin/koha/offline_circ/download.pl",
+ data: { "data": "all",
+ "page": page
+ },
+ dataType: "json",
+ }).done(function (data) {
+ $.indexedDB("koha").transaction(["patrons", "items", "issues"]).then(function(){
+ if ($.isEmptyObject(data.patrons) && $.isEmptyObject(data.items)) {
+ finishedLoading();
+ } else {
+ setTimeout(function () { loadRecords(page + 1); }, 200);
+ }
+ }, function(err, e){
+ }, function(transaction){
+ if (data.patrons) {
+ var patrons = transaction.objectStore("patrons");
+ $.each(data.patrons, function () {
+ patrons.put(this);
+ });
+ }
+ if (data.items) {
+ var items = transaction.objectStore("items");
+ $.each(data.items, function () {
+ items.put(this);
+ });
+ }
+ if (data.issues) {
+ var issues = transaction.objectStore("issues");
+ $.each(data.issues, function () {
+ issues.put(this);
+ });
+ }
+ });
+ });
+[% END %]
+}
+
+function validate1(date) {
+ var today = new Date();
+ if ( date < today ) {
+ return true;
+ } else {
+ return false;
+ }
+};
+
+function loadPatron(barcode) {
+ $('#oldissues').hide();
+ $('#session-issues').hide();
+ $('#session-payments').hide();
+ $.indexedDB("koha").transaction(["patrons", "issues"]).then(function() {
+ }, function(err, e){
+ }, function(transaction){
+ var patrons = transaction.objectStore("patrons");
+ patrons.get(barcode).done(function (patron, error) {
+ showPatron(barcode, patron, error);
+ });
+ var issuesidx = transaction.objectStore("issues").index("cardnumber");
+ $('#oldissuest tbody').empty();
+ issuesidx.each(function (item) {
+ $('#oldissues').show();
+ $('#oldissuest tbody').append("<tr><td>" + item.value.date_due + "</td><td>" + item.value.barcode + "</td><td>" + item.value.title + "</td><td>" + item.value.itype + "</td><td>" + item.value.issuedate + "</td><td>" + item.value.issuebranch + "</td><td>" + item.value.callnumber + "</td><td>" + "" + "</td></tr>");
+ }, barcode);
+ });
+}
+
+function checkout(barcode, item, error) {
+ var alerts = checkAlerts(barcode, item);
+ if (typeof item === 'undefined') {
+ item = { };
+ }
+ item.title = item.title || "";
+ item.author = item.author || "";
+ item.homebranch = item.homebranch || "";
+ item.holdingbranch = item.holdingbranch || "";
+ item.callnumber = item.callnumber || "";
+ item.itemtype = item.itemtype || "";
+ if ($('#duedatespec').val().length === 0) {
+ alert(_("You must set a due date in order to use offline circulation!"));
+ $('#duedatespec').focus();
+ return;
+ }
+ var date_due = new Date($('#duedatespec').datepicker('getDate'));
+ var trans = { "timestamp" : new Date().toMySQLString(),
+ "barcode" : barcode,
+ "cardnumber" : curpatron.cardnumber,
+ "date_due" : date_due.toMySQLString(),
+ "action" : "issue"
+ };
+ $('#alerts').empty();
+ kohadb.recordTransaction(trans, function () {
+ $('#session-issues').show();
+ $('#issuest tbody').prepend('<tr><td>' + $.datepicker.formatDate(dateformat, date_due) + date_due.toTimeString() + '</td><td>' + item.title + '</td><td>' + barcode + '</td><td>' + item.itemtype + '</td><td>' + $.datepicker.formatDate(dateformat, new Date()) + '</td><td>' + kohadb.settings.branchcode + '</td><td>' + item.callnumber + '</td><td></td></tr>');
+ if (alerts.length > 0) {
+ $('#alerts').append('<div class="dialog alert"><h3>' + _("Check out message") + '</h3></div>');
+ for (var msg in alerts) {
+ $('#alerts .dialog').append('<p>' + alerts[msg] + '</p');
+ }
+ }
+ });
+}
+
+function recordFine(amount) {
+ var timestamp = new Date()
+ var trans = { "timestamp" : timestamp.toMySQLString(),
+ "cardnumber" : curpatron.cardnumber,
+ "amount" : amount,
+ "action" : "payment",
+ };
+ kohadb.recordTransaction(trans, function () {
+ $('#session-payments').show();
+ $('#session-payments tbody').prepend('<tr><td>' + amount + '</td><td>' + $.datepicker.formatDate(dateformat, timestamp) + timestamp.toTimeString() + '</td></tr>');
+ });
+}
+
+function checkPatronAlerts(cardnumber, patron) {
+ var alerts = [];
+ if (typeof patron === 'undefined') {
+ alerts.push(ALERT_NO_MATCHING_PATRON + cardnumber);
+ } else {
+ if (patron.gonenoaddress !== '0') {
+ alerts.push(ALERT_PATRON_GONE_NO_ADDRESS);
+ }
+ if (patron.lost !== '0') {
+ alerts.push(ALERT_PATRON_CARD_LOST);
+ }
+ if (patron.debarred !== null) {
+ if (patron.debarred != '9999-12-31') {
+ alerts.push(ALERT_PATRON_BLOCKED_TEMPORARY + $.datepicker.formatDate(dateformat, patron.debarred));
+ } else {
+ alerts.push(ALERT_PATRON_RESTRICTED);
+ }
+ }
+ if (parseInt(patron.fine) > [% maxoutstanding %]) {
+ alerts.push(ALERT_PATRON_FINE_OVER_LIMIT + patron.fine);
+ } else if (parseInt(patron.fine) > 0) {
+ alerts.push(ALERT_PATRON_FINE + patron.fine);
+ }
+ }
+ return alerts;
+}
+
+var curpatron;
+
+function showPatron(barcode, patron, error) {
+ var alerts = checkPatronAlerts(barcode, patron);
+ if (typeof patron === 'undefined') {
+ patron = { };
+ }
+ patron.surname = patron.surname || "";
+ patron.firstname = patron.firstname || "";
+ patron.othernames = patron.othernames || "";
+ patron.address = patron.address || "";
+ patron.address2 = patron.address2 || "";
+ patron.city = patron.city || "";
+ patron.state = patron.state || "";
+ patron.country = patron.country || "";
+ patron.zipcode = patron.zipcode || "";
+ patron.phone = patron.phone || "";
+ patron.mobile = patron.mobile || "";
+ patron.phonepro = patron.phonepro || "";
+ patron.email = patron.email || "";
+ patron.emailpro = patron.emailpro || "";
+ patron.categorycode = patron.categorycode || "";
+ patron.branchcode = patron.branchcode || "";
+ patron.cardnumber = barcode;
+ patron.fine = patron.fine || "0";
+
+ patron.name = patron.firstname + (patron.othernames.length > 0 ? " (" + patron.othernames + ") " : " ") + patron.surname + " (" + barcode + ")";
+ if (patron.name.length > 0) {
+ $('.patron-title').text(patron.name);
+ } else {
+ $('.patron-title').text(_("Unrecognized patron") + " (" + barcode + ")");
+ }
+ if (patron.address.length > 0 || patron.address2.length > 0) {
+ $('#patron-address-1').text(patron.address);
+ $('#patron-address-2').text(patron.address2);
+ } else {
+ $('#patron-address-1').html('<span class="empty" id="noaddressstored">' + _("No address stored.") + '</span></li>');
+ $('#patron-address-2').text('');
+ }
+ if (patron.city.length > 0) {
+ $('#patron-address-parts').text(patron.city + (patron.state.length > 0 ? ", " + patron.state : "") + " " + patron.zipcode + (patron.country.length > 0 ? ", " + patron.country : ""));
+ } else {
+ $('#patron-address-parts').html('<span class="empty" id="nocitystored">' + _("No city stored.") + '</span></li>');
+ }
+ if (patron.phone.length > 0 || patron.mobile.length > 0 || patron.phonepro.length > 0) {
+ $('#patron-phone').text((patron.phone.length > 0 ? patron.phone : (patron.mobile.length > 0 ? patron.mobile : (patron.phonepro.length > 0 ? patron.phonepro : ''))));
+ } else {
+ $('#patron-phone').html('<span class="empty" id="nophonestored">' + _("No phone stored.") + '</span></li>');
+ }
+ if (patron.email.length > 0 || patron.emailpro.length > 0) {
+ $('#patron-email').text((patron.email.length > 0 ? patron.email : (patron.emailpro.length > 0 ? patron.emailpro : "")));
+ } else {
+ $('#patron-email').html('<span class="empty" id="noemailstored">' + _("No email stored.") + '</span></li>');
+ }
+ if (patron.categorycode.length > 0) {
+ $('#patron-category').text(_("Category: ") + patron.categorycode);
+ } else {
+ $('#patron-category').html('<span class="empty" id="unknowncategory">' + _("Category code unknown.") + '</span></li>');
+ }
+ if (patron.branchcode.length > 0) {
+ $('#patron-library').text(_("Home library: ") + patron.branchcode);
+ } else {
+ $('#patron-library').html('<span class="empty" id="unknowncategory">' + _("Home library unknown.") + '</span></li>');
+ }
+ $('.fine-amount').text(patron.fine);
+ $('#alerts').empty();
+ if (alerts.length > 0) {
+ $('#alerts').append('<div class="dialog alert"><h3>' + _("Check out message") + '</h3></div>');
+ for (var msg in alerts) {
+ $('#alerts .dialog').append('<p>' + alerts[msg] + '</p');
+ }
+ }
+ curpatron = patron;
+ $('#yui-main').show();
+ $('#barcode').focus();
+}
+
+// This next bit of code is to deal with the updated session issue
+window.addEventListener('load', function(e) {
+ window.applicationCache.addEventListener('updateready', function(e) {
+ if (window.applicationCache.status == window.applicationCache.UPDATEREADY) {
+ // Browser downloaded a new app cache.
+ // Swap it in and reload the page to get the new hotness.
+ window.applicationCache.swapCache();
+ if (confirm(_("A new version of this site is available. Load it?"))) {
+ window.location.reload();
+ }
+ } else {
+ // Manifest didn't changed. Nothing new to server.
+ }
+ }, false);
+}, false);
+
+
+$(document).ready(function () {
+ kohadb.initialize();
+
+ // Returns code
+ $('#checkin-form, #checkin_search form').submit(function (event) {
+ event.preventDefault();
+ var barcode = $('input[name="barcode"]', this).val();
+ $('input[name="barcode"]', this).val('');
+ $.indexedDB("koha").transaction(["items"]).then(function() {
+ }, function(err, e){
+ }, function(transaction){
+ var items = transaction.objectStore("items");
+ items.get(barcode).done(function (item, error) {
+ checkin(barcode, item, error);
+ });
+ });
+ });
+
+ $('#go-to-home').click(function () {
+ $('.offline-sync').hide();
+ $('.offline-circulation').hide();
+ $('.offline-returns').hide();
+ $('.offline-home').show();
+ });
+
+ $('#go-to-returns').click(function () {
+ $('.offline-home').hide();
+ $('.offline-sync').hide();
+ $('.offline-circulation').hide();
+ $('.offline-returns').show();
+ $('#checkin-form input[name="barcode"]').focus();
+ });
+
+ $('#go-to-circ').click(function () {
+ $('.offline-home').hide();
+ $('.offline-sync').hide();
+ $('.offline-returns').hide();
+ $('.offline-circulation').hide();
+ $('#header_search').tabs("option", "active", 0);
+ $('#circ_search input[name="findborrower"]').focus();
+ });
+
+ $('#go-to-sync').click(function () {
+ $.ajax({
+ type: "GET",
+ url: "/cgi-bin/koha/offline_circ/list.pl",
+ success: function () {
+ $('.offline-home').hide();
+ $('.offline-returns').hide();
+ $('.offline-circulation').hide();
+ $('.offline-sync').show();
+ synchronize();
+ },
+ error: function () {
+ alert(_("You are offline and therefore cannot sync your database"));
+ }
+ });
+ });
+
+ $('#patronsearch').submit(function (event) {
+ event.preventDefault();
+ loadPatron($('#findborrower').val());
+ $('.offline-home').hide();
+ $('.offline-returns').hide();
+ $('.offline-sync').hide();
+ $('.offline-circulation').show();
+ $('#findborrower').val('');
+ $('#barcode').focus();
+ });
+
+ $('#pay-fine').click(function (event) {
+ event.preventDefault();
+ recordFine($('#pay-fine-amount').val());
+ });
+
+ $('#patronlists').tabs();
+
+ $("#newduedate").datetimepicker({
+ minDate: 1, // require that renewal date is after today
+ hour: 23,
+ minute: 59
+ });
+ $("#duedatespec").datetimepicker({
+ onClose: function(dateText, inst) { $("#barcode").focus(); },
+ hour: 23,
+ minute: 59
+ });
+ $('#mainform').submit(function (event) {
+ event.preventDefault();
+ var barcode = $('#barcode').val();
+ $.indexedDB("koha").transaction(["items"]).then(function() {
+ }, function(err, e){
+ }, function(transaction){
+ var items = transaction.objectStore("items");
+ items.get(barcode).done(function (item, error) {
+ checkout(barcode, item, error);
+ });
+ });
+ });
+});
+//]]>
+</script>
+</head>
+<body id="circ_offline" class="circ">
+[% INCLUDE 'header.inc' %]
+[% INCLUDE 'circ-search.inc' %]
+<div class="loading-overlay" style="display: none;">
+ <div>Downloading records, please wait...</div>
+</div>
+
+<div id="breadcrumbs"><a href="/cgi-bin/koha/mainpage.pl">Home</a> › <a id="go-to-home" href="#offline-home">Offline circulation</a></div>
+
+<div id="doc3" class="yui-t2">
+
+ <div id="bd">
+ <div id="yui-main">
+ <audio id="alert_sound" src="/intranet-tmpl/prog/sound/critical.ogg" autobuffer="autobuffer"></audio>
+ <audio id="success_sound" src="/intranet-tmpl/prog/sound/beep.ogg" autobuffer="autobuffer"></audio>
+
+ <div id="alerts" class="yui-b">
+ </div>
+ [% UNLESS (AllowOfflineCirculation) %]
+ <div id="noofflinecircwarning" class="dialog alert">
+ <p><strong>Warning:</strong> Offline Circulation has been disabled. You may continue and record transactions, but patron and item information will not be available.</p>
+ </div>
+ [% END %]
+
+ <div id="offline-home" class="yui-b offline-home">
+ <div class="yui-g">
+ <h1>Offline circulation</h1>
+ <div class="yui-u first">
+ <ul>
+ <li><a id="go-to-circ" href="#offline-circulation">Check out</a></li>
+ <li><a id="go-to-returns" href="#offline-returns">Check in</a></li>
+ <li><a id="go-to-sync" href="#offline-sync">Synchronize (must be online)</a></li>
+ </ul>
+ </div>
+
+ <div class="yui-u">
+ <p><strong>Note:</strong> You must be online to use these options.</p>
+ <ul>
+ <li><a href="/cgi-bin/koha/offline_circ/list.pl">Pending offline circulation actions</a>
+ </ul>
+ </div>
+ </div>
+ </div>
+
+ <div id="offline-sync" style="display: none;" class="yui-b offline-sync">
+ <div id="toolbar" class="btn-toolbar">
+ [% IF (AllowOfflineCirculation) %]
+ <a href="#" id="download-records" class="btn btn-small"><i class="icon-arrow-down"></i>Download records</a>
+ [% END %]
+ <a href="#" id="upload-transactions" class="btn btn-small"><i class="icon-arrow-up"></i>Upload transactions</a>
+ </div>
+ <div class="yui-g">
+ <h1>Offline circulation</h1>
+ <div class="yui-u first">
+ <div id="download-message">
+ You have records in the offline circulation database on this
+ computer, but they may not be current:
+ <ul>
+ <li>Patron records were last synced on: <span id="patron-timestamp">(checking)</span></li>
+ <li>Item records were last synced on: <span id="item-timestamp">(checking)</span></li>
+ <li>Circulation records were last synced on: <span id="issue-timestamp">(checking)</span></li>
+ </ul>
+ </div>
+ </div>
+
+ <div class="yui-u">
+ <div id="upload-message">You have transactions in the offline
+ circulation database on this computer that have not been
+ uploaded.
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div id="offline-returns" style="display: none;" class="yui-b offline-returns">
+ <div class="yui-g">
+ <form id="checkin-form" method="post" action="/cgi-bin/koha/circ/returns.pl" autocomplete="off" >
+ <div class="yui-u first">
+ <fieldset>
+ <legend>Check In</legend>
+ <label for="barcode">Enter item barcode: </label>
+ <input name="barcode" id="barcode" size="14" class="focus"/>
+ <input type="submit" class="submit" value="Submit" />
+ </fieldset>
+ </div>
+ </form>
+ </div>
+
+ <div id="session-returned" style="display: none;">
+ <h2>Checked-in items</h2>
+ <table id="already-checked-in">
+ <thead>
+ <tr><th>Title</th><th>Author</th><th>Barcode</th><th>Home library</th><th>Holding library</th><th>Shelving location</th><th>Call number</th><th>Type</th></tr>
+ </thead>
+ <tbody>
+ </tbody>
+ </table>
+ </div>
+ </div>
+
+ <div id="offline-circulation" style="display: none;" class="yui-b offline-circulation">
+ <div class="yui-g">
+ <form method="post" action="/cgi-bin/koha/circ/offline-circulation.pl" id="mainform" name="mainform" autocomplete="off">
+ <fieldset id="circ_circulation_issue">
+ <span id="clearscreen"><a href="/cgi-bin/koha/circ/offline-circulation.pl" title="Clear screen">x</a></span>
+ <label for="barcode">Checking out to <span class="patron-title"></span></label>
+ <div class="hint">Enter item barcode:</div>
+ <input type="text" name="barcode" id="barcode" class="barcode focus" size="14" />
+ <input type="submit" value="Check Out" />
+
+ <div class="date-select">
+ <div class="hint">Specify due date [% INCLUDE 'date-format.inc' %]: </div>
+ <input type="text" size="13" id="duedatespec" name="duedatespec" value="[% duedatespec %]" readonly="readonly" />
+ <label for="stickyduedate"> Remember for session:</label>
+ <input type="checkbox" id="stickyduedate" onclick="this.form.barcode.focus();" name="stickyduedate" checked="checked" />
+ <input type="button" class="action" id="cleardate" value="Clear" name="cleardate" onclick="this.checked = false; this.form.duedatespec.value = ''; this.form.stickyduedate.checked = false; this.form.barcode.focus(); return false;" />
+ </div>
+ </fieldset>
+ </form>
+ </div>
+
+ <div class="yui-g"><div id="patronlists" class="toptabs">
+ <ul>
+ <li><a href="#checkouts"><span class="checkout-count">0</span> Checkouts</a></li>
+ <li><a href="#fines"><span class="fine-amount">0</span> in fines</a></li>
+ </ul>
+
+ <!-- SUMMARY : TODAY & PREVIOUS ISSUES -->
+ <div id="checkouts">
+ <div id="session-issues">
+ <table id="issuest">
+ <thead><tr>
+ <th scope="col">Due date</th>
+ <th scope="col">Title</th>
+ <th scope="col">Barcode</th>
+ <th scope="col">Item type</th>
+ <th scope="col">Checked out on</th>
+ <th scope="col">Checked out from</th>
+ <th scope="col">Call no</th>
+ <th scope="col">Charge</th>
+ </tr></thead>
+ <tbody>
+ </tbody>
+ </table>
+ </div>
+
+ <div id="oldissues">
+ <h5>Previous checkouts</h5>
+ <table id="oldissuest">
+ <thead><tr>
+ <th scope="col">Due date</th>
+ <th scope="col">Title</th>
+ <th scope="col">Barcode</th>
+ <th scope="col">Item type</th>
+ <th scope="col">Checked out on</th>
+ <th scope="col">Checked out from</th>
+ <th scope="col">Call no</th>
+ <th scope="col">Charge</th>
+ </tr></thead>
+ <tbody>
+ </tbody>
+ </table>
+ </div>
+ </div>
+
+ <div id="fines">
+ <span class="patron-title"></span> has <span class="fine-amount">0</span> in fines. If you would like you can record payments.
+ <fieldset><legend>Pay fines</legend>
+ <label for="pay-fine-amount">Fine amount: </label><input type="text" name="pay-fine-amount" id="pay-fine-amount"/>
+ <button id="pay-fine" class="submit">Pay fine</button>
+
+ <table id="session-payments" style="display: none;">
+ <thead><tr><th>Amount</th><th>Timestamp</th></tr></thead>
+ <tbody></tbody>
+ </table>
+ </fieldset>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div class="yui-b offline-circulation" style="display: none;">
+ <div class="patroninfo"><h5 class="patron-title"></h5>
+ <ul>
+ <li id="patron-address-1"></li>
+ <li id="patron-address-2"></li>
+ <li id="patron-address-parts"><!-- city, state, zipcode, country --></li>
+ <li id="patron-phone"></li>
+ <li id="patron-email"></li>
+ <li id="patron-category"></li>
+ <li id="patron-library"></li>
+ </ul>
+ </div>
+
+[% INCLUDE 'intranet-bottom.inc' %]
--- /dev/null
+#!/usr/bin/perl
+
+# Copyright 2013 C & P Bibliography Services
+#
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
+# Suite 330, Boston, MA 02111-1307 USA
+#
+
+use Modern::Perl;
+use CGI;
+use JSON;
+use C4::Auth;
+use C4::Output;
+use C4::Context;
+use C4::Koha;
+
+my $query = new CGI;
+my ( $template, $loggedinuser, $cookie, $flags ) =
+ checkauth( $query, undef, { circulate => "circulate_remaining_permissions" },
+ "intranet" );
+
+my $page = $query->param('page') || 0;
+my $startrec = int($page) * 5000;
+my $req_data = $query->param('data') || '';
+
+my $patrons_query = qq{SELECT
+ borrowers.borrowernumber, cardnumber, surname, firstname, title,
+ othernames, initials, streetnumber, streettype, address, address2, city,
+ state, zipcode, country, email, phone, mobile, fax, dateofbirth, branchcode,
+ categorycode, dateenrolled, dateexpiry, gonenoaddress, lost, debarred,
+ debarredcomment, SUM(accountlines.amountoutstanding) AS fine
+ FROM borrowers
+ LEFT JOIN accountlines ON borrowers.borrowernumber=accountlines.borrowernumber
+ GROUP BY borrowers.borrowernumber
+ LIMIT $startrec, 5000;
+ };
+
+# NOTE: we can't fit very long titles on the interface so there isn't really any point in transferring them
+my $items_query = qq{SELECT
+ items.barcode AS barcode, items.itemnumber AS itemnumber,
+ items.itemcallnumber AS callnumber, items.homebranch AS homebranch,
+ items.holdingbranch AS holdingbranch, items.itype AS itemtype,
+ items.materials AS materials, LEFT(biblio.title, 60) AS title,
+ biblio.author AS author, biblio.biblionumber AS biblionumber
+ FROM items
+ JOIN biblio ON biblio.biblionumber = items.biblionumber
+ LIMIT $startrec, 5000;
+ };
+
+my $issues_query = qq{SELECT
+ biblio.title AS title,
+ items.barcode AS barcode,
+ items.itemcallnumber AS callnumber,
+ issues.date_due AS date_due,
+ issues.issuedate AS issuedate,
+ issues.renewals AS renewals,
+ borrowers.cardnumber AS cardnumber,
+ CONCAT(borrowers.surname, ', ', borrowers.firstname) AS borrower_name
+ FROM issues
+ JOIN items ON items.itemnumber = issues.itemnumber
+ JOIN biblio ON biblio.biblionumber = items.biblionumber
+ JOIN borrowers ON borrowers.borrowernumber = issues.borrowernumber
+ LIMIT $startrec, 5000;
+ };
+
+if ( $req_data eq 'all' ) {
+ print $query->header( -type => 'application/json', -charset => 'utf-8' );
+ print to_json(
+ {
+ 'patrons' => get_data( $patrons_query, 'cardnumber' ),
+ 'items' => get_data( $items_query, 'barcode' ),
+ 'issues' => get_data( $issues_query, 'barcode' ),
+ }
+ );
+}
+elsif ( $req_data eq 'patrons' ) {
+ print $query->header( -type => 'application/json', -charset => 'utf-8' );
+ print to_json( { 'patrons' => get_data( $patrons_query, 'cardnumber' ), } );
+}
+elsif ( $req_data eq 'items' ) {
+ print $query->header( -type => 'application/json', -charset => 'utf-8' );
+ print to_json( { 'items' => get_data( $items_query, 'barcode' ), } );
+}
+elsif ( $req_data eq 'issues' ) {
+ print $query->header( -type => 'application/json', -charset => 'utf-8' );
+ print to_json( { 'issues' => get_data( $issues_query, 'barcode' ), } );
+}
+
+sub get_data {
+ my ( $sql, $key ) = @_;
+ my $dbh = C4::Context->dbh;
+ my $sth = $dbh->prepare($sql);
+ $sth->execute();
+ return $sth->fetchall_hashref($key);
+}
# get the status of the user, this will check his credentials and rights
my ($status, $cookie, $sessionId) = C4::Auth::check_api_auth($cgi, undef);
+($status, $sessionId) = C4::Auth::check_cookie_auth($cgi, undef) if ($status ne 'ok');
my $result;