Bug 25566: Add option to ignore found holds and use it when checking high holds
[koha.git] / Koha / Patrons.pm
1 package Koha::Patrons;
2
3 # Copyright 2014 ByWater Solutions
4 # Copyright 2016 Koha Development Team
5 #
6 # This file is part of Koha.
7 #
8 # Koha is free software; you can redistribute it and/or modify it under the
9 # terms of the GNU General Public License as published by the Free Software
10 # Foundation; either version 3 of the License, or (at your option) any later
11 # version.
12 #
13 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
14 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
15 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License along
18 # with Koha; if not, write to the Free Software Foundation, Inc.,
19 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20
21 use Modern::Perl;
22
23 use Carp;
24
25 use Koha::Database;
26 use Koha::DateUtils;
27
28 use Koha::ArticleRequests;
29 use Koha::ArticleRequest::Status;
30 use Koha::Patron;
31 use Koha::Exceptions::Patron;
32
33 use base qw(Koha::Objects);
34
35 =head1 NAME
36
37 Koha::Patron - Koha Patron Object class
38
39 =head1 API
40
41 =head2 Class Methods
42
43 =cut
44
45 =head3 search_limited
46
47 my $patrons = Koha::Patrons->search_limit( $params, $attributes );
48
49 Returns all the patrons the logged in user is allowed to see
50
51 =cut
52
53 sub search_limited {
54     my ( $self, $params, $attributes ) = @_;
55
56     my $userenv = C4::Context->userenv;
57     my @restricted_branchcodes;
58     if ( $userenv and $userenv->{number} ) {
59         my $logged_in_user = Koha::Patrons->find( $userenv->{number} );
60         @restricted_branchcodes = $logged_in_user->libraries_where_can_see_patrons;
61     }
62     $params->{'me.branchcode'} = { -in => \@restricted_branchcodes } if @restricted_branchcodes;
63     return $self->search( $params, $attributes );
64 }
65
66 =head3 search_housebound_choosers
67
68 Returns all Patrons which are Housebound choosers.
69
70 =cut
71
72 sub search_housebound_choosers {
73     my ( $self ) = @_;
74     my $cho = $self->_resultset
75         ->search_related('housebound_role', {
76             housebound_chooser => 1,
77         })->search_related('borrowernumber');
78     return Koha::Patrons->_new_from_dbic($cho);
79 }
80
81 =head3 search_housebound_deliverers
82
83 Returns all Patrons which are Housebound deliverers.
84
85 =cut
86
87 sub search_housebound_deliverers {
88     my ( $self ) = @_;
89     my $del = $self->_resultset
90         ->search_related('housebound_role', {
91             housebound_deliverer => 1,
92         })->search_related('borrowernumber');
93     return Koha::Patrons->_new_from_dbic($del);
94 }
95
96 =head3 search_upcoming_membership_expires
97
98 my $patrons = Koha::Patrons->search_upcoming_membership_expires();
99
100 The 'before' and 'after' represent the number of days before/after the date
101 that is set by the preference MembershipExpiryDaysNotice.
102 If the pref is 14, before 2 and after 3 then you will get all expires
103 from 12 to 17 days.
104
105 =cut
106
107 sub search_upcoming_membership_expires {
108     my ( $self, $params ) = @_;
109     my $before = $params->{before} || 0;
110     my $after  = $params->{after} || 0;
111     delete $params->{before};
112     delete $params->{after};
113
114     my $days = C4::Context->preference("MembershipExpiryDaysNotice") || 0;
115     my $date_before = dt_from_string->add( days => $days - $before );
116     my $date_after = dt_from_string->add( days => $days + $after );
117     my $dtf = Koha::Database->new->schema->storage->datetime_parser;
118
119     $params->{dateexpiry} = {
120         ">=" => $dtf->format_date( $date_before ),
121         "<=" => $dtf->format_date( $date_after ),
122     };
123     return $self->SUPER::search(
124         $params, { join => ['branchcode', 'categorycode'] }
125     );
126 }
127
128 =head3 guarantor
129
130 Returns a Koha::Patron object for this borrower's guarantor
131
132 =cut
133
134 sub guarantor {
135     my ( $self ) = @_;
136
137     return Koha::Patrons->find( $self->guarantorid() );
138 }
139
140 =head3 search_patrons_to_anonymise
141
142     my $patrons = Koha::Patrons->search_patrons_to_anonymise( { before => $older_than_date, [ library => $library ] } );
143
144 This method returns all patrons who has an issue history older than a given date.
145
146 =cut
147
148 sub search_patrons_to_anonymise {
149     my ( $class, $params ) = @_;
150     my $older_than_date = $params->{before};
151     my $library         = $params->{library};
152     $older_than_date = $older_than_date ? dt_from_string($older_than_date) : dt_from_string;
153     $library ||=
154       ( C4::Context->preference('IndependentBranches') && C4::Context->userenv && !C4::Context->IsSuperLibrarian() && C4::Context->userenv->{branch} )
155       ? C4::Context->userenv->{branch}
156       : undef;
157     my $anonymous_patron = C4::Context->preference('AnonymousPatron') || undef;
158
159     my $dtf = Koha::Database->new->schema->storage->datetime_parser;
160     my $rs = $class->_resultset->search(
161         {   returndate                  => { '<'   =>  $dtf->format_datetime($older_than_date), },
162             'old_issues.borrowernumber' => { 'not' => undef },
163             privacy                     => { '<>'  => 0 },                  # Keep forever
164             ( $library ? ( 'old_issues.branchcode' => $library ) : () ),
165             ( $anonymous_patron ? ( 'old_issues.borrowernumber' => { '!=' => $anonymous_patron } ) : () ),
166         },
167         {   join     => ["old_issues"],
168             distinct => 1,
169         }
170     );
171     return Koha::Patrons->_new_from_dbic($rs);
172 }
173
174 =head3 anonymise_issue_history
175
176     Koha::Patrons->search->anonymise_issue_history( { [ before => $older_than_date ] } );
177
178 Anonymise issue history (old_issues) for all patrons older than the given date (optional).
179 To make sure all the conditions are met, the caller has the responsibility to
180 call search_patrons_to_anonymise to filter the Koha::Patrons set
181
182 =cut
183
184 sub anonymise_issue_history {
185     my ( $self, $params ) = @_;
186
187     my $older_than_date = $params->{before};
188
189     $older_than_date = dt_from_string $older_than_date if $older_than_date;
190
191     # The default of 0 does not work due to foreign key constraints
192     # The anonymisation should not fail quietly if AnonymousPatron is not a valid entry
193     # Set it to undef (NULL)
194     my $dtf = Koha::Database->new->schema->storage->datetime_parser;
195     my $nb_rows = 0;
196     while ( my $patron = $self->next ) {
197         my $old_issues_to_anonymise = $patron->old_checkouts->search(
198         {
199             (
200                 $older_than_date
201                 ? ( returndate =>
202                       { '<' => $dtf->format_datetime($older_than_date) } )
203                 : ()
204             )
205         }
206         );
207         my $anonymous_patron = C4::Context->preference('AnonymousPatron') || undef;
208         $nb_rows += $old_issues_to_anonymise->update( { 'old_issues.borrowernumber' => $anonymous_patron } );
209     }
210     return $nb_rows;
211 }
212
213 =head3 delete
214
215     Koha::Patrons->search({ some filters here })->delete({ move => 1 });
216
217     Delete passed set of patron objects.
218     Wrapper for Koha::Patron->delete. (We do not want to bypass Koha::Patron
219     and let DBIx do the job without further housekeeping.)
220     Includes a move to deletedborrowers if move flag set.
221
222     Just like DBIx, the delete will only succeed when all entries could be
223     deleted. Returns true or throws an exception.
224
225 =cut
226
227 sub delete {
228     my ( $self, $params ) = @_;
229     my $patrons_deleted;
230     $self->_resultset->result_source->schema->txn_do( sub {
231         my ( $set, $params ) = @_;
232         my $count = $set->count;
233         while( my $patron = $set->next ) {
234             $patron->move_to_deleted if $params->{move};
235             $patron->delete == 1 || Koha::Exceptions::Patron::FailedDelete->throw;
236             $patrons_deleted++;
237         }
238     }, $self, $params );
239     return $patrons_deleted;
240 }
241
242 =head3 search_unsubscribed
243
244     Koha::Patrons->search_unsubscribed;
245
246     Returns a set of Koha patron objects for patrons that recently
247     unsubscribed and are not locked (candidates for locking).
248     Depends on UnsubscribeReflectionDelay.
249
250 =cut
251
252 sub search_unsubscribed {
253     my ( $class ) = @_;
254
255     my $delay = C4::Context->preference('UnsubscribeReflectionDelay');
256     if( !defined($delay) || $delay eq q{} ) {
257         # return empty set
258         return $class->search({ borrowernumber => undef });
259     }
260     my $parser = Koha::Database->new->schema->storage->datetime_parser;
261     my $dt = dt_from_string()->subtract( days => $delay );
262     my $str = $parser->format_datetime($dt);
263     my $fails = C4::Context->preference('FailedLoginAttempts') || 0;
264     my $cond = [ undef, 0, 1..$fails-1 ]; # NULL, 0, 1..fails-1 (if fails>0)
265     return $class->search(
266         {
267             'patron_consents.refused_on' => { '<=' => $str },
268             'login_attempts' => $cond,
269         },
270         { join => 'patron_consents' },
271     );
272 }
273
274 =head3 search_anonymize_candidates
275
276     Koha::Patrons->search_anonymize_candidates({ locked => 1 });
277
278     Returns a set of Koha patron objects for patrons whose account is expired
279     and locked (if parameter set). These are candidates for anonymizing.
280     Depends on PatronAnonymizeDelay.
281
282 =cut
283
284 sub search_anonymize_candidates {
285     my ( $class, $params ) = @_;
286
287     my $delay = C4::Context->preference('PatronAnonymizeDelay');
288     if( !defined($delay) || $delay eq q{} ) {
289         # return empty set
290         return $class->search({ borrowernumber => undef });
291     }
292     my $cond = {};
293     my $parser = Koha::Database->new->schema->storage->datetime_parser;
294     my $dt = dt_from_string()->subtract( days => $delay );
295     my $str = $parser->format_datetime($dt);
296     $cond->{dateexpiry} = { '<=' => $str };
297     $cond->{anonymized} = 0; # not yet done
298     if( $params->{locked} ) {
299         my $fails = C4::Context->preference('FailedLoginAttempts') || 0;
300         $cond->{login_attempts} = [ -and => { '!=' => undef }, { -not_in => [0, 1..$fails-1 ] } ]; # -not_in does not like undef
301     }
302     return $class->search( $cond );
303 }
304
305 =head3 search_anonymized
306
307     Koha::Patrons->search_anonymized;
308
309     Returns a set of Koha patron objects for patron accounts that have been
310     anonymized before and could be removed.
311     Depends on PatronRemovalDelay.
312
313 =cut
314
315 sub search_anonymized {
316     my ( $class ) = @_;
317
318     my $delay = C4::Context->preference('PatronRemovalDelay');
319     if( !defined($delay) || $delay eq q{} ) {
320         # return empty set
321         return $class->search({ borrowernumber => undef });
322     }
323     my $cond = {};
324     my $parser = Koha::Database->new->schema->storage->datetime_parser;
325     my $dt = dt_from_string()->subtract( days => $delay );
326     my $str = $parser->format_datetime($dt);
327     $cond->{dateexpiry} = { '<=' => $str };
328     $cond->{anonymized} = 1;
329     return $class->search( $cond );
330 }
331
332 =head3 lock
333
334     Koha::Patrons->search({ some filters })->lock({ expire => 1, remove => 1 })
335
336     Lock the passed set of patron objects. Optionally expire and remove holds.
337     Wrapper around Koha::Patron->lock.
338
339 =cut
340
341 sub lock {
342     my ( $self, $params ) = @_;
343     my $count = $self->count;
344     while( my $patron = $self->next ) {
345         $patron->lock($params);
346     }
347 }
348
349 =head3 anonymize
350
351     Koha::Patrons->search({ some filters })->anonymize();
352
353     Anonymize passed set of patron objects.
354     Wrapper around Koha::Patron->anonymize.
355
356 =cut
357
358 sub anonymize {
359     my ( $self ) = @_;
360     my $count = $self->count;
361     while( my $patron = $self->next ) {
362         $patron->anonymize;
363     }
364 }
365
366 =head3 _type
367
368 =cut
369
370 sub _type {
371     return 'Borrower';
372 }
373
374 sub object_class {
375     return 'Koha::Patron';
376 }
377
378 =head1 AUTHOR
379
380 Kyle M Hall <kyle@bywatersolutions.com>
381
382 =cut
383
384 1;