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 $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 ));
70 GetOptions( 'c' => \$confirm,
73 'f:s' => \$fromaddress,
75 'itemscontent=s' => \$itemscontent,
77 my $usage = << 'ENDUSAGE';
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.
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
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') ) {
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.
109 print "Do you wish to continue? (y/n)";
115 # The fields that will be substituted into <<items.content>>
116 my @item_content_fields = split(/,/,$itemscontent);
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;
122 # hash of borrowernumber to number of items upcoming
123 # for patrons wishing digests only.
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()) = ?)
138 UPCOMINGITEM: foreach my $upcoming ( @$upcoming_dues ) {
139 warn 'examining ' . $upcoming->{'itemnumber'} . ' upcoming due items' if $verbose;
140 # warn( Data::Dumper->Dump( [ $upcoming ], [ 'overdue' ] ) );
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;
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'}}++;
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');
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";
166 $letter = parse_letter( { letter => $letter,
167 borrowernumber => $upcoming->{'borrowernumber'},
168 branchcode => $upcoming->{'branchcode'},
169 biblionumber => $biblio->{'biblionumber'},
170 substitute => { 'items.content' => $titles }
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'};
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'}}++;
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'},$borrower_preferences->{'days_in_advance'});
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";
195 $letter = parse_letter( { letter => $letter,
196 borrowernumber => $upcoming->{'borrowernumber'},
197 branchcode => $upcoming->{'branchcode'},
198 biblionumber => $biblio->{'biblionumber'},
199 substitute => { 'items.content' => $titles }
204 # If we have prepared a letter, send it.
208 print $letter->{'content'};
211 foreach my $transport ( @{$borrower_preferences->{'transports'}} ) {
212 C4::Letters::EnqueueLetter( { letter => $letter,
213 borrowernumber => $upcoming->{'borrowernumber'},
214 message_transport_type => $transport } );
221 # warn( Data::Dumper->Dump( [ $upcoming_digest ], [ 'upcoming_digest' ] ) );
223 # Now, run through all the people that want digests and send them
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()) = ?)
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?
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,$borrower_preferences->{'days_in_advance'});
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";
250 $letter = parse_letter( { letter => $letter,
251 borrowernumber => $borrowernumber,
252 substitute => { count => $count,
253 'items.content' => $titles
258 print $letter->{'content'};
261 foreach my $transport ( @{$borrower_preferences->{'transports'}} ) {
262 C4::Letters::EnqueueLetter( { letter => $letter,
263 borrowernumber => $borrowernumber,
264 message_transport_type => $transport } );
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?
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');
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";
285 $letter = parse_letter( { letter => $letter,
286 borrowernumber => $borrowernumber,
287 substitute => { count => $count,
288 'items.content' => $titles
294 print $letter->{'content'};
297 foreach my $transport ( @{$borrower_preferences->{'transports'}} ) {
298 C4::Letters::EnqueueLetter( { letter => $letter,
299 borrowernumber => $borrowernumber,
300 message_transport_type => $transport } );
315 foreach my $required ( qw( letter borrowernumber ) ) {
316 return unless exists $params->{$required};
319 if ( $params->{'substitute'} ) {
320 while ( my ($key, $replacedby) = each %{$params->{'substitute'}} ) {
321 my $replacefield = "<<$key>>";
323 $params->{'letter'}->{title} =~ s/$replacefield/$replacedby/g;
324 $params->{'letter'}->{content} =~ s/$replacefield/$replacedby/g;
328 C4::Letters::parseletter( $params->{'letter'}, 'borrowers', $params->{'borrowernumber'} );
330 if ( $params->{'branchcode'} ) {
331 C4::Letters::parseletter( $params->{'letter'}, 'branches', $params->{'branchcode'} );
334 if ( $params->{'biblionumber'} ) {
335 C4::Letters::parseletter( $params->{'letter'}, 'biblio', $params->{'biblionumber'} );
336 C4::Letters::parseletter( $params->{'letter'}, 'biblioitems', $params->{'biblionumber'} );
339 return $params->{'letter'};