3 # Copyright 2014 ByWater Solutions
4 # Copyright 2016 Koha Development Team
6 # This file is part of Koha.
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.
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.
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>.
25 use Koha::DateUtils qw( dt_from_string );
27 use Koha::ArticleRequests;
29 use Koha::Exceptions::Patron;
30 use Koha::Patron::Categories;
32 use base qw(Koha::Objects);
36 Koha::Patron - Koha Patron Object class
46 my $patrons = Koha::Patrons->search_limit( $params, $attributes );
48 Returns all the patrons the logged in user is allowed to see
53 my ( $self, $params, $attributes ) = @_;
55 my $userenv = C4::Context->userenv;
56 my @restricted_branchcodes;
57 if ( $userenv and $userenv->{number} ) {
58 my $logged_in_user = Koha::Patrons->find( $userenv->{number} );
59 @restricted_branchcodes = $logged_in_user->libraries_where_can_see_patrons;
61 $params->{'me.branchcode'} = { -in => \@restricted_branchcodes } if @restricted_branchcodes;
62 return $self->search( $params, $attributes );
65 =head3 search_housebound_choosers
67 Returns all Patrons which are Housebound choosers.
71 sub search_housebound_choosers {
73 my $cho = $self->_resultset
74 ->search_related('housebound_role', {
75 housebound_chooser => 1,
76 })->search_related('borrowernumber');
77 return Koha::Patrons->_new_from_dbic($cho);
80 =head3 search_housebound_deliverers
82 Returns all Patrons which are Housebound deliverers.
86 sub search_housebound_deliverers {
88 my $del = $self->_resultset
89 ->search_related('housebound_role', {
90 housebound_deliverer => 1,
91 })->search_related('borrowernumber');
92 return Koha::Patrons->_new_from_dbic($del);
95 =head3 search_upcoming_membership_expires
97 my $patrons = Koha::Patrons->search_upcoming_membership_expires();
99 The 'before' and 'after' represent the number of days before/after the date
100 that is set by the preference MembershipExpiryDaysNotice.
101 If the pref is 14, before 2 and after 3 then you will get all expires
106 sub search_upcoming_membership_expires {
107 my ( $self, $params ) = @_;
108 my $before = $params->{before} || 0;
109 my $after = $params->{after} || 0;
110 delete $params->{before};
111 delete $params->{after};
113 my $days = C4::Context->preference("MembershipExpiryDaysNotice") || 0;
114 my $date_before = dt_from_string->add( days => $days - $before );
115 my $date_after = dt_from_string->add( days => $days + $after );
116 my $dtf = Koha::Database->new->schema->storage->datetime_parser;
118 $params->{dateexpiry} = {
119 ">=" => $dtf->format_date( $date_before ),
120 "<=" => $dtf->format_date( $date_after ),
122 return $self->SUPER::search(
123 $params, { join => ['branchcode', 'categorycode'] }
127 =head3 search_patrons_to_anonymise
129 my $patrons = Koha::Patrons->search_patrons_to_anonymise( { before => $older_than_date, [ library => $library ] } );
131 This method returns all patrons who has an issue history older than a given date.
135 sub search_patrons_to_anonymise {
136 my ( $class, $params ) = @_;
137 my $older_than_date = $params->{before};
138 my $library = $params->{library};
139 $older_than_date = $older_than_date ? dt_from_string($older_than_date) : dt_from_string;
141 ( C4::Context->preference('IndependentBranches') && C4::Context->userenv && !C4::Context->IsSuperLibrarian() && C4::Context->userenv->{branch} )
142 ? C4::Context->userenv->{branch}
144 my $anonymous_patron = C4::Context->preference('AnonymousPatron') || undef;
146 my $dtf = Koha::Database->new->schema->storage->datetime_parser;
147 my $rs = $class->_resultset->search(
148 { returndate => { '<' => $dtf->format_datetime($older_than_date), },
149 'old_issues.borrowernumber' => { 'not' => undef },
150 privacy => { '<>' => 0 }, # Keep forever
151 ( $library ? ( 'old_issues.branchcode' => $library ) : () ),
152 ( $anonymous_patron ? ( 'old_issues.borrowernumber' => { '!=' => $anonymous_patron } ) : () ),
154 { join => ["old_issues"],
158 return Koha::Patrons->_new_from_dbic($rs);
163 Koha::Patrons->search({ some filters here })->delete({ move => 1 });
165 Delete passed set of patron objects.
166 Wrapper for Koha::Patron->delete. (We do not want to bypass Koha::Patron
167 and let DBIx do the job without further housekeeping.)
168 Includes a move to deletedborrowers if move flag set.
170 Just like DBIx, the delete will only succeed when all entries could be
171 deleted. Returns true or throws an exception.
176 my ( $self, $params ) = @_;
178 $self->_resultset->result_source->schema->txn_do( sub {
179 my ( $set, $params ) = @_;
180 my $count = $set->count;
181 while ( my $patron = $set->next ) {
183 next unless $patron->in_storage;
185 $patron->move_to_deleted if $params->{move};
191 return $patrons_deleted;
194 =head3 filter_by_expiration_date
196 Koha::Patrons->filter_by_expiration_date{{ days => $x });
198 Returns set of Koha patron objects expired $x days.
202 sub filter_by_expiration_date {
203 my ( $class, $params ) = @_;
205 return $class->filter_by_last_update(
207 timestamp_column_name => 'dateexpiry',
208 min_days => $params->{days} || 0,
213 =head3 search_unsubscribed
215 Koha::Patrons->search_unsubscribed;
217 Returns a set of Koha patron objects for patrons that recently
218 unsubscribed and are not locked (candidates for locking).
219 Depends on UnsubscribeReflectionDelay.
223 sub search_unsubscribed {
226 my $delay = C4::Context->preference('UnsubscribeReflectionDelay');
227 if( !defined($delay) || $delay eq q{} ) {
229 return $class->search({ borrowernumber => undef });
231 my $parser = Koha::Database->new->schema->storage->datetime_parser;
232 my $dt = dt_from_string()->subtract( days => $delay );
233 my $str = $parser->format_datetime($dt);
234 my $fails = C4::Context->preference('FailedLoginAttempts') || 0;
235 my $cond = [ undef, 0, 1..$fails-1 ]; # NULL, 0, 1..fails-1 (if fails>0)
236 return $class->search(
238 'patron_consents.refused_on' => { '<=' => $str },
239 'patron_consents.type' => 'GDPR_PROCESSING',
240 'login_attempts' => $cond,
242 { join => 'patron_consents' },
246 =head3 search_anonymize_candidates
248 Koha::Patrons->search_anonymize_candidates({ locked => 1 });
250 Returns a set of Koha patron objects for patrons whose account is expired
251 and locked (if parameter set). These are candidates for anonymizing.
252 Depends on PatronAnonymizeDelay.
256 sub search_anonymize_candidates {
257 my ( $class, $params ) = @_;
259 my $delay = C4::Context->preference('PatronAnonymizeDelay');
260 if( !defined($delay) || $delay eq q{} ) {
262 return $class->search({ borrowernumber => undef });
265 my $parser = Koha::Database->new->schema->storage->datetime_parser;
266 my $dt = dt_from_string()->subtract( days => $delay );
267 my $str = $parser->format_datetime($dt);
268 $cond->{dateexpiry} = { '<=' => $str };
269 $cond->{anonymized} = 0; # not yet done
270 if( $params->{locked} ) {
271 my $fails = C4::Context->preference('FailedLoginAttempts') || 0;
272 $cond->{login_attempts} = [ -and => { '!=' => undef }, { -not_in => [0, 1..$fails-1 ] } ]; # -not_in does not like undef
274 return $class->search( $cond );
277 =head3 search_anonymized
279 Koha::Patrons->search_anonymized;
281 Returns a set of Koha patron objects for patron accounts that have been
282 anonymized before and could be removed.
283 Depends on PatronRemovalDelay.
287 sub search_anonymized {
290 my $delay = C4::Context->preference('PatronRemovalDelay');
291 if( !defined($delay) || $delay eq q{} ) {
293 return $class->search({ borrowernumber => undef });
296 my $parser = Koha::Database->new->schema->storage->datetime_parser;
297 my $dt = dt_from_string()->subtract( days => $delay );
298 my $str = $parser->format_datetime($dt);
299 $cond->{dateexpiry} = { '<=' => $str };
300 $cond->{anonymized} = 1;
301 return $class->search( $cond );
306 Koha::Patrons->search({ some filters })->lock({ expire => 1, remove => 1 })
308 Lock the passed set of patron objects. Optionally expire and remove holds.
309 Wrapper around Koha::Patron->lock.
314 my ( $self, $params ) = @_;
315 my $count = $self->count;
316 while( my $patron = $self->next ) {
317 $patron->lock($params);
323 Koha::Patrons->search({ some filters })->anonymize();
325 Anonymize passed set of patron objects.
326 Wrapper around Koha::Patron->anonymize.
332 my $count = $self->count;
333 while( my $patron = $self->next ) {
338 =head3 search_patrons_to_update_category
340 my $patrons = Koha::Patrons->search_patrons_to_update_category( {
341 from => $from_category,
342 fine_max => $fine_max,
343 fine_min => $fin_min,
344 too_young => $too_young,
348 This method returns all patron who should be updated from one category to another meeting criteria:
350 from - borrower categorycode
351 fine_min - with fines totaling at least this amount
352 fine_max - with fines above this amount
353 too_young - if passed, select patrons who are under the age limit for the current category
354 too_old - if passed, select patrons who are over the age limit for the current category
358 sub search_patrons_to_update_category {
359 my ( $self, $params ) = @_;
363 my $cat_from = Koha::Patron::Categories->find($params->{from});
364 $search_params->{categorycode}=$params->{from};
365 if ($params->{too_young} || $params->{too_old}){
366 my $dtf = Koha::Database->new->schema->storage->datetime_parser;
367 if( $cat_from->dateofbirthrequired && $params->{too_young} ) {
368 my $date_after = dt_from_string()->subtract( years => $cat_from->dateofbirthrequired);
369 $search_params->{dateofbirth}{'>'} = $dtf->format_datetime( $date_after );
371 if( $cat_from->upperagelimit && $params->{too_old} ) {
372 my $date_before = dt_from_string()->subtract( years => $cat_from->upperagelimit);
373 $search_params->{dateofbirth}{'<'} = $dtf->format_datetime( $date_before );
376 if ($params->{fine_min} || $params->{fine_max}) {
377 $query{join} = ["accountlines"];
378 $query{columns} = ["borrowernumber"];
379 $query{group_by} = ["borrowernumber"];
380 $query{having} = \['COALESCE(sum(accountlines.amountoutstanding),0) <= ?',$params->{fine_max}] if defined $params->{fine_max};
381 $query{having} = \['COALESCE(sum(accountlines.amountoutstanding),0) >= ?',$params->{fine_min}] if defined $params->{fine_min};
383 return $self->search($search_params,\%query);
386 =head3 update_category_to
388 Koha::Patrons->search->update_category_to( {
389 category => $to_category,
392 Update supplied patrons from current category to another and take care of guarantor info.
393 To make sure all the conditions are met, the caller has the responsibility to
394 call search_patrons_to_update to filter the Koha::Patrons set
398 sub update_category_to {
399 my ( $self, $params ) = @_;
401 while( my $patron = $self->next ) {
403 $patron->categorycode($params->{category})->store();
408 =head3 filter_by_attribute_type
410 my $patrons = Koha::Patrons->filter_by_attribute_type($attribute_type_code);
412 Return a Koha::Patrons set with patrons having the attribute defined.
416 sub filter_by_attribute_type {
417 my ( $self, $attribute_type ) = @_;
418 my $rs = Koha::Patron::Attributes->search( { code => $attribute_type } )
419 ->_resultset()->search_related('borrowernumber');
420 return Koha::Patrons->_new_from_dbic($rs);
423 =head3 filter_by_attribute_value
425 my $patrons = Koha::Patrons->filter_by_attribute_value($attribute_value);
427 Return a Koha::Patrons set with patrong having the attribute value passed in parameter.
431 sub filter_by_attribute_value {
432 my ( $self, $attribute_value ) = @_;
433 my $rs = Koha::Patron::Attributes->search(
435 'borrower_attribute_types.staff_searchable' => 1,
436 attribute => { like => "%$attribute_value%" }
438 { join => 'borrower_attribute_types' }
439 )->_resultset()->search_related('borrowernumber');
440 return Koha::Patrons->_new_from_dbic($rs);
443 =head3 filter_by_amount_owed
445 Koha::Patrons->filter_by_amount_owed(
449 debit_type => $debit_type_code,
450 library => $branchcode
454 Returns patrons filtered by how much money they owe, between passed limits.
456 Optionally limit to debts of a particular debit_type or/and owed to a particular library.
458 =head4 arguments hashref
462 =item less_than (optional) - filter out patrons who owe less than Amount
464 =item more_than (optional) - filter out patrons who owe more than Amount
466 =item debit_type (optional) - filter the amount owed by debit type
468 =item library (optional) - filter the amount owed to a particular branch
474 sub filter_by_amount_owed {
475 my ( $self, $options ) = @_;
480 && ( defined( $options->{less_than} )
481 || defined( $options->{more_than} ) )
486 [ map { 'me.' . $_ } $self->_resultset->result_source->columns ];
489 join => 'accountlines',
490 group_by => $group_by,
492 { sum => 'accountlines.amountoutstanding', '-as' => 'outstanding' },
493 '+as' => 'outstanding'
496 $where->{'accountlines.debit_type_code'} = $options->{debit_type}
497 if defined( $options->{debit_type} );
499 $where->{'accountlines.branchcode'} = $options->{library}
500 if defined( $options->{library} );
502 $attrs->{'having'} = [
503 { 'outstanding' => { '<' => $options->{less_than} } },
504 { 'outstanding' => undef }
506 if ( defined( $options->{less_than} )
507 && !defined( $options->{more_than} ) );
509 $attrs->{'having'} = { 'outstanding' => { '>' => $options->{more_than} } }
510 if (!defined( $options->{less_than} )
511 && defined( $options->{more_than} ) );
513 $attrs->{'having'}->{'-and'} = [
514 { 'outstanding' => { '>' => $options->{more_than} } },
515 { 'outstanding' => { '<' => $options->{less_than} } }
517 if ( defined( $options->{less_than} )
518 && defined( $options->{more_than} ) );
520 return $self->search( $where, $attrs );
523 =head3 filter_by_have_permission
525 my $patrons = Koha::Patrons->search->filter_by_have_permission('suggestions.suggestions_manage');
527 my $patrons = Koha::Patrons->search->filter_by_have_permission('suggestions');
529 Filter patrons who have a given subpermission or the whole permission.
533 sub filter_by_have_permission {
534 my ($self, $subpermission) = @_;
536 my ($p, $sp) = split '\.', $subpermission;
538 my $perm = Koha::Database->new()->schema()->resultset('Userflag')->find({flag => $p});
540 Koha::Exceptions::ObjectNotFound->throw( sprintf( "Permission %s not found", $p ) )
543 my $bit = $perm->bit;
545 return $self->search(
549 \"me.flags & (1 << $bit)",
555 { 'user_permissions.module_bit' => $bit },
556 { 'user_permissions.code' => $sp }
564 { prefetch => 'user_permissions' }
581 return 'Koha::Patron';
586 Kyle M Hall <kyle@bywatersolutions.com>