Bug 33522: Add active and inactive parameter to membership_expiry.pl
[koha.git] / misc / cronjobs / membership_expiry.pl
1 #!/usr/bin/perl
2
3 # This file is part of Koha.
4 #
5 # Copyright 2023 Koha development team
6 # Copyright 2015 Amit Gupta (amitddng135@gmail.com)
7 #
8 # Koha is free software; you can redistribute it and/or modify it
9 # under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # Koha is distributed in the hope that it will be useful, but
14 # WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the GNU General Public License
19 # along with Koha; if not, see <http://www.gnu.org/licenses>.
20
21 =head1 NAME
22
23 membership_expiry.pl - cron script to put membership expiry reminders into the message queue
24
25 =head1 SYNOPSIS
26
27 ./membership_expiry.pl -c
28
29 or, in crontab:
30
31 0 1 * * * membership_expiry.pl -c
32
33 Options:
34    --help                   brief help message
35    --man                    full documentation
36    --where <conditions>     where clause to add to the query
37    -v -verbose              verbose mode
38    -n --nomail              if supplied, messages will be output to STDOUT and no email or sms will be sent
39    -c --confirm             commit changes to db, no action will be taken unless this switch is included
40    -b --branch <branchname> only deal with patrons from this library/branch
41    --before=X               include patrons expiring a number of days BEFORE the date set by the preference
42    --after=X                include patrons expiring a number of days AFTER  the date set by the preference
43    -l --letter <lettercode> use a specific notice rather than the default
44    --active=X               only send notices to active patrons (active within X months)
45    --inactive=X             only send notices to inactive patrons (inactive within X months)
46
47 =head1 DESCRIPTION
48
49 This script sends membership expiry reminder notices to patrons, by email and sms.
50 It queues them in the message queue, which is processed by
51 the process_message_queue.pl cronjob.
52
53 =head1 OPTIONS
54
55 =over 8
56
57 =item B<--help>
58
59 Print a brief help message and exits.
60
61 =item B<--man>
62
63 Prints the manual page and exits.
64
65 =item B<-v>
66
67 Verbose. Without this flag set, only fatal errors are reported.
68
69 =item B<-n>
70
71 Do not send any email. Membership expire notices that would have been sent to
72 the patrons are printed to standard out.
73
74 =item B<-c>
75
76 Confirm flag: Add this option. The script will only print a usage
77 statement otherwise.
78
79 =item B<-branch>
80
81 Optional branchcode to restrict the cronjob to that branch.
82
83 =item B<-before>
84
85 Optional parameter to extend the selection with a number of days BEFORE
86 the date set by the preference.
87
88 =item B<-after>
89
90 Optional parameter to extend the selection with a number of days AFTER
91 the date set by the preference.
92
93 =item B<-where>
94
95 Use this option to specify a condition built with columns from the borrowers table
96
97 e.g.
98 --where 'lastseen IS NOT NULL'
99 will only notify patrons who have been seen.
100
101 =item B<-letter>
102
103 Optional parameter to use another notice than the default: MEMBERSHIP_EXPIRY
104
105 =item B<-active>
106
107 Optional parameter to include active patrons only (active within passed number of months).
108
109 =item B<-inactive>
110
111 Optional parameter to include inactive patrons only (inactive within passed number of months).
112 This allows you to skip active patrons when you renew them automatically (see bug 28688).
113
114 =back
115
116 =head1 CONFIGURATION
117
118 The content of the messages is configured in Tools -> Notices and slips. Use the MEMBERSHIP_EXPIRY notice or
119 supply another via the parameters.
120
121 Typically, messages are prepared for each patron when the memberships are going to expire.
122
123 These emails are staged in the outgoing message queue, as are messages
124 produced by other features of Koha. This message queue must be
125 processed regularly by the
126 F<misc/cronjobs/process_message_queue.pl> program.
127
128 In the event that the C<-n> flag is passed to this program, no emails
129 are sent. Instead, messages are sent on standard output from this
130 program.
131
132 Notices can contain variables enclosed in double angle brackets like
133 E<lt>E<lt>thisE<gt>E<gt>. Those variables will be replaced with values
134 specific to the soon expiring members.
135 Available variables are:
136
137 =over
138
139 =item E<lt>E<lt>borrowers.*E<gt>E<gt>
140
141 any field from the borrowers table
142
143 =item E<lt>E<lt>branches.*E<gt>E<gt>
144
145 any field from the branches table
146
147 =back
148
149 =cut
150
151 use Modern::Perl;
152 use Getopt::Long qw( GetOptions );
153 use Pod::Usage qw( pod2usage );
154
155 use Koha::Script -cron;
156 use C4::Context;
157 use C4::Letters;
158 use C4::Log qw( cronlogaction );
159
160 use Koha::Patrons;
161
162 # These are defaults for command line options.
163 my $confirm;                              # -c: Confirm that the user has read and configured this script.
164 my $nomail;                               # -n: No mail. Will not send any emails.
165 my $verbose = 0;                           # -v: verbose
166 my $help    = 0;
167 my $man     = 0;
168 my $before  = 0;
169 my $after   = 0;
170 my ( $branch, $letter_type );
171 my @where;
172 my $active;
173 my $inactive;
174
175 my $command_line_options = join(" ",@ARGV);
176
177 GetOptions(
178     'help|?'         => \$help,
179     'man'            => \$man,
180     'c'              => \$confirm,
181     'n'              => \$nomail,
182     'v'              => \$verbose,
183     'branch:s'       => \$branch,
184     'before:i'       => \$before,
185     'after:i'        => \$after,
186     'letter:s'       => \$letter_type,
187     'where=s'        => \@where,
188     'active:i'       => \$active,
189     'inactive:i'     => \$inactive,
190 ) or pod2usage(2);
191
192 pod2usage( -verbose => 2 ) if $man;
193 pod2usage(1) if $help || !$confirm;
194 if( defined($active) && defined($inactive) ) {
195     print "Sorry, it is not possible to pass both -active as well as -inactive.\n";
196     exit;
197 }
198
199 cronlogaction({ info => $command_line_options });
200
201 my $expdays = C4::Context->preference('MembershipExpiryDaysNotice');
202 if( !$expdays ) {
203     #If the pref is not set, we will exit
204     warn 'Exiting membership_expiry.pl: MembershipExpiryDaysNotice not set'
205         if $verbose;
206     exit;
207 }
208
209 warn 'getting upcoming membership expires' if $verbose;
210 my $upcoming_mem_expires = Koha::Patrons->search_upcoming_membership_expires(
211     {
212         ( $branch ? ( 'me.branchcode' => $branch ) : () ),
213         before => $before,
214         after  => $after,
215     }
216 );
217
218 my $where_literal = join ' AND ', @where;
219 $upcoming_mem_expires = $upcoming_mem_expires->search( \$where_literal ) if @where;
220
221 warn 'found ' . $upcoming_mem_expires->count . ' soon expiring members'
222     if $verbose;
223
224 # main loop
225 $letter_type = 'MEMBERSHIP_EXPIRY' if !$letter_type;
226 my ( $count_active, $count_inactive, $count_enqueued ) = ( 0, 0, 0 );
227 while ( my $recent = $upcoming_mem_expires->next ) {
228     my $patron_active = $recent->is_active({ months => $active // $inactive }); # checked already that only one is defined
229     if( defined($active) && !$patron_active ) {
230         $count_inactive++;
231         next;
232     } elsif( defined($inactive) && $patron_active ) {
233         $count_active++;
234         next;
235     }
236     my $from_address = $recent->library->from_email_address;
237     my $letter =  C4::Letters::GetPreparedLetter(
238         module      => 'members',
239         letter_code => $letter_type,
240         branchcode  => $recent->branchcode,
241         lang        => $recent->lang,
242         tables      => {
243             borrowers => $recent->borrowernumber,
244             branches  => $recent->branchcode,
245         },
246     );
247     last if !$letter; # Letters.pm already warned, just exit
248     if( $nomail ) {
249         print $letter->{'content'}."\n";
250         next;
251     }
252
253     C4::Letters::EnqueueLetter({
254         letter                 => $letter,
255         borrowernumber         =>  $recent->borrowernumber,
256         from_address           => $from_address,
257         message_transport_type => 'email',
258     });
259     $count_enqueued++;
260
261     if ($recent->smsalertnumber) {
262         my $smsletter = C4::Letters::GetPreparedLetter(
263             module      => 'members',
264             letter_code => $letter_type,
265             branchcode  => $recent->branchcode,
266             lang        => $recent->lang,
267             tables      => {
268                 borrowers => $recent->borrowernumber,
269                 branches  => $recent->branchcode,
270             },
271             message_transport_type => 'sms',
272         );
273         if ($smsletter) {
274             C4::Letters::EnqueueLetter({
275                 letter                 => $smsletter,
276                 borrowernumber         => $recent->borrowernumber,
277                 message_transport_type => 'sms',
278             });
279         }
280     }
281 }
282
283 if( $verbose ) {
284     print "Enqueued notices for $count_enqueued patrons\n";
285     print "Skipped $count_active active patrons\n" if $count_active;
286     print "Skipped $count_inactive inactive patrons\n" if $count_inactive;
287 }
288
289 cronlogaction({ action => 'End', info => "COMPLETED" });