From: Andrew Moore <[EMAIL PROTECTED]>

This patch adds the misc/cronjobs/overdue_notices.pl script that is intended to 
replace
overduenotices.pl, overduenotices-30.pl and overduenotices-csv.pl. It adds 
messages to
the message_queue to be sent later (by process_message_queue.pl). It also marks 
borrowers
as debarred if their issues become too overdue.

It is intended to be run from cron nightly with usage something like:
0 2 * * * misc/cronjobs/overdue_notices.pl -c

C4::Members:
 - improved documentation on ModMember
 - made ModMember return a useful value (the return value of the database call)
 - added a DebarMember method
 - adding t/lib/KohaTest/Members/DebarMember.pm to test ModMember

misc/cronjobs/overdue_notices.pl
 - designed to replace overduenotices.pl, overduenotices-30.pl, and 
overduenotice-csv

Changes to C4::Letters:
 - EnqueueLetter now lets you pass in to_address and from_address which can 
override defaults
 - _send_message_by_email pays attention to these defaults.

Signed-off-by: Galen Charlton <[EMAIL PROTECTED]>
---
 C4/Letters.pm                         |   30 ++-
 C4/Members.pm                         |   41 ++++-
 misc/cronjobs/overdue_notices.pl      |  399 +++++++++++++++++++++++++++++++++
 t/lib/KohaTest/Members/DebarMember.pm |   44 ++++
 4 files changed, 499 insertions(+), 15 deletions(-)
 create mode 100755 misc/cronjobs/overdue_notices.pl
 create mode 100644 t/lib/KohaTest/Members/DebarMember.pm

diff --git a/C4/Letters.pm b/C4/Letters.pm
index d82e2b8..af69be5 100644
--- a/C4/Letters.pm
+++ b/C4/Letters.pm
@@ -532,18 +532,21 @@ sub EnqueueLetter {
     my $dbh = C4::Context->dbh();
     my $statement = << 'ENDSQL';
 INSERT INTO message_queue
-( borrowernumber, subject, content, message_transport_type, status, 
time_queued )
+( borrowernumber, subject, content, message_transport_type, status, 
time_queued, to_address, from_address )
 VALUES
-( ?,              ?,       ?,       ?,                      ?,      NOW() )
+( ?,              ?,       ?,       ?,                      ?,      NOW(),     
  ?,          ? )
 ENDSQL
 
     my $sth = $dbh->prepare( $statement );
-    my $result = $sth->execute( $params->{'borrowernumber'},         # 
borrowernumber
-                                $params->{'letter'}->{'title'},      # subject
-                                $params->{'letter'}->{'content'},    # content
-                                $params->{'message_transport_type'}, # 
message_transport_type
-                                'pending',                           # status
-                           );
+    my $result = $sth->execute(
+        $params->{'borrowernumber'},            # borrowernumber
+        $params->{'letter'}->{'title'},         # subject
+        $params->{'letter'}->{'content'},       # content
+        $params->{'message_transport_type'},    # message_transport_type
+        'pending',                              # status
+        $params->{'to_address'},                # to_address
+        $params->{'from_address'},              # from_address
+    );
     return $result;
 }
 
@@ -690,11 +693,12 @@ sub _send_message_by_email {
     my $member = C4::Members::GetMember( $message->{'borrowernumber'} );
     return unless $member->{'email'};
 
-    my $success = sendmail( To      => $member->{'email'},
-                            From    => 
C4::Context->preference('KohaAdminEmailAddress'),
-                            Subject => $message->{'subject'},
-                            Message => $message->{'content'},
-                       );
+    my $success = sendmail(
+        To   => $message->{'to_address'}   || $member->{'email'},
+        From => $message->{'from_address'} || 
C4::Context->preference('KohaAdminEmailAddress'),
+        Subject => $message->{'subject'},
+        Message => $message->{'content'},
+    );
     if ( $success ) {
         # warn "OK. Log says:\n", $Mail::Sendmail::log;
         _set_message_status( { message_id => $message->{'message_id'},
diff --git a/C4/Members.pm b/C4/Members.pm
index 7c7c48c..999d8fb 100644
--- a/C4/Members.pm
+++ b/C4/Members.pm
@@ -594,10 +594,17 @@ sub GetMemberIssuesAndFines {
 
 =head2 ModMember
 
-  &ModMember($borrowernumber);
+=over 4
+
+my $success = ModMember(borrowernumber => $borrowernumber, [ field => value 
]... );
 
 Modify borrower's data.  All date fields should ALREADY be in ISO format.
 
+return :
+true on success, or false on failure
+
+=back
+
 =cut
 
 #'
@@ -647,7 +654,7 @@ sub ModMember {
     push @parameters, $data{'borrowernumber'};
     $debug and print STDERR "$query (executed w/ arg: 
$data{'borrowernumber'})";
     $sth = $dbh->prepare($query);
-    $sth->execute(@parameters);
+    my $execute_success = $sth->execute(@parameters);
     $sth->finish;
 
 # ok if its an adult (type) it may have borrowers that depend on it as a 
guarantor
@@ -660,6 +667,8 @@ sub ModMember {
     }
     logaction("MEMBERS", "MODIFY", $data{'borrowernumber'}, "$query (executed 
w/ arg: $data{'borrowernumber'})") 
         if C4::Context->preference("BorrowersLog");
+
+    return $execute_success;
 }
 
 
@@ -2018,6 +2027,34 @@ sub GetBorrowersNamesAndLatestIssue {
     my $results = $sth->fetchall_arrayref({});
     return $results;
 }
+
+=head2 DebarMember
+
+=over 4
+
+my $success = DebarMember( { borrowernumber => $borrowernumber } );
+
+marks a Member as debarred, and therefore unable to checkout any more
+items.
+
+return :
+true on success, false on failure
+
+=back
+
+=cut
+
+sub DebarMember {
+    my $borrowernumber = shift;
+
+    return unless defined $borrowernumber;
+    return unless $borrowernumber =~ /^\d+$/;
+
+    return ModMember( borrowernumber => $borrowernumber,
+                      debarred       => 1 );
+    
+}
+
 END { }    # module clean-up code here (global destructor)
 
 1;
diff --git a/misc/cronjobs/overdue_notices.pl b/misc/cronjobs/overdue_notices.pl
new file mode 100755
index 0000000..637fc71
--- /dev/null
+++ b/misc/cronjobs/overdue_notices.pl
@@ -0,0 +1,399 @@
+#!/usr/bin/perl -w
+
+# Copyright 2008 Liblime
+#
+# 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 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
+
+use strict;
+use warnings;
+
+BEGIN {
+
+    # find Koha's Perl modules
+    # test carefully before changing this
+    use FindBin;
+    eval { require "$FindBin::Bin/../kohalib.pl" };
+}
+
+use C4::Context;
+use C4::Dates qw/format_date/;
+use C4::Debug;
+use C4::Letters;
+
+use Getopt::Long;
+use Pod::Usage;
+use Text::CSV_XS;
+
+=head1 NAME
+
+overdue_notices.pl - prepare messages to be sent to patrons for overdue items
+
+=head1 SYNOPSIS
+
+overdue_notices.pl -c [ -n ] [ -branch MAIN ] [ -max 31 ]
+
+ Options:
+   -help                  brief help message
+   -man                   full documentation
+   -c                     Confirm that you have read the documentation
+   -n                     No email will be sent
+   -max    <days>         maximum days overdue to deal with
+   -branch <branchname>   only deal with overdues from this branch.
+   -csv    <filename>     populate CSV file
+
+=head1 OPTIONS
+
+=over 8
+
+=item B<-help>
+
+Print a brief help message and exits.
+
+=item B<-man>
+
+Prints the manual page and exits.
+
+=item B<-v>
+
+Verbose. Without this flag set, only fatal errors are reported.
+
+=item B<-n>
+
+Do not send any email. The overdue notices are printed to standard out in this 
case.
+
+=item B<-max>
+
+Items older than max days are handled somewhere else, probably borrower 
suspended. Defaults to 365.
+
+=item B<-branch>
+
+select overdues for one specific branch
+
+=item B<-csv>
+
+populate CSV file with overdues information. If -n (no mail) is
+selected, all overdues are put in file, otherwise, only overdues that
+could not be emailed are sent.
+
+=back
+
+=head1 DESCRIPTION
+
+This script will send overdue notices by e-mail and prepare a file of
+notices for printing if the borrower does not have e-mail.
+
+=cut
+
+# These variables are set by command line options.
+# They are initially set to default values.
+my $help    = 0;
+my $man     = 0;
+my $verbose = 0;
+my $nomail  = 0;
+my $MAX     = 365;
+my $mybranch;
+my $csvfilename;
+
+GetOptions(
+    'help|?'   => \$help,
+    'man'      => \$man,
+    'v'        => \$verbose,
+    'n'        => \$nomail,
+    'max=s'    => \$MAX,
+    'branch=s' => \$mybranch,
+    'csv=s'    => \$csvfilename,
+) or pod2usage(2);
+pod2usage(1) if $help;
+pod2usage( -verbose => 2 ) if $man;
+
+my @branches    = get_branches_with_overdue_rules();
+my $branchcount = scalar(@branches);
+if ($branchcount) {
+    my $branch_word = scalar @branches > 1 ? 'branches' : 'branch';
+    $verbose and warn "Found $branchcount $branch_word with first message 
enabled: " . join( ', ', map { "'$_'" } @branches ), "\n";
+} else {
+    die 'No branches with active overduerules';
+}
+
+if ($mybranch) {
+    $verbose and warn "Branch $mybranch selected\n";
+    if ( scalar grep { $mybranch eq $_ } @branches ) {
+        @branches = ($mybranch);
+    } else {
+        $verbose and warn "No active overduerules for branch '$mybranch'\n";
+        ( scalar grep { '' eq $_ } @branches )
+          or die "No active overduerules for DEFAULT either!";
+        $verbose and warn "Falling back on default rules for $mybranch\n";
+        @branches = ('');
+    }
+}
+
+my $dbh = C4::Context->dbh();
+
+our $csv;       # the Text::CSV_XS object
+our $csv_fh;    # the filehandle to the CSV file.
+if ($csvfilename) {
+    $csv = Text::CSV_XS->new( { binary => 1 } );
+    open $csv_fh, ">", $csvfilename or die "unable to open $csvfilename: $!";
+    if ( $csv->combine(qw(name surname address1 address2 zipcode city email 
itemcount itemsinfo)) ) {
+        print $csv_fh $csv->string, "\n";
+    } else {
+        $verbose and warn 'combine failed on argument: ' . $csv->error_input;
+    }
+
+}
+
+foreach my $branchcode (@branches) {
+
+    my $branch_details = C4::Branch::GetBranchDetail($branchcode);
+    my $admin_email_address = $branch_details->{'branchemail'} || 
C4::Context->preference('KohaAdminEmailAddress');
+
+    $verbose and warn sprintf "branchcode : '%s' using %s\n", $branchcode, 
$admin_email_address;
+
+    my $sth2 = $dbh->prepare( <<'END_SQL' );
+SELECT biblio.title, biblio.author, items.barcode, issues.timestamp
+  FROM issues,items,biblio
+  WHERE items.itemnumber=issues.itemnumber
+    AND biblio.biblionumber   = items.biblionumber
+    AND issues.borrowernumber = ?
+    AND TO_DAYS(NOW())-TO_DAYS(date_due) BETWEEN ? and ?
+END_SQL
+
+    my $rqoverduerules = $dbh->prepare("SELECT * FROM overduerules WHERE 
delay1 IS NOT NULL AND branchcode = ? ");
+    $rqoverduerules->execute($branchcode);
+    my $outfile = 'overdues_' . ( $mybranch || $branchcode || 'default' );
+    while ( my $overdue_rules = $rqoverduerules->fetchrow_hashref ) {
+      PERIOD: foreach my $i ( 1 .. 3 ) {
+
+            $verbose and warn "branch '$branchcode', pass $i\n";
+            my $mindays = $overdue_rules->{"delay$i"};    # the notice will be 
sent after mindays days (grace period)
+            my $maxdays = (
+                  $overdue_rules->{ "delay" . ( $i + 1 ) }
+                ? $overdue_rules->{ "delay" . ( $i + 1 ) }
+                : ($MAX)
+            );                                            # issues being more 
than maxdays late are managed somewhere else. (borrower probably suspended)
+
+            my $letter;
+            if ( $overdue_rules->{"letter$i"} ) {
+                $letter = C4::Letters::getletter( 'circulation', 
$overdue_rules->{"letter$i"} );
+                unless ($letter) {
+                    $verbose and warn "Message '$overdue_rules->{letter$i}' 
content not found";
+                    next PERIOD;
+                }
+            } else {
+                $verbose and warn "No letter$i code for branch '$branchcode'";
+                next PERIOD;
+            }
+
+            # $letter->{'content'} is the text of the mail that is sent.
+            # this text contains fields that are replaced by their value. 
Those fields must be written between brackets
+            # The following fields are available :
+            # <date> <itemcount> <firstname> <lastname> <address1> <address2> 
<address3> <city> <postcode>
+
+            my $borrower_sql = <<'END_SQL';
+SELECT COUNT(*), issues.borrowernumber, firstname, surname, address, address2, 
city, zipcode, email, MIN(date_due) as longest_issue
+FROM   issues,borrowers,categories
+WHERE  issues.borrowernumber=borrowers.borrowernumber
+AND    borrowers.categorycode=categories.categorycode
+END_SQL
+            my @borrower_parameters;
+            if ($branchcode) {
+                $borrower_sql .= ' AND issues.branchcode=? ';
+                push @borrower_parameters, $branchcode;
+            }
+            if ( $overdue_rules->{categorycode} ) {
+                $borrower_sql .= ' AND borrowers.categorycode=? ';
+                push @borrower_parameters, $overdue_rules->{categorycode};
+            }
+            $borrower_sql .= <<'END_SQL';
+AND    categories.overduenoticerequired=1
+GROUP BY issues.borrowernumber
+HAVING TO_DAYS(NOW())-TO_DAYS(longest_issue) BETWEEN ? and ?
+END_SQL
+            push @borrower_parameters, $mindays, $maxdays;
+            my $sth = $dbh->prepare($borrower_sql);
+            $sth->execute(@borrower_parameters);
+            $verbose and warn $borrower_sql . "\n\n ($mindays, 
$maxdays)\nreturns " . $sth->rows . " rows";
+            my $count   = 0;                          # to keep track of how 
many notices are printed
+            my $e_count = 0;                          # and e-mailed
+            my $date    = C4::Dates->new()->output;
+
+            while ( my ( $itemcount, $borrowernumber, $firstname, $lastname, 
$address1, $address2, $city, $postcode, $email ) = $sth->fetchrow ) {
+                if ( $overdue_rules->{"debarred$i"} ) {
+
+                    #action taken is debarring
+                    C4::Members::DebarMember( $borrowernumber );
+                    $verbose and warn "debarring $borrowernumber $firstname 
$lastname\n";
+                }
+
+                $sth2->execute( $borrowernumber, $mindays, $maxdays );
+                my $titles = "";
+                while ( my ( $title, $author, $barcode, $issuedate ) = 
$sth2->fetchrow ) {
+                    $titles .= join "\t", format_date($issuedate), ( $barcode 
? $barcode : "" ), ( $title ? $title : "" ), ( $author ? $author : "" ) . "\n";
+                }
+                $sth2->finish;
+
+                $letter = parse_letter(
+                    {   letter         => $letter,
+                        borrowernumber => $borrowernumber,
+                        branchcode     => $branchcode,
+                        substitute     => {
+                            date   => $date,
+                            bib    => $branch_details->{'branchname'},
+                            titles => $titles
+                        }
+                    }
+                );
+
+                my @misses = grep { /./ } map { /^([^>]*)[>]+/; ( $1 || '' ); 
} split /\</, $letter->{'content'};
+                if (@misses) {
+                    $verbose and warn "The following terms were not matched 
and replaced: \n\t" . join "\n\t", @misses;
+                }
+                $letter->{'content'} =~ s/\<[^<>]*?\>//g;    # Now that we've 
warned about them, remove them.
+                $letter->{'content'} =~ s/\<[^<>]*?\>//g;    # 2nd pass for 
the double nesting.
+
+                if ($nomail) {
+
+                    # print the mail to stdout
+                    print_letter(
+                        {   letter         => $letter,
+                            borrowernumber => $borrowernumber,
+                            firstname      => $firstname,
+                            lastname       => $lastname,
+                            address1       => $address1,
+                            address2       => $address2,
+                            city           => $city,
+                            postcode       => $postcode,
+                            email          => $email,
+                            itemcount      => $itemcount,
+                            titles         => $titles,
+                            outputformat   => $csvfilename ? 'csv' : '',
+                        }
+                    );
+                } else {
+                    my $enqueue_parameters = {
+                        letter                 => $letter,
+                        borrowernumber         => $borrowernumber,
+                        message_transport_type => 'email',
+                        from_address           => $admin_email_address,
+                    };
+
+                    # If we don't have an email address for this patron, send 
it to the admin to deal with.
+                    if ( !$email ) {
+                        $enqueue_parameters->{'to_address'} = 
$admin_email_address;
+                    }
+                    C4::Letters::EnqueueLetter($enqueue_parameters);
+                }
+            }
+            $sth->finish;
+        }
+    }
+}
+
+if ($csvfilename) {
+    close $csv_fh;
+}
+
+=head1 INTERNAL METHODS
+
+=head2 get_branches_with_overdue_rules
+
+=cut
+
+sub get_branches_with_overdue_rules {
+    my $dbh               = C4::Context->dbh;
+    my $rqoverduebranches = $dbh->prepare("SELECT DISTINCT branchcode FROM 
overduerules WHERE delay1 IS NOT NULL");
+    $rqoverduebranches->execute;
+    my @branches = map { shift @$_ } @{ $rqoverduebranches->fetchall_arrayref 
};
+    $rqoverduebranches->finish;
+    return @branches;
+}
+
+=head2 parse_letter
+
+
+
+=cut
+
+sub parse_letter {
+    my $params = shift;
+    foreach my $required (qw( letter borrowernumber )) {
+        return unless exists $params->{$required};
+    }
+
+    if ( $params->{'substitute'} ) {
+        while ( my ( $key, $replacedby ) = each %{ $params->{'substitute'} } ) 
{
+            my $replacefield = "<<$key>>";
+
+            $params->{'letter'}->{title}   =~ s/$replacefield/$replacedby/g;
+            $params->{'letter'}->{content} =~ s/$replacefield/$replacedby/g;
+        }
+    }
+
+    C4::Letters::parseletter( $params->{'letter'}, 'borrowers', 
$params->{'borrowernumber'} );
+
+    if ( $params->{'branchcode'} ) {
+        C4::Letters::parseletter( $params->{'letter'}, 'branches', 
$params->{'branchcode'} );
+    }
+
+    if ( $params->{'biblionumber'} ) {
+        C4::Letters::parseletter( $params->{'letter'}, 'biblio',      
$params->{'biblionumber'} );
+        C4::Letters::parseletter( $params->{'letter'}, 'biblioitems', 
$params->{'biblionumber'} );
+    }
+
+    return $params->{'letter'};
+}
+
+=head2 print_letter
+
+required parameters:
+  letter
+  borrowernumber
+
+optional parameters:
+  outputformat
+
+=cut
+
+sub print_letter {
+    my $params = shift;
+
+    return unless ref $params eq 'HASH';
+
+    foreach my $required_parameter (qw( letter borrowernumber )) {
+        return unless defined $params->{$required_parameter};
+    }
+
+    if ( exists $params->{'outputformat'} && $params->{'outputformat'} eq 
'csv' ) {
+        if ($csv->combine(
+                $params->{'firstname'}, $params->{'lastname'}, 
$params->{'address1'},  $params->{'address2'}, $params->{'postcode'},
+                $params->{'city'},      $params->{'email'},    
$params->{'itemcount'}, $params->{'titles'}
+            )
+          ) {
+            print $csv_fh $csv->string, "\n";
+        } else {
+            $verbose and warn 'combine failed on argument: ' . 
$csv->error_input;
+        }
+    } else {
+        print sprintf( 'To: "%s %s" <%s>', $params->{'firstname'}, 
$params->{'lastname'}, $params->{'email'} ), "\n";
+        print "Subject: $params->{'letter'}->{'title'}\n\n";
+        print "$params->{'letter'}->{'content'}\n";
+
+        # print Data::Dumper->Dump( [ $params->{'borrowernumber'}, 
$params->{'letter'} ], [qw( borrowernumber letter )] );
+    }
+
+}
+
diff --git a/t/lib/KohaTest/Members/DebarMember.pm 
b/t/lib/KohaTest/Members/DebarMember.pm
new file mode 100644
index 0000000..9e27d66
--- /dev/null
+++ b/t/lib/KohaTest/Members/DebarMember.pm
@@ -0,0 +1,44 @@
+package KohaTest::Members::DebarMember;
+use base qw( KohaTest::Members );
+
+use strict;
+use warnings;
+
+use Test::More;
+
+use C4::Members;
+sub testing_class { 'C4::Members' };
+
+
+sub simple_usage : Test( 6 ) {
+    my $self = shift;
+
+    ok( $self->{'memberid'}, 'we have a valid memberid to test with' );
+
+    my $details = C4::Members::GetMemberDetails( $self->{'memberid'} );
+    ok(     exists $details->{'flags'},                  'member details has a 
"flags" attribute');
+    isa_ok( $details->{'flags'},                 'HASH', 'the "flags" 
attribute is a hashref');
+    ok(     ! $details->{'flags'}->{'DBARRED'},          'this member is NOT 
debarred' );
+
+    # Now, let's debar this member and see what happens
+    my $success = C4::Members::DebarMember( $self->{'memberid'} );
+
+    ok( $success, 'we were able to debar the member' );
+    
+    $details = C4::Members::GetMemberDetails( $self->{'memberid'} );
+    ok( $details->{'flags'}->{'DBARRED'},         'this member is debarred 
now' )
+      or diag( Data::Dumper->Dump( [ $details->{'flags'} ], [ 'flags' ] ) );
+}
+
+sub incorrect_usage : Test( 2 ) {
+    my $self = shift;
+
+    my $result = C4::Members::DebarMember();
+    ok( ! defined $result, 'DebarMember returns undef when passed no 
parameters' );
+
+    $result = C4::Members::DebarMember( 'this is not a borrowernumber' );
+    ok( ! defined $result, 'DebarMember returns undef when not passed a 
numeric argument' );
+
+}
+
+1;
-- 
1.5.5.GIT

_______________________________________________
Koha-patches mailing list
[email protected]
http://lists.koha.org/mailman/listinfo/koha-patches

Reply via email to