Bug 9103: overdue_notices.pl should use AutoEmailPrimaryAddress syspref
[koha.git] / misc / cronjobs / overdue_notices.pl
index 71e6a05..b265707 100755 (executable)
@@ -47,7 +47,10 @@ overdue_notices.pl - prepare messages to be sent to patrons for overdue items
 
 =head1 SYNOPSIS
 
-overdue_notices.pl [ -n ] [ -library <branchcode> ] [ -library <branchcode>...] [ -max <number of days> ] [ -csv [ <filename> ] ] [ -itemscontent <field list> ]
+overdue_notices.pl
+  [ -n ][ -library <branchcode> ][ -library <branchcode> ... ]
+  [ -max <number of days> ][ -csv [<filename>] ][ -itemscontent <field list> ]
+  [ -email <email_type> ... ]
 
  Options:
    -help                          brief help message
@@ -60,6 +63,7 @@ overdue_notices.pl [ -n ] [ -library <branchcode> ] [ -library <branchcode>...]
    -itemscontent <list of fields> item information in templates
    -borcat       <categorycode>   category code that must be included
    -borcatout    <categorycode>   category code that must be excluded
+   -email        <email_type>     type of email that will be used. Can be 'email', 'emailpro' or 'B_email'. Repeatable.
 
 =head1 OPTIONS
 
@@ -143,6 +147,14 @@ Default items.content lists only those items that fall in the
 range of the currently processing notice.
 Choose list-all to include all overdue items in the list (limited by B<-max> setting).
 
+=item B<-date>
+
+use it in order to send overdues on a specific date and not Now.
+
+=item B<-email>
+
+Allows to specify which type of email will be used. Can be email, emailpro or B_email. Repeatable.
+
 =back
 
 =head1 DESCRIPTION
@@ -252,6 +264,8 @@ my $verbose = 0;
 my $nomail  = 0;
 my $MAX     = 90;
 my @branchcodes; # Branch(es) passed as parameter
+my @emails_to_use;    # Emails to use for messaging
+my @emails;           # Emails given in command-line parameters
 my $csvfilename;
 my $htmlfilename;
 my $triggered = 0;
@@ -259,6 +273,7 @@ my $listall = 0;
 my $itemscontent = join( ',', qw( date_due title barcode author itemnumber ) );
 my @myborcat;
 my @myborcatout;
+my $date;
 
 GetOptions(
     'help|?'         => \$help,
@@ -268,12 +283,14 @@ GetOptions(
     'max=s'          => \$MAX,
     'library=s'      => \@branchcodes,
     'csv:s'          => \$csvfilename,    # this optional argument gets '' if not supplied.
-    'html:s'          => \$htmlfilename,    # this optional argument gets '' if not supplied.
+    'html:s'         => \$htmlfilename,    # this optional argument gets '' if not supplied.
     'itemscontent=s' => \$itemscontent,
-    'list-all'      => \$listall,
-    't|triggered'             => \$triggered,
-    'borcat=s'      => \@myborcat,
-    'borcatout=s'   => \@myborcatout,
+    'list-all'       => \$listall,
+    't|triggered'    => \$triggered,
+    'date'           => \$date,
+    'borcat=s'       => \@myborcat,
+    'borcatout=s'    => \@myborcatout,
+    'email=s'        => \@emails,
 ) or pod2usage(2);
 pod2usage(1) if $help;
 pod2usage( -verbose => 2 ) if $man;
@@ -320,10 +337,17 @@ if (@branchcodes) {
     }
 }
 
+if ($date){
+    $date=$dbh->quote($date);
+}
+else {
+    $date="NOW()";
+}
+
 # these are the fields that will be substituted into <<item.content>>
 my @item_content_fields = split( /,/, $itemscontent );
 
-binmode( STDOUT, ":utf8" );
+binmode( STDOUT, ':encoding(UTF-8)' );
 
 
 our $csv;       # the Text::CSV_XS object
@@ -350,8 +374,8 @@ if ( defined $htmlfilename ) {
   if ( $htmlfilename eq '' ) {
     $html_fh = *STDOUT;
   } else {
-    my $today = C4::Dates->new();
-    open $html_fh, ">",File::Spec->catdir ($htmlfilename,"notices-".$today->output('iso').".html");
+    my $today = DateTime->now(time_zone => C4::Context->tz );
+    open $html_fh, ">",File::Spec->catdir ($htmlfilename,"notices-".$today->ymd().".html");
   }
   
   print $html_fh "<html>\n";
@@ -375,14 +399,14 @@ foreach my $branchcode (@branches) {
 
     $verbose and warn sprintf "branchcode : '%s' using %s\n", $branchcode, $admin_email_address;
 
-    my $sth2 = $dbh->prepare( <<'END_SQL' );
-SELECT biblio.*, items.*, issues.*, biblioitems.itemtype, TO_DAYS(NOW())-TO_DAYS(date_due) AS days_overdue
+    my $sth2 = $dbh->prepare( <<"END_SQL" );
+SELECT biblio.*, items.*, issues.*, biblioitems.itemtype, TO_DAYS($date)-TO_DAYS(date_due) AS days_overdue
   FROM issues,items,biblio, biblioitems
   WHERE items.itemnumber=issues.itemnumber
     AND biblio.biblionumber   = items.biblionumber
     AND biblio.biblionumber   = biblioitems.biblionumber
     AND issues.borrowernumber = ?
-    AND TO_DAYS(NOW())-TO_DAYS(date_due) BETWEEN ? and ?
+    AND TO_DAYS($date)-TO_DAYS(date_due) BETWEEN ? and ?
 END_SQL
 
     my $query = "SELECT * FROM overduerules WHERE delay1 IS NOT NULL AND branchcode = ? ";
@@ -423,10 +447,10 @@ END_SQL
             # this text contains fields that are replaced by their value. Those fields must be written between brackets
             # The following fields are available :
            # itemcount is interpreted here as the number of items in the overdue range defined by the current notice or all overdues < max if(-list-all).
-            # <date> <itemcount> <firstname> <lastname> <address1> <address2> <address3> <city> <postcode>
+            # <date> <itemcount> <firstname> <lastname> <address1> <address2> <address3> <city> <postcode> <country>
 
             my $borrower_sql = <<'END_SQL';
-SELECT distinct(issues.borrowernumber), firstname, surname, address, address2, city, zipcode, country, email
+SELECT distinct(issues.borrowernumber), firstname, surname, address, address2, city, zipcode, country, email, emailpro, B_email
 FROM   issues,borrowers,categories
 WHERE  issues.borrowernumber=borrowers.borrowernumber
 AND    borrowers.categorycode=categories.categorycode
@@ -442,10 +466,10 @@ END_SQL
             }
             $borrower_sql .= '  AND categories.overduenoticerequired=1 ';
             if($triggered) {
-                $borrower_sql .= ' AND TO_DAYS(NOW())-TO_DAYS(date_due) = ?';
+                $borrower_sql .= " AND TO_DAYS($date)-TO_DAYS(date_due) = ?";
                 push @borrower_parameters, $mindays;
             } else {
-                $borrower_sql .= ' AND TO_DAYS(NOW())-TO_DAYS(date_due) BETWEEN ? and ? ' ;
+                $borrower_sql .= " AND TO_DAYS($date)-TO_DAYS(date_due) BETWEEN ? and ? " ;
                 push @borrower_parameters, $mindays, $maxdays;
             }
 
@@ -454,17 +478,34 @@ END_SQL
             $sth->execute(@borrower_parameters);
             $verbose and warn $borrower_sql . "\n $branchcode | " . $overdue_rules->{'categorycode'} . "\n ($mindays, $maxdays)\nreturns " . $sth->rows . " rows";
 
-            while ( my ( $borrowernumber, $firstname, $lastname,
-                    $address1, $address2, $city, $postcode, $country, $email
-                    ) = $sth->fetchrow )
-            {
-                $verbose and warn "borrower $firstname, $lastname ($borrowernumber) has items triggering level $i.";
-    
-                my $letter = C4::Letters::getletter( 'circulation', $overdue_rules->{"letter$i"} );
+            while ( my $data = $sth->fetchrow_hashref ) {
+                my $borrowernumber = $data->{'borrowernumber'};
+                my $borr =
+                    $data->{'firstname'} . ', '
+                  . $data->{'surname'} . ' ('
+                  . $borrowernumber . ')';
+                $verbose
+                  and warn "borrower $borr has items triggering level $i.";
+
+                @emails_to_use = ();
+                my $notice_email =
+                    C4::Members::GetNoticeEmailAddress($borrowernumber);
+                unless ($nomail) {
+                    if (@emails) {
+                        foreach (@emails) {
+                            push @emails_to_use, $data->{$_} if ( $data->{$_} );
+                        }
+                    }
+                    else {
+                        push @emails_to_use, $notice_email if ($notice_email);
+                    }
+                }
+
+                my $letter = C4::Letters::getletter( 'circulation', $overdue_rules->{"letter$i"}, $branchcode );
 
                 unless ($letter) {
                     $verbose and warn "Message '$overdue_rules->{letter$i}' content not found";
-    
+
                     # might as well skip while PERIOD, no other borrowers are going to work.
                     # FIXME : Does this mean a letter must be defined in order to trigger a debar ?
                     next PERIOD;
@@ -474,7 +515,7 @@ END_SQL
     
                     #action taken is debarring
                     C4::Members::DebarMember($borrowernumber, '9999-12-31');
-                    $verbose and warn "debarring $borrowernumber $firstname $lastname\n";
+                    $verbose and warn "debarring $borr\n";
                 }
                 my @params = ($listall ? ( $borrowernumber , 1 , $MAX ) : ( $borrowernumber, $mindays, $maxdays ));
                 $verbose and warn "STH2 PARAMS: borrowernumber = $borrowernumber, mindays = $mindays, maxdays = $maxdays";
@@ -483,22 +524,23 @@ END_SQL
                 my $titles = "";
                 my @items = ();
                 
-                my $i = 0;
+                my $j = 0;
                 my $exceededPrintNoticesMaxLines = 0;
                 while ( my $item_info = $sth2->fetchrow_hashref() ) {
-                    if ( ( !$email || $nomail ) && $PrintNoticesMaxLines && $i >= $PrintNoticesMaxLines ) {
+                    if ( ( scalar(@emails_to_use) == 0 || $nomail ) && $PrintNoticesMaxLines && $j >= $PrintNoticesMaxLines ) {
                       $exceededPrintNoticesMaxLines = 1;
                       last;
                     }
-                    $i++;
+                    $j++;
                     my @item_info = map { $_ =~ /^date|date$/ ? format_date( $item_info->{$_} ) : $item_info->{$_} || '' } @item_content_fields;
                     $titles .= join("\t", @item_info) . "\n";
                     $itemcount++;
-                    push @items, { itemnumber => $item_info->{'itemnumber'}, biblionumber => $item_info->{'biblionumber'} };
+                    push @items, $item_info;
                 }
                 $sth2->finish;
+
                 $letter = parse_letter(
-                    {   letter          => $letter,
+                    {   letter_code     => $overdue_rules->{"letter$i"},
                         borrowernumber  => $borrowernumber,
                         branchcode      => $branchcode,
                         items           => \@items,
@@ -509,6 +551,13 @@ END_SQL
                                            }
                     }
                 );
+                unless ($letter) {
+                    $verbose and warn "Message '$overdue_rules->{letter$i}' content not found";
+
+                    # might as well skip while PERIOD, no other borrowers are going to work.
+                    # FIXME : Does this mean a letter must be defined in order to trigger a debar ?
+                    next PERIOD;
+                }
                 
                 if ( $exceededPrintNoticesMaxLines ) {
                   $letter->{'content'} .= "List too long for form; please check your account online for a complete list of your overdue items.";
@@ -520,54 +569,35 @@ END_SQL
                 }
                 $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) {
-    
+
+                if ( !$nomail && scalar @emails_to_use ) {
+                    C4::Letters::EnqueueLetter(
+                        {   letter                 => $letter,
+                            borrowernumber         => $borrowernumber,
+                            message_transport_type => 'email',
+                            from_address           => $admin_email_address,
+                            to_address             => join(',', @emails_to_use),
+                        }
+                    );
+                } else {
+                    # if not sent by email then print
                     push @output_chunks,
                       prepare_letter_for_printing(
                         {   letter         => $letter,
                             borrowernumber => $borrowernumber,
-                            firstname      => $firstname,
-                            lastname       => $lastname,
-                            address1       => $address1,
-                            address2       => $address2,
-                            city           => $city,
-                            postcode       => $postcode,
-                            email          => $email,
+                            firstname      => $data->{'firstname'},
+                            lastname       => $data->{'surname'},
+                            address1       => $data->{'address'},
+                            address2       => $data->{'address2'},
+                            city           => $data->{'city'},
+                            postcode       => $data->{'zipcode'},
+                            country        => $data->{'country'},
+                            email          => $notice_email,
                             itemcount      => $itemcount,
                             titles         => $titles,
                             outputformat   => defined $csvfilename ? 'csv' : defined $htmlfilename ? 'html' : '',
                         }
                       );
-                } else {
-                    if ($email) {
-                        C4::Letters::EnqueueLetter(
-                            {   letter                 => $letter,
-                                borrowernumber         => $borrowernumber,
-                                message_transport_type => 'email',
-                                from_address           => $admin_email_address,
-                            }
-                        );
-                    } else {
-    
-                        # If we don't have an email address for this patron, send it to the admin to deal with.
-                        push @output_chunks,
-                          prepare_letter_for_printing(
-                            {   letter         => $letter,
-                                borrowernumber => $borrowernumber,
-                                firstname      => $firstname,
-                                lastname       => $lastname,
-                                address1       => $address1,
-                                address2       => $address2,
-                                city           => $city,
-                                postcode       => $postcode,
-                                email          => $email,
-                                itemcount      => $itemcount,
-                                titles         => $titles,
-                                outputformat   => defined $csvfilename ? 'csv' : defined $htmlfilename ? 'html' : '',
-                            }
-                          );
-                    }
                 }
             }
             $sth->finish;
@@ -586,7 +616,7 @@ END_SQL
                 print @output_chunks;
         }
         # Generate the content of the csv with headers
-        my $content = join(";", qw(title name surname address1 address2 zipcode city email itemcount itemsinfo due_date issue_date)) . "\n";
+        my $content = join(";", qw(title name surname address1 address2 zipcode city country email itemcount itemsinfo due_date issue_date)) . "\n";
         $content .= join( "\n", @output_chunks );
             
         my $attachment = {
@@ -643,54 +673,58 @@ substituted keys and values.
 
 =cut
 
-sub parse_letter { # FIXME: this code should probably be moved to C4::Letters:parseletter
+sub parse_letter {
     my $params = shift;
-    foreach my $required (qw( letter borrowernumber )) {
-        return unless exists $params->{$required};
+    foreach my $required (qw( letter_code borrowernumber )) {
+        return unless ( exists $params->{$required} && $params->{$required} );
     }
 
-   my $todaysdate = C4::Dates->new()->output("syspref");
-   $params->{'letter'}->{title}   =~ s/<<today>>/$todaysdate/g;
-   $params->{'letter'}->{content} =~ s/<<today>>/$todaysdate/g;
+    my $substitute = $params->{'substitute'} || {};
+    $substitute->{today} ||= C4::Dates->new()->output("syspref");
 
-    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;
-        }
+    my %tables = ( 'borrowers' => $params->{'borrowernumber'} );
+    if ( my $p = $params->{'branchcode'} ) {
+        $tables{'branches'} = $p;
     }
 
-    $params->{'letter'} = C4::Letters::parseletter( $params->{'letter'}, 'borrowers', $params->{'borrowernumber'} );
-
-    if ( $params->{'branchcode'} ) {
-        $params->{'letter'} = C4::Letters::parseletter( $params->{'letter'}, 'branches', $params->{'branchcode'} );
+    my $currency_format;
+    if ( defined $params->{'letter'}->{'content'}
+        and $params->{'letter'}->{'content'} =~ m/<fine>(.*)<\/fine>/o )
+    {    # process any fine tags...
+        $currency_format = $1;
+        $params->{'letter'}->{'content'} =~ s/<fine>.*<\/fine>/<<item.fine>>/o;
     }
 
-    if ( $params->{'items'} ) {
+    my @item_tables;
+    if ( my $i = $params->{'items'} ) {
         my $item_format = '';
-        PROCESS_ITEMS:
-        while (scalar(@{$params->{'items'}}) > 0) {
-            my $item = shift @{$params->{'items'}};
+        foreach my $item (@$i) {
             my $fine = GetFine($item->{'itemnumber'}, $params->{'borrowernumber'});
-            if (!$item_format) {
+            if ( !$item_format and defined $params->{'letter'}->{'content'} ) {
                 $params->{'letter'}->{'content'} =~ m/(<item>.*<\/item>)/;
                 $item_format = $1;
             }
-            if ($params->{'letter'}->{'content'} =~ m/<fine>(.*)<\/fine>/) { # process any fine tags...
-                my $formatted_fine = currency_format("$1", "$fine", FMT_SYMBOL);
-                $params->{'letter'}->{'content'} =~ s/<fine>.*<\/fine>/$formatted_fine/;
-            }
-            $params->{'letter'} = C4::Letters::parseletter( $params->{'letter'}, 'biblio',      $item->{'biblionumber'} );
-            $params->{'letter'} = C4::Letters::parseletter( $params->{'letter'}, 'biblioitems', $item->{'biblionumber'} );
-            $params->{'letter'} = C4::Letters::parseletter( $params->{'letter'}, 'items', $item->{'itemnumber'} );
-            $params->{'letter'} = C4::Letters::parseletter( $params->{'letter'}, 'issues', $item->{'itemnumber'} );
-            $params->{'letter'}->{'content'} =~ s/(<item>.*<\/item>)/$1\n$item_format/ if scalar(@{$params->{'items'}} > 0);
 
+            $item->{'fine'} = currency_format($currency_format, "$fine", FMT_SYMBOL)
+              if $currency_format;
+
+            push @item_tables, {
+                'biblio' => $item->{'biblionumber'},
+                'biblioitems' => $item->{'biblionumber'},
+                'items' => $item,
+                'issues' => $item->{'itemnumber'},
+            };
         }
     }
-    $params->{'letter'}->{'content'} =~ s/<\/{0,1}?item>//g; # strip all remaining item tags...
-    return $params->{'letter'};
+
+    return C4::Letters::GetPreparedLetter (
+        module => 'circulation',
+        letter_code => $params->{'letter_code'},
+        branchcode => $params->{'branchcode'},
+        tables => \%tables,
+        substitute => $substitute,
+        repeat => { item => \@item_tables },
+    );
 }
 
 =head2 prepare_letter_for_printing
@@ -722,7 +756,7 @@ sub prepare_letter_for_printing {
     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'}
+                $params->{'city'}, $params->{'country'}, $params->{'email'}, $params->{'itemcount'}, $params->{'titles'}
             )
           ) {
             return $csv->string, "\n";