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