Bug 34943: Unit tests
[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::Context;
83 use C4::Log qw( cronlogaction );
84 use C4::Letters;
85 use Koha::Checkouts;
86 use Koha::Libraries;
87 use Koha::Patrons;
88
89 my $command_line_options = join(" ",@ARGV);
90
91 my ( $help, $send_notices, $verbose, $confirm, $digest_per_branch );
92 GetOptions(
93     'h|help' => \$help,
94     's|send-notices' => \$send_notices,
95     'v|verbose'    => \$verbose,
96     'c|confirm'     => \$confirm,
97     'b|digest-per-branch' => \$digest_per_branch,
98 ) || pod2usage(1);
99
100 pod2usage(0) if $help;
101
102 my $send_notices_pref = C4::Context->preference('AutoRenewalNotices');
103 if ( $send_notices_pref eq 'cron' ) {
104     warn <<'END_WARN';
105
106 The "AutoRenewalNotices" syspref is set to 'Follow the cron switch'.
107 The send_notices switch for this script is deprecated, you should either set the preference
108 to 'Never send emails' or 'Follow patron messaging preferences'
109
110 END_WARN
111 } else {
112     # If not following cron then:
113     # - we should not send if set to never
114     # - we will send any notice generated according to preferences if following those
115     $send_notices = $send_notices_pref eq 'never' ? 0 : 1;
116 }
117
118 # Since advance notice options are not visible in the web-interface
119 # unless EnhancedMessagingPreferences is on, let the user know that
120 # this script probably isn't going to do much
121 if ( ! C4::Context->preference('EnhancedMessagingPreferences') ) {
122     warn <<'END_WARN';
123
124 The "EnhancedMessagingPreferences" syspref is off.
125 Therefore, it is unlikely that this script will actually produce any messages to be sent.
126 To change this, edit the "EnhancedMessagingPreferences" syspref.
127
128 END_WARN
129 }
130
131 cronlogaction({ info => $command_line_options });
132
133 $verbose = 1 unless $verbose or $confirm;
134 print "Test run only\n" unless $confirm;
135
136 print "getting auto renewals\n" if $verbose;
137 my $auto_renews = Koha::Checkouts->search(
138     {
139         auto_renew                   => 1,
140         'patron.autorenew_checkouts' => 1,
141     },
142     {
143         join => ['patron','item']
144     }
145 );
146 print "found " . $auto_renews->count . " auto renewals\n" if $verbose;
147
148 my $renew_digest = {};
149 my %report;
150 my @item_renewal_ids;
151 while ( my $auto_renew = $auto_renews->next ) {
152     print "examining item '" . $auto_renew->itemnumber . "' to auto renew\n" if $verbose;
153
154     my ( $borrower_preferences, $wants_messages, $wants_digest ) = ( undef, 0, 0 );
155     if ( $send_notices_pref eq 'preferences' ) {
156         $borrower_preferences = C4::Members::Messaging::GetMessagingPreferences(
157             {
158                 borrowernumber => $auto_renew->borrowernumber,
159                 message_name   => 'auto_renewals'
160             }
161         );
162         $wants_messages = 1
163             if $borrower_preferences
164             && $borrower_preferences->{transports};
165         $wants_digest = 1
166             if $wants_messages
167             && $borrower_preferences->{wants_digest};
168     } else {    # Preference is never or cron
169         $wants_messages = $send_notices;
170         $wants_digest   = 0;
171     }
172
173     my ( $success, $error, $updated ) = $auto_renew->attempt_auto_renew( { confirm => $confirm } );
174     if ($success) {
175         if ($verbose) {
176             say sprintf "Issue id: %s for borrower: %s and item: %s %s be renewed.",
177                 $auto_renew->issue_id, $auto_renew->borrowernumber, $auto_renew->itemnumber,
178                 $confirm ? 'will' : 'would';
179         }
180         if ($confirm) {
181             push @item_renewal_ids, $auto_renew->itemnumber;
182         }
183         push @{ $report{ $auto_renew->borrowernumber } }, $auto_renew
184             if ($wants_messages) && !$wants_digest;
185     } elsif (
186
187         # FIXME Do we need to list every status? Why not simply else?
188            $error eq 'too_many'
189         || $error eq 'on_reserve'
190         || $error eq 'restriction'
191         || $error eq 'overdue'
192         || $error eq 'too_unseen'
193         || $error eq 'auto_account_expired'
194         || $error eq 'auto_too_late'
195         || $error eq 'auto_too_much_oweing'
196         || $error eq 'auto_too_soon'
197         || $error eq 'item_denied_renewal'
198         || $error eq 'item_issued_to_other_patron'
199         )
200     {
201         if ($verbose) {
202             say sprintf "Issue id: %s for borrower: %s and item: %s %s not be renewed. (%s)",
203                 $auto_renew->issue_id, $auto_renew->borrowernumber, $auto_renew->itemnumber,
204                 $confirm ? 'will' : 'would', $error;
205         }
206         if ($updated) {
207             push @{ $report{ $auto_renew->borrowernumber } }, $auto_renew
208                 if $error ne 'auto_too_soon' && ( $wants_messages && !$wants_digest );  # Do not notify if it's too soon
209         }
210     }
211
212     if ( $wants_digest ) {
213         # cache this one to process after we've run through all of the items.
214         if ($digest_per_branch) {
215             $renew_digest->{ $auto_renew->branchcode }->{ $auto_renew->borrowernumber }->{success}++ if $success;
216             $renew_digest->{ $auto_renew->branchcode }->{ $auto_renew->borrowernumber }->{error}++ unless $success || $error eq 'auto_too_soon';
217             $renew_digest->{ $auto_renew->branchcode }->{ $auto_renew->borrowernumber }->{results}->{defined $error ? $error : 'auto-renew'}++ ;
218             push @{$renew_digest->{ $auto_renew->branchcode }->{ $auto_renew->borrowernumber }->{issues}}, $auto_renew->itemnumber;
219             $renew_digest->{ $auto_renew->branchcode }->{ $auto_renew->borrowernumber }->{updated} = 1 if $updated && (!$error || $error ne 'auto_too_soon');
220         } else {
221             $renew_digest->{ $auto_renew->borrowernumber }->{success} ++ if $success;
222             $renew_digest->{ $auto_renew->borrowernumber }->{error}++ unless $success || $error eq 'auto_too_soon';
223             $renew_digest->{ $auto_renew->borrowernumber }->{results}->{defined $error ? $error : 'auto-renew'}++ ;
224             $renew_digest->{ $auto_renew->borrowernumber }->{updated} = 1 if $updated && (!$error || $error ne 'auto_too_soon');
225             push @{$renew_digest->{ $auto_renew->borrowernumber }->{issues}}, $auto_renew->itemnumber;
226         }
227     }
228
229 }
230
231 if( @item_renewal_ids ){
232     my $indexer = Koha::SearchEngine::Indexer->new({ index => $Koha::SearchEngine::BIBLIOS_INDEX });
233     $indexer->index_records( \@item_renewal_ids, "specialUpdate", "biblioserver" );
234 }
235
236 if ( $send_notices && $confirm ) {
237     for my $borrowernumber ( keys %report ) {
238         my $patron = Koha::Patrons->find($borrowernumber);
239         my $borrower_preferences =
240             C4::Members::Messaging::GetMessagingPreferences(
241                 {
242                     borrowernumber => $borrowernumber,
243                     message_name   => 'auto_renewals'
244                 }
245             );
246         for my $issue ( @{ $report{$borrowernumber} } ) {
247             my $item = $issue->item;
248             # Force sending of email and only email if pref is set to "cron"
249             my @transports = $send_notices_pref eq 'preferences' ? keys %{ $borrower_preferences->{'transports'} } : 'email';
250             foreach my $transport ( @transports ) {
251                 my $letter = C4::Letters::GetPreparedLetter (
252                     module      => 'circulation',
253                     letter_code => 'AUTO_RENEWALS',
254                     tables      => {
255                         borrowers => $patron->borrowernumber,
256                         issues    => $issue->itemnumber,
257                         items     => $issue->itemnumber,
258                         biblio    => $item->biblionumber,
259                     },
260                     lang => $patron->lang,
261                     message_transport_type => $transport,
262                 );
263
264                 if ($letter) {
265                     my $library = $patron->library;
266                     my $admin_email_address = $library->from_email_address;
267
268                     C4::Letters::EnqueueLetter(
269                         {
270                             letter                 => $letter,
271                             borrowernumber         => $borrowernumber,
272                             from_address           => $admin_email_address,
273                             message_transport_type => $transport
274                         }
275                     );
276                 }
277             }
278         }
279     }
280
281     if ($digest_per_branch) {
282         while (my ($branchcode, $digests) = each %$renew_digest) {
283             send_digests({
284                 digests => $digests,
285                 branchcode => $branchcode,
286                 letter_code => 'AUTO_RENEWALS_DGST',
287             });
288         }
289     } else {
290         send_digests({
291             digests => $renew_digest,
292             letter_code => 'AUTO_RENEWALS_DGST',
293         });
294     }
295 }
296
297 cronlogaction({ action => 'End', info => "COMPLETED" });
298
299 =head1 METHODS
300
301 =head2 send_digests
302
303     send_digests({
304         digests => ...,
305         letter_code => ...,
306     })
307
308 Enqueue digested letters.
309
310 Parameters:
311
312 =over 4
313
314 =item C<$digests>
315
316 Reference to the array of digested messages.
317
318 =item C<$letter_code>
319
320 String that denote the letter code.
321
322 =back
323
324 =cut
325
326 sub send_digests {
327     my $params = shift;
328
329     PATRON: while ( my ( $borrowernumber, $digest ) = each %{$params->{digests}} ) {
330         next unless defined $digest->{updated} && $digest->{updated} == 1;
331         my $borrower_preferences =
332             C4::Members::Messaging::GetMessagingPreferences(
333                 {
334                     borrowernumber => $borrowernumber,
335                     message_name   => 'auto_renewals'
336                 }
337             );
338
339         next PATRON unless $borrower_preferences; # how could this happen?
340
341         my $patron = Koha::Patrons->find( $borrowernumber );
342         my $branchcode;
343         if ( defined $params->{branchcode} ) {
344             $branchcode = $params->{branchcode};
345         } else {
346             $branchcode = $patron->branchcode;
347         }
348         my $library = Koha::Libraries->find( $branchcode );
349         my $from_address = $library->from_email_address;
350
351         foreach my $transport ( keys %{ $borrower_preferences->{'transports'} } ) {
352             my $letter = C4::Letters::GetPreparedLetter (
353                 module => 'circulation',
354                 letter_code => $params->{letter_code},
355                 branchcode => $branchcode,
356                 lang => $patron->lang,
357                 substitute => {
358                     error => $digest->{error}||0,
359                     success => $digest->{success}||0,
360                     results => $digest->{results},
361                 },
362                 loops => { issues => \@{$digest->{issues}} },
363                 tables      => {
364                     borrowers => $patron->borrowernumber,
365                 },
366                 message_transport_type => $transport,
367             );
368
369             if ($letter) {
370                 C4::Letters::EnqueueLetter(
371                     {
372                         letter                 => $letter,
373                         borrowernumber         => $borrowernumber,
374                         from_address           => $from_address,
375                         message_transport_type => $transport
376                     }
377                 );
378             }
379             else {
380                 warn
381 "no letter of type '$params->{letter_code}' found for borrowernumber $borrowernumber. Please see sample_notices.sql";
382             }
383
384         }
385     }
386 }