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;
28 use Koha::ArticleRequest::Status;
30 use Koha::Exceptions::Patron;
31 use Koha::Patron::Categories;
33 use base qw(Koha::Objects);
37 Koha::Patron - Koha Patron Object class
47 my $patrons = Koha::Patrons->search_limit( $params, $attributes );
49 Returns all the patrons the logged in user is allowed to see
54 my ( $self, $params, $attributes ) = @_;
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;
62 $params->{'me.branchcode'} = { -in => \@restricted_branchcodes } if @restricted_branchcodes;
63 return $self->search( $params, $attributes );
66 =head3 search_housebound_choosers
68 Returns all Patrons which are Housebound choosers.
72 sub search_housebound_choosers {
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);
81 =head3 search_housebound_deliverers
83 Returns all Patrons which are Housebound deliverers.
87 sub search_housebound_deliverers {
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);
96 =head3 search_upcoming_membership_expires
98 my $patrons = Koha::Patrons->search_upcoming_membership_expires();
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
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};
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;
119 $params->{dateexpiry} = {
120 ">=" => $dtf->format_date( $date_before ),
121 "<=" => $dtf->format_date( $date_after ),
123 return $self->SUPER::search(
124 $params, { join => ['branchcode', 'categorycode'] }
128 =head3 search_patrons_to_anonymise
130 my $patrons = Koha::Patrons->search_patrons_to_anonymise( { before => $older_than_date, [ library => $library ] } );
132 This method returns all patrons who has an issue history older than a given date.
136 sub search_patrons_to_anonymise {
137 my ( $class, $params ) = @_;
138 my $older_than_date = $params->{before};
139 my $library = $params->{library};
140 $older_than_date = $older_than_date ? dt_from_string($older_than_date) : dt_from_string;
142 ( C4::Context->preference('IndependentBranches') && C4::Context->userenv && !C4::Context->IsSuperLibrarian() && C4::Context->userenv->{branch} )
143 ? C4::Context->userenv->{branch}
145 my $anonymous_patron = C4::Context->preference('AnonymousPatron') || undef;
147 my $dtf = Koha::Database->new->schema->storage->datetime_parser;
148 my $rs = $class->_resultset->search(
149 { returndate => { '<' => $dtf->format_datetime($older_than_date), },
150 'old_issues.borrowernumber' => { 'not' => undef },
151 privacy => { '<>' => 0 }, # Keep forever
152 ( $library ? ( 'old_issues.branchcode' => $library ) : () ),
153 ( $anonymous_patron ? ( 'old_issues.borrowernumber' => { '!=' => $anonymous_patron } ) : () ),
155 { join => ["old_issues"],
159 return Koha::Patrons->_new_from_dbic($rs);
162 =head3 anonymise_issue_history
164 Koha::Patrons->search->anonymise_issue_history( { [ before => $older_than_date ] } );
166 Anonymise issue history (old_issues) for all patrons older than the given date (optional).
167 To make sure all the conditions are met, the caller has the responsibility to
168 call search_patrons_to_anonymise to filter the Koha::Patrons set
172 sub anonymise_issue_history {
173 my ( $self, $params ) = @_;
175 my $older_than_date = $params->{before};
177 $older_than_date = dt_from_string $older_than_date if $older_than_date;
179 # The default of 0 does not work due to foreign key constraints
180 # The anonymisation should not fail quietly if AnonymousPatron is not a valid entry
181 # Set it to undef (NULL)
182 my $dtf = Koha::Database->new->schema->storage->datetime_parser;
184 while ( my $patron = $self->next ) {
185 my $old_issues_to_anonymise = $patron->old_checkouts->search(
190 { '<' => $dtf->format_datetime($older_than_date) } )
195 my $anonymous_patron = C4::Context->preference('AnonymousPatron') || undef;
196 $nb_rows += $old_issues_to_anonymise->update( { 'old_issues.borrowernumber' => $anonymous_patron } );
203 Koha::Patrons->search({ some filters here })->delete({ move => 1 });
205 Delete passed set of patron objects.
206 Wrapper for Koha::Patron->delete. (We do not want to bypass Koha::Patron
207 and let DBIx do the job without further housekeeping.)
208 Includes a move to deletedborrowers if move flag set.
210 Just like DBIx, the delete will only succeed when all entries could be
211 deleted. Returns true or throws an exception.
216 my ( $self, $params ) = @_;
218 $self->_resultset->result_source->schema->txn_do( sub {
219 my ( $set, $params ) = @_;
220 my $count = $set->count;
221 while ( my $patron = $set->next ) {
223 next unless $patron->in_storage;
225 $patron->move_to_deleted if $params->{move};
231 return $patrons_deleted;
234 =head3 filter_by_expiration_date
236 Koha::Patrons->filter_by_expiration_date{{ days => $x });
238 Returns set of Koha patron objects expired $x days.
242 sub filter_by_expiration_date {
243 my ( $class, $params ) = @_;
245 return $class->filter_by_last_update(
247 timestamp_column_name => 'dateexpiry',
248 days => $params->{days} || 0,
254 =head3 search_unsubscribed
256 Koha::Patrons->search_unsubscribed;
258 Returns a set of Koha patron objects for patrons that recently
259 unsubscribed and are not locked (candidates for locking).
260 Depends on UnsubscribeReflectionDelay.
264 sub search_unsubscribed {
267 my $delay = C4::Context->preference('UnsubscribeReflectionDelay');
268 if( !defined($delay) || $delay eq q{} ) {
270 return $class->search({ borrowernumber => undef });
272 my $parser = Koha::Database->new->schema->storage->datetime_parser;
273 my $dt = dt_from_string()->subtract( days => $delay );
274 my $str = $parser->format_datetime($dt);
275 my $fails = C4::Context->preference('FailedLoginAttempts') || 0;
276 my $cond = [ undef, 0, 1..$fails-1 ]; # NULL, 0, 1..fails-1 (if fails>0)
277 return $class->search(
279 'patron_consents.refused_on' => { '<=' => $str },
280 'login_attempts' => $cond,
282 { join => 'patron_consents' },
286 =head3 search_anonymize_candidates
288 Koha::Patrons->search_anonymize_candidates({ locked => 1 });
290 Returns a set of Koha patron objects for patrons whose account is expired
291 and locked (if parameter set). These are candidates for anonymizing.
292 Depends on PatronAnonymizeDelay.
296 sub search_anonymize_candidates {
297 my ( $class, $params ) = @_;
299 my $delay = C4::Context->preference('PatronAnonymizeDelay');
300 if( !defined($delay) || $delay eq q{} ) {
302 return $class->search({ borrowernumber => undef });
305 my $parser = Koha::Database->new->schema->storage->datetime_parser;
306 my $dt = dt_from_string()->subtract( days => $delay );
307 my $str = $parser->format_datetime($dt);
308 $cond->{dateexpiry} = { '<=' => $str };
309 $cond->{anonymized} = 0; # not yet done
310 if( $params->{locked} ) {
311 my $fails = C4::Context->preference('FailedLoginAttempts') || 0;
312 $cond->{login_attempts} = [ -and => { '!=' => undef }, { -not_in => [0, 1..$fails-1 ] } ]; # -not_in does not like undef
314 return $class->search( $cond );
317 =head3 search_anonymized
319 Koha::Patrons->search_anonymized;
321 Returns a set of Koha patron objects for patron accounts that have been
322 anonymized before and could be removed.
323 Depends on PatronRemovalDelay.
327 sub search_anonymized {
330 my $delay = C4::Context->preference('PatronRemovalDelay');
331 if( !defined($delay) || $delay eq q{} ) {
333 return $class->search({ borrowernumber => undef });
336 my $parser = Koha::Database->new->schema->storage->datetime_parser;
337 my $dt = dt_from_string()->subtract( days => $delay );
338 my $str = $parser->format_datetime($dt);
339 $cond->{dateexpiry} = { '<=' => $str };
340 $cond->{anonymized} = 1;
341 return $class->search( $cond );
346 Koha::Patrons->search({ some filters })->lock({ expire => 1, remove => 1 })
348 Lock the passed set of patron objects. Optionally expire and remove holds.
349 Wrapper around Koha::Patron->lock.
354 my ( $self, $params ) = @_;
355 my $count = $self->count;
356 while( my $patron = $self->next ) {
357 $patron->lock($params);
363 Koha::Patrons->search({ some filters })->anonymize();
365 Anonymize passed set of patron objects.
366 Wrapper around Koha::Patron->anonymize.
372 my $count = $self->count;
373 while( my $patron = $self->next ) {
378 =head3 search_patrons_to_update_category
380 my $patrons = Koha::Patrons->search_patrons_to_update_category( {
381 from => $from_category,
382 fine_max => $fine_max,
383 fine_min => $fin_min,
384 too_young => $too_young,
388 This method returns all patron who should be updated from one category to another meeting criteria:
390 from - borrower categorycode
391 fine_min - with fines totaling at least this amount
392 fine_max - with fines above this amount
393 too_young - if passed, select patrons who are under the age limit for the current category
394 too_old - if passed, select patrons who are over the age limit for the current category
398 sub search_patrons_to_update_category {
399 my ( $self, $params ) = @_;
403 my $cat_from = Koha::Patron::Categories->find($params->{from});
404 $search_params->{categorycode}=$params->{from};
405 if ($params->{too_young} || $params->{too_old}){
406 my $dtf = Koha::Database->new->schema->storage->datetime_parser;
407 if( $cat_from->dateofbirthrequired && $params->{too_young} ) {
408 my $date_after = dt_from_string()->subtract( years => $cat_from->dateofbirthrequired);
409 $search_params->{dateofbirth}{'>'} = $dtf->format_datetime( $date_after );
411 if( $cat_from->upperagelimit && $params->{too_old} ) {
412 my $date_before = dt_from_string()->subtract( years => $cat_from->upperagelimit);
413 $search_params->{dateofbirth}{'<'} = $dtf->format_datetime( $date_before );
416 if ($params->{fine_min} || $params->{fine_max}) {
417 $query{join} = ["accountlines"];
418 $query{columns} = ["borrowernumber"];
419 $query{group_by} = ["borrowernumber"];
420 $query{having} = \['COALESCE(sum(accountlines.amountoutstanding),0) <= ?',$params->{fine_max}] if defined $params->{fine_max};
421 $query{having} = \['COALESCE(sum(accountlines.amountoutstanding),0) >= ?',$params->{fine_min}] if defined $params->{fine_min};
423 return $self->search($search_params,\%query);
426 =head3 update_category_to
428 Koha::Patrons->search->update_category_to( {
429 category => $to_category,
432 Update supplied patrons from current category to another and take care of guarantor info.
433 To make sure all the conditions are met, the caller has the responsibility to
434 call search_patrons_to_update to filter the Koha::Patrons set
438 sub update_category_to {
439 my ( $self, $params ) = @_;
441 while( my $patron = $self->next ) {
443 $patron->categorycode($params->{category})->store();
448 =head3 filter_by_attribute_type
450 my $patrons = Koha::Patrons->filter_by_attribute_type($attribute_type_code);
452 Return a Koha::Patrons set with patrons having the attribute defined.
456 sub filter_by_attribute_type {
457 my ( $self, $attribute_type ) = @_;
458 my $rs = Koha::Patron::Attributes->search( { code => $attribute_type } )
459 ->_resultset()->search_related('borrowernumber');
460 return Koha::Patrons->_new_from_dbic($rs);
463 =head3 filter_by_attribute_value
465 my $patrons = Koha::Patrons->filter_by_attribute_value($attribute_value);
467 Return a Koha::Patrons set with patrong having the attribute value passed in parameter.
471 sub filter_by_attribute_value {
472 my ( $self, $attribute_value ) = @_;
473 my $rs = Koha::Patron::Attributes->search(
475 'borrower_attribute_types.staff_searchable' => 1,
476 attribute => { like => "%$attribute_value%" }
478 { join => 'borrower_attribute_types' }
479 )->_resultset()->search_related('borrowernumber');
480 return Koha::Patrons->_new_from_dbic($rs);
497 return 'Koha::Patron';
502 Kyle M Hall <kyle@bywatersolutions.com>