Bug 17942 [Follow-up] Update style of the web installer with Bootstrap 3
[koha.git] / C4 / Members.pm
index 383799d..cf7ea7c 100644 (file)
@@ -25,7 +25,7 @@ use strict;
 use C4::Context;
 use String::Random qw( random_string );
 use Scalar::Util qw( looks_like_number );
-use Date::Calc qw/Today Add_Delta_YM check_date Date_to_Days/;
+use Date::Calc qw/Today check_date Date_to_Days/;
 use C4::Log; # logaction
 use C4::Overdues;
 use C4::Reserves;
@@ -37,11 +37,14 @@ use C4::NewsChannels; #get slip news
 use DateTime;
 use Koha::Database;
 use Koha::DateUtils;
-use Koha::Patron::Debarments qw(IsDebarred);
 use Text::Unaccent qw( unac_string );
 use Koha::AuthUtils qw(hash_password);
 use Koha::Database;
+use Koha::Holds;
 use Koha::List::Patron;
+use Koha::Patrons;
+use Koha::Patron::Categories;
+use Koha::Schema;
 
 our (@ISA,@EXPORT,@EXPORT_OK,$debug);
 
@@ -57,44 +60,22 @@ BEGIN {
     @ISA = qw(Exporter);
     #Get data
     push @EXPORT, qw(
-        &Search
-        &GetMemberDetails
-        &GetMemberRelatives
         &GetMember
 
-        &GetMemberIssuesAndFines
         &GetPendingIssues
         &GetAllIssues
 
         &GetFirstValidEmailAddress
         &GetNoticeEmailAddress
 
-        &GetAge
-        &GetSortDetails
-        &GetTitles
-
-        &GetHideLostItemsPreference
-
-        &IsMemberBlocked
         &GetMemberAccountRecords
         &GetBorNotifyAcctRecord
 
-        &GetborCatFromCatType
-        &GetBorrowercategory
-        GetBorrowerCategorycode
-        &GetBorrowercategoryList
-
         &GetBorrowersToExpunge
-        &GetBorrowersWhoHaveNeverBorrowed
-        &GetBorrowersWithIssuesHistoryOlderThan
-
-        &GetExpiryDate
-        &GetUpcomingMembershipExpires
 
         &IssueSlip
         GetBorrowersWithEmail
 
-        HasOverdues
         GetOverduesForPatron
     );
 
@@ -104,17 +85,11 @@ BEGIN {
         &changepassword
     );
 
-    #Delete data
-    push @EXPORT, qw(
-        &DelMember
-    );
-
     #Insert data
     push @EXPORT, qw(
         &AddMember
+    &AddMember_Auto
         &AddMember_Opac
-        &MoveMemberToDeleted
-        &ExtendMemberSubscriptionTo
     );
 
     #Check data
@@ -142,107 +117,6 @@ This module contains routines for adding, modifying and deleting members/patrons
 
 =head1 FUNCTIONS
 
-=head2 GetMemberDetails
-
-($borrower) = &GetMemberDetails($borrowernumber, $cardnumber);
-
-Looks up a patron and returns information about him or her. If
-C<$borrowernumber> is true (nonzero), C<&GetMemberDetails> looks
-up the borrower by number; otherwise, it looks up the borrower by card
-number.
-
-C<$borrower> is a reference-to-hash whose keys are the fields of the
-borrowers table in the Koha database. In addition,
-C<$borrower-E<gt>{flags}> is a hash giving more detailed information
-about the patron. Its keys act as flags :
-
-    if $borrower->{flags}->{LOST} {
-        # Patron's card was reported lost
-    }
-
-If the state of a flag means that the patron should not be
-allowed to borrow any more books, then it will have a C<noissues> key
-with a true value.
-
-See patronflags for more details.
-
-C<$borrower-E<gt>{authflags}> is a hash giving more detailed information
-about the top-level permissions flags set for the borrower.  For example,
-if a user has the "editcatalogue" permission,
-C<$borrower-E<gt>{authflags}-E<gt>{editcatalogue}> will exist and have
-the value "1".
-
-=cut
-
-sub GetMemberDetails {
-    my ( $borrowernumber, $cardnumber ) = @_;
-    my $dbh = C4::Context->dbh;
-    my $query;
-    my $sth;
-    if ($borrowernumber) {
-        $sth = $dbh->prepare("
-            SELECT borrowers.*,
-                   category_type,
-                   categories.description,
-                   categories.BlockExpiredPatronOpacActions,
-                   reservefee,
-                   enrolmentperiod
-            FROM borrowers
-            LEFT JOIN categories ON borrowers.categorycode=categories.categorycode
-            WHERE borrowernumber = ?
-        ");
-        $sth->execute($borrowernumber);
-    }
-    elsif ($cardnumber) {
-        $sth = $dbh->prepare("
-            SELECT borrowers.*,
-                   category_type,
-                   categories.description,
-                   categories.BlockExpiredPatronOpacActions,
-                   reservefee,
-                   enrolmentperiod
-            FROM borrowers
-            LEFT JOIN categories ON borrowers.categorycode = categories.categorycode
-            WHERE cardnumber = ?
-        ");
-        $sth->execute($cardnumber);
-    }
-    else {
-        return;
-    }
-    my $borrower = $sth->fetchrow_hashref;
-    return unless $borrower;
-    my ($amount) = GetMemberAccountRecords($borrower->{borrowernumber});
-    $borrower->{'amountoutstanding'} = $amount;
-    # FIXME - patronflags calls GetMemberAccountRecords... just have patronflags return $amount
-    my $flags = patronflags( $borrower);
-    my $accessflagshash;
-
-    $sth = $dbh->prepare("select bit,flag from userflags");
-    $sth->execute;
-    while ( my ( $bit, $flag ) = $sth->fetchrow ) {
-        if ( $borrower->{'flags'} && $borrower->{'flags'} & 2**$bit ) {
-            $accessflagshash->{$flag} = 1;
-        }
-    }
-    $borrower->{'flags'}     = $flags;
-    $borrower->{'authflags'} = $accessflagshash;
-
-    # Handle setting the true behavior for BlockExpiredPatronOpacActions
-    $borrower->{'BlockExpiredPatronOpacActions'} =
-      C4::Context->preference('BlockExpiredPatronOpacActions')
-      if ( $borrower->{'BlockExpiredPatronOpacActions'} == -1 );
-
-    $borrower->{'is_expired'} = 0;
-    $borrower->{'is_expired'} = 1 if
-      defined($borrower->{dateexpiry}) &&
-      $borrower->{'dateexpiry'} ne '0000-00-00' &&
-      Date_to_Days( Today() ) >
-      Date_to_Days( split /-/, $borrower->{'dateexpiry'} );
-
-    return ($borrower);    #, $flags, $accessflagshash);
-}
-
 =head2 patronflags
 
  $flags = &patronflags($patron);
@@ -467,125 +341,6 @@ sub GetMember {
     return;
 }
 
-=head2 IsMemberBlocked
-
-  my ($block_status, $count) = IsMemberBlocked( $borrowernumber );
-
-Returns whether a patron is restricted or has overdue items that may result
-in a block of circulation privileges.
-
-C<$block_status> can have the following values:
-
-1 if the patron is currently restricted, in which case
-C<$count> is the expiration date (9999-12-31 for indefinite)
-
--1 if the patron has overdue items, in which case C<$count> is the number of them
-
-0 if the patron has no overdue items or outstanding fine days, in which case C<$count> is 0
-
-Existing active restrictions are checked before current overdue items.
-
-=cut
-
-sub IsMemberBlocked {
-    my $borrowernumber = shift;
-    my $dbh            = C4::Context->dbh;
-
-    my $blockeddate = Koha::Patron::Debarments::IsDebarred($borrowernumber);
-
-    return ( 1, $blockeddate ) if $blockeddate;
-
-    # if he have late issues
-    my $sth = $dbh->prepare(
-        "SELECT COUNT(*) as latedocs
-         FROM issues
-         WHERE borrowernumber = ?
-         AND date_due < now()"
-    );
-    $sth->execute($borrowernumber);
-    my $latedocs = $sth->fetchrow_hashref->{'latedocs'};
-
-    return ( -1, $latedocs ) if $latedocs > 0;
-
-    return ( 0, 0 );
-}
-
-=head2 GetMemberIssuesAndFines
-
-  ($overdue_count, $issue_count, $total_fines) = &GetMemberIssuesAndFines($borrowernumber);
-
-Returns aggregate data about items borrowed by the patron with the
-given borrowernumber.
-
-C<&GetMemberIssuesAndFines> returns a three-element array.  C<$overdue_count> is the
-number of overdue items the patron currently has borrowed. C<$issue_count> is the
-number of books the patron currently has borrowed.  C<$total_fines> is
-the total fine currently due by the borrower.
-
-=cut
-
-#'
-sub GetMemberIssuesAndFines {
-    my ( $borrowernumber ) = @_;
-    my $dbh   = C4::Context->dbh;
-    my $query = "SELECT COUNT(*) FROM issues WHERE borrowernumber = ?";
-
-    $debug and warn $query."\n";
-    my $sth = $dbh->prepare($query);
-    $sth->execute($borrowernumber);
-    my $issue_count = $sth->fetchrow_arrayref->[0];
-
-    $sth = $dbh->prepare(
-        "SELECT COUNT(*) FROM issues 
-         WHERE borrowernumber = ? 
-         AND date_due < now()"
-    );
-    $sth->execute($borrowernumber);
-    my $overdue_count = $sth->fetchrow_arrayref->[0];
-
-    $sth = $dbh->prepare("SELECT SUM(amountoutstanding) FROM accountlines WHERE borrowernumber = ?");
-    $sth->execute($borrowernumber);
-    my $total_fines = $sth->fetchrow_arrayref->[0];
-
-    return ($overdue_count, $issue_count, $total_fines);
-}
-
-
-=head2 columns
-
-  my @columns = C4::Member::columns();
-
-Returns an array of borrowers' table columns on success,
-and an empty array on failure.
-
-=cut
-
-sub columns {
-
-    # Pure ANSI SQL goodness.
-    my $sql = 'SELECT * FROM borrowers WHERE 1=0;';
-
-    # Get the database handle.
-    my $dbh = C4::Context->dbh;
-
-    # Run the SQL statement to load STH's readonly properties.
-    my $sth = $dbh->prepare($sql);
-    my $rv = $sth->execute();
-
-    # This only fails if the table doesn't exist.
-    # This will always be called AFTER an install or upgrade,
-    # so borrowers will exist!
-    my @data;
-    if ($sth->{NUM_OF_FIELDS}>0) {
-        @data = @{$sth->{NAME}};
-    }
-    else {
-        @data = ();
-    }
-    return @data;
-}
-
-
 =head2 ModMember
 
   my $success = ModMember(borrowernumber => $borrowernumber,
@@ -600,6 +355,14 @@ true on success, or false on failure
 
 sub ModMember {
     my (%data) = @_;
+
+    # trim whitespace from data which has some non-whitespace in it.
+    foreach my $field_name (keys(%data)) {
+        if ( defined $data{$field_name} && $data{$field_name} =~ /\S/ ) {
+            $data{$field_name} =~ s/^\s*|\s*$//g;
+        }
+    }
+
     # test to know if you must update or not the borrower password
     if (exists $data{password}) {
         if ($data{password} eq '****' or $data{password} eq '') {
@@ -613,32 +376,31 @@ sub ModMember {
         }
     }
 
-    my $old_categorycode = GetBorrowerCategorycode( $data{borrowernumber} );
+    my $old_categorycode = Koha::Patrons->find( $data{borrowernumber} )->categorycode;
 
     # get only the columns of a borrower
     my $schema = Koha::Database->new()->schema;
     my @columns = $schema->source('Borrower')->columns;
     my $new_borrower = { map { join(' ', @columns) =~ /$_/ ? ( $_ => $data{$_} ) : () } keys(%data) };
-    delete $new_borrower->{flags};
 
     $new_borrower->{dateofbirth}     ||= undef if exists $new_borrower->{dateofbirth};
     $new_borrower->{dateenrolled}    ||= undef if exists $new_borrower->{dateenrolled};
     $new_borrower->{dateexpiry}      ||= undef if exists $new_borrower->{dateexpiry};
     $new_borrower->{debarred}        ||= undef if exists $new_borrower->{debarred};
     $new_borrower->{sms_provider_id} ||= undef if exists $new_borrower->{sms_provider_id};
+    $new_borrower->{guarantorid}     ||= undef if exists $new_borrower->{guarantorid};
 
-    my $rs = $schema->resultset('Borrower')->search({
-        borrowernumber => $new_borrower->{borrowernumber},
-     });
+    my $patron = Koha::Patrons->find( $new_borrower->{borrowernumber} );
 
     delete $new_borrower->{userid} if exists $new_borrower->{userid} and not $new_borrower->{userid};
 
-    my $execute_success = $rs->update($new_borrower);
-    if ($execute_success ne '0E0') { # only proceed if the update was a success
+    my $execute_success = $patron->store if $patron->set($new_borrower);
+
+    if ($execute_success) { # only proceed if the update was a success
         # If the patron changes to a category with enrollment fee, we add a fee
         if ( $data{categorycode} and $data{categorycode} ne $old_categorycode ) {
             if ( C4::Context->preference('FeeOnChangePatronCategory') ) {
-                AddEnrolmentFeeIfNeeded( $data{categorycode}, $data{borrowernumber} );
+                $patron->add_enrolment_fee_if_needed;
             }
         }
 
@@ -688,20 +450,31 @@ sub AddMember {
     my $dbh = C4::Context->dbh;
     my $schema = Koha::Database->new()->schema;
 
+    # trim whitespace from data which has some non-whitespace in it.
+    foreach my $field_name (keys(%data)) {
+        if ( defined $data{$field_name} && $data{$field_name} =~ /\S/ ) {
+            $data{$field_name} =~ s/^\s*|\s*$//g;
+        }
+    }
+
     # generate a proper login if none provided
     $data{'userid'} = Generate_Userid( $data{'borrowernumber'}, $data{'firstname'}, $data{'surname'} )
       if ( $data{'userid'} eq '' || !Check_Userid( $data{'userid'} ) );
 
     # add expiration date if it isn't already there
-    unless ( $data{'dateexpiry'} ) {
-        $data{'dateexpiry'} = GetExpiryDate( $data{'categorycode'}, output_pref( { dt => dt_from_string, dateonly => 1, dateformat => 'iso' } ) );
-    }
+    $data{dateexpiry} ||= Koha::Patron::Categories->find( $data{categorycode} )->get_expiry_date;
 
     # add enrollment date if it isn't already there
     unless ( $data{'dateenrolled'} ) {
         $data{'dateenrolled'} = output_pref( { dt => dt_from_string, dateonly => 1, dateformat => 'iso' } );
     }
 
+    if ( C4::Context->preference("autoMemberNum") ) {
+        if ( not exists $data{cardnumber} or not defined $data{cardnumber} or $data{cardnumber} eq '' ) {
+            $data{cardnumber} = fixup_cardnumber( $data{cardnumber} );
+        }
+    }
+
     my $patron_category = $schema->resultset('Category')->find( $data{'categorycode'} );
     $data{'privacy'} =
         $patron_category->default_privacy() eq 'default' ? 1
@@ -721,14 +494,17 @@ sub AddMember {
     $data{'dateofbirth'}     = undef if ( not $data{'dateofbirth'} );
     $data{'debarred'}        = undef if ( not $data{'debarred'} );
     $data{'sms_provider_id'} = undef if ( not $data{'sms_provider_id'} );
+    $data{'guarantorid'}     = undef if ( not $data{'guarantorid'} );
 
     # get only the columns of Borrower
+    # FIXME Do we really need this check?
     my @columns = $schema->source('Borrower')->columns;
     my $new_member = { map { join(' ',@columns) =~ /$_/ ? ( $_ => $data{$_} )  : () } keys(%data) } ;
+
     delete $new_member->{borrowernumber};
 
-    my $rs = $schema->resultset('Borrower');
-    $data{borrowernumber} = $rs->create($new_member)->id;
+    my $patron = Koha::Patron->new( $new_member )->store;
+    $data{borrowernumber} = $patron->borrowernumber;
 
     # If NorwegianPatronDBEnable is enabled, we set syncstatus to something that a
     # cronjob will use for syncing with NL
@@ -742,10 +518,9 @@ sub AddMember {
         });
     }
 
-    # mysql_insertid is probably bad.  not necessarily accurate and mysql-specific at best.
     logaction("MEMBERS", "CREATE", $data{'borrowernumber'}, "") if C4::Context->preference("BorrowersLog");
 
-    AddEnrolmentFeeIfNeeded( $data{categorycode}, $data{borrowernumber} );
+    $patron->add_enrolment_fee_if_needed;
 
     return $data{borrowernumber};
 }
@@ -813,35 +588,6 @@ sub Generate_Userid {
    return $newuid;
 }
 
-sub changepassword {
-    my ( $uid, $member, $digest ) = @_;
-    my $dbh = C4::Context->dbh;
-
-#Make sure the userid chosen is unique and not theirs if non-empty. If it is not,
-#Then we need to tell the user and have them create a new one.
-    my $resultcode;
-    my $sth =
-      $dbh->prepare(
-        "SELECT * FROM borrowers WHERE userid=? AND borrowernumber != ?");
-    $sth->execute( $uid, $member );
-    if ( ( $uid ne '' ) && ( my $row = $sth->fetchrow_hashref ) ) {
-        $resultcode=0;
-    }
-    else {
-        #Everything is good so we can update the information.
-        $sth =
-          $dbh->prepare(
-            "update borrowers set userid=?, password=? where borrowernumber=?");
-        $sth->execute( $uid, $digest, $member );
-        $resultcode=1;
-    }
-    
-    logaction("MEMBERS", "CHANGE PASS", $member, "") if C4::Context->preference("BorrowersLog");
-    return $resultcode;    
-}
-
-
-
 =head2 fixup_cardnumber
 
 Warning: The caller is responsible for locking the members table in write
@@ -921,7 +667,7 @@ Looks up what the patron with the given borrowernumber has borrowed.
 C<&GetPendingIssues> returns a
 reference-to-array where each element is a reference-to-hash; the
 keys are the fields from the C<issues>, C<biblio>, and C<items> tables.
-The keys include C<biblioitems> fields except marc and marcxml.
+The keys include C<biblioitems> fields.
 
 =cut
 
@@ -941,7 +687,6 @@ sub GetPendingIssues {
         }
     }
 
-    # must avoid biblioitems.* to prevent large marc and marcxml fields from killing performance
     # FIXME: namespace collision: each table has "timestamp" fields.  Which one is "timestamp" ?
     # FIXME: circ/ciculation.pl tries to sort by timestamp!
     # FIXME: namespace collision: other collisions possible.
@@ -1174,45 +919,6 @@ sub GetBorNotifyAcctRecord {
     return ( $total, \@acctlines, $numlines );
 }
 
-=head2 checkuniquemember (OUEST-PROVENCE)
-
-  ($result,$categorycode)  = &checkuniquemember($collectivity,$surname,$firstname,$dateofbirth);
-
-Checks that a member exists or not in the database.
-
-C<&result> is nonzero (=exist) or 0 (=does not exist)
-C<&categorycode> is from categorycode table
-C<&collectivity> is 1 (= we add a collectivity) or 0 (= we add a physical member)
-C<&surname> is the surname
-C<&firstname> is the firstname (only if collectivity=0)
-C<&dateofbirth> is the date of birth in ISO format (only if collectivity=0)
-
-=cut
-
-# FIXME: This function is not legitimate.  Multiple patrons might have the same first/last name and birthdate.
-# This is especially true since first name is not even a required field.
-
-sub checkuniquemember {
-    my ( $collectivity, $surname, $firstname, $dateofbirth ) = @_;
-    my $dbh = C4::Context->dbh;
-    my $request = ($collectivity) ?
-        "SELECT borrowernumber,categorycode FROM borrowers WHERE surname=? " :
-            ($dateofbirth) ?
-            "SELECT borrowernumber,categorycode FROM borrowers WHERE surname=? and firstname=?  and dateofbirth=?" :
-            "SELECT borrowernumber,categorycode FROM borrowers WHERE surname=? and firstname=?";
-    my $sth = $dbh->prepare($request);
-    if ($collectivity) {
-        $sth->execute( uc($surname) );
-    } elsif($dateofbirth){
-        $sth->execute( uc($surname), ucfirst($firstname), $dateofbirth );
-    }else{
-        $sth->execute( uc($surname), ucfirst($firstname));
-    }
-    my @data = $sth->fetchrow;
-    ( $data[0] ) and return $data[0], $data[1];
-    return 0;
-}
-
 sub checkcardnumber {
     my ( $cardnumber, $borrowernumber ) = @_;
 
@@ -1266,6 +972,9 @@ sub get_cardnumber_length {
         }
 
     }
+    my $borrower = Koha::Schema->resultset('Borrower');
+    my $field_size = $borrower->result_source->column_info('cardnumber')->{size};
+    $min = $field_size if $min > $field_size;
     return ( $min, $max );
 }
 
@@ -1281,20 +990,10 @@ addresses.
 
 sub GetFirstValidEmailAddress {
     my $borrowernumber = shift;
-    my $dbh = C4::Context->dbh;
-    my $sth = $dbh->prepare( "SELECT email, emailpro, B_email FROM borrowers where borrowernumber = ? ");
-    $sth->execute( $borrowernumber );
-    my $data = $sth->fetchrow_hashref;
 
-    if ($data->{'email'}) {
-       return $data->{'email'};
-    } elsif ($data->{'emailpro'}) {
-       return $data->{'emailpro'};
-    } elsif ($data->{'B_email'}) {
-       return $data->{'B_email'};
-    } else {
-       return '';
-    }
+    my $borrower = Koha::Patrons->find( $borrowernumber );
+
+    return $borrower->first_valid_email_address();
 }
 
 =head2 GetNoticeEmailAddress
@@ -1326,463 +1025,6 @@ sub GetNoticeEmailAddress {
     return $data->{'primaryemail'} || '';
 }
 
-=head2 GetExpiryDate 
-
-  $expirydate = GetExpiryDate($categorycode, $dateenrolled);
-
-Calculate expiry date given a categorycode and starting date.  Date argument must be in ISO format.
-Return date is also in ISO format.
-
-=cut
-
-sub GetExpiryDate {
-    my ( $categorycode, $dateenrolled ) = @_;
-    my $enrolments;
-    if ($categorycode) {
-        my $dbh = C4::Context->dbh;
-        my $sth = $dbh->prepare("SELECT enrolmentperiod,enrolmentperioddate FROM categories WHERE categorycode=?");
-        $sth->execute($categorycode);
-        $enrolments = $sth->fetchrow_hashref;
-    }
-    # die "GetExpiryDate: for enrollmentperiod $enrolmentperiod (category '$categorycode') starting $dateenrolled.\n";
-    my @date = split (/-/,$dateenrolled);
-    if($enrolments->{enrolmentperiod}){
-        return sprintf("%04d-%02d-%02d", Add_Delta_YM(@date,0,$enrolments->{enrolmentperiod}));
-    }else{
-        return $enrolments->{enrolmentperioddate};
-    }
-}
-
-=head2 GetUpcomingMembershipExpires
-
-    my $expires = GetUpcomingMembershipExpires({
-        branch => $branch, before => $before, after => $after,
-    });
-
-    $branch is an optional branch code.
-    $before/$after is an optional number of days before/after the date that
-    is set by the preference MembershipExpiryDaysNotice.
-    If the pref would be 14, before 2 and after 3, you will get all expires
-    from 12 to 17 days.
-
-=cut
-
-sub GetUpcomingMembershipExpires {
-    my ( $params ) = @_;
-    my $before = $params->{before} || 0;
-    my $after  = $params->{after} || 0;
-    my $branch = $params->{branch};
-
-    my $dbh = C4::Context->dbh;
-    my $days = C4::Context->preference("MembershipExpiryDaysNotice") || 0;
-    my $date1 = dt_from_string->add( days => $days - $before );
-    my $date2 = dt_from_string->add( days => $days + $after );
-    $date1= output_pref({ dt => $date1, dateformat => 'iso', dateonly => 1 });
-    $date2= output_pref({ dt => $date2, dateformat => 'iso', dateonly => 1 });
-
-    my $query = q|
-        SELECT borrowers.*, categories.description,
-        branches.branchname, branches.branchemail FROM borrowers
-        LEFT JOIN branches USING (branchcode)
-        LEFT JOIN categories USING (categorycode)
-    |;
-    if( $branch ) {
-        $query.= 'WHERE branchcode=? AND dateexpiry BETWEEN ? AND ?';
-    } else {
-        $query.= 'WHERE dateexpiry BETWEEN ? AND ?';
-    }
-
-    my $sth = $dbh->prepare( $query );
-    my @pars = $branch? ( $branch ): ();
-    push @pars, $date1, $date2;
-    $sth->execute( @pars );
-    my $results = $sth->fetchall_arrayref( {} );
-    return $results;
-}
-
-=head2 GetborCatFromCatType
-
-  ($codes_arrayref, $labels_hashref) = &GetborCatFromCatType();
-
-Looks up the different types of borrowers in the database. Returns two
-elements: a reference-to-array, which lists the borrower category
-codes, and a reference-to-hash, which maps the borrower category codes
-to category descriptions.
-
-=cut
-
-#'
-sub GetborCatFromCatType {
-    my ( $category_type, $action, $no_branch_limit ) = @_;
-
-    my $branch_limit = $no_branch_limit
-        ? 0
-        : C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
-
-    # FIXME - This API  seems both limited and dangerous.
-    my $dbh     = C4::Context->dbh;
-
-    my $request = qq{
-        SELECT categories.categorycode, categories.description
-        FROM categories
-    };
-    $request .= qq{
-        LEFT JOIN categories_branches ON categories.categorycode = categories_branches.categorycode
-    } if $branch_limit;
-    if($action) {
-        $request .= " $action ";
-        $request .= " AND (branchcode = ? OR branchcode IS NULL) GROUP BY description" if $branch_limit;
-    } else {
-        $request .= " WHERE branchcode = ? OR branchcode IS NULL GROUP BY description" if $branch_limit;
-    }
-    $request .= " ORDER BY categorycode";
-
-    my $sth = $dbh->prepare($request);
-    $sth->execute(
-        $action ? $category_type : (),
-        $branch_limit ? $branch_limit : ()
-    );
-
-    my %labels;
-    my @codes;
-
-    while ( my $data = $sth->fetchrow_hashref ) {
-        push @codes, $data->{'categorycode'};
-        $labels{ $data->{'categorycode'} } = $data->{'description'};
-    }
-    $sth->finish;
-    return ( \@codes, \%labels );
-}
-
-=head2 GetBorrowercategory
-
-  $hashref = &GetBorrowercategory($categorycode);
-
-Given the borrower's category code, the function returns the corresponding
-data hashref for a comprehensive information display.
-
-=cut
-
-sub GetBorrowercategory {
-    my ($catcode) = @_;
-    my $dbh       = C4::Context->dbh;
-    if ($catcode){
-        my $sth       =
-        $dbh->prepare(
-    "SELECT description,dateofbirthrequired,upperagelimit,category_type 
-    FROM categories 
-    WHERE categorycode = ?"
-        );
-        $sth->execute($catcode);
-        my $data =
-        $sth->fetchrow_hashref;
-        return $data;
-    } 
-    return;  
-}    # sub getborrowercategory
-
-
-=head2 GetBorrowerCategorycode
-
-    $categorycode = &GetBorrowerCategoryCode( $borrowernumber );
-
-Given the borrowernumber, the function returns the corresponding categorycode
-
-=cut
-
-sub GetBorrowerCategorycode {
-    my ( $borrowernumber ) = @_;
-    my $dbh = C4::Context->dbh;
-    my $sth = $dbh->prepare( qq{
-        SELECT categorycode
-        FROM borrowers
-        WHERE borrowernumber = ?
-    } );
-    $sth->execute( $borrowernumber );
-    return $sth->fetchrow;
-}
-
-=head2 GetBorrowercategoryList
-
-  $arrayref_hashref = &GetBorrowercategoryList;
-If no category code provided, the function returns all the categories.
-
-=cut
-
-sub GetBorrowercategoryList {
-    my $no_branch_limit = @_ ? shift : 0;
-    my $branch_limit = $no_branch_limit
-        ? 0
-        : C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
-    my $dbh       = C4::Context->dbh;
-    my $query = "SELECT categories.* FROM categories";
-    $query .= qq{
-        LEFT JOIN categories_branches ON categories.categorycode = categories_branches.categorycode
-        WHERE branchcode = ? OR branchcode IS NULL GROUP BY description
-    } if $branch_limit;
-    $query .= " ORDER BY description";
-    my $sth = $dbh->prepare( $query );
-    $sth->execute( $branch_limit ? $branch_limit : () );
-    my $data = $sth->fetchall_arrayref( {} );
-    $sth->finish;
-    return $data;
-}    # sub getborrowercategory
-
-=head2 GetAge
-
-  $dateofbirth,$date = &GetAge($date);
-
-this function return the borrowers age with the value of dateofbirth
-
-=cut
-
-#'
-sub GetAge{
-    my ( $date, $date_ref ) = @_;
-
-    if ( not defined $date_ref ) {
-        $date_ref = sprintf( '%04d-%02d-%02d', Today() );
-    }
-
-    my ( $year1, $month1, $day1 ) = split /-/, $date;
-    my ( $year2, $month2, $day2 ) = split /-/, $date_ref;
-
-    my $age = $year2 - $year1;
-    if ( $month1 . $day1 > $month2 . $day2 ) {
-        $age--;
-    }
-
-    return $age;
-}    # sub get_age
-
-=head2 SetAge
-
-  $borrower = C4::Members::SetAge($borrower, $datetimeduration);
-  $borrower = C4::Members::SetAge($borrower, '0015-12-10');
-  $borrower = C4::Members::SetAge($borrower, $datetimeduration, $datetime_reference);
-
-  eval { $borrower = C4::Members::SetAge($borrower, '015-1-10'); };
-  if ($@) {print $@;} #Catch a bad ISO Date or kill your script!
-
-This function sets the borrower's dateofbirth to match the given age.
-Optionally relative to the given $datetime_reference.
-
-@PARAM1 koha.borrowers-object
-@PARAM2 DateTime::Duration-object as the desired age
-        OR a ISO 8601 Date. (To make the API more pleasant)
-@PARAM3 DateTime-object as the relative date, defaults to now().
-RETURNS The given borrower reference @PARAM1.
-DIES    If there was an error with the ISO Date handling.
-
-=cut
-
-#'
-sub SetAge{
-    my ( $borrower, $datetimeduration, $datetime_ref ) = @_;
-    $datetime_ref = DateTime->now() unless $datetime_ref;
-
-    if ($datetimeduration && ref $datetimeduration ne 'DateTime::Duration') {
-        if ($datetimeduration =~ /^(\d{4})-(\d{2})-(\d{2})/) {
-            $datetimeduration = DateTime::Duration->new(years => $1, months => $2, days => $3);
-        }
-        else {
-            die "C4::Members::SetAge($borrower, $datetimeduration), datetimeduration not a valid ISO 8601 Date!\n";
-        }
-    }
-
-    my $new_datetime_ref = $datetime_ref->clone();
-    $new_datetime_ref->subtract_duration( $datetimeduration );
-
-    $borrower->{dateofbirth} = $new_datetime_ref->ymd();
-
-    return $borrower;
-}    # sub SetAge
-
-=head2 GetSortDetails (OUEST-PROVENCE)
-
-  ($lib) = &GetSortDetails($category,$sortvalue);
-
-Returns the authorized value  details
-C<&$lib>return value of authorized value details
-C<&$sortvalue>this is the value of authorized value 
-C<&$category>this is the value of authorized value category
-
-=cut
-
-sub GetSortDetails {
-    my ( $category, $sortvalue ) = @_;
-    my $dbh   = C4::Context->dbh;
-    my $query = qq|SELECT lib 
-        FROM authorised_values 
-        WHERE category=?
-        AND authorised_value=? |;
-    my $sth = $dbh->prepare($query);
-    $sth->execute( $category, $sortvalue );
-    my $lib = $sth->fetchrow;
-    return ($lib) if ($lib);
-    return ($sortvalue) unless ($lib);
-}
-
-=head2 MoveMemberToDeleted
-
-  $result = &MoveMemberToDeleted($borrowernumber);
-
-Copy the record from borrowers to deletedborrowers table.
-The routine returns 1 for success, undef for failure.
-
-=cut
-
-sub MoveMemberToDeleted {
-    my ($member) = shift or return;
-
-    my $schema       = Koha::Database->new()->schema();
-    my $borrowers_rs = $schema->resultset('Borrower');
-    $borrowers_rs->result_class('DBIx::Class::ResultClass::HashRefInflator');
-    my $borrower = $borrowers_rs->find($member);
-    return unless $borrower;
-
-    my $deleted = $schema->resultset('Deletedborrower')->create($borrower);
-
-    return $deleted ? 1 : undef;
-}
-
-=head2 DelMember
-
-    DelMember($borrowernumber);
-
-This function remove directly a borrower whitout writing it on deleteborrower.
-+ Deletes reserves for the borrower
-
-=cut
-
-sub DelMember {
-    my $dbh            = C4::Context->dbh;
-    my $borrowernumber = shift;
-    #warn "in delmember with $borrowernumber";
-    return unless $borrowernumber;    # borrowernumber is mandatory.
-
-    my $query = qq|DELETE 
-          FROM  reserves 
-          WHERE borrowernumber=?|;
-    my $sth = $dbh->prepare($query);
-    $sth->execute($borrowernumber);
-    $query = "
-       DELETE
-       FROM borrowers
-       WHERE borrowernumber = ?
-   ";
-    $sth = $dbh->prepare($query);
-    $sth->execute($borrowernumber);
-    logaction("MEMBERS", "DELETE", $borrowernumber, "") if C4::Context->preference("BorrowersLog");
-    return $sth->rows;
-}
-
-=head2 HandleDelBorrower
-
-     HandleDelBorrower($borrower);
-
-When a member is deleted (DelMember in Members.pm), you should call me first.
-This routine deletes/moves lists and entries for the deleted member/borrower.
-Lists owned by the borrower are deleted, but entries from the borrower to
-other lists are kept.
-
-=cut
-
-sub HandleDelBorrower {
-    my ($borrower)= @_;
-    my $query;
-    my $dbh = C4::Context->dbh;
-
-    #Delete all lists and all shares of this borrower
-    #Consistent with the approach Koha uses on deleting individual lists
-    #Note that entries in virtualshelfcontents added by this borrower to
-    #lists of others will be handled by a table constraint: the borrower
-    #is set to NULL in those entries.
-    $query="DELETE FROM virtualshelves WHERE owner=?";
-    $dbh->do($query,undef,($borrower));
-
-    #NOTE:
-    #We could handle the above deletes via a constraint too.
-    #But a new BZ report 11889 has been opened to discuss another approach.
-    #Instead of deleting we could also disown lists (based on a pref).
-    #In that way we could save shared and public lists.
-    #The current table constraints support that idea now.
-    #This pref should then govern the results of other routines/methods such as
-    #Koha::Virtualshelf->new->delete too.
-}
-
-=head2 ExtendMemberSubscriptionTo (OUEST-PROVENCE)
-
-    $date = ExtendMemberSubscriptionTo($borrowerid, $date);
-
-Extending the subscription to a given date or to the expiry date calculated on ISO date.
-Returns ISO date.
-
-=cut
-
-sub ExtendMemberSubscriptionTo {
-    my ( $borrowerid,$date) = @_;
-    my $dbh = C4::Context->dbh;
-    my $borrower = GetMember('borrowernumber'=>$borrowerid);
-    unless ($date){
-      $date = (C4::Context->preference('BorrowerRenewalPeriodBase') eq 'dateexpiry') ?
-                                        eval { output_pref( { dt => dt_from_string( $borrower->{'dateexpiry'}  ), dateonly => 1, dateformat => 'iso' } ); }
-                                        :
-                                        output_pref( { dt => dt_from_string, dateonly => 1, dateformat => 'iso' } );
-      $date = GetExpiryDate( $borrower->{'categorycode'}, $date );
-    }
-    my $sth = $dbh->do(<<EOF);
-UPDATE borrowers 
-SET  dateexpiry='$date' 
-WHERE borrowernumber='$borrowerid'
-EOF
-
-    AddEnrolmentFeeIfNeeded( $borrower->{categorycode}, $borrower->{borrowernumber} );
-
-    logaction("MEMBERS", "RENEW", $borrower->{'borrowernumber'}, "Membership renewed")if C4::Context->preference("BorrowersLog");
-    return $date if ($sth);
-    return 0;
-}
-
-=head2 GetTitles (OUEST-PROVENCE)
-
-  ($borrowertitle)= &GetTitles();
-
-Looks up the different title . Returns array  with all borrowers title
-
-=cut
-
-sub GetTitles {
-    my @borrowerTitle = split (/,|\|/,C4::Context->preference('BorrowersTitles'));
-    unshift( @borrowerTitle, "" );
-    my $count=@borrowerTitle;
-    if ($count == 1){
-        return ();
-    }
-    else {
-        return ( \@borrowerTitle);
-    }
-}
-
-=head2 GetHideLostItemsPreference
-
-  $hidelostitemspref = &GetHideLostItemsPreference($borrowernumber);
-
-Returns the HideLostItems preference for the patron category of the supplied borrowernumber
-C<&$hidelostitemspref>return value of function, 0 or 1
-
-=cut
-
-sub GetHideLostItemsPreference {
-    my ($borrowernumber) = @_;
-    my $dbh = C4::Context->dbh;
-    my $query = "SELECT hidelostitems FROM borrowers,categories WHERE borrowers.categorycode = categories.categorycode AND borrowernumber = ?";
-    my $sth = $dbh->prepare($query);
-    $sth->execute($borrowernumber);
-    my $hidelostitems = $sth->fetchrow;    
-    return $hidelostitems;    
-}
-
 =head2 GetBorrowersToExpunge
 
   $borrowers = &GetBorrowersToExpunge(
@@ -1802,6 +1044,7 @@ sub GetBorrowersToExpunge {
     my $params = shift;
     my $filterdate       = $params->{'not_borrowed_since'};
     my $filterexpiry     = $params->{'expired_before'};
+    my $filterlastseen   = $params->{'last_seen'};
     my $filtercategory   = $params->{'category_code'};
     my $filterbranch     = $params->{'branchcode'} ||
                         ((C4::Context->preference('IndependentBranches')
@@ -1842,6 +1085,10 @@ sub GetBorrowersToExpunge {
         $query .= " AND dateexpiry < ? ";
         push( @query_params, $filterexpiry );
     }
+    if ( $filterlastseen ) {
+        $query .= ' AND lastseen < ? ';
+        push @query_params, $filterlastseen;
+    }
     if ( $filtercategory ) {
         $query .= " AND categorycode = ? ";
         push( @query_params, $filtercategory );
@@ -1872,145 +1119,6 @@ sub GetBorrowersToExpunge {
     return \@results;
 }
 
-=head2 GetBorrowersWhoHaveNeverBorrowed
-
-  $results = &GetBorrowersWhoHaveNeverBorrowed
-
-This function get all borrowers who have never borrowed.
-
-I<$result> is a ref to an array which all elements are a hasref.
-
-=cut
-
-sub GetBorrowersWhoHaveNeverBorrowed {
-    my $filterbranch = shift || 
-                        ((C4::Context->preference('IndependentBranches')
-                             && C4::Context->userenv 
-                             && !C4::Context->IsSuperLibrarian()
-                             && C4::Context->userenv->{branch})
-                         ? C4::Context->userenv->{branch}
-                         : "");  
-    my $dbh   = C4::Context->dbh;
-    my $query = "
-        SELECT borrowers.borrowernumber,max(timestamp) as latestissue
-        FROM   borrowers
-          LEFT JOIN issues ON borrowers.borrowernumber = issues.borrowernumber
-        WHERE issues.borrowernumber IS NULL
-   ";
-    my @query_params;
-    if ($filterbranch && $filterbranch ne ""){ 
-        $query.=" AND borrowers.branchcode= ?";
-        push @query_params,$filterbranch;
-    }
-    warn $query if $debug;
-  
-    my $sth = $dbh->prepare($query);
-    if (scalar(@query_params)>0){  
-        $sth->execute(@query_params);
-    } 
-    else {
-        $sth->execute;
-    }      
-    
-    my @results;
-    while ( my $data = $sth->fetchrow_hashref ) {
-        push @results, $data;
-    }
-    return \@results;
-}
-
-=head2 GetBorrowersWithIssuesHistoryOlderThan
-
-  $results = &GetBorrowersWithIssuesHistoryOlderThan($date)
-
-this function get all borrowers who has an issue history older than I<$date> given on input arg.
-
-I<$result> is a ref to an array which all elements are a hashref.
-This hashref is containt the number of time this borrowers has borrowed before I<$date> and the borrowernumber.
-
-=cut
-
-sub GetBorrowersWithIssuesHistoryOlderThan {
-    my $dbh  = C4::Context->dbh;
-    my $date = shift ||POSIX::strftime("%Y-%m-%d",localtime());
-    my $filterbranch = shift || 
-                        ((C4::Context->preference('IndependentBranches')
-                             && C4::Context->userenv 
-                             && !C4::Context->IsSuperLibrarian()
-                             && C4::Context->userenv->{branch})
-                         ? C4::Context->userenv->{branch}
-                         : "");  
-    my $query = "
-       SELECT count(borrowernumber) as n,borrowernumber
-       FROM old_issues
-       WHERE returndate < ?
-         AND borrowernumber IS NOT NULL 
-    "; 
-    my @query_params;
-    push @query_params, $date;
-    if ($filterbranch){
-        $query.="   AND branchcode = ?";
-        push @query_params, $filterbranch;
-    }    
-    $query.=" GROUP BY borrowernumber ";
-    warn $query if $debug;
-    my $sth = $dbh->prepare($query);
-    $sth->execute(@query_params);
-    my @results;
-
-    while ( my $data = $sth->fetchrow_hashref ) {
-        push @results, $data;
-    }
-    return \@results;
-}
-
-=head2 GetBorrowersNamesAndLatestIssue
-
-  $results = &GetBorrowersNamesAndLatestIssueList(@borrowernumbers)
-
-this function get borrowers Names and surnames and Issue information.
-
-I<@borrowernumbers> is an array which all elements are borrowernumbers.
-This hashref is containt the number of time this borrowers has borrowed before I<$date> and the borrowernumber.
-
-=cut
-
-sub GetBorrowersNamesAndLatestIssue {
-    my $dbh  = C4::Context->dbh;
-    my @borrowernumbers=@_;  
-    my $query = "
-       SELECT surname,lastname, phone, email,max(timestamp)
-       FROM borrowers 
-         LEFT JOIN issues ON borrowers.borrowernumber=issues.borrowernumber
-       GROUP BY borrowernumber
-   ";
-    my $sth = $dbh->prepare($query);
-    $sth->execute;
-    my $results = $sth->fetchall_arrayref({});
-    return $results;
-}
-
-=head2 ModPrivacy
-
-  my $success = ModPrivacy( $borrowernumber, $privacy );
-
-Update the privacy of a patron.
-
-return :
-true on success, false on failure
-
-=cut
-
-sub ModPrivacy {
-    my $borrowernumber = shift;
-    my $privacy = shift;
-    return unless defined $borrowernumber;
-    return unless $borrowernumber =~ /^\d+$/;
-
-    return ModMember( borrowernumber => $borrowernumber,
-                      privacy        => $privacy );
-}
-
 =head2 IssueSlip
 
   IssueSlip($branchcode, $borrowernumber, $quickslip)
@@ -2160,6 +1268,20 @@ sub GetBorrowersWithEmail {
     return @result;
 }
 
+=head2 AddMember_Auto
+
+=cut
+
+sub AddMember_Auto {
+    my ( %borrower ) = @_;
+
+    $borrower{'cardnumber'} ||= fixup_cardnumber();
+
+    $borrower{'borrowernumber'} = AddMember(%borrower);
+
+    return ( %borrower );
+}
+
 =head2 AddMember_Opac
 
 =cut
@@ -2175,55 +1297,9 @@ sub AddMember_Opac {
         $borrower{'password'} = $password;
     }
 
-    $borrower{'cardnumber'} = fixup_cardnumber( $borrower{'cardnumber'} );
-
-    my $borrowernumber = AddMember(%borrower);
-
-    return ( $borrowernumber, $borrower{'password'} );
-}
-
-=head2 AddEnrolmentFeeIfNeeded
-
-    AddEnrolmentFeeIfNeeded( $borrower->{categorycode}, $borrower->{borrowernumber} );
-
-Add enrolment fee for a patron if needed.
-
-=cut
-
-sub AddEnrolmentFeeIfNeeded {
-    my ( $categorycode, $borrowernumber ) = @_;
-    # check for enrollment fee & add it if needed
-    my $dbh = C4::Context->dbh;
-    my $sth = $dbh->prepare(q{
-        SELECT enrolmentfee
-        FROM categories
-        WHERE categorycode=?
-    });
-    $sth->execute( $categorycode );
-    if ( $sth->err ) {
-        warn sprintf('Database returned the following error: %s', $sth->errstr);
-        return;
-    }
-    my ($enrolmentfee) = $sth->fetchrow;
-    if ($enrolmentfee && $enrolmentfee > 0) {
-        # insert fee in patron debts
-        C4::Accounts::manualinvoice( $borrowernumber, '', '', 'A', $enrolmentfee );
-    }
-}
-
-=head2 HasOverdues
-
-=cut
-
-sub HasOverdues {
-    my ( $borrowernumber ) = @_;
-
-    my $sql = "SELECT COUNT(*) FROM issues WHERE date_due < NOW() AND borrowernumber = ?";
-    my $sth = C4::Context->dbh->prepare( $sql );
-    $sth->execute( $borrowernumber );
-    my ( $count ) = $sth->fetchrow_array();
+    %borrower = AddMember_Auto(%borrower);
 
-    return $count;
+    return ( $borrower{'borrowernumber'}, $borrower{'password'} );
 }
 
 =head2 DeleteExpiredOpacRegistrations
@@ -2250,7 +1326,7 @@ WHERE categorycode = ? AND DATEDIFF( NOW(), dateenrolled ) > ? |;
     $sth->execute( $category_code, $delay );
     my $cnt=0;
     while ( my ($borrowernumber) = $sth->fetchrow_array() ) {
-        DelMember($borrowernumber);
+        Koha::Patrons->find($borrowernumber)->delete;
         $cnt++;
     }
     return $cnt;