Bug 15818 - OPAC search with utf-8 characters and without results generates encoding...
[koha.git] / misc / cronjobs / longoverdue.pl
blobb73d31f47e02cbf959ffc3ac0b396359096dfd72
1 #!/usr/bin/perl
2 #-----------------------------------
3 # Copyright 2008 LibLime
5 # This file is part of Koha.
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.
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.
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 #-----------------------------------
21 =head1 NAME
23 longoverdue.pl cron script to set lost statuses on overdue materials.
24 Execute without options for help.
26 =cut
28 use strict;
29 use warnings;
30 BEGIN {
31 # find Koha's Perl modules
32 # test carefully before changing this
33 use FindBin;
34 eval { require "$FindBin::Bin/../kohalib.pl" };
36 use C4::Context;
37 use C4::Items;
38 use C4::Circulation qw/LostItem/;
39 use Getopt::Long;
40 use C4::Log;
41 use Pod::Usage;
42 use Koha::Borrowers;
44 my $lost; # key=lost value, value=num days.
45 my ($charge, $verbose, $confirm, $quiet);
46 my $endrange = 366;
47 my $mark_returned = 0;
48 my $borrower_category = [];
49 my $skip_borrower_category = [];
50 my $help=0;
51 my $man=0;
52 my $list_categories = 0;
54 GetOptions(
55 'lost=s%' => \$lost,
56 'c|charge=s' => \$charge,
57 'confirm' => \$confirm,
58 'v|verbose' => \$verbose,
59 'quiet' => \$quiet,
60 'maxdays=s' => \$endrange,
61 'mark-returned' => \$mark_returned,
62 'h|help' => \$help,
63 'man|manual' => \$man,
64 'category=s' => $borrower_category,
65 'skip-category=s' => $skip_borrower_category,
66 'list-categories' => \$list_categories,
69 if ( $man ) {
70 pod2usage( -verbose => 2
71 -exitval => 0
75 if ( $help ) {
76 pod2usage( -verbose => 1,
77 -exitval => 0
81 if ( scalar @$borrower_category && scalar @$skip_borrower_category) {
82 pod2usage( -verbose => 1,
83 -message => "The options --category and --skip-category are mually exclusive.\n"
84 . "Use one or the other.",
85 -exitval => 1
89 if ( $list_categories ) {
90 my @categories = sort map { uc $_->[0] } @{ C4::Context->dbh->selectall_arrayref(q|SELECT categorycode FROM categories|) };
91 print "\nBorrowrer Categories: " . join( " ", @categories ) . "\n\n";
92 exit 0;
95 =head1 SYNOPSIS
97 longoverdue.pl [ --help | -h | --man | --list-categories ]
98 longoverdue.pl --lost | -l DAYS=LOST_CODE [ --charge | -c CHARGE_CODE ] [ --verbose | -v ] [ --quiet ]
99 [ --maxdays MAX_DAYS ] [ --mark-returned ] [ --category BORROWER_CATEGORY ] ...
100 [ --skip-category BORROWER_CATEGORY ] ...
101 [ --commit ]
104 WARNING: Flippant use of this script could set all or most of the items in your catalog to Lost and charge your
105 patrons for them!
107 WARNING: This script is known to be faulty. It is NOT recommended to use multiple --lost options.
108 See http://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=2883
110 =cut
112 =head1 OPTIONS
114 This script takes the following parameters :
116 =over 8
118 =item B<--lost | -l>
120 This option takes the form of n=lv, where n is num days overdue, and lv is the lost value. See warning above.
122 =item B<--charge | -c>
124 This specifies what lost value triggers Koha to charge the account for the lost item. Replacement costs are not charged if this is not specified.
126 =item B<--verbose | -v>
128 verbose.
130 =item B<--confirm>
132 confirm. without this option, the script will report the number of affected items and return without modifying any records.
134 =item B<--quiet>
136 suppress summary output.
138 =item B<--maxdays>
140 Specifies the end of the range of overdue days to deal with (defaults to 366). This value is universal to all lost num days overdue passed.
142 =item B<--mark-returned>
144 When an item is marked lost, remove it from the borrowers issued items.
146 =item B<--category>
148 Act on the listed borrower category code (borrowers.categorycode).
149 Exclude all others. This may be specified multiple times to include multiple categories.
150 May not be used with B<--skip-category>
152 =item B<--skip-category>
154 Act on all available borrower category codes, except those listed.
155 This may be specified multiple times, to exclude multiple categories.
156 May not be used with B<--category>
158 =item B<--list-categories>
160 List borrower categories available for use by B<--category> or
161 B<--skip-category>, and exit.
163 =item B<--help | -h>
165 Display short help message an exit.
167 =item B<--man | --manual >
169 Display entire manual and exit.
171 =back
173 =cut
175 =head1 Description
177 This cron script set lost values on overdue items and optionally sets charges the patron's account
178 for the item's replacement price. It is designed to be run as a nightly job. The command line options that globally
179 define this behavior for this script will likely be moved into Koha's core circulation / issuing rules code in a
180 near-term release, so this script is not intended to have a long lifetime.
183 =cut
185 =head1 Examples
187 $PERL5LIB/misc/cronjobs/longoverdue.pl --lost 30=1
188 Would set LOST=1 after 30 days (up to one year), but not charge the account.
189 This would be suitable for the Koha default LOST authorized value of 1 -> 'Lost'.
191 $PERL5LIB/misc/cronjobs/longoverdue.pl --lost 60=2 --charge 2
192 Would set LOST=2 after 60 days (up to one year), and charge the account when setting LOST=2.
193 This would be suitable for the Koha default LOST authorized value of 2 -> 'Long Overdue'
195 =cut
197 # FIXME: We need three pieces of data to operate:
198 # ~ lower bound (number of days),
199 # ~ upper bound (number of days),
200 # ~ new lost value.
201 # Right now we get only two, causing the endrange hack. This is a design-level failure.
202 # FIXME: do checks on --lost ranges to make sure they are exclusive.
203 # FIXME: do checks on --lost ranges to make sure the authorized values exist.
204 # FIXME: do checks on --lost ranges to make sure don't go past endrange.
206 if ( ! defined($lost) ) {
207 my $longoverdue_value = C4::Context->preference('DefaultLongOverdueLostValue');
208 my $longoverdue_days = C4::Context->preference('DefaultLongOverdueDays');
209 if(defined($longoverdue_value) and defined($longoverdue_days) and $longoverdue_value ne '' and $longoverdue_days ne '' and $longoverdue_days >= 0) {
210 $lost->{$longoverdue_days} = $longoverdue_value;
212 else {
213 pod2usage( {
214 -exitval => 1,
215 -msg => q|ERROR: No --lost (-l) option defined|,
216 } );
219 if ( ! defined($charge) ) {
220 my $charge_value = C4::Context->preference('DefaultLongOverdueChargeValue');
221 if(defined($charge_value) and $charge_value ne '') {
222 $charge = $charge_value;
225 unless ($confirm) {
226 $verbose = 1; # If you're not running it for real, then the whole point is the print output.
227 print "### TEST MODE -- NO ACTIONS TAKEN ###\n";
230 cronlogaction();
232 # In my opinion, this line is safe SQL to have outside the API. --atz
233 our $bounds_sth = C4::Context->dbh->prepare("SELECT DATE_SUB(CURDATE(), INTERVAL ? DAY)");
235 sub bounds ($) {
236 $bounds_sth->execute(shift);
237 return $bounds_sth->fetchrow;
240 # FIXME - This sql should be inside the API.
241 sub longoverdue_sth {
242 my $query = "
243 SELECT items.itemnumber, borrowernumber, date_due
244 FROM issues, items
245 WHERE items.itemnumber = issues.itemnumber
246 AND DATE_SUB(CURDATE(), INTERVAL ? DAY) > date_due
247 AND DATE_SUB(CURDATE(), INTERVAL ? DAY) <= date_due
248 AND itemlost <> ?
249 ORDER BY date_due
251 return C4::Context->dbh->prepare($query);
254 my $dbh = C4::Context->dbh;
255 my @available_categories = map { uc $_->[0] } @{ $dbh->selectall_arrayref(q|SELECT categorycode FROM categories|) };
256 $borrower_category = [ map { uc $_ } @$borrower_category ];
257 $skip_borrower_category = [ map { uc $_} @$skip_borrower_category ];
258 my %category_to_process;
259 for my $cat ( @$borrower_category ) {
260 unless ( grep { /^$cat$/ } @available_categories ) {
261 pod2usage(
262 '-exitval' => 1,
263 '-message' => "The category $cat does not exist in the database",
266 $category_to_process{$cat} = 1;
268 if ( @$skip_borrower_category ) {
269 for my $cat ( @$skip_borrower_category ) {
270 unless ( grep { /^$cat$/ } @available_categories ) {
271 pod2usage(
272 '-exitval' => 1,
273 '-message' => "The category $cat does not exist in the database",
277 %category_to_process = map { $_ => 1 } @available_categories;
278 %category_to_process = ( %category_to_process, map { $_ => 0 } @$skip_borrower_category );
281 my $filter_borrower_categories = ( scalar @$borrower_category || scalar @$skip_borrower_category );
283 my $count;
284 my @report;
285 my $total = 0;
286 my $i = 0;
288 # FIXME - The item is only marked returned if you supply --charge .
289 # We need a better way to handle this.
291 my $sth_items = longoverdue_sth();
293 foreach my $startrange (sort keys %$lost) {
294 if( my $lostvalue = $lost->{$startrange} ) {
295 my ($date1) = bounds($startrange);
296 my ($date2) = bounds( $endrange);
297 # print "\nRange ", ++$i, "\nDue $startrange - $endrange days ago ($date2 to $date1), lost => $lostvalue\n" if($verbose);
298 $verbose and
299 printf "\nRange %s\nDue %3s - %3s days ago (%s to %s), lost => %s\n", ++$i,
300 $startrange, $endrange, $date2, $date1, $lostvalue;
301 $sth_items->execute($startrange, $endrange, $lostvalue);
302 $count=0;
303 ITEM: while (my $row=$sth_items->fetchrow_hashref) {
304 if( $filter_borrower_categories ) {
305 my $category = uc Koha::Borrowers->find( $row->{borrowernumber} )->categorycode();
306 next ITEM unless ( $category_to_process{ $category } );
308 printf ("Due %s: item %5s from borrower %5s to lost: %s\n", $row->{date_due}, $row->{itemnumber}, $row->{borrowernumber}, $lostvalue) if($verbose);
309 if($confirm) {
310 ModItem({ itemlost => $lostvalue }, $row->{'biblionumber'}, $row->{'itemnumber'});
311 LostItem($row->{'itemnumber'}, $mark_returned) if( $charge && $charge eq $lostvalue);
313 $count++;
315 push @report, {
316 startrange => $startrange,
317 endrange => $endrange,
318 range => "$startrange - $endrange",
319 date1 => $date1,
320 date2 => $date2,
321 lostvalue => $lostvalue,
322 count => $count,
324 $total += $count;
326 $endrange = $startrange;
329 sub summarize ($$) {
330 my $arg = shift; # ref to array
331 my $got_items = shift || 0; # print "count" line for items
332 my @report = @$arg or return undef;
333 my $i = 0;
334 for my $range (@report) {
335 printf "\nRange %s\nDue %3s - %3s days ago (%s to %s), lost => %s\n", ++$i,
336 map {$range->{$_}} qw(startrange endrange date2 date1 lostvalue);
337 $got_items and printf " %4s items\n", $range->{count};
341 if (!$quiet){
342 print "\n### LONGOVERDUE SUMMARY ###";
343 summarize (\@report, 1);
344 print "\nTOTAL: $total items\n";