Bug 20727: Move temporary_directory() to C4::Context
[koha.git] / misc / cronjobs / fines.pl
index 0e28357..9fa7b94 100755 (executable)
 #  This script loops through each overdue item, determines the fine,
 #  and updates the total amount of fines due by each user.  It relies on
 #  the existence of /tmp/fines, which is created by ???
-# Doesnt really rely on it, it relys on being able to write to /tmp/
+# Doesn't really rely on it, it relys on being able to write to /tmp/
 # It creates the fines file
 #
 #  This script is meant to be run nightly out of cron.
 
 # Copyright 2000-2002 Katipo Communications
+# Copyright 2011 PTFS-Europe Ltd
 #
 # This file is part of Koha.
 #
-# Koha is free software; you can redistribute it and/or modify it under the
-# terms of the GNU General Public License as published by the Free Software
-# Foundation; either version 2 of the License, or (at your option) any later
-# version.
+# Koha is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
 #
-# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
-# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+# Koha is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
 #
-# You should have received a copy of the GNU General Public License along with
-# Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
-# Suite 330, Boston, MA  02111-1307 USA
+# You should have received a copy of the GNU General Public License
+# along with Koha; if not, see <http://www.gnu.org/licenses>.
 
-
-# FIXME: use FinesMode as described or change syspref description
 use strict;
-
-BEGIN {
-    # find Koha's Perl modules
-    # test carefully before changing this
-    use FindBin;
-    eval { require "$FindBin::Bin/kohalib.pl" };
-}
-
-use Date::Calc qw/Date_to_Days/;
+use warnings;
+use 5.010;
 
 use C4::Context;
-use C4::Circulation;
 use C4::Overdues;
-use C4::Calendar qw();  # don't need any exports from Calendar
-use C4::Biblio;
-use C4::Debug;  # supplying $debug and $cgi_debug
-
-use vars qw(@borrower_fields @item_fields @other_fields);
-use vars qw($fldir $libname $control $mode $delim $dbname $today $today_iso $today_days);
-use vars qw($filename $summary);
-
-CHECK {
-    @borrower_fields = qw(cardnumber categorycode surname firstname email phone address citystate);
-        @item_fields = qw(itemnumber barcode date_due);
-       @other_fields = qw(type days_overdue fine);
-    $libname = C4::Context->preference('LibraryName');
-    $control = C4::Context->preference('CircControl');
-    $mode    = C4::Context->preference('finesMode');
-    $dbname  = C4::Context->config('database');
-    $delim   = "\t"; # ?  C4::Context->preference('delimiter') || "\t";
-
-    $today = C4::Dates->new();
-    $today_iso = $today->output('iso');
-    $today_days = Date_to_Days(split(/-/,$today_iso));
-    $fldir = $ENV{TMPDIR} || "/tmp"; # TODO: use GetOpt
-    $filename = $dbname;
-    $filename =~ s/\W//;
-    $filename = $fldir . '/'. $filename . '_' .  $today_iso . ".log";
-    $summary = 1;  # TODO: use GetOpt
+use Getopt::Long;
+use Carp;
+use File::Spec;
+
+use Koha::Calendar;
+use Koha::DateUtils;
+use Koha::UploadedFile;
+use C4::Log;
+
+my $help;
+my $verbose;
+my $output_dir;
+my $log;
+
+GetOptions(
+    'h|help'    => \$help,
+    'v|verbose' => \$verbose,
+    'l|log'     => \$log,
+    'o|out:s'   => \$output_dir,
+);
+my $usage = << 'ENDUSAGE';
+
+This script calculates and charges overdue fines
+to patron accounts.  The Koha system preference 'finesMode' controls
+whether the fines are calculated and charged to the patron accounts ("Calculate and charge");
+calculated and emailed to the admin but not applied ("Calculate (but only for mailing to the admin)"); or not calculated ("Don't calculate").
+
+This script has the following parameters :
+    -h --help: this message
+    -l --log: log the output to a file (optional if the -o parameter is given)
+    -o --out:  ouput directory for logs (defaults to env or /tmp if !exist)
+    -v --verbose
+
+ENDUSAGE
+
+if ($help) {
+    print $usage;
+    exit;
+}
+
+cronlogaction();
+
+my @borrower_fields =
+  qw(cardnumber categorycode surname firstname email phone address citystate);
+my @item_fields  = qw(itemnumber barcode date_due);
+my @other_fields = qw(type days_overdue fine);
+my $libname      = C4::Context->preference('LibraryName');
+my $control      = C4::Context->preference('CircControl');
+my $mode         = C4::Context->preference('finesMode');
+my $delim = "\t";    # ?  C4::Context->preference('delimiter') || "\t";
+
+my %is_holiday;
+my $today = DateTime->now( time_zone => C4::Context->tz() );
+my $filename;
+if ($log or $output_dir) {
+    $filename = get_filename($output_dir);
 }
 
-INIT {
-    $debug and print "Each line will contain the following fields:\n",
-        "From borrowers : ", join(', ', @borrower_fields), "\n",
-        "From items : ", join(', ', @item_fields), "\n",
-        "Per overdue: ", join(', ', @other_fields), "\n",
-        "Delimiter: '$delim'\n";
+my $fh;
+if ($filename) {
+    open $fh, '>>', $filename or croak "Cannot write file $filename: $!";
+    print {$fh} join $delim, ( @borrower_fields, @item_fields, @other_fields );
+    print {$fh} "\n";
 }
+my $counted = 0;
+my $overdues = Getoverdues();
+for my $overdue ( @{$overdues} ) {
+    next if $overdue->{itemlost};
+
+    if ( !defined $overdue->{borrowernumber} ) {
+        carp
+"ERROR in Getoverdues : issues.borrowernumber IS NULL.  Repair 'issues' table now!  Skipping record.\n";
+        next;
+    }
+    my $borrower = BorType( $overdue->{borrowernumber} );
+    my $branchcode =
+        ( $control eq 'ItemHomeLibrary' ) ? $overdue->{homebranch}
+      : ( $control eq 'PatronLibrary' )   ? $borrower->{branchcode}
+      :                                     $overdue->{branchcode};
+
+# In final case, CircControl must be PickupLibrary. (branchcode comes from issues table here).
+    if ( !exists $is_holiday{$branchcode} ) {
+        $is_holiday{$branchcode} = set_holiday( $branchcode, $today );
+    }
 
-open (FILE, ">$filename") or die "Cannot write file $filename: $!";
-print FILE join $delim, (@borrower_fields, @item_fields, @other_fields);
-print FILE "\n";
-
-my $data = Getoverdues();
-my $overdueItemsCounted = 0;
-my %calendars = ();
-
-for (my $i=0; $i<scalar(@$data); $i++) {
-    my $datedue = C4::Dates->new($data->[$i]->{'date_due'},'iso');
-    my $datedue_days = Date_to_Days(split(/-/,$datedue->output('iso')));
-    my $due_str = $datedue->output();
-    my $borrower = BorType($data->[$i]->{'borrowernumber'});
-    my $branchcode = ($control eq 'ItemHomeLibrary') ? $data->[$i]->{homebranch} :
-                     ($control eq 'PatronLibrary'  ) ?   $borrower->{branchcode} :
-                                                       $data->[$i]->{branchcode} ;
-    # In final case, CircControl must be PickupLibrary. (branchcode comes from issues table here).
-    my $calendar;
-    unless (defined ($calendars{$branchcode})) {
-        $calendars{$branchcode} = C4::Calendar->new(branchcode => $branchcode);
+    my $datedue = dt_from_string( $overdue->{date_due} );
+    if ( DateTime->compare( $datedue, $today ) == 1 ) {
+        next;    # not overdue
+    }
+    ++$counted;
+
+    my ( $amount, $type, $unitcounttotal ) =
+      CalcFine( $overdue, $borrower->{categorycode},
+        $branchcode, $datedue, $today );
+    $type ||= q{};
+
+    # Don't update the fine if today is a holiday.
+    # This ensures that dropbox mode will remove the correct amount of fine.
+    if ( $mode eq 'production' && !$is_holiday{$branchcode} ) {
+        if ( $amount && $amount > 0 ) {
+            UpdateFine(
+                {
+                    issue_id       => $overdue->{issue_id},
+                    itemnumber     => $overdue->{itemnumber},
+                    borrowernumber => $overdue->{borrowernumber},
+                    amount         => $amount,
+                    type           => $type,
+                    due            => output_pref($datedue),
+                }
+            );
+        }
+    }
+    if ($filename) {
+        my @cells;
+        push @cells,
+          map { defined $borrower->{$_} ? $borrower->{$_} : q{} }
+          @borrower_fields;
+        push @cells, map { $overdue->{$_} } @item_fields;
+        push @cells, $type, $unitcounttotal, $amount;
+        say {$fh} join $delim, @cells;
     }
-    $calendar = $calendars{$branchcode};
-    my $isHoliday = $calendar->isHoliday(split '/', $today->output('metric'));
-      
-    ($datedue_days <= $today_days) or next; # or it's not overdue, right?
-
-    $overdueItemsCounted++;
-    my ($amount,$type,$daycounttotal,$daycount)=
-               CalcFine($data->[$i], $borrower->{'categorycode'}, $branchcode,undef,undef, $datedue, $today);
-        # FIXME: $type NEVER gets populated by anything.
-    (defined $type) or $type = '';
-       # Don't update the fine if today is a holiday.  
-       # This ensures that dropbox mode will remove the correct amount of fine.
-       if ($mode eq 'production' and  ! $isHoliday) {
-               UpdateFine($data->[$i]->{'itemnumber'},$data->[$i]->{'borrowernumber'},$amount,$type,$due_str) if( $amount > 0 ) ;
-       }
-    my @cells = ();
-    push @cells, map {$borrower->{$_}} @borrower_fields;
-    push @cells, map {$data->[$i]->{$_}} @item_fields;
-    push @cells, $type, $daycounttotal, $amount;
-    print FILE join($delim, @cells), "\n";
+}
+if ($filename){
+    close $fh;
 }
 
-my $numOverdueItems = scalar(@$data);
-if ($summary) {
-   print <<EOM;
-Fines assessment -- $today_iso -- Saved to $filename
+if ($verbose) {
+    my $overdue_items = @{$overdues};
+    print <<"EOM";
+Fines assessment -- $today
+EOM
+    if ($filename) {
+        say "Saved to $filename";
+    }
+    print <<"EOM";
 Number of Overdue Items:
-     counted $overdueItemsCounted
-    reported $numOverdueItems
+     counted $overdue_items
+    reported $counted
 
 EOM
 }
 
-close FILE;
+sub set_holiday {
+    my ( $branch, $dt ) = @_;
+
+    my $calendar = Koha::Calendar->new( branchcode => $branch );
+    return $calendar->is_holiday($dt);
+}
+
+sub get_filename {
+    my $directory = shift;
+    if ( !$directory ) {
+        $directory = C4::Context::temporary_directory;
+    }
+    if ( !-d $directory ) {
+        carp "Could not write to $directory ... does not exist!";
+    }
+    my $name = C4::Context->config('database');
+    $name =~ s/\W//;
+    $name .= join q{}, q{_}, $today->ymd(), '.log';
+    $name = File::Spec->catfile( $directory, $name );
+    if ($verbose && $log) {
+        say "writing to $name";
+    }
+    return $name;
+}