Bug 2505 - remove unnecessary -w and replace with use warnings;
[koha.git] / misc / cronjobs / advance_notices.pl
1 #!/usr/bin/perl
2
3 # Copyright 2008 LibLime
4 #
5 # This file is part of Koha.
6 #
7 # Koha is free software; you can redistribute it and/or modify it under the
8 # terms of the GNU General Public License as published by the Free Software
9 # Foundation; either version 2 of the License, or (at your option) any later
10 # version.
11 #
12 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License along
17 # with Koha; if not, write to the Free Software Foundation, Inc.,
18 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20 =head1 NAME
21
22 advance_notices.pl - cron script to put item due reminders into message queue
23
24 =head1 SYNOPSIS
25
26 ./advance_notices.pl -c
27
28 or, in crontab:
29 0 1 * * * advance_notices.pl -c
30
31 =head1 DESCRIPTION
32
33 This script prepares pre-due and item due reminders to be sent to
34 patrons. It queues them in the message queue, which is processed by
35 the process_message_queue.pl cronjob. The type and timing of the
36 messages can be configured by the patrons in their "My Alerts" tab in
37 the OPAC.
38
39 =cut
40
41 use strict;
42 use warnings;
43 use Getopt::Long;
44 use Data::Dumper;
45 BEGIN {
46     # find Koha's Perl modules
47     # test carefully before changing this
48     use FindBin;
49     eval { require "$FindBin::Bin/../kohalib.pl" };
50 }
51 use C4::Biblio;
52 use C4::Context;
53 use C4::Letters;
54 use C4::Members;
55 use C4::Members::Messaging;
56 use C4::Overdues;
57 use C4::Dates qw/format_date/;
58
59
60 # These are defaults for command line options.
61 my $confirm;                                                        # -c: Confirm that the user has read and configured this script.
62 # my $confirm     = 1;                                                # -c: Confirm that the user has read and configured this script.
63 my $nomail;                                                         # -n: No mail. Will not send any emails.
64 my $mindays     = 0;                                                # -m: Maximum number of days in advance to send notices
65 my $maxdays     = 30;                                               # -e: the End of the time period
66 my $fromaddress = C4::Context->preference('KohaAdminEmailAddress'); # -f: From address for the emails
67 my $verbose     = 0;                                                # -v: verbose
68 my $itemscontent = join(',',qw( issuedate title barcode author ));
69
70 GetOptions( 'c'              => \$confirm,
71             'n'              => \$nomail,
72             'm:i'            => \$maxdays,
73             'f:s'            => \$fromaddress,
74             'v'              => \$verbose,
75             'itemscontent=s' => \$itemscontent,
76        );
77 my $usage = << 'ENDUSAGE';
78
79 This script prepares pre-due and item due reminders to be sent to
80 patrons. It queues them in the message queue, which is processed by
81 the process_message_queue.pl cronjob.
82 See the comments in the script for directions on changing the script.
83 This script has the following parameters :
84         -c Confirm and remove this help & warning
85         -m maximum number of days in advance to send advance notices.
86         -f from address for the emails. Defaults to KohaAdminEmailAddress system preference
87         -n send No mail. Instead, all mail messages are printed on screen. Usefull for testing purposes.
88         -v verbose
89         -i csv list of fields that get substituted into templates in places
90            of the E<lt>E<lt>items.contentE<gt>E<gt> placeholder.  Defaults to
91            issuedate,title,barcode,author
92 ENDUSAGE
93
94 # Since advance notice options are not visible in the web-interface
95 # unless EnhancedMessagingPreferences is on, let the user know that
96 # this script probably isn't going to do much
97 if ( ! C4::Context->preference('EnhancedMessagingPreferences') ) {
98     warn <<'END_WARN';
99
100 The "EnhancedMessagingPreferences" syspref is off.
101 Therefore, it is unlikely that this script will actually produce any messages to be sent.
102 To change this, edit the "EnhancedMessagingPreferences" syspref.
103
104 END_WARN
105 }
106
107 unless ($confirm) {
108     print $usage;
109     print "Do you wish to continue? (y/n)";
110     chomp($_ = <STDIN>);
111     exit unless (/^y/i);
112         
113 }
114
115 # The fields that will be substituted into <<items.content>>
116 my @item_content_fields = split(/,/,$itemscontent);
117
118 warn 'getting upcoming due issues' if $verbose;
119 my $upcoming_dues = C4::Circulation::GetUpcomingDueIssues( { days_in_advance => $maxdays } );
120 warn 'found ' . scalar( @$upcoming_dues ) . ' issues' if $verbose;
121
122 # hash of borrowernumber to number of items upcoming
123 # for patrons wishing digests only.
124 my $upcoming_digest;
125 my $due_digest;
126
127 my $dbh = C4::Context->dbh();
128 my $sth = $dbh->prepare(<<'END_SQL');
129 SELECT biblio.*, items.*, issues.*
130   FROM issues,items,biblio
131   WHERE items.itemnumber=issues.itemnumber
132     AND biblio.biblionumber=items.biblionumber
133     AND issues.borrowernumber = ?
134     AND issues.itemnumber = ?
135     AND (TO_DAYS(date_due)-TO_DAYS(NOW()) = ?)
136 END_SQL
137
138 UPCOMINGITEM: foreach my $upcoming ( @$upcoming_dues ) {
139     warn 'examining ' . $upcoming->{'itemnumber'} . ' upcoming due items' if $verbose;
140     # warn( Data::Dumper->Dump( [ $upcoming ], [ 'overdue' ] ) );
141
142     my $letter;
143     my $borrower_preferences;
144     if ( 0 == $upcoming->{'days_until_due'} ) {
145         # This item is due today. Send an 'item due' message.
146         $borrower_preferences = C4::Members::Messaging::GetMessagingPreferences( { borrowernumber => $upcoming->{'borrowernumber'},
147                                                                                    message_name   => 'item due' } );
148         # warn( Data::Dumper->Dump( [ $borrower_preferences ], [ 'borrower_preferences' ] ) );
149         next DUEITEM unless $borrower_preferences;
150         
151         if ( $borrower_preferences->{'wants_digest'} ) {
152             # cache this one to process after we've run through all of the items.
153             $due_digest->{$upcoming->{'borrowernumber'}}++;
154         } else {
155             my $biblio = C4::Biblio::GetBiblioFromItemNumber( $upcoming->{'itemnumber'} );
156             my $letter_type = 'DUE';
157             $letter = C4::Letters::getletter( 'circulation', $letter_type );
158             die "no letter of type '$letter_type' found. Please see sample_notices.sql" unless $letter;
159             $sth->execute($upcoming->{'borrowernumber'},$upcoming->{'itemnumber'},'0');
160             my $titles = "";
161             while ( my $item_info = $sth->fetchrow_hashref()) {
162               my @item_info = map { $_ =~ /^date|date$/ ? format_date($item_info->{$_}) : $item_info->{$_} || '' } @item_content_fields;
163               $titles .= join("\t",@item_info) . "\n";
164             }
165         
166             $letter = parse_letter( { letter         => $letter,
167                                       borrowernumber => $upcoming->{'borrowernumber'},
168                                       branchcode     => $upcoming->{'branchcode'},
169                                       biblionumber   => $biblio->{'biblionumber'},
170                                       substitute     => { 'items.content' => $titles }
171                                     } );
172         }
173     } else {
174         $borrower_preferences = C4::Members::Messaging::GetMessagingPreferences( { borrowernumber => $upcoming->{'borrowernumber'},
175                                                                                    message_name   => 'advance notice' } );
176         # warn( Data::Dumper->Dump( [ $borrower_preferences ], [ 'borrower_preferences' ] ) );
177         next UPCOMINGITEM unless $borrower_preferences && exists $borrower_preferences->{'days_in_advance'};
178         next UPCOMINGITEM unless $borrower_preferences->{'days_in_advance'} == $upcoming->{'days_until_due'};
179
180         if ( $borrower_preferences->{'wants_digest'} ) {
181             # cache this one to process after we've run through all of the items.
182             $upcoming_digest->{$upcoming->{'borrowernumber'}}++;
183         } else {
184             my $biblio = C4::Biblio::GetBiblioFromItemNumber( $upcoming->{'itemnumber'} );
185             my $letter_type = 'PREDUE';
186             $letter = C4::Letters::getletter( 'circulation', $letter_type );
187             die "no letter of type '$letter_type' found. Please see sample_notices.sql" unless $letter;
188             $sth->execute($upcoming->{'borrowernumber'},$upcoming->{'itemnumber'},$maxdays);
189             my $titles = "";
190             while ( my $item_info = $sth->fetchrow_hashref()) {
191               my @item_info = map { $_ =~ /^date|date$/ ? format_date($item_info->{$_}) : $item_info->{$_} || '' } @item_content_fields;
192               $titles .= join("\t",@item_info) . "\n";
193             }
194         
195             $letter = parse_letter( { letter         => $letter,
196                                       borrowernumber => $upcoming->{'borrowernumber'},
197                                       branchcode     => $upcoming->{'branchcode'},
198                                       biblionumber   => $biblio->{'biblionumber'},
199                                       substitute     => { 'items.content' => $titles }
200                                     } );
201         }
202     }
203
204     # If we have prepared a letter, send it.
205     if ($letter) {
206       if ($nomail) {
207         local $, = "\f";
208         print $letter->{'content'};
209       }
210       else {
211         foreach my $transport ( @{$borrower_preferences->{'transports'}} ) {
212             C4::Letters::EnqueueLetter( { letter                 => $letter,
213                                           borrowernumber         => $upcoming->{'borrowernumber'},
214                                           message_transport_type => $transport } );
215         }
216       }
217     }
218 }
219
220
221 # warn( Data::Dumper->Dump( [ $upcoming_digest ], [ 'upcoming_digest' ] ) );
222
223 # Now, run through all the people that want digests and send them
224
225 $sth = $dbh->prepare(<<'END_SQL');
226 SELECT biblio.*, items.*, issues.*
227   FROM issues,items,biblio
228   WHERE items.itemnumber=issues.itemnumber
229     AND biblio.biblionumber=items.biblionumber
230     AND issues.borrowernumber = ?
231     AND (TO_DAYS(date_due)-TO_DAYS(NOW()) = ?)
232 END_SQL
233
234 PATRON: while ( my ( $borrowernumber, $count ) = each %$upcoming_digest ) {
235     my $borrower_preferences = C4::Members::Messaging::GetMessagingPreferences( { borrowernumber => $borrowernumber,
236                                                                                   message_name   => 'advance notice' } );
237     # warn( Data::Dumper->Dump( [ $borrower_preferences ], [ 'borrower_preferences' ] ) );
238     next PATRON unless $borrower_preferences; # how could this happen?
239
240
241     my $letter_type = 'PREDUEDGST';
242     my $letter = C4::Letters::getletter( 'circulation', $letter_type );
243     die "no letter of type '$letter_type' found. Please see sample_notices.sql" unless $letter;
244     $sth->execute($borrowernumber,$maxdays);
245     my $titles = "";
246     while ( my $item_info = $sth->fetchrow_hashref()) {
247       my @item_info = map { $_ =~ /^date|date$/ ? format_date($item_info->{$_}) : $item_info->{$_} || '' } @item_content_fields;
248       $titles .= join("\t",@item_info) . "\n";
249     }
250     $letter = parse_letter( { letter         => $letter,
251                               borrowernumber => $borrowernumber,
252                               substitute     => { count => $count,
253                                                   'items.content' => $titles
254                                                 }
255                          } );
256     if ($nomail) {
257       local $, = "\f";
258       print $letter->{'content'};
259     }
260     else {
261       foreach my $transport ( @{$borrower_preferences->{'transports'}} ) {
262         C4::Letters::EnqueueLetter( { letter                 => $letter,
263                                       borrowernumber         => $borrowernumber,
264                                       message_transport_type => $transport } );
265       }
266     }
267 }
268
269 # Now, run through all the people that want digests and send them
270 PATRON: while ( my ( $borrowernumber, $count ) = each %$due_digest ) {
271     my $borrower_preferences = C4::Members::Messaging::GetMessagingPreferences( { borrowernumber => $borrowernumber,
272                                                                                   message_name   => 'item due' } );
273     # warn( Data::Dumper->Dump( [ $borrower_preferences ], [ 'borrower_preferences' ] ) );
274     next PATRON unless $borrower_preferences; # how could this happen?
275
276     my $letter_type = 'DUEDGST';
277     my $letter = C4::Letters::getletter( 'circulation', $letter_type );
278     die "no letter of type '$letter_type' found. Please see sample_notices.sql" unless $letter;
279     $sth->execute($borrowernumber,'0');
280     my $titles = "";
281     while ( my $item_info = $sth->fetchrow_hashref()) {
282       my @item_info = map { $_ =~ /^date|date$/ ? format_date($item_info->{$_}) : $item_info->{$_} || '' } @item_content_fields;
283       $titles .= join("\t",@item_info) . "\n";
284     }
285     $letter = parse_letter( { letter         => $letter,
286                               borrowernumber => $borrowernumber,
287                               substitute     => { count => $count,
288                                                   'items.content' => $titles
289                                                 }
290                          } );
291
292     if ($nomail) {
293       local $, = "\f";
294       print $letter->{'content'};
295     }
296     else {
297       foreach my $transport ( @{$borrower_preferences->{'transports'}} ) {
298         C4::Letters::EnqueueLetter( { letter                 => $letter,
299                                       borrowernumber         => $borrowernumber,
300                                       message_transport_type => $transport } );
301       }
302     }
303 }
304
305 =head1 METHODS
306
307 =head2 parse_letter
308
309
310
311 =cut
312
313 sub parse_letter {
314     my $params = shift;
315     foreach my $required ( qw( letter borrowernumber ) ) {
316         return unless exists $params->{$required};
317     }
318
319     if ( $params->{'substitute'} ) {
320         while ( my ($key, $replacedby) = each %{$params->{'substitute'}} ) {
321             my $replacefield = "<<$key>>";
322             
323             $params->{'letter'}->{title}   =~ s/$replacefield/$replacedby/g;
324             $params->{'letter'}->{content} =~ s/$replacefield/$replacedby/g;
325         }
326     }
327
328     C4::Letters::parseletter( $params->{'letter'}, 'borrowers',   $params->{'borrowernumber'} );
329
330     if ( $params->{'branchcode'} ) {
331         C4::Letters::parseletter( $params->{'letter'}, 'branches',    $params->{'branchcode'} );
332     }
333     
334     if ( $params->{'biblionumber'} ) {
335         C4::Letters::parseletter( $params->{'letter'}, 'biblio',      $params->{'biblionumber'} );
336         C4::Letters::parseletter( $params->{'letter'}, 'biblioitems', $params->{'biblionumber'} );
337     }
338
339     return $params->{'letter'};
340 }
341
342 1;
343
344 __END__