Bug 9302: Add ability to merge patron records
[koha.git] / Koha / Patrons.pm
index 1af2c06..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.
 #
@@ -22,11 +23,41 @@ use Modern::Perl;
 use Carp;
 
 use Koha::Database;
+use Koha::DateUtils;
 
+use Koha::ArticleRequests;
+use Koha::ArticleRequest::Status;
 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
@@ -37,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.
@@ -67,6 +119,169 @@ sub search_housebound_deliverers {
     return Koha::Patrons->_new_from_dbic($del);
 }
 
+=head3 search_upcoming_membership_expires
+
+my $patrons = Koha::Patrons->search_upcoming_membership_expires();
+
+The 'before' and 'after' represent the number of days before/after the date
+that is set by the preference MembershipExpiryDaysNotice.
+If the pref is 14, before 2 and after 3 then you will get all expires
+from 12 to 17 days.
+
+=cut
+
+sub search_upcoming_membership_expires {
+    my ( $self, $params ) = @_;
+    my $before = $params->{before} || 0;
+    my $after  = $params->{after} || 0;
+    delete $params->{before};
+    delete $params->{after};
+
+    my $days = C4::Context->preference("MembershipExpiryDaysNotice") || 0;
+    my $date_before = dt_from_string->add( days => $days - $before );
+    my $date_after = dt_from_string->add( days => $days + $after );
+    my $dtf = Koha::Database->new->schema->storage->datetime_parser;
+
+    $params->{dateexpiry} = {
+        ">=" => $dtf->format_date( $date_before ),
+        "<=" => $dtf->format_date( $date_after ),
+    };
+    return $self->SUPER::search(
+        $params, { join => ['branchcode', 'categorycode'] }
+    );
+}
+
+=head3 guarantor
+
+Returns a Koha::Patron object for this borrower's guarantor
+
+=cut
+
+sub guarantor {
+    my ( $self ) = @_;
+
+    return Koha::Patrons->find( $self->guarantorid() );
+}
+
+=head3 search_patrons_to_anonymise
+
+    my $patrons = Koha::Patrons->search_patrons_to_anonymise( { before => $older_than_date, [ library => $library ] } );
+
+This method returns all patrons who has an issue history older than a given date.
+
+=cut
+
+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;
+
+    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 anonymise_issue_history
+
+    Koha::Patrons->search->anonymise_issue_history( { [ before => $older_than_date ] } );
+
+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 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;
+
+    # 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(
+        {
+            (
+                $older_than_date
+                ? ( returndate =>
+                      { '<' => $dtf->format_datetime($older_than_date) } )
+                : ()
+            )
+        }
+        );
+        my $anonymous_patron = C4::Context->preference('AnonymousPatron') || undef;
+        $nb_rows += $old_issues_to_anonymise->update( { 'old_issues.borrowernumber' => $anonymous_patron } );
+    }
+    return $nb_rows;
+}
+
+=head3 merge
+
+    Koha::Patrons->search->merge( { keeper => $borrowernumber, patrons => \@borrowernumbers } );
+
+    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 merge {
+    my ( $self, $params ) = @_;
+
+    my $keeper          = $params->{keeper};
+    my @borrowernumbers = @{ $params->{patrons} };
+
+    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 });
+        }
+
+        $patron->delete();
+    }
+
+    $results->{keeper} = $patron_to_keep;
+
+    return $results;
+}
+
 =head3 type
 
 =cut