3 # Copyright 2008 LibLime
5 # This file is part of Koha.
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
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.
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.
22 advance_notices.pl - cron script to put item due reminders into message queue
26 ./advance_notices.pl -c
29 0 1 * * * advance_notices.pl -c
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
46 # find Koha's Perl modules
47 # test carefully before changing this
49 eval { require "$FindBin::Bin/../kohalib.pl" };
55 use C4::Members::Messaging;
57 use C4::Dates qw/format_date/;
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 ));
69 GetOptions( 'c' => \$confirm,
73 'itemscontent=s' => \$itemscontent,
75 my $usage = << 'ENDUSAGE';
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.
86 -i csv list of fields that get substituted into templates in places
87 of the E<lt>E<lt>items.contentE<gt>E<gt> placeholder. Defaults to
88 issuedate,title,barcode,author
91 # Since advance notice options are not visible in the web-interface
92 # unless EnhancedMessagingPreferences is on, let the user know that
93 # this script probably isn't going to do much
94 if ( ! C4::Context->preference('EnhancedMessagingPreferences') ) {
97 The "EnhancedMessagingPreferences" syspref is off.
98 Therefore, it is unlikely that this script will actually produce any messages to be sent.
99 To change this, edit the "EnhancedMessagingPreferences" syspref.
106 print "Do you wish to continue? (y/n)";
112 # The fields that will be substituted into <<items.content>>
113 my @item_content_fields = split(/,/,$itemscontent);
115 warn 'getting upcoming due issues' if $verbose;
116 my $upcoming_dues = C4::Circulation::GetUpcomingDueIssues( { days_in_advance => $maxdays } );
117 warn 'found ' . scalar( @$upcoming_dues ) . ' issues' if $verbose;
119 # hash of borrowernumber to number of items upcoming
120 # for patrons wishing digests only.
124 my $dbh = C4::Context->dbh();
125 my $sth = $dbh->prepare(<<'END_SQL');
126 SELECT biblio.*, items.*, issues.*
127 FROM issues,items,biblio
128 WHERE items.itemnumber=issues.itemnumber
129 AND biblio.biblionumber=items.biblionumber
130 AND issues.borrowernumber = ?
131 AND issues.itemnumber = ?
132 AND (TO_DAYS(date_due)-TO_DAYS(NOW()) = ?)
135 my $admin_adress = C4::Context->preference('KohaAdminEmailAddress');
137 UPCOMINGITEM: foreach my $upcoming ( @$upcoming_dues ) {
138 warn 'examining ' . $upcoming->{'itemnumber'} . ' upcoming due items' if $verbose;
139 # warn( Data::Dumper->Dump( [ $upcoming ], [ 'overdue' ] ) );
141 my $from_address = $upcoming->{branchemail} || $admin_adress;
144 my $borrower_preferences;
145 if ( 0 == $upcoming->{'days_until_due'} ) {
146 # This item is due today. Send an 'item due' message.
147 $borrower_preferences = C4::Members::Messaging::GetMessagingPreferences( { borrowernumber => $upcoming->{'borrowernumber'},
148 message_name => 'item_due' } );
149 # warn( Data::Dumper->Dump( [ $borrower_preferences ], [ 'borrower_preferences' ] ) );
150 next unless $borrower_preferences;
152 if ( $borrower_preferences->{'wants_digest'} ) {
153 # cache this one to process after we've run through all of the items.
154 my $digest = $due_digest->{$upcoming->{'borrowernumber'}} ||= {};
155 $digest->{email} ||= $from_address;
158 my $biblio = C4::Biblio::GetBiblioFromItemNumber( $upcoming->{'itemnumber'} );
159 my $letter_type = 'DUE';
160 $letter = C4::Letters::getletter( 'circulation', $letter_type );
161 die "no letter of type '$letter_type' found. Please see sample_notices.sql" unless $letter;
162 $sth->execute($upcoming->{'borrowernumber'},$upcoming->{'itemnumber'},'0');
164 while ( my $item_info = $sth->fetchrow_hashref()) {
165 my @item_info = map { $_ =~ /^date|date$/ ? format_date($item_info->{$_}) : $item_info->{$_} || '' } @item_content_fields;
166 $titles .= join("\t",@item_info) . "\n";
169 $letter = parse_letter( { letter => $letter,
170 borrowernumber => $upcoming->{'borrowernumber'},
171 branchcode => $upcoming->{'branchcode'},
172 biblionumber => $biblio->{'biblionumber'},
173 itemnumber => $upcoming->{'itemnumber'},
174 substitute => { 'items.content' => $titles }
178 $borrower_preferences = C4::Members::Messaging::GetMessagingPreferences( { borrowernumber => $upcoming->{'borrowernumber'},
179 message_name => 'advance_notice' } );
180 # warn( Data::Dumper->Dump( [ $borrower_preferences ], [ 'borrower_preferences' ] ) );
181 next UPCOMINGITEM unless $borrower_preferences && exists $borrower_preferences->{'days_in_advance'};
182 next UPCOMINGITEM unless $borrower_preferences->{'days_in_advance'} == $upcoming->{'days_until_due'};
184 if ( $borrower_preferences->{'wants_digest'} ) {
185 # cache this one to process after we've run through all of the items.
186 my $digest = $upcoming_digest->{$upcoming->{'borrowernumber'}} ||= {};
187 $digest->{email} ||= $from_address;
190 my $biblio = C4::Biblio::GetBiblioFromItemNumber( $upcoming->{'itemnumber'} );
191 my $letter_type = 'PREDUE';
192 $letter = C4::Letters::getletter( 'circulation', $letter_type );
193 die "no letter of type '$letter_type' found. Please see sample_notices.sql" unless $letter;
194 $sth->execute($upcoming->{'borrowernumber'},$upcoming->{'itemnumber'},$borrower_preferences->{'days_in_advance'});
196 while ( my $item_info = $sth->fetchrow_hashref()) {
197 my @item_info = map { $_ =~ /^date|date$/ ? format_date($item_info->{$_}) : $item_info->{$_} || '' } @item_content_fields;
198 $titles .= join("\t",@item_info) . "\n";
201 $letter = parse_letter( { letter => $letter,
202 borrowernumber => $upcoming->{'borrowernumber'},
203 branchcode => $upcoming->{'branchcode'},
204 biblionumber => $biblio->{'biblionumber'},
205 itemnumber => $upcoming->{'itemnumber'},
206 substitute => { 'items.content' => $titles }
211 # If we have prepared a letter, send it.
215 print $letter->{'content'};
218 foreach my $transport ( @{$borrower_preferences->{'transports'}} ) {
219 C4::Letters::EnqueueLetter( { letter => $letter,
220 borrowernumber => $upcoming->{'borrowernumber'},
221 from_address => $from_address,
222 message_transport_type => $transport } );
229 # warn( Data::Dumper->Dump( [ $upcoming_digest ], [ 'upcoming_digest' ] ) );
231 # Now, run through all the people that want digests and send them
233 $sth = $dbh->prepare(<<'END_SQL');
234 SELECT biblio.*, items.*, issues.*
235 FROM issues,items,biblio
236 WHERE items.itemnumber=issues.itemnumber
237 AND biblio.biblionumber=items.biblionumber
238 AND issues.borrowernumber = ?
239 AND (TO_DAYS(date_due)-TO_DAYS(NOW()) = ?)
242 PATRON: while ( my ( $borrowernumber, $digest ) = each %$upcoming_digest ) {
243 my $count = $digest->{count};
244 my $from_address = $digest->{email};
246 my $borrower_preferences = C4::Members::Messaging::GetMessagingPreferences( { borrowernumber => $borrowernumber,
247 message_name => 'advance_notice' } );
248 # warn( Data::Dumper->Dump( [ $borrower_preferences ], [ 'borrower_preferences' ] ) );
249 next PATRON unless $borrower_preferences; # how could this happen?
252 my $letter_type = 'PREDUEDGST';
253 my $letter = C4::Letters::getletter( 'circulation', $letter_type );
254 die "no letter of type '$letter_type' found. Please see sample_notices.sql" unless $letter;
256 $sth->execute($borrowernumber,$borrower_preferences->{'days_in_advance'});
258 while ( my $item_info = $sth->fetchrow_hashref()) {
259 my @item_info = map { $_ =~ /^date|date$/ ? format_date($item_info->{$_}) : $item_info->{$_} || '' } @item_content_fields;
260 $titles .= join("\t",@item_info) . "\n";
262 $letter = parse_letter( { letter => $letter,
263 borrowernumber => $borrowernumber,
264 substitute => { count => $count,
265 'items.content' => $titles
270 print $letter->{'content'};
273 foreach my $transport ( @{$borrower_preferences->{'transports'}} ) {
274 C4::Letters::EnqueueLetter( { letter => $letter,
275 borrowernumber => $borrowernumber,
276 from_address => $from_address,
277 message_transport_type => $transport } );
282 # Now, run through all the people that want digests and send them
283 PATRON: while ( my ( $borrowernumber, $digest ) = each %$due_digest ) {
284 my $count = $digest->{count};
285 my $from_address = $digest->{email};
287 my $borrower_preferences = C4::Members::Messaging::GetMessagingPreferences( { borrowernumber => $borrowernumber,
288 message_name => 'item_due' } );
289 # warn( Data::Dumper->Dump( [ $borrower_preferences ], [ 'borrower_preferences' ] ) );
290 next PATRON unless $borrower_preferences; # how could this happen?
292 my $letter_type = 'DUEDGST';
293 my $letter = C4::Letters::getletter( 'circulation', $letter_type );
294 die "no letter of type '$letter_type' found. Please see sample_notices.sql" unless $letter;
295 $sth->execute($borrowernumber,'0');
297 while ( my $item_info = $sth->fetchrow_hashref()) {
298 my @item_info = map { $_ =~ /^date|date$/ ? format_date($item_info->{$_}) : $item_info->{$_} || '' } @item_content_fields;
299 $titles .= join("\t",@item_info) . "\n";
301 $letter = parse_letter( { letter => $letter,
302 borrowernumber => $borrowernumber,
303 substitute => { count => $count,
304 'items.content' => $titles
310 print $letter->{'content'};
313 foreach my $transport ( @{$borrower_preferences->{'transports'}} ) {
314 C4::Letters::EnqueueLetter( { letter => $letter,
315 borrowernumber => $borrowernumber,
316 from_address => $from_address,
317 message_transport_type => $transport } );
332 foreach my $required ( qw( letter borrowernumber ) ) {
333 return unless exists $params->{$required};
336 if ( $params->{'substitute'} ) {
337 while ( my ($key, $replacedby) = each %{$params->{'substitute'}} ) {
338 my $replacefield = "<<$key>>";
340 $params->{'letter'}->{title} =~ s/$replacefield/$replacedby/g;
341 $params->{'letter'}->{content} =~ s/$replacefield/$replacedby/g;
345 C4::Letters::parseletter( $params->{'letter'}, 'borrowers', $params->{'borrowernumber'} );
347 if ( $params->{'branchcode'} ) {
348 C4::Letters::parseletter( $params->{'letter'}, 'branches', $params->{'branchcode'} );
350 if ( $params->{'itemnumber'} ) {
351 C4::Letters::parseletter( $params->{'letter'}, 'issues', $params->{'itemnumber'} );
353 if ( $params->{'biblionumber'} ) {
354 C4::Letters::parseletter( $params->{'letter'}, 'biblio', $params->{'biblionumber'} );
355 C4::Letters::parseletter( $params->{'letter'}, 'biblioitems', $params->{'biblionumber'} );
356 C4::Letters::parseletter( $params->{'letter'}, 'items', $params->{'biblionumber'} );
359 return $params->{'letter'};