Bug 9302: Add ability to merge patron records
[koha.git] / Koha / Patrons.pm
index d079df0..6d51bf0 100644 (file)
@@ -1,6 +1,7 @@
 package Koha::Patrons;
 
-# Copyright ByWater Solutions 2014
+# Copyright 2014 ByWater Solutions
+# Copyright 2016 Koha Development Team
 #
 # This file is part of Koha.
 #
@@ -30,6 +31,33 @@ use Koha::Patron;
 
 use base qw(Koha::Objects);
 
+our $RESULTSET_PATRON_ID_MAPPING = {
+    Accountline          => 'borrowernumber',
+    ArticleRequest       => 'borrowernumber',
+    BorrowerAttribute    => 'borrowernumber',
+    BorrowerDebarment    => 'borrowernumber',
+    BorrowerFile         => 'borrowernumber',
+    BorrowerModification => 'borrowernumber',
+    ClubEnrollment       => 'borrowernumber',
+    Issue                => 'borrowernumber',
+    ItemsLastBorrower    => 'borrowernumber',
+    Linktracker          => 'borrowernumber',
+    Message              => 'borrowernumber',
+    MessageQueue         => 'borrowernumber',
+    OldIssue             => 'borrowernumber',
+    OldReserve           => 'borrowernumber',
+    Rating               => 'borrowernumber',
+    Reserve              => 'borrowernumber',
+    Review               => 'borrowernumber',
+    Statistic            => 'borrowernumber',
+    SearchHistory        => 'userid',
+    Suggestion           => 'suggestedby',
+    TagAll               => 'borrowernumber',
+    Virtualshelfcontent  => 'borrowernumber',
+    Virtualshelfshare    => 'borrowernumber',
+    Virtualshelve        => 'owner',
+};
+
 =head1 NAME
 
 Koha::Patron - Koha Patron Object class
@@ -40,6 +68,27 @@ Koha::Patron - Koha Patron Object class
 
 =cut
 
+=head3 search_limited
+
+my $patrons = Koha::Patrons->search_limit( $params, $attributes );
+
+Returns all the patrons the logged in user is allowed to see
+
+=cut
+
+sub search_limited {
+    my ( $self, $params, $attributes ) = @_;
+
+    my $userenv = C4::Context->userenv;
+    my @restricted_branchcodes;
+    if ( $userenv and $userenv->{number} ) {
+        my $logged_in_user = Koha::Patrons->find( $userenv->{number} );
+        @restricted_branchcodes = $logged_in_user->libraries_where_can_see_patrons;
+    }
+    $params->{'me.branchcode'} = { -in => \@restricted_branchcodes } if @restricted_branchcodes;
+    return $self->search( $params, $attributes );
+}
+
 =head3 search_housebound_choosers
 
 Returns all Patrons which are Housebound choosers.
@@ -70,7 +119,7 @@ sub search_housebound_deliverers {
     return Koha::Patrons->_new_from_dbic($del);
 }
 
-=head3
+=head3 search_upcoming_membership_expires
 
 my $patrons = Koha::Patrons->search_upcoming_membership_expires();
 
@@ -114,71 +163,123 @@ sub guarantor {
     return Koha::Patrons->find( $self->guarantorid() );
 }
 
-=head3 article_requests
+=head3 search_patrons_to_anonymise
 
-my @requests = $borrower->article_requests();
-my $requests = $borrower->article_requests();
+    my $patrons = Koha::Patrons->search_patrons_to_anonymise( { before => $older_than_date, [ library => $library ] } );
 
-Returns either a list of ArticleRequests objects,
-or an ArtitleRequests object, depending on the
-calling context.
+This method returns all patrons who has an issue history older than a given date.
 
 =cut
 
-sub article_requests {
-    my ( $self ) = @_;
+sub search_patrons_to_anonymise {
+    my ( $class, $params ) = @_;
+    my $older_than_date = $params->{before};
+    my $library         = $params->{library};
+    $older_than_date = $older_than_date ? dt_from_string($older_than_date) : dt_from_string;
+    $library ||=
+      ( C4::Context->preference('IndependentBranches') && C4::Context->userenv && !C4::Context->IsSuperLibrarian() && C4::Context->userenv->{branch} )
+      ? C4::Context->userenv->{branch}
+      : undef;
 
-    $self->{_article_requests} ||= Koha::ArticleRequests->search({ borrowernumber => $self->borrowernumber() });
-
-    return $self->{_article_requests};
+    my $dtf = Koha::Database->new->schema->storage->datetime_parser;
+    my $rs = $class->_resultset->search(
+        {   returndate                  => { '<'   =>  $dtf->format_datetime($older_than_date), },
+            'old_issues.borrowernumber' => { 'not' => undef },
+            privacy                     => { '<>'  => 0 },                  # Keep forever
+            ( $library ? ( 'old_issues.branchcode' => $library ) : () ),
+        },
+        {   join     => ["old_issues"],
+            distinct => 1,
+        }
+    );
+    return Koha::Patrons->_new_from_dbic($rs);
 }
 
-=head3 article_requests_current
+=head3 anonymise_issue_history
 
-my @requests = $patron->article_requests_current
+    Koha::Patrons->search->anonymise_issue_history( { [ before => $older_than_date ] } );
 
-Returns the article requests associated with this patron that are incomplete
+Anonymise issue history (old_issues) for all patrons older than the given date (optional).
+To make sure all the conditions are met, the caller has the responsibility to
+call search_patrons_to_anonymise to filter the Koha::Patrons set
 
 =cut
 
-sub article_requests_current {
-    my ( $self ) = @_;
+sub anonymise_issue_history {
+    my ( $self, $params ) = @_;
+
+    my $older_than_date = $params->{before};
+
+    $older_than_date = dt_from_string $older_than_date if $older_than_date;
 
-    $self->{_article_requests_current} ||= Koha::ArticleRequests->search(
+    # The default of 0 does not work due to foreign key constraints
+    # The anonymisation should not fail quietly if AnonymousPatron is not a valid entry
+    # Set it to undef (NULL)
+    my $dtf = Koha::Database->new->schema->storage->datetime_parser;
+    my $nb_rows = 0;
+    while ( my $patron = $self->next ) {
+        my $old_issues_to_anonymise = $patron->old_checkouts->search(
         {
-            borrowernumber => $self->id(),
-            -or          => [
-                { status => Koha::ArticleRequest::Status::Pending },
-                { status => Koha::ArticleRequest::Status::Processing }
-            ]
+            (
+                $older_than_date
+                ? ( returndate =>
+                      { '<' => $dtf->format_datetime($older_than_date) } )
+                : ()
+            )
         }
-    );
-
-    return $self->{_article_requests_current};
+        );
+        my $anonymous_patron = C4::Context->preference('AnonymousPatron') || undef;
+        $nb_rows += $old_issues_to_anonymise->update( { 'old_issues.borrowernumber' => $anonymous_patron } );
+    }
+    return $nb_rows;
 }
 
-=head3 article_requests_finished
+=head3 merge
 
-my @requests = $biblio->article_requests_finished
+    Koha::Patrons->search->merge( { keeper => $borrowernumber, patrons => \@borrowernumbers } );
 
-Returns the article requests associated with this patron that are completed
+    This subroutine merges a list of patrons into another patron record. This is accomplished by finding
+    all related patron ids for the patrons to be merged in other tables and changing the ids to be that
+    of the keeper patron.
 
 =cut
 
-sub article_requests_finished {
-    my ( $self, $borrower ) = @_;
+sub merge {
+    my ( $self, $params ) = @_;
+
+    my $keeper          = $params->{keeper};
+    my @borrowernumbers = @{ $params->{patrons} };
 
-    $self->{_article_requests_finished} ||= Koha::ArticleRequests->search(
-        {
-            borrowernumber => $self->id(),
-            -or          => [
-                { status => Koha::ArticleRequest::Status::Completed },
-                { status => Koha::ArticleRequest::Status::Canceled }
-            ]
+    my $patron_to_keep = Koha::Patrons->find( $keeper );
+    return unless $patron_to_keep;
+
+    # Ensure the keeper isn't in the list of patrons to merge
+    @borrowernumbers = grep { $_ ne $keeper } @borrowernumbers;
+
+    my $schema = Koha::Database->new()->schema();
+
+    my $results;
+
+    foreach my $borrowernumber (@borrowernumbers) {
+        my $patron = Koha::Patrons->find( $borrowernumber );
+
+        next unless $patron;
+
+        # Unbless for safety, the patron will end up being deleted
+        $results->{merged}->{$borrowernumber}->{patron} = $patron->unblessed;
+
+        while (my ($r, $field) = each(%$RESULTSET_PATRON_ID_MAPPING)) {
+            my $rs = $schema->resultset($r)->search({ $field => $borrowernumber} );
+            $results->{merged}->{ $borrowernumber }->{updated}->{$r} = $rs->count();
+            $rs->update( { $field => $keeper });
         }
-    );
 
-    return $self->{_article_requests_finished};
+        $patron->delete();
+    }
+
+    $results->{keeper} = $patron_to_keep;
+
+    return $results;
 }
 
 =head3 type