View | Details | Raw Unified | Return to bug 9302
Collapse All | Expand All

(-)a/Koha/Patrons.pm (+75 lines)
Lines 31-36 use Koha::Patron; Link Here
31
31
32
use base qw(Koha::Objects);
32
use base qw(Koha::Objects);
33
33
34
our $RESULTSET_PATRON_ID_MAPPING = {
35
    Accountline          => 'borrowernumber',
36
    ArticleRequest       => 'borrowernumber',
37
    BorrowerAttribute    => 'borrowernumber',
38
    BorrowerDebarment    => 'borrowernumber',
39
    BorrowerFile         => 'borrowernumber',
40
    BorrowerModification => 'borrowernumber',
41
    ClubEnrollment       => 'borrowernumber',
42
    Issue                => 'borrowernumber',
43
    ItemsLastBorrower    => 'borrowernumber',
44
    Linktracker          => 'borrowernumber',
45
    Message              => 'borrowernumber',
46
    MessageQueue         => 'borrowernumber',
47
    OldIssue             => 'borrowernumber',
48
    OldReserve           => 'borrowernumber',
49
    Rating               => 'borrowernumber',
50
    Reserve              => 'borrowernumber',
51
    Review               => 'borrowernumber',
52
    Statistic            => 'borrowernumber',
53
    SearchHistory        => 'userid',
54
    Suggestion           => 'suggestedby',
55
    TagAll               => 'borrowernumber',
56
    Virtualshelfcontent  => 'borrowernumber',
57
    Virtualshelfshare    => 'borrowernumber',
58
    Virtualshelve        => 'owner',
59
};
60
34
=head1 NAME
61
=head1 NAME
35
62
36
Koha::Patron - Koha Patron Object class
63
Koha::Patron - Koha Patron Object class
Lines 207-212 sub anonymise_issue_history { Link Here
207
    return $nb_rows;
234
    return $nb_rows;
208
}
235
}
209
236
237
=head3 merge
238
239
    Koha::Patrons->search->merge( { keeper => $borrowernumber, patrons => \@borrowernumbers } );
240
241
    This subroutine merges a list of patrons into another patron record. This is accomplished by finding
242
    all related patron ids for the patrons to be merged in other tables and changing the ids to be that
243
    of the keeper patron.
244
245
=cut
246
247
sub merge {
248
    my ( $self, $params ) = @_;
249
250
    my $keeper          = $params->{keeper};
251
    my @borrowernumbers = @{ $params->{patrons} };
252
253
    my $patron_to_keep = Koha::Patrons->find( $keeper );
254
    return unless $patron_to_keep;
255
256
    # Ensure the keeper isn't in the list of patrons to merge
257
    @borrowernumbers = grep { $_ ne $keeper } @borrowernumbers;
258
259
    my $schema = Koha::Database->new()->schema();
260
261
    my $results;
262
263
    foreach my $borrowernumber (@borrowernumbers) {
264
        my $patron = Koha::Patrons->find( $borrowernumber );
265
266
        next unless $patron;
267
268
        # Unbless for safety, the patron will end up being deleted
269
        $results->{merged}->{$borrowernumber}->{patron} = $patron->unblessed;
270
271
        while (my ($r, $field) = each(%$RESULTSET_PATRON_ID_MAPPING)) {
272
            my $rs = $schema->resultset($r)->search({ $field => $borrowernumber} );
273
            $results->{merged}->{ $borrowernumber }->{updated}->{$r} = $rs->count();
274
            $rs->update( { $field => $keeper });
275
        }
276
277
        $patron->delete();
278
    }
279
280
    $results->{keeper} = $patron_to_keep;
281
282
    return $results;
283
}
284
210
=head3 type
285
=head3 type
211
286
212
=cut
287
=cut
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/members/member.tt (-2 / +29 lines)
Lines 52-60 Link Here
52
            <div id="searchheader">
52
            <div id="searchheader">
53
              <h3>Patrons found for: <span id="searchpattern">[% IF searchmember %] for '[% searchmember | html %]'[% END %]</span></h3>
53
              <h3>Patrons found for: <span id="searchpattern">[% IF searchmember %] for '[% searchmember | html %]'[% END %]</span></h3>
54
            </div>
54
            </div>
55
            [% IF CAN_user_tools_manage_patron_lists %]
55
            [% IF CAN_user_tools_manage_patron_lists || CAN_user_borrowers %]
56
              <div id="searchheader">
56
              <div id="searchheader">
57
                  <div>
57
                  <div>
58
                    [% IF CAN_user_tools_manage_patron_lists %]
58
                      <a href="#" id="select_all"><i class="fa fa-check"></i> Select all</a>
59
                      <a href="#" id="select_all"><i class="fa fa-check"></i> Select all</a>
59
                      |
60
                      |
60
                      <a href="#" id="clear_all"><i class="fa fa-remove"></i> Clear all</a>
61
                      <a href="#" id="clear_all"><i class="fa fa-remove"></i> Clear all</a>
Lines 78-85 Link Here
78
79
79
                          <input id="add_to_patron_list_submit" type="submit" class="submit" value="Save">
80
                          <input id="add_to_patron_list_submit" type="submit" class="submit" value="Save">
80
                      </span>
81
                      </span>
82
                    [% END %]
83
84
                    [% IF CAN_user_tools_manage_patron_lists && CAN_user_borrowers %]
85
                        |
86
                    [% END %]
87
88
                    [% IF CAN_user_borrowers %]
89
                          <button id="merge-patrons" type="submit">Merge selected patrons</button>
90
                    [% END %]
81
                  </div>
91
                  </div>
82
              </div>
92
                </div>
83
            [% END %]
93
            [% END %]
84
94
85
            <table id="memberresultst">
95
            <table id="memberresultst">
Lines 227-232 Link Here
227
    <script type="text/javascript" src="[% interface %]/[% theme %]/js/members-menu_[% KOHA_VERSION %].js"></script>
237
    <script type="text/javascript" src="[% interface %]/[% theme %]/js/members-menu_[% KOHA_VERSION %].js"></script>
228
    <script type="text/javascript">
238
    <script type="text/javascript">
229
        $(document).ready(function() {
239
        $(document).ready(function() {
240
            $('#merge-patrons').prop('disabled', true);
241
            $('#memberresultst').on('change', 'input.selection', function() {
242
                if ( $('.selection:checked').length > 1 ) {
243
                    $('#merge-patrons').prop('disabled', false);
244
                } else {
245
                    $('#merge-patrons').prop('disabled', true);
246
                }
247
            });
248
            $('#merge-patrons').on('click', function() {
249
                var merge_patrons_url = 'merge-patrons.pl?' + $('.selection:checked')
250
                    .map(function() {
251
                       return "id=" + $(this).val()
252
                    }).get().join('&');
253
254
                window.location.href = merge_patrons_url;
255
            });
256
230
            $('#add_to_patron_list_submit').prop('disabled', true);
257
            $('#add_to_patron_list_submit').prop('disabled', true);
231
            $('#new_patron_list').hide();
258
            $('#new_patron_list').hide();
232
259
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/members/merge-patrons.tt (+133 lines)
Line 0 Link Here
1
[% USE Branches %]
2
[% USE Categories %]
3
[% USE KohaDates %]
4
[% INCLUDE 'doc-head-open.inc' %]
5
<title>Koha &rsaquo; Patrons &rsaquo; Merge patron records</title>
6
[% INCLUDE 'doc-head-close.inc' %]
7
8
<script type="text/javascript">
9
//<![CDATA[
10
$(document).ready(function() {
11
    $('#merge-patrons').prop('disabled', true);
12
    $('#patron-merge-table').on('change', 'input', function() {
13
        if ( $('.keeper:checked').length > 0 ) {
14
            $('#merge-patrons').prop('disabled', false);
15
        } else {
16
            $('#merge-patrons').prop('disabled', true);
17
        }
18
    });
19
});
20
//]]>
21
</script>
22
23
</head>
24
<body id="pat_merge" class="pat">
25
[% INCLUDE 'header.inc' %]
26
[% INCLUDE 'patron-search.inc' %]
27
28
[% BLOCK display_names %]
29
    [% SWITCH rs %]
30
        [% CASE 'Accountline'           %]account lines
31
        [% CASE 'ArticleRequest'        %]article requests
32
        [% CASE 'BorrowerAttribute'     %]extended patron attributes
33
        [% CASE 'BorrowerDebarment'     %]patron restrictions
34
        [% CASE 'BorrowerFile'          %]patrons files
35
        [% CASE 'BorrowerModification'  %]patron modification requests
36
        [% CASE 'ClubEnrollment'        %]club enrollments
37
        [% CASE 'Issue'                 %]checkouts
38
        [% CASE 'ItemsLastBorrower'     %]marks as last borrower of item
39
        [% CASE 'Linktracker'           %]tracked link clicks
40
        [% CASE 'Message'               %]patron messages
41
        [% CASE 'MessageQueue'          %]patron notices
42
        [% CASE 'OldIssue'              %]previous checkouts
43
        [% CASE 'OldReserve'            %]filled holds
44
        [% CASE 'Rating'                %]ratings
45
        [% CASE 'Reserve'               %]current holds
46
        [% CASE 'Review'                %]reviews
47
        [% CASE 'Statistic'             %]statistics
48
        [% CASE 'SearchHistory'         %]historical searches
49
        [% CASE 'Suggestion'            %]purchase suggestions
50
        [% CASE 'TagAll'                %]tags
51
        [% CASE 'Virtualshelfcontent'   %]list items
52
        [% CASE 'Virtualshelfshare'     %]list shares
53
        [% CASE 'Virtualshelve'         %]lists
54
        [% CASE %][% rs %]
55
    [% END %]
56
[% END %]
57
58
<div id="breadcrumbs"><a href="/cgi-bin/koha/mainpage.pl">Home</a> &rsaquo; <a href="/cgi-bin/koha/members/members-home.pl">Patrons</a> &rsaquo; Merge patron records</div>
59
60
<div id="doc2" class="yui-t7">
61
   <div id="bd">
62
        <div id="yui-main">
63
            <h3>Merge patron records</h3>
64
65
            [% IF action == 'show' %]
66
                <p>Select patron to keep. Data from the other patrons will be transferred to this patron record and the remaining patron records will be deleted.</p>
67
                <form type="post" action="merge-patrons.pl">
68
                    <table id="patron-merge-table" class="datatable">
69
                        <thead>
70
                            <tr>
71
                                <th>&nbsp;</th>
72
                                <th>Card</th>
73
                                <th>Name</th>
74
                                <th>Date of birth</th>
75
                                <th>Category</th>
76
                                <th>Library</th>
77
                                <th>Expires on</th>
78
                            </tr>
79
                        </thead>
80
81
                        <tbody>
82
                            [% FOREACH p IN patrons %]
83
                                <tr>
84
                                    <td><input class='keeper' type='radio' name='keeper' value='[% p.id %]' /></td>
85
                                    <td>[% p.cardnumber | html %]</td>
86
                                    <td>[% p.firstname | html %] [% p.surname | html %]</td>
87
                                    <td>[% p.dateofbirth | $KohaDates %]</td>
88
                                    <td>[% Categories.GetName( p.categorycode ) %] ([% p.categorycode %])</td>
89
                                    <td>[% Branches.GetName( p.branchcode ) %]</td>
90
                                    <td>[% p.dateexpiry | $KohaDates %]</td>
91
                            [% END %]
92
                        </tbody>
93
                    </table>
94
95
                    [% FOREACH p IN patrons %]
96
                        <input type="hidden" name="id" value="[% p.id %]" />
97
                    [% END %]
98
99
                    <p/>
100
101
                    <input type="hidden" name="action" value="merge" />
102
                    <input id="merge-patrons" type="submit" value="Merge patrons" />
103
                </form>
104
            [% ELSIF action == 'merge' %]
105
                <h4>Results</h4>
106
107
                <p>
108
                    Patron records merged into <a href="moremember.pl?borrowernumber=[% results.keeper.id %]">[% results.keeper.firstname %] [% results.keeper.surname %] ([% results.keeper.cardnumber | html %])</a>
109
                </p>
110
111
                [% FOREACH pair IN results.merged.pairs %]
112
                    [% SET patron = pair.value.patron %]
113
114
                    <h5>[% patron.firstname %] [% patron.surname %] ([% patron.cardnumber %])</h5>
115
116
                    [% USE Dumper %]
117
                    [% FOREACH r IN pair.value.updated.pairs %]
118
                        [% SET name = r.key %]
119
                        [% SET count = r.value %]
120
                        [% IF count %]
121
                            <p>
122
                                [% count %] [% PROCESS display_names rs = name %] transferred.
123
                                [% IF name == 'Reserve' %]
124
                                    <strong>It is advisable to check for and resolve duplicate holds due to merging.</strong>
125
                                [% END %]
126
                            </p>
127
                        [% END %]
128
                    [% END %]
129
                [% END %]
130
            [% END %]
131
        </div>
132
    </div>
133
[% INCLUDE 'intranet-bottom.inc' %]
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/patron_lists/list.tt (-2 / +32 lines)
Lines 106-111 Link Here
106
                <div class="btn-group">
106
                <div class="btn-group">
107
                    <button class="btn btn-default btn-xs list-remove" type="submit"><i class="fa fa-trash"></i> Remove selected</button>
107
                    <button class="btn btn-default btn-xs list-remove" type="submit"><i class="fa fa-trash"></i> Remove selected</button>
108
                </div>
108
                </div>
109
                |
110
                <div class="btn-group">
111
                    <button class="btn btn-default btn-xs merge-patrons"><i class="fa fa-compress"></i> Merge selected patrons</button>
112
                </div>
109
            </div>
113
            </div>
110
114
111
            <table id="patron-list-table">
115
            <table id="patron-list-table">
Lines 126-132 Link Here
126
                <tbody>
130
                <tbody>
127
                    [% FOREACH p IN list.patron_list_patrons %]
131
                    [% FOREACH p IN list.patron_list_patrons %]
128
                        <tr>
132
                        <tr>
129
                            <td><input type="checkbox" name="patrons_to_remove" value="[% p.patron_list_patron_id %]" /></td>
133
                            <td>
134
                                <input type="checkbox" name="patrons_to_remove" class="selection" value="[% p.patron_list_patron_id %]" />
135
                                <input type="hidden" id="borrowernumber_[% p.patron_list_patron_id %]" value="[% p.borrowernumber.id %]" />
136
                            </td>
130
                            <td>
137
                            <td>
131
                                <a href="/cgi-bin/koha/members/moremember.pl?borrowernumber=[% p.borrowernumber.borrowernumber %]">
138
                                <a href="/cgi-bin/koha/members/moremember.pl?borrowernumber=[% p.borrowernumber.borrowernumber %]">
132
                                    [% p.borrowernumber.cardnumber %]
139
                                    [% p.borrowernumber.cardnumber %]
Lines 151-157 Link Here
151
            </table>
158
            </table>
152
159
153
            <input type="hidden" name="patron_list_id" value="[% list.patron_list_id %]" />
160
            <input type="hidden" name="patron_list_id" value="[% list.patron_list_id %]" />
154
            <button type="submit" class="btn btn-default btn-sm"><i class="fa fa-trash" aria-hidden="true"></i> Remove selected patrons</button>
161
            <button type="submit" class="btn btn-default btn-sm list-remove"><i class="fa fa-trash" aria-hidden="true"></i> Remove selected patrons</button>
162
            <button class="btn btn-default btn-sm merge-patrons" type="submit"><i class="fa fa-compress"></i> Merge selected patrons</button>
155
        </form>
163
        </form>
156
164
157
            </div>
165
            </div>
Lines 227-232 Link Here
227
                $("#add_patrons_by_barcode, #patron_search_line").show();
235
                $("#add_patrons_by_barcode, #patron_search_line").show();
228
                $("#add_patrons_by_search, #patron_barcodes_line, #patron_barcodes_submit").hide();
236
                $("#add_patrons_by_search, #patron_barcodes_line, #patron_barcodes_submit").hide();
229
            });
237
            });
238
239
            $('.merge-patrons').on('click', function() {
240
                var checkedItems = $("input:checked");
241
                if ($(checkedItems).length < 2) {
242
                    alert(_("You must select one or more patrons to remove"));
243
                    return false;
244
                }
245
                $(checkedItems).parents('tr').addClass("warn");
246
                if (confirm(_("Are you sure you want to remove the selected patrons?"))) {
247
                    var merge_patrons_url = '/cgi-bin/koha/members/merge-patrons.pl?' +
248
                        $('.selection:checked')
249
                        .map(function() {
250
                            return "id=" + $( '#borrowernumber_' + $(this).val() ).val()
251
                        }).get().join('&');
252
253
                    window.location.href = merge_patrons_url;
254
                    return false;
255
                } else {
256
                    $(checkedItems).parents('tr').removeClass("warn");
257
                    return false;
258
                }
259
            });
230
        });
260
        });
231
    </script>
261
    </script>
232
[% END %]
262
[% END %]
(-)a/members/merge-patrons.pl (+58 lines)
Line 0 Link Here
1
#!/usr/bin/perl
2
3
# Copyright ByWater Solutions 2017
4
# This file is part of Koha.
5
#
6
# Koha is free software; you can redistribute it and/or modify it
7
# under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 3 of the License, or
9
# (at your option) any later version.
10
#
11
# Koha is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
# GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with Koha; if not, see <http://www.gnu.org/licenses>.
18
19
use Modern::Perl;
20
21
use CGI qw ( -utf8 );
22
23
use C4::Auth;
24
use C4::Output;
25
use C4::Context;
26
use Koha::Patrons;
27
28
my $cgi = new CGI;
29
30
my ( $template, $loggedinuser, $cookie, $flags ) = get_template_and_user(
31
    {
32
        template_name   => "members/merge-patrons.tt",
33
        query           => $cgi,
34
        type            => "intranet",
35
        authnotrequired => 0,
36
        flagsrequired   => { borrowers => 1 },
37
        debug           => 1,
38
    }
39
);
40
41
my $action = $cgi->param('action') || 'show';
42
my @ids = $cgi->multi_param('id');
43
44
if ( $action eq 'show' ) {
45
    my $patrons =
46
      Koha::Patrons->search( { borrowernumber => { -in => \@ids } } );
47
    $template->param( patrons => $patrons );
48
} elsif ( $action eq 'merge' ) {
49
    my $keeper = $cgi->param('keeper');
50
    my $results = Koha::Patrons->merge( { keeper => $keeper, patrons => \@ids } );
51
    $template->param( results => $results );
52
}
53
54
$template->param( action => $action );
55
56
output_html_with_http_headers $cgi, $cookie, $template->output;
57
58
1;
(-)a/t/db_dependent/Patrons.t (-2 / +47 lines)
Lines 17-23 Link Here
17
17
18
use Modern::Perl;
18
use Modern::Perl;
19
19
20
use Test::More tests => 17;
20
use Test::More tests => 18;
21
use Test::Warn;
21
use Test::Warn;
22
22
23
use C4::Context;
23
use C4::Context;
Lines 104-108 foreach my $b ( $patrons->as_list() ) { Link Here
104
    is( $b->categorycode(), $categorycode, "Iteration returns a patron object" );
104
    is( $b->categorycode(), $categorycode, "Iteration returns a patron object" );
105
}
105
}
106
106
107
subtest 'Test Koha::Patrons::merge' => sub {
108
    plan tests => 98;
109
110
    my $schema = Koha::Database->new()->schema();
111
112
    my $resultsets = $Koha::Patrons::RESULTSET_PATRON_ID_MAPPING;
113
114
    my $keeper  = $builder->build( { source => 'Borrower' } )->{borrowernumber};
115
    my $loser_1 = $builder->build( { source => 'Borrower' } )->{borrowernumber};
116
    my $loser_2 = $builder->build( { source => 'Borrower' } )->{borrowernumber};
117
118
    while (my ($r, $field) = each(%$resultsets)) {
119
        $builder->build( { source => $r, value => { $field => $keeper } } );
120
        $builder->build( { source => $r, value => { $field => $loser_1 } } );
121
        $builder->build( { source => $r, value => { $field => $loser_2 } } );
122
123
        my $keeper_rs =
124
          $schema->resultset($r)->search( { $field => $keeper } );
125
        is( $keeper_rs->count(), 1, "Found 1 $r rows for keeper" );
126
127
        my $loser_1_rs =
128
          $schema->resultset($r)->search( { $field => $loser_1 } );
129
        is( $loser_1_rs->count(), 1, "Found 1 $r rows for loser_1" );
130
131
        my $loser_2_rs =
132
          $schema->resultset($r)->search( { $field => $loser_2 } );
133
        is( $loser_2_rs->count(), 1, "Found 1 $r rows for loser_2" );
134
    }
135
136
    my $results = Koha::Patrons->merge(
137
        {
138
            keeper  => $keeper,
139
            patrons => [ $loser_1, $loser_2 ],
140
        }
141
    );
142
143
    while (my ($r, $field) = each(%$resultsets)) {
144
        my $keeper_rs =
145
          $schema->resultset($r)->search( {$field => $keeper } );
146
        is( $keeper_rs->count(), 3, "Found 2 $r rows for keeper" );
147
    }
148
149
    is( Koha::Patrons->find($loser_1), undef, 'Loser 1 has been deleted' );
150
    is( Koha::Patrons->find($loser_2), undef, 'Loser 2 has been deleted' );
151
};
152
107
$schema->storage->txn_rollback();
153
$schema->storage->txn_rollback();
108
154
109
- 

Return to bug 9302