Bug 21395: Make perlcritic happy
[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
34 use Koha::Script -cron;
35 use Koha::DateUtils qw/ dt_from_string /;
36 use C4::Context;
37 use C4::Biblio;
38 use C4::Log;
39 use DateTime;
40 use DateTime::Format::MySQL;
41 use Time::HiRes qw/time/;
42 use POSIX qw/strftime ceil/;
43
44 sub usage {
45     pod2usage( -verbose => 2 );
46     exit;
47 }
48
49 $| = 1;
50
51 # command-line parameters
52 my $verbose   = 0;
53 my $test_only = 0;
54 my $want_help = 0;
55 my $since;
56 my $interval;
57 my $usestats    = 0;
58 my $useitems    = 0;
59 my $incremental = 0;
60 my $commit      = 100;
61 my $unit;
62
63 my $result = GetOptions(
64     'v|verbose'    => \$verbose,
65     't|test'       => \$test_only,
66     's|since=s'    => \$since,
67     'i|interval=s' => \$interval,
68     'use-stats'    => \$usestats,
69     'use-items'    => \$useitems,
70     'incremental'  => \$incremental,
71     'c|commit=i'   => \$commit,
72     'h|help'       => \$want_help
73 );
74
75 binmode( STDOUT, ":encoding(UTF-8)" );
76
77 if ( defined $since && defined $interval ) {
78     print "The --since and --interval options are mutually exclusive.\n\n";
79     $want_help = 1;
80 }
81
82 if ( $useitems && $incremental ) {
83     print
84       "The --use-items and --incremental options are mutually exclusive.\n\n";
85     $want_help = 1;
86 }
87
88 if ( $incremental && !( defined $since || defined $interval ) ) {
89     $interval = '24h';
90 }
91
92 unless ( $usestats || $useitems ) {
93     print "You must specify either --use-stats and/or --use-items.\n\n";
94     $want_help = 1;
95 }
96
97 if ( not $result or $want_help ) {
98     usage();
99 }
100
101 cronlogaction();
102
103 my $dbh = C4::Context->dbh;
104 $dbh->{AutoCommit} = 0;
105
106 my $num_bibs_processed = 0;
107 my $num_bibs_error = 0;
108
109 my $starttime = time();
110
111 process_items() if $useitems;
112 process_stats() if $usestats;
113
114 report();
115
116 exit 0;
117
118 sub process_items {
119     my $query =
120 "SELECT items.biblionumber, SUM(items.issues) FROM items GROUP BY items.biblionumber;";
121     process_query($query);
122 }
123
124 sub process_stats {
125     if ($interval) {
126         my $dt = dt_from_string();
127
128         my %units = (
129             h => 'hours',
130             d => 'days',
131             w => 'weeks',
132             m => 'months',
133             y => 'years'
134         );
135
136         $interval =~ m/([0-9]*)([hdwmy]?)$/;
137         $unit = $2 || 'd';
138         $since = DateTime::Format::MySQL->format_datetime(
139             $dt->subtract( $units{$unit} => $1 ) );
140     }
141     my $limit = '';
142     $limit = " WHERE statistics.datetime >= ?" if ( $interval || $since );
143
144     my $query =
145 "SELECT biblio.biblionumber, COUNT(statistics.itemnumber) FROM biblio\
146  LEFT JOIN items ON (biblio.biblionumber=items.biblionumber)\
147  LEFT JOIN statistics ON (items.itemnumber=statistics.itemnumber AND statistics.type = 'issue')
148  $limit\
149  GROUP BY biblio.biblionumber";
150     process_query( $query, $limit );
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