Merge remote-tracking branch 'origin/new/bug_8408'
[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 Koha::DateUtils;
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 $verbose     = 0;                                                # -v: verbose
67 my $itemscontent = join(',',qw( issuedate title barcode author ));
68
69 GetOptions( 'c'              => \$confirm,
70             'n'              => \$nomail,
71             'm:i'            => \$maxdays,
72             'v'              => \$verbose,
73             'itemscontent=s' => \$itemscontent,
74        );
75 my $usage = << 'ENDUSAGE';
76
77 This script prepares pre-due and item due reminders to be sent to
78 patrons. It queues them in the message queue, which is processed by
79 the process_message_queue.pl cronjob.
80 See the comments in the script for directions on changing the script.
81 This script has the following parameters :
82     -c Confirm and remove this help & warning
83     -m maximum number of days in advance to send advance notices.
84     -n send No mail. Instead, all mail messages are printed on screen. Usefull for testing purposes.
85     -v verbose
86 ENDUSAGE
87
88 # Since advance notice options are not visible in the web-interface
89 # unless EnhancedMessagingPreferences is on, let the user know that
90 # this script probably isn't going to do much
91 if ( ! C4::Context->preference('EnhancedMessagingPreferences') ) {
92     warn <<'END_WARN';
93
94 The "EnhancedMessagingPreferences" syspref is off.
95 Therefore, it is unlikely that this script will actually produce any messages to be sent.
96 To change this, edit the "EnhancedMessagingPreferences" syspref.
97
98 END_WARN
99 }
100
101 unless ($confirm) {
102     print $usage;
103     print "Do you wish to continue? (y/n)";
104     chomp($_ = <STDIN>);
105     exit unless (/^y/i);
106         
107 }
108
109 # The fields that will be substituted into <<items.content>>
110 my @item_content_fields = split(/,/,$itemscontent);
111
112 warn 'getting upcoming due issues' if $verbose;
113 my $upcoming_dues = C4::Circulation::GetUpcomingDueIssues( { days_in_advance => $maxdays } );
114 warn 'found ' . scalar( @$upcoming_dues ) . ' issues' if $verbose;
115
116 # hash of borrowernumber to number of items upcoming
117 # for patrons wishing digests only.
118 my $upcoming_digest;
119 my $due_digest;
120
121 my $dbh = C4::Context->dbh();
122 my $sth = $dbh->prepare(<<'END_SQL');
123 SELECT biblio.*, items.*, issues.*
124   FROM issues,items,biblio
125   WHERE items.itemnumber=issues.itemnumber
126     AND biblio.biblionumber=items.biblionumber
127     AND issues.borrowernumber = ?
128     AND issues.itemnumber = ?
129     AND (TO_DAYS(date_due)-TO_DAYS(NOW()) = ?)
130 END_SQL
131
132 my $admin_adress = C4::Context->preference('KohaAdminEmailAddress');
133
134 UPCOMINGITEM: foreach my $upcoming ( @$upcoming_dues ) {
135     warn 'examining ' . $upcoming->{'itemnumber'} . ' upcoming due items' if $verbose;
136     # warn( Data::Dumper->Dump( [ $upcoming ], [ 'overdue' ] ) );
137
138     my $from_address = $upcoming->{branchemail} || $admin_adress;
139
140     my $letter;
141     my $borrower_preferences;
142     if ( 0 == $upcoming->{'days_until_due'} ) {
143         # This item is due today. Send an 'item due' message.
144         $borrower_preferences = C4::Members::Messaging::GetMessagingPreferences( { borrowernumber => $upcoming->{'borrowernumber'},
145                                                                                    message_name   => 'item_due' } );
146         # warn( Data::Dumper->Dump( [ $borrower_preferences ], [ 'borrower_preferences' ] ) );
147         next unless $borrower_preferences;
148         
149         if ( $borrower_preferences->{'wants_digest'} ) {
150             # cache this one to process after we've run through all of the items.
151             my $digest = $due_digest->{$upcoming->{'borrowernumber'}} ||= {};
152             $digest->{email} ||= $from_address;
153             $digest->{count}++;
154         } else {
155             my $biblio = C4::Biblio::GetBiblioFromItemNumber( $upcoming->{'itemnumber'} );
156             my $letter_type = 'DUE';
157             $sth->execute($upcoming->{'borrowernumber'},$upcoming->{'itemnumber'},'0');
158             my $titles = "";
159             while ( my $item_info = $sth->fetchrow_hashref()) {
160               my @item_info = map { $_ =~ /^date|date$/ ? format_date($item_info->{$_}) : $item_info->{$_} || '' } @item_content_fields;
161               $titles .= join("\t",@item_info) . "\n";
162             }
163
164             ## Get branch info for borrowers home library.
165             $letter = parse_letter( { letter_code    => $letter_type,
166                                       borrowernumber => $upcoming->{'borrowernumber'},
167                                       branchcode     => $upcoming->{'branchcode'},
168                                       biblionumber   => $biblio->{'biblionumber'},
169                                       itemnumber     => $upcoming->{'itemnumber'},
170                                       substitute     => { 'items.content' => $titles }
171                                     } )
172               or die "no letter of type '$letter_type' found. Please see sample_notices.sql";
173         }
174     } else {
175         $borrower_preferences = C4::Members::Messaging::GetMessagingPreferences( { borrowernumber => $upcoming->{'borrowernumber'},
176                                                                                    message_name   => 'advance_notice' } );
177         # warn( Data::Dumper->Dump( [ $borrower_preferences ], [ 'borrower_preferences' ] ) );
178         next UPCOMINGITEM unless $borrower_preferences && exists $borrower_preferences->{'days_in_advance'};
179         next UPCOMINGITEM unless $borrower_preferences->{'days_in_advance'} == $upcoming->{'days_until_due'};
180
181         if ( $borrower_preferences->{'wants_digest'} ) {
182             # cache this one to process after we've run through all of the items.
183             my $digest = $upcoming_digest->{$upcoming->{'borrowernumber'}} ||= {};
184             $digest->{email} ||= $from_address;
185             $digest->{count}++;
186         } else {
187             my $biblio = C4::Biblio::GetBiblioFromItemNumber( $upcoming->{'itemnumber'} );
188             my $letter_type = 'PREDUE';
189             $sth->execute($upcoming->{'borrowernumber'},$upcoming->{'itemnumber'},$borrower_preferences->{'days_in_advance'});
190             my $titles = "";
191             while ( my $item_info = $sth->fetchrow_hashref()) {
192               my @item_info = map { $_ =~ /^date|date$/ ? format_date($item_info->{$_}) : $item_info->{$_} || '' } @item_content_fields;
193               $titles .= join("\t",@item_info) . "\n";
194             }
195
196             ## Get branch info for borrowers home library.
197             $letter = parse_letter( { letter_code    => $letter_type,
198                                       borrowernumber => $upcoming->{'borrowernumber'},
199                                       branchcode     => $upcoming->{'branchcode'},
200                                       biblionumber   => $biblio->{'biblionumber'},
201                                       itemnumber     => $upcoming->{'itemnumber'},
202                                       substitute     => { 'items.content' => $titles }
203                                     } )
204               or die "no letter of type '$letter_type' found. Please see sample_notices.sql";
205         }
206     }
207
208     # If we have prepared a letter, send it.
209     if ($letter) {
210       if ($nomail) {
211         local $, = "\f";
212         print $letter->{'content'};
213       }
214       else {
215         foreach my $transport ( keys %{$borrower_preferences->{'transports'}} ) {
216             C4::Letters::EnqueueLetter( { letter                 => $letter,
217                                           borrowernumber         => $upcoming->{'borrowernumber'},
218                                           from_address           => $from_address,
219                                           message_transport_type => $transport } );
220         }
221       }
222     }
223 }
224
225
226 # warn( Data::Dumper->Dump( [ $upcoming_digest ], [ 'upcoming_digest' ] ) );
227
228 # Now, run through all the people that want digests and send them
229
230 $sth = $dbh->prepare(<<'END_SQL');
231 SELECT biblio.*, items.*, issues.*
232   FROM issues,items,biblio
233   WHERE items.itemnumber=issues.itemnumber
234     AND biblio.biblionumber=items.biblionumber
235     AND issues.borrowernumber = ?
236     AND (TO_DAYS(date_due)-TO_DAYS(NOW()) = ?)
237 END_SQL
238
239 PATRON: while ( my ( $borrowernumber, $digest ) = each %$upcoming_digest ) {
240     my $count = $digest->{count};
241     my $from_address = $digest->{email};
242
243     my $borrower_preferences = C4::Members::Messaging::GetMessagingPreferences( { borrowernumber => $borrowernumber,
244                                                                                   message_name   => 'advance_notice' } );
245     # warn( Data::Dumper->Dump( [ $borrower_preferences ], [ 'borrower_preferences' ] ) );
246     next PATRON unless $borrower_preferences; # how could this happen?
247
248
249     my $letter_type = 'PREDUEDGST';
250
251     $sth->execute($borrowernumber,$borrower_preferences->{'days_in_advance'});
252     my $titles = "";
253     while ( my $item_info = $sth->fetchrow_hashref()) {
254       my @item_info = map { $_ =~ /^date|date$/ ? format_date($item_info->{$_}) : $item_info->{$_} || '' } @item_content_fields;
255       $titles .= join("\t",@item_info) . "\n";
256     }
257
258     ## Get branch info for borrowers home library.
259     my %branch_info = get_branch_info( $borrowernumber );
260
261     my $letter = parse_letter( { letter_code    => $letter_type,
262                               borrowernumber => $borrowernumber,
263                               substitute     => { count => $count,
264                                                   'items.content' => $titles,
265                                                   %branch_info,
266                                                 }
267                          } )
268       or die "no letter of type '$letter_type' found. Please see sample_notices.sql";
269     if ($nomail) {
270       local $, = "\f";
271       print $letter->{'content'};
272     }
273     else {
274       foreach my $transport ( keys %{$borrower_preferences->{'transports'}} ) {
275         C4::Letters::EnqueueLetter( { letter                 => $letter,
276                                       borrowernumber         => $borrowernumber,
277                                       from_address           => $from_address,
278                                       message_transport_type => $transport } );
279       }
280     }
281 }
282
283 # Now, run through all the people that want digests and send them
284 PATRON: while ( my ( $borrowernumber, $digest ) = each %$due_digest ) {
285     my $count = $digest->{count};
286     my $from_address = $digest->{email};
287
288     my $borrower_preferences = C4::Members::Messaging::GetMessagingPreferences( { borrowernumber => $borrowernumber,
289                                                                                   message_name   => 'item_due' } );
290     # warn( Data::Dumper->Dump( [ $borrower_preferences ], [ 'borrower_preferences' ] ) );
291     next PATRON unless $borrower_preferences; # how could this happen?
292
293     my $letter_type = 'DUEDGST';
294     $sth->execute($borrowernumber,'0');
295     my $titles = "";
296     while ( my $item_info = $sth->fetchrow_hashref()) {
297       my @item_info = map { $_ =~ /^date|date$/ ? format_date($item_info->{$_}) : $item_info->{$_} || '' } @item_content_fields;
298       $titles .= join("\t",@item_info) . "\n";
299     }
300
301     ## Get branch info for borrowers home library.
302     my %branch_info = get_branch_info( $borrowernumber );
303
304     my $letter = parse_letter( { letter_code    => $letter_type,
305                               borrowernumber => $borrowernumber,
306                               substitute     => { count => $count,
307                                                   'items.content' => $titles,
308                                                   %branch_info,
309                                                 }
310                          } )
311       or die "no letter of type '$letter_type' found. Please see sample_notices.sql";
312
313     if ($nomail) {
314       local $, = "\f";
315       print $letter->{'content'};
316     }
317     else {
318       foreach my $transport ( keys %{$borrower_preferences->{'transports'}} ) {
319         C4::Letters::EnqueueLetter( { letter                 => $letter,
320                                       borrowernumber         => $borrowernumber,
321                                       from_address           => $from_address,
322                                       message_transport_type => $transport } );
323       }
324     }
325 }
326
327 =head1 METHODS
328
329 =head2 parse_letter
330
331 =cut
332
333 sub parse_letter {
334     my $params = shift;
335     foreach my $required ( qw( letter_code borrowernumber ) ) {
336         return unless exists $params->{$required};
337     }
338
339     my %table_params = ( 'borrowers' => $params->{'borrowernumber'} );
340
341     if ( my $p = $params->{'branchcode'} ) {
342         $table_params{'branches'} = $p;
343     }
344     if ( my $p = $params->{'itemnumber'} ) {
345         $table_params{'issues'} = $p;
346         $table_params{'items'} = $p;
347     }
348     if ( my $p = $params->{'biblionumber'} ) {
349         $table_params{'biblio'} = $p;
350         $table_params{'biblioitems'} = $p;
351     }
352
353     return C4::Letters::GetPreparedLetter (
354         module => 'circulation',
355         letter_code => $params->{'letter_code'},
356         branchcode => $table_params{'branches'},
357         substitute => $params->{'substitute'},
358         tables     => \%table_params,
359     );
360 }
361
362 sub format_date {
363     my $date_string = shift;
364     my $dt=dt_from_string($date_string);
365     return output_pref($dt);
366 }
367
368 =head2 get_branch_info
369
370 =cut
371
372 sub get_branch_info {
373     my ( $borrowernumber ) = @_;
374
375     ## Get branch info for borrowers home library.
376     my $borrower_details = C4::Members::GetMember( borrowernumber => $borrowernumber );
377     my $borrower_branchcode = $borrower_details->{'branchcode'};
378     my $branch = C4::Branch::GetBranchDetail( $borrower_branchcode );
379     my %branch_info;
380     foreach my $key( keys %$branch ) {
381         $branch_info{"branches.$key"} = $branch->{$key};
382     }
383
384     return %branch_info;
385 }
386
387 1;
388
389 __END__