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);
161 =head3 anonymise_issue_history
163 Koha::Patrons->search->anonymise_issue_history( { [ before => $older_than_date ] } );
165 Anonymise issue history (old_issues) for all patrons older than the given date (optional).
166 To make sure all the conditions are met, the caller has the responsibility to
167 call search_patrons_to_anonymise to filter the Koha::Patrons set
171 sub anonymise_issue_history {
172 my ( $self, $params ) = @_;
174 my $older_than_date = $params->{before};
176 $older_than_date = dt_from_string $older_than_date if $older_than_date;
178 # The default of 0 does not work due to foreign key constraints
179 # The anonymisation should not fail quietly if AnonymousPatron is not a valid entry
180 # Set it to undef (NULL)
181 my $dtf = Koha::Database->new->schema->storage->datetime_parser;
183 while ( my $patron = $self->next ) {
184 my $old_issues_to_anonymise = $patron->old_checkouts->search(
189 { '<' => $dtf->format_datetime($older_than_date) } )
194 my $anonymous_patron = C4::Context->preference('AnonymousPatron') || undef;
195 $nb_rows += $old_issues_to_anonymise->update( { 'old_issues.borrowernumber' => $anonymous_patron } );
202 Koha::Patrons->search({ some filters here })->delete({ move => 1 });
204 Delete passed set of patron objects.
205 Wrapper for Koha::Patron->delete. (We do not want to bypass Koha::Patron
206 and let DBIx do the job without further housekeeping.)
207 Includes a move to deletedborrowers if move flag set.
209 Just like DBIx, the delete will only succeed when all entries could be
210 deleted. Returns true or throws an exception.
215 my ( $self, $params ) = @_;
217 $self->_resultset->result_source->schema->txn_do( sub {
218 my ( $set, $params ) = @_;
219 my $count = $set->count;
220 while ( my $patron = $set->next ) {
222 next unless $patron->in_storage;
224 $patron->move_to_deleted if $params->{move};
230 return $patrons_deleted;
233 =head3 filter_by_expiration_date
235 Koha::Patrons->filter_by_expiration_date{{ days => $x });
237 Returns set of Koha patron objects expired $x days.
241 sub filter_by_expiration_date {
242 my ( $class, $params ) = @_;
244 return $class->filter_by_last_update(
246 timestamp_column_name => 'dateexpiry',
247 days => $params->{days} || 0,
253 =head3 search_unsubscribed
255 Koha::Patrons->search_unsubscribed;
257 Returns a set of Koha patron objects for patrons that recently
258 unsubscribed and are not locked (candidates for locking).
259 Depends on UnsubscribeReflectionDelay.
263 sub search_unsubscribed {
266 my $delay = C4::Context->preference('UnsubscribeReflectionDelay');
267 if( !defined($delay) || $delay eq q{} ) {
269 return $class->search({ borrowernumber => undef });
271 my $parser = Koha::Database->new->schema->storage->datetime_parser;
272 my $dt = dt_from_string()->subtract( days => $delay );
273 my $str = $parser->format_datetime($dt);
274 my $fails = C4::Context->preference('FailedLoginAttempts') || 0;
275 my $cond = [ undef, 0, 1..$fails-1 ]; # NULL, 0, 1..fails-1 (if fails>0)
276 return $class->search(
278 'patron_consents.refused_on' => { '<=' => $str },
279 'login_attempts' => $cond,
281 { join => 'patron_consents' },
285 =head3 search_anonymize_candidates
287 Koha::Patrons->search_anonymize_candidates({ locked => 1 });
289 Returns a set of Koha patron objects for patrons whose account is expired
290 and locked (if parameter set). These are candidates for anonymizing.
291 Depends on PatronAnonymizeDelay.
295 sub search_anonymize_candidates {
296 my ( $class, $params ) = @_;
298 my $delay = C4::Context->preference('PatronAnonymizeDelay');
299 if( !defined($delay) || $delay eq q{} ) {
301 return $class->search({ borrowernumber => undef });
304 my $parser = Koha::Database->new->schema->storage->datetime_parser;
305 my $dt = dt_from_string()->subtract( days => $delay );
306 my $str = $parser->format_datetime($dt);
307 $cond->{dateexpiry} = { '<=' => $str };
308 $cond->{anonymized} = 0; # not yet done
309 if( $params->{locked} ) {
310 my $fails = C4::Context->preference('FailedLoginAttempts') || 0;
311 $cond->{login_attempts} = [ -and => { '!=' => undef }, { -not_in => [0, 1..$fails-1 ] } ]; # -not_in does not like undef
313 return $class->search( $cond );
316 =head3 search_anonymized
318 Koha::Patrons->search_anonymized;
320 Returns a set of Koha patron objects for patron accounts that have been
321 anonymized before and could be removed.
322 Depends on PatronRemovalDelay.
326 sub search_anonymized {
329 my $delay = C4::Context->preference('PatronRemovalDelay');
330 if( !defined($delay) || $delay eq q{} ) {
332 return $class->search({ borrowernumber => undef });
335 my $parser = Koha::Database->new->schema->storage->datetime_parser;
336 my $dt = dt_from_string()->subtract( days => $delay );
337 my $str = $parser->format_datetime($dt);
338 $cond->{dateexpiry} = { '<=' => $str };
339 $cond->{anonymized} = 1;
340 return $class->search( $cond );
345 Koha::Patrons->search({ some filters })->lock({ expire => 1, remove => 1 })
347 Lock the passed set of patron objects. Optionally expire and remove holds.
348 Wrapper around Koha::Patron->lock.
353 my ( $self, $params ) = @_;
354 my $count = $self->count;
355 while( my $patron = $self->next ) {
356 $patron->lock($params);
362 Koha::Patrons->search({ some filters })->anonymize();
364 Anonymize passed set of patron objects.
365 Wrapper around Koha::Patron->anonymize.
371 my $count = $self->count;
372 while( my $patron = $self->next ) {
377 =head3 search_patrons_to_update_category
379 my $patrons = Koha::Patrons->search_patrons_to_update_category( {
380 from => $from_category,
381 fine_max => $fine_max,
382 fine_min => $fin_min,
383 too_young => $too_young,
387 This method returns all patron who should be updated from one category to another meeting criteria:
389 from - borrower categorycode
390 fine_min - with fines totaling at least this amount
391 fine_max - with fines above this amount
392 too_young - if passed, select patrons who are under the age limit for the current category
393 too_old - if passed, select patrons who are over the age limit for the current category
397 sub search_patrons_to_update_category {
398 my ( $self, $params ) = @_;
402 my $cat_from = Koha::Patron::Categories->find($params->{from});
403 $search_params->{categorycode}=$params->{from};
404 if ($params->{too_young} || $params->{too_old}){
405 my $dtf = Koha::Database->new->schema->storage->datetime_parser;
406 if( $cat_from->dateofbirthrequired && $params->{too_young} ) {
407 my $date_after = dt_from_string()->subtract( years => $cat_from->dateofbirthrequired);
408 $search_params->{dateofbirth}{'>'} = $dtf->format_datetime( $date_after );
410 if( $cat_from->upperagelimit && $params->{too_old} ) {
411 my $date_before = dt_from_string()->subtract( years => $cat_from->upperagelimit);
412 $search_params->{dateofbirth}{'<'} = $dtf->format_datetime( $date_before );
415 if ($params->{fine_min} || $params->{fine_max}) {
416 $query{join} = ["accountlines"];
417 $query{columns} = ["borrowernumber"];
418 $query{group_by} = ["borrowernumber"];
419 $query{having} = \['COALESCE(sum(accountlines.amountoutstanding),0) <= ?',$params->{fine_max}] if defined $params->{fine_max};
420 $query{having} = \['COALESCE(sum(accountlines.amountoutstanding),0) >= ?',$params->{fine_min}] if defined $params->{fine_min};
422 return $self->search($search_params,\%query);
425 =head3 update_category_to
427 Koha::Patrons->search->update_category_to( {
428 category => $to_category,
431 Update supplied patrons from current category to another and take care of guarantor info.
432 To make sure all the conditions are met, the caller has the responsibility to
433 call search_patrons_to_update to filter the Koha::Patrons set
437 sub update_category_to {
438 my ( $self, $params ) = @_;
440 while( my $patron = $self->next ) {
442 $patron->categorycode($params->{category})->store();
447 =head3 filter_by_attribute_type
449 my $patrons = Koha::Patrons->filter_by_attribute_type($attribute_type_code);
451 Return a Koha::Patrons set with patrons having the attribute defined.
455 sub filter_by_attribute_type {
456 my ( $self, $attribute_type ) = @_;
457 my $rs = Koha::Patron::Attributes->search( { code => $attribute_type } )
458 ->_resultset()->search_related('borrowernumber');
459 return Koha::Patrons->_new_from_dbic($rs);
462 =head3 filter_by_attribute_value
464 my $patrons = Koha::Patrons->filter_by_attribute_value($attribute_value);
466 Return a Koha::Patrons set with patrong having the attribute value passed in parameter.
470 sub filter_by_attribute_value {
471 my ( $self, $attribute_value ) = @_;
472 my $rs = Koha::Patron::Attributes->search(
474 'borrower_attribute_types.staff_searchable' => 1,
475 attribute => { like => "%$attribute_value%" }
477 { join => 'borrower_attribute_types' }
478 )->_resultset()->search_related('borrowernumber');
479 return Koha::Patrons->_new_from_dbic($rs);
482 =head3 filter_by_amount_owed
484 Koha::Patrons->filter_by_amount_owed(
488 debit_type => $debit_type_code,
489 library => $branchcode
493 Returns patrons filtered by how much money they owe, between passed limits.
495 Optionally limit to debts of a particular debit_type or/and owed to a particular library.
497 =head4 arguments hashref
501 =item less_than (optional) - filter out patrons who owe less than Amount
503 =item more_than (optional) - filter out patrons who owe more than Amount
505 =item debit_type (optional) - filter the amount owed by debit type
507 =item library (optional) - filter the amount owed to a particular branch
513 sub filter_by_amount_owed {
514 my ( $self, $options ) = @_;
519 && ( defined( $options->{less_than} )
520 || defined( $options->{more_than} ) )
525 [ map { 'me.' . $_ } $self->_resultset->result_source->columns ];
528 join => 'accountlines',
529 group_by => $group_by,
531 { sum => 'accountlines.amountoutstanding', '-as' => 'outstanding' },
532 '+as' => 'outstanding'
535 $where->{'accountlines.debit_type_code'} = $options->{debit_type}
536 if defined( $options->{debit_type} );
538 $where->{'accountlines.branchcode'} = $options->{library}
539 if defined( $options->{library} );
541 $attrs->{'having'} = [
542 { 'outstanding' => { '<' => $options->{less_than} } },
543 { 'outstanding' => undef }
545 if ( defined( $options->{less_than} )
546 && !defined( $options->{more_than} ) );
548 $attrs->{'having'} = { 'outstanding' => { '>' => $options->{more_than} } }
549 if (!defined( $options->{less_than} )
550 && defined( $options->{more_than} ) );
552 $attrs->{'having'}->{'-and'} = [
553 { 'outstanding' => { '>' => $options->{more_than} } },
554 { 'outstanding' => { '<' => $options->{less_than} } }
556 if ( defined( $options->{less_than} )
557 && defined( $options->{more_than} ) );
559 return $self->search( $where, $attrs );
575 return 'Koha::Patron';
580 Kyle M Hall <kyle@bywatersolutions.com>