View | Details | Raw Unified | Return to bug 6557
Collapse All | Expand All

(-)a/C4/Biblio.pm (+48 lines)
Lines 104-109 BEGIN { Link Here
104
      &ModBiblio
104
      &ModBiblio
105
      &ModBiblioframework
105
      &ModBiblioframework
106
      &ModZebra
106
      &ModZebra
107
      &UpdateTotalIssues
107
    );
108
    );
108
109
109
    # To delete something
110
    # To delete something
Lines 3825-3830 sub prepare_host_field { Link Here
3825
    return;
3826
    return;
3826
}
3827
}
3827
3828
3829
3830
=head2 UpdateTotalIssues
3831
3832
  UpdateTotalIssues($biblionumber, $increase, [$value])
3833
3834
Update the total issue count for a particular bib record.
3835
3836
=over 4
3837
3838
=item C<$biblionumber> is the biblionumber of the bib to update
3839
3840
=item C<$increase> is the amount to increase (or decrease) the total issues count by
3841
3842
=item C<$value> is the absolute value that total issues count should be set to. If provided, C<$increase> is ignored.
3843
3844
=back
3845
3846
=cut
3847
3848
sub UpdateTotalIssues {
3849
    my ($biblionumber, $increase, $value) = @_;
3850
    my $totalissues;
3851
3852
    my $data = GetBiblioData($biblionumber);
3853
3854
    if (defined $value) {
3855
        $totalissues = $value;
3856
    } else {
3857
        $totalissues = $data->{'totalissues'} + $increase;
3858
    }
3859
     my ($totalissuestag, $totalissuessubfield) = GetMarcFromKohaField('biblioitems.totalissues', $data->{'frameworkcode'});
3860
3861
     my $record = GetMarcBiblio($biblionumber);
3862
3863
     my $field = $record->field($totalissuestag);
3864
     if (defined $field) {
3865
         $field->update( $totalissuessubfield => $totalissues );
3866
     } else {
3867
         $field = MARC::Field->new($totalissuestag, '0', '0',
3868
                 $totalissuessubfield => $totalissues);
3869
         $record->insert_grouped_field($field);
3870
     }
3871
3872
     ModBiblio($record, $biblionumber, $data->{'frameworkcode'});
3873
     return;
3874
}
3875
3828
1;
3876
1;
3829
3877
3830
3878
(-)a/C4/Circulation.pm (+3 lines)
Lines 1059-1064 sub AddIssue { Link Here
1059
          CartToShelf( $item->{'itemnumber'} );
1059
          CartToShelf( $item->{'itemnumber'} );
1060
        }
1060
        }
1061
        $item->{'issues'}++;
1061
        $item->{'issues'}++;
1062
        if ( C4::Context->preference('UpdateTotalIssuesOnCirc') ) {
1063
            UpdateTotalIssues($item->{'biblionumber'}, 1);
1064
        }
1062
1065
1063
        ## If item was lost, it has now been found, reverse any list item charges if neccessary.
1066
        ## If item was lost, it has now been found, reverse any list item charges if neccessary.
1064
        if ( $item->{'itemlost'} ) {
1067
        if ( $item->{'itemlost'} ) {
(-)a/installer/data/mysql/sysprefs.sql (+1 lines)
Lines 365-367 INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES ( Link Here
365
INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES('OpacBrowseResults','1','Disable/enable browsing and paging search results from the OPAC detail page.',NULL,'YesNo');
365
INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES('OpacBrowseResults','1','Disable/enable browsing and paging search results from the OPAC detail page.',NULL,'YesNo');
366
INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES('SvcMaxReportRows','10','Maximum number of rows to return via the report web service.',NULL,'Integer');
366
INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES('SvcMaxReportRows','10','Maximum number of rows to return via the report web service.',NULL,'Integer');
367
INSERT INTO systempreferences (variable,value,options,explanation,type) VALUES ('ReservesControlBranch','PatronLibrary','ItemHomeLibrary|PatronLibrary','Branch checked for members reservations rights','Choice');
367
INSERT INTO systempreferences (variable,value,options,explanation,type) VALUES ('ReservesControlBranch','PatronLibrary','ItemHomeLibrary|PatronLibrary','Branch checked for members reservations rights','Choice');
368
INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES('UpdateTotalIssuesOnCirc','0','Whether to update the totalissues field in the biblio on each circ.',NULL,'YesNo');
(-)a/installer/data/mysql/updatedatabase.pl (+7 lines)
Lines 5308-5313 if (C4::Context->preference("Version") < TransformToNum($DBversion)) { Link Here
5308
    SetVersion ($DBversion);
5308
    SetVersion ($DBversion);
5309
}
5309
}
5310
5310
5311
$DBversion = "3.09.00.XXX";
5312
if ( C4::Context->preference("Version") < TransformToNum($DBversion) ) {
5313
    $dbh->do("INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES('UpdateTotalIssuesOnCirc','0','Whether to update the totalissues field in the biblio on each circ.',NULL,'YesNo');");
5314
    print "Upgrade to $DBversion done (Add syspref UpdateTotalIssuesOnCirc)\n";
5315
    SetVersion($DBversion);
5316
}
5317
5311
=head1 FUNCTIONS
5318
=head1 FUNCTIONS
5312
5319
5313
=head2 TableExists($table)
5320
=head2 TableExists($table)
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref (+6 lines)
Lines 103-108 Circulation: Link Here
103
            - pref: NoticeCSS
103
            - pref: NoticeCSS
104
              class: url
104
              class: url
105
            - on Notices. (This should be a complete URL, starting with <code>http://</code>)
105
            - on Notices. (This should be a complete URL, starting with <code>http://</code>)
106
        -
107
            - pref: UpdateTotalIssuesOnCirc
108
              choices:
109
                  yes: Do
110
                  no: "Do not"
111
            - update a bibliographic record's total issues count whenever an item is issued (WARNING! This increases server load significantly; if performance is a concern, use the update_totalissues.pl cron job to update the total issues count).
106
    Checkout Policy:
112
    Checkout Policy:
107
        -
113
        -
108
            - pref: AllowNotForLoanOverride
114
            - pref: AllowNotForLoanOverride
(-)a/misc/cronjobs/crontab.example (+3 lines)
Lines 67-72 KOHA_CRON_PATH = /usr/share/koha/bin/cronjobs Link Here
67
# Cancel expired holds
67
# Cancel expired holds
68
0 1 * * *  $KOHA_CRON_PATH/holds/cancel_expired_holds.pl >/dev/null 2>&1
68
0 1 * * *  $KOHA_CRON_PATH/holds/cancel_expired_holds.pl >/dev/null 2>&1
69
69
70
# Update popularity counts for biblio records
71
0 2 * * *  $KOHA_CRON_PATH/update_totalissues.pl --commit=1000 --use-stats --incremental --interval=1d >/dev/null 2>&1
72
70
# ZEBRA INDEX UPDATES with -z option, incremental index updates throughout the day
73
# ZEBRA INDEX UPDATES with -z option, incremental index updates throughout the day
71
# for both authorities and bibs
74
# for both authorities and bibs
72
*/10 * * * *  $KOHA_CRON_PATH/../migration_tools/rebuild_zebra.pl -b -a -z >/dev/null
75
*/10 * * * *  $KOHA_CRON_PATH/../migration_tools/rebuild_zebra.pl -b -a -z >/dev/null
(-)a/misc/cronjobs/update_totalissues.pl (-1 / +297 lines)
Line 0 Link Here
0
- 
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 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
10
# version.
11
#
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.
15
#
16
# You should have received a copy of the GNU General Public License along
17
# with Koha; if not, write to the Free Software Foundation, Inc.,
18
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
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
102
my $starttime = time();
103
104
process_items() if $useitems;
105
process_stats() if $usestats;
106
107
report();
108
109
exit 0;
110
111
sub process_items {
112
    my $query =
113
"SELECT items.biblionumber, SUM(items.issues) FROM items GROUP BY items.biblionumber;";
114
    process_query($query);
115
}
116
117
sub process_stats {
118
    if ($interval) {
119
        my $dt = DateTime->now;
120
121
        my %units = (
122
            h => 'hours',
123
            d => 'days',
124
            w => 'weeks',
125
            m => 'months',
126
            y => 'years'
127
        );
128
129
        $interval =~ m/([0-9]*)([hdwmy]?)$/;
130
        $unit = $2 || 'd';
131
        $since = DateTime::Format::MySQL->format_datetime(
132
            $dt->subtract( $units{$unit} => $1 ) );
133
    }
134
    my $limit = '';
135
    $limit = " AND statistics.datetime >= ?" if ( $interval || $since );
136
137
    my $query =
138
"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;";
139
    process_query( $query, $limit );
140
141
    unless ($incremental) {
142
        $query =
143
"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;";
144
        process_query( $query, '' );
145
146
        $query =
147
"SELECT biblio.biblionumber, 0 FROM biblio LEFT JOIN items ON (biblio.biblionumber=items.biblionumber) WHERE items.itemnumber IS NULL GROUP BY biblio.biblionumber;";
148
        process_query( $query, '' );
149
    }
150
151
    $dbh->commit();
152
}
153
154
sub process_query {
155
    my $query    = shift;
156
    my $uselimit = shift;
157
    my $sth      = $dbh->prepare($query);
158
159
    if ( $since && $uselimit ) {
160
        $sth->execute($since);
161
    }
162
    else {
163
        $sth->execute();
164
    }
165
166
    while ( my ( $biblionumber, $totalissues ) = $sth->fetchrow_array() ) {
167
        $num_bibs_processed++;
168
        $totalissues = 0 unless $totalissues;
169
        print "Processing bib $biblionumber ($totalissues issues)\n"
170
          if $verbose;
171
        if ( not $test_only ) {
172
            if ( $incremental && $totalissues > 0 ) {
173
                UpdateTotalIssues( $biblionumber, $totalissues );
174
            }
175
            else {
176
                UpdateTotalIssues( $biblionumber, 0, $totalissues );
177
            }
178
        }
179
        if ( not $test_only and ( $num_bibs_processed % $commit ) == 0 ) {
180
            print_progress_and_commit($num_bibs_processed);
181
        }
182
    }
183
184
    $dbh->commit();
185
}
186
187
sub report {
188
    my $endtime = time();
189
    my $totaltime = ceil( ( $endtime - $starttime ) * 1000 );
190
    $starttime = strftime( '%D %T', localtime($starttime) );
191
    $endtime   = strftime( '%D %T', localtime($endtime) );
192
193
    my $summary = <<_SUMMARY_;
194
195
Update total issues count script report
196
=======================================================
197
Run started at:                         $starttime
198
Run ended at:                           $endtime
199
Total run time:                         $totaltime ms
200
Number of bibs modified:                $num_bibs_processed
201
_SUMMARY_
202
    $summary .= "\n****  Ran in test mode only  ****\n" if $test_only;
203
    print $summary;
204
}
205
206
sub print_progress_and_commit {
207
    my $recs = shift;
208
    $dbh->commit();
209
    print "... processed $recs records\n";
210
}
211
212
=head1 NAME
213
214
update_totalissues.pl
215
216
=head1 SYNOPSIS
217
218
  update_totalissues.pl --use-stats
219
  update_totalissues.pl --use-items
220
  update_totalissues.pl --commit=1000
221
  update_totalissues.pl --since='2012-01-01'
222
  update_totalissues.pl --interval=30d
223
224
=head1 DESCRIPTION
225
226
This batch job populates bibliographic records' total issues count based
227
on historical issue statistics.
228
229
=over 8
230
231
=item B<--help>
232
233
Prints this help
234
235
=item B<-v|--verbose>
236
237
Provide verbose log information (list every bib modified).
238
239
=item B<--use-stats>
240
241
Use the data in the statistics table for populating total issues.
242
243
=item B<--use-items>
244
245
Use items.issues data for populating total issues. Note that issues
246
data from the items table does not respect the --since or --interval
247
options, by definition. Also note that if both --use-stats and
248
--use-items are specified, the count of biblios processed will be
249
misleading.
250
251
=item B<-s|--since=DATE>
252
253
Only process issues recorded in the statistics table since DATE.
254
255
=item B<-i|--interval=S>
256
257
Only process issues recorded in the statistics table in the last N
258
units of time. The interval should consist of a number with a one-letter
259
unit suffix. The valid suffixes are h (hours), d (days), w (weeks),
260
m (months), and y (years). The default unit is days.
261
262
=item B<--incremental>
263
264
Add the number of issues found in the statistics table to the existing
265
total issues count. Intended so that this script can be used as a cron
266
job to update popularity information during low-usage periods. If neither
267
--since or --interval are specified, incremental mode will default to
268
processing the last twenty-four hours.
269
270
=item B<--commit=N>
271
272
Commit the results to the database after every N records are processed.
273
274
=item B<--test>
275
276
Only test the popularity population script.
277
278
=back
279
280
=head1 WARNING
281
282
If the time on your database server does not match the time on your Koha
283
server you will need to take that into account, and probably use the
284
--since argument instead of the --interval argument for incremental
285
updating.
286
287
=head1 CREDITS
288
289
This patch to Koha was sponsored by the Arcadia Public Library and the
290
Arcadia Public Library Foundation in honor of Jackie Faust-Moreno, late
291
director of the Arcadia Public Library.
292
293
=head1 AUTHOR
294
295
Jared Camins-Esakov <jcamins AT cpbibliography DOT com>
296
297
=cut

Return to bug 6557