3 # This file is part of Koha.
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.
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.
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>.
20 use Getopt::Long qw( GetOptions );
21 use Pod::Usage qw( pod2usage );
23 use DateTime::Duration;
27 use C4::Log qw( cronlogaction );
28 use Koha::DateUtils qw( dt_from_string );
31 use Koha::Notice::Templates;
33 use Koha::Script -cron;
37 holds_reminder.pl - prepare reminder messages to be sent to patrons with waiting holds
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> ]
48 -help brief help message
49 -man full documentation
51 -c --confirm confirm, if not set no email will be sent
52 -days <days> days waiting to deal with
53 -t --triggered include only holds <days> days waiting, and not longer
54 -lettercode <lettercode> predefined notice to use, default is HOLD_REMINDER
55 -library <branchname> only deal with holds from this library (repeatable : several libraries can be given)
56 -holidays use the calendar to not count holidays as waiting days
57 -mtt <message_transport_type> type of messages to send, default is to use patrons messaging preferences for Hold reminder
58 populating this will force send even if patron has not chosen to receive hold notices
59 email and sms will fallback to print if borrower does not have an address/phone
60 -date Send notices as would have been sent on a specific date
68 Print a brief help message and exits.
72 Prints the manual page and exits.
76 Verbose. Without this flag set, only fatal errors are reported.
78 =item B<--confirm | -c>
80 Confirm. Unless set this script does not send any email (test-mode).
81 If verbose and not confirm a list of notices that would have been sent to
82 the patrons are printed to standard out.
86 Optional parameter, number of days an items has been 'waiting' on hold
87 to send a message for. If not included a notice will be sent to all
88 patrons with waiting holds.
90 =item B<-t> | B<--triggered>
92 Optional parameter, only send notices for holds exactly <days> waiting.
93 If not included a notice will be sent to all patrons with waiting holds
94 equal to OR greater than <days>. This option is useful if the cron is
95 being run daily to avoid spamming the patrons.
99 Optional parameter, choose a notice to use. Default is 'HOLD_REMINDER'.
103 select notices for one specific library. Use the value in the
104 branches.branchcode table. This option can be repeated in order
105 to select notices for a group of libraries.
109 This option determines whether library holidays are used when calculating how
110 long an item has been waiting. If enabled the count will skip closed days.
114 use it in order to send notices on a specific date and not Now. Format: YYYY-MM-DD.
118 send a notices via a specific transport, this can be repeated to send various notices.
119 If omitted the patron's messaging preferences for Hold notices will be used.
120 If supplied the notice types will be force sent even if patron has not selected hold notices
121 Email and SMS will fall back to print if there is no valid info in the patron's account
128 This script is designed to alert patrons of waiting
133 This script sends reminders to patrons with waiting holds using a notice
134 defined in the Tools > Notices and slips module within Koha. The lettercode
135 is passed into this script and, along with other options, determine the content
136 of the notices sent to patrons.
139 =head1 USAGE EXAMPLES
141 C<holds_reminder.pl> - With no arguments the simple help is printed
143 C<holds_reminder.pl > In this most basic usage all
144 libraries are processed individually, and notices are prepared for
145 all patrons with waiting holds for whom we have email addresses.
146 Messages for those patrons for whom we have no email
147 address are sent in a single attachment to the library administrator's
148 email address, or to the address in the KohaAdminEmailAddress system
151 C<holds_reminder.pl -n -csv /tmp/holds_reminder.csv> - sends no email and
152 populates F</tmp/holds_reminder.csv> with information about all waiting holds
155 C<holds_reminder.pl -library MAIN -days 14> - prepare notices of
156 holds waiting for 2 weeks for the MAIN library.
158 C<holds_reminder.pl -lettercode LATE_HOLDS -library MAIN -days 14> - prepare notices of
159 holds waiting for 2 weeks for the MAIN library. Use lettercode 'LATE_HOLDS'
163 # These variables are set by command line options.
164 # They are initially set to default values.
165 my $dbh = C4::Context->dbh();
173 my @branchcodes; # Branch(es) passed as parameter
174 my $use_calendar = 0;
179 my $command_line_options = join(" ",@ARGV);
185 'c|confirm' => \$confirm,
187 't|triggered' => \$triggered,
188 'lettercode=s' => \$lettercode,
189 'library=s' => \@branchcodes,
190 'date=s' => \$date_input,
191 'holidays' => \$use_calendar,
194 pod2usage(1) if $help;
195 pod2usage( -verbose => 2 ) if $man;
197 $lettercode ||= 'HOLD_REMINDER';
199 cronlogaction({ info => $command_line_options });
201 # Unless a delay is specified by the user we target all waiting holds
202 unless (defined $days) {
206 # Unless one ore more branchcodes are passed we use all the branches
207 if (scalar @branchcodes > 0) {
208 my $branchcodes_word = scalar @branchcodes > 1 ? 'branches' : 'branch';
209 $verbose and warn "$branchcodes_word @branchcodes passed on parameter\n";
212 @branchcodes = Koha::Libraries->search()->get_column('branchcode');
215 # If provided we run the report as if it had run on a specified date
219 $date_to_run = dt_from_string( $date_input, 'iso' );
221 die "$date_input is not a valid date, aborting! Use a date in format YYYY-MM-DD."
222 if $@ or not $date_to_run;
225 $date_to_run = dt_from_string();
228 my $waiting_date_static = $date_to_run->clone->subtract( days => $days );
230 # Loop through each branch
231 foreach my $branchcode (@branchcodes) { #BEGIN BRANCH LOOP
232 # Check that this branch has the letter code specified or skip this branch
234 # FIXME What if we don't want to default if the translated template does not exist?
235 my $template_exists = Koha::Notice::Templates->find_effective_template(
237 module => 'reserves',
239 branchcode => $branchcode,
243 unless ($template_exists) {
244 $verbose and print qq|Message '$lettercode' content not found for $branchcode\n|;
248 # If respecting calendar get the correct waiting since date
251 my $calendar = Koha::Calendar->new( branchcode => $branchcode, days_mode => 'Calendar' );
252 my $duration = DateTime::Duration->new( days => -$days );
253 $waiting_date = $calendar->addDays($date_to_run,$duration); #Add negative of days
255 $waiting_date = $waiting_date_static;
258 # Find all the holds waiting since this date for the current branch
259 my $dtf = Koha::Database->new->schema->storage->datetime_parser;
260 my $waiting_since = $dtf->format_date( $waiting_date );
261 my $comparator = $triggered ? '=' : '<=';
262 my $reserves = Koha::Holds->search({
263 waitingdate => {$comparator => $waiting_since },
264 'me.branchcode' => $branchcode,
265 '-or' => [ expirationdate => undef, expirationdate => { '>' => \'CURDATE()' } ]
266 },{ prefetch => 'patron' });
268 $verbose and warn "No reserves found for $branchcode\n" unless $reserves->count;
269 next unless $reserves->count;
270 $verbose and warn $reserves->count . " reserves waiting since $waiting_since for $branchcode\n";
272 # We only want to send one notice per patron per branch - this variable will hold the completed borrowers
275 # If passed message transports we force use those, otherwise we will use the patrons preferences
276 # for the 'Hold_Reminder' notice
277 my $sending_params = @mtts ? { message_transports => \@mtts } : { message_name => "Hold_Reminder" };
281 while ( my $reserve = $reserves->next ) {
282 $patrons{$reserve->borrowernumber}->{patron} = $reserve->patron unless exists $patrons{$reserve->borrowernumber};
283 push @{$patrons{$reserve->borrowernumber}->{reserves}}, $reserve->reserve_id;
286 foreach my $borrowernumber (keys %patrons){
288 my $patron = $patrons{$borrowernumber}->{patron};
289 $verbose and print " borrower " . $patron->surname . ", " . $patron->firstname . " has " . scalar @{$patrons{$borrowernumber}->{reserves}} . " holds triggering notice.\n";
291 # Setup the notice information
292 my $letter_params = {
293 module => 'reserves',
294 letter_code => $lettercode,
295 borrowernumber => $patron->borrowernumber,
296 branchcode => $branchcode,
298 borrowers => $patron->borrowernumber,
299 branches => $branchcode,
302 reserves => $patrons{$borrowernumber}->{reserves}
305 $sending_params->{letter_params} = $letter_params;
306 $sending_params->{test_mode} = !$confirm;
307 my $result_text = $confirm ? "was sent" : "would have been sent";
308 # queue_notice queues the notices, falling back to print for email or SMS, and ignores phone (they are handled by Itiva)
309 my $result = $patron->queue_notice( $sending_params );
310 $verbose and print " borrower " . $patron->surname . ", " . $patron->firstname . " $result_text notices via: @{$result->{sent}}\n" if defined $result->{sent};
311 $verbose and print " borrower " . $patron->surname . ", " . $patron->firstname . " $result_text print fallback for: @{$result->{fallback}}\n" if defined $result->{fallback};
312 # Mark this borrower as completed
313 $done{$patron->borrowernumber} = 1;
319 cronlogaction({ action => 'End', info => "COMPLETED" });