Bug 28617: Remove kohalib.pl and rely on PERL5LIB
[koha.git] / misc / cronjobs / holds / holds_reminder.pl
1 #!/usr/bin/perl
2
3 # This file is part of Koha.
4 #
5 # Koha is free software; you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 3 of the License, or
8 # (at your option) any later version.
9 #
10 # Koha is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18 use Modern::Perl;
19
20 use Getopt::Long qw( GetOptions );
21 use Pod::Usage qw( pod2usage );
22 use DateTime;
23 use DateTime::Duration;
24
25 use C4::Context;
26 use C4::Letters;
27 use C4::Log qw( cronlogaction );
28 use Koha::DateUtils qw( dt_from_string );
29 use Koha::Calendar;
30 use Koha::Libraries;
31 use Koha::Notice::Templates;
32 use Koha::Patrons;
33 use Koha::Script -cron;
34
35 =head1 NAME
36
37 holds_reminder.pl - prepare reminder messages to be sent to patrons with waiting holds
38
39 =head1 SYNOPSIS
40
41 holds_reminder.pl
42   -lettercode <notice to send>
43   [ -c ][ -v ][ -library <branchcode> ][ -library <branchcode> ... ]
44   [ -days <number of days> ][ -mtt <transport type> ... ][ -holidays ]
45   [ -date <YYYY-MM-DD> ]
46
47  Options:
48    -help                          brief help message
49    -man                           full documentation
50    -v                             verbose
51    -c --confirm                   confirm, if not set no email will be sent
52    -days          <days>          days waiting to deal with
53    -lettercode   <lettercode>     predefined notice to use, default is HOLD_REMINDER
54    -library      <branchname>     only deal with holds from this library (repeatable : several libraries can be given)
55    -holidays                      use the calendar to not count holidays as waiting days
56    -mtt          <message_transport_type> type of messages to send, default is to use patrons messaging preferences for Hold reminder
57                                   populating this will force send even if patron has not chosen to receive hold notices
58                                   email and sms will fallback to print if borrower does not have an address/phone
59    -date                          Send notices as would have been sent on a specific date
60
61 =head1 OPTIONS
62
63 =over 8
64
65 =item B<-help>
66
67 Print a brief help message and exits.
68
69 =item B<-man>
70
71 Prints the manual page and exits.
72
73 =item B<-v>
74
75 Verbose. Without this flag set, only fatal errors are reported.
76
77 =item B<--confirm | -c>
78
79 Confirm. Unless set this script does not send any email (test-mode). 
80 If verbose and not confirm a list of notices that would have been sent to
81 the patrons are printed to standard out.
82
83 =item B<-days>
84
85 Optional parameter, number of days an items has been 'waiting' on hold
86 to send a message for. If not included a notice will be sent to all
87 patrons with waiting holds.
88
89 =item B<-lettercode>
90
91 Optional parameter, choose a notice to use. Default is 'HOLD_REMINDER'.
92
93 =item B<-library>
94
95 select notices for one specific library. Use the value in the
96 branches.branchcode table. This option can be repeated in order
97 to select notices for a group of libraries.
98
99 =item B<-holidays>
100
101 This option determines whether library holidays are used when calculating how
102 long an item has been waiting. If enabled the count will skip closed days.
103
104 =item B<-date>
105
106 use it in order to send notices on a specific date and not Now. Format: YYYY-MM-DD.
107
108 =item B<-mtt>
109
110 send a notices via a specific transport, this can be repeated to send various notices.
111 If omitted the patron's messaging preferences for Hold notices will be used.
112 If supplied the notice types will be force sent even if patron has not selected hold notices
113 Email and SMS will fall back to print if there is no valid info in the patron's account
114
115
116 =back
117
118 =head1 DESCRIPTION
119
120 This script is designed to alert patrons of waiting
121 holds.
122
123 =head2 Configuration
124
125 This script sends reminders to patrons with waiting holds using a notice
126 defined in the Tools->Notices & slips module within Koha. The lettercode
127 is passed into this script and, along with other options, determine the content
128 of the notices sent to patrons.
129
130
131 =head1 USAGE EXAMPLES
132
133 C<holds_reminder.pl> - With no arguments the simple help is printed
134
135 C<holds_reminder.pl > In this most basic usage all
136 libraries are processed individually, and notices are prepared for
137 all patrons with waiting holds for whom we have email addresses.
138 Messages for those patrons for whom we have no email
139 address are sent in a single attachment to the library administrator's
140 email address, or to the address in the KohaAdminEmailAddress system
141 preference.
142
143 C<holds_reminder.pl -n -csv /tmp/holds_reminder.csv> - sends no email and
144 populates F</tmp/holds_reminder.csv> with information about all waiting holds
145 items.
146
147 C<holds_reminder.pl -library MAIN -days 14> - prepare notices of
148 holds waiting for 2 weeks for the MAIN library.
149
150 C<holds_reminder.pl -lettercode LATE_HOLDS -library MAIN -days 14> - prepare notices of
151 holds waiting for 2 weeks for the MAIN library. Use lettercode 'LATE_HOLDS'
152
153 =cut
154
155 # These variables are set by command line options.
156 # They are initially set to default values.
157 my $dbh = C4::Context->dbh();
158 my $help    = 0;
159 my $man     = 0;
160 my $verbose = 0;
161 my $confirm  = 0;
162 my $days    ;
163 my $lettercode;
164 my @branchcodes; # Branch(es) passed as parameter
165 my $use_calendar = 0;
166 my $date_input;
167 my $opt_out = 0;
168 my @mtts;
169
170 GetOptions(
171     'help|?'         => \$help,
172     'man'            => \$man,
173     'v'              => \$verbose,
174     'c|confirm'      => \$confirm,
175     'days=s'         => \$days,
176     'lettercode=s'   => \$lettercode,
177     'library=s'      => \@branchcodes,
178     'date=s'         => \$date_input,
179     'holidays'       => \$use_calendar,
180     'mtt=s'          => \@mtts
181 );
182 pod2usage(1) if $help;
183 pod2usage( -verbose => 2 ) if $man;
184
185 $lettercode ||= 'HOLD_REMINDER';
186
187 cronlogaction();
188
189 # Unless a delay is specified by the user we target all waiting holds
190 unless (defined $days) {
191     $days=0;
192 }
193
194 # Unless one ore more branchcodes are passed we use all the branches
195 if (scalar @branchcodes > 0) {
196     my $branchcodes_word = scalar @branchcodes > 1 ? 'branches' : 'branch';
197     $verbose and warn "$branchcodes_word @branchcodes passed on parameter\n";
198 }
199 else {
200     @branchcodes = Koha::Libraries->search()->get_column('branchcode');
201 }
202
203 # If provided we run the report as if it had run on a specified date
204 my $date_to_run;
205 if ( $date_input ){
206     eval {
207         $date_to_run = dt_from_string( $date_input, 'iso' );
208     };
209     die "$date_input is not a valid date, aborting! Use a date in format YYYY-MM-DD."
210         if $@ or not $date_to_run;
211 }
212 else {
213     $date_to_run = dt_from_string();
214 }
215
216 # Loop through each branch
217 foreach my $branchcode (@branchcodes) { #BEGIN BRANCH LOOP
218     # Check that this branch has the letter code specified or skip this branch
219
220     # FIXME What if we don't want to default if the translated template does not exist?
221     my $template_exists = Koha::Notice::Templates->search(
222         {
223             module     => 'reserves',
224             code       => $lettercode,
225             branchcode => $branchcode,
226             lang       => 'default',
227         }
228     )->count;
229     unless ($template_exists) {
230         $verbose and print qq|Message '$lettercode' content not found for $branchcode\n|;
231         next;
232     }
233
234     # If respecting calendar get the correct waiting since date
235     my $waiting_date;
236     if( $use_calendar ){
237         my $calendar = Koha::Calendar->new( branchcode => $branchcode, days_mode => 'Calendar' );
238         my $duration = DateTime::Duration->new( days => -$days );
239         $waiting_date = $calendar->addDays($date_to_run,$duration); #Add negative of days
240     } else {
241         $waiting_date = $date_to_run->subtract( days => $days );
242     }
243
244     # Find all the holds waiting since this date for the current branch
245     my $dtf = Koha::Database->new->schema->storage->datetime_parser;
246     my $waiting_since = $dtf->format_date( $waiting_date );
247     my $reserves = Koha::Holds->search({
248         waitingdate => {'<=' => $waiting_since },
249         'me.branchcode'  => $branchcode,
250     },{ prefetch => 'patron' });
251
252     $verbose and warn "No reserves found for $branchcode\n" unless $reserves->count;
253     next unless $reserves->count;
254     $verbose and warn $reserves->count . " reserves waiting since $waiting_since for $branchcode\n";
255
256     # We only want to send one notice per patron per branch - this variable will hold the completed borrowers
257     my %done;
258
259     # If passed message transports we force use those, otherwise we will use the patrons preferences
260     # for the 'Hold_Reminder' notice
261     my $sending_params = @mtts ? { message_transports => \@mtts } : { message_name => "Hold_Reminder" };
262
263
264     my %patrons;
265     while ( my $reserve = $reserves->next ) {
266         $patrons{$reserve->borrowernumber}->{patron} = $reserve->patron unless exists $patrons{$reserve->borrowernumber};
267         push @{$patrons{$reserve->borrowernumber}->{reserves}}, $reserve->reserve_id;
268     }
269
270     foreach my $borrowernumber (keys %patrons){
271
272         my $patron = $patrons{$borrowernumber}->{patron};
273         $verbose and print "  borrower " . $patron->surname . ", " . $patron->firstname . " has " . scalar @{$patrons{$borrowernumber}->{reserves}}  . " holds triggering notice.\n";
274
275         # Setup the notice information
276         my $letter_params = {
277             module          => 'reserves',
278             letter_code     => $lettercode,
279             borrowernumber  => $patron->borrowernumber,
280             branchcode      => $branchcode,
281             tables          => {
282                  borrowers  => $patron->borrowernumber,
283                  branches   => $branchcode,
284             },
285             loops           => {
286                 reserves    => $patrons{$borrowernumber}->{reserves}
287             },
288         };
289         $sending_params->{letter_params} = $letter_params;
290         $sending_params->{test_mode} = !$confirm;
291         my $result_text = $confirm ? "was sent" : "would have been sent";
292         # queue_notice queues the notices, falling back to print for email or SMS, and ignores phone (they are handled by Itiva)
293         my $result = $patron->queue_notice( $sending_params );
294         $verbose and print "   borrower " . $patron->surname . ", " . $patron->firstname . " $result_text notices via: @{$result->{sent}}\n" if defined $result->{sent};
295         $verbose and print "   borrower " . $patron->surname . ", " . $patron->firstname . " $result_text print fallback for: @{$result->{fallback}}\n" if defined $result->{fallback};
296         # Mark this borrower as completed
297         $done{$patron->borrowernumber} = 1;
298     }
299
300
301 } #END BRANCH LOOP