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
207 Koha::Patrons->search({ some filters here })->delete({ move => 1 });
209 Delete passed set of patron objects.
210 Wrapper for Koha::Patron->delete. (We do not want to bypass Koha::Patron
211 and let DBIx do the job without further housekeeping.)
212 Includes a move to deletedborrowers if move flag set.
214 Just like DBIx, the delete will only succeed when all entries could be
215 deleted. Returns true or throws an exception.
220 my ( $self, $params ) = @_;
222 $self->_resultset->result_source->schema->txn_do( sub {
223 my ( $set, $params ) = @_;
224 my $count = $set->count;
225 while ( my $patron = $set->next ) {
227 next unless $patron->in_storage;
229 $patron->move_to_deleted if $params->{move};
235 return $patrons_deleted;
238 =head3 search_unsubscribed
240 Koha::Patrons->search_unsubscribed;
242 Returns a set of Koha patron objects for patrons that recently
243 unsubscribed and are not locked (candidates for locking).
244 Depends on UnsubscribeReflectionDelay.
248 sub search_unsubscribed {
251 my $delay = C4::Context->preference('UnsubscribeReflectionDelay');
252 if( !defined($delay) || $delay eq q{} ) {
254 return $class->search({ borrowernumber => undef });
256 my $parser = Koha::Database->new->schema->storage->datetime_parser;
257 my $dt = dt_from_string()->subtract( days => $delay );
258 my $str = $parser->format_datetime($dt);
259 my $fails = C4::Context->preference('FailedLoginAttempts') || 0;
260 my $cond = [ undef, 0, 1..$fails-1 ]; # NULL, 0, 1..fails-1 (if fails>0)
261 return $class->search(
263 'patron_consents.refused_on' => { '<=' => $str },
264 'login_attempts' => $cond,
266 { join => 'patron_consents' },
270 =head3 search_anonymize_candidates
272 Koha::Patrons->search_anonymize_candidates({ locked => 1 });
274 Returns a set of Koha patron objects for patrons whose account is expired
275 and locked (if parameter set). These are candidates for anonymizing.
276 Depends on PatronAnonymizeDelay.
280 sub search_anonymize_candidates {
281 my ( $class, $params ) = @_;
283 my $delay = C4::Context->preference('PatronAnonymizeDelay');
284 if( !defined($delay) || $delay eq q{} ) {
286 return $class->search({ borrowernumber => undef });
289 my $parser = Koha::Database->new->schema->storage->datetime_parser;
290 my $dt = dt_from_string()->subtract( days => $delay );
291 my $str = $parser->format_datetime($dt);
292 $cond->{dateexpiry} = { '<=' => $str };
293 $cond->{anonymized} = 0; # not yet done
294 if( $params->{locked} ) {
295 my $fails = C4::Context->preference('FailedLoginAttempts') || 0;
296 $cond->{login_attempts} = [ -and => { '!=' => undef }, { -not_in => [0, 1..$fails-1 ] } ]; # -not_in does not like undef
298 return $class->search( $cond );
301 =head3 search_anonymized
303 Koha::Patrons->search_anonymized;
305 Returns a set of Koha patron objects for patron accounts that have been
306 anonymized before and could be removed.
307 Depends on PatronRemovalDelay.
311 sub search_anonymized {
314 my $delay = C4::Context->preference('PatronRemovalDelay');
315 if( !defined($delay) || $delay eq q{} ) {
317 return $class->search({ borrowernumber => undef });
320 my $parser = Koha::Database->new->schema->storage->datetime_parser;
321 my $dt = dt_from_string()->subtract( days => $delay );
322 my $str = $parser->format_datetime($dt);
323 $cond->{dateexpiry} = { '<=' => $str };
324 $cond->{anonymized} = 1;
325 return $class->search( $cond );
330 Koha::Patrons->search({ some filters })->lock({ expire => 1, remove => 1 })
332 Lock the passed set of patron objects. Optionally expire and remove holds.
333 Wrapper around Koha::Patron->lock.
338 my ( $self, $params ) = @_;
339 my $count = $self->count;
340 while( my $patron = $self->next ) {
341 $patron->lock($params);
347 Koha::Patrons->search({ some filters })->anonymize();
349 Anonymize passed set of patron objects.
350 Wrapper around Koha::Patron->anonymize.
356 my $count = $self->count;
357 while( my $patron = $self->next ) {
362 =head3 search_patrons_to_update_category
364 my $patrons = Koha::Patrons->search_patrons_to_update_category( {
365 from => $from_category,
366 fine_max => $fine_max,
367 fine_min => $fin_min,
368 too_young => $too_young,
372 This method returns all patron who should be updated from one category to another meeting criteria:
374 from - borrower categorycode
375 fine_min - with fines totaling at least this amount
376 fine_max - with fines above this amount
377 too_young - if passed, select patrons who are under the age limit for the current category
378 too_old - if passed, select patrons who are over the age limit for the current category
382 sub search_patrons_to_update_category {
383 my ( $self, $params ) = @_;
387 my $cat_from = Koha::Patron::Categories->find($params->{from});
388 $search_params->{categorycode}=$params->{from};
389 if ($params->{too_young} || $params->{too_old}){
390 my $dtf = Koha::Database->new->schema->storage->datetime_parser;
391 if( $cat_from->dateofbirthrequired && $params->{too_young} ) {
392 my $date_after = dt_from_string()->subtract( years => $cat_from->dateofbirthrequired);
393 $search_params->{dateofbirth}{'>'} = $dtf->format_datetime( $date_after );
395 if( $cat_from->upperagelimit && $params->{too_old} ) {
396 my $date_before = dt_from_string()->subtract( years => $cat_from->upperagelimit);
397 $search_params->{dateofbirth}{'<'} = $dtf->format_datetime( $date_before );
400 if ($params->{fine_min} || $params->{fine_max}) {
401 $query{join} = ["accountlines"];
402 $query{columns} = ["borrowernumber"];
403 $query{group_by} = ["borrowernumber"];
404 $query{having} = \['COALESCE(sum(accountlines.amountoutstanding),0) <= ?',$params->{fine_max}] if defined $params->{fine_max};
405 $query{having} = \['COALESCE(sum(accountlines.amountoutstanding),0) >= ?',$params->{fine_min}] if defined $params->{fine_min};
407 return $self->search($search_params,\%query);
410 =head3 update_category_to
412 Koha::Patrons->search->update_category_to( {
413 category => $to_category,
416 Update supplied patrons from current category to another and take care of guarantor info.
417 To make sure all the conditions are met, the caller has the responsibility to
418 call search_patrons_to_update to filter the Koha::Patrons set
422 sub update_category_to {
423 my ( $self, $params ) = @_;
425 while( my $patron = $self->next ) {
427 $patron->categorycode($params->{category})->store();
432 =head3 filter_by_attribute_type
434 my $patrons = Koha::Patrons->filter_by_attribute_type($attribute_type_code);
436 Return a Koha::Patrons set with patrons having the attribute defined.
440 sub filter_by_attribute_type {
441 my ( $self, $attribute_type ) = @_;
442 my $rs = Koha::Patron::Attributes->search( { code => $attribute_type } )
443 ->_resultset()->search_related('borrowernumber');
444 return Koha::Patrons->_new_from_dbic($rs);
447 =head3 filter_by_attribute_value
449 my $patrons = Koha::Patrons->filter_by_attribute_value($attribute_value);
451 Return a Koha::Patrons set with patrong having the attribute value passed in parameter.
455 sub filter_by_attribute_value {
456 my ( $self, $attribute_value ) = @_;
457 my $rs = Koha::Patron::Attributes->search(
459 'borrower_attribute_types.staff_searchable' => 1,
460 attribute => { like => "%$attribute_value%" }
462 { join => 'borrower_attribute_types' }
463 )->_resultset()->search_related('borrowernumber');
464 return Koha::Patrons->_new_from_dbic($rs);
481 return 'Koha::Patron';
486 Kyle M Hall <kyle@bywatersolutions.com>