Bug 20482: DBRev 17.12.00.039
[koha.git] / Koha / Patron.pm
blob35cfce4ca1b92678591dcd2838034789e3faa75d
1 package Koha::Patron;
3 # Copyright ByWater Solutions 2014
4 # Copyright PTFS Europe 2016
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
11 # version.
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.
21 use Modern::Perl;
23 use Carp;
24 use List::MoreUtils qw( uniq );
25 use Text::Unaccent qw( unac_string );
27 use C4::Context;
28 use C4::Log;
29 use Koha::Checkouts;
30 use Koha::Database;
31 use Koha::DateUtils;
32 use Koha::Holds;
33 use Koha::Old::Checkouts;
34 use Koha::Patron::Categories;
35 use Koha::Patron::HouseboundProfile;
36 use Koha::Patron::HouseboundRole;
37 use Koha::Patron::Images;
38 use Koha::Patrons;
39 use Koha::Virtualshelves;
40 use Koha::Club::Enrollments;
41 use Koha::Account;
42 use Koha::Subscription::Routinglists;
44 use base qw(Koha::Object);
46 our $RESULTSET_PATRON_ID_MAPPING = {
47 Accountline => 'borrowernumber',
48 Aqbasketuser => 'borrowernumber',
49 Aqbudget => 'budget_owner_id',
50 Aqbudgetborrower => 'borrowernumber',
51 ArticleRequest => 'borrowernumber',
52 BorrowerAttribute => 'borrowernumber',
53 BorrowerDebarment => 'borrowernumber',
54 BorrowerFile => 'borrowernumber',
55 BorrowerModification => 'borrowernumber',
56 ClubEnrollment => 'borrowernumber',
57 Issue => 'borrowernumber',
58 ItemsLastBorrower => 'borrowernumber',
59 Linktracker => 'borrowernumber',
60 Message => 'borrowernumber',
61 MessageQueue => 'borrowernumber',
62 OldIssue => 'borrowernumber',
63 OldReserve => 'borrowernumber',
64 Rating => 'borrowernumber',
65 Reserve => 'borrowernumber',
66 Review => 'borrowernumber',
67 SearchHistory => 'userid',
68 Statistic => 'borrowernumber',
69 Suggestion => 'suggestedby',
70 TagAll => 'borrowernumber',
71 Virtualshelfcontent => 'borrowernumber',
72 Virtualshelfshare => 'borrowernumber',
73 Virtualshelve => 'owner',
76 =head1 NAME
78 Koha::Patron - Koha Patron Object class
80 =head1 API
82 =head2 Class Methods
84 =cut
86 =head3 delete
88 $patron->delete
90 Delete patron's holds, lists and finally the patron.
92 Lists owned by the borrower are deleted, but entries from the borrower to
93 other lists are kept.
95 =cut
97 sub delete {
98 my ($self) = @_;
100 my $deleted;
101 $self->_result->result_source->schema->txn_do(
102 sub {
103 # Delete Patron's holds
104 $self->holds->delete;
106 # Delete all lists and all shares of this borrower
107 # Consistent with the approach Koha uses on deleting individual lists
108 # Note that entries in virtualshelfcontents added by this borrower to
109 # lists of others will be handled by a table constraint: the borrower
110 # is set to NULL in those entries.
111 # NOTE:
112 # We could handle the above deletes via a constraint too.
113 # But a new BZ report 11889 has been opened to discuss another approach.
114 # Instead of deleting we could also disown lists (based on a pref).
115 # In that way we could save shared and public lists.
116 # The current table constraints support that idea now.
117 # This pref should then govern the results of other routines/methods such as
118 # Koha::Virtualshelf->new->delete too.
119 # FIXME Could be $patron->get_lists
120 $_->delete for Koha::Virtualshelves->search( { owner => $self->borrowernumber } );
122 $deleted = $self->SUPER::delete;
124 logaction( "MEMBERS", "DELETE", $self->borrowernumber, "" ) if C4::Context->preference("BorrowersLog");
127 return $deleted;
131 =head3 category
133 my $patron_category = $patron->category
135 Return the patron category for this patron
137 =cut
139 sub category {
140 my ( $self ) = @_;
141 return Koha::Patron::Category->_new_from_dbic( $self->_result->categorycode );
144 =head3 guarantor
146 Returns a Koha::Patron object for this patron's guarantor
148 =cut
150 sub guarantor {
151 my ( $self ) = @_;
153 return unless $self->guarantorid();
155 return Koha::Patrons->find( $self->guarantorid() );
158 sub image {
159 my ( $self ) = @_;
161 return scalar Koha::Patron::Images->find( $self->borrowernumber );
164 sub library {
165 my ( $self ) = @_;
166 return Koha::Library->_new_from_dbic($self->_result->branchcode);
169 =head3 guarantees
171 Returns the guarantees (list of Koha::Patron) of this patron
173 =cut
175 sub guarantees {
176 my ( $self ) = @_;
178 return Koha::Patrons->search( { guarantorid => $self->borrowernumber } );
181 =head3 housebound_profile
183 Returns the HouseboundProfile associated with this patron.
185 =cut
187 sub housebound_profile {
188 my ( $self ) = @_;
189 my $profile = $self->_result->housebound_profile;
190 return Koha::Patron::HouseboundProfile->_new_from_dbic($profile)
191 if ( $profile );
192 return;
195 =head3 housebound_role
197 Returns the HouseboundRole associated with this patron.
199 =cut
201 sub housebound_role {
202 my ( $self ) = @_;
204 my $role = $self->_result->housebound_role;
205 return Koha::Patron::HouseboundRole->_new_from_dbic($role) if ( $role );
206 return;
209 =head3 siblings
211 Returns the siblings of this patron.
213 =cut
215 sub siblings {
216 my ( $self ) = @_;
218 my $guarantor = $self->guarantor;
220 return unless $guarantor;
222 return Koha::Patrons->search(
224 guarantorid => {
225 '!=' => undef,
226 '=' => $guarantor->id,
228 borrowernumber => {
229 '!=' => $self->borrowernumber,
235 =head3 merge_with
237 my $patron = Koha::Patrons->find($id);
238 $patron->merge_with( \@patron_ids );
240 This subroutine merges a list of patrons into the patron record. This is accomplished by finding
241 all related patron ids for the patrons to be merged in other tables and changing the ids to be that
242 of the keeper patron.
244 =cut
246 sub merge_with {
247 my ( $self, $patron_ids ) = @_;
249 my @patron_ids = @{ $patron_ids };
251 # Ensure the keeper isn't in the list of patrons to merge
252 @patron_ids = grep { $_ ne $self->id } @patron_ids;
254 my $schema = Koha::Database->new()->schema();
256 my $results;
258 $self->_result->result_source->schema->txn_do( sub {
259 foreach my $patron_id (@patron_ids) {
260 my $patron = Koha::Patrons->find( $patron_id );
262 next unless $patron;
264 # Unbless for safety, the patron will end up being deleted
265 $results->{merged}->{$patron_id}->{patron} = $patron->unblessed;
267 while (my ($r, $field) = each(%$RESULTSET_PATRON_ID_MAPPING)) {
268 my $rs = $schema->resultset($r)->search({ $field => $patron_id });
269 $results->{merged}->{ $patron_id }->{updated}->{$r} = $rs->count();
270 $rs->update({ $field => $self->id });
273 $patron->move_to_deleted();
274 $patron->delete();
278 return $results;
283 =head3 wants_check_for_previous_checkout
285 $wants_check = $patron->wants_check_for_previous_checkout;
287 Return 1 if Koha needs to perform PrevIssue checking, else 0.
289 =cut
291 sub wants_check_for_previous_checkout {
292 my ( $self ) = @_;
293 my $syspref = C4::Context->preference("checkPrevCheckout");
295 # Simple cases
296 ## Hard syspref trumps all
297 return 1 if ($syspref eq 'hardyes');
298 return 0 if ($syspref eq 'hardno');
299 ## Now, patron pref trumps all
300 return 1 if ($self->checkprevcheckout eq 'yes');
301 return 0 if ($self->checkprevcheckout eq 'no');
303 # More complex: patron inherits -> determine category preference
304 my $checkPrevCheckoutByCat = $self->category->checkprevcheckout;
305 return 1 if ($checkPrevCheckoutByCat eq 'yes');
306 return 0 if ($checkPrevCheckoutByCat eq 'no');
308 # Finally: category preference is inherit, default to 0
309 if ($syspref eq 'softyes') {
310 return 1;
311 } else {
312 return 0;
316 =head3 do_check_for_previous_checkout
318 $do_check = $patron->do_check_for_previous_checkout($item);
320 Return 1 if the bib associated with $ITEM has previously been checked out to
321 $PATRON, 0 otherwise.
323 =cut
325 sub do_check_for_previous_checkout {
326 my ( $self, $item ) = @_;
328 # Find all items for bib and extract item numbers.
329 my @items = Koha::Items->search({biblionumber => $item->{biblionumber}});
330 my @item_nos;
331 foreach my $item (@items) {
332 push @item_nos, $item->itemnumber;
335 # Create (old)issues search criteria
336 my $criteria = {
337 borrowernumber => $self->borrowernumber,
338 itemnumber => \@item_nos,
341 # Check current issues table
342 my $issues = Koha::Checkouts->search($criteria);
343 return 1 if $issues->count; # 0 || N
345 # Check old issues table
346 my $old_issues = Koha::Old::Checkouts->search($criteria);
347 return $old_issues->count; # 0 || N
350 =head3 is_debarred
352 my $debarment_expiration = $patron->is_debarred;
354 Returns the date a patron debarment will expire, or undef if the patron is not
355 debarred
357 =cut
359 sub is_debarred {
360 my ($self) = @_;
362 return unless $self->debarred;
363 return $self->debarred
364 if $self->debarred =~ '^9999'
365 or dt_from_string( $self->debarred ) > dt_from_string;
366 return;
369 =head3 is_expired
371 my $is_expired = $patron->is_expired;
373 Returns 1 if the patron is expired or 0;
375 =cut
377 sub is_expired {
378 my ($self) = @_;
379 return 0 unless $self->dateexpiry;
380 return 0 if $self->dateexpiry =~ '^9999';
381 return 1 if dt_from_string( $self->dateexpiry ) < dt_from_string->truncate( to => 'day' );
382 return 0;
385 =head3 is_going_to_expire
387 my $is_going_to_expire = $patron->is_going_to_expire;
389 Returns 1 if the patron is going to expired, depending on the NotifyBorrowerDeparture pref or 0
391 =cut
393 sub is_going_to_expire {
394 my ($self) = @_;
396 my $delay = C4::Context->preference('NotifyBorrowerDeparture') || 0;
398 return 0 unless $delay;
399 return 0 unless $self->dateexpiry;
400 return 0 if $self->dateexpiry =~ '^9999';
401 return 1 if dt_from_string( $self->dateexpiry )->subtract( days => $delay ) < dt_from_string->truncate( to => 'day' );
402 return 0;
405 =head3 update_password
407 my $updated = $patron->update_password( $userid, $password );
409 Update the userid and the password of a patron.
410 If the userid already exists, returns and let DBIx::Class warns
411 This will add an entry to action_logs if BorrowersLog is set.
413 =cut
415 sub update_password {
416 my ( $self, $userid, $password ) = @_;
417 eval { $self->userid($userid)->store; };
418 return if $@; # Make sure the userid is not already in used by another patron
419 $self->update(
421 password => $password,
422 login_attempts => 0,
425 logaction( "MEMBERS", "CHANGE PASS", $self->borrowernumber, "" ) if C4::Context->preference("BorrowersLog");
426 return 1;
429 =head3 renew_account
431 my $new_expiry_date = $patron->renew_account
433 Extending the subscription to the expiry date.
435 =cut
437 sub renew_account {
438 my ($self) = @_;
439 my $date;
440 if ( C4::Context->preference('BorrowerRenewalPeriodBase') eq 'combination' ) {
441 $date = ( dt_from_string gt dt_from_string( $self->dateexpiry ) ) ? dt_from_string : dt_from_string( $self->dateexpiry );
442 } else {
443 $date =
444 C4::Context->preference('BorrowerRenewalPeriodBase') eq 'dateexpiry'
445 ? dt_from_string( $self->dateexpiry )
446 : dt_from_string;
448 my $expiry_date = $self->category->get_expiry_date($date);
450 $self->dateexpiry($expiry_date);
451 $self->date_renewed( dt_from_string() );
452 $self->store();
454 $self->add_enrolment_fee_if_needed;
456 logaction( "MEMBERS", "RENEW", $self->borrowernumber, "Membership renewed" ) if C4::Context->preference("BorrowersLog");
457 return dt_from_string( $expiry_date )->truncate( to => 'day' );
460 =head3 has_overdues
462 my $has_overdues = $patron->has_overdues;
464 Returns the number of patron's overdues
466 =cut
468 sub has_overdues {
469 my ($self) = @_;
470 my $dtf = Koha::Database->new->schema->storage->datetime_parser;
471 return $self->_result->issues->search({ date_due => { '<' => $dtf->format_datetime( dt_from_string() ) } })->count;
474 =head3 track_login
476 $patron->track_login;
477 $patron->track_login({ force => 1 });
479 Tracks a (successful) login attempt.
480 The preference TrackLastPatronActivity must be enabled. Or you
481 should pass the force parameter.
483 =cut
485 sub track_login {
486 my ( $self, $params ) = @_;
487 return if
488 !$params->{force} &&
489 !C4::Context->preference('TrackLastPatronActivity');
490 $self->lastseen( dt_from_string() )->store;
493 =head3 move_to_deleted
495 my $is_moved = $patron->move_to_deleted;
497 Move a patron to the deletedborrowers table.
498 This can be done before deleting a patron, to make sure the data are not completely deleted.
500 =cut
502 sub move_to_deleted {
503 my ($self) = @_;
504 my $patron_infos = $self->unblessed;
505 delete $patron_infos->{updated_on}; #This ensures the updated_on date in deletedborrowers will be set to the current timestamp
506 return Koha::Database->new->schema->resultset('Deletedborrower')->create($patron_infos);
509 =head3 article_requests
511 my @requests = $borrower->article_requests();
512 my $requests = $borrower->article_requests();
514 Returns either a list of ArticleRequests objects,
515 or an ArtitleRequests object, depending on the
516 calling context.
518 =cut
520 sub article_requests {
521 my ( $self ) = @_;
523 $self->{_article_requests} ||= Koha::ArticleRequests->search({ borrowernumber => $self->borrowernumber() });
525 return $self->{_article_requests};
528 =head3 article_requests_current
530 my @requests = $patron->article_requests_current
532 Returns the article requests associated with this patron that are incomplete
534 =cut
536 sub article_requests_current {
537 my ( $self ) = @_;
539 $self->{_article_requests_current} ||= Koha::ArticleRequests->search(
541 borrowernumber => $self->id(),
542 -or => [
543 { status => Koha::ArticleRequest::Status::Pending },
544 { status => Koha::ArticleRequest::Status::Processing }
549 return $self->{_article_requests_current};
552 =head3 article_requests_finished
554 my @requests = $biblio->article_requests_finished
556 Returns the article requests associated with this patron that are completed
558 =cut
560 sub article_requests_finished {
561 my ( $self, $borrower ) = @_;
563 $self->{_article_requests_finished} ||= Koha::ArticleRequests->search(
565 borrowernumber => $self->id(),
566 -or => [
567 { status => Koha::ArticleRequest::Status::Completed },
568 { status => Koha::ArticleRequest::Status::Canceled }
573 return $self->{_article_requests_finished};
576 =head3 add_enrolment_fee_if_needed
578 my $enrolment_fee = $patron->add_enrolment_fee_if_needed;
580 Add enrolment fee for a patron if needed.
582 =cut
584 sub add_enrolment_fee_if_needed {
585 my ($self) = @_;
586 my $enrolment_fee = $self->category->enrolmentfee;
587 if ( $enrolment_fee && $enrolment_fee > 0 ) {
588 # insert fee in patron debts
589 C4::Accounts::manualinvoice( $self->borrowernumber, '', '', 'A', $enrolment_fee );
591 return $enrolment_fee || 0;
594 =head3 checkouts
596 my $checkouts = $patron->checkouts
598 =cut
600 sub checkouts {
601 my ($self) = @_;
602 my $checkouts = $self->_result->issues;
603 return Koha::Checkouts->_new_from_dbic( $checkouts );
606 =head3 pending_checkouts
608 my $pending_checkouts = $patron->pending_checkouts
610 This method will return the same as $self->checkouts, but with a prefetch on
611 items, biblio and biblioitems.
613 It has been introduced to replaced the C4::Members::GetPendingIssues subroutine
615 It should not be used directly, prefer to access fields you need instead of
616 retrieving all these fields in one go.
619 =cut
621 sub pending_checkouts {
622 my( $self ) = @_;
623 my $checkouts = $self->_result->issues->search(
626 order_by => [
627 { -desc => 'me.timestamp' },
628 { -desc => 'issuedate' },
629 { -desc => 'issue_id' }, # Sort by issue_id should be enough
631 prefetch => { item => { biblio => 'biblioitems' } },
634 return Koha::Checkouts->_new_from_dbic( $checkouts );
637 =head3 old_checkouts
639 my $old_checkouts = $patron->old_checkouts
641 =cut
643 sub old_checkouts {
644 my ($self) = @_;
645 my $old_checkouts = $self->_result->old_issues;
646 return Koha::Old::Checkouts->_new_from_dbic( $old_checkouts );
649 =head3 get_overdues
651 my $overdue_items = $patron->get_overdues
653 Return the overdue items
655 =cut
657 sub get_overdues {
658 my ($self) = @_;
659 my $dtf = Koha::Database->new->schema->storage->datetime_parser;
660 return $self->checkouts->search(
662 'me.date_due' => { '<' => $dtf->format_datetime(dt_from_string) },
665 prefetch => { item => { biblio => 'biblioitems' } },
670 =head3 get_routing_lists
672 my @routinglists = $patron->get_routing_lists
674 Returns the routing lists a patron is subscribed to.
676 =cut
678 sub get_routing_lists {
679 my ($self) = @_;
680 my $routing_list_rs = $self->_result->subscriptionroutinglists;
681 return Koha::Subscription::Routinglists->_new_from_dbic($routing_list_rs);
684 =head3 get_age
686 my $age = $patron->get_age
688 Return the age of the patron
690 =cut
692 sub get_age {
693 my ($self) = @_;
694 my $today_str = dt_from_string->strftime("%Y-%m-%d");
695 return unless $self->dateofbirth;
696 my $dob_str = dt_from_string( $self->dateofbirth )->strftime("%Y-%m-%d");
698 my ( $dob_y, $dob_m, $dob_d ) = split /-/, $dob_str;
699 my ( $today_y, $today_m, $today_d ) = split /-/, $today_str;
701 my $age = $today_y - $dob_y;
702 if ( $dob_m . $dob_d > $today_m . $today_d ) {
703 $age--;
706 return $age;
709 =head3 account
711 my $account = $patron->account
713 =cut
715 sub account {
716 my ($self) = @_;
717 return Koha::Account->new( { patron_id => $self->borrowernumber } );
720 =head3 holds
722 my $holds = $patron->holds
724 Return all the holds placed by this patron
726 =cut
728 sub holds {
729 my ($self) = @_;
730 my $holds_rs = $self->_result->reserves->search( {}, { order_by => 'reservedate' } );
731 return Koha::Holds->_new_from_dbic($holds_rs);
734 =head3 old_holds
736 my $old_holds = $patron->old_holds
738 Return all the historical holds for this patron
740 =cut
742 sub old_holds {
743 my ($self) = @_;
744 my $old_holds_rs = $self->_result->old_reserves->search( {}, { order_by => 'reservedate' } );
745 return Koha::Old::Holds->_new_from_dbic($old_holds_rs);
748 =head3 notice_email_address
750 my $email = $patron->notice_email_address;
752 Return the email address of patron used for notices.
753 Returns the empty string if no email address.
755 =cut
757 sub notice_email_address{
758 my ( $self ) = @_;
760 my $which_address = C4::Context->preference("AutoEmailPrimaryAddress");
761 # if syspref is set to 'first valid' (value == OFF), look up email address
762 if ( $which_address eq 'OFF' ) {
763 return $self->first_valid_email_address;
766 return $self->$which_address || '';
769 =head3 first_valid_email_address
771 my $first_valid_email_address = $patron->first_valid_email_address
773 Return the first valid email address for a patron.
774 For now, the order is defined as email, emailpro, B_email.
775 Returns the empty string if the borrower has no email addresses.
777 =cut
779 sub first_valid_email_address {
780 my ($self) = @_;
782 return $self->email() || $self->emailpro() || $self->B_email() || q{};
785 =head3 get_club_enrollments
787 =cut
789 sub get_club_enrollments {
790 my ( $self, $return_scalar ) = @_;
792 my $e = Koha::Club::Enrollments->search( { borrowernumber => $self->borrowernumber(), date_canceled => undef } );
794 return $e if $return_scalar;
796 return wantarray ? $e->as_list : $e;
799 =head3 get_enrollable_clubs
801 =cut
803 sub get_enrollable_clubs {
804 my ( $self, $is_enrollable_from_opac, $return_scalar ) = @_;
806 my $params;
807 $params->{is_enrollable_from_opac} = $is_enrollable_from_opac
808 if $is_enrollable_from_opac;
809 $params->{is_email_required} = 0 unless $self->first_valid_email_address();
811 $params->{borrower} = $self;
813 my $e = Koha::Clubs->get_enrollable($params);
815 return $e if $return_scalar;
817 return wantarray ? $e->as_list : $e;
820 =head3 account_locked
822 my $is_locked = $patron->account_locked
824 Return true if the patron has reach the maximum number of login attempts (see pref FailedLoginAttempts).
825 Otherwise return false.
826 If the pref is not set (empty string, null or 0), the feature is considered as disabled.
828 =cut
830 sub account_locked {
831 my ($self) = @_;
832 my $FailedLoginAttempts = C4::Context->preference('FailedLoginAttempts');
833 return ( $FailedLoginAttempts
834 and $self->login_attempts
835 and $self->login_attempts >= $FailedLoginAttempts )? 1 : 0;
838 =head3 can_see_patron_infos
840 my $can_see = $patron->can_see_patron_infos( $patron );
842 Return true if the patron (usually the logged in user) can see the patron's infos for a given patron
844 =cut
846 sub can_see_patron_infos {
847 my ( $self, $patron ) = @_;
848 return $self->can_see_patrons_from( $patron->library->branchcode );
851 =head3 can_see_patrons_from
853 my $can_see = $patron->can_see_patrons_from( $branchcode );
855 Return true if the patron (usually the logged in user) can see the patron's infos from a given library
857 =cut
859 sub can_see_patrons_from {
860 my ( $self, $branchcode ) = @_;
861 my $can = 0;
862 if ( $self->branchcode eq $branchcode ) {
863 $can = 1;
864 } elsif ( $self->has_permission( { borrowers => 'view_borrower_infos_from_any_libraries' } ) ) {
865 $can = 1;
866 } elsif ( my $library_groups = $self->library->library_groups ) {
867 while ( my $library_group = $library_groups->next ) {
868 if ( $library_group->parent->has_child( $branchcode ) ) {
869 $can = 1;
870 last;
874 return $can;
877 =head3 libraries_where_can_see_patrons
879 my $libraries = $patron-libraries_where_can_see_patrons;
881 Return the list of branchcodes(!) of libraries the patron is allowed to see other patron's infos.
882 The branchcodes are arbitrarily returned sorted.
883 We are supposing here that the object is related to the logged in patron (use of C4::Context::only_my_library)
885 An empty array means no restriction, the patron can see patron's infos from any libraries.
887 =cut
889 sub libraries_where_can_see_patrons {
890 my ( $self ) = @_;
891 my $userenv = C4::Context->userenv;
893 return () unless $userenv; # For tests, but userenv should be defined in tests...
895 my @restricted_branchcodes;
896 if (C4::Context::only_my_library) {
897 push @restricted_branchcodes, $self->branchcode;
899 else {
900 unless (
901 $self->has_permission(
902 { borrowers => 'view_borrower_infos_from_any_libraries' }
906 my $library_groups = $self->library->library_groups({ ft_hide_patron_info => 1 });
907 if ( $library_groups->count )
909 while ( my $library_group = $library_groups->next ) {
910 my $parent = $library_group->parent;
911 if ( $parent->has_child( $self->branchcode ) ) {
912 push @restricted_branchcodes, $parent->children->get_column('branchcode');
917 @restricted_branchcodes = ( $self->branchcode ) unless @restricted_branchcodes;
921 @restricted_branchcodes = grep { defined $_ } @restricted_branchcodes;
922 @restricted_branchcodes = uniq(@restricted_branchcodes);
923 @restricted_branchcodes = sort(@restricted_branchcodes);
924 return @restricted_branchcodes;
927 sub has_permission {
928 my ( $self, $flagsrequired ) = @_;
929 return unless $self->userid;
930 # TODO code from haspermission needs to be moved here!
931 return C4::Auth::haspermission( $self->userid, $flagsrequired );
934 =head3 is_adult
936 my $is_adult = $patron->is_adult
938 Return true if the patron has a category with a type Adult (A) or Organization (I)
940 =cut
942 sub is_adult {
943 my ( $self ) = @_;
944 return $self->category->category_type =~ /^(A|I)$/ ? 1 : 0;
947 =head3 is_child
949 my $is_child = $patron->is_child
951 Return true if the patron has a category with a type Child (C)
953 =cut
954 sub is_child {
955 my( $self ) = @_;
956 return $self->category->category_type eq 'C' ? 1 : 0;
959 =head3 has_valid_userid
961 my $patron = Koha::Patrons->find(42);
962 $patron->userid( $new_userid );
963 my $has_a_valid_userid = $patron->has_valid_userid
965 my $patron = Koha::Patron->new( $params );
966 my $has_a_valid_userid = $patron->has_valid_userid
968 Return true if the current userid of this patron is valid/unique, otherwise false.
970 Note that this should be done in $self->store instead and raise an exception if needed.
972 =cut
974 sub has_valid_userid {
975 my ($self) = @_;
977 return 0 unless $self->userid;
979 return 0 if ( $self->userid eq C4::Context->config('user') ); # DB user
981 my $already_exists = Koha::Patrons->search(
983 userid => $self->userid,
985 $self->in_storage
986 ? ( borrowernumber => { '!=' => $self->borrowernumber } )
987 : ()
990 )->count;
991 return $already_exists ? 0 : 1;
994 =head3 generate_userid
996 my $patron = Koha::Patron->new( $params );
997 my $userid = $patron->generate_userid
999 Generate a userid using the $surname and the $firstname (if there is a value in $firstname).
1001 Return the generate userid ($firstname.$surname if there is a $firstname, or $surname if there is no value in $firstname) plus offset (0 if the $userid is unique, or a higher numeric value if not unique).
1003 # Note: Should we set $self->userid with the generated value?
1004 # Certainly yes, but we AddMember and ModMember will be rewritten
1006 =cut
1008 sub generate_userid {
1009 my ($self) = @_;
1010 my $userid;
1011 my $offset = 0;
1012 my $existing_userid = $self->userid;
1013 my $firstname = $self->firstname // q{};
1014 my $surname = $self->surname // q{};
1015 #The script will "do" the following code and increment the $offset until the generated userid is unique
1016 do {
1017 $firstname =~ s/[[:digit:][:space:][:blank:][:punct:][:cntrl:]]//g;
1018 $surname =~ s/[[:digit:][:space:][:blank:][:punct:][:cntrl:]]//g;
1019 $userid = lc(($firstname)? "$firstname.$surname" : $surname);
1020 $userid = unac_string('utf-8',$userid);
1021 $userid .= $offset unless $offset == 0;
1022 $self->userid( $userid );
1023 $offset++;
1024 } while (! $self->has_valid_userid );
1026 # Resetting to the previous value as the callers do not expect
1027 # this method to modify the userid attribute
1028 # This will be done later (move of AddMember and ModMember)
1029 $self->userid( $existing_userid );
1031 return $userid;
1035 =head2 Internal methods
1037 =head3 _type
1039 =cut
1041 sub _type {
1042 return 'Borrower';
1045 =head1 AUTHOR
1047 Kyle M Hall <kyle@bywatersolutions.com>
1048 Alex Sassmannshausen <alex.sassmannshausen@ptfs-europe.com>
1050 =cut