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;
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.
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') ) {
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.
103 print "Do you wish to continue? (y/n)";
109 # The fields that will be substituted into <<items.content>>
110 my @item_content_fields = split(/,/,$itemscontent);
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;
116 # hash of borrowernumber to number of items upcoming
117 # for patrons wishing digests only.
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()) = ?)
132 my $admin_adress = C4::Context->preference('KohaAdminEmailAddress');
134 UPCOMINGITEM: foreach my $upcoming ( @$upcoming_dues ) {
135 warn 'examining ' . $upcoming->{'itemnumber'} . ' upcoming due items' if $verbose;
136 # warn( Data::Dumper->Dump( [ $upcoming ], [ 'overdue' ] ) );
138 my $from_address = $upcoming->{branchemail} || $admin_adress;
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;
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;
155 my $biblio = C4::Biblio::GetBiblioFromItemNumber( $upcoming->{'itemnumber'} );
156 my $letter_type = 'DUE';
157 $sth->execute($upcoming->{'borrowernumber'},$upcoming->{'itemnumber'},'0');
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";
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 }
172 or die "no letter of type '$letter_type' found. Please see sample_notices.sql";
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'};
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;
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'});
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";
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 }
204 or die "no letter of type '$letter_type' found. Please see sample_notices.sql";
208 # If we have prepared a letter, send it.
212 print $letter->{'content'};
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 } );
226 # warn( Data::Dumper->Dump( [ $upcoming_digest ], [ 'upcoming_digest' ] ) );
228 # Now, run through all the people that want digests and send them
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()) = ?)
239 PATRON: while ( my ( $borrowernumber, $digest ) = each %$upcoming_digest ) {
240 my $count = $digest->{count};
241 my $from_address = $digest->{email};
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?
249 my $letter_type = 'PREDUEDGST';
251 $sth->execute($borrowernumber,$borrower_preferences->{'days_in_advance'});
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";
258 ## Get branch info for borrowers home library.
259 my %branch_info = get_branch_info( $borrowernumber );
261 my $letter = parse_letter( { letter_code => $letter_type,
262 borrowernumber => $borrowernumber,
263 substitute => { count => $count,
264 'items.content' => $titles,
268 or die "no letter of type '$letter_type' found. Please see sample_notices.sql";
271 print $letter->{'content'};
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 } );
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};
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?
293 my $letter_type = 'DUEDGST';
294 $sth->execute($borrowernumber,'0');
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";
301 ## Get branch info for borrowers home library.
302 my %branch_info = get_branch_info( $borrowernumber );
304 my $letter = parse_letter( { letter_code => $letter_type,
305 borrowernumber => $borrowernumber,
306 substitute => { count => $count,
307 'items.content' => $titles,
311 or die "no letter of type '$letter_type' found. Please see sample_notices.sql";
315 print $letter->{'content'};
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 } );
335 foreach my $required ( qw( letter_code borrowernumber ) ) {
336 return unless exists $params->{$required};
339 my %table_params = ( 'borrowers' => $params->{'borrowernumber'} );
341 if ( my $p = $params->{'branchcode'} ) {
342 $table_params{'branches'} = $p;
344 if ( my $p = $params->{'itemnumber'} ) {
345 $table_params{'issues'} = $p;
346 $table_params{'items'} = $p;
348 if ( my $p = $params->{'biblionumber'} ) {
349 $table_params{'biblio'} = $p;
350 $table_params{'biblioitems'} = $p;
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,
363 my $date_string = shift;
364 my $dt=dt_from_string($date_string);
365 return output_pref($dt);
368 =head2 get_branch_info
372 sub get_branch_info {
373 my ( $borrowernumber ) = @_;
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 );
380 foreach my $key( keys %$branch ) {
381 $branch_info{"branches.$key"} = $branch->{$key};