3 # 2008 Kyle Hall <kyle.m.hall@gmail.com>
5 # This file is part of Koha.
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 2 of the License, or (at your option) any later
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.
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
23 create_koc_db.pl - create a database file for the offline circulation tool
30 -help brief help message
31 -man full documentation
39 Print a brief help message and exits.
43 Prints the manual page and exits.
47 The filename that we should use for the database file that we produce. Defaults to "borrowers.db"
51 Forcefully overwrite any existing db file. Defaults to false, so
52 program will terminate prematurely if the file already exists.
56 This option cause the output file to be an SQLite2 database.
57 This is the format that the original offline circ client
58 ( the one written in PHP/Gtk, version < 1.0 ) requires.
62 This option cause the output file to be an SQLite3 database.
63 This is the format that the new offline circ client
64 ( the one written in C++/Qt4, version >= 1.0 ) requires.
70 This script generates a sqlite database file full of patron and
71 holdings data that can be used by an offline circulation tool.
75 This program could be run from cron to occasionally refresh the
76 offline circulation database. For instance:
78 C<0 0 * * * create_koc_db.pl>
82 This program was originally created to interact with Kyle Hall's
83 offline circulation tool, which is available from
84 L<http://kylehall.info/index.php/projects/koha-tools/koha-offline-circulation/>,
85 but any similar tool could use the database that is produced.
98 use English
qw(-no_match_vars);
103 my $filename = 'borrowers.db';
109 'verbose' => \
$verbose,
112 'file=s' => \
$filename,
114 'sqlite2' => \
$sqlite2,
115 'sqlite3' => \
$sqlite3,
118 pod2usage
(1) if $help;
119 pod2usage
( -verbose
=> 2 ) if $man;
122 my %wanted_borrowers_columns = map { $_ => 1 } qw
/borrowernumber cardnumber surname firstname address city phone dateofbirth/;
123 my %wanted_issues_columns = map { $_ => 1 } qw
/borrowernumber date_due itemcallnumber title itemtype/;
125 prepare_file_for_writing
($filename)
126 or die "file: '$filename' already exists. Use --force to overwrite\n";
130 ## Create DB Connections
131 my $dbh_mysql = C4
::Context
->dbh;
134 $dbh_sqlite = DBI
->connect( "dbi:SQLite2:dbname=$filename", "", "" );
135 } elsif ( $sqlite3 ) {
136 $dbh_sqlite = DBI
->connect( "dbi:SQLite:dbname=$filename", "", "" );
138 $dbh_sqlite->{AutoCommit
} = 0;
140 create_borrowers_table
();
141 populate_borrowers_table
();
143 create_issues_table
();
144 populate_issues_table
();
146 =head1 INTERNAL METHODS
148 =head2 verify_dbd_sqlite
150 Since DBD::SQLite is a new prerequisite and an optional one, let's
151 make sure we have a new enough version of it.
155 sub verify_dbd_sqlite
{
158 eval { require DBD
::SQLite2
; };
160 my $msg = <<'END_MESSAGE';
161 DBD::SQLite2 is required to generate offline circultion database files, but not found.
162 Please install the DBD::SQLite2 perl module. It is available from
163 http://search.cpan.org/dist/DBD-SQLite2/ or through the CPAN module.
167 } elsif ( $sqlite3 ) {
168 eval { require DBD::SQLite; };
170 my $msg = <<'END_MESSAGE
';
171 DBD::SQLite3 is required to generate offline circultion database files, but not found.
172 Please install the DBD::SQLite3 perl module. It is available from
173 http://search.cpan.org/dist/DBD-SQLite3/ or through the CPAN module.
178 die( "Error: execution requires either the option --sqlite2 or --sqlite3. Run with --help for details." );
182 =head2 prepare_file_for_writing
184 pass in the filename that we're considering using
for the SQLite db
.
186 returns true
if we can
use it
.
188 returns false
if we can
't. For example, if it alredy exists and we
189 don't have
--force
or don
't have permissions to unlink it.
193 sub prepare_file_for_writing {
194 my $filename = shift;
195 if ( -e $filename ) {
197 # this file exists. remove it if --force.
199 return unlink $filename;
207 =head2 create_borrowers_table
209 Create sqlite borrowers table to mirror the koha borrowers table structure
213 sub create_borrowers_table {
215 my %borrowers_info = get_columns_and_types_of_table( 'borrowers
' );
216 my $sqlite_create_sql = "CREATE TABLE borrowers ( \n";
218 $sqlite_create_sql .= join(',', map{ $_ . ' ' . $borrowers_info{$_} }
219 grep { exists($wanted_borrowers_columns{$_}) } keys %borrowers_info);
221 $sqlite_create_sql .= " , \n total_fines decimal(28,6) "; ## Extra field to store the total fines for a borrower in.
222 $sqlite_create_sql .= " ) ";
224 my $return = $dbh_sqlite->do($sqlite_create_sql);
226 warn 'unable to create borrowers table
: ' . $dbh_sqlite->errstr();
232 =head2 populate_borrowers_table
234 Import the data from the koha.borrowers table into our sqlite table
238 sub populate_borrowers_table {
240 my @borrower_fields = grep { exists($wanted_borrowers_columns{$_}) } get_columns_of_table( 'borrowers
' );
241 push @borrower_fields, 'total_fines
';
243 my $sql = "INSERT INTO borrowers ( ";
244 $sql .= join( ',', @borrower_fields );
245 $sql .= " ) VALUES ( ";
246 $sql .= join( ',', map { '?
' } @borrower_fields );
248 my $sth_sqlite = $dbh_sqlite->prepare($sql);
250 my $sth_mysql = $dbh_mysql->prepare(<<'END_SQL');
251 SELECT borrowernumber
,
259 sum
( accountlines
.amountoutstanding
) as total_fines
261 LEFT JOIN accountlines USING
(borrowernumber
)
262 GROUP BY borrowernumber
;
265 my $fields_count = $sth_mysql->execute();
266 warn "preparing to insert $fields_count borrowers\n" if $verbose;
269 while ( my $borrower = $sth_mysql->fetchrow_hashref ) {
272 print '.' unless ( $count % 10 );
273 print "$count\n" unless ( $count % 1000 );
275 $sth_sqlite->execute( @
$borrower{ @borrower_fields } );
276 $sth_sqlite->finish();
277 $dbh_sqlite->commit() if ( 0 == $count % 1000 );
279 $dbh_sqlite->commit();
280 print "inserted $count borrowers\n" if $verbose;
281 # add_fines_to_borrowers_table();
284 =head2 add_fines_to_borrowers_table
286 Import the fines from koha.accountlines into the sqlite db
290 sub add_fines_to_borrowers_table
{
292 print "preparing to update borrowers\n" if $verbose;
293 my $sth_mysql = $dbh_mysql->prepare(
294 "SELECT DISTINCT borrowernumber, SUM( amountoutstanding ) AS total_fines
296 GROUP BY borrowernumber"
298 $sth_mysql->execute();
300 while ( my $result = $sth_mysql->fetchrow_hashref() ) {
303 print '.' unless ( $count % 10 );
304 print "$count\n" unless ( $count % 1000 );
307 my $borrowernumber = $result->{'borrowernumber'};
308 my $total_fines = $result->{'total_fines'};
310 # warn "Fines for Borrower # $borrowernumber are \$ $total_fines \n" if $verbose;
311 my $sql = "UPDATE borrowers SET total_fines = ? WHERE borrowernumber = ?";
313 my $sth_sqlite = $dbh_sqlite->prepare($sql);
314 $sth_sqlite->execute( $total_fines, $borrowernumber );
315 $sth_sqlite->finish();
317 print "updated $count borrowers\n" if ( $verbose && $count );
320 =head2 create_issue_table
322 Create sqlite issues table with minimal information needed from koha tables issues, items, biblio, biblioitems
326 sub create_issues_table
{
328 my $fields = get_columns_for_issues_table
();
330 my $sqlite_create_sql = "CREATE TABLE issues ( \n";
333 foreach my $key ( keys %$fields ) {
335 my $type = $fields->{$key};
338 $sqlite_create_sql .= " $field $type ";
341 $sqlite_create_sql .= ", \n $field $type ";
344 $sqlite_create_sql .= " ) ";
346 my $sth_sqlite = $dbh_sqlite->prepare($sqlite_create_sql);
347 $sth_sqlite->execute();
348 $sth_sqlite->finish();
352 =head2 populate_issues_table
354 Import open issues from the koha database
358 sub populate_issues_table
{
360 print "preparing to populate ISSUES table\n" if $verbose;
361 my $sth_mysql = $dbh_mysql->prepare(
362 "SELECT issues.borrowernumber,
364 items.itemcallnumber,
367 FROM issues, items, biblioitems, biblio
368 WHERE issues.itemnumber = items.itemnumber
369 AND items.biblionumber = biblioitems.biblionumber
370 AND items.biblionumber = biblio.biblionumber
371 AND returndate IS NULL"
373 $sth_mysql->execute();
375 my $column_names = $sth_mysql->{'NAME'};
376 my $sql_sqlite = "INSERT INTO issues ( ";
377 $sql_sqlite .= join( ',', @
$column_names );
378 $sql_sqlite .= " ) VALUES ( ";
379 $sql_sqlite .= join( ',', map { '?' } @
$column_names );
380 $sql_sqlite .= " ) ";
382 warn "$sql_sqlite\n" if $verbose;
383 my $sth_sqlite = $dbh_sqlite->prepare($sql_sqlite);
386 while ( my $result = $sth_mysql->fetchrow_hashref ) {
390 print '.' unless ( $count % 10 );
391 print "$count\n" unless ( $count % 1000 );
394 $sth_sqlite->execute( @
$result{ @
$column_names } );
395 $sth_sqlite->finish();
396 $dbh_sqlite->commit() if ( 0 == $count % 1000 );
398 $dbh_sqlite->commit();
399 print "inserted $count issues\n" if ( $verbose && $count );
402 =head2 get_columns_of_table
404 pass in the name of a database table.
406 returns list of column names in that table.
410 sub get_columns_of_table
{
411 my $table_name = shift;
413 my %column_info = get_columns_and_types_of_table
( $table_name );
414 my @columns = keys %column_info;
419 =head2 get_columns_and_types_of_table
421 pass in the name of a database table
423 returns a hash of column names to their types.
427 sub get_columns_and_types_of_table
{
428 my $table_name = shift;
430 my $column_info = $dbh_mysql->selectall_arrayref( "SHOW COLUMNS FROM $table_name" );
431 my %columns = map{ $_->[0] => $_->[1] } @
$column_info;
436 =head2 get_columns_for_issues_table
438 This sub returns a hashref where the keys are all the fields in the given tables, and the data is the field's type
442 sub get_columns_for_issues_table
{
444 my @tables = ( 'issues', 'items', 'biblioitems', 'biblio' );
448 foreach my $table ( @tables ) {
449 my %table_info = get_columns_and_types_of_table
( $table );
450 %fields = ( %fields, %table_info );
452 return { map { $_ => $fields{$_} } grep { exists($wanted_issues_columns{$_}) } keys %fields };