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 under the
9 # terms of the GNU General Public License as published by the Free Software
10 # Foundation; either version 3 of the License, or (at your option) any later
13 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
14 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
15 # A PARTICULAR PURPOSE. See the GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License along
18 # with Koha; if not, write to the Free Software Foundation, Inc.,
19 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
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( { 'old_issues.borrowernumber' => $anonymous_patron } );
205 Koha::Patrons->search({ some filters here })->delete({ move => 1, verbose => 1 });
207 Delete passed set of patron objects.
208 Wrapper for Koha::Patron->delete. (We do not want to bypass Koha::Patron
209 and let DBIx do the job without further housekeeping.)
210 Includes a move to deletedborrowers if move flag set.
212 Just like DBIx, the delete will only succeed when all entries could be
213 deleted. Returns true or throws an exception.
218 my ( $self, $params ) = @_;
220 $self->_resultset->result_source->schema->txn_do( sub {
221 my ( $set, $params ) = @_;
222 my $count = $set->count;
223 while( my $patron = $set->next ) {
224 $patron->move_to_deleted if $params->{move};
225 $patron->delete == 1 || Koha::Exceptions::Patron::FailedDelete->throw;
228 warn "Deleted $count patrons\n" if $params->{verbose};
230 return $patrons_deleted;
233 =head3 search_unsubscribed
235 Koha::Patrons->search_unsubscribed;
237 Returns a set of Koha patron objects for patrons that recently
238 unsubscribed and are not locked (candidates for locking).
239 Depends on UnsubscribeReflectionDelay.
243 sub search_unsubscribed {
246 my $delay = C4::Context->preference('UnsubscribeReflectionDelay');
247 if( !defined($delay) || $delay eq q{} ) {
249 return $class->search({ borrowernumber => undef });
251 my $parser = Koha::Database->new->schema->storage->datetime_parser;
252 my $dt = dt_from_string()->subtract( days => $delay );
253 my $str = $parser->format_datetime($dt);
254 my $fails = C4::Context->preference('FailedLoginAttempts') || 0;
255 my $cond = [ undef, 0, 1..$fails-1 ]; # NULL, 0, 1..fails-1 (if fails>0)
256 return $class->search(
258 'patron_consents.refused_on' => { '<=' => $str },
259 'login_attempts' => $cond,
261 { join => 'patron_consents' },
265 =head3 search_anonymize_candidates
267 Koha::Patrons->search_anonymize_candidates({ locked => 1 });
269 Returns a set of Koha patron objects for patrons whose account is expired
270 and locked (if parameter set). These are candidates for anonymizing.
271 Depends on PatronAnonymizeDelay.
275 sub search_anonymize_candidates {
276 my ( $class, $params ) = @_;
278 my $delay = C4::Context->preference('PatronAnonymizeDelay');
279 if( !defined($delay) || $delay eq q{} ) {
281 return $class->search({ borrowernumber => undef });
284 my $parser = Koha::Database->new->schema->storage->datetime_parser;
285 my $dt = dt_from_string()->subtract( days => $delay );
286 my $str = $parser->format_datetime($dt);
287 $cond->{dateexpiry} = { '<=' => $str };
288 $cond->{anonymized} = 0; # not yet done
289 if( $params->{locked} ) {
290 my $fails = C4::Context->preference('FailedLoginAttempts');
291 $cond->{login_attempts} = [ -and => { '!=' => undef }, { -not_in => [0, 1..$fails-1 ] } ]; # -not_in does not like undef
293 return $class->search( $cond );
296 =head3 search_anonymized
298 Koha::Patrons->search_anonymized;
300 Returns a set of Koha patron objects for patron accounts that have been
301 anonymized before and could be removed.
302 Depends on PatronRemovalDelay.
306 sub search_anonymized {
309 my $delay = C4::Context->preference('PatronRemovalDelay');
310 if( !defined($delay) || $delay eq q{} ) {
312 return $class->search({ borrowernumber => undef });
315 my $parser = Koha::Database->new->schema->storage->datetime_parser;
316 my $dt = dt_from_string()->subtract( days => $delay );
317 my $str = $parser->format_datetime($dt);
318 $cond->{dateexpiry} = { '<=' => $str };
319 $cond->{anonymized} = 1;
320 return $class->search( $cond );
325 Koha::Patrons->search({ some filters })->lock({ expire => 1, remove => 1, verbose => 1 })
327 Lock the passed set of patron objects. Optionally expire and remove holds.
328 Optional verbose flag is used in cron job.
329 Wrapper around Koha::Patron->lock.
334 my ( $self, $params ) = @_;
335 my $count = $self->count;
336 while( my $patron = $self->next ) {
337 $patron->lock($params);
339 if( $params->{verbose} ) {
340 warn "Locked $count patrons\n";
346 Koha::Patrons->search({ some filters })->anonymize({ verbose => 1 });
348 Anonymize passed set of patron objects.
349 Optional verbose flag is used in cron job.
350 Wrapper around Koha::Patron->anonymize.
355 my ( $self, $params ) = @_;
356 my $count = $self->count;
357 while( my $patron = $self->next ) {
360 if( $params->{verbose} ) {
361 warn "Anonymized $count patrons\n";
364 =head3 search_patrons_to_update
366 my $patrons = Koha::Patrons->search_patrons_to_anonymise( {
367 from => $from_category,
368 fine_max => $fine_max,
369 fine_min => $fin_min,
374 This method returns all patron who should be updated form one category to another meeting criteria:
376 from - original category
377 fine_min - with fines totaling at least this amount
378 fine_max - with fines above this amount
379 au - under the age limit for 'from'
380 ao - over the agelimit for 'from'
384 sub search_patrons_to_update {
385 my ( $self, $params ) = @_;
387 my $search_params = $params->{search_params};;
389 my $cat_from = Koha::Patron::Categories->find($params->{from});
390 $search_params->{categorycode}=$params->{from};
391 if ($params->{ageover} || $params->{ageunder}){
392 if( $cat_from->dateofbirthrequired && $params->{ageunder} ) {
393 my $date_after = output_pref({
394 dt => dt_from_string()->subtract( years => $cat_from->dateofbirthrequired),
398 $search_params->{dateofbirth}{'>'} = $date_after;
400 if( $cat_from->upperagelimit && $params->{ageover} ) {
401 my $date_before = output_pref({
402 dt => dt_from_string()->subtract( years => $cat_from->upperagelimit),
406 $search_params->{dateofbirth}{'<'} = $date_before;
409 if ($params->{fine_min} || $params->{fine_max}) {
410 $query{join} = ["accountlines"];
411 $query{select} = ["borrowernumber", { sum => 'amountoutstanding', -as => 'total_fines'} ];
412 $query{as} = [qw/borrowernumber total_fines/];
413 $query{group_by} = ["borrowernumber"];
414 $query{having}{total_fines}{'<='}=$params->{fine_max} if defined $params->{fine_max};
415 $query{having}{total_fines}{'>='}=$params->{fine_min} if defined $params->{fine_min};
417 return Koha::Patrons->search($search_params,\%query);
420 =head3 update_category
422 Koha::Patrons->search->update_category( {
426 Update supplied patrons from one category to another and take care of guarantor info.
427 To make sure all the conditions are met, the caller has the responsibility to
428 call search_patrons_to_update to filter the Koha::Patrons set
432 sub update_category {
433 my ( $self, $params ) = @_;
434 my $to = $params->{to};
436 my $to_cat = Koha::Patron::Categories->find($to);
437 return unless $to_cat;
440 my $remove_guarantor = ( $to_cat->category_type ne 'C' || $to_cat->category_type ne 'P' ) ? 1 : 0;
441 while( my $patron = $self->next ) {
443 if ( $remove_guarantor && ($patron->category->category_type eq 'C' || $patron->category->category_type eq 'P') ) {
444 $patron->guarantorid(0);
445 $patron->contactname('');
446 $patron->contactfirstname('');
447 $patron->contacttitle('');
448 $patron->relationship('');
450 $patron->categorycode($to);
469 return 'Koha::Patron';
474 Kyle M Hall <kyle@bywatersolutions.com>