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