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