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 days => $params->{days} || 0,
214 =head3 search_unsubscribed
216 Koha::Patrons->search_unsubscribed;
218 Returns a set of Koha patron objects for patrons that recently
219 unsubscribed and are not locked (candidates for locking).
220 Depends on UnsubscribeReflectionDelay.
224 sub search_unsubscribed {
227 my $delay = C4::Context->preference('UnsubscribeReflectionDelay');
228 if( !defined($delay) || $delay eq q{} ) {
230 return $class->search({ borrowernumber => undef });
232 my $parser = Koha::Database->new->schema->storage->datetime_parser;
233 my $dt = dt_from_string()->subtract( days => $delay );
234 my $str = $parser->format_datetime($dt);
235 my $fails = C4::Context->preference('FailedLoginAttempts') || 0;
236 my $cond = [ undef, 0, 1..$fails-1 ]; # NULL, 0, 1..fails-1 (if fails>0)
237 return $class->search(
239 'patron_consents.refused_on' => { '<=' => $str },
240 'patron_consents.type' => 'GDPR_PROCESSING',
241 'login_attempts' => $cond,
243 { join => 'patron_consents' },
247 =head3 search_anonymize_candidates
249 Koha::Patrons->search_anonymize_candidates({ locked => 1 });
251 Returns a set of Koha patron objects for patrons whose account is expired
252 and locked (if parameter set). These are candidates for anonymizing.
253 Depends on PatronAnonymizeDelay.
257 sub search_anonymize_candidates {
258 my ( $class, $params ) = @_;
260 my $delay = C4::Context->preference('PatronAnonymizeDelay');
261 if( !defined($delay) || $delay eq q{} ) {
263 return $class->search({ borrowernumber => undef });
266 my $parser = Koha::Database->new->schema->storage->datetime_parser;
267 my $dt = dt_from_string()->subtract( days => $delay );
268 my $str = $parser->format_datetime($dt);
269 $cond->{dateexpiry} = { '<=' => $str };
270 $cond->{anonymized} = 0; # not yet done
271 if( $params->{locked} ) {
272 my $fails = C4::Context->preference('FailedLoginAttempts') || 0;
273 $cond->{login_attempts} = [ -and => { '!=' => undef }, { -not_in => [0, 1..$fails-1 ] } ]; # -not_in does not like undef
275 return $class->search( $cond );
278 =head3 search_anonymized
280 Koha::Patrons->search_anonymized;
282 Returns a set of Koha patron objects for patron accounts that have been
283 anonymized before and could be removed.
284 Depends on PatronRemovalDelay.
288 sub search_anonymized {
291 my $delay = C4::Context->preference('PatronRemovalDelay');
292 if( !defined($delay) || $delay eq q{} ) {
294 return $class->search({ borrowernumber => undef });
297 my $parser = Koha::Database->new->schema->storage->datetime_parser;
298 my $dt = dt_from_string()->subtract( days => $delay );
299 my $str = $parser->format_datetime($dt);
300 $cond->{dateexpiry} = { '<=' => $str };
301 $cond->{anonymized} = 1;
302 return $class->search( $cond );
307 Koha::Patrons->search({ some filters })->lock({ expire => 1, remove => 1 })
309 Lock the passed set of patron objects. Optionally expire and remove holds.
310 Wrapper around Koha::Patron->lock.
315 my ( $self, $params ) = @_;
316 my $count = $self->count;
317 while( my $patron = $self->next ) {
318 $patron->lock($params);
324 Koha::Patrons->search({ some filters })->anonymize();
326 Anonymize passed set of patron objects.
327 Wrapper around Koha::Patron->anonymize.
333 my $count = $self->count;
334 while( my $patron = $self->next ) {
339 =head3 search_patrons_to_update_category
341 my $patrons = Koha::Patrons->search_patrons_to_update_category( {
342 from => $from_category,
343 fine_max => $fine_max,
344 fine_min => $fin_min,
345 too_young => $too_young,
349 This method returns all patron who should be updated from one category to another meeting criteria:
351 from - borrower categorycode
352 fine_min - with fines totaling at least this amount
353 fine_max - with fines above this amount
354 too_young - if passed, select patrons who are under the age limit for the current category
355 too_old - if passed, select patrons who are over the age limit for the current category
359 sub search_patrons_to_update_category {
360 my ( $self, $params ) = @_;
364 my $cat_from = Koha::Patron::Categories->find($params->{from});
365 $search_params->{categorycode}=$params->{from};
366 if ($params->{too_young} || $params->{too_old}){
367 my $dtf = Koha::Database->new->schema->storage->datetime_parser;
368 if( $cat_from->dateofbirthrequired && $params->{too_young} ) {
369 my $date_after = dt_from_string()->subtract( years => $cat_from->dateofbirthrequired);
370 $search_params->{dateofbirth}{'>'} = $dtf->format_datetime( $date_after );
372 if( $cat_from->upperagelimit && $params->{too_old} ) {
373 my $date_before = dt_from_string()->subtract( years => $cat_from->upperagelimit);
374 $search_params->{dateofbirth}{'<'} = $dtf->format_datetime( $date_before );
377 if ($params->{fine_min} || $params->{fine_max}) {
378 $query{join} = ["accountlines"];
379 $query{columns} = ["borrowernumber"];
380 $query{group_by} = ["borrowernumber"];
381 $query{having} = \['COALESCE(sum(accountlines.amountoutstanding),0) <= ?',$params->{fine_max}] if defined $params->{fine_max};
382 $query{having} = \['COALESCE(sum(accountlines.amountoutstanding),0) >= ?',$params->{fine_min}] if defined $params->{fine_min};
384 return $self->search($search_params,\%query);
387 =head3 update_category_to
389 Koha::Patrons->search->update_category_to( {
390 category => $to_category,
393 Update supplied patrons from current category to another and take care of guarantor info.
394 To make sure all the conditions are met, the caller has the responsibility to
395 call search_patrons_to_update to filter the Koha::Patrons set
399 sub update_category_to {
400 my ( $self, $params ) = @_;
402 while( my $patron = $self->next ) {
404 $patron->categorycode($params->{category})->store();
409 =head3 filter_by_attribute_type
411 my $patrons = Koha::Patrons->filter_by_attribute_type($attribute_type_code);
413 Return a Koha::Patrons set with patrons having the attribute defined.
417 sub filter_by_attribute_type {
418 my ( $self, $attribute_type ) = @_;
419 my $rs = Koha::Patron::Attributes->search( { code => $attribute_type } )
420 ->_resultset()->search_related('borrowernumber');
421 return Koha::Patrons->_new_from_dbic($rs);
424 =head3 filter_by_attribute_value
426 my $patrons = Koha::Patrons->filter_by_attribute_value($attribute_value);
428 Return a Koha::Patrons set with patrong having the attribute value passed in parameter.
432 sub filter_by_attribute_value {
433 my ( $self, $attribute_value ) = @_;
434 my $rs = Koha::Patron::Attributes->search(
436 'borrower_attribute_types.staff_searchable' => 1,
437 attribute => { like => "%$attribute_value%" }
439 { join => 'borrower_attribute_types' }
440 )->_resultset()->search_related('borrowernumber');
441 return Koha::Patrons->_new_from_dbic($rs);
444 =head3 filter_by_amount_owed
446 Koha::Patrons->filter_by_amount_owed(
450 debit_type => $debit_type_code,
451 library => $branchcode
455 Returns patrons filtered by how much money they owe, between passed limits.
457 Optionally limit to debts of a particular debit_type or/and owed to a particular library.
459 =head4 arguments hashref
463 =item less_than (optional) - filter out patrons who owe less than Amount
465 =item more_than (optional) - filter out patrons who owe more than Amount
467 =item debit_type (optional) - filter the amount owed by debit type
469 =item library (optional) - filter the amount owed to a particular branch
475 sub filter_by_amount_owed {
476 my ( $self, $options ) = @_;
481 && ( defined( $options->{less_than} )
482 || defined( $options->{more_than} ) )
487 [ map { 'me.' . $_ } $self->_resultset->result_source->columns ];
490 join => 'accountlines',
491 group_by => $group_by,
493 { sum => 'accountlines.amountoutstanding', '-as' => 'outstanding' },
494 '+as' => 'outstanding'
497 $where->{'accountlines.debit_type_code'} = $options->{debit_type}
498 if defined( $options->{debit_type} );
500 $where->{'accountlines.branchcode'} = $options->{library}
501 if defined( $options->{library} );
503 $attrs->{'having'} = [
504 { 'outstanding' => { '<' => $options->{less_than} } },
505 { 'outstanding' => undef }
507 if ( defined( $options->{less_than} )
508 && !defined( $options->{more_than} ) );
510 $attrs->{'having'} = { 'outstanding' => { '>' => $options->{more_than} } }
511 if (!defined( $options->{less_than} )
512 && defined( $options->{more_than} ) );
514 $attrs->{'having'}->{'-and'} = [
515 { 'outstanding' => { '>' => $options->{more_than} } },
516 { 'outstanding' => { '<' => $options->{less_than} } }
518 if ( defined( $options->{less_than} )
519 && defined( $options->{more_than} ) );
521 return $self->search( $where, $attrs );
524 =head3 filter_by_have_permission
526 my $patrons = Koha::Patrons->search->filter_by_have_permission('suggestions.suggestions_manage');
528 my $patrons = Koha::Patrons->search->filter_by_have_permission('suggestions');
530 Filter patrons who have a given subpermission or the whole permission.
534 sub filter_by_have_permission {
535 my ($self, $subpermission) = @_;
537 my ($p, $sp) = split '\.', $subpermission;
539 my $perm = Koha::Database->new()->schema()->resultset('Userflag')->find({flag => $p});
541 Koha::Exceptions::ObjectNotFound->throw( sprintf( "Permission %s not found", $p ) )
544 my $bit = $perm->bit;
546 return $self->search(
550 \"me.flags & (1 << $bit)",
556 { 'user_permissions.module_bit' => $bit },
557 { 'user_permissions.code' => $sp }
565 { prefetch => 'user_permissions' }
582 return 'Koha::Patron';
587 Kyle M Hall <kyle@bywatersolutions.com>