Bug 23916: (follow-up) Don't anonymise issuer and don't update action_logs on upgrade
[srvgit] / 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
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 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 use Koha::Patron::Categories;
33 use Date::Calc qw( Today Add_Delta_YMD );
34
35 use base qw(Koha::Objects);
36
37 =head1 NAME
38
39 Koha::Patron - Koha Patron Object class
40
41 =head1 API
42
43 =head2 Class Methods
44
45 =cut
46
47 =head3 search_limited
48
49 my $patrons = Koha::Patrons->search_limit( $params, $attributes );
50
51 Returns all the patrons the logged in user is allowed to see
52
53 =cut
54
55 sub search_limited {
56     my ( $self, $params, $attributes ) = @_;
57
58     my $userenv = C4::Context->userenv;
59     my @restricted_branchcodes;
60     if ( $userenv and $userenv->{number} ) {
61         my $logged_in_user = Koha::Patrons->find( $userenv->{number} );
62         @restricted_branchcodes = $logged_in_user->libraries_where_can_see_patrons;
63     }
64     $params->{'me.branchcode'} = { -in => \@restricted_branchcodes } if @restricted_branchcodes;
65     return $self->search( $params, $attributes );
66 }
67
68 =head3 search_housebound_choosers
69
70 Returns all Patrons which are Housebound choosers.
71
72 =cut
73
74 sub search_housebound_choosers {
75     my ( $self ) = @_;
76     my $cho = $self->_resultset
77         ->search_related('housebound_role', {
78             housebound_chooser => 1,
79         })->search_related('borrowernumber');
80     return Koha::Patrons->_new_from_dbic($cho);
81 }
82
83 =head3 search_housebound_deliverers
84
85 Returns all Patrons which are Housebound deliverers.
86
87 =cut
88
89 sub search_housebound_deliverers {
90     my ( $self ) = @_;
91     my $del = $self->_resultset
92         ->search_related('housebound_role', {
93             housebound_deliverer => 1,
94         })->search_related('borrowernumber');
95     return Koha::Patrons->_new_from_dbic($del);
96 }
97
98 =head3 search_upcoming_membership_expires
99
100 my $patrons = Koha::Patrons->search_upcoming_membership_expires();
101
102 The 'before' and 'after' represent the number of days before/after the date
103 that is set by the preference MembershipExpiryDaysNotice.
104 If the pref is 14, before 2 and after 3 then you will get all expires
105 from 12 to 17 days.
106
107 =cut
108
109 sub search_upcoming_membership_expires {
110     my ( $self, $params ) = @_;
111     my $before = $params->{before} || 0;
112     my $after  = $params->{after} || 0;
113     delete $params->{before};
114     delete $params->{after};
115
116     my $days = C4::Context->preference("MembershipExpiryDaysNotice") || 0;
117     my $date_before = dt_from_string->add( days => $days - $before );
118     my $date_after = dt_from_string->add( days => $days + $after );
119     my $dtf = Koha::Database->new->schema->storage->datetime_parser;
120
121     $params->{dateexpiry} = {
122         ">=" => $dtf->format_date( $date_before ),
123         "<=" => $dtf->format_date( $date_after ),
124     };
125     return $self->SUPER::search(
126         $params, { join => ['branchcode', 'categorycode'] }
127     );
128 }
129
130 =head3 search_patrons_to_anonymise
131
132     my $patrons = Koha::Patrons->search_patrons_to_anonymise( { before => $older_than_date, [ library => $library ] } );
133
134 This method returns all patrons who has an issue history older than a given date.
135
136 =cut
137
138 sub search_patrons_to_anonymise {
139     my ( $class, $params ) = @_;
140     my $older_than_date = $params->{before};
141     my $library         = $params->{library};
142     $older_than_date = $older_than_date ? dt_from_string($older_than_date) : dt_from_string;
143     $library ||=
144       ( C4::Context->preference('IndependentBranches') && C4::Context->userenv && !C4::Context->IsSuperLibrarian() && C4::Context->userenv->{branch} )
145       ? C4::Context->userenv->{branch}
146       : undef;
147     my $anonymous_patron = C4::Context->preference('AnonymousPatron') || undef;
148
149     my $dtf = Koha::Database->new->schema->storage->datetime_parser;
150     my $rs = $class->_resultset->search(
151         {   returndate                  => { '<'   =>  $dtf->format_datetime($older_than_date), },
152             'old_issues.borrowernumber' => { 'not' => undef },
153             privacy                     => { '<>'  => 0 },                  # Keep forever
154             ( $library ? ( 'old_issues.branchcode' => $library ) : () ),
155             ( $anonymous_patron ? ( 'old_issues.borrowernumber' => { '!=' => $anonymous_patron } ) : () ),
156         },
157         {   join     => ["old_issues"],
158             distinct => 1,
159         }
160     );
161     return Koha::Patrons->_new_from_dbic($rs);
162 }
163
164 =head3 anonymise_issue_history
165
166     Koha::Patrons->search->anonymise_issue_history( { [ before => $older_than_date ] } );
167
168 Anonymise issue history (old_issues) for all patrons older than the given date (optional).
169 To make sure all the conditions are met, the caller has the responsibility to
170 call search_patrons_to_anonymise to filter the Koha::Patrons set
171
172 =cut
173
174 sub anonymise_issue_history {
175     my ( $self, $params ) = @_;
176
177     my $older_than_date = $params->{before};
178
179     $older_than_date = dt_from_string $older_than_date if $older_than_date;
180
181     # The default of 0 does not work due to foreign key constraints
182     # The anonymisation should not fail quietly if AnonymousPatron is not a valid entry
183     # Set it to undef (NULL)
184     my $dtf = Koha::Database->new->schema->storage->datetime_parser;
185     my $nb_rows = 0;
186     while ( my $patron = $self->next ) {
187         my $old_issues_to_anonymise = $patron->old_checkouts->search(
188         {
189             (
190                 $older_than_date
191                 ? ( returndate =>
192                       { '<' => $dtf->format_datetime($older_than_date) } )
193                 : ()
194             )
195         }
196         );
197         my $anonymous_patron = C4::Context->preference('AnonymousPatron') || undef;
198         $nb_rows += $old_issues_to_anonymise->update( {
199             'old_issues.borrowernumber' => $anonymous_patron
200         } );
201     }
202     return $nb_rows;
203 }
204
205 =head3 delete
206
207     Koha::Patrons->search({ some filters here })->delete({ move => 1 });
208
209     Delete passed set of patron objects.
210     Wrapper for Koha::Patron->delete. (We do not want to bypass Koha::Patron
211     and let DBIx do the job without further housekeeping.)
212     Includes a move to deletedborrowers if move flag set.
213
214     Just like DBIx, the delete will only succeed when all entries could be
215     deleted. Returns true or throws an exception.
216
217 =cut
218
219 sub delete {
220     my ( $self, $params ) = @_;
221     my $patrons_deleted;
222     $self->_resultset->result_source->schema->txn_do( sub {
223         my ( $set, $params ) = @_;
224         my $count = $set->count;
225         while ( my $patron = $set->next ) {
226
227             next unless $patron->in_storage;
228
229             $patron->move_to_deleted if $params->{move};
230             $patron->delete;
231
232             $patrons_deleted++;
233         }
234     }, $self, $params );
235     return $patrons_deleted;
236 }
237
238 =head3 search_unsubscribed
239
240     Koha::Patrons->search_unsubscribed;
241
242     Returns a set of Koha patron objects for patrons that recently
243     unsubscribed and are not locked (candidates for locking).
244     Depends on UnsubscribeReflectionDelay.
245
246 =cut
247
248 sub search_unsubscribed {
249     my ( $class ) = @_;
250
251     my $delay = C4::Context->preference('UnsubscribeReflectionDelay');
252     if( !defined($delay) || $delay eq q{} ) {
253         # return empty set
254         return $class->search({ borrowernumber => undef });
255     }
256     my $parser = Koha::Database->new->schema->storage->datetime_parser;
257     my $dt = dt_from_string()->subtract( days => $delay );
258     my $str = $parser->format_datetime($dt);
259     my $fails = C4::Context->preference('FailedLoginAttempts') || 0;
260     my $cond = [ undef, 0, 1..$fails-1 ]; # NULL, 0, 1..fails-1 (if fails>0)
261     return $class->search(
262         {
263             'patron_consents.refused_on' => { '<=' => $str },
264             'login_attempts' => $cond,
265         },
266         { join => 'patron_consents' },
267     );
268 }
269
270 =head3 search_anonymize_candidates
271
272     Koha::Patrons->search_anonymize_candidates({ locked => 1 });
273
274     Returns a set of Koha patron objects for patrons whose account is expired
275     and locked (if parameter set). These are candidates for anonymizing.
276     Depends on PatronAnonymizeDelay.
277
278 =cut
279
280 sub search_anonymize_candidates {
281     my ( $class, $params ) = @_;
282
283     my $delay = C4::Context->preference('PatronAnonymizeDelay');
284     if( !defined($delay) || $delay eq q{} ) {
285         # return empty set
286         return $class->search({ borrowernumber => undef });
287     }
288     my $cond = {};
289     my $parser = Koha::Database->new->schema->storage->datetime_parser;
290     my $dt = dt_from_string()->subtract( days => $delay );
291     my $str = $parser->format_datetime($dt);
292     $cond->{dateexpiry} = { '<=' => $str };
293     $cond->{anonymized} = 0; # not yet done
294     if( $params->{locked} ) {
295         my $fails = C4::Context->preference('FailedLoginAttempts') || 0;
296         $cond->{login_attempts} = [ -and => { '!=' => undef }, { -not_in => [0, 1..$fails-1 ] } ]; # -not_in does not like undef
297     }
298     return $class->search( $cond );
299 }
300
301 =head3 search_anonymized
302
303     Koha::Patrons->search_anonymized;
304
305     Returns a set of Koha patron objects for patron accounts that have been
306     anonymized before and could be removed.
307     Depends on PatronRemovalDelay.
308
309 =cut
310
311 sub search_anonymized {
312     my ( $class ) = @_;
313
314     my $delay = C4::Context->preference('PatronRemovalDelay');
315     if( !defined($delay) || $delay eq q{} ) {
316         # return empty set
317         return $class->search({ borrowernumber => undef });
318     }
319     my $cond = {};
320     my $parser = Koha::Database->new->schema->storage->datetime_parser;
321     my $dt = dt_from_string()->subtract( days => $delay );
322     my $str = $parser->format_datetime($dt);
323     $cond->{dateexpiry} = { '<=' => $str };
324     $cond->{anonymized} = 1;
325     return $class->search( $cond );
326 }
327
328 =head3 lock
329
330     Koha::Patrons->search({ some filters })->lock({ expire => 1, remove => 1 })
331
332     Lock the passed set of patron objects. Optionally expire and remove holds.
333     Wrapper around Koha::Patron->lock.
334
335 =cut
336
337 sub lock {
338     my ( $self, $params ) = @_;
339     my $count = $self->count;
340     while( my $patron = $self->next ) {
341         $patron->lock($params);
342     }
343 }
344
345 =head3 anonymize
346
347     Koha::Patrons->search({ some filters })->anonymize();
348
349     Anonymize passed set of patron objects.
350     Wrapper around Koha::Patron->anonymize.
351
352 =cut
353
354 sub anonymize {
355     my ( $self ) = @_;
356     my $count = $self->count;
357     while( my $patron = $self->next ) {
358         $patron->anonymize;
359     }
360 }
361
362 =head3 search_patrons_to_update_category
363
364     my $patrons = Koha::Patrons->search_patrons_to_update_category( {
365                       from          => $from_category,
366                       fine_max      => $fine_max,
367                       fine_min      => $fin_min,
368                       too_young     => $too_young,
369                       too_old      => $too_old,
370                   });
371
372 This method returns all patron who should be updated from one category to another meeting criteria:
373
374 from          - borrower categorycode
375 fine_min      - with fines totaling at least this amount
376 fine_max      - with fines above this amount
377 too_young     - if passed, select patrons who are under the age limit for the current category
378 too_old       - if passed, select patrons who are over the age limit for the current category
379
380 =cut
381
382 sub search_patrons_to_update_category {
383     my ( $self, $params ) = @_;
384     my %query;
385     my $search_params;
386
387     my $cat_from = Koha::Patron::Categories->find($params->{from});
388     $search_params->{categorycode}=$params->{from};
389     if ($params->{too_young} || $params->{too_old}){
390         my $dtf = Koha::Database->new->schema->storage->datetime_parser;
391         if( $cat_from->dateofbirthrequired && $params->{too_young} ) {
392             my $date_after = dt_from_string()->subtract( years => $cat_from->dateofbirthrequired);
393             $search_params->{dateofbirth}{'>'} = $dtf->format_datetime( $date_after );
394         }
395         if( $cat_from->upperagelimit && $params->{too_old} ) {
396             my $date_before = dt_from_string()->subtract( years => $cat_from->upperagelimit);
397             $search_params->{dateofbirth}{'<'} = $dtf->format_datetime( $date_before );
398         }
399     }
400     if ($params->{fine_min} || $params->{fine_max}) {
401         $query{join} = ["accountlines"];
402         $query{columns} = ["borrowernumber"];
403         $query{group_by} = ["borrowernumber"];
404         $query{having} = \['COALESCE(sum(accountlines.amountoutstanding),0) <= ?',$params->{fine_max}] if defined $params->{fine_max};
405         $query{having} = \['COALESCE(sum(accountlines.amountoutstanding),0) >= ?',$params->{fine_min}] if defined $params->{fine_min};
406     }
407     return $self->search($search_params,\%query);
408 }
409
410 =head3 update_category_to
411
412     Koha::Patrons->search->update_category_to( {
413             category   => $to_category,
414         });
415
416 Update supplied patrons from current category to another and take care of guarantor info.
417 To make sure all the conditions are met, the caller has the responsibility to
418 call search_patrons_to_update to filter the Koha::Patrons set
419
420 =cut
421
422 sub update_category_to {
423     my ( $self, $params ) = @_;
424     my $counter = 0;
425     while( my $patron = $self->next ) {
426         $counter++;
427         $patron->categorycode($params->{category})->store();
428     }
429     return $counter;
430 }
431
432 =head3 filter_by_attribute_type
433
434 my $patrons = Koha::Patrons->filter_by_attribute_type($attribute_type_code);
435
436 Return a Koha::Patrons set with patrons having the attribute defined.
437
438 =cut
439
440 sub filter_by_attribute_type {
441     my ( $self, $attribute_type ) = @_;
442     my $rs = Koha::Patron::Attributes->search( { code => $attribute_type } )
443       ->_resultset()->search_related('borrowernumber');
444     return Koha::Patrons->_new_from_dbic($rs);
445 }
446
447 =head3 filter_by_attribute_value
448
449 my $patrons = Koha::Patrons->filter_by_attribute_value($attribute_value);
450
451 Return a Koha::Patrons set with patrong having the attribute value passed in parameter.
452
453 =cut
454
455 sub filter_by_attribute_value {
456     my ( $self, $attribute_value ) = @_;
457     my $rs = Koha::Patron::Attributes->search(
458         {
459             'borrower_attribute_types.staff_searchable' => 1,
460             attribute => { like => "%$attribute_value%" }
461         },
462         { join => 'borrower_attribute_types' }
463     )->_resultset()->search_related('borrowernumber');
464     return Koha::Patrons->_new_from_dbic($rs);
465 }
466
467
468 =head3 _type
469
470 =cut
471
472 sub _type {
473     return 'Borrower';
474 }
475
476 =head3 object_class
477
478 =cut
479
480 sub object_class {
481     return 'Koha::Patron';
482 }
483
484 =head1 AUTHOR
485
486 Kyle M Hall <kyle@bywatersolutions.com>
487
488 =cut
489
490 1;