Bug 9978: Replace license header with the correct license (GPLv3+)
[koha.git] / misc / cronjobs / update_totalissues.pl
1 #!/usr/bin/perl
2
3 # Copyright 2012 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 strict;
21 use warnings;
22
23 BEGIN {
24
25     # find Koha's Perl modules
26     # test carefully before changing this
27     use FindBin;
28     eval { require "$FindBin::Bin/../kohalib.pl" };
29 }
30
31 use Getopt::Long;
32 use Pod::Usage;
33 use C4::Context;
34 use C4::Biblio;
35 use DateTime;
36 use DateTime::Format::MySQL;
37 use Time::HiRes qw/time/;
38 use POSIX qw/strftime ceil/;
39
40 sub usage {
41     pod2usage( -verbose => 2 );
42     exit;
43 }
44
45 $| = 1;
46
47 # command-line parameters
48 my $verbose   = 0;
49 my $test_only = 0;
50 my $want_help = 0;
51 my $since;
52 my $interval;
53 my $usestats    = 0;
54 my $useitems    = 0;
55 my $incremental = 0;
56 my $commit      = 100;
57 my $unit;
58
59 my $result = GetOptions(
60     'v|verbose'    => \$verbose,
61     't|test'       => \$test_only,
62     's|since=s'    => \$since,
63     'i|interval=s' => \$interval,
64     'use-stats'    => \$usestats,
65     'use-items'    => \$useitems,
66     'incremental'  => \$incremental,
67     'c|commit=i'   => \$commit,
68     'h|help'       => \$want_help
69 );
70
71 binmode( STDOUT, ":utf8" );
72
73 if ( defined $since && defined $interval ) {
74     print "The --since and --interval options are mutually exclusive.\n\n";
75     $want_help = 1;
76 }
77
78 if ( $useitems && $incremental ) {
79     print
80       "The --use-items and --incremental options are mutually exclusive.\n\n";
81     $want_help = 1;
82 }
83
84 if ( $incremental && !( defined $since || defined $interval ) ) {
85     $interval = '24h';
86 }
87
88 unless ( $usestats || $useitems ) {
89     print "You must specify either --use-stats and/or --use-items.\n\n";
90     $want_help = 1;
91 }
92
93 if ( not $result or $want_help ) {
94     usage();
95 }
96
97 my $dbh = C4::Context->dbh;
98 $dbh->{AutoCommit} = 0;
99
100 my $num_bibs_processed = 0;
101 my $num_bibs_error = 0;
102
103 my $starttime = time();
104
105 process_items() if $useitems;
106 process_stats() if $usestats;
107
108 report();
109
110 exit 0;
111
112 sub process_items {
113     my $query =
114 "SELECT items.biblionumber, SUM(items.issues) FROM items GROUP BY items.biblionumber;";
115     process_query($query);
116 }
117
118 sub process_stats {
119     if ($interval) {
120         my $dt = DateTime->now;
121
122         my %units = (
123             h => 'hours',
124             d => 'days',
125             w => 'weeks',
126             m => 'months',
127             y => 'years'
128         );
129
130         $interval =~ m/([0-9]*)([hdwmy]?)$/;
131         $unit = $2 || 'd';
132         $since = DateTime::Format::MySQL->format_datetime(
133             $dt->subtract( $units{$unit} => $1 ) );
134     }
135     my $limit = '';
136     $limit = " AND statistics.datetime >= ?" if ( $interval || $since );
137
138     my $query =
139 "SELECT biblio.biblionumber, COUNT(statistics.itemnumber) FROM biblio LEFT JOIN items ON (biblio.biblionumber=items.biblionumber) LEFT JOIN statistics ON (items.itemnumber=statistics.itemnumber) WHERE statistics.type = 'issue' $limit GROUP BY biblio.biblionumber;";
140     process_query( $query, $limit );
141
142     unless ($incremental) {
143         $query =
144 "SELECT biblio.biblionumber, 0 FROM biblio LEFT JOIN items ON (biblio.biblionumber=items.biblionumber) LEFT JOIN statistics ON (items.itemnumber=statistics.itemnumber) WHERE statistics.itemnumber IS NULL GROUP BY biblio.biblionumber;";
145         process_query( $query, '' );
146
147         $query =
148 "SELECT biblio.biblionumber, 0 FROM biblio LEFT JOIN items ON (biblio.biblionumber=items.biblionumber) WHERE items.itemnumber IS NULL GROUP BY biblio.biblionumber;";
149         process_query( $query, '' );
150     }
151
152     $dbh->commit();
153 }
154
155 sub process_query {
156     my $query    = shift;
157     my $uselimit = shift;
158     my $sth      = $dbh->prepare($query);
159
160     if ( $since && $uselimit ) {
161         $sth->execute($since);
162     }
163     else {
164         $sth->execute();
165     }
166
167     while ( my ( $biblionumber, $totalissues ) = $sth->fetchrow_array() ) {
168         $num_bibs_processed++;
169         $totalissues = 0 unless $totalissues;
170         print "Processing bib $biblionumber ($totalissues issues)\n"
171           if $verbose;
172         if ( not $test_only ) {
173             my $ret;
174             if ( $incremental && $totalissues > 0 ) {
175                 $ret = UpdateTotalIssues( $biblionumber, $totalissues );
176             }
177             else {
178                 $ret = UpdateTotalIssues( $biblionumber, 0, $totalissues );
179             }
180             unless ($ret) {
181                 print "Error while processing bib $biblionumber\n" if $verbose;
182                 $num_bibs_error++;
183             }
184         }
185         if ( not $test_only and ( $num_bibs_processed % $commit ) == 0 ) {
186             print_progress_and_commit($num_bibs_processed);
187         }
188     }
189
190     $dbh->commit();
191 }
192
193 sub report {
194     my $endtime = time();
195     my $totaltime = ceil( ( $endtime - $starttime ) * 1000 );
196     $starttime = strftime( '%D %T', localtime($starttime) );
197     $endtime   = strftime( '%D %T', localtime($endtime) );
198
199     my $summary = <<_SUMMARY_;
200
201 Update total issues count script report
202 =======================================================
203 Run started at:                         $starttime
204 Run ended at:                           $endtime
205 Total run time:                         $totaltime ms
206 Number of bibs modified:                $num_bibs_processed
207 Number of bibs with error:              $num_bibs_error
208 _SUMMARY_
209     $summary .= "\n****  Ran in test mode only  ****\n" if $test_only;
210     print $summary;
211 }
212
213 sub print_progress_and_commit {
214     my $recs = shift;
215     $dbh->commit();
216     print "... processed $recs records\n";
217 }
218
219 =head1 NAME
220
221 update_totalissues.pl
222
223 =head1 SYNOPSIS
224
225   update_totalissues.pl --use-stats
226   update_totalissues.pl --use-items
227   update_totalissues.pl --commit=1000
228   update_totalissues.pl --since='2012-01-01'
229   update_totalissues.pl --interval=30d
230
231 =head1 DESCRIPTION
232
233 This batch job populates bibliographic records' total issues count based
234 on historical issue statistics.
235
236 =over 8
237
238 =item B<--help>
239
240 Prints this help
241
242 =item B<-v|--verbose>
243
244 Provide verbose log information (list every bib modified).
245
246 =item B<--use-stats>
247
248 Use the data in the statistics table for populating total issues.
249
250 =item B<--use-items>
251
252 Use items.issues data for populating total issues. Note that issues
253 data from the items table does not respect the --since or --interval
254 options, by definition. Also note that if both --use-stats and
255 --use-items are specified, the count of biblios processed will be
256 misleading.
257
258 =item B<-s|--since=DATE>
259
260 Only process issues recorded in the statistics table since DATE.
261
262 =item B<-i|--interval=S>
263
264 Only process issues recorded in the statistics table in the last N
265 units of time. The interval should consist of a number with a one-letter
266 unit suffix. The valid suffixes are h (hours), d (days), w (weeks),
267 m (months), and y (years). The default unit is days.
268
269 =item B<--incremental>
270
271 Add the number of issues found in the statistics table to the existing
272 total issues count. Intended so that this script can be used as a cron
273 job to update popularity information during low-usage periods. If neither
274 --since or --interval are specified, incremental mode will default to
275 processing the last twenty-four hours.
276
277 =item B<--commit=N>
278
279 Commit the results to the database after every N records are processed.
280
281 =item B<--test>
282
283 Only test the popularity population script.
284
285 =back
286
287 =head1 WARNING
288
289 If the time on your database server does not match the time on your Koha
290 server you will need to take that into account, and probably use the
291 --since argument instead of the --interval argument for incremental
292 updating.
293
294 =head1 CREDITS
295
296 This patch to Koha was sponsored by the Arcadia Public Library and the
297 Arcadia Public Library Foundation in honor of Jackie Faust-Moreno, late
298 director of the Arcadia Public Library.
299
300 =head1 AUTHOR
301
302 Jared Camins-Esakov <jcamins AT cpbibliography DOT com>
303
304 =cut