Talking Tech Support - Phase I - Followup - Fix Messaging Preferences
[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             $letter = parse_letter( { letter_code    => $letter_type,
165                                       borrowernumber => $upcoming->{'borrowernumber'},
166                                       branchcode     => $upcoming->{'branchcode'},
167                                       biblionumber   => $biblio->{'biblionumber'},
168                                       itemnumber     => $upcoming->{'itemnumber'},
169                                       substitute     => { 'items.content' => $titles }
170                                     } )
171               or die "no letter of type '$letter_type' found. Please see sample_notices.sql";
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             my $digest = $upcoming_digest->{$upcoming->{'borrowernumber'}} ||= {};
183             $digest->{email} ||= $from_address;
184             $digest->{count}++;
185         } else {
186             my $biblio = C4::Biblio::GetBiblioFromItemNumber( $upcoming->{'itemnumber'} );
187             my $letter_type = 'PREDUE';
188             $sth->execute($upcoming->{'borrowernumber'},$upcoming->{'itemnumber'},$borrower_preferences->{'days_in_advance'});
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_code    => $letter_type,
196                                       borrowernumber => $upcoming->{'borrowernumber'},
197                                       branchcode     => $upcoming->{'branchcode'},
198                                       biblionumber   => $biblio->{'biblionumber'},
199                                       itemnumber     => $upcoming->{'itemnumber'},
200                                       substitute     => { 'items.content' => $titles }
201                                     } )
202               or die "no letter of type '$letter_type' found. Please see sample_notices.sql";
203         }
204     }
205
206     # If we have prepared a letter, send it.
207     if ($letter) {
208       if ($nomail) {
209         local $, = "\f";
210         print $letter->{'content'};
211       }
212       else {
213         foreach my $transport ( keys %{$borrower_preferences->{'transports'}} ) {
214             C4::Letters::EnqueueLetter( { letter                 => $letter,
215                                           borrowernumber         => $upcoming->{'borrowernumber'},
216                                           from_address           => $from_address,
217                                           message_transport_type => $transport } );
218         }
219       }
220     }
221 }
222
223
224 # warn( Data::Dumper->Dump( [ $upcoming_digest ], [ 'upcoming_digest' ] ) );
225
226 # Now, run through all the people that want digests and send them
227
228 $sth = $dbh->prepare(<<'END_SQL');
229 SELECT biblio.*, items.*, issues.*
230   FROM issues,items,biblio
231   WHERE items.itemnumber=issues.itemnumber
232     AND biblio.biblionumber=items.biblionumber
233     AND issues.borrowernumber = ?
234     AND (TO_DAYS(date_due)-TO_DAYS(NOW()) = ?)
235 END_SQL
236
237 PATRON: while ( my ( $borrowernumber, $digest ) = each %$upcoming_digest ) {
238     my $count = $digest->{count};
239     my $from_address = $digest->{email};
240
241     my $borrower_preferences = C4::Members::Messaging::GetMessagingPreferences( { borrowernumber => $borrowernumber,
242                                                                                   message_name   => 'advance_notice' } );
243     # warn( Data::Dumper->Dump( [ $borrower_preferences ], [ 'borrower_preferences' ] ) );
244     next PATRON unless $borrower_preferences; # how could this happen?
245
246
247     my $letter_type = 'PREDUEDGST';
248
249     $sth->execute($borrowernumber,$borrower_preferences->{'days_in_advance'});
250     my $titles = "";
251     while ( my $item_info = $sth->fetchrow_hashref()) {
252       my @item_info = map { $_ =~ /^date|date$/ ? format_date($item_info->{$_}) : $item_info->{$_} || '' } @item_content_fields;
253       $titles .= join("\t",@item_info) . "\n";
254     }
255     my $letter = parse_letter( { letter_code    => $letter_type,
256                               borrowernumber => $borrowernumber,
257                               substitute     => { count => $count,
258                                                   'items.content' => $titles
259                                                 }
260                          } )
261       or die "no letter of type '$letter_type' found. Please see sample_notices.sql";
262     if ($nomail) {
263       local $, = "\f";
264       print $letter->{'content'};
265     }
266     else {
267       foreach my $transport ( keys %{$borrower_preferences->{'transports'}} ) {
268         C4::Letters::EnqueueLetter( { letter                 => $letter,
269                                       borrowernumber         => $borrowernumber,
270                                       from_address           => $from_address,
271                                       message_transport_type => $transport } );
272       }
273     }
274 }
275
276 # Now, run through all the people that want digests and send them
277 PATRON: while ( my ( $borrowernumber, $digest ) = each %$due_digest ) {
278     my $count = $digest->{count};
279     my $from_address = $digest->{email};
280
281     my $borrower_preferences = C4::Members::Messaging::GetMessagingPreferences( { borrowernumber => $borrowernumber,
282                                                                                   message_name   => 'item_due' } );
283     # warn( Data::Dumper->Dump( [ $borrower_preferences ], [ 'borrower_preferences' ] ) );
284     next PATRON unless $borrower_preferences; # how could this happen?
285
286     my $letter_type = 'DUEDGST';
287     $sth->execute($borrowernumber,'0');
288     my $titles = "";
289     while ( my $item_info = $sth->fetchrow_hashref()) {
290       my @item_info = map { $_ =~ /^date|date$/ ? format_date($item_info->{$_}) : $item_info->{$_} || '' } @item_content_fields;
291       $titles .= join("\t",@item_info) . "\n";
292     }
293     my $letter = parse_letter( { letter_code    => $letter_type,
294                               borrowernumber => $borrowernumber,
295                               substitute     => { count => $count,
296                                                   'items.content' => $titles
297                                                 }
298                          } )
299       or die "no letter of type '$letter_type' found. Please see sample_notices.sql";
300
301     if ($nomail) {
302       local $, = "\f";
303       print $letter->{'content'};
304     }
305     else {
306       foreach my $transport ( keys %{$borrower_preferences->{'transports'}} ) {
307         C4::Letters::EnqueueLetter( { letter                 => $letter,
308                                       borrowernumber         => $borrowernumber,
309                                       from_address           => $from_address,
310                                       message_transport_type => $transport } );
311       }
312     }
313 }
314
315 =head1 METHODS
316
317 =head2 parse_letter
318
319 =cut
320
321 sub parse_letter {
322     my $params = shift;
323     foreach my $required ( qw( letter_code borrowernumber ) ) {
324         return unless exists $params->{$required};
325     }
326
327     my %table_params = ( 'borrowers' => $params->{'borrowernumber'} );
328
329     if ( my $p = $params->{'branchcode'} ) {
330         $table_params{'branches'} = $p;
331     }
332     if ( my $p = $params->{'itemnumber'} ) {
333         $table_params{'issues'} = $p;
334         $table_params{'items'} = $p;
335     }
336     if ( my $p = $params->{'biblionumber'} ) {
337         $table_params{'biblio'} = $p;
338         $table_params{'biblioitems'} = $p;
339     }
340
341     return C4::Letters::GetPreparedLetter (
342         module => 'circulation',
343         letter_code => $params->{'letter_code'},
344         branchcode => $table_params{'branches'},
345         substitute => $params->{'substitute'},
346         tables     => \%table_params,
347     );
348 }
349
350 sub format_date {
351     my $date_string = shift;
352     my $dt=dt_from_string($date_string);
353     return output_pref($dt);
354 }
355
356 1;
357
358 __END__