Bug 15504: (QA follow-up) Final changes
[koha.git] / misc / cronjobs / automatic_renewals.pl
1 #!/usr/bin/perl
2
3 # This file is part of Koha.
4 #
5 # Copyright (C) 2014 Hochschule für Gesundheit (hsg), Germany
6 #
7 # Koha is free software; you can redistribute it and/or modify it
8 # under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # Koha is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20 =head1 NAME
21
22 automatic_renewals.pl - cron script to renew loans
23
24 =head1 SYNOPSIS
25
26 ./automatic_renewals.pl [-c|--confirm] [-s|--send-notices] [-d|--digest] [-b|--digest-per-branch] [-v|--verbose]
27
28 or, in crontab:
29 # Once every day for digest messages
30 0 3 * * * automatic_renewals.pl -c -d
31 # Three times a day for non digest messages
32 0 0,8,16 * * * automatic_renewals.pl -c
33
34 =head1 DESCRIPTION
35
36 This script searches for issues scheduled for automatic renewal
37 (issues.auto_renew). If there are still renews left (Renewals allowed)
38 and the renewal isn't premature (No Renewal before) the issue is renewed.
39
40 =head1 OPTIONS
41
42 =over
43
44 =item B<-s|--send-notices>
45
46 DEPRECATED: The system preference AutoRenewalNotices should be used to determine
47 whether notices are sent or not
48 Send AUTO_RENEWALS notices to patrons if the auto renewal has been done.
49
50 =item B<-v|--verbose>
51
52 Print report to standard out.
53
54 =item B<-c|--confirm>
55
56 Without this parameter no changes will be made
57
58 =item B<-b|--digest-per-branch>
59
60 Flag to indicate that generation of message digests should be
61 performed separately for each branch.
62
63 A patron could potentially have loans at several different branches
64 There is no natural branch to set as the sender on the aggregated
65 message in this situation so the default behavior is to use the
66 borrowers home branch.  This could surprise to the borrower when
67 message sender is a library where they have not borrowed anything.
68
69 Enabling this flag ensures that the issuing library is the sender of
70 the digested message.  It has no effect unless the borrower has
71 chosen 'Digests only' on the advance messages.
72
73 =back
74
75 =cut
76
77 use Modern::Perl;
78 use Pod::Usage qw( pod2usage );
79 use Getopt::Long qw( GetOptions );
80
81 use Koha::Script -cron;
82 use C4::Circulation qw( CanBookBeRenewed AddRenewal );
83 use C4::Context;
84 use C4::Log qw( cronlogaction );
85 use C4::Letters;
86 use Koha::Checkouts;
87 use Koha::Libraries;
88 use Koha::Patrons;
89
90 my $command_line_options = join(" ",@ARGV);
91
92 my ( $help, $send_notices, $verbose, $confirm, $digest_per_branch );
93 GetOptions(
94     'h|help' => \$help,
95     's|send-notices' => \$send_notices,
96     'v|verbose'    => \$verbose,
97     'c|confirm'     => \$confirm,
98     'b|digest-per-branch' => \$digest_per_branch,
99 ) || pod2usage(1);
100
101 pod2usage(0) if $help;
102
103 my $send_notices_pref = C4::Context->preference('AutoRenewalNotices');
104 if ( $send_notices_pref eq 'cron' ) {
105     warn <<'END_WARN';
106
107 The "AutoRenewalNotices" syspref is set to 'Follow the cron switch'.
108 The send_notices switch for this script is deprecated, you should either set the preference
109 to 'Never send emails' or 'Follow patron messaging preferences'
110
111 END_WARN
112 } else {
113     # If not following cron then:
114     # - we should not send if set to never
115     # - we will send any notice generated according to preferences if following those
116     $send_notices = $send_notices_pref eq 'never' ? 0 : 1;
117 }
118
119 # Since advance notice options are not visible in the web-interface
120 # unless EnhancedMessagingPreferences is on, let the user know that
121 # this script probably isn't going to do much
122 if ( ! C4::Context->preference('EnhancedMessagingPreferences') ) {
123     warn <<'END_WARN';
124
125 The "EnhancedMessagingPreferences" syspref is off.
126 Therefore, it is unlikely that this script will actually produce any messages to be sent.
127 To change this, edit the "EnhancedMessagingPreferences" syspref.
128
129 END_WARN
130 }
131
132 cronlogaction({ info => $command_line_options });
133
134 $verbose = 1 unless $verbose or $confirm;
135 print "Test run only\n" unless $confirm;
136
137 print "getting auto renewals\n" if $verbose;
138 my $auto_renews = Koha::Checkouts->search(
139     {
140         auto_renew                   => 1,
141         'patron.autorenew_checkouts' => 1,
142     },
143     {
144         join => ['patron','item']
145     }
146 );
147 print "found " . $auto_renews->count . " auto renewals\n" if $verbose;
148
149 my $renew_digest = {};
150 my %report;
151 my @item_renewal_ids;
152 while ( my $auto_renew = $auto_renews->next ) {
153     print "examining item '" . $auto_renew->itemnumber . "' to auto renew\n" if $verbose;
154
155     my ( $borrower_preferences, $wants_messages, $wants_digest ) = ( undef, 0, 0 );
156     if ( $send_notices_pref eq 'preferences' ){
157         $borrower_preferences = C4::Members::Messaging::GetMessagingPreferences(
158             {
159                 borrowernumber => $auto_renew->borrowernumber,
160                 message_name   => 'auto_renewals'
161             }
162         );
163         $wants_messages = 1
164             if $borrower_preferences
165             && $borrower_preferences->{transports};
166         $wants_digest = 1
167             if $wants_messages
168             && $borrower_preferences->{wants_digest};
169     } else { # Preference is never or cron
170         $wants_messages = $send_notices;
171         $wants_digest = 0;
172     }
173
174     # CanBookBeRenewed returns 'auto_renew' when the renewal should be done by this script
175     my ( $ok, $error ) = CanBookBeRenewed( $auto_renew->patron, $auto_renew, undef, 1 );
176     my $updated;
177     if ( $error eq 'auto_renew' ) {
178         $updated = 1;
179         if ($verbose) {
180             say sprintf "Issue id: %s for borrower: %s and item: %s %s be renewed.",
181                 $auto_renew->issue_id, $auto_renew->borrowernumber, $auto_renew->itemnumber,
182                 $confirm ? 'will' : 'would';
183         }
184         if ($confirm) {
185             my $date_due = AddRenewal(
186                 {
187                     borrowernumber => $auto_renew->borrowernumber,
188                     itemnumber     => $auto_renew->itemnumber,
189                     branch         => $auto_renew->branchcode,
190                     seen           => 0,
191                     automatic      => 1,
192                 }
193             );
194             push @item_renewal_ids, $auto_renew->itemnumber;
195             $auto_renew->auto_renew_error(undef)->store;
196         }
197         push @{ $report{ $auto_renew->borrowernumber } }, $auto_renew
198             if ($wants_messages) && !$wants_digest;
199     } elsif ( $error eq 'too_many'
200         || $error eq 'on_reserve'
201         || $error eq 'restriction'
202         || $error eq 'overdue'
203         || $error eq 'too_unseen'
204         || $error eq 'auto_account_expired'
205         || $error eq 'auto_too_late'
206         || $error eq 'auto_too_much_oweing'
207         || $error eq 'auto_too_soon'
208         || $error eq 'too_soon'
209         || $error eq 'item_denied_renewal'
210         || $error eq 'item_issued_to_other_patron' )
211     {
212         if ($verbose) {
213             say sprintf "Issue id: %s for borrower: %s and item: %s %s not be renewed. (%s)",
214                 $auto_renew->issue_id, $auto_renew->borrowernumber, $auto_renew->itemnumber,
215                 $confirm ? 'will' : 'would', $error;
216         }
217         $updated = 1 if ( !$auto_renew->auto_renew_error || $error ne $auto_renew->auto_renew_error );
218         if ($updated) {
219             $auto_renew->auto_renew_error($error)->store if $confirm;
220             push @{ $report{ $auto_renew->borrowernumber } }, $auto_renew
221                 if $error ne 'auto_too_soon' && ( $wants_messages && !$wants_digest );  # Do not notify if it's too soon
222         }
223     }
224
225     if ( $wants_digest ) {
226         # cache this one to process after we've run through all of the items.
227         if ($digest_per_branch) {
228             $renew_digest->{ $auto_renew->branchcode }->{ $auto_renew->borrowernumber }->{success}++ if $error eq 'auto_renew';
229             $renew_digest->{ $auto_renew->branchcode }->{ $auto_renew->borrowernumber }->{error}++ unless $error eq 'auto_renew' || $error eq 'auto_too_soon';
230             $renew_digest->{ $auto_renew->branchcode }->{ $auto_renew->borrowernumber }->{results}->{$error}++ ;
231             push @{$renew_digest->{ $auto_renew->branchcode }->{ $auto_renew->borrowernumber }->{issues}}, $auto_renew->itemnumber;
232             $renew_digest->{ $auto_renew->branchcode }->{ $auto_renew->borrowernumber }->{updated} = 1 if $updated && $error ne 'auto_too_soon';
233         } else {
234             $renew_digest->{ $auto_renew->borrowernumber }->{success} ++ if $error eq 'auto_renew';
235             $renew_digest->{ $auto_renew->borrowernumber }->{error}++ unless $error eq 'auto_renew' || $error eq 'auto_too_soon';
236             $renew_digest->{ $auto_renew->borrowernumber }->{results}->{$error}++ ;
237             $renew_digest->{ $auto_renew->borrowernumber }->{updated} = 1 if $updated && $error ne 'auto_too_soon';
238             push @{$renew_digest->{ $auto_renew->borrowernumber }->{issues}}, $auto_renew->itemnumber;
239         }
240     }
241
242 }
243
244 if( @item_renewal_ids ){
245     my $indexer = Koha::SearchEngine::Indexer->new({ index => $Koha::SearchEngine::BIBLIOS_INDEX });
246     $indexer->index_records( \@item_renewal_ids, "specialUpdate", "biblioserver" );
247 }
248
249 if ( $send_notices && $confirm ) {
250     for my $borrowernumber ( keys %report ) {
251         my $patron = Koha::Patrons->find($borrowernumber);
252         my $borrower_preferences =
253             C4::Members::Messaging::GetMessagingPreferences(
254                 {
255                     borrowernumber => $borrowernumber,
256                     message_name   => 'auto_renewals'
257                 }
258             );
259         for my $issue ( @{ $report{$borrowernumber} } ) {
260             my $item = $issue->item;
261             # Force sending of email and only email if pref is set to "cron"
262             my @transports = $send_notices_pref eq 'preferences' ? keys %{ $borrower_preferences->{'transports'} } : 'email';
263             foreach my $transport ( @transports ) {
264                 my $letter = C4::Letters::GetPreparedLetter (
265                     module      => 'circulation',
266                     letter_code => 'AUTO_RENEWALS',
267                     tables      => {
268                         borrowers => $patron->borrowernumber,
269                         issues    => $issue->itemnumber,
270                         items     => $issue->itemnumber,
271                         biblio    => $item->biblionumber,
272                     },
273                     lang => $patron->lang,
274                     message_transport_type => $transport,
275                 );
276
277                 if ($letter) {
278                     my $library = $patron->library;
279                     my $admin_email_address = $library->from_email_address;
280
281                     C4::Letters::EnqueueLetter(
282                         {
283                             letter                 => $letter,
284                             borrowernumber         => $borrowernumber,
285                             from_address           => $admin_email_address,
286                             message_transport_type => $transport
287                         }
288                     );
289                 }
290             }
291         }
292     }
293
294     if ($digest_per_branch) {
295         while (my ($branchcode, $digests) = each %$renew_digest) {
296             send_digests({
297                 digests => $digests,
298                 branchcode => $branchcode,
299                 letter_code => 'AUTO_RENEWALS_DGST',
300             });
301         }
302     } else {
303         send_digests({
304             digests => $renew_digest,
305             letter_code => 'AUTO_RENEWALS_DGST',
306         });
307     }
308 }
309
310 cronlogaction({ action => 'End', info => "COMPLETED" });
311
312 =head1 METHODS
313
314 =head2 send_digests
315
316     send_digests({
317         digests => ...,
318         letter_code => ...,
319     })
320
321 Enqueue digested letters.
322
323 Parameters:
324
325 =over 4
326
327 =item C<$digests>
328
329 Reference to the array of digested messages.
330
331 =item C<$letter_code>
332
333 String that denote the letter code.
334
335 =back
336
337 =cut
338
339 sub send_digests {
340     my $params = shift;
341
342     PATRON: while ( my ( $borrowernumber, $digest ) = each %{$params->{digests}} ) {
343         next unless defined $digest->{updated} && $digest->{updated} == 1;
344         my $borrower_preferences =
345             C4::Members::Messaging::GetMessagingPreferences(
346                 {
347                     borrowernumber => $borrowernumber,
348                     message_name   => 'auto_renewals'
349                 }
350             );
351
352         next PATRON unless $borrower_preferences; # how could this happen?
353
354         my $patron = Koha::Patrons->find( $borrowernumber );
355         my $branchcode;
356         if ( defined $params->{branchcode} ) {
357             $branchcode = $params->{branchcode};
358         } else {
359             $branchcode = $patron->branchcode;
360         }
361         my $library = Koha::Libraries->find( $branchcode );
362         my $from_address = $library->from_email_address;
363
364         foreach my $transport ( keys %{ $borrower_preferences->{'transports'} } ) {
365             my $letter = C4::Letters::GetPreparedLetter (
366                 module => 'circulation',
367                 letter_code => $params->{letter_code},
368                 branchcode => $branchcode,
369                 lang => $patron->lang,
370                 substitute => {
371                     error => $digest->{error}||0,
372                     success => $digest->{success}||0,
373                     results => $digest->{results},
374                 },
375                 loops => { issues => \@{$digest->{issues}} },
376                 tables      => {
377                     borrowers => $patron->borrowernumber,
378                 },
379                 message_transport_type => $transport,
380             );
381
382             if ($letter) {
383                 C4::Letters::EnqueueLetter(
384                     {
385                         letter                 => $letter,
386                         borrowernumber         => $borrowernumber,
387                         from_address           => $from_address,
388                         message_transport_type => $transport
389                     }
390                 );
391             }
392             else {
393                 warn
394 "no letter of type '$params->{letter_code}' found for borrowernumber $borrowernumber. Please see sample_notices.sql";
395             }
396
397         }
398     }
399 }