View | Details | Raw Unified | Return to bug 10240
Collapse All | Expand All

(-)a/circ/circulation-home.pl (+1 lines)
Lines 39-44 $template->param( fast_cataloging => 1 ) if (defined $fa); Link Here
39
39
40
# Checking if the transfer page needs to be displayed
40
# Checking if the transfer page needs to be displayed
41
$template->param( display_transfer => 1 ) if ( ($flags->{'superlibrarian'} == 1) || (C4::Context->preference("IndependentBranches") == 0) );
41
$template->param( display_transfer => 1 ) if ( ($flags->{'superlibrarian'} == 1) || (C4::Context->preference("IndependentBranches") == 0) );
42
$template->{'VARS'}->{'AllowOfflineCirculation'} = C4::Context->preference('AllowOfflineCirculation');
42
43
43
44
44
output_html_with_http_headers $query, $cookie, $template->output;
45
output_html_with_http_headers $query, $cookie, $template->output;
(-)a/circ/offline-mf.pl (+34 lines)
Line 0 Link Here
1
#!/usr/bin/perl
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it under the
6
# terms of the GNU General Public License as published by the Free Software
7
# Foundation; either version 2 of the License, or (at your option) any later
8
# version.
9
#
10
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
11
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License along with
15
# Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
16
# Suite 330, Boston, MA  02111-1307 USA
17
#
18
19
use Modern::Perl;
20
use CGI;
21
use C4::Auth;
22
23
my $query = new CGI;
24
my ($template, $loggedinuser, $cookie, $flags)
25
= get_template_and_user({template_name => "circ/offline-mf.tt",
26
                query => $query,
27
                type => "intranet",
28
                authnotrequired => 0,
29
                flagsrequired => {circulate => "circulate_remaining_permissions"},
30
                });
31
32
$template->{'VARS'}->{'cookie'} = $cookie;
33
print $query->header(-type => 'text/cache-manifest', cookie => $cookie);
34
print $template->output;
(-)a/circ/offline.pl (+36 lines)
Line 0 Link Here
1
#!/usr/bin/perl
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it under the
6
# terms of the GNU General Public License as published by the Free Software
7
# Foundation; either version 2 of the License, or (at your option) any later
8
# version.
9
#
10
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
11
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License along with
15
# Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
16
# Suite 330, Boston, MA  02111-1307 USA
17
#
18
19
use Modern::Perl;
20
use CGI;
21
use C4::Auth;
22
use C4::Output;
23
use C4::Context;
24
25
my $query = new CGI;
26
my ($template, $loggedinuser, $cookie, $flags)
27
= get_template_and_user({template_name => "circ/offline.tt",
28
                query => $query,
29
                type => "intranet",
30
                authnotrequired => 0,
31
                flagsrequired => {circulate => "circulate_remaining_permissions"},
32
                });
33
34
$template->{'VARS'}->{'AllowOfflineCirculation'} = C4::Context->preference('AllowOfflineCirculation');
35
$template->{'VARS'}->{'maxoutstanding'} = C4::Context->preference('maxoutstanding') || 0;
36
output_html_with_http_headers $query, $cookie, $template->output;
(-)a/installer/data/mysql/sysprefs.sql (+1 lines)
Lines 17-22 INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` Link Here
17
('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'),
17
('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'),
18
('AllowMultipleCovers','0','1','Allow multiple cover images to be attached to each bibliographic record.','YesNo'),
18
('AllowMultipleCovers','0','1','Allow multiple cover images to be attached to each bibliographic record.','YesNo'),
19
('AllowNotForLoanOverride','0','','If ON, Koha will allow the librarian to loan a not for loan item.','YesNo'),
19
('AllowNotForLoanOverride','0','','If ON, Koha will allow the librarian to loan a not for loan item.','YesNo'),
20
('AllowOfflineCirculation','0','','If on, enables HTML5 offline circulation functionality.','YesNo'),
20
('AllowOnShelfHolds','0','','Allow hold requests to be placed on items that are not on loan','YesNo'),
21
('AllowOnShelfHolds','0','','Allow hold requests to be placed on items that are not on loan','YesNo'),
21
('AllowPKIAuth','None','None|Common Name|emailAddress','Use the field from a client-side SSL certificate to look a user in the Koha database','Choice'),
22
('AllowPKIAuth','None','None|Common Name|emailAddress','Use the field from a client-side SSL certificate to look a user in the Koha database','Choice'),
22
('AllowPurchaseSuggestionBranchChoice','0','1','Allow user to choose branch when making a purchase suggestion','YesNo'),
23
('AllowPurchaseSuggestionBranchChoice','0','1','Allow user to choose branch when making a purchase suggestion','YesNo'),
(-)a/installer/data/mysql/updatedatabase.pl (+7 lines)
Lines 7155-7160 if ( CheckVersion($DBversion) ) { Link Here
7155
    SetVersion($DBversion);
7155
    SetVersion($DBversion);
7156
}
7156
}
7157
7157
7158
$DBversion = "3.13.00.XXX";
7159
if ( CheckVersion($DBversion) ) {
7160
    $dbh->do("INSERT INTO systempreferences (variable,value,options,explanation,type) VALUES ('AllowOfflineCirculation','0','','If on, enables HTML5 offline circulation functionality.','YesNo')");
7161
    print "Upgrade to $DBversion done (Bug 10240: Add syspref AllowOfflineCirculation)\n";
7162
    SetVersion ($DBversion);
7163
}
7164
7158
=head1 FUNCTIONS
7165
=head1 FUNCTIONS
7159
7166
7160
=head2 TableExists($table)
7167
=head2 TableExists($table)
(-)a/koha-tmpl/intranet-tmpl/lib/jquery/plugins/jquery.indexeddb.js (+517 lines)
Line 0 Link Here
1
(function($, undefined) {
2
	var indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
3
	var IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange;
4
	var IDBCursor = window.IDBCursor || window.webkitIDBCursor;
5
	IDBCursor.PREV = IDBCursor.PREV || "prev";
6
	IDBCursor.NEXT = IDBCursor.NEXT || "next";
7
8
	/**
9
	 * Best to use the constant IDBTransaction since older version support numeric types while the latest spec
10
	 * supports strings
11
	 */
12
	var IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction;
13
14
	function getDefaultTransaction(mode) {
15
		var result = null;
16
		switch (mode) {
17
			case 0:
18
			case 1:
19
			case "readwrite":
20
			case "readonly":
21
				result = mode;
22
				break;
23
			default:
24
				result = IDBTransaction.READ_WRITE || "readwrite";
25
		}
26
		return result;
27
	}
28
29
	$.extend({
30
		/**
31
		 * The IndexedDB object used to open databases
32
		 * @param {Object} dbName - name of the database
33
		 * @param {Object} config - version, onupgradeneeded, onversionchange, schema
34
		 */
35
		"indexedDB": function(dbName, config) {
36
			if (config) {
37
				// Parse the config argument
38
				if (typeof config === "number") config = {
39
					"version": config
40
				};
41
42
				var version = config.version;
43
				if (config.schema && !version) {
44
					var max = -1;
45
					for (key in config.schema) {
46
						max = max > key ? max : key;
47
					}
48
					version = config.version || max;
49
				}
50
			}
51
52
53
			var wrap = {
54
				"request": function(req, args) {
55
					return $.Deferred(function(dfd) {
56
						try {
57
							var idbRequest = typeof req === "function" ? req(args) : req;
58
							idbRequest.onsuccess = function(e) {
59
60
								dfd.resolveWith(idbRequest, [idbRequest.result, e]);
61
							};
62
							idbRequest.onerror = function(e) {
63
64
								dfd.rejectWith(idbRequest, [idbRequest.error, e]);
65
							};
66
							if (typeof idbRequest.onblocked !== "undefined" && idbRequest.onblocked === null) {
67
								idbRequest.onblocked = function(e) {
68
69
									var res;
70
									try {
71
										res = idbRequest.result;
72
									} catch (e) {
73
										res = null; // Required for Older Chrome versions, accessing result causes error
74
									}
75
									dfd.notifyWith(idbRequest, [res, e]);
76
								};
77
							}
78
							if (typeof idbRequest.onupgradeneeded !== "undefined" && idbRequest.onupgradeneeded === null) {
79
								idbRequest.onupgradeneeded = function(e) {
80
81
									dfd.notifyWith(idbRequest, [idbRequest.result, e]);
82
								};
83
							}
84
						} catch (e) {
85
							e.name = "exception";
86
							dfd.rejectWith(idbRequest, ["exception", e]);
87
						}
88
					});
89
				},
90
				// Wraps the IDBTransaction to return promises, and other dependent methods
91
				"transaction": function(idbTransaction) {
92
					return {
93
						"objectStore": function(storeName) {
94
							try {
95
								return wrap.objectStore(idbTransaction.objectStore(storeName));
96
							} catch (e) {
97
								idbTransaction.readyState !== idbTransaction.DONE && idbTransaction.abort();
98
								return wrap.objectStore(null);
99
							}
100
						},
101
						"createObjectStore": function(storeName, storeParams) {
102
							try {
103
								return wrap.objectStore(idbTransaction.db.createObjectStore(storeName, storeParams));
104
							} catch (e) {
105
								idbTransaction.readyState !== idbTransaction.DONE && idbTransaction.abort();
106
							}
107
						},
108
						"deleteObjectStore": function(storeName) {
109
							try {
110
								idbTransaction.db.deleteObjectStore(storeName);
111
							} catch (e) {
112
								idbTransaction.readyState !== idbTransaction.DONE && idbTransaction.abort();
113
							}
114
						},
115
						"abort": function() {
116
							idbTransaction.abort();
117
						}
118
					};
119
				},
120
				"objectStore": function(idbObjectStore) {
121
					var result = {};
122
					// Define CRUD operations
123
					var crudOps = ["add", "put", "get", "delete", "clear", "count"];
124
					for (var i = 0; i < crudOps.length; i++) {
125
						result[crudOps[i]] = (function(op) {
126
							return function() {
127
								return wrap.request(function(args) {
128
									return idbObjectStore[op].apply(idbObjectStore, args);
129
								}, arguments);
130
							};
131
						})(crudOps[i]);
132
					}
133
134
					result.each = function(callback, range, direction) {
135
						return wrap.cursor(function() {
136
							if (direction) {
137
								return idbObjectStore.openCursor(wrap.range(range), direction);
138
							} else {
139
								return idbObjectStore.openCursor(wrap.range(range));
140
							}
141
						}, callback);
142
					};
143
144
					result.index = function(name) {
145
						return wrap.index(function() {
146
							return idbObjectStore.index(name);
147
						});
148
					};
149
150
					result.createIndex = function(prop, options, indexName) {
151
						if (arguments.length === 2 && typeof options === "string") {
152
							indexName = arguments[1];
153
							options = null;
154
						}
155
						if (!indexName) {
156
							indexName = prop;
157
						}
158
						return wrap.index(function() {
159
							return idbObjectStore.createIndex(indexName, prop, options);
160
						});
161
					};
162
163
					result.deleteIndex = function(indexName) {
164
						return idbObjectStore.deleteIndex(indexName);
165
					};
166
167
					return result;
168
				},
169
170
				"range": function(r) {
171
					if ($.isArray(r)) {
172
						if (r.length === 1) {
173
							return IDBKeyRange.only(r[0]);
174
						} else {
175
							return IDBKeyRange.bound(r[0], r[1], (typeof r[2] === 'undefined') ? true : r[2], (typeof r[3] === 'undefined') ? true : r[3]);
176
						}
177
					} else if (typeof r === "undefined") {
178
						return null;
179
					} else {
180
						return r;
181
					}
182
				},
183
184
				"cursor": function(idbCursor, callback) {
185
					return $.Deferred(function(dfd) {
186
						try {
187
188
							var cursorReq = typeof idbCursor === "function" ? idbCursor() : idbCursor;
189
							cursorReq.onsuccess = function(e) {
190
191
								if (!cursorReq.result) {
192
									dfd.resolveWith(cursorReq, [null, e]);
193
									return;
194
								}
195
								var elem = {
196
									// Delete, update do not move
197
									"delete": function() {
198
										return wrap.request(function() {
199
											return cursorReq.result["delete"]();
200
										});
201
									},
202
									"update": function(data) {
203
										return wrap.request(function() {
204
											return cursorReq.result["update"](data);
205
										});
206
									},
207
									"next": function(key) {
208
										this.data = key;
209
									},
210
									"key": cursorReq.result.key,
211
									"value": cursorReq.result.value
212
								};
213
214
								dfd.notifyWith(cursorReq, [elem, e]);
215
								var result = callback.apply(cursorReq, [elem]);
216
217
								try {
218
									if (result === false) {
219
										dfd.resolveWith(cursorReq, [null, e]);
220
									} else if (typeof result === "number") {
221
										cursorReq.result["advance"].apply(cursorReq.result, [result]);
222
									} else {
223
										if (elem.data) cursorReq.result["continue"].apply(cursorReq.result, [elem.data]);
224
										else cursorReq.result["continue"]();
225
									}
226
								} catch (e) {
227
228
									dfd.rejectWith(cursorReq, [cursorReq.result, e]);
229
								}
230
							};
231
							cursorReq.onerror = function(e) {
232
233
								dfd.rejectWith(cursorReq, [cursorReq.result, e]);
234
							};
235
						} catch (e) {
236
237
							e.type = "exception";
238
							dfd.rejectWith(cursorReq, [null, e]);
239
						}
240
					});
241
				},
242
243
				"index": function(index) {
244
					try {
245
						var idbIndex = (typeof index === "function" ? index() : index);
246
					} catch (e) {
247
						idbIndex = null;
248
					}
249
250
					return {
251
						"each": function(callback, range, direction) {
252
							return wrap.cursor(function() {
253
								if (direction) {
254
									return idbIndex.openCursor(wrap.range(range), direction);
255
								} else {
256
									return idbIndex.openCursor(wrap.range(range));
257
								}
258
259
							}, callback);
260
						},
261
						"eachKey": function(callback, range, direction) {
262
							return wrap.cursor(function() {
263
								if (direction) {
264
									return idbIndex.openKeyCursor(wrap.range(range), direction);
265
								} else {
266
									return idbIndex.openKeyCursor(wrap.range(range));
267
								}
268
							}, callback);
269
						},
270
						"get": function(key) {
271
							if (typeof idbIndex.get === "function") {
272
								return wrap.request(idbIndex.get(key));
273
							} else {
274
								return idbIndex.openCursor(wrap.range(key));
275
							}
276
						},
277
						"count": function() {
278
							if (typeof idbIndex.count === "function") {
279
								return wrap.request(idbIndex.count());
280
							} else {
281
								throw "Count not implemented for cursors";
282
							}
283
						},
284
						"getKey": function(key) {
285
							if (typeof idbIndex.getKey === "function") {
286
								return wrap.request(idbIndex.getKey(key));
287
							} else {
288
								return idbIndex.openKeyCursor(wrap.range(key));
289
							}
290
						}
291
					};
292
				}
293
			};
294
295
296
			// Start with opening the database
297
			var dbPromise = wrap.request(function() {
298
299
				return version ? indexedDB.open(dbName, parseInt(version)) : indexedDB.open(dbName);
300
			});
301
			dbPromise.then(function(db, e) {
302
303
				db.onversionchange = function() {
304
					// Try to automatically close the database if there is a version change request
305
					if (!(config && config.onversionchange && config.onversionchange() !== false)) {
306
						db.close();
307
					}
308
				};
309
			}, function(error, e) {
310
311
				// Nothing much to do if an error occurs
312
			}, function(db, e) {
313
				if (e && e.type === "upgradeneeded") {
314
					if (config && config.schema) {
315
						// Assuming that version is always an integer
316
317
						for (var i = e.oldVersion + 1; i <= e.newVersion; i++) {
318
							typeof config.schema[i] === "function" && config.schema[i].call(this, wrap.transaction(this.transaction));
319
						}
320
					}
321
					if (config && typeof config.upgrade === "function") {
322
						config.upgrade.call(this, wrap.transaction(this.transaction));
323
					}
324
				}
325
			});
326
327
			return $.extend(dbPromise, {
328
				"cmp": function(key1, key2) {
329
					return indexedDB.cmp(key1, key2);
330
				},
331
				"deleteDatabase": function() {
332
					// Kinda looks ugly coz DB is opened before it needs to be deleted.
333
					// Blame it on the API
334
					return $.Deferred(function(dfd) {
335
						dbPromise.then(function(db, e) {
336
							db.close();
337
							wrap.request(function() {
338
								return indexedDB.deleteDatabase(dbName);
339
							}).then(function(result, e) {
340
								dfd.resolveWith(this, [result, e]);
341
							}, function(error, e) {
342
								dfd.rejectWith(this, [error, e]);
343
							}, function(db, e) {
344
								dfd.notifyWith(this, [db, e]);
345
							});
346
						}, function(error, e) {
347
							dfd.rejectWith(this, [error, e]);
348
						}, function(db, e) {
349
							dfd.notifyWith(this, [db, e]);
350
						});
351
					});
352
				},
353
				"transaction": function(storeNames, mode) {
354
					!$.isArray(storeNames) && (storeNames = [storeNames]);
355
					mode = getDefaultTransaction(mode);
356
					return $.Deferred(function(dfd) {
357
						dbPromise.then(function(db, e) {
358
							var idbTransaction;
359
							try {
360
361
								idbTransaction = db.transaction(storeNames, mode);
362
363
								idbTransaction.onabort = idbTransaction.onerror = function(e) {
364
									dfd.rejectWith(idbTransaction, [e]);
365
								};
366
								idbTransaction.oncomplete = function(e) {
367
									dfd.resolveWith(idbTransaction, [e]);
368
								};
369
							} catch (e) {
370
371
								e.type = "exception";
372
								dfd.rejectWith(this, [e]);
373
								return;
374
							}
375
							try {
376
								dfd.notifyWith(idbTransaction, [wrap.transaction(idbTransaction)]);
377
							} catch (e) {
378
								e.type = "exception";
379
								dfd.rejectWith(this, [e]);
380
							}
381
						}, function(err, e) {
382
							dfd.rejectWith(this, [e, err]);
383
						}, function(res, e) {
384
385
							//dfd.notifyWith(this, ["", e]);
386
						});
387
388
					});
389
				},
390
				"objectStore": function(storeName, mode) {
391
					var me = this,
392
						result = {};
393
394
					function op(callback) {
395
						return $.Deferred(function(dfd) {
396
							function onTransactionProgress(trans, callback) {
397
								try {
398
399
									callback(trans.objectStore(storeName)).then(function(result, e) {
400
										dfd.resolveWith(this, [result, e]);
401
									}, function(err, e) {
402
										dfd.rejectWith(this, [err, e]);
403
									});
404
								} catch (e) {
405
406
									e.name = "exception";
407
									dfd.rejectWith(trans, [e, e]);
408
								}
409
							}
410
							me.transaction(storeName, getDefaultTransaction(mode)).then(function() {
411
412
								// Nothing to do when transaction is complete
413
							}, function(err, e) {
414
								// If transaction fails, CrudOp fails
415
								if (err.code === err.NOT_FOUND_ERR && (mode === true || typeof mode === "object")) {
416
417
									var db = this.result;
418
									db.close();
419
									dbPromise = wrap.request(function() {
420
421
										return indexedDB.open(dbName, (parseInt(db.version, 10) || 1) + 1);
422
									});
423
									dbPromise.then(function(db, e) {
424
425
										db.onversionchange = function() {
426
											// Try to automatically close the database if there is a version change request
427
											if (!(config && config.onversionchange && config.onversionchange() !== false)) {
428
												db.close();
429
											}
430
										};
431
										me.transaction(storeName, getDefaultTransaction(mode)).then(function() {
432
433
											// Nothing much to do
434
										}, function(err, e) {
435
											dfd.rejectWith(this, [err, e]);
436
										}, function(trans, e) {
437
438
											onTransactionProgress(trans, callback);
439
										});
440
									}, function(err, e) {
441
										dfd.rejectWith(this, [err, e]);
442
									}, function(db, e) {
443
										if (e.type === "upgradeneeded") {
444
											try {
445
446
												db.createObjectStore(storeName, mode === true ? {
447
													"autoIncrement": true
448
												} : mode);
449
450
											} catch (ex) {
451
452
												dfd.rejectWith(this, [ex, e]);
453
											}
454
										}
455
									});
456
								} else {
457
									dfd.rejectWith(this, [err, e]);
458
								}
459
							}, function(trans) {
460
461
								onTransactionProgress(trans, callback);
462
							});
463
						});
464
					}
465
466
					function crudOp(opName, args) {
467
						return op(function(wrappedObjectStore) {
468
							return wrappedObjectStore[opName].apply(wrappedObjectStore, args);
469
						});
470
					}
471
472
					function indexOp(opName, indexName, args) {
473
						return op(function(wrappedObjectStore) {
474
							var index = wrappedObjectStore.index(indexName);
475
							return index[opName].apply(index[opName], args);
476
						});
477
					}
478
479
					var crud = ["add", "delete", "get", "put", "clear", "count", "each"];
480
					for (var i = 0; i < crud.length; i++) {
481
						result[crud[i]] = (function(op) {
482
							return function() {
483
								return crudOp(op, arguments);
484
							};
485
						})(crud[i]);
486
					}
487
488
					result.index = function(indexName) {
489
						return {
490
							"each": function(callback, range, direction) {
491
								return indexOp("each", indexName, [callback, range, direction]);
492
							},
493
							"eachKey": function(callback, range, direction) {
494
								return indexOp("eachKey", indexName, [callback, range, direction]);
495
							},
496
							"get": function(key) {
497
								return indexOp("get", indexName, [key]);
498
							},
499
							"count": function() {
500
								return indexOp("count", indexName, []);
501
							},
502
							"getKey": function(key) {
503
								return indexOp("getKey", indexName, [key]);
504
							}
505
						};
506
					};
507
508
					return result;
509
				}
510
			});
511
		}
512
	});
513
514
	$.indexedDB.IDBCursor = IDBCursor;
515
	$.indexedDB.IDBTransaction = IDBTransaction;
516
	$.idb = $.indexedDB;
517
})(jQuery);
(-)a/koha-tmpl/intranet-tmpl/prog/en/css/staff-global.css (+24 lines)
Lines 2659-2661 span.browse-button { Link Here
2659
    float: right;
2659
    float: right;
2660
    padding-right: 1em;
2660
    padding-right: 1em;
2661
}
2661
}
2662
2663
.loading-overlay {
2664
    background-color: #FFF;
2665
    cursor: wait;
2666
    height: 100%;
2667
    left: 0;
2668
    opacity: .7;
2669
    position: fixed;
2670
    top: 0;
2671
    width: 100%;
2672
    z-index: 1000;
2673
}
2674
.loading-overlay div {
2675
    background : transparent url(../../img/loading.gif) top left no-repeat;
2676
    font-size : 175%;
2677
    font-weight: bold;
2678
    height: 2em;
2679
    left: 50%;
2680
    margin: -1em 0 0 -2.5em;
2681
    padding-left : 50px;
2682
    position: absolute;
2683
    top: 50%;
2684
    width: 15em;
2685
}
(-)a/koha-tmpl/intranet-tmpl/prog/en/js/offlinecirc.js (+109 lines)
Line 0 Link Here
1
/* Copyright 2013 C & P Bibliography Services
2
 *
3
 * This file is part of Koha.
4
 *
5
 * Koha is free software; you can redistribute it and/or modify it under the
6
 * terms of the GNU General Public License as published by the Free Software
7
 * Foundation; either version 3 of the License, or (at your option) any later
8
 * version.
9
 *
10
 * Koha is distributed in the hope that it will be useful, but WITHOUT ANY
11
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12
 * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU General Public License along
15
 * with Koha; if not, write to the Free Software Foundation, Inc.,
16
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
 */
18
19
(function( kohadb, $, undefined ) {
20
    kohadb.settings = kohadb.settings || {};
21
    kohadb.initialize = function (callback) {
22
        $.indexedDB("koha", {
23
            "version": 1,
24
            "schema": {
25
                "1": function(versionTransaction){
26
                    var patrons = versionTransaction.createObjectStore("patrons", {
27
                        "keyPath": "cardnumber"
28
                    });
29
                    var items = versionTransaction.createObjectStore("items", {
30
                        "keyPath": "barcode"
31
                    });
32
                    var issues = versionTransaction.createObjectStore("issues", {
33
                        "keyPath": "barcode"
34
                    });
35
                    issues.createIndex("cardnumber", { "multiEntry": true });
36
                    var transactions = versionTransaction.createObjectStore("transactions", {
37
                        "keyPath": "timestamp"
38
                    });
39
                    var settings = versionTransaction.createObjectStore("offline_settings", {
40
                        "keyPath": "key"
41
                    });
42
                },
43
            }
44
        }).done(function(){
45
            if (typeof callback === 'function') {
46
                callback();
47
                kohadb.loadSetting('userid');
48
                kohadb.loadSetting('branchcode');
49
            }
50
        });
51
    };
52
    kohadb.loadSetting = function (key, callback) {
53
        $.indexedDB("koha").transaction(["offline_settings"]).then(function(){
54
        }, function(err, e){
55
        }, function(transaction){
56
            var settings = transaction.objectStore("offline_settings");
57
            settings.get(key).done(function (item, error) {
58
                if (typeof item !== 'undefined') {
59
                    kohadb.settings[key] = item.value;
60
                }
61
                if (typeof callback === 'function') {
62
                    callback(key, kohadb.settings[key]);
63
                }
64
            });
65
        });
66
    };
67
    kohadb.saveSetting = function (key, value) {
68
        $.indexedDB("koha").transaction(["offline_settings"]).then(function(){
69
        }, function(err, e){
70
        }, function(transaction){
71
            var settings = transaction.objectStore("offline_settings");
72
            settings.put({ "key" : key, "value" : value });
73
            kohadb.settings[key] = value;
74
        });
75
    };
76
    kohadb.recordTransaction = function (newtrans, callback) {
77
        $.indexedDB("koha").transaction(["transactions"]).then(function(){
78
            callback(newtrans);
79
        }, function(err, e){
80
        }, function(dbtransaction) {
81
            var transactions = dbtransaction.objectStore("transactions");
82
            transactions.put(newtrans);
83
        });
84
    };
85
}( window.kohadb = window.bndb || {}, jQuery ));
86
87
if ( !Date.prototype.toMySQLString ) {
88
  ( function() {
89
90
    function pad(number) {
91
      var r = String(number);
92
      if ( r.length === 1 ) {
93
        r = '0' + r;
94
      }
95
      return r;
96
    }
97
98
    Date.prototype.toMySQLString = function() {
99
      return this.getFullYear()
100
        + '-' + pad( this.getMonth() + 1 )
101
        + '-' + pad( this.getDate() )
102
        + ' ' + pad( this.getHours() )
103
        + ':' + pad( this.getMinutes() )
104
        + ':' + pad( this.getSeconds() )
105
        + '.' + String( (this.getMilliseconds()/1000).toFixed(3) ).slice( 2, 5 );
106
    };
107
108
  }() );
109
}
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref (+6 lines)
Lines 129-134 Circulation: Link Here
129
            - The following fields should be excluded from the patron checkout history CSV or iso2709 export
129
            - The following fields should be excluded from the patron checkout history CSV or iso2709 export
130
            - pref: ExportRemoveFields
130
            - pref: ExportRemoveFields
131
            - (separate fields with space, e.g. 100a 200b 300c)
131
            - (separate fields with space, e.g. 100a 200b 300c)
132
        -
133
            - pref: AllowOfflineCirculation
134
              choices:
135
                  yes: Enable
136
                  no: "Do not enable"
137
            - "offline circulation on regular circulation computers. (NOTE: This system preference does not affect the Firefox plugin or the desktop application)"
132
138
133
    Checkout Policy:
139
    Checkout Policy:
134
        -
140
        -
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation-home.tt (-6 / +9 lines)
Lines 53-64 Link Here
53
	<div class="yui-u">
53
	<div class="yui-u">
54
        <h5>Offline circulation</h5>
54
        <h5>Offline circulation</h5>
55
		<ul>
55
		<ul>
56
                    <li><a href="/cgi-bin/koha/offline_circ/process_koc.pl">Upload offline circulation file (.koc)</a></li>
56
            [% IF (AllowOfflineCirculation) %]
57
                    <li><a href="/cgi-bin/koha/offline_circ/list.pl">Pending offline circulation actions</a>
57
            <li><a href="/cgi-bin/koha/circ/offline.pl">Built-in offline circulation interface</a></li>
58
                    <ul>
58
            [% END %]
59
                        <li><a href="http://kylehall.info/index.php/projects/koha/koha-offline-circulation/">Get desktop application</a></li>
59
            <li><a href="/cgi-bin/koha/offline_circ/process_koc.pl">Upload offline circulation file (.koc)</a></li>
60
                        <li><a href="https://addons.mozilla.org/[% lang %]/firefox/addon/koct/">Get Firefox add-on</a></li>
60
            <li><a href="/cgi-bin/koha/offline_circ/list.pl">Pending offline circulation actions</a>
61
                    </ul>
61
            <ul>
62
                <li><a href="http://kylehall.info/index.php/projects/koha/koha-offline-circulation/">Get desktop application</a></li>
63
                <li><a href="https://addons.mozilla.org/[% lang %]/firefox/addon/koct/">Get Firefox add-on</a></li>
64
            </ul>
62
		</ul>
65
		</ul>
63
	</div>
66
	</div>
64
</div>
67
</div>
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/offline-mf.tt (+43 lines)
Line 0 Link Here
1
CACHE MANIFEST
2
# [% cookie %]
3
4
# Explicitly cached 'master entries'.
5
CACHE:
6
/cgi-bin/koha/circ/offline.pl
7
/intranet-tmpl/lib/bootstrap/bootstrap.min.css
8
/intranet-tmpl/lib/bootstrap/bootstrap.min.js
9
/intranet-tmpl/lib/jquery/jquery-ui.css
10
/intranet-tmpl/lib/jquery/jquery-ui.js
11
/intranet-tmpl/lib/jquery/jquery.js
12
/intranet-tmpl/lib/jquery/plugins/jquery.cookie.min.js
13
/intranet-tmpl/lib/jquery/plugins/jquery.highlight-3.js
14
/intranet-tmpl/lib/jquery/plugins/jquery.hotkeys.min.js
15
/intranet-tmpl/lib/jquery/plugins/jquery.indexeddb.js
16
/intranet-tmpl/lib/jquery/plugins/jquery.validate.min.js
17
/intranet-tmpl/prog/en/css/print.css
18
/intranet-tmpl/prog/en/css/staff-global.css
19
/intranet-tmpl/prog/en/js/basket.js
20
/intranet-tmpl/prog/en/js/offlinecirc.js
21
/intranet-tmpl/prog/en/js/staff-global.js
22
/intranet-tmpl/prog/en/lib/jquery/plugins/jquery-ui-timepicker-addon.js
23
/intranet-tmpl/prog/en/lib/yui/button/button-min.js
24
/intranet-tmpl/prog/en/lib/yui/container/container_core-min.js
25
/intranet-tmpl/prog/en/lib/yui/menu/menu-min.js
26
/intranet-tmpl/prog/en/lib/yui/reset-fonts-grids.css
27
/intranet-tmpl/prog/en/lib/yui/skin.css
28
/intranet-tmpl/prog/en/lib/yui/utilities/utilities.js
29
/intranet-tmpl/prog/img/cart-small.gif
30
/intranet-tmpl/prog/img/glyphicons-halflings-koha.png
31
/intranet-tmpl/prog/img/koha-logo-medium.gif
32
/intranet-tmpl/prog/img/loading.gif
33
/intranet-tmpl/prog/sound/beep.ogg
34
/intranet-tmpl/prog/sound/critical.ogg
35
36
# Resources that require the user to be online.
37
NETWORK:
38
*
39
40
# static.html will be served if main.py is inaccessible
41
# offline.jpg will be served in place of all images in images/large/
42
# offline.html will be served in place of all other .html files
43
FALLBACK:
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/offline.tt (+690 lines)
Line 0 Link Here
1
<!DOCTYPE html>
2
[% IF (AllowOfflineCirculation) %]
3
[% SET manifestattr = 'manifest="/cgi-bin/koha/circ/offline-mf.pl"' %]
4
[% END %]
5
[% IF ( bidi ) %]<html lang="[% lang %]" dir="[% bidi %]" [% manifestattr %]>[% ELSE %]<html lang="[% lang %]" [% manifestattr %]>[% END %]
6
<head>
7
<title>Koha &rsaquo; Circulation</title>
8
[% INCLUDE 'doc-head-close.inc' %]
9
<script type="text/javascript" src="/intranet-tmpl/lib/jquery/plugins/jquery.indexeddb.js"></script>
10
<script type="text/javascript" src="/intranet-tmpl/prog/en/js/offlinecirc.js"></script>
11
<script type="text/javascript" src="[% themelang %]/lib/jquery/plugins/jquery-ui-timepicker-addon.js"></script>
12
<script type="text/javascript">
13
//<![CDATA[
14
var ALERT_MATERIALS = _("Note about the accompanying materials: ");
15
var ALERT_RESTRICTED = _("Patron is RESTRICTED");
16
var ALERT_NO_MATCHING_ITEM = _("No item with barcode in offline database (transaction recorded anyway): ");
17
var ALERT_NOT_CHECKED_OUT = _("Item not listed as checked out in offline database (transaction recorded anyway)");
18
var ALERT_ITEM_WITHDRAWN = _("Item has been withdrawn (transaction recorded anyway)");
19
var ALERT_ITEM_RESTRICTED = _("Item is restricted (transaction recorded anyway)");
20
var ALERT_ITEM_LOST = _("Item is has been lost (transaction recorded anyway)");
21
var ALERT_NO_MATCHING_PATRON = _("No patron cardnumber in offline database (proceeding anyway): ");
22
var ALERT_PATRON_GONE_NO_ADDRESS = _("Patron's address is in doubt (transaction recorded anyway)");
23
var ALERT_PATRON_CARD_LOST = _("Patron's card is lost");
24
var ALERT_PATRON_EXPIRED = _("Patron's card is expired");
25
var ALERT_PATRON_BLOCKED_TEMPORARY = _("Patron has had overdue items and is restricted for: ");
26
var ALERT_PATRON_RESTRICTED = _("Patron is restricted");
27
var ALERT_PATRON_FINE = _("Patron has outstanding fines: ");
28
var ALERT_PATRON_FINE_OVER_LIMIT = _("Patron fines are over limit: ");
29
30
var start;
31
32
var dateformat = '[% IF ( dateformat_us ) %]mm/dd/yy[% ELSIF ( dateformat_metric ) %]dd/mm/yy[% ELSE %]yy-mm-dd[% END %]';
33
34
function checkin(barcode, item, error) {
35
    var alerts = checkAlerts(barcode, item);
36
    if (typeof item === 'undefined') {
37
        item = { };
38
    }
39
    item.title = item.title || _("(Unknown)");
40
    item.author = item.author || _("(Unknown)");
41
    item.homebranch = item.homebranch || "";
42
    item.holdingbranch = item.holdingbranch || "";
43
    item.callnumber = item.callnumber || "";
44
    item.itemtype = item.itemtype || "";
45
    item.barcode = item.barcode || barcode;
46
    var trans = { "timestamp" : new Date().toMySQLString(),
47
                  "barcode" : barcode,
48
                  "action" : "return"
49
                };
50
    $('#alerts').empty();
51
    $('#offline-home').hide();
52
    $('#offline-returns').show();
53
    kohadb.recordTransaction(trans, function () {
54
        $('#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>');
55
        if (alerts.length > 0) {
56
            $('#alerts').append('<div class="dialog alert"><h3>' + _("Check in message") + '</h3></div>');
57
            for (var msg in alerts) {
58
                $('#alerts .dialog').append('<p>' + alerts[msg] + '</p');
59
            }
60
        }
61
    });
62
}
63
64
function checkAlerts(barcode, item) {
65
    var alerts = [];
66
    if (typeof item === 'undefined') {
67
        alerts.push(ALERT_NO_MATCHING_ITEM + barcode);
68
    } else {
69
        if (typeof item.materials !== 'undefined' && item.materials != null) {
70
            alerts.push(ALERT_MATERIALS + item.materials);
71
        }
72
    }
73
    return alerts;
74
}
75
76
function synchronize() {
77
    kohadb.saveSetting("userid", "[% loggedinusername %]");
78
    kohadb.saveSetting("branchcode", "[% LoginBranchcode %]");
79
    kohadb.loadSetting("item-timestamp", showTimestamp);
80
    kohadb.loadSetting("patron-timestamp", showTimestamp);
81
    kohadb.loadSetting("issue-timestamp", showTimestamp);
82
    [% UNLESS (AllowOfflineCirculation) %]
83
        reloadRecords();
84
    [% END %]
85
    $('#download-records').click(reloadRecords);
86
    $('#upload-transactions').click(function () {
87
        $('.loading-overlay div').text(_("Uploading transactions, please wait..."));
88
        $('.loading-overlay').show();
89
        var uploadIter = $.indexedDB("koha").objectStore("transactions").each(uploadTransaction);
90
        uploadIter.done(function() {
91
            $('.loading-overlay').hide();
92
        });
93
    });
94
95
}
96
97
function showTimestamp(key, value) {
98
    if (typeof value !== 'undefined') {
99
        var ts = new Date(value);
100
        $('#' + key).text($.datepicker.formatDate(dateformat, ts) + ' ' + ts.toTimeString());
101
    } else {
102
        $('#' + key).text(_("(never)"));
103
    }
104
}
105
106
function reloadRecords(ev) {
107
    $(".loading-overlay div").text(_("Loading records, please wait..."));
108
    $(".loading-overlay").show();
109
    start = new Date();
110
    $.indexedDB("koha").transaction(["patrons", "items", "issues"]).then(function(){
111
        loadRecords(0);
112
    }, function(err, e){
113
    }, function(transaction){
114
        transaction.objectStore("patrons").clear();
115
        transaction.objectStore("items").clear();
116
        transaction.objectStore("issues").clear();
117
    });
118
    if (typeof ev !== 'undefined') {
119
        ev.stopPropagation();
120
    }
121
}
122
123
function uploadTransaction(transaction) {
124
    $.ajax({
125
        type: "GET",
126
        url: "/cgi-bin/koha/offline_circ/service.pl",
127
        data: { "userid" : kohadb.settings.userid,
128
                "branchcode" : kohadb.settings.branchcode,
129
                "timestamp" : transaction.value.timestamp,
130
                "action" : transaction.value.action,
131
                "barcode" : transaction.value.barcode,
132
                "cardnumber" : transaction.value.cardnumber,
133
                "pending" : true,
134
              },
135
    }).done(function () {
136
        transaction.delete();
137
    });
138
}
139
140
function finishedLoading() {
141
    kohadb.saveSetting('item-timestamp', start.toISOString())
142
    kohadb.saveSetting('patron-timestamp', start.toISOString())
143
    kohadb.saveSetting('issue-timestamp', start.toISOString())
144
    showTimestamp('item-timestamp', start.toISOString());
145
    showTimestamp('patron-timestamp', start.toISOString());
146
    showTimestamp('issue-timestamp', start.toISOString());
147
    $(".loading-overlay").hide();
148
}
149
150
function loadRecords(page) {
151
[% IF (AllowOfflineCirculation) %]
152
    $(".loading-overlay div").text(_("Loading page " + page + ", please wait..."));
153
    $(".loading-overlay").show();
154
    $.ajax({
155
        type: "GET",
156
        url: "/cgi-bin/koha/offline_circ/download.pl",
157
        data: { "data": "all",
158
                "page": page
159
              },
160
        dataType: "json",
161
    }).done(function (data) {
162
        $.indexedDB("koha").transaction(["patrons", "items", "issues"]).then(function(){
163
            if ($.isEmptyObject(data.patrons) && $.isEmptyObject(data.items)) {
164
                finishedLoading();
165
            } else {
166
                setTimeout(function () { loadRecords(page + 1); }, 200);
167
            }
168
        }, function(err, e){
169
        }, function(transaction){
170
            if (data.patrons) {
171
                var patrons = transaction.objectStore("patrons");
172
                $.each(data.patrons, function () {
173
                    patrons.put(this);
174
                });
175
            }
176
            if (data.items) {
177
                var items = transaction.objectStore("items");
178
                $.each(data.items, function () {
179
                    items.put(this);
180
                });
181
            }
182
            if (data.issues) {
183
                var issues = transaction.objectStore("issues");
184
                $.each(data.issues, function () {
185
                    issues.put(this);
186
                });
187
            }
188
        });
189
    });
190
[% END %]
191
}
192
193
function validate1(date) {
194
    var today = new Date();
195
    if ( date < today ) {
196
        return true;
197
     } else {
198
        return false;
199
     }
200
};
201
202
function loadPatron(barcode) {
203
    $('#oldissues').hide();
204
    $('#session-issues').hide();
205
    $('#session-payments').hide();
206
    $.indexedDB("koha").transaction(["patrons", "issues"]).then(function() {
207
    }, function(err, e){
208
    }, function(transaction){
209
        var patrons = transaction.objectStore("patrons");
210
        patrons.get(barcode).done(function (patron, error) {
211
            showPatron(barcode, patron, error);
212
        });
213
        var issuesidx = transaction.objectStore("issues").index("cardnumber");
214
        $('#oldissuest tbody').empty();
215
        issuesidx.each(function (item) {
216
            $('#oldissues').show();
217
            $('#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>");
218
        }, barcode);
219
    });
220
}
221
222
function checkout(barcode, item, error) {
223
    var alerts = checkAlerts(barcode, item);
224
    if (typeof item === 'undefined') {
225
        item = { };
226
    }
227
    item.title = item.title || "";
228
    item.author = item.author || "";
229
    item.homebranch = item.homebranch || "";
230
    item.holdingbranch = item.holdingbranch || "";
231
    item.callnumber = item.callnumber || "";
232
    item.itemtype = item.itemtype || "";
233
    if ($('#duedatespec').val().length === 0) {
234
        alert(_("You must set a due date in order to use offline circulation!"));
235
        $('#duedatespec').focus();
236
        return;
237
    }
238
    var date_due = new Date($('#duedatespec').datepicker('getDate'));
239
    var trans = { "timestamp" : new Date().toMySQLString(),
240
                  "barcode" : barcode,
241
                  "cardnumber" : curpatron.cardnumber,
242
                  "date_due" : date_due.toMySQLString(),
243
                  "action" : "issue"
244
                };
245
    $('#alerts').empty();
246
    kohadb.recordTransaction(trans, function () {
247
        $('#session-issues').show();
248
        $('#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>');
249
        if (alerts.length > 0) {
250
            $('#alerts').append('<div class="dialog alert"><h3>' + _("Check out message") + '</h3></div>');
251
            for (var msg in alerts) {
252
                $('#alerts .dialog').append('<p>' + alerts[msg] + '</p');
253
            }
254
        }
255
    });
256
}
257
258
function recordFine(amount) {
259
    var timestamp = new Date()
260
    var trans = { "timestamp" : timestamp.toMySQLString(),
261
                  "cardnumber" : curpatron.cardnumber,
262
                  "amount" : amount,
263
                  "action" : "payment",
264
                };
265
    kohadb.recordTransaction(trans, function () {
266
        $('#session-payments').show();
267
        $('#session-payments tbody').prepend('<tr><td>' + amount + '</td><td>' + $.datepicker.formatDate(dateformat, timestamp) + timestamp.toTimeString() + '</td></tr>');
268
    });
269
}
270
271
function checkPatronAlerts(cardnumber, patron) {
272
    var alerts = [];
273
    if (typeof patron === 'undefined') {
274
        alerts.push(ALERT_NO_MATCHING_PATRON + cardnumber);
275
    } else {
276
        if (patron.gonenoaddress !== '0') {
277
            alerts.push(ALERT_PATRON_GONE_NO_ADDRESS);
278
        }
279
        if (patron.lost !== '0') {
280
            alerts.push(ALERT_PATRON_CARD_LOST);
281
        }
282
        if (patron.debarred !== null) {
283
            if (patron.debarred != '9999-12-31') {
284
                alerts.push(ALERT_PATRON_BLOCKED_TEMPORARY + $.datepicker.formatDate(dateformat, patron.debarred));
285
            } else {
286
                alerts.push(ALERT_PATRON_RESTRICTED);
287
            }
288
        }
289
        if (parseInt(patron.fine) > [% maxoutstanding %]) {
290
            alerts.push(ALERT_PATRON_FINE_OVER_LIMIT + patron.fine);
291
        } else if (parseInt(patron.fine) > 0) {
292
            alerts.push(ALERT_PATRON_FINE + patron.fine);
293
        }
294
    }
295
    return alerts;
296
}
297
298
var curpatron;
299
300
function showPatron(barcode, patron, error) {
301
    var alerts = checkPatronAlerts(barcode, patron);
302
    if (typeof patron === 'undefined') {
303
        patron = { };
304
    }
305
    patron.surname = patron.surname || "";
306
    patron.firstname = patron.firstname || "";
307
    patron.othernames = patron.othernames || "";
308
    patron.address = patron.address || "";
309
    patron.address2 = patron.address2 || "";
310
    patron.city = patron.city || "";
311
    patron.state = patron.state || "";
312
    patron.country = patron.country || "";
313
    patron.zipcode = patron.zipcode || "";
314
    patron.phone = patron.phone || "";
315
    patron.mobile = patron.mobile || "";
316
    patron.phonepro = patron.phonepro || "";
317
    patron.email = patron.email || "";
318
    patron.emailpro = patron.emailpro || "";
319
    patron.categorycode = patron.categorycode || "";
320
    patron.branchcode = patron.branchcode || "";
321
    patron.cardnumber = barcode;
322
    patron.fine = patron.fine || "0";
323
324
    patron.name = patron.firstname + (patron.othernames.length > 0 ? " (" + patron.othernames + ") " : " ") + patron.surname + " (" + barcode + ")";
325
    if (patron.name.length > 0) {
326
        $('.patron-title').text(patron.name);
327
    } else {
328
        $('.patron-title').text(_("Unrecognized patron") + " (" + barcode + ")");
329
    }
330
    if (patron.address.length > 0 || patron.address2.length > 0) {
331
        $('#patron-address-1').text(patron.address);
332
        $('#patron-address-2').text(patron.address2);
333
    } else {
334
        $('#patron-address-1').html('<span class="empty" id="noaddressstored">' + _("No address stored.") + '</span></li>');
335
        $('#patron-address-2').text('');
336
    }
337
    if (patron.city.length > 0) {
338
        $('#patron-address-parts').text(patron.city + (patron.state.length > 0 ? ", " + patron.state : "") + " " + patron.zipcode + (patron.country.length > 0 ? ", " + patron.country : ""));
339
    } else {
340
        $('#patron-address-parts').html('<span class="empty" id="nocitystored">' + _("No city stored.") + '</span></li>');
341
    }
342
    if (patron.phone.length > 0 || patron.mobile.length > 0 || patron.phonepro.length > 0) {
343
        $('#patron-phone').text((patron.phone.length > 0 ? patron.phone : (patron.mobile.length > 0 ? patron.mobile : (patron.phonepro.length > 0 ? patron.phonepro : ''))));
344
    } else {
345
        $('#patron-phone').html('<span class="empty" id="nophonestored">' + _("No phone stored.") + '</span></li>');
346
    }
347
    if (patron.email.length > 0 || patron.emailpro.length > 0) {
348
        $('#patron-email').text((patron.email.length > 0 ? patron.email : (patron.emailpro.length > 0 ? patron.emailpro : "")));
349
    } else {
350
        $('#patron-email').html('<span class="empty" id="noemailstored">' + _("No email stored.") + '</span></li>');
351
    }
352
    if (patron.categorycode.length > 0) {
353
        $('#patron-category').text(_("Category: ") + patron.categorycode);
354
    } else {
355
        $('#patron-category').html('<span class="empty" id="unknowncategory">' + _("Category code unknown.") + '</span></li>');
356
    }
357
    if (patron.branchcode.length > 0) {
358
        $('#patron-library').text(_("Home library: ") + patron.branchcode);
359
    } else {
360
        $('#patron-library').html('<span class="empty" id="unknowncategory">' + _("Home library unknown.") + '</span></li>');
361
    }
362
    $('.fine-amount').text(patron.fine);
363
    $('#alerts').empty();
364
    if (alerts.length > 0) {
365
        $('#alerts').append('<div class="dialog alert"><h3>' + _("Check out message") + '</h3></div>');
366
        for (var msg in alerts) {
367
            $('#alerts .dialog').append('<p>' + alerts[msg] + '</p');
368
        }
369
    }
370
    curpatron = patron;
371
    $('#yui-main').show();
372
    $('#barcode').focus();
373
}
374
375
// This next bit of code is to deal with the updated session issue
376
window.addEventListener('load', function(e) {
377
    window.applicationCache.addEventListener('updateready', function(e) {
378
        if (window.applicationCache.status == window.applicationCache.UPDATEREADY) {
379
            // Browser downloaded a new app cache.
380
            // Swap it in and reload the page to get the new hotness.
381
            window.applicationCache.swapCache();
382
            if (confirm(_("A new version of this site is available. Load it?"))) {
383
                window.location.reload();
384
            }
385
        } else {
386
        // Manifest didn't changed. Nothing new to server.
387
        }
388
    }, false);
389
}, false);
390
391
392
$(document).ready(function () {
393
    kohadb.initialize();
394
395
    // Returns code
396
    $('#checkin-form, #checkin_search form').submit(function (event) {
397
        event.preventDefault();
398
        var barcode = $('input[name="barcode"]', this).val();
399
        $('input[name="barcode"]', this).val('');
400
        $.indexedDB("koha").transaction(["items"]).then(function() {
401
        }, function(err, e){
402
        }, function(transaction){
403
            var items = transaction.objectStore("items");
404
            items.get(barcode).done(function (item, error) {
405
                checkin(barcode, item, error);
406
            });
407
        });
408
    });
409
410
    $('#go-to-home').click(function () {
411
        $('.offline-sync').hide();
412
        $('.offline-circulation').hide();
413
        $('.offline-returns').hide();
414
        $('.offline-home').show();
415
    });
416
417
    $('#go-to-returns').click(function () {
418
        $('.offline-home').hide();
419
        $('.offline-sync').hide();
420
        $('.offline-circulation').hide();
421
        $('.offline-returns').show();
422
        $('#checkin-form input[name="barcode"]').focus();
423
    });
424
425
    $('#go-to-circ').click(function () {
426
        $('.offline-home').hide();
427
        $('.offline-sync').hide();
428
        $('.offline-returns').hide();
429
        $('.offline-circulation').hide();
430
        $('#header_search').tabs("option", "active", 0);
431
        $('#circ_search input[name="findborrower"]').focus();
432
    });
433
434
    $('#go-to-sync').click(function () {
435
        $.ajax({
436
            type: "GET",
437
            url: "/cgi-bin/koha/offline_circ/list.pl",
438
            success: function () {
439
                $('.offline-home').hide();
440
                $('.offline-returns').hide();
441
                $('.offline-circulation').hide();
442
                $('.offline-sync').show();
443
                synchronize();
444
            },
445
            error: function () {
446
                alert(_("You are offline and therefore cannot sync your database"));
447
            }
448
        });
449
    });
450
451
    $('#patronsearch').submit(function (event) {
452
        event.preventDefault();
453
        loadPatron($('#findborrower').val());
454
        $('.offline-home').hide();
455
        $('.offline-returns').hide();
456
        $('.offline-sync').hide();
457
        $('.offline-circulation').show();
458
        $('#findborrower').val('');
459
        $('#barcode').focus();
460
    });
461
462
    $('#pay-fine').click(function (event) {
463
        event.preventDefault();
464
        recordFine($('#pay-fine-amount').val());
465
    });
466
467
    $('#patronlists').tabs();
468
469
    $("#newduedate").datetimepicker({
470
        minDate: 1, // require that renewal date is after today
471
        hour: 23,
472
        minute: 59
473
    });
474
    $("#duedatespec").datetimepicker({
475
        onClose: function(dateText, inst) { $("#barcode").focus(); },
476
        hour: 23,
477
        minute: 59
478
    });
479
    $('#mainform').submit(function (event) {
480
        event.preventDefault();
481
        var barcode = $('#barcode').val();
482
        $.indexedDB("koha").transaction(["items"]).then(function() {
483
        }, function(err, e){
484
        }, function(transaction){
485
            var items = transaction.objectStore("items");
486
            items.get(barcode).done(function (item, error) {
487
                checkout(barcode, item, error);
488
            });
489
        });
490
    });
491
});
492
//]]>
493
</script>
494
</head>
495
<body id="circ_offline" class="circ">
496
[% INCLUDE 'header.inc' %]
497
[% INCLUDE 'circ-search.inc' %]
498
<div class="loading-overlay" style="display: none;">
499
    <div>Downloading records, please wait...</div>
500
</div>
501
502
<div id="breadcrumbs"><a href="/cgi-bin/koha/mainpage.pl">Home</a> &rsaquo; <a id="go-to-home" href="#offline-home">Offline circulation</a></div>
503
504
<div id="doc3" class="yui-t2">
505
506
    <div id="bd">
507
        <div id="yui-main">
508
            <audio id="alert_sound" src="/intranet-tmpl/prog/sound/critical.ogg" autobuffer="autobuffer"></audio>
509
            <audio id="success_sound" src="/intranet-tmpl/prog/sound/beep.ogg" autobuffer="autobuffer"></audio>
510
511
            <div id="alerts" class="yui-b">
512
            </div>
513
            [% UNLESS (AllowOfflineCirculation) %]
514
                <div id="noofflinecircwarning" class="dialog alert">
515
                    <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>
516
                </div>
517
            [% END %]
518
519
            <div id="offline-home" class="yui-b offline-home">
520
                <div class="yui-g">
521
                    <h1>Offline circulation</h1>
522
                    <div class="yui-u first">
523
                        <ul>
524
                            <li><a id="go-to-circ" href="#offline-circulation">Check out</a></li>
525
                            <li><a id="go-to-returns" href="#offline-returns">Check in</a></li>
526
                            <li><a id="go-to-sync" href="#offline-sync">Synchronize (must be online)</a></li>
527
                        </ul>
528
                    </div>
529
530
                    <div class="yui-u">
531
                        <p><strong>Note:</strong> You must be online to use these options.</p>
532
                        <ul>
533
                            <li><a href="/cgi-bin/koha/offline_circ/list.pl">Pending offline circulation actions</a>
534
                        </ul>
535
                    </div>
536
                </div>
537
            </div>
538
539
            <div id="offline-sync" style="display: none;" class="yui-b offline-sync">
540
                <div id="toolbar" class="btn-toolbar">
541
                    [% IF (AllowOfflineCirculation) %]
542
                        <a href="#" id="download-records" class="btn btn-small"><i class="icon-arrow-down"></i>Download records</a>
543
                    [% END %]
544
                    <a href="#" id="upload-transactions" class="btn btn-small"><i class="icon-arrow-up"></i>Upload transactions</a>
545
                </div>
546
                <div class="yui-g">
547
                    <h1>Offline circulation</h1>
548
                    <div class="yui-u first">
549
                        <div id="download-message">
550
                            You have records in the offline circulation database on this
551
                            computer, but they may not be current:
552
                            <ul>
553
                                <li>Patron records were last synced on: <span id="patron-timestamp">(checking)</span></li>
554
                                <li>Item records were last synced on: <span id="item-timestamp">(checking)</span></li>
555
                                <li>Circulation records were last synced on: <span id="issue-timestamp">(checking)</span></li>
556
                            </ul>
557
                        </div>
558
                    </div>
559
560
                    <div class="yui-u">
561
                        <div id="upload-message">You have transactions in the offline
562
                            circulation database on this computer that have not been
563
                            uploaded.
564
                        </div>
565
                    </div>
566
                </div>
567
            </div>
568
569
            <div id="offline-returns" style="display: none;" class="yui-b offline-returns">
570
                <div class="yui-g">
571
                    <form id="checkin-form" method="post" action="/cgi-bin/koha/circ/returns.pl" autocomplete="off" >
572
                        <div class="yui-u first">
573
                            <fieldset>
574
                                <legend>Check In</legend>
575
                                <label for="barcode">Enter item barcode: </label>
576
                                <input name="barcode" id="barcode" size="14" class="focus"/>
577
                                <input type="submit" class="submit" value="Submit" />
578
                            </fieldset>
579
                        </div>
580
                    </form>
581
                </div>
582
583
                <div id="session-returned" style="display: none;">
584
                    <h2>Checked-in items</h2>
585
                    <table id="already-checked-in">
586
                        <thead>
587
                            <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>
588
                        </thead>
589
                        <tbody>
590
                        </tbody>
591
                    </table>
592
                </div>
593
            </div>
594
595
            <div id="offline-circulation" style="display: none;" class="yui-b offline-circulation">
596
                <div class="yui-g">
597
                    <form method="post" action="/cgi-bin/koha/circ/offline-circulation.pl" id="mainform" name="mainform" autocomplete="off">
598
                        <fieldset id="circ_circulation_issue">
599
                            <span id="clearscreen"><a href="/cgi-bin/koha/circ/offline-circulation.pl" title="Clear screen">x</a></span>
600
                            <label for="barcode">Checking out to <span class="patron-title"></span></label>
601
                            <div class="hint">Enter item barcode:</div>
602
                            <input type="text" name="barcode" id="barcode" class="barcode focus" size="14" />
603
                            <input type="submit" value="Check Out" />
604
605
                            <div class="date-select">
606
                                <div class="hint">Specify due date [% INCLUDE 'date-format.inc' %]: </div>
607
                                <input type="text" size="13" id="duedatespec" name="duedatespec" value="[% duedatespec %]" readonly="readonly" />
608
                                <label for="stickyduedate"> Remember for session:</label>
609
                                <input type="checkbox" id="stickyduedate" onclick="this.form.barcode.focus();" name="stickyduedate" checked="checked" />
610
                                <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;" />
611
                            </div>
612
                        </fieldset>
613
                    </form>
614
                </div>
615
616
                <div class="yui-g"><div id="patronlists" class="toptabs">
617
                    <ul>
618
                        <li><a href="#checkouts"><span class="checkout-count">0</span> Checkouts</a></li>
619
                        <li><a href="#fines"><span class="fine-amount">0</span> in fines</a></li>
620
                    </ul>
621
622
                    <!-- SUMMARY : TODAY & PREVIOUS ISSUES -->
623
                    <div id="checkouts">
624
                        <div id="session-issues">
625
                            <table id="issuest">
626
                                <thead><tr>
627
                                    <th scope="col">Due date</th>
628
                                    <th scope="col">Title</th>
629
                                    <th scope="col">Barcode</th>
630
                                    <th scope="col">Item type</th>
631
                                    <th scope="col">Checked out on</th>
632
                                    <th scope="col">Checked out from</th>
633
                                    <th scope="col">Call no</th>
634
                                    <th scope="col">Charge</th>
635
                                </tr></thead>
636
                                <tbody>
637
                                </tbody>
638
                            </table>
639
                        </div>
640
641
                        <div id="oldissues">
642
                            <h5>Previous checkouts</h5>
643
                            <table id="oldissuest">
644
                                <thead><tr>
645
                                    <th scope="col">Due date</th>
646
                                    <th scope="col">Title</th>
647
                                    <th scope="col">Barcode</th>
648
                                    <th scope="col">Item type</th>
649
                                    <th scope="col">Checked out on</th>
650
                                    <th scope="col">Checked out from</th>
651
                                    <th scope="col">Call no</th>
652
                                    <th scope="col">Charge</th>
653
                                </tr></thead>
654
                                <tbody>
655
                                </tbody>
656
                            </table>
657
                        </div>
658
                    </div>
659
660
                    <div id="fines">
661
                        <span class="patron-title"></span> has <span class="fine-amount">0</span> in fines. If you would like you can record payments.
662
                        <fieldset><legend>Pay fines</legend>
663
                            <label for="pay-fine-amount">Fine amount: </label><input type="text" name="pay-fine-amount" id="pay-fine-amount"/>
664
                            <button id="pay-fine" class="submit">Pay fine</button>
665
666
                            <table id="session-payments" style="display: none;">
667
                                <thead><tr><th>Amount</th><th>Timestamp</th></tr></thead>
668
                                <tbody></tbody>
669
                            </table>
670
                        </fieldset>
671
                    </div>
672
                </div>
673
            </div>
674
        </div>
675
    </div>
676
677
    <div class="yui-b offline-circulation" style="display: none;">
678
        <div class="patroninfo"><h5 class="patron-title"></h5>
679
            <ul>
680
                <li id="patron-address-1"></li>
681
                <li id="patron-address-2"></li>
682
                <li id="patron-address-parts"><!-- city, state, zipcode, country --></li>
683
                <li id="patron-phone"></li>
684
                <li id="patron-email"></li>
685
                <li id="patron-category"></li>
686
                <li id="patron-library"></li>
687
            </ul>
688
        </div>
689
690
[% INCLUDE 'intranet-bottom.inc' %]
(-)a/offline_circ/download.pl (+107 lines)
Line 0 Link Here
1
#!/usr/bin/perl
2
3
# Copyright 2013 C & P Bibliography Services
4
#
5
# This file is part of Koha.
6
#
7
# Koha is free software; you can redistribute it and/or modify it under the
8
# terms of the GNU General Public License as published by the Free Software
9
# Foundation; either version 3 of the License, or (at your option) any later
10
# version.
11
#
12
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License along with
17
# Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
18
# Suite 330, Boston, MA  02111-1307 USA
19
#
20
21
use Modern::Perl;
22
use CGI;
23
use JSON;
24
use C4::Auth;
25
use C4::Output;
26
use C4::Context;
27
use C4::Koha;
28
29
my $query = new CGI;
30
my ( $template, $loggedinuser, $cookie, $flags ) =
31
  checkauth( $query, undef, { circulate => "circulate_remaining_permissions" },
32
    "intranet" );
33
34
my $page     = $query->param('page') || 0;
35
my $startrec = int($page) * 5000;
36
my $req_data = $query->param('data') || '';
37
38
my $patrons_query = qq{SELECT
39
    borrowers.borrowernumber, cardnumber, surname, firstname, title,
40
    othernames, initials, streetnumber, streettype, address, address2, city,
41
    state, zipcode, country, email, phone, mobile, fax, dateofbirth, branchcode,
42
    categorycode, dateenrolled, dateexpiry, gonenoaddress, lost, debarred,
43
    debarredcomment, SUM(accountlines.amountoutstanding) AS fine
44
    FROM borrowers
45
    LEFT JOIN accountlines ON borrowers.borrowernumber=accountlines.borrowernumber
46
    GROUP BY borrowers.borrowernumber
47
    LIMIT $startrec, 5000;
48
    };
49
50
# NOTE: we can't fit very long titles on the interface so there isn't really any point in transferring them
51
my $items_query = qq{SELECT
52
    items.barcode AS barcode, items.itemnumber AS itemnumber,
53
    items.itemcallnumber AS callnumber, items.homebranch AS homebranch,
54
    items.holdingbranch AS holdingbranch, items.itype AS itemtype,
55
    items.materials AS materials, LEFT(biblio.title, 60) AS title,
56
    biblio.author AS author, biblio.biblionumber AS biblionumber
57
    FROM items
58
    JOIN biblio ON biblio.biblionumber = items.biblionumber
59
    LIMIT $startrec, 5000;
60
    };
61
62
my $issues_query = qq{SELECT
63
    biblio.title AS title,
64
    items.barcode AS barcode,
65
    items.itemcallnumber AS callnumber,
66
    issues.date_due AS date_due,
67
    issues.issuedate AS issuedate,
68
    issues.renewals AS renewals,
69
    borrowers.cardnumber AS cardnumber,
70
    CONCAT(borrowers.surname, ', ', borrowers.firstname) AS borrower_name
71
    FROM issues
72
    JOIN items ON items.itemnumber = issues.itemnumber
73
    JOIN biblio ON biblio.biblionumber = items.biblionumber
74
    JOIN borrowers ON borrowers.borrowernumber = issues.borrowernumber
75
    LIMIT $startrec, 5000;
76
    };
77
78
if ( $req_data eq 'all' ) {
79
    print $query->header( -type => 'application/json', -charset => 'utf-8' );
80
    print to_json(
81
        {
82
            'patrons' => get_data( $patrons_query, 'cardnumber' ),
83
            'items'   => get_data( $items_query,   'barcode' ),
84
            'issues'  => get_data( $issues_query,  'barcode' ),
85
        }
86
    );
87
}
88
elsif ( $req_data eq 'patrons' ) {
89
    print $query->header( -type => 'application/json', -charset => 'utf-8' );
90
    print to_json( { 'patrons' => get_data( $patrons_query, 'cardnumber' ), } );
91
}
92
elsif ( $req_data eq 'items' ) {
93
    print $query->header( -type => 'application/json', -charset => 'utf-8' );
94
    print to_json( { 'items' => get_data( $items_query, 'barcode' ), } );
95
}
96
elsif ( $req_data eq 'issues' ) {
97
    print $query->header( -type => 'application/json', -charset => 'utf-8' );
98
    print to_json( { 'issues' => get_data( $issues_query, 'barcode' ), } );
99
}
100
101
sub get_data {
102
    my ( $sql, $key ) = @_;
103
    my $dbh = C4::Context->dbh;
104
    my $sth = $dbh->prepare($sql);
105
    $sth->execute();
106
    return $sth->fetchall_hashref($key);
107
}
(-)a/offline_circ/service.pl (-1 / +1 lines)
Lines 29-34 my $cgi = CGI->new; Link Here
29
29
30
# get the status of the user, this will check his credentials and rights
30
# get the status of the user, this will check his credentials and rights
31
my ($status, $cookie, $sessionId) = C4::Auth::check_api_auth($cgi, undef);
31
my ($status, $cookie, $sessionId) = C4::Auth::check_api_auth($cgi, undef);
32
($status, $sessionId) = C4::Auth::check_cookie_auth($cgi, undef) if ($status ne 'ok');
32
33
33
my $result;
34
my $result;
34
35
35
- 

Return to bug 10240