Bug 17600: Standardize our EXPORT_OK
[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 qw( GetOptions );
29 use Pod::Usage qw( pod2usage );
30 use DateTime;
31 use DateTime::Duration;
32
33 use C4::Context;
34 use C4::Letters;
35 use C4::Log qw( cronlogaction );
36 use Koha::DateUtils qw( dt_from_string );
37 use Koha::Calendar;
38 use Koha::Libraries;
39 use Koha::Notice::Templates;
40 use Koha::Patrons;
41 use Koha::Script -cron;
42
43 =head1 NAME
44
45 holds_reminder.pl - prepare reminder messages to be sent to patrons with waiting holds
46
47 =head1 SYNOPSIS
48
49 holds_reminder.pl
50   -lettercode <notice to send>
51   [ -c ][ -v ][ -library <branchcode> ][ -library <branchcode> ... ]
52   [ -days <number of days> ][ -mtt <transport type> ... ][ -holidays ]
53   [ -date <YYYY-MM-DD> ]
54
55  Options:
56    -help                          brief help message
57    -man                           full documentation
58    -v                             verbose
59    -c --confirm                   confirm, if not set no email will be sent
60    -days          <days>          days waiting to deal with
61    -lettercode   <lettercode>     predefined notice to use, default is HOLD_REMINDER
62    -library      <branchname>     only deal with holds from this library (repeatable : several libraries can be given)
63    -holidays                      use the calendar to not count holidays as waiting days
64    -mtt          <message_transport_type> type of messages to send, default is to use patrons messaging preferences for Hold filled
65                                   populating this will force send even if patron has not chosen to receive hold notices
66                                   email and sms will fallback to print if borrower does not have an address/phone
67    -date                          Send notices as would have been sent on a specific date
68
69 =head1 OPTIONS
70
71 =over 8
72
73 =item B<-help>
74
75 Print a brief help message and exits.
76
77 =item B<-man>
78
79 Prints the manual page and exits.
80
81 =item B<-v>
82
83 Verbose. Without this flag set, only fatal errors are reported.
84
85 =item B<--confirm | -c>
86
87 Confirm. Unless set this script does not send any email (test-mode). 
88 If verbose and not confirm a list of notices that would have been sent to
89 the patrons are printed to standard out.
90
91 =item B<-days>
92
93 Optional parameter, number of days an items has been 'waiting' on hold
94 to send a message for. If not included a notice will be sent to all
95 patrons with waiting holds.
96
97 =item B<-lettercode>
98
99 Optional parameter, choose a notice to use. Default is 'HOLD_REMINDER'.
100
101 =item B<-library>
102
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.
106
107 =item B<-holidays>
108
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.
111
112 =item B<-date>
113
114 use it in order to send notices on a specific date and not Now. Format: YYYY-MM-DD.
115
116 =item B<-mtt>
117
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
122
123
124 =back
125
126 =head1 DESCRIPTION
127
128 This script is designed to alert patrons of waiting
129 holds.
130
131 =head2 Configuration
132
133 This script sends reminders to patrons with waiting holds using a notice
134 defined in the Tools->Notices & 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.
137
138
139 =head1 USAGE EXAMPLES
140
141 C<holds_reminder.pl> - With no arguments the simple help is printed
142
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
149 preference.
150
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
153 items.
154
155 C<holds_reminder.pl -library MAIN -days 14> - prepare notices of
156 holds waiting for 2 weeks for the MAIN library.
157
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'
160
161 =cut
162
163 # These variables are set by command line options.
164 # They are initially set to default values.
165 my $dbh = C4::Context->dbh();
166 my $help    = 0;
167 my $man     = 0;
168 my $verbose = 0;
169 my $confirm  = 0;
170 my $days    ;
171 my $lettercode;
172 my @branchcodes; # Branch(es) passed as parameter
173 my $use_calendar = 0;
174 my $date_input;
175 my $opt_out = 0;
176 my @mtts;
177
178 GetOptions(
179     'help|?'         => \$help,
180     'man'            => \$man,
181     'v'              => \$verbose,
182     'c|confirm'      => \$confirm,
183     'days=s'         => \$days,
184     'lettercode=s'   => \$lettercode,
185     'library=s'      => \@branchcodes,
186     'date=s'         => \$date_input,
187     'holidays'       => \$use_calendar,
188     'mtt=s'          => \@mtts
189 );
190 pod2usage(1) if $help;
191 pod2usage( -verbose => 2 ) if $man;
192
193 $lettercode ||= 'HOLD_REMINDER';
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
228     # FIXME What if we don't want to default if the translated template does not exist?
229     my $template_exists = Koha::Notice::Templates->search(
230         {
231             module     => 'reserves',
232             code       => $lettercode,
233             branchcode => $branchcode,
234             lang       => 'default',
235         }
236     )->count;
237     unless ($template_exists) {
238         $verbose and print qq|Message '$lettercode' content not found for $branchcode\n|;
239         next;
240     }
241
242     # If respecting calendar get the correct waiting since date
243     my $waiting_date;
244     if( $use_calendar ){
245         my $calendar = Koha::Calendar->new( branchcode => $branchcode, days_mode => 'Calendar' );
246         my $duration = DateTime::Duration->new( days => -$days );
247         $waiting_date = $calendar->addDays($date_to_run,$duration); #Add negative of days
248     } else {
249         $waiting_date = $date_to_run->subtract( days => $days );
250     }
251
252     # Find all the holds waiting since this date for the current branch
253     my $dtf = Koha::Database->new->schema->storage->datetime_parser;
254     my $waiting_since = $dtf->format_date( $waiting_date );
255     my $reserves = Koha::Holds->search({
256         waitingdate => {'<=' => $waiting_since },
257         'me.branchcode'  => $branchcode,
258     },{ prefetch => 'patron' });
259
260     $verbose and warn "No reserves found for $branchcode\n" unless $reserves->count;
261     next unless $reserves->count;
262     $verbose and warn $reserves->count . " reserves waiting since $waiting_since for $branchcode\n";
263
264     # We only want to send one notice per patron per branch - this variable will hold the completed borrowers
265     my %done;
266
267     # If passed message transports we force use those, otherwise we will use the patrons preferences
268     # for the 'Hold_Filled' notice
269     my $sending_params = @mtts ? { message_transports => \@mtts } : { message_name => "Hold_Filled" };
270
271
272     my %patrons;
273     while ( my $reserve = $reserves->next ) {
274         $patrons{$reserve->borrowernumber}->{patron} = $reserve->patron unless exists $patrons{$reserve->borrowernumber};
275         push @{$patrons{$reserve->borrowernumber}->{reserves}}, $reserve->reserve_id;
276     }
277
278     foreach my $borrowernumber (keys %patrons){
279
280         my $patron = $patrons{$borrowernumber}->{patron};
281         $verbose and print "  borrower " . $patron->surname . ", " . $patron->firstname . " has " . scalar @{$patrons{$borrowernumber}->{reserves}}  . " holds triggering notice.\n";
282
283         # Setup the notice information
284         my $letter_params = {
285             module          => 'reserves',
286             letter_code     => $lettercode,
287             borrowernumber  => $patron->borrowernumber,
288             branchcode      => $branchcode,
289             tables          => {
290                  borrowers  => $patron->borrowernumber,
291                  branches   => $branchcode,
292             },
293             loops           => {
294                 reserves    => $patrons{$borrowernumber}->{reserves}
295             },
296         };
297         $sending_params->{letter_params} = $letter_params;
298         $sending_params->{test_mode} = !$confirm;
299         my $result_text = $confirm ? "was sent" : "would have been sent";
300         # queue_notice queues the notices, falling back to print for email or SMS, and ignores phone (they are handled by Itiva)
301         my $result = $patron->queue_notice( $sending_params );
302         $verbose and print "   borrower " . $patron->surname . ", " . $patron->firstname . " $result_text notices via: @{$result->{sent}}\n" if defined $result->{sent};
303         $verbose and print "   borrower " . $patron->surname . ", " . $patron->firstname . " $result_text print fallback for: @{$result->{fallback}}\n" if defined $result->{fallback};
304         # Mark this borrower as completed
305         $done{$patron->borrowernumber} = 1;
306     }
307
308
309 } #END BRANCH LOOP