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

(-)a/C4/UsageStats.pm (-1 lines)
Lines 225-231 sub _shared_preferences { Link Here
225
        SuspendHoldsOpac
225
        SuspendHoldsOpac
226
        TransferWhenCancelAllWaitingHolds
226
        TransferWhenCancelAllWaitingHolds
227
        AllowAllMessageDeletion
227
        AllowAllMessageDeletion
228
        AllowOfflineCirculation
229
        PatronAutoComplete
228
        PatronAutoComplete
230
        CircAutoPrintQuickSlip
229
        CircAutoPrintQuickSlip
231
        DisplayClearScreenButton
230
        DisplayClearScreenButton
(-)a/circ/circulation-home.pl (-2 lines)
Lines 37-42 my ($template, $loggedinuser, $cookie, $flags) = get_template_and_user( Link Here
37
# Checking if there is a Fast Cataloging Framework
37
# Checking if there is a Fast Cataloging Framework
38
$template->param( fast_cataloging => 1 ) if Koha::BiblioFrameworks->find( 'FA' );
38
$template->param( fast_cataloging => 1 ) if Koha::BiblioFrameworks->find( 'FA' );
39
39
40
$template->{'VARS'}->{'AllowOfflineCirculation'} = C4::Context->preference('AllowOfflineCirculation');
41
42
output_html_with_http_headers $query, $cookie, $template->output;
40
output_html_with_http_headers $query, $cookie, $template->output;
(-)a/circ/offline-mf.pl (-35 lines)
Lines 1-35 Link Here
1
#!/usr/bin/perl
2
3
# Copyright 2013 C & P Bibliography Services
4
# This file is part of Koha.
5
#
6
# Koha is free software; you can redistribute it and/or modify it
7
# under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 3 of the License, or
9
# (at your option) any later version.
10
#
11
# Koha is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
# GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with Koha; if not, see <http://www.gnu.org/licenses>.
18
19
use Modern::Perl;
20
use CGI qw ( -utf8 );
21
use C4::Auth qw( get_template_and_user );
22
23
my $query = CGI->new;
24
my ( $template, $loggedinuser, $cookie, $flags ) = get_template_and_user(
25
    {
26
        template_name   => "circ/offline-mf.tt",
27
        query           => $query,
28
        type            => "intranet",
29
        flagsrequired   => { circulate => "circulate_remaining_permissions" },
30
    }
31
);
32
33
$template->{'VARS'}->{'cookie'} = $cookie;
34
print $query->header( -type => 'text/cache-manifest', cookie => $cookie );
35
print $template->output;
(-)a/circ/offline.pl (-39 lines)
Lines 1-39 Link Here
1
#!/usr/bin/perl
2
3
# Copyright 2013 C & P Bibliography Services
4
# This file is part of Koha.
5
#
6
# Koha is free software; you can redistribute it and/or modify it
7
# under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 3 of the License, or
9
# (at your option) any later version.
10
#
11
# Koha is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
# GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with Koha; if not, see <http://www.gnu.org/licenses>.
18
19
use Modern::Perl;
20
use CGI qw ( -utf8 );
21
use C4::Auth qw( get_template_and_user );
22
use C4::Output qw( output_html_with_http_headers );
23
use C4::Context;
24
25
my $query = CGI->new;
26
my ( $template, $loggedinuser, $cookie, $flags ) = get_template_and_user(
27
    {
28
        template_name   => "circ/offline.tt",
29
        query           => $query,
30
        type            => "intranet",
31
        flagsrequired   => { circulate => "circulate_remaining_permissions" },
32
    }
33
);
34
35
$template->{'VARS'}->{'AllowOfflineCirculation'} =
36
  C4::Context->preference('AllowOfflineCirculation');
37
$template->{'VARS'}->{'maxoutstanding'} =
38
  C4::Context->preference('maxoutstanding') || 0;
39
output_html_with_http_headers $query, $cookie, $template->output;
(-)a/installer/data/mysql/atomicupdate/bug_33961.pl (+15 lines)
Line 0 Link Here
1
use Modern::Perl;
2
3
return {
4
    bug_number => "33961",
5
    description => "Remove Offline circulation tool",
6
    up => sub {
7
        my ($args) = @_;
8
        my ($dbh, $out) = @$args{qw(dbh out)};
9
        $dbh->do(q{
10
            DELETE FROM systempreferences
11
            WHERE variable="AllowOfflineCirculation"
12
        });
13
        say $out "Removed system preference 'AllowOfflineCirculation'";
14
    },
15
};
(-)a/installer/data/mysql/mandatory/sysprefs.sql (-1 lines)
Lines 35-41 INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` Link Here
35
('AllowMultipleCovers','0','1','Allow multiple cover images to be attached to each bibliographic record.','YesNo'),
35
('AllowMultipleCovers','0','1','Allow multiple cover images to be attached to each bibliographic record.','YesNo'),
36
('AllowMultipleIssuesOnABiblio',1,'Allow/Don\'t allow patrons to check out multiple items from one biblio','','YesNo'),
36
('AllowMultipleIssuesOnABiblio',1,'Allow/Don\'t allow patrons to check out multiple items from one biblio','','YesNo'),
37
('AllowNotForLoanOverride','0','','If ON, Koha will allow the librarian to loan a not for loan item.','YesNo'),
37
('AllowNotForLoanOverride','0','','If ON, Koha will allow the librarian to loan a not for loan item.','YesNo'),
38
('AllowOfflineCirculation','0','','If on, enables HTML5 offline circulation functionality.','YesNo'),
39
('AllowPatronToControlAutorenewal','0',NULL,'If enabled, patrons will have a field in their account to choose whether their checkouts are auto renewed or not','YesNo'),
38
('AllowPatronToControlAutorenewal','0',NULL,'If enabled, patrons will have a field in their account to choose whether their checkouts are auto renewed or not','YesNo'),
40
('AllowPatronToSetCheckoutsVisibilityForGuarantor',  '0', NULL, 'If enabled, the patron can set checkouts to be visible to their guarantor',  'YesNo'),
39
('AllowPatronToSetCheckoutsVisibilityForGuarantor',  '0', NULL, 'If enabled, the patron can set checkouts to be visible to their guarantor',  'YesNo'),
41
('AllowPatronToSetFinesVisibilityForGuarantor',  '0', NULL, 'If enabled, the patron can set fines to be visible to their guarantor',  'YesNo'),
40
('AllowPatronToSetFinesVisibilityForGuarantor',  '0', NULL, 'If enabled, the patron can set fines to be visible to their guarantor',  'YesNo'),
(-)a/koha-tmpl/intranet-tmpl/lib/jquery/plugins/jquery.indexeddb.js (-517 lines)
Lines 1-517 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/css/src/staff-global.scss (-25 lines)
Lines 3549-3579 span { Link Here
3549
    border-bottom-left-radius: 5px;
3549
    border-bottom-left-radius: 5px;
3550
}
3550
}
3551
3551
3552
.loading-overlay {
3553
    background-color: #FFFFFF;
3554
    cursor: wait;
3555
    height: 100%;
3556
    left: 0;
3557
    opacity: .7;
3558
    position: fixed;
3559
    top: 0;
3560
    width: 100%;
3561
    z-index: 1000;
3562
3563
    div {
3564
        background: transparent url("../img/loading.gif") top left no-repeat;
3565
        font-size: 175%;
3566
        font-weight: bold;
3567
        height: 2em;
3568
        left: 50%;
3569
        margin: -1em 0 0 -2.5em;
3570
        padding-left: 50px;
3571
        position: absolute;
3572
        top: 50%;
3573
        width: 15em;
3574
    }
3575
}
3576
3577
#merge_invoices {
3552
#merge_invoices {
3578
    display: none;
3553
    display: none;
3579
    margin: 1em auto;
3554
    margin: 1em auto;
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref (-6 lines)
Lines 145-156 Circulation: Link Here
145
            - "The following fields should be excluded from the patron checkout history CSV or MARC (ISO 2709) export:"
145
            - "The following fields should be excluded from the patron checkout history CSV or MARC (ISO 2709) export:"
146
            - pref: ExportRemoveFields
146
            - pref: ExportRemoveFields
147
            - (separate fields with space, e.g. 100a 200b 300c).
147
            - (separate fields with space, e.g. 100a 200b 300c).
148
        -
149
            - pref: AllowOfflineCirculation
150
              choices:
151
                  1: Enable
152
                  0: "Don't enable"
153
            - "offline circulation on regular circulation computers. (<strong>NOTE:</strong> This system preference does not affect the Firefox add-on or the desktop application)."
154
        -
148
        -
155
            - pref: ShowAllCheckins
149
            - pref: ShowAllCheckins
156
              choices:
150
              choices:
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation-home.tt (-3 lines)
Lines 164-172 Link Here
164
            <div class="col-md-12">
164
            <div class="col-md-12">
165
                <h4>Offline circulation</h4>
165
                <h4>Offline circulation</h4>
166
                    <div class="col-sm-5 col-md-3">
166
                    <div class="col-sm-5 col-md-3">
167
                    [% IF (AllowOfflineCirculation) %]
168
                        <a href="/cgi-bin/koha/circ/offline.pl">Built-in offline circulation interface</a>
169
                    [% END %]
170
                       <p><a href="/cgi-bin/koha/offline_circ/process_koc.pl">Upload offline circulation file (.koc)</a></p>
167
                       <p><a href="/cgi-bin/koha/offline_circ/process_koc.pl">Upload offline circulation file (.koc)</a></p>
171
                       <p><a href="/cgi-bin/koha/offline_circ/list.pl">Pending offline circulation actions</a></p>
168
                       <p><a href="/cgi-bin/koha/offline_circ/list.pl">Pending offline circulation actions</a></p>
172
                    </div>
169
                    </div>
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/offline-mf.tt (-33 lines)
Lines 1-33 Link Here
1
CACHE MANIFEST
2
# [% cookie | html %]
3
4
# Explicitly cached 'master entries'.
5
CACHE:
6
/cgi-bin/koha/circ/offline.pl
7
[% interface | html %]/lib/bootstrap/bootstrap.min.css
8
[% interface | html %]/lib/bootstrap/bootstrap-theme.min.css
9
[% interface | html %]/lib/bootstrap/bootstrap.min.js
10
[% interface | html %]/lib/jquery/jquery-ui-1.13.1.min.css
11
[% interface | html %]/lib/jquery/jquery-ui-1.13.1.min.js
12
[% interface | html %]/lib/jquery/jquery-3.6.0.min.js
13
[% interface | html %]/lib/jquery/jquery-migrate-3.3.2.min.js
14
[% interface | html %]/lib/jquery/plugins/jquery.highlight-3.js
15
[% interface | html %]/lib/shortcut/shortcut.js
16
[% interface | html %]/lib/jquery/plugins/jquery.indexeddb.js
17
[% interface | html %]/lib/jquery/plugins/jquery.validate.min.js
18
[% interface | html %]/[% theme | html %]/css/print.css
19
[% interface | html %]/[% theme | html %]/css/staff-global.css
20
[% interface | html %]/[% theme | html %]/js/basket.js
21
[% interface | html %]/[% theme | html %]/js/offlinecirc.js
22
[% interface | html %]/[% theme | html %]/js/staff-global.js
23
[% interface | html %]/prog/img/koha-logo-medium.png
24
[% interface | html %]/prog/img/loading.gif
25
[% interface | html %]/prog/sound/beep.ogg
26
[% interface | html %]/prog/sound/critical.ogg
27
28
# Resources that require the user to be online.
29
NETWORK:
30
*
31
32
# Resources that can be substituted if the user is offline
33
FALLBACK:
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/offline.tt (-788 lines)
Lines 1-788 Link Here
1
[% USE raw %]
2
[% USE Asset %]
3
[% USE Branches %]
4
[% SET footerjs = 1 %]
5
<!DOCTYPE html>
6
[% IF (AllowOfflineCirculation) %]
7
[% SET manifestattr = 'manifest="/cgi-bin/koha/circ/offline-mf.pl"' %]
8
[% END %]
9
[% IF ( bidi && AllowOfflineCirculation ) %]<html lang="[% lang | html %]" dir="[% bidi | html %]" manifest="/cgi-bin/koha/circ/offline-mf.pl">
10
[% ELSIF ( bidi ) %]<html lang="[% lang | html %]" dir="[% bidi | html %]">
11
[% ELSIF ( AllowOfflineCirculation ) %]<html lang="[% lang | html %]" manifest="/cgi-bin/koha/circ/offline-mf.pl">
12
[% ELSE %]<html lang="[% lang | html %]">[% END %]
13
<head>
14
<title>Circulation &rsaquo; Koha</title>
15
[% INCLUDE 'doc-head-close.inc' %]
16
</head>
17
18
<body id="circ_offline" class="circ">
19
[% WRAPPER 'header.inc' %]
20
    [% INCLUDE 'circ-search.inc' %]
21
[% END %]
22
<div class="loading-overlay" style="display: none;">
23
    <div>Downloading records, please wait...</div>
24
</div>
25
26
[% WRAPPER 'sub-header.inc' %]
27
    [% WRAPPER breadcrumbs %]
28
        [% WRAPPER breadcrumb_item %]
29
            <a id="go-to-home" href="#offline-home" aria-current="page">Offline circulation</a>
30
        [% END %]
31
    [% END #/ WRAPPER breadcrumbs %]
32
[% END #/ WRAPPER sub-header.inc %]
33
34
<div class="main container-fluid">
35
    <div class="row">
36
        <div class="col-sm-10 col-sm-push-2">
37
            <main>
38
                <h1>Offline circulation</h1>
39
40
            <audio id="alert_sound" src="[% interface | html %]/prog/sound/critical.ogg" preload="auto"></audio>
41
            <audio id="success_sound" src="[% interface | html %]/prog/sound/beep.ogg" preload="auto"></audio>
42
43
            <div id="alerts"></div>
44
45
            [% UNLESS (AllowOfflineCirculation) %]
46
                <div id="noofflinecircwarning" class="dialog alert">
47
                    <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>
48
                </div>
49
            [% END %]
50
51
            <div id="offline-home" class="offline-home">
52
                <div class="row">
53
                    <div class="col-sm-6">
54
                        <ul>
55
                            <li><a id="go-to-circ" href="#offline-circulation">Check out</a></li>
56
                            <li><a id="go-to-returns" href="#offline-returns">Check in</a></li>
57
                        </ul>
58
                    </div>
59
60
                    <div class="col-sm-6">
61
                        <p><strong>Note:</strong> You must be online to use these options.</p>
62
                        <ul>
63
                            <li><a id="go-to-sync" href="#offline-sync">Synchronize</a></li>
64
                            <li><a id="go-to-pending" href="/cgi-bin/koha/offline_circ/list.pl">Pending offline circulation actions</a>
65
                        </ul>
66
                    </div>
67
                </div>
68
            </div>
69
70
            <div id="offline-sync" style="display: none;" class="offline-sync">
71
                <div id="toolbar" class="btn-toolbar">
72
                    [% IF (AllowOfflineCirculation) %]
73
                        <a href="#" id="download-records" class="btn btn-default"><i class="fa fa-arrow-down"></i> Download records</a>
74
                    [% END %]
75
                    <a href="#" id="upload-transactions" class="btn btn-default"><i class="fa fa-arrow-up"></i> Upload transactions</a>
76
                </div>
77
                <h1>Offline circulation</h1>
78
                <div class="row">
79
                    <div class="col-sm-6">
80
                        <div id="download-message">
81
                            In order for offline circulation to work on this computer,
82
                            your library's records must be up-to-date on this computer:
83
                            <ul>
84
                                <li>Patron records were last synced on: <span id="patron-timestamp">(checking)</span></li>
85
                                <li>Item records were last synced on: <span id="item-timestamp">(checking)</span></li>
86
                                <li>Circulation records were last synced on: <span id="issue-timestamp">(checking)</span></li>
87
                            </ul>
88
                        </div>
89
                    </div>
90
91
                    <div class="col-sm-6">
92
                        <div id="upload-message">
93
                        </div>
94
                        <div>View <a href="/cgi-bin/koha/offline_circ/list.pl">pending offline circulation actions</a></div>
95
                    </div>
96
                </div>
97
            </div>
98
99
            <div id="offline-returns" style="display: none;" class="offline-returns">
100
                <div class="row">
101
                    <form id="checkin-form" method="post" action="/cgi-bin/koha/circ/returns.pl" autocomplete="off" >
102
                        <div class="col-sm-6">
103
                            <fieldset>
104
                                <legend>Check in</legend>
105
                                <label for="checkin-barcode">Enter item barcode: </label>
106
                                <input name="barcode" id="checkin-barcode" size="14" class="focus" type="text" />
107
                                <input type="submit" class="btn btn-primary" value="Submit" />
108
                            </fieldset>
109
                        </div>
110
                    </form>
111
                </div>
112
113
                <div id="session-returned" style="display: none;">
114
                    <h2>Checked-in items</h2>
115
                    <table id="already-checked-in">
116
                        <thead>
117
                            <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>
118
                        </thead>
119
                        <tbody>
120
                        </tbody>
121
                    </table>
122
                </div>
123
            </div>
124
125
            <div style="display: none;" class="offline-circulation-instructions">
126
                Scan a patron barcode to start.
127
            </div>
128
129
            <div id="offline-circulation" style="display: none;" class="offline-circulation">
130
                <form method="post" action="/cgi-bin/koha/circ/offline.pl" id="mainform" name="mainform" autocomplete="off">
131
                    <fieldset id="circ_circulation_issue">
132
                        <span id="clearscreen"><a href="/cgi-bin/koha/circ/offline.pl" title="Clear screen">x</a></span>
133
                        <label for="checkout-barcode">Checking out to <span class="patron-title"></span></label>
134
                        <div class="hint">Enter item barcode:</div>
135
                        <input type="text" name="barcode" id="checkout-barcode" class="barcode focus" size="14" />
136
                        <input type="submit" class="btn btn-primary" value="Check out" />
137
138
                        <div class="date-select">
139
                            <div class="hint">Specify due date [% INCLUDE 'date-format.inc' %]: </div>
140
                                <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" />
141
                            <label for="stickyduedate"> Remember for session:</label>
142
                            <input type="checkbox" id="stickyduedate" onclick="this.form.barcode.focus();" name="stickyduedate" checked="checked" />
143
                            <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;" />
144
                        </div>
145
                    </fieldset>
146
                </form>
147
148
                <div id="patronlists" class="toptabs">
149
                    <ul class="nav nav-tabs" role="tablist">
150
                        <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>
151
                        <li role="presentation"><a href="#fines" aria-controls="fines" role="tab" data-toggle="tab"><span class="fine-amount">0</span> in fines</a></li>
152
                    </ul>
153
                    <div class="tab-content">
154
                        <!-- SUMMARY : TODAY & PREVIOUS ISSUES -->
155
                        <div role="tabpanel" class="tab-pane active" id="checkouts">
156
                            <div id="session-issues">
157
                                <table id="issuest">
158
                                    <thead><tr>
159
                                        <th scope="col">Due date</th>
160
                                        <th scope="col">Title</th>
161
                                        <th scope="col">Barcode</th>
162
                                        <th scope="col">Item type</th>
163
                                        <th scope="col">Checked out on</th>
164
                                        <th scope="col">Checked out from</th>
165
                                        <th scope="col">Call number</th>
166
                                        <th scope="col">Charge</th>
167
                                    </tr></thead>
168
                                    <tbody>
169
                                    </tbody>
170
                                </table>
171
                            </div>
172
173
                            <div id="oldissues">
174
                                <h5>Previous checkouts</h5>
175
                                <table id="oldissuest">
176
                                    <thead><tr>
177
                                        <th scope="col">Due date</th>
178
                                        <th scope="col">Title</th>
179
                                        <th scope="col">Barcode</th>
180
                                        <th scope="col">Item type</th>
181
                                        <th scope="col">Checked out on</th>
182
                                        <th scope="col">Checked out from</th>
183
                                        <th scope="col">Call number</th>
184
                                        <th scope="col">Charge</th>
185
                                    </tr></thead>
186
                                    <tbody>
187
                                    </tbody>
188
                                </table>
189
                            </div>
190
                        </div>
191
192
                        <div role="tabpanel" class="tab-pane" id="fines">
193
                            <span class="patron-title"></span> has <span class="fine-amount">0</span> in fines. If you would like you can record payments.
194
                            <fieldset><legend>Pay fines</legend>
195
                                <label for="pay-fine-amount">Fine amount: </label><input type="text" name="pay-fine-amount" id="pay-fine-amount"/>
196
                                <button id="pay-fine" class="submit">Pay fine</button>
197
198
                                <table id="session-payments" style="display: none;">
199
                                    <thead><tr><th>Amount</th><th>Date</th></tr></thead>
200
                                    <tbody></tbody>
201
                                </table>
202
                            </fieldset>
203
                        </div> <!-- /# -->fines
204
                    </div> <!-- /.tab-content -->
205
                </div> <!-- /#patronlists -->
206
            </div>
207
        </div>
208
    </main>
209
210
            </main>
211
        </div> <!-- /.col-sm-10.col-sm-push-2 -->
212
213
        <div class="offline-circulation" style="display: none;">
214
            <div class="col-sm-2 col-sm-pull-10">
215
                <aside>
216
                    <div class="patroninfo"><h5 class="patron-title">&nbsp;</h5>
217
                        <ul>
218
                            <li id="patron-address-1"></li>
219
                            <li id="patron-address-2"></li>
220
                            <li id="patron-address-parts"><!-- city, state, zipcode, country --></li>
221
                            <li id="patron-phone"></li>
222
                            <li id="patron-email"></li>
223
                            <li id="patron-category"></li>
224
                            <li id="patron-library"></li>
225
                        </ul>
226
                    </div>
227
                </aside>
228
            </div> <!-- /.col-sm-2.col-sm-pull-10 -->
229
        </div> <!-- /.row -->
230
    </div>
231
232
[% MACRO jsinclude BLOCK %]
233
    [% INCLUDE 'calendar.inc' %]
234
    [% Asset.js("lib/jquery/plugins/jquery.indexeddb.js") | $raw %]
235
    [% Asset.js("js/offlinecirc.js") | $raw %]
236
    <script>
237
        var ALERT_SUCCESSFUL_CHECKIN = _("Checked in item.");
238
        var ALERT_MATERIALS = _("Note about the accompanying materials: %s");
239
        var ALERT_RESTRICTED = _("Patron is RESTRICTED");
240
        var ALERT_NO_MATCHING_ITEM = _("No item with barcode in offline database (transaction recorded anyway): %s");
241
        var ALERT_NOT_CHECKED_OUT = _("Item not listed as checked out in offline database (transaction recorded anyway)");
242
        var ALERT_ITEM_WITHDRAWN = _("Item has been withdrawn (transaction recorded anyway)");
243
        var ALERT_ITEM_RESTRICTED = _("Item is restricted (transaction recorded anyway)");
244
        var ALERT_ITEM_LOST = _("Item has been lost (transaction recorded anyway)");
245
        var ALERT_NO_MATCHING_PATRON = _("No patron card number in offline database (proceeding anyway): %s");
246
        var ALERT_PATRON_GONE_NO_ADDRESS = _("Patron's address is in doubt (proceeding anyway)");
247
        var ALERT_PATRON_CARD_LOST = _("Patron's card is lost");
248
        var ALERT_PATRON_EXPIRED = _("Patron's card is expired (%s)");
249
        var ALERT_PATRON_BLOCKED_TEMPORARY = _("Patron has had overdue items and is restricted until: %s");
250
        var ALERT_PATRON_RESTRICTED = _("Patron is restricted");
251
        var ALERT_PATRON_FINE = _("Patron has outstanding fines: %s");
252
        var ALERT_PATRON_FINE_OVER_LIMIT = _("Patron fines are over limit: %s");
253
        var UPLOAD_PENDING_MESSAGE = _("You have transactions in the offline circulation database on this computer that have not been uploaded.");
254
        var NO_UPLOAD_PENDING_MESSAGE = _("You do not have any pending transactions in the offline circulation database on this computer.");
255
256
        var start;
257
258
        var dateformat = '[% IF ( dateformat_us ) %]mm/dd/yy[% ELSIF ( dateformat_metric ) %]dd/mm/yy[% ELSE %]yy-mm-dd[% END %]';
259
        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 %];
260
        var flatpickr_timeformat_string = [% IF Koha.Preference('TimeFormat') == '12hr' %]"G:i K"[% ELSE %]"H:i"[% END %];
261
        var flatpickr_timestamp_string = "Y-m-d H:i";
262
        var flatpickr_datetime_string = flatpickr_dateformat_string + " " + flatpickr_timeformat_string;
263
264
        function checkin(barcode, item, error) {
265
            var alerts = checkAlerts(barcode, item);
266
            if (typeof item === 'undefined') {
267
                item = { };
268
            }
269
            item.title = item.title || _("(Unknown)");
270
            item.author = item.author || _("(Unknown)");
271
            item.homebranch = item.homebranch || "";
272
            item.holdingbranch = item.holdingbranch || "";
273
            item.callnumber = item.callnumber || "";
274
            item.itemtype = item.itemtype || "";
275
            item.barcode = item.barcode || barcode;
276
            var trans = { "timestamp" : new Date().toMySQLString(),
277
                          "barcode" : barcode,
278
                          "action" : "return"
279
                        };
280
            $('#alerts').empty();
281
            $('.offline-home').hide();
282
            $('.offline-sync').hide();
283
            $('.offline-circulation').hide();
284
            $('.offline-circulation-instructions').hide();
285
            $('.offline-returns').show();
286
            kohadb.recordTransaction(trans, function () {
287
                $('#session-returned').show();
288
                $('#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>');
289
                if (alerts.length > 0) {
290
                    $('#alerts').append('<div class="dialog alert"><h3>' + _("Check in message") + '</h3></div>');
291
                    for (var msg in alerts) {
292
                        $('#alerts .dialog').append('<p>' + alerts[msg] + '</p');
293
                    }
294
                } else {
295
                    $('#alerts').append('<div class="dialog"><h3>' + ALERT_SUCCESSFUL_CHECKIN + '</h3></div>');
296
                }
297
            });
298
            setTimeout(function() { $('#checkin-barcode').trigger('focus'), 1 });
299
        }
300
301
        function checkAlerts(barcode, item) {
302
            var alerts = [];
303
            if (typeof item === 'undefined') {
304
                alerts.push(ALERT_NO_MATCHING_ITEM.format(barcode));
305
            } else {
306
                if (typeof item.materials !== 'undefined' && item.materials != null) {
307
                    alerts.push(ALERT_MATERIALS.format(item.materials));
308
                }
309
            }
310
            return alerts;
311
        }
312
313
        function showSyncInfo() {
314
            kohadb.loadSetting("item-timestamp", showTimestamp);
315
            kohadb.loadSetting("patron-timestamp", showTimestamp);
316
            kohadb.loadSetting("issue-timestamp", showTimestamp);
317
            kohadb.loadSetting("dirty", function (key, val) {
318
                if (val) {
319
                    $('#upload-message').text(UPLOAD_PENDING_MESSAGE);
320
                } else {
321
                    $('#upload-message').text(NO_UPLOAD_PENDING_MESSAGE);
322
                }
323
            });
324
        }
325
326
        function synchronize() {
327
            kohadb.saveSetting("userid", "[% logged_in_user.userid | html %]");
328
            kohadb.saveSetting("branchcode", "[% Branches.GetLoggedInBranchcode | html %]");
329
            showSyncInfo();
330
            [% UNLESS (AllowOfflineCirculation) %]
331
                reloadRecords();
332
            [% END %]
333
            showSyncInfo();
334
            $('#download-records').click(reloadRecords);
335
            $('#upload-transactions').click(function () {
336
                $('.loading-overlay div').text(_("Uploading transactions, please wait..."));
337
                $('.loading-overlay').show();
338
                $.ajax({
339
                    type: "GET",
340
                    url: "/cgi-bin/koha/offline_circ/service.pl",
341
                }).done(function (data) {
342
                    if (data) {
343
                        $('.loading-overlay').hide();
344
                        alert(_("Please log in to Koha and try again. (Error: '%s')").format(data));
345
                    } else {
346
                        var uploadIter = $.indexedDB("koha").objectStore("transactions").each(uploadTransaction);
347
                        uploadIter.done(function() {
348
                            $.indexedDB("koha").transaction(["transactions"]).then(function(){
349
                            }, function(err, e){
350
                            }, function(transaction){
351
                                transaction.objectStore("transactions").clear();
352
                            });
353
                            $('.loading-overlay').hide();
354
                            kohadb.saveSetting("dirty", false);
355
                            $('#upload-message').text(NO_UPLOAD_PENDING_MESSAGE);
356
                        });
357
                    }
358
                });
359
            });
360
361
        }
362
363
        function fPformatTimeStamp( timestamp ){
364
            var datetimeObj = flatpickr.parseDate( timestamp, flatpickr_timestamp_string );
365
            return flatpickr.formatDate( datetimeObj, flatpickr_datetime_string );
366
        }
367
368
        function showTimestamp(key, value) {
369
            if (typeof value !== 'undefined') {
370
                var ts = new Date(value);
371
                $('#' + key).text( fPformatTimeStamp( ts ) + ' ' + ts.toTimeString());
372
            } else {
373
                $('#' + key).text(_("(never)"));
374
            }
375
        }
376
377
        function reloadRecords(ev) {
378
            $(".loading-overlay div").text(_("Loading records, please wait..."));
379
            $(".loading-overlay").show();
380
            start = new Date();
381
            $.indexedDB("koha").transaction(["patrons", "items", "issues"]).then(function(){
382
                loadRecords(0);
383
            }, function(err, e){
384
            }, function(transaction){
385
                transaction.objectStore("patrons").clear();
386
                transaction.objectStore("items").clear();
387
                transaction.objectStore("issues").clear();
388
            });
389
            if (typeof ev !== 'undefined') {
390
                ev.stopPropagation();
391
            }
392
        }
393
394
        function uploadTransaction(transaction) {
395
            $.ajax({
396
                type: "POST",
397
                url: "/cgi-bin/koha/offline_circ/service.pl",
398
                data: { "userid" : kohadb.settings.userid,
399
                        "branchcode" : kohadb.settings.branchcode,
400
                        "timestamp" : transaction.value.timestamp,
401
                        "action" : transaction.value.action,
402
                        "barcode" : transaction.value.barcode,
403
                        "cardnumber" : transaction.value.cardnumber,
404
                        "amount" : transaction.value.amount,
405
                        "pending" : true,
406
                      },
407
            });
408
            return undefined, true;
409
        }
410
411
        function finishedLoading() {
412
            kohadb.saveSetting('item-timestamp', start.toISOString())
413
            kohadb.saveSetting('patron-timestamp', start.toISOString())
414
            kohadb.saveSetting('issue-timestamp', start.toISOString())
415
            showTimestamp('item-timestamp', start.toISOString());
416
            showTimestamp('patron-timestamp', start.toISOString());
417
            showTimestamp('issue-timestamp', start.toISOString());
418
            $(".loading-overlay").hide();
419
        }
420
421
        function loadRecords(page) {
422
        [% IF (AllowOfflineCirculation) %]
423
            $(".loading-overlay div").text(_("Loading page %s, please wait...").format(page));
424
            $(".loading-overlay").show();
425
            $.ajax({
426
                type: "GET",
427
                url: "/cgi-bin/koha/offline_circ/download.pl",
428
                data: { "data": "all",
429
                        "page": page
430
                      },
431
                dataType: "json",
432
            }).done(function (data) {
433
                $.indexedDB("koha").transaction(["patrons", "items", "issues"]).then(function(){
434
                    if (data.finished) {
435
                        finishedLoading();
436
                    } else {
437
                        setTimeout(function () { loadRecords(page + 1); }, 200);
438
                    }
439
                }, function(err, e){
440
                }, function(transaction){
441
                    if (data.patrons) {
442
                        var patrons = transaction.objectStore("patrons");
443
                        $.each(data.patrons, function () {
444
                            patrons.put(this);
445
                        });
446
                    }
447
                    if (data.items) {
448
                        var items = transaction.objectStore("items");
449
                        $.each(data.items, function () {
450
                            items.put(this);
451
                        });
452
                    }
453
                    if (data.issues) {
454
                        var issues = transaction.objectStore("issues");
455
                        $.each(data.issues, function () {
456
                            issues.put(this);
457
                        });
458
                    }
459
                });
460
            });
461
        [% END %]
462
        }
463
464
        function loadPatron(barcode) {
465
            $('#oldissues').hide();
466
            $('#session-issues').hide();
467
            $('#issuest tbody').empty();
468
            $('#session-payments').hide();
469
            $('.checkout-count').text(0);
470
            $.indexedDB("koha").transaction(["patrons", "issues"]).then(function() {
471
            }, function(err, e){
472
            }, function(transaction){
473
                var patrons = transaction.objectStore("patrons");
474
                patrons.get(barcode).done(function (patron, error) {
475
                    showPatron(barcode, patron, error);
476
                });
477
                var issuesidx = transaction.objectStore("issues").index("cardnumber");
478
                $('#oldissuest tbody').empty();
479
                issuesidx.each(function (item) {
480
                    $('#oldissues').show();
481
                    $('#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>");
482
                    $('.checkout-count').text(parseInt($('.checkout-count').text()) + 1);
483
                }, barcode);
484
            });
485
        }
486
487
        function checkout(barcode, item, error) {
488
            var alerts = checkAlerts(barcode, item);
489
            if (typeof item === 'undefined') {
490
                item = { };
491
            }
492
            item.title = item.title || "";
493
            item.author = item.author || "";
494
            item.homebranch = item.homebranch || "";
495
            item.holdingbranch = item.holdingbranch || "";
496
            item.callnumber = item.callnumber || "";
497
            item.itemtype = item.itemtype || "";
498
            if ($('#duedatespec').val().length === 0) {
499
                alert(_("You must set a due date in order to use offline circulation!"));
500
                setTimeout(function() { $('#duedatespec').trigger('focus'), 1 });
501
                return;
502
            }
503
            var date_due = flatpickr.parseDate( $('#duedatespec').val(), flatpickr_datetime_string );
504
            var trans = { "timestamp" : new Date().toMySQLString(),
505
                          "barcode" : barcode,
506
                          "cardnumber" : curpatron.cardnumber,
507
                          "date_due" : date_due.toMySQLString(),
508
                          "action" : "issue"
509
                        };
510
            $('#alerts').empty();
511
            kohadb.recordTransaction(trans, function () {
512
                $('#session-issues').show();
513
                $('#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>');
514
                $('.checkout-count').text(parseInt($('.checkout-count').text()) + 1);
515
                if (alerts.length > 0) {
516
                    $('#alerts').append('<div class="dialog alert"><h3>' + _("Check out message") + '</h3></div>');
517
                    for (var msg in alerts) {
518
                        $('#alerts .dialog').append('<p>' + alerts[msg] + '</p');
519
                    }
520
                }
521
                $('#checkout-barcode').val('');
522
            });
523
        }
524
525
        function recordFine(amount) {
526
            var timestamp = new Date()
527
            var trans = { "timestamp" : timestamp.toMySQLString(),
528
                          "cardnumber" : curpatron.cardnumber,
529
                          "amount" : amount,
530
                          "action" : "payment",
531
                        };
532
            kohadb.recordTransaction(trans, function () {
533
                $('#session-payments').show();
534
                $('#session-payments tbody').prepend('<tr><td>' + amount + '</td><td>' + fPformatTimeStamp( timestamp ) + '</td></tr>');
535
                $('.fine-amount').text(parseInt($('.fine-amount').text()) - amount);
536
            });
537
        }
538
539
        function checkPatronAlerts(cardnumber, patron) {
540
            var alerts = [];
541
            if (typeof patron === 'undefined') {
542
                alerts.push(ALERT_NO_MATCHING_PATRON.format(cardnumber));
543
            } else {
544
                if (patron.gonenoaddress !== '0') {
545
                    alerts.push(ALERT_PATRON_GONE_NO_ADDRESS);
546
                }
547
                if (patron.lost !== '0') {
548
                    alerts.push(ALERT_PATRON_CARD_LOST);
549
                }
550
                if (patron.debarred !== null) {
551
                    if (patron.debarred != '9999-12-31') {
552
                        alerts.push( ALERT_PATRON_BLOCKED_TEMPORARY.format( flatpickr.formatDate( new Date(patron.debarred), flatpickr_datetime_string ) ) );
553
                    } else {
554
                        alerts.push(ALERT_PATRON_RESTRICTED);
555
                    }
556
                }
557
                if (new Date(patron.dateexpiry) < new Date()) {
558
                    alerts.push( ALERT_PATRON_EXPIRED.format( flatpickr.formatDate( new Date(patron.dateexpiry), flatpickr_datetime_string ) ) );
559
                }
560
                if (parseInt(patron.fine) > [% maxoutstanding | html %]) {
561
                    alerts.push(ALERT_PATRON_FINE_OVER_LIMIT.format(patron.fine));
562
                } else if (parseInt(patron.fine) > 0) {
563
                    alerts.push(ALERT_PATRON_FINE.format(patron.fine));
564
                }
565
            }
566
            return alerts;
567
        }
568
569
        var curpatron;
570
571
        function showPatron(barcode, patron, error) {
572
            var alerts = checkPatronAlerts(barcode, patron);
573
            if (typeof patron === 'undefined') {
574
                patron = { };
575
            }
576
            patron.surname = patron.surname || "";
577
            patron.firstname = patron.firstname || "";
578
            patron.othernames = patron.othernames || "";
579
            patron.address = patron.address || "";
580
            patron.address2 = patron.address2 || "";
581
            patron.city = patron.city || "";
582
            patron.state = patron.state || "";
583
            patron.country = patron.country || "";
584
            patron.zipcode = patron.zipcode || "";
585
            patron.phone = patron.phone || "";
586
            patron.mobile = patron.mobile || "";
587
            patron.phonepro = patron.phonepro || "";
588
            patron.email = patron.email || "";
589
            patron.emailpro = patron.emailpro || "";
590
            patron.categorycode = patron.categorycode || "";
591
            patron.branchcode = patron.branchcode || "";
592
            patron.cardnumber = barcode;
593
            patron.fine = patron.fine || "0";
594
595
            patron.name = patron.firstname + (patron.othernames.length > 0 ? " (" + patron.othernames + ") " : " ") + patron.surname + " (" + barcode + ")";
596
            if (patron.name.length > 0) {
597
                $('.patron-title').text(patron.name);
598
            } else {
599
                $('.patron-title').text(_("Unrecognized patron (%s)").format(barcode));
600
            }
601
            if (patron.address.length > 0 || patron.address2.length > 0) {
602
                $('#patron-address-1').text(patron.address);
603
                $('#patron-address-2').text(patron.address2);
604
            } else {
605
                $('#patron-address-1').html('<span class="empty" id="noaddressstored">' + _("No address stored.") + '</span></li>');
606
                $('#patron-address-2').text('');
607
            }
608
            if (patron.city.length > 0) {
609
                $('#patron-address-parts').text(patron.city + (patron.state.length > 0 ? ", " + patron.state : "") + " " + patron.zipcode + (patron.country.length > 0 ? ", " + patron.country : ""));
610
            } else {
611
                $('#patron-address-parts').html('<span class="empty" id="nocitystored">' + _("No city stored.") + '</span></li>');
612
            }
613
            if (patron.phone.length > 0 || patron.mobile.length > 0 || patron.phonepro.length > 0) {
614
                $('#patron-phone').text((patron.phone.length > 0 ? patron.phone : (patron.mobile.length > 0 ? patron.mobile : (patron.phonepro.length > 0 ? patron.phonepro : ''))));
615
            } else {
616
                $('#patron-phone').html('<span class="empty" id="nophonestored">' + _("No phone stored.") + '</span></li>');
617
            }
618
            if (patron.email.length > 0 || patron.emailpro.length > 0) {
619
                $('#patron-email').text((patron.email.length > 0 ? patron.email : (patron.emailpro.length > 0 ? patron.emailpro : "")));
620
            } else {
621
                $('#patron-email').html('<span class="empty" id="noemailstored">' + _("No email stored.") + '</span></li>');
622
            }
623
            if (patron.categorycode.length > 0) {
624
                $('#patron-category').text(_("Category: %s").format(patron.categorycode));
625
            } else {
626
                $('#patron-category').html('<span class="empty" id="unknowncategory">' + _("Category code unknown.") + '</span></li>');
627
            }
628
            if (patron.branchcode.length > 0) {
629
                $('#patron-library').text(_("Home library: %s").format(patron.branchcode));
630
            } else {
631
                $('#patron-library').html('<span class="empty" id="unknowncategory">' + _("Home library unknown.") + '</span></li>');
632
            }
633
            $('.fine-amount').text(patron.fine);
634
            $('#alerts').empty();
635
            if (alerts.length > 0) {
636
                $('#alerts').append('<div class="dialog alert"><h3>' + _("Check out message") + '</h3></div>');
637
                for (var msg in alerts) {
638
                    $('#alerts .dialog').append('<p>' + alerts[msg] + '</p>');
639
                }
640
            }
641
            curpatron = patron;
642
            $('main').show();
643
            setTimeout(function() { $('#checkout-barcode').trigger('focus'), 1 });
644
        }
645
646
        // This next bit of code is to deal with the updated session issue
647
        if( typeof window.applicationCache !== "undefined" ){
648
            window.addEventListener('load', function(e) {
649
                window.applicationCache.addEventListener('updateready', function(e) {
650
                    if (window.applicationCache.status == window.applicationCache.UPDATEREADY) {
651
                        // Browser downloaded a new app cache.
652
                        // Swap it in and reload the page to get the new hotness.
653
                        window.applicationCache.swapCache();
654
                        if (confirm(_("A new version of this site is available. Load it?"))) {
655
                            window.location.reload();
656
                        }
657
                    } else {
658
                    // Manifest didn't changed. Nothing new to server.
659
                    }
660
                }, false);
661
            }, false);
662
        }
663
664
665
        $(document).ready(function () {
666
            if( typeof window.applicationCache === "undefined" ){
667
                $("#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>');
668
            } else {
669
                kohadb.initialize();
670
            }
671
            $('#header_search #circ_search .tip').text(_("Enter patron card number:"));
672
673
            $('ul[aria-labelledby="logged-in-menu"]').html('<li><a class="toplinks">' + _("You cannot change your branch or logout while using offline circulation") + '</a></li>');
674
675
            // Returns code
676
            $('#checkin-form, #checkin_search form').submit(function (event) {
677
                event.preventDefault();
678
                var barcode = $('input[name="barcode"]', this).val();
679
                $('input[name="barcode"]', this).val('');
680
                $.indexedDB("koha").transaction(["items"]).then(function() {
681
                }, function(err, e){
682
                }, function(transaction){
683
                    var items = transaction.objectStore("items");
684
                    items.get(barcode).done(function (item, error) {
685
                        checkin(barcode, item, error);
686
                    });
687
                });
688
            });
689
690
            $('#go-to-home').click(function () {
691
                $('#alerts').empty();
692
                $('.offline-sync').hide();
693
                $('.offline-circulation').hide();
694
                $('.offline-returns').hide();
695
                $('.offline-circulation-instructions').hide();
696
                $('.offline-home').show();
697
            });
698
699
            $('#go-to-returns').click(function () {
700
                $('#alerts').empty();
701
                $('.offline-home').hide();
702
                $('.offline-sync').hide();
703
                $('.offline-circulation').hide();
704
                $('.offline-circulation-instructions').hide();
705
                $('.offline-returns').show();
706
                setTimeout(function() { $('#checkin-form input[name="barcode"]').trigger('focus'), 1 });
707
            });
708
709
            $('#go-to-circ').click(function () {
710
                $('#alerts').empty();
711
                $('.offline-home').hide();
712
                $('.offline-sync').hide();
713
                $('.offline-returns').hide();
714
                $('.offline-circulation').hide();
715
                $('.offline-circulation-instructions').show();
716
                $('#header_search').selectTabByID("#circ_search");
717
                setTimeout(function() { $('#circ_search input[name="findborrower"]').trigger('focus'), 1 });
718
            });
719
720
            $('#go-to-sync').click(function () {
721
                $('#alerts').empty();
722
                showSyncInfo();
723
                $.ajax({
724
                    type: "GET",
725
                    url: "/cgi-bin/koha/offline_circ/list.pl",
726
                    success: function () {
727
                        $('.offline-home').hide();
728
                        $('.offline-returns').hide();
729
                        $('.offline-circulation').hide();
730
                        $('.offline-circulation-instructions').hide();
731
                        $('.offline-sync').show();
732
                        synchronize();
733
                    },
734
                    error: function () {
735
                        alert(_("You are offline and therefore cannot sync your database"));
736
                    }
737
                });
738
            });
739
740
            $('#go-to-pending').click(function (ev) {
741
                $('#alerts').empty();
742
                ev.preventDefault();
743
                $.ajax({
744
                    type: "GET",
745
                    url: "/cgi-bin/koha/offline_circ/list.pl",
746
                    success: function () {
747
                        window.location = '/cgi-bin/koha/offline_circ/list.pl';
748
                    },
749
                    error: function () {
750
                        alert(_("You are offline and therefore cannot process pending operations"));
751
                    }
752
                });
753
            });
754
755
            $('#patronsearch').submit(function (event) {
756
                event.preventDefault();
757
                loadPatron($('#findborrower').val());
758
                $('.offline-home').hide();
759
                $('.offline-returns').hide();
760
                $('.offline-sync').hide();
761
                $('.offline-circulation-instructions').hide();
762
                $('.offline-circulation').show();
763
                $('#findborrower').val('');
764
                setTimeout(function() { $('#checkout-barcode').trigger('focus'), 1 });
765
            });
766
767
            $('#pay-fine').click(function (event) {
768
                event.preventDefault();
769
                recordFine($('#pay-fine-amount').val());
770
            });
771
772
            $('#mainform').submit(function (event) {
773
                event.preventDefault();
774
                var barcode = $('#checkout-barcode').val();
775
                $.indexedDB("koha").transaction(["items"]).then(function() {
776
                }, function(err, e){
777
                }, function(transaction){
778
                    var items = transaction.objectStore("items");
779
                    items.get(barcode).done(function (item, error) {
780
                        checkout(barcode, item, error);
781
                    });
782
                });
783
            });
784
        });
785
    </script>
786
[% END %]
787
788
[% INCLUDE 'intranet-bottom.inc' %]
(-)a/koha-tmpl/intranet-tmpl/prog/js/offlinecirc.js (-113 lines)
Lines 1-113 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
6
 * under the terms of the GNU General Public License as published by
7
 * the Free Software Foundation; either version 3 of the License, or
8
 * (at your option) any later version.
9
 *
10
 * Koha is distributed in the hope that it will be useful, but
11
 * WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
 * GNU General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU General Public License
16
 * along with Koha; if not, see <http://www.gnu.org/licenses>.
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
            if (typeof callback === 'function') {
56
                callback(key, undefined);
57
            }
58
        }, function(transaction){
59
            var settings = transaction.objectStore("offline_settings");
60
            settings.get(key).done(function (item, error) {
61
                if (typeof item !== 'undefined') {
62
                    kohadb.settings[key] = item.value;
63
                }
64
                if (typeof callback === 'function') {
65
                    callback(key, kohadb.settings[key]);
66
                }
67
            });
68
        });
69
    };
70
    kohadb.saveSetting = function (key, value) {
71
        $.indexedDB("koha").transaction(["offline_settings"]).then(function(){
72
        }, function(err, e){
73
        }, function(transaction){
74
            var settings = transaction.objectStore("offline_settings");
75
            settings.put({ "key" : key, "value" : value });
76
            kohadb.settings[key] = value;
77
        });
78
    };
79
    kohadb.recordTransaction = function (newtrans, callback) {
80
        $.indexedDB("koha").transaction(["transactions"]).then(function(){
81
            callback(newtrans);
82
        }, function(err, e){
83
        }, function(dbtransaction) {
84
            var transactions = dbtransaction.objectStore("transactions");
85
            transactions.put(newtrans);
86
            kohadb.saveSetting('dirty', true);
87
        });
88
    };
89
}( window.kohadb = window.kohadb || {}, jQuery ));
90
91
if ( !Date.prototype.toMySQLString ) {
92
  ( function() {
93
94
    function pad(number) {
95
      var r = String(number);
96
      if ( r.length === 1 ) {
97
        r = '0' + r;
98
      }
99
      return r;
100
    }
101
102
    Date.prototype.toMySQLString = function() {
103
      return this.getFullYear()
104
        + '-' + pad( this.getMonth() + 1 )
105
        + '-' + pad( this.getDate() )
106
        + ' ' + pad( this.getHours() )
107
        + ':' + pad( this.getMinutes() )
108
        + ':' + pad( this.getSeconds() )
109
        + '.' + String( (this.getMilliseconds()/1000).toFixed(3) ).slice( 2, 5 );
110
    };
111
112
  }() );
113
}
(-)a/offline_circ/download.pl (-109 lines)
Lines 1-108 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
8
# under the terms of the GNU General Public License as published by
9
# the Free Software Foundation; either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# Koha is distributed in the hope that it will be useful, but
13
# WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General Public License
18
# along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20
use Modern::Perl;
21
use CGI qw ( -utf8 );
22
use JSON qw( to_json );
23
use C4::Auth qw( checkauth );
24
use C4::Output;
25
use C4::Context;
26
use C4::Koha;
27
28
my $query = CGI->new;
29
checkauth( $query, undef, { circulate => "circulate_remaining_permissions" },
30
    "intranet" );
31
32
my $page     = $query->param('page') || 0;
33
my $startrec = int($page) * 5000;
34
my $req_data = $query->param('data') || '';
35
36
my $patrons_query = q{SELECT
37
    borrowers.borrowernumber, cardnumber, surname, firstname, title,
38
    othernames, initials, streetnumber, streettype, address, address2, city,
39
    state, zipcode, country, email, phone, mobile, fax, dateofbirth, borrowers.branchcode,
40
    categorycode, dateenrolled, dateexpiry, COALESCE(gonenoaddress, 0) AS gonenoaddress,
41
    COALESCE(lost, 0) AS lost, debarred,
42
    debarredcomment, SUM(accountlines.amountoutstanding) AS fine
43
    FROM borrowers
44
    LEFT JOIN accountlines ON borrowers.borrowernumber=accountlines.borrowernumber
45
    WHERE cardnumber IS NOT NULL
46
    GROUP BY borrowers.borrowernumber
47
    LIMIT ?, 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 = q{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
    WHERE barcode IS NOT NULL
60
    LIMIT ?, 5000;
61
    };
62
63
my $issues_query = q{SELECT
64
    biblio.title AS title,
65
    items.barcode AS barcode,
66
    items.itemcallnumber AS callnumber,
67
    issues.date_due AS date_due,
68
    issues.issuedate AS issuedate,
69
    issues.renewals_count AS renewals,
70
    issues.unseen_renewals AS unseen_renewals,
71
    borrowers.cardnumber AS cardnumber,
72
    CONCAT(borrowers.surname, ', ', borrowers.firstname) AS borrower_name
73
    FROM issues
74
    JOIN items ON items.itemnumber = issues.itemnumber
75
    JOIN biblio ON biblio.biblionumber = items.biblionumber
76
    JOIN borrowers ON borrowers.borrowernumber = issues.borrowernumber
77
    WHERE barcode IS NOT NULL
78
    LIMIT ?, 5000;
79
    };
80
81
my %results;
82
my $finished = 1;
83
if ( $req_data eq 'patrons' || $req_data eq 'all' ) {
84
    $results{'patrons'} = get_data( $patrons_query, 'cardnumber', $startrec );
85
}
86
if ( $req_data eq 'items' || $req_data eq 'all' ) {
87
    $results{'items'} = get_data( $items_query, 'barcode', $startrec );
88
}
89
if ( $req_data eq 'issues' || $req_data eq 'all' ) {
90
    $results{'issues'} = get_data( $issues_query, 'barcode', $startrec );
91
}
92
93
foreach my $key ( keys %results ) {
94
    $finished = 0 if keys %{ $results{$key} } == 5000;
95
}
96
$results{'finished'} = $finished;
97
98
print $query->header( -type => 'application/json', -charset => 'utf-8' );
99
print to_json( \%results );
100
101
sub get_data {
102
    my ( $sql, $key, $start ) = @_;
103
    $start ||= 0;
104
    my $dbh = C4::Context->dbh;
105
    my $sth = $dbh->prepare($sql);
106
    $sth->execute($start);
107
    return $sth->fetchall_hashref($key);
108
}
109
- 

Return to bug 33961