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