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>.
28 use Koha::ArticleRequests;
29 use Koha::ArticleRequest::Status;
31 use Koha::Exceptions::Patron;
32 use Koha::Patron::Categories;
33 use Date::Calc qw( Today Add_Delta_YMD );
35 use base qw(Koha::Objects);
39 Koha::Patron - Koha Patron Object class
49 my $patrons = Koha::Patrons->search_limit( $params, $attributes );
51 Returns all the patrons the logged in user is allowed to see
56 my ( $self, $params, $attributes ) = @_;
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;
64 $params->{'me.branchcode'} = { -in => \@restricted_branchcodes } if @restricted_branchcodes;
65 return $self->search( $params, $attributes );
68 =head3 search_housebound_choosers
70 Returns all Patrons which are Housebound choosers.
74 sub search_housebound_choosers {
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);
83 =head3 search_housebound_deliverers
85 Returns all Patrons which are Housebound deliverers.
89 sub search_housebound_deliverers {
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);
98 =head3 search_upcoming_membership_expires
100 my $patrons = Koha::Patrons->search_upcoming_membership_expires();
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
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};
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;
121 $params->{dateexpiry} = {
122 ">=" => $dtf->format_date( $date_before ),
123 "<=" => $dtf->format_date( $date_after ),
125 return $self->SUPER::search(
126 $params, { join => ['branchcode', 'categorycode'] }
130 =head3 search_patrons_to_anonymise
132 my $patrons = Koha::Patrons->search_patrons_to_anonymise( { before => $older_than_date, [ library => $library ] } );
134 This method returns all patrons who has an issue history older than a given date.
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;
144 ( C4::Context->preference('IndependentBranches') && C4::Context->userenv && !C4::Context->IsSuperLibrarian() && C4::Context->userenv->{branch} )
145 ? C4::Context->userenv->{branch}
147 my $anonymous_patron = C4::Context->preference('AnonymousPatron') || undef;
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 } ) : () ),
157 { join => ["old_issues"],
161 return Koha::Patrons->_new_from_dbic($rs);
164 =head3 anonymise_issue_history
166 Koha::Patrons->search->anonymise_issue_history( { [ before => $older_than_date ] } );
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
174 sub anonymise_issue_history {
175 my ( $self, $params ) = @_;
177 my $older_than_date = $params->{before};
179 $older_than_date = dt_from_string $older_than_date if $older_than_date;
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;
186 while ( my $patron = $self->next ) {
187 my $old_issues_to_anonymise = $patron->old_checkouts->search(
192 { '<' => $dtf->format_datetime($older_than_date) } )
197 my $anonymous_patron = C4::Context->preference('AnonymousPatron') || undef;
198 $nb_rows += $old_issues_to_anonymise->update( {
199 'old_issues.borrowernumber' => $anonymous_patron,
200 'old_issues.issuer' => $anonymous_patron
208 Koha::Patrons->search({ some filters here })->delete({ move => 1 });
210 Delete passed set of patron objects.
211 Wrapper for Koha::Patron->delete. (We do not want to bypass Koha::Patron
212 and let DBIx do the job without further housekeeping.)
213 Includes a move to deletedborrowers if move flag set.
215 Just like DBIx, the delete will only succeed when all entries could be
216 deleted. Returns true or throws an exception.
221 my ( $self, $params ) = @_;
223 $self->_resultset->result_source->schema->txn_do( sub {
224 my ( $set, $params ) = @_;
225 my $count = $set->count;
226 while ( my $patron = $set->next ) {
228 next unless $patron->in_storage;
230 $patron->move_to_deleted if $params->{move};
236 return $patrons_deleted;
239 =head3 search_unsubscribed
241 Koha::Patrons->search_unsubscribed;
243 Returns a set of Koha patron objects for patrons that recently
244 unsubscribed and are not locked (candidates for locking).
245 Depends on UnsubscribeReflectionDelay.
249 sub search_unsubscribed {
252 my $delay = C4::Context->preference('UnsubscribeReflectionDelay');
253 if( !defined($delay) || $delay eq q{} ) {
255 return $class->search({ borrowernumber => undef });
257 my $parser = Koha::Database->new->schema->storage->datetime_parser;
258 my $dt = dt_from_string()->subtract( days => $delay );
259 my $str = $parser->format_datetime($dt);
260 my $fails = C4::Context->preference('FailedLoginAttempts') || 0;
261 my $cond = [ undef, 0, 1..$fails-1 ]; # NULL, 0, 1..fails-1 (if fails>0)
262 return $class->search(
264 'patron_consents.refused_on' => { '<=' => $str },
265 'login_attempts' => $cond,
267 { join => 'patron_consents' },
271 =head3 search_anonymize_candidates
273 Koha::Patrons->search_anonymize_candidates({ locked => 1 });
275 Returns a set of Koha patron objects for patrons whose account is expired
276 and locked (if parameter set). These are candidates for anonymizing.
277 Depends on PatronAnonymizeDelay.
281 sub search_anonymize_candidates {
282 my ( $class, $params ) = @_;
284 my $delay = C4::Context->preference('PatronAnonymizeDelay');
285 if( !defined($delay) || $delay eq q{} ) {
287 return $class->search({ borrowernumber => undef });
290 my $parser = Koha::Database->new->schema->storage->datetime_parser;
291 my $dt = dt_from_string()->subtract( days => $delay );
292 my $str = $parser->format_datetime($dt);
293 $cond->{dateexpiry} = { '<=' => $str };
294 $cond->{anonymized} = 0; # not yet done
295 if( $params->{locked} ) {
296 my $fails = C4::Context->preference('FailedLoginAttempts') || 0;
297 $cond->{login_attempts} = [ -and => { '!=' => undef }, { -not_in => [0, 1..$fails-1 ] } ]; # -not_in does not like undef
299 return $class->search( $cond );
302 =head3 search_anonymized
304 Koha::Patrons->search_anonymized;
306 Returns a set of Koha patron objects for patron accounts that have been
307 anonymized before and could be removed.
308 Depends on PatronRemovalDelay.
312 sub search_anonymized {
315 my $delay = C4::Context->preference('PatronRemovalDelay');
316 if( !defined($delay) || $delay eq q{} ) {
318 return $class->search({ borrowernumber => undef });
321 my $parser = Koha::Database->new->schema->storage->datetime_parser;
322 my $dt = dt_from_string()->subtract( days => $delay );
323 my $str = $parser->format_datetime($dt);
324 $cond->{dateexpiry} = { '<=' => $str };
325 $cond->{anonymized} = 1;
326 return $class->search( $cond );
331 Koha::Patrons->search({ some filters })->lock({ expire => 1, remove => 1 })
333 Lock the passed set of patron objects. Optionally expire and remove holds.
334 Wrapper around Koha::Patron->lock.
339 my ( $self, $params ) = @_;
340 my $count = $self->count;
341 while( my $patron = $self->next ) {
342 $patron->lock($params);
348 Koha::Patrons->search({ some filters })->anonymize();
350 Anonymize passed set of patron objects.
351 Wrapper around Koha::Patron->anonymize.
357 my $count = $self->count;
358 while( my $patron = $self->next ) {
363 =head3 search_patrons_to_update_category
365 my $patrons = Koha::Patrons->search_patrons_to_update_category( {
366 from => $from_category,
367 fine_max => $fine_max,
368 fine_min => $fin_min,
369 too_young => $too_young,
373 This method returns all patron who should be updated from one category to another meeting criteria:
375 from - borrower categorycode
376 fine_min - with fines totaling at least this amount
377 fine_max - with fines above this amount
378 too_young - if passed, select patrons who are under the age limit for the current category
379 too_old - if passed, select patrons who are over the age limit for the current category
383 sub search_patrons_to_update_category {
384 my ( $self, $params ) = @_;
388 my $cat_from = Koha::Patron::Categories->find($params->{from});
389 $search_params->{categorycode}=$params->{from};
390 if ($params->{too_young} || $params->{too_old}){
391 my $dtf = Koha::Database->new->schema->storage->datetime_parser;
392 if( $cat_from->dateofbirthrequired && $params->{too_young} ) {
393 my $date_after = dt_from_string()->subtract( years => $cat_from->dateofbirthrequired);
394 $search_params->{dateofbirth}{'>'} = $dtf->format_datetime( $date_after );
396 if( $cat_from->upperagelimit && $params->{too_old} ) {
397 my $date_before = dt_from_string()->subtract( years => $cat_from->upperagelimit);
398 $search_params->{dateofbirth}{'<'} = $dtf->format_datetime( $date_before );
401 if ($params->{fine_min} || $params->{fine_max}) {
402 $query{join} = ["accountlines"];
403 $query{columns} = ["borrowernumber"];
404 $query{group_by} = ["borrowernumber"];
405 $query{having} = \['COALESCE(sum(accountlines.amountoutstanding),0) <= ?',$params->{fine_max}] if defined $params->{fine_max};
406 $query{having} = \['COALESCE(sum(accountlines.amountoutstanding),0) >= ?',$params->{fine_min}] if defined $params->{fine_min};
408 return $self->search($search_params,\%query);
411 =head3 update_category_to
413 Koha::Patrons->search->update_category_to( {
414 category => $to_category,
417 Update supplied patrons from current category to another and take care of guarantor info.
418 To make sure all the conditions are met, the caller has the responsibility to
419 call search_patrons_to_update to filter the Koha::Patrons set
423 sub update_category_to {
424 my ( $self, $params ) = @_;
426 while( my $patron = $self->next ) {
428 $patron->categorycode($params->{category})->store();
433 =head3 filter_by_attribute_type
435 my $patrons = Koha::Patrons->filter_by_attribute_type($attribute_type_code);
437 Return a Koha::Patrons set with patrons having the attribute defined.
441 sub filter_by_attribute_type {
442 my ( $self, $attribute_type ) = @_;
443 my $rs = Koha::Patron::Attributes->search( { code => $attribute_type } )
444 ->_resultset()->search_related('borrowernumber');
445 return Koha::Patrons->_new_from_dbic($rs);
448 =head3 filter_by_attribute_value
450 my $patrons = Koha::Patrons->filter_by_attribute_value($attribute_value);
452 Return a Koha::Patrons set with patrong having the attribute value passed in parameter.
456 sub filter_by_attribute_value {
457 my ( $self, $attribute_value ) = @_;
458 my $rs = Koha::Patron::Attributes->search(
460 'borrower_attribute_types.staff_searchable' => 1,
461 attribute => { like => "%$attribute_value%" }
463 { join => 'borrower_attribute_types' }
464 )->_resultset()->search_related('borrowernumber');
465 return Koha::Patrons->_new_from_dbic($rs);
482 return 'Koha::Patron';
487 Kyle M Hall <kyle@bywatersolutions.com>