Bug 33961: Remove built-in offline circ tool

The tool has not been updated and is no longer working with modern
browser.

It should either be rewritten/adjusted or removed. Given that we didn't
get complains its non-functional status, bugs related to this tool
didn't get attention, and the community is lacking resources, I am
suggesting to remove it and redirect users to the koct FF plugin that
is known to be working.

Test plan:
See bug 10240 and use `git grep` to confirm that we are removing all
tracks of this feature.

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Katrin Fischer <katrin.fischer.83@web.de>
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
This commit is contained in:
Jonathan Druart 2023-06-08 17:00:31 +02:00 committed by Tomas Cohen Arazi
parent 5308697ab8
commit 3248afdc12
No known key found for this signature in database
GPG Key ID: 0A272EA1B2F3C15F
14 changed files with 15 additions and 1671 deletions

View File

@ -225,7 +225,6 @@ sub _shared_preferences {
SuspendHoldsOpac
TransferWhenCancelAllWaitingHolds
AllowAllMessageDeletion
AllowOfflineCirculation
PatronAutoComplete
CircAutoPrintQuickSlip
DisplayClearScreenButton

View File

@ -37,6 +37,4 @@ my ($template, $loggedinuser, $cookie, $flags) = get_template_and_user(
# Checking if there is a Fast Cataloging Framework
$template->param( fast_cataloging => 1 ) if Koha::BiblioFrameworks->find( 'FA' );
$template->{'VARS'}->{'AllowOfflineCirculation'} = C4::Context->preference('AllowOfflineCirculation');
output_html_with_http_headers $query, $cookie, $template->output;

View File

@ -1,35 +0,0 @@
#!/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, see <http://www.gnu.org/licenses>.
use Modern::Perl;
use CGI qw ( -utf8 );
use C4::Auth qw( get_template_and_user );
my $query = CGI->new;
my ( $template, $loggedinuser, $cookie, $flags ) = get_template_and_user(
{
template_name => "circ/offline-mf.tt",
query => $query,
type => "intranet",
flagsrequired => { circulate => "circulate_remaining_permissions" },
}
);
$template->{'VARS'}->{'cookie'} = $cookie;
print $query->header( -type => 'text/cache-manifest', cookie => $cookie );
print $template->output;

View File

@ -1,39 +0,0 @@
#!/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, see <http://www.gnu.org/licenses>.
use Modern::Perl;
use CGI qw ( -utf8 );
use C4::Auth qw( get_template_and_user );
use C4::Output qw( output_html_with_http_headers );
use C4::Context;
my $query = CGI->new;
my ( $template, $loggedinuser, $cookie, $flags ) = get_template_and_user(
{
template_name => "circ/offline.tt",
query => $query,
type => "intranet",
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;

View File

@ -0,0 +1,15 @@
use Modern::Perl;
return {
bug_number => "33961",
description => "Remove Offline circulation tool",
up => sub {
my ($args) = @_;
my ($dbh, $out) = @$args{qw(dbh out)};
$dbh->do(q{
DELETE FROM systempreferences
WHERE variable="AllowOfflineCirculation"
});
say $out "Removed system preference 'AllowOfflineCirculation'";
},
};

View File

@ -35,7 +35,6 @@ INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, `
('AllowMultipleCovers','0','1','Allow multiple cover images to be attached to each bibliographic record.','YesNo'),
('AllowMultipleIssuesOnABiblio',1,'Allow/Don\'t allow patrons to check out multiple items from one biblio','','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'),
('AllowPatronToControlAutorenewal','0',NULL,'If enabled, patrons will have a field in their account to choose whether their checkouts are auto renewed or not','YesNo'),
('AllowPatronToSetCheckoutsVisibilityForGuarantor', '0', NULL, 'If enabled, the patron can set checkouts to be visible to their guarantor', 'YesNo'),
('AllowPatronToSetFinesVisibilityForGuarantor', '0', NULL, 'If enabled, the patron can set fines to be visible to their guarantor', 'YesNo'),

View File

@ -1,517 +0,0 @@
(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);

View File

@ -3549,31 +3549,6 @@ span {
border-bottom-left-radius: 5px;
}
.loading-overlay {
background-color: #FFFFFF;
cursor: wait;
height: 100%;
left: 0;
opacity: .7;
position: fixed;
top: 0;
width: 100%;
z-index: 1000;
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;
}
}
#merge_invoices {
display: none;
margin: 1em auto;

View File

@ -145,12 +145,6 @@ Circulation:
- "The following fields should be excluded from the patron checkout history CSV or MARC (ISO 2709) export:"
- pref: ExportRemoveFields
- (separate fields with space, e.g. 100a 200b 300c).
-
- pref: AllowOfflineCirculation
choices:
1: Enable
0: "Don't enable"
- "offline circulation on regular circulation computers. (<strong>NOTE:</strong> This system preference does not affect the Firefox add-on or the desktop application)."
-
- pref: ShowAllCheckins
choices:

View File

@ -164,9 +164,6 @@
<div class="col-md-12">
<h4>Offline circulation</h4>
<div class="col-sm-5 col-md-3">
[% IF (AllowOfflineCirculation) %]
<a href="/cgi-bin/koha/circ/offline.pl">Built-in offline circulation interface</a>
[% END %]
<p><a href="/cgi-bin/koha/offline_circ/process_koc.pl">Upload offline circulation file (.koc)</a></p>
<p><a href="/cgi-bin/koha/offline_circ/list.pl">Pending offline circulation actions</a></p>
</div>

View File

@ -1,33 +0,0 @@
CACHE MANIFEST
# [% cookie | html %]
# Explicitly cached 'master entries'.
CACHE:
/cgi-bin/koha/circ/offline.pl
[% interface | html %]/lib/bootstrap/bootstrap.min.css
[% interface | html %]/lib/bootstrap/bootstrap-theme.min.css
[% interface | html %]/lib/bootstrap/bootstrap.min.js
[% interface | html %]/lib/jquery/jquery-ui-1.13.1.min.css
[% interface | html %]/lib/jquery/jquery-ui-1.13.1.min.js
[% interface | html %]/lib/jquery/jquery-3.6.0.min.js
[% interface | html %]/lib/jquery/jquery-migrate-3.3.2.min.js
[% interface | html %]/lib/jquery/plugins/jquery.highlight-3.js
[% interface | html %]/lib/shortcut/shortcut.js
[% interface | html %]/lib/jquery/plugins/jquery.indexeddb.js
[% interface | html %]/lib/jquery/plugins/jquery.validate.min.js
[% interface | html %]/[% theme | html %]/css/print.css
[% interface | html %]/[% theme | html %]/css/staff-global.css
[% interface | html %]/[% theme | html %]/js/basket.js
[% interface | html %]/[% theme | html %]/js/offlinecirc.js
[% interface | html %]/[% theme | html %]/js/staff-global.js
[% interface | html %]/prog/img/koha-logo-medium.png
[% interface | html %]/prog/img/loading.gif
[% interface | html %]/prog/sound/beep.ogg
[% interface | html %]/prog/sound/critical.ogg
# Resources that require the user to be online.
NETWORK:
*
# Resources that can be substituted if the user is offline
FALLBACK:

View File

@ -1,788 +0,0 @@
[% USE raw %]
[% USE Asset %]
[% USE Branches %]
[% SET footerjs = 1 %]
<!DOCTYPE html>
[% IF (AllowOfflineCirculation) %]
[% SET manifestattr = 'manifest="/cgi-bin/koha/circ/offline-mf.pl"' %]
[% END %]
[% IF ( bidi && AllowOfflineCirculation ) %]<html lang="[% lang | html %]" dir="[% bidi | html %]" manifest="/cgi-bin/koha/circ/offline-mf.pl">
[% ELSIF ( bidi ) %]<html lang="[% lang | html %]" dir="[% bidi | html %]">
[% ELSIF ( AllowOfflineCirculation ) %]<html lang="[% lang | html %]" manifest="/cgi-bin/koha/circ/offline-mf.pl">
[% ELSE %]<html lang="[% lang | html %]">[% END %]
<head>
<title>Circulation &rsaquo; Koha</title>
[% INCLUDE 'doc-head-close.inc' %]
</head>
<body id="circ_offline" class="circ">
[% WRAPPER 'header.inc' %]
[% INCLUDE 'circ-search.inc' %]
[% END %]
<div class="loading-overlay" style="display: none;">
<div>Downloading records, please wait...</div>
</div>
[% WRAPPER 'sub-header.inc' %]
[% WRAPPER breadcrumbs %]
[% WRAPPER breadcrumb_item %]
<a id="go-to-home" href="#offline-home" aria-current="page">Offline circulation</a>
[% END %]
[% END #/ WRAPPER breadcrumbs %]
[% END #/ WRAPPER sub-header.inc %]
<div class="main container-fluid">
<div class="row">
<div class="col-sm-10 col-sm-push-2">
<main>
<h1>Offline circulation</h1>
<audio id="alert_sound" src="[% interface | html %]/prog/sound/critical.ogg" preload="auto"></audio>
<audio id="success_sound" src="[% interface | html %]/prog/sound/beep.ogg" preload="auto"></audio>
<div id="alerts"></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="offline-home">
<div class="row">
<div class="col-sm-6">
<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>
</ul>
</div>
<div class="col-sm-6">
<p><strong>Note:</strong> You must be online to use these options.</p>
<ul>
<li><a id="go-to-sync" href="#offline-sync">Synchronize</a></li>
<li><a id="go-to-pending" 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="offline-sync">
<div id="toolbar" class="btn-toolbar">
[% IF (AllowOfflineCirculation) %]
<a href="#" id="download-records" class="btn btn-default"><i class="fa fa-arrow-down"></i> Download records</a>
[% END %]
<a href="#" id="upload-transactions" class="btn btn-default"><i class="fa fa-arrow-up"></i> Upload transactions</a>
</div>
<h1>Offline circulation</h1>
<div class="row">
<div class="col-sm-6">
<div id="download-message">
In order for offline circulation to work on this computer,
your library's records must be up-to-date on this computer:
<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="col-sm-6">
<div id="upload-message">
</div>
<div>View <a href="/cgi-bin/koha/offline_circ/list.pl">pending offline circulation actions</a></div>
</div>
</div>
</div>
<div id="offline-returns" style="display: none;" class="offline-returns">
<div class="row">
<form id="checkin-form" method="post" action="/cgi-bin/koha/circ/returns.pl" autocomplete="off" >
<div class="col-sm-6">
<fieldset>
<legend>Check in</legend>
<label for="checkin-barcode">Enter item barcode: </label>
<input name="barcode" id="checkin-barcode" size="14" class="focus" type="text" />
<input type="submit" class="btn btn-primary" 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 style="display: none;" class="offline-circulation-instructions">
Scan a patron barcode to start.
</div>
<div id="offline-circulation" style="display: none;" class="offline-circulation">
<form method="post" action="/cgi-bin/koha/circ/offline.pl" id="mainform" name="mainform" autocomplete="off">
<fieldset id="circ_circulation_issue">
<span id="clearscreen"><a href="/cgi-bin/koha/circ/offline.pl" title="Clear screen">x</a></span>
<label for="checkout-barcode">Checking out to <span class="patron-title"></span></label>
<div class="hint">Enter item barcode:</div>
<input type="text" name="barcode" id="checkout-barcode" class="barcode focus" size="14" />
<input type="submit" class="btn btn-primary" 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 | html %]" class="flatpickr" data-flatpickr-enable-time="true" data-flatpickr-on-close-focus="#checkout-barcode" />
<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 id="patronlists" class="toptabs">
<ul class="nav nav-tabs" role="tablist">
<li role="presentation" class="active"><a href="#checkouts" aria-controls="checkouts" role="tab" data-toggle="tab"><span class="checkout-count">0</span> Checkouts</a></li>
<li role="presentation"><a href="#fines" aria-controls="fines" role="tab" data-toggle="tab"><span class="fine-amount">0</span> in fines</a></li>
</ul>
<div class="tab-content">
<!-- SUMMARY : TODAY & PREVIOUS ISSUES -->
<div role="tabpanel" class="tab-pane active" 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 number</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 number</th>
<th scope="col">Charge</th>
</tr></thead>
<tbody>
</tbody>
</table>
</div>
</div>
<div role="tabpanel" class="tab-pane" 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>Date</th></tr></thead>
<tbody></tbody>
</table>
</fieldset>
</div> <!-- /# -->fines
</div> <!-- /.tab-content -->
</div> <!-- /#patronlists -->
</div>
</div>
</main>
</main>
</div> <!-- /.col-sm-10.col-sm-push-2 -->
<div class="offline-circulation" style="display: none;">
<div class="col-sm-2 col-sm-pull-10">
<aside>
<div class="patroninfo"><h5 class="patron-title">&nbsp;</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>
</aside>
</div> <!-- /.col-sm-2.col-sm-pull-10 -->
</div> <!-- /.row -->
</div>
[% MACRO jsinclude BLOCK %]
[% INCLUDE 'calendar.inc' %]
[% Asset.js("lib/jquery/plugins/jquery.indexeddb.js") | $raw %]
[% Asset.js("js/offlinecirc.js") | $raw %]
<script>
var ALERT_SUCCESSFUL_CHECKIN = _("Checked in item.");
var ALERT_MATERIALS = _("Note about the accompanying materials: %s");
var ALERT_RESTRICTED = _("Patron is RESTRICTED");
var ALERT_NO_MATCHING_ITEM = _("No item with barcode in offline database (transaction recorded anyway): %s");
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 has been lost (transaction recorded anyway)");
var ALERT_NO_MATCHING_PATRON = _("No patron card number in offline database (proceeding anyway): %s");
var ALERT_PATRON_GONE_NO_ADDRESS = _("Patron's address is in doubt (proceeding anyway)");
var ALERT_PATRON_CARD_LOST = _("Patron's card is lost");
var ALERT_PATRON_EXPIRED = _("Patron's card is expired (%s)");
var ALERT_PATRON_BLOCKED_TEMPORARY = _("Patron has had overdue items and is restricted until: %s");
var ALERT_PATRON_RESTRICTED = _("Patron is restricted");
var ALERT_PATRON_FINE = _("Patron has outstanding fines: %s");
var ALERT_PATRON_FINE_OVER_LIMIT = _("Patron fines are over limit: %s");
var UPLOAD_PENDING_MESSAGE = _("You have transactions in the offline circulation database on this computer that have not been uploaded.");
var NO_UPLOAD_PENDING_MESSAGE = _("You do not have any pending transactions in the offline circulation database on this computer.");
var start;
var dateformat = '[% IF ( dateformat_us ) %]mm/dd/yy[% ELSIF ( dateformat_metric ) %]dd/mm/yy[% ELSE %]yy-mm-dd[% END %]';
var flatpickr_dateformat_string = [% IF ( dateformat == "us" ) %]"m/d/Y"[% ELSIF ( dateformat == "metric" ) %]"d/m/Y"[% ELSIF ( dateformat == "dmydot" ) %]"d.m.Y"[% ELSE %]"Y-m-d"[% END %];
var flatpickr_timeformat_string = [% IF Koha.Preference('TimeFormat') == '12hr' %]"G:i K"[% ELSE %]"H:i"[% END %];
var flatpickr_timestamp_string = "Y-m-d H:i";
var flatpickr_datetime_string = flatpickr_dateformat_string + " " + flatpickr_timeformat_string;
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-sync').hide();
$('.offline-circulation').hide();
$('.offline-circulation-instructions').hide();
$('.offline-returns').show();
kohadb.recordTransaction(trans, function () {
$('#session-returned').show();
$('#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');
}
} else {
$('#alerts').append('<div class="dialog"><h3>' + ALERT_SUCCESSFUL_CHECKIN + '</h3></div>');
}
});
setTimeout(function() { $('#checkin-barcode').trigger('focus'), 1 });
}
function checkAlerts(barcode, item) {
var alerts = [];
if (typeof item === 'undefined') {
alerts.push(ALERT_NO_MATCHING_ITEM.format(barcode));
} else {
if (typeof item.materials !== 'undefined' && item.materials != null) {
alerts.push(ALERT_MATERIALS.format(item.materials));
}
}
return alerts;
}
function showSyncInfo() {
kohadb.loadSetting("item-timestamp", showTimestamp);
kohadb.loadSetting("patron-timestamp", showTimestamp);
kohadb.loadSetting("issue-timestamp", showTimestamp);
kohadb.loadSetting("dirty", function (key, val) {
if (val) {
$('#upload-message').text(UPLOAD_PENDING_MESSAGE);
} else {
$('#upload-message').text(NO_UPLOAD_PENDING_MESSAGE);
}
});
}
function synchronize() {
kohadb.saveSetting("userid", "[% logged_in_user.userid | html %]");
kohadb.saveSetting("branchcode", "[% Branches.GetLoggedInBranchcode | html %]");
showSyncInfo();
[% UNLESS (AllowOfflineCirculation) %]
reloadRecords();
[% END %]
showSyncInfo();
$('#download-records').click(reloadRecords);
$('#upload-transactions').click(function () {
$('.loading-overlay div').text(_("Uploading transactions, please wait..."));
$('.loading-overlay').show();
$.ajax({
type: "GET",
url: "/cgi-bin/koha/offline_circ/service.pl",
}).done(function (data) {
if (data) {
$('.loading-overlay').hide();
alert(_("Please log in to Koha and try again. (Error: '%s')").format(data));
} else {
var uploadIter = $.indexedDB("koha").objectStore("transactions").each(uploadTransaction);
uploadIter.done(function() {
$.indexedDB("koha").transaction(["transactions"]).then(function(){
}, function(err, e){
}, function(transaction){
transaction.objectStore("transactions").clear();
});
$('.loading-overlay').hide();
kohadb.saveSetting("dirty", false);
$('#upload-message').text(NO_UPLOAD_PENDING_MESSAGE);
});
}
});
});
}
function fPformatTimeStamp( timestamp ){
var datetimeObj = flatpickr.parseDate( timestamp, flatpickr_timestamp_string );
return flatpickr.formatDate( datetimeObj, flatpickr_datetime_string );
}
function showTimestamp(key, value) {
if (typeof value !== 'undefined') {
var ts = new Date(value);
$('#' + key).text( fPformatTimeStamp( 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: "POST",
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,
"amount" : transaction.value.amount,
"pending" : true,
},
});
return undefined, true;
}
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 %s, please wait...").format(page));
$(".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 (data.finished) {
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 loadPatron(barcode) {
$('#oldissues').hide();
$('#session-issues').hide();
$('#issuest tbody').empty();
$('#session-payments').hide();
$('.checkout-count').text(0);
$.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>" + fPformatTimeStamp( item.value.date_due ) + "</td><td>" + item.value.title + "</td><td>" + item.value.barcode + "</td><td>" + item.value.itype + "</td><td>" + fPformatTimeStamp( item.value.issuedate ) + "</td><td>" + item.value.issuebranch + "</td><td>" + item.value.callnumber + "</td><td>" + "" + "</td></tr>");
$('.checkout-count').text(parseInt($('.checkout-count').text()) + 1);
}, 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!"));
setTimeout(function() { $('#duedatespec').trigger('focus'), 1 });
return;
}
var date_due = flatpickr.parseDate( $('#duedatespec').val(), flatpickr_datetime_string );
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>' + flatpickr.formatDate( date_due, flatpickr_datetime_string ) + '</td><td>' + item.title + '</td><td>' + barcode + '</td><td>' + item.itemtype + '</td><td>' + flatpickr.formatDate( new Date(), flatpickr_datetime_string ) + '</td><td>' + kohadb.settings.branchcode + '</td><td>' + item.callnumber + '</td><td></td></tr>');
$('.checkout-count').text(parseInt($('.checkout-count').text()) + 1);
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');
}
}
$('#checkout-barcode').val('');
});
}
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>' + fPformatTimeStamp( timestamp ) + '</td></tr>');
$('.fine-amount').text(parseInt($('.fine-amount').text()) - amount);
});
}
function checkPatronAlerts(cardnumber, patron) {
var alerts = [];
if (typeof patron === 'undefined') {
alerts.push(ALERT_NO_MATCHING_PATRON.format(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.format( flatpickr.formatDate( new Date(patron.debarred), flatpickr_datetime_string ) ) );
} else {
alerts.push(ALERT_PATRON_RESTRICTED);
}
}
if (new Date(patron.dateexpiry) < new Date()) {
alerts.push( ALERT_PATRON_EXPIRED.format( flatpickr.formatDate( new Date(patron.dateexpiry), flatpickr_datetime_string ) ) );
}
if (parseInt(patron.fine) > [% maxoutstanding | html %]) {
alerts.push(ALERT_PATRON_FINE_OVER_LIMIT.format(patron.fine));
} else if (parseInt(patron.fine) > 0) {
alerts.push(ALERT_PATRON_FINE.format(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 (%s)").format(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: %s").format(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: %s").format(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;
$('main').show();
setTimeout(function() { $('#checkout-barcode').trigger('focus'), 1 });
}
// This next bit of code is to deal with the updated session issue
if( typeof window.applicationCache !== "undefined" ){
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 () {
if( typeof window.applicationCache === "undefined" ){
$("#offline-home div").html('<div class="dialog alert"><h3>' + _("Offline circulation disabled") + '</h3><p>' + _("Your browser is not compatible with the built-in offline circulation tool.") + '</div>');
} else {
kohadb.initialize();
}
$('#header_search #circ_search .tip').text(_("Enter patron card number:"));
$('ul[aria-labelledby="logged-in-menu"]').html('<li><a class="toplinks">' + _("You cannot change your branch or logout while using offline circulation") + '</a></li>');
// 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 () {
$('#alerts').empty();
$('.offline-sync').hide();
$('.offline-circulation').hide();
$('.offline-returns').hide();
$('.offline-circulation-instructions').hide();
$('.offline-home').show();
});
$('#go-to-returns').click(function () {
$('#alerts').empty();
$('.offline-home').hide();
$('.offline-sync').hide();
$('.offline-circulation').hide();
$('.offline-circulation-instructions').hide();
$('.offline-returns').show();
setTimeout(function() { $('#checkin-form input[name="barcode"]').trigger('focus'), 1 });
});
$('#go-to-circ').click(function () {
$('#alerts').empty();
$('.offline-home').hide();
$('.offline-sync').hide();
$('.offline-returns').hide();
$('.offline-circulation').hide();
$('.offline-circulation-instructions').show();
$('#header_search').selectTabByID("#circ_search");
setTimeout(function() { $('#circ_search input[name="findborrower"]').trigger('focus'), 1 });
});
$('#go-to-sync').click(function () {
$('#alerts').empty();
showSyncInfo();
$.ajax({
type: "GET",
url: "/cgi-bin/koha/offline_circ/list.pl",
success: function () {
$('.offline-home').hide();
$('.offline-returns').hide();
$('.offline-circulation').hide();
$('.offline-circulation-instructions').hide();
$('.offline-sync').show();
synchronize();
},
error: function () {
alert(_("You are offline and therefore cannot sync your database"));
}
});
});
$('#go-to-pending').click(function (ev) {
$('#alerts').empty();
ev.preventDefault();
$.ajax({
type: "GET",
url: "/cgi-bin/koha/offline_circ/list.pl",
success: function () {
window.location = '/cgi-bin/koha/offline_circ/list.pl';
},
error: function () {
alert(_("You are offline and therefore cannot process pending operations"));
}
});
});
$('#patronsearch').submit(function (event) {
event.preventDefault();
loadPatron($('#findborrower').val());
$('.offline-home').hide();
$('.offline-returns').hide();
$('.offline-sync').hide();
$('.offline-circulation-instructions').hide();
$('.offline-circulation').show();
$('#findborrower').val('');
setTimeout(function() { $('#checkout-barcode').trigger('focus'), 1 });
});
$('#pay-fine').click(function (event) {
event.preventDefault();
recordFine($('#pay-fine-amount').val());
});
$('#mainform').submit(function (event) {
event.preventDefault();
var barcode = $('#checkout-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>
[% END %]
[% INCLUDE 'intranet-bottom.inc' %]

View File

@ -1,113 +0,0 @@
/* 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, see <http://www.gnu.org/licenses>.
*/
(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){
if (typeof callback === 'function') {
callback(key, undefined);
}
}, 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);
kohadb.saveSetting('dirty', true);
});
};
}( window.kohadb = window.kohadb || {}, 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 );
};
}() );
}

View File

@ -1,108 +0,0 @@
#!/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, see <http://www.gnu.org/licenses>.
use Modern::Perl;
use CGI qw ( -utf8 );
use JSON qw( to_json );
use C4::Auth qw( checkauth );
use C4::Output;
use C4::Context;
use C4::Koha;
my $query = CGI->new;
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 = q{SELECT
borrowers.borrowernumber, cardnumber, surname, firstname, title,
othernames, initials, streetnumber, streettype, address, address2, city,
state, zipcode, country, email, phone, mobile, fax, dateofbirth, borrowers.branchcode,
categorycode, dateenrolled, dateexpiry, COALESCE(gonenoaddress, 0) AS gonenoaddress,
COALESCE(lost, 0) AS lost, debarred,
debarredcomment, SUM(accountlines.amountoutstanding) AS fine
FROM borrowers
LEFT JOIN accountlines ON borrowers.borrowernumber=accountlines.borrowernumber
WHERE cardnumber IS NOT NULL
GROUP BY borrowers.borrowernumber
LIMIT ?, 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 = q{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
WHERE barcode IS NOT NULL
LIMIT ?, 5000;
};
my $issues_query = q{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_count AS renewals,
issues.unseen_renewals AS unseen_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
WHERE barcode IS NOT NULL
LIMIT ?, 5000;
};
my %results;
my $finished = 1;
if ( $req_data eq 'patrons' || $req_data eq 'all' ) {
$results{'patrons'} = get_data( $patrons_query, 'cardnumber', $startrec );
}
if ( $req_data eq 'items' || $req_data eq 'all' ) {
$results{'items'} = get_data( $items_query, 'barcode', $startrec );
}
if ( $req_data eq 'issues' || $req_data eq 'all' ) {
$results{'issues'} = get_data( $issues_query, 'barcode', $startrec );
}
foreach my $key ( keys %results ) {
$finished = 0 if keys %{ $results{$key} } == 5000;
}
$results{'finished'} = $finished;
print $query->header( -type => 'application/json', -charset => 'utf-8' );
print to_json( \%results );
sub get_data {
my ( $sql, $key, $start ) = @_;
$start ||= 0;
my $dbh = C4::Context->dbh;
my $sth = $dbh->prepare($sql);
$sth->execute($start);
return $sth->fetchall_hashref($key);
}