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 #-----------------------------------
23 longoverdue.pl cron script to set lost statuses on overdue materials.
24 Execute without options for help.
31 # find Koha's Perl modules
32 # test carefully before changing this
34 eval { require "$FindBin::Bin/../kohalib.pl" };
40 use C4
::Circulation qw
/LostItem MarkIssueReturned/;
45 use Koha
::Patron
::Categories
;
47 use Koha
::Script
-cron
;
49 my $lost; # key=lost value, value=num days.
50 my ($charge, $verbose, $confirm, $quiet);
53 my $borrower_category = [];
54 my $skip_borrower_category = [];
56 my $skip_itemtype = [];
59 my $list_categories = 0;
60 my $list_itemtypes = 0;
64 'l|lost=s%' => \
$lost,
65 'c|charge=s' => \
$charge,
66 'confirm' => \
$confirm,
67 'v|verbose' => \
$verbose,
69 'maxdays=s' => \
$endrange,
70 'mark-returned' => \
$mark_returned,
72 'man|manual' => \
$man,
73 'category=s' => $borrower_category,
74 'skip-category=s' => $skip_borrower_category,
75 'list-categories' => \
$list_categories,
76 'itemtype=s' => $itemtype,
77 'skip-itemtype=s' => $skip_itemtype,
78 'list-itemtypes' => \
$list_itemtypes,
79 'skip-lost-value=s' => \
@skip_lost_values,
83 pod2usage
( -verbose
=> 2
89 pod2usage
( -verbose
=> 1,
94 if ( scalar @
$borrower_category && scalar @
$skip_borrower_category) {
95 pod2usage
( -verbose
=> 1,
96 -message
=> "The options --category and --skip-category are mutually exclusive.\n"
97 . "Use one or the other.",
102 if ( scalar @
$itemtype && scalar @
$skip_itemtype) {
103 pod2usage
( -verbose
=> 1,
104 -message
=> "The options --itemtype and --skip-itemtype are mutually exclusive.\n"
105 . "Use one or the other.",
110 if ( $list_categories ) {
112 my @categories = Koha
::Patron
::Categories
->search()->get_column('categorycode');
113 print "\nBorrower Categories: " . join( " ", @categories ) . "\n\n";
117 if ( $list_itemtypes ) {
118 my @itemtypes = Koha
::ItemTypes
->search()->get_column('itemtype');
119 print "\nItemtypes: " . join( " ", @itemtypes ) . "\n\n";
125 longoverdue.pl [ --help | -h | --man | --list-categories ]
126 longoverdue.pl --lost | -l DAYS=LOST_CODE [ --charge | -c CHARGE_CODE ] [ --verbose | -v ] [ --quiet ]
127 [ --maxdays MAX_DAYS ] [ --mark-returned ] [ --category BORROWER_CATEGORY ] ...
128 [ --skip-category BORROWER_CATEGORY ] ...
129 [ --skip-lost-value LOST_VALUE [ --skip-lost-value LOST_VALUE ] ]
133 WARNING: Flippant use of this script could set all or most of the items in your catalog to Lost and charge your
136 WARNING: This script is known to be faulty. It is NOT recommended to use multiple --lost options.
137 See http://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=2883
143 This script takes the following parameters :
149 This option takes the form of n=lv, where n is num days overdue, and lv is the lost value. See warning above.
151 =item B<--charge | -c>
153 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.
155 =item B<--verbose | -v>
161 confirm. without this option, the script will report the number of affected items and return without modifying any records.
165 suppress summary output.
169 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.
171 =item B<--mark-returned>
173 When an item is marked lost, remove it from the borrowers issued items.
174 If not provided, the value of the system preference 'MarkLostItemsAsReturned' will be used.
178 Act on the listed borrower category code (borrowers.categorycode).
179 Exclude all others. This may be specified multiple times to include multiple categories.
180 May not be used with B<--skip-category>
182 =item B<--skip-category>
184 Act on all available borrower category codes, except those listed.
185 This may be specified multiple times, to exclude multiple categories.
186 May not be used with B<--category>
188 =item B<--list-categories>
190 List borrower categories available for use by B<--category> or
191 B<--skip-category>, and exit.
195 Act on the listed itemtype code.
196 Exclude all others. This may be specified multiple times to include multiple itemtypes.
197 May not be used with B<--skip-itemtype>
199 =item B<--skip-itemtype>
201 Act on all available itemtype codes, except those listed.
202 This may be specified multiple times, to exclude multiple itemtypes.
203 May not be used with B<--itemtype>
205 =item B<--skip-lost-value>
207 Act on all available lost values, except those listed.
208 This may be specified multiple times, to exclude multiple lost values.
210 =item B<--list-itemtypes>
212 List itemtypes available for use by B<--itemtype> or
213 B<--skip-itemtype>, and exit.
217 Display short help message an exit.
219 =item B<--man | --manual >
221 Display entire manual and exit.
229 This cron script set lost values on overdue items and optionally sets charges the patron's account
230 for the item's replacement price. It is designed to be run as a nightly job. The command line options that globally
231 define this behavior for this script will likely be moved into Koha's core circulation / issuing rules code in a
232 near-term release, so this script is not intended to have a long lifetime.
239 $PERL5LIB/misc/cronjobs/longoverdue.pl --lost 30=1
240 Would set LOST=1 after 30 days (up to one year), but not charge the account.
241 This would be suitable for the Koha default LOST authorized value of 1 -> 'Lost'.
243 $PERL5LIB/misc/cronjobs/longoverdue.pl --lost 60=2 --charge 2
244 Would set LOST=2 after 60 days (up to one year), and charge the account when setting LOST=2.
245 This would be suitable for the Koha default LOST authorized value of 2 -> 'Long Overdue'
249 # FIXME: We need three pieces of data to operate:
250 # ~ lower bound (number of days),
251 # ~ upper bound (number of days),
253 # Right now we get only two, causing the endrange hack. This is a design-level failure.
254 # FIXME: do checks on --lost ranges to make sure they are exclusive.
255 # FIXME: do checks on --lost ranges to make sure the authorized values exist.
256 # FIXME: do checks on --lost ranges to make sure don't go past endrange.
259 unless ( scalar @skip_lost_values ) {
260 my $preference = C4
::Context
->preference( 'DefaultLongOverdueSkipLostStatuses' );
261 @skip_lost_values = split( ',', $preference );
264 if ( ! defined($lost) ) {
265 my $longoverdue_value = C4
::Context
->preference('DefaultLongOverdueLostValue');
266 my $longoverdue_days = C4
::Context
->preference('DefaultLongOverdueDays');
267 if(defined($longoverdue_value) and defined($longoverdue_days) and $longoverdue_value ne '' and $longoverdue_days ne '' and $longoverdue_days >= 0) {
268 $lost->{$longoverdue_days} = $longoverdue_value;
273 -msg
=> q
|ERROR
: No
--lost
(-l
) option
defined|,
277 if ( ! defined($charge) ) {
278 my $charge_value = C4
::Context
->preference('DefaultLongOverdueChargeValue');
279 if(defined($charge_value) and $charge_value ne '') {
280 $charge = $charge_value;
284 $verbose = 1; # If you're not running it for real, then the whole point is the print output.
285 print "### TEST MODE -- NO ACTIONS TAKEN ###\n";
290 # In my opinion, this line is safe SQL to have outside the API. --atz
291 our $bounds_sth = C4
::Context
->dbh->prepare("SELECT DATE_SUB(CURDATE(), INTERVAL ? DAY)");
294 $bounds_sth->execute(shift);
295 return $bounds_sth->fetchrow;
298 # FIXME - This sql should be inside the API.
299 sub longoverdue_sth
{
301 my $skip_lost_values = $params->{skip_lost_values
};
303 my $skip_lost_values_sql = q{};
304 if ( @
$skip_lost_values ) {
305 my $values = join( ',', map { qq{'$_'} } @
$skip_lost_values );
306 $skip_lost_values_sql = "AND itemlost NOT IN ( $values )"
310 SELECT items.itemnumber, borrowernumber, date_due, itemlost
312 WHERE items.itemnumber = issues.itemnumber
313 AND DATE_SUB(CURDATE(), INTERVAL ? DAY) > date_due
314 AND DATE_SUB(CURDATE(), INTERVAL ? DAY) <= date_due
316 $skip_lost_values_sql
319 return C4
::Context
->dbh->prepare($query);
322 my $dbh = C4
::Context
->dbh;
324 my @available_categories = Koha
::Patron
::Categories
->search()->get_column('categorycode');
325 $borrower_category = [ map { uc $_ } @
$borrower_category ];
326 $skip_borrower_category = [ map { uc $_} @
$skip_borrower_category ];
327 my %category_to_process;
328 for my $cat ( @
$borrower_category ) {
329 unless ( grep { $_ eq $cat } @available_categories ) {
332 '-message' => "The category $cat does not exist in the database",
335 $category_to_process{$cat} = 1;
337 if ( @
$skip_borrower_category ) {
338 for my $cat ( @
$skip_borrower_category ) {
339 unless ( grep { $_ eq $cat } @available_categories ) {
342 '-message' => "The category $cat does not exist in the database",
346 %category_to_process = map { $_ => 1 } @available_categories;
347 %category_to_process = ( %category_to_process, map { $_ => 0 } @
$skip_borrower_category );
350 my $filter_borrower_categories = ( scalar @
$borrower_category || scalar @
$skip_borrower_category );
352 my @available_itemtypes = Koha
::ItemTypes
->search()->get_column('itemtype');
353 $itemtype = [ map { uc $_ } @
$itemtype ];
354 $skip_itemtype = [ map { uc $_} @
$skip_itemtype ];
355 my %itemtype_to_process;
356 for my $it ( @
$itemtype ) {
357 unless ( grep { $_ eq $it } @available_itemtypes ) {
360 '-message' => "The itemtype $it does not exist in the database",
363 $itemtype_to_process{$it} = 1;
365 if ( @
$skip_itemtype ) {
366 for my $it ( @
$skip_itemtype ) {
367 unless ( grep { $_ eq $it } @available_itemtypes ) {
370 '-message' => "The itemtype $it does not exist in the database",
374 %itemtype_to_process = map { $_ => 1 } @available_itemtypes;
375 %itemtype_to_process = ( %itemtype_to_process, map { $_ => 0 } @
$skip_itemtype );
378 my $filter_itemtypes = ( scalar @
$itemtype || scalar @
$skip_itemtype );
385 # FIXME - The item is only marked returned if you supply --charge .
386 # We need a better way to handle this.
388 my $sth_items = longoverdue_sth
({ skip_lost_values
=> \
@skip_lost_values });
390 foreach my $startrange (sort keys %$lost) {
391 if( my $lostvalue = $lost->{$startrange} ) {
392 my ($date1) = bounds
($startrange);
393 my ($date2) = bounds
( $endrange);
394 # print "\nRange ", ++$i, "\nDue $startrange - $endrange days ago ($date2 to $date1), lost => $lostvalue\n" if($verbose);
396 printf "\nRange %s\nDue %3s - %3s days ago (%s to %s), lost => %s\n", ++$i,
397 $startrange, $endrange, $date2, $date1, $lostvalue;
398 $sth_items->execute($startrange, $endrange, $lostvalue);
400 ITEM
: while (my $row=$sth_items->fetchrow_hashref) {
401 if( $filter_borrower_categories ) {
402 my $category = uc Koha
::Patrons
->find( $row->{borrowernumber
} )->categorycode();
403 next ITEM
unless ( $category_to_process{ $category } );
405 if ($filter_itemtypes) {
406 my $it = uc Koha
::Items
->find( $row->{itemnumber
} )->effective_itemtype();
407 next ITEM
unless ( $itemtype_to_process{$it} );
409 printf ("Due %s: item %5s from borrower %5s to lost: %s\n", $row->{date_due
}, $row->{itemnumber
}, $row->{borrowernumber
}, $lostvalue) if($verbose);
411 Koha
::Items
->find( $row->{itemnumber
} )->itemlost($lostvalue)
413 if ( $charge && $charge eq $lostvalue ) {
414 LostItem
( $row->{'itemnumber'}, 'cronjob', $mark_returned );
415 } elsif ( $mark_returned ) {
416 my $patron = Koha
::Patrons
->find( $row->{borrowernumber
} );
417 MarkIssueReturned
($row->{borrowernumber
},$row->{itemnumber
},undef,$patron->privacy)
423 startrange
=> $startrange,
424 endrange
=> $endrange,
425 range
=> "$startrange - $endrange",
428 lostvalue
=> $lostvalue,
433 $endrange = $startrange;
437 my $arg = shift; # ref to array
438 my $got_items = shift || 0; # print "count" line for items
439 my @report = @
$arg or return;
441 for my $range (@report) {
442 printf "\nRange %s\nDue %3s - %3s days ago (%s to %s), lost => %s\n", ++$i,
443 map {$range->{$_}} qw(startrange endrange date2 date1 lostvalue);
444 $got_items and printf " %4s items\n", $range->{count
};
449 print "\n### LONGOVERDUE SUMMARY ###";
450 summarize
(\
@report, 1);
451 print "\nTOTAL: $total items\n";