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

(-)a/C4/ImportBatch.pm (-33 / +25 lines)
Lines 690-696 sub BatchCommitRecords { Link Here
690
        } elsif ($record_result eq 'ignore') {
690
        } elsif ($record_result eq 'ignore') {
691
            $recordid = $record_match;
691
            $recordid = $record_match;
692
            $num_ignored++;
692
            $num_ignored++;
693
            $recordid = $record_match;
694
            if ($record_type eq 'biblio' and defined $recordid and ( $item_result eq 'create_new' || $item_result eq 'replace' ) ) {
693
            if ($record_type eq 'biblio' and defined $recordid and ( $item_result eq 'create_new' || $item_result eq 'replace' ) ) {
695
                my ($bib_items_added, $bib_items_replaced, $bib_items_errored) = BatchCommitItems($rowref->{'import_record_id'}, $recordid, $item_result);
694
                my ($bib_items_added, $bib_items_replaced, $bib_items_errored) = BatchCommitItems($rowref->{'import_record_id'}, $recordid, $item_result);
696
                $num_items_added += $bib_items_added;
695
                $num_items_added += $bib_items_added;
Lines 1179-1184 sub GetBestRecordMatch { Link Here
1179
                             WHERE  import_record_matches.import_record_id = ? AND
1178
                             WHERE  import_record_matches.import_record_id = ? AND
1180
                             (  (import_records.record_type = 'biblio' AND biblio.biblionumber IS NOT NULL) OR
1179
                             (  (import_records.record_type = 'biblio' AND biblio.biblionumber IS NOT NULL) OR
1181
                                (import_records.record_type = 'auth' AND auth_header.authid IS NOT NULL) )
1180
                                (import_records.record_type = 'auth' AND auth_header.authid IS NOT NULL) )
1181
                             AND chosen = 1
1182
                             ORDER BY score DESC, candidate_match_id DESC");
1182
                             ORDER BY score DESC, candidate_match_id DESC");
1183
    $sth->execute($import_record_id);
1183
    $sth->execute($import_record_id);
1184
    my ($record_id) = $sth->fetchrow_array();
1184
    my ($record_id) = $sth->fetchrow_array();
Lines 1459-1470 sub GetImportRecordMatches { Link Here
1459
    my $dbh = C4::Context->dbh;
1459
    my $dbh = C4::Context->dbh;
1460
    # FIXME currently biblio only
1460
    # FIXME currently biblio only
1461
    my $sth = $dbh->prepare_cached("SELECT title, author, biblionumber,
1461
    my $sth = $dbh->prepare_cached("SELECT title, author, biblionumber,
1462
                                    candidate_match_id, score, record_type
1462
                                    candidate_match_id, score, record_type,
1463
                                    chosen
1463
                                    FROM import_records
1464
                                    FROM import_records
1464
                                    JOIN import_record_matches USING (import_record_id)
1465
                                    JOIN import_record_matches USING (import_record_id)
1465
                                    LEFT JOIN biblio ON (biblionumber = candidate_match_id)
1466
                                    LEFT JOIN biblio ON (biblionumber = candidate_match_id)
1466
                                    WHERE import_record_id = ?
1467
                                    WHERE import_record_id = ?
1467
                                    ORDER BY score DESC, biblionumber DESC");
1468
                                    ORDER BY score DESC, chosen DESC, biblionumber DESC");
1468
    $sth->bind_param(1, $import_record_id);
1469
    $sth->bind_param(1, $import_record_id);
1469
    my $results = [];
1470
    my $results = [];
1470
    $sth->execute();
1471
    $sth->execute();
Lines 1497-1506 sub SetImportRecordMatches { Link Here
1497
    $delsth->execute($import_record_id);
1498
    $delsth->execute($import_record_id);
1498
    $delsth->finish();
1499
    $delsth->finish();
1499
1500
1500
    my $sth = $dbh->prepare("INSERT INTO import_record_matches (import_record_id, candidate_match_id, score)
1501
    my $sth = $dbh->prepare("INSERT INTO import_record_matches (import_record_id, candidate_match_id, score, chosen)
1501
                                    VALUES (?, ?, ?)");
1502
                                    VALUES (?, ?, ?, ?)");
1503
    my $chosen = 1; #The first match is defaulted to be chosen
1502
    foreach my $match (@matches) {
1504
    foreach my $match (@matches) {
1503
        $sth->execute($import_record_id, $match->{'record_id'}, $match->{'score'});
1505
        $sth->execute($import_record_id, $match->{'record_id'}, $match->{'score'}, $chosen);
1506
        $chosen = 0; #After the first we do not default to other matches
1504
    }
1507
    }
1505
}
1508
}
1506
1509
Lines 1717-1757 sub _get_commit_action { Link Here
1717
    if ($record_type eq 'biblio') {
1720
    if ($record_type eq 'biblio') {
1718
        my ($bib_result, $bib_match, $item_result);
1721
        my ($bib_result, $bib_match, $item_result);
1719
1722
1720
        if ($overlay_status ne 'no_match') {
1723
        $bib_match = GetBestRecordMatch($import_record_id);
1721
            $bib_match = GetBestRecordMatch($import_record_id);
1724
        if ($overlay_status ne 'no_match' && defined($bib_match)) {
1722
            if ($overlay_action eq 'replace') {
1725
1723
                $bib_result  = defined($bib_match) ? 'replace' : 'create_new';
1726
            $bib_result = $overlay_action;
1724
            } elsif ($overlay_action eq 'create_new') {
1727
1725
                $bib_result  = 'create_new';
1728
            if($item_action eq 'always_add' or $item_action eq 'add_only_for_matches'){
1726
            } elsif ($overlay_action eq 'ignore') {
1727
                $bib_result  = 'ignore';
1728
            }
1729
         if($item_action eq 'always_add' or $item_action eq 'add_only_for_matches'){
1730
                $item_result = 'create_new';
1729
                $item_result = 'create_new';
1731
       }
1730
            } elsif($item_action eq 'replace'){
1732
      elsif($item_action eq 'replace'){
1731
                $item_result = 'replace';
1733
          $item_result = 'replace';
1732
            } else {
1734
          }
1733
                $item_result = 'ignore';
1735
      else {
1734
            }
1736
             $item_result = 'ignore';
1735
1737
           }
1738
        } else {
1736
        } else {
1739
            $bib_result = $nomatch_action;
1737
            $bib_result = $nomatch_action;
1740
            $item_result = ($item_action eq 'always_add' or $item_action eq 'add_only_for_new')     ? 'create_new' : 'ignore';
1738
            $item_result = ($item_action eq 'always_add' or $item_action eq 'add_only_for_new') ? 'create_new' : 'ignore';
1741
        }
1739
        }
1742
        return ($bib_result, $item_result, $bib_match);
1740
        return ($bib_result, $item_result, $bib_match);
1743
    } else { # must be auths
1741
    } else { # must be auths
1744
        my ($auth_result, $auth_match);
1742
        my ($auth_result, $auth_match);
1745
1743
1746
        if ($overlay_status ne 'no_match') {
1744
        $auth_match = GetBestRecordMatch($import_record_id);
1747
            $auth_match = GetBestRecordMatch($import_record_id);
1745
        if ($overlay_status ne 'no_match' && defined($auth_match)) {
1748
            if ($overlay_action eq 'replace') {
1746
            $auth_result = $overlay_action;
1749
                $auth_result  = defined($auth_match) ? 'replace' : 'create_new';
1750
            } elsif ($overlay_action eq 'create_new') {
1751
                $auth_result  = 'create_new';
1752
            } elsif ($overlay_action eq 'ignore') {
1753
                $auth_result  = 'ignore';
1754
            }
1755
        } else {
1747
        } else {
1756
            $auth_result = $nomatch_action;
1748
            $auth_result = $nomatch_action;
1757
        }
1749
        }
(-)a/Koha/REST/V1/ImportRecordMatches.pm (+106 lines)
Line 0 Link Here
1
package Koha::REST::V1::ImportRecordMatches;
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it
6
# under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# Koha is distributed in the hope that it will be useful, but
11
# WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18
use Modern::Perl;
19
20
use Mojo::Base 'Mojolicious::Controller';
21
22
use Koha::Import::Record::Matches;
23
24
use Try::Tiny;
25
26
=head1 API
27
28
=head2 Methods
29
30
=cut
31
32
=head3 unset_chosen
33
34
Method that handles unselecting all chosen matches for an import record
35
36
DELETE /api/v1/import/{import_batch_id}/records/{import_record_id}/matches/chosen
37
38
=cut
39
40
sub unset_chosen {
41
    my $c = shift->openapi->valid_input or return;
42
43
    my $import_record_id = $c->validation->param('import_record_id');
44
    my $matches = Koha::Import::Record::Matches->search({
45
        import_record_id => $import_record_id,
46
    });
47
    unless ($matches) {
48
        return $c->render(
49
            status  => 404,
50
            openapi => { error => "No matches not found" }
51
        );
52
    }
53
    return try {
54
        $matches->unset_chosen;
55
        return $c->render( status => 204, openapi => $matches );
56
    }
57
    catch {
58
        $c->unhandled_exception($_);
59
    };
60
}
61
62
=head3 set_chosen
63
64
Method that handles modifying if a Koha::Import::Record::Match object has been chosen for overlay
65
66
PUT /api/v1/import/{import_batch_id}/records/{import_record_id}/matches/chosen
67
68
Body should contain the condidate_match_id to chose
69
70
=cut
71
72
sub set_chosen {
73
    my $c = shift->openapi->valid_input or return;
74
75
    my $import_record_id = $c->validation->param('import_record_id');
76
    my $body    = $c->validation->param('body');
77
    my $candidate_match_id = $body->{'candidate_match_id'};
78
79
    my $match = Koha::Import::Record::Matches->find({
80
        import_record_id => $import_record_id,
81
        candidate_match_id => $candidate_match_id
82
    });
83
84
    unless ($match) {
85
        return $c->render(
86
            status  => 404,
87
            openapi => { error => "Match not found" }
88
        );
89
    }
90
91
    return try {
92
        my $matches = Koha::Import::Record::Matches->search({
93
            import_record_id => $import_record_id,
94
            chosen => 1
95
        });
96
        $matches->unset_chosen if $matches;
97
        $match->set_from_api({ chosen => JSON::true });
98
        $match->store;
99
        return $c->render( status => 200, openapi => $match );
100
    }
101
    catch {
102
        $c->unhandled_exception($_);
103
    };
104
}
105
106
1;
(-)a/api/v1/swagger/definitions.json (+3 lines)
Lines 47-52 Link Here
47
  "import_batch_profiles": {
47
  "import_batch_profiles": {
48
    "$ref": "definitions/import_batch_profiles.json"
48
    "$ref": "definitions/import_batch_profiles.json"
49
  },
49
  },
50
  "import_record_match": {
51
    "$ref": "definitions/import_record_match.json"
52
  },
50
  "library": {
53
  "library": {
51
    "$ref": "definitions/library.json"
54
    "$ref": "definitions/library.json"
52
  },
55
  },
(-)a/api/v1/swagger/definitions/import_record_match.json (+21 lines)
Line 0 Link Here
1
{
2
  "type": "object",
3
  "properties": {
4
    "import_record_id": {
5
      "type": "integer",
6
      "description": "Internal import record identifier"
7
    },
8
    "candidate_match_id": {
9
      "type": "integer",
10
      "description": "Internal import record match candidate identifier"
11
    },
12
    "chosen": {
13
      "type": "boolean",
14
      "description": "Whether match has been chosen for overlay"
15
    },
16
    "score": {
17
      "type": "integer",
18
      "description": "Ranking value for this match calculated by the matching rules"
19
    }
20
  }
21
}
(-)a/api/v1/swagger/parameters.json (+3 lines)
Lines 23-28 Link Here
23
  "hold_id_pp": {
23
  "hold_id_pp": {
24
    "$ref": "parameters/hold.json#/hold_id_pp"
24
    "$ref": "parameters/hold.json#/hold_id_pp"
25
  },
25
  },
26
  "import_record_id_pp": {
27
    "$ref": "parameters/import_record_match.json#/import_record_id_pp"
28
  },
26
  "club_id_pp": {
29
  "club_id_pp": {
27
    "$ref": "parameters/club.json#/club_id_pp"
30
    "$ref": "parameters/club.json#/club_id_pp"
28
  },
31
  },
(-)a/api/v1/swagger/parameters/import_record_match.json (+16 lines)
Line 0 Link Here
1
{
2
  "import_record_id_pp": {
3
    "name": "import_record_id",
4
    "in": "path",
5
    "description": "Internal import_record identifier",
6
    "required": true,
7
    "type": "integer"
8
  },
9
  "candidate_match_id_pp": {
10
    "name": "candidate_match_id",
11
    "in": "path",
12
    "description": "Internal import_record_match_candidate identifier",
13
    "required": true,
14
    "type": "integer"
15
  }
16
}
(-)a/api/v1/swagger/paths.json (+3 lines)
Lines 77-82 Link Here
77
  "/holds/{hold_id}/pickup_location": {
77
  "/holds/{hold_id}/pickup_location": {
78
    "$ref": "paths/holds.json#/~1holds~1{hold_id}~1pickup_location"
78
    "$ref": "paths/holds.json#/~1holds~1{hold_id}~1pickup_location"
79
  },
79
  },
80
  "/import/{import_batch_id}/records/{import_record_id}/matches/chosen": {
81
    "$ref": "paths/import_record_matches.json#/~1import~1{import_batch_id}~1records~1{import_record_id}~1matches~1chosen"
82
  },
80
  "/items": {
83
  "/items": {
81
    "$ref": "paths/items.json#/~1items"
84
    "$ref": "paths/items.json#/~1items"
82
  },
85
  },
(-)a/api/v1/swagger/paths/import_record_matches.json (+144 lines)
Line 0 Link Here
1
{
2
  "/import/{import_batch_id}/records/{import_record_id}/matches/chosen": {
3
    "put": {
4
      "x-mojo-to": "ImportRecordMatches#set_chosen",
5
      "operationId": "setChosen",
6
      "tags": ["import_record_matches"],
7
      "parameters": [{
8
          "name": "import_batch_id",
9
          "in": "path",
10
          "required": true,
11
          "description": "An import_batch ID",
12
          "type": "integer"
13
      }, {
14
          "name": "import_record_id",
15
          "in": "path",
16
          "required": true,
17
          "description": "An import_record ID",
18
          "type": "integer"
19
      }, {
20
          "name": "body",
21
          "in": "body",
22
          "description": "A JSON object containing fields to modify",
23
          "required": true,
24
          "schema": {
25
            "type": "object",
26
            "properties": {
27
              "candidate_match_id": {
28
                "description": "Candudate match to choose",
29
                "type": "integer"
30
              }
31
            }
32
          }
33
        }
34
      ],
35
      "consumes": ["application/json"],
36
      "produces": ["application/json"],
37
      "responses": {
38
        "200": {
39
          "description": "Match updated"
40
        },
41
        "400": {
42
          "description": "Missing or wrong parameters",
43
          "schema": {
44
            "$ref": "../definitions.json#/error"
45
          }
46
        },
47
        "401": {
48
          "description": "Authentication required",
49
          "schema": {
50
            "$ref": "../definitions.json#/error"
51
          }
52
        },
53
        "403": {
54
          "description": "Match management not allowed",
55
          "schema": {
56
            "$ref": "../definitions.json#/error"
57
          }
58
        },
59
        "404": {
60
          "description": "Import record match not found",
61
          "schema": {
62
            "$ref": "../definitions.json#/error"
63
          }
64
        },
65
        "500": {
66
          "description": "Internal server error",
67
          "schema": {
68
            "$ref": "../definitions.json#/error"
69
          }
70
        },
71
        "503": {
72
          "description": "Under maintenance",
73
          "schema": {
74
            "$ref": "../definitions.json#/error"
75
          }
76
        }
77
      },
78
      "x-koha-authorization": {
79
        "permissions": {
80
          "tools": "manage_staged_marc"
81
        }
82
      }
83
    },
84
    "delete": {
85
      "x-mojo-to": "ImportRecordMatches#unset_chosen",
86
      "operationId": "unsetChosen",
87
      "tags": ["import_record_matches"],
88
      "parameters": [{
89
          "name": "import_batch_id",
90
          "in": "path",
91
          "required": true,
92
          "description": "An import_batch ID",
93
          "type": "integer"
94
      }, {
95
          "name": "import_record_id",
96
          "in": "path",
97
          "required": true,
98
          "description": "An import_record ID",
99
          "type": "integer"
100
      }],
101
      "produces": ["application/json"],
102
      "responses": {
103
        "204": {
104
          "description": "Matches unchosen"
105
        },
106
        "401": {
107
          "description": "Authentication required",
108
          "schema": {
109
            "$ref": "../definitions.json#/error"
110
          }
111
        },
112
        "403": {
113
          "description": "Match management not allowed",
114
          "schema": {
115
            "$ref": "../definitions.json#/error"
116
          }
117
        },
118
        "404": {
119
          "description": "Import record matches not found",
120
          "schema": {
121
            "$ref": "../definitions.json#/error"
122
          }
123
        },
124
        "500": {
125
          "description": "Internal server error",
126
          "schema": {
127
            "$ref": "../definitions.json#/error"
128
          }
129
        },
130
        "503": {
131
          "description": "Under maintenance",
132
          "schema": {
133
            "$ref": "../definitions.json#/error"
134
          }
135
        }
136
      },
137
      "x-koha-authorization": {
138
        "permissions": {
139
          "tools": "manage_staged_marc"
140
        }
141
      }
142
    }
143
  }
144
}
(-)a/api/v1/swagger/x-primitives.json (+8 lines)
Lines 3-8 Link Here
3
    "type": "integer",
3
    "type": "integer",
4
    "description": "Internal biblio identifier"
4
    "description": "Internal biblio identifier"
5
  },
5
  },
6
  "import_record_id": {
7
    "type": "integer",
8
    "description": "Internal import record identifier"
9
  },
10
  "candidate_match_id": {
11
    "type": "integer",
12
    "description": "Internal import record match identifier"
13
  },
6
  "advancededitormacro_id": {
14
  "advancededitormacro_id": {
7
    "type": "integer",
15
    "type": "integer",
8
    "description": "Internal advanced editor macro identifier",
16
    "description": "Internal advanced editor macro identifier",
(-)a/installer/data/mysql/atomicupdate/bug_22785.perl (+8 lines)
Line 0 Link Here
1
$DBversion = 'XXX';
2
if( CheckVersion( $DBversion ) ) {
3
4
    unless( column_exists('import_record_matches','chosen') ){
5
        $dbh->do( "ALTER TABLE import_record_matches ADD COLUMN chosen TINYINT null DEFAULT null AFTER score" );
6
    }
7
    NewVersion( $DBversion, 22785, "Add chosen column to miport_record_matches");
8
}
(-)a/installer/data/mysql/kohastructure.sql (+1 lines)
Lines 2902-2907 CREATE TABLE `import_record_matches` ( Link Here
2902
  `import_record_id` int(11) NOT NULL COMMENT 'the id given to the imported bib record (import_records.import_record_id)',
2902
  `import_record_id` int(11) NOT NULL COMMENT 'the id given to the imported bib record (import_records.import_record_id)',
2903
  `candidate_match_id` int(11) NOT NULL COMMENT 'the biblio the imported record matches (biblio.biblionumber)',
2903
  `candidate_match_id` int(11) NOT NULL COMMENT 'the biblio the imported record matches (biblio.biblionumber)',
2904
  `score` int(11) NOT NULL DEFAULT 0 COMMENT 'the match score',
2904
  `score` int(11) NOT NULL DEFAULT 0 COMMENT 'the match score',
2905
  `chosen` tinyint(1) NULL DEFAULT NULL COMMENT 'whether this match has been allowed or denied',
2905
  PRIMARY KEY (`import_record_id`,`candidate_match_id`),
2906
  PRIMARY KEY (`import_record_id`,`candidate_match_id`),
2906
  KEY `record_score` (`import_record_id`,`score`),
2907
  KEY `record_score` (`import_record_id`,`score`),
2907
  CONSTRAINT `import_record_matches_ibfk_1` FOREIGN KEY (`import_record_id`) REFERENCES `import_records` (`import_record_id`) ON DELETE CASCADE ON UPDATE CASCADE
2908
  CONSTRAINT `import_record_matches_ibfk_1` FOREIGN KEY (`import_record_id`) REFERENCES `import_records` (`import_record_id`) ON DELETE CASCADE ON UPDATE CASCADE
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/manage-marc-import.tt (-18 / +56 lines)
Lines 13-18 Link Here
13
<style>
13
<style>
14
    #jobpanel,#jobstatus,#jobfailed { display : none; }
14
    #jobpanel,#jobstatus,#jobfailed { display : none; }
15
    span.change-status { font-style:italic; color:#666; display:none; }
15
    span.change-status { font-style:italic; color:#666; display:none; }
16
    ul.matches li { list-style-type:none; }
16
</style>
17
</style>
17
</head>
18
</head>
18
19
Lines 483-490 Link Here
483
                        { "mDataProp": "citation" },
484
                        { "mDataProp": "citation" },
484
                        { "mDataProp": "status" },
485
                        { "mDataProp": "status" },
485
                        { "mDataProp": "overlay_status" },
486
                        { "mDataProp": "overlay_status" },
486
                        { "mDataProp": "match_citation" },
487
                        { "mDataProp": "" },
487
                        { "mDataProp": "diff_url" },
488
                        { "mDataProp": "" },
488
                        { "mDataProp": "matched" }
489
                        { "mDataProp": "matched" }
489
                    ],
490
                    ],
490
                    "fnServerData": function(sSource, aoData, fnCallback) {
491
                    "fnServerData": function(sSource, aoData, fnCallback) {
Lines 540-561 Link Here
540
                            aData['overlay_status']
541
                            aData['overlay_status']
541
                        );
542
                        );
542
543
543
                        if (aData['match_id']) {
544
                        if ( aData['matches'].length > 0 ) {
544
                            [% IF(record_type == 'auth') -%]
545
545
                                var matching_msg = _("Matches authority %s (score=%s):%s");
546
                            var any_checked = 0;
546
                            [%- ELSE -%]
547
                            $('td:eq(4)', nRow).html('<ul class="matches"></ul>');
547
                                var matching_msg = _("Matches bibliographic record %s (score=%s):%s");
548
                            $('td:eq(5)', nRow).html('<ul class="matches"></ul>');
548
                            [%- END %]
549
                            var checked = "";
549
                            $('td:eq(4)', nRow).html(
550
                            var disabled = "";
550
                                matching_msg.format(aData['match_id'], aData['score'],
551
                            if( aData['status'] == "imported" || aData['status'] == "ignored" ){
551
                                    '<a target="_blank" href="' + record_details_url
552
                                disabled = ' disabled ';
552
                                    + aData['match_id'] + '">' + aData['match_citation'] + '</a>')
553
                            }
553
                            );
554
                            aData['matches'].forEach(function(item,index){
554
                        }
555
                                if( item.chosen == 1 ){
555
                        if (aData['diff_url']) {
556
                                    checked = 'checked="checked"';
556
                            $('td:eq(5)', nRow).html(
557
                                    any_checked = 1;
557
                                '<a href="' + aData['diff_url'] + '">' + _("View") + '</a>'
558
                                }
558
                            );
559
                                [% IF(item.record_type == 'auth') -%]
560
                                    var matching_msg = _("Matches authority %s (score=%s):%s");
561
                                [%- ELSE -%]
562
                                    var matching_msg = _("Matches bibliographic record %s (score=%s):%s");
563
                                [%- END %]
564
                                    var match_option = "";
565
                                    match_option = '<input type="radio" data-import_record_id="'+aData['import_record_id']+'" class="chosen" name="import_record_id_'+aData['import_record_id']+'_match" value="'+item.candidate_match_id+'" ' + checked + disabled + '> ';
566
567
                                    var diff_url = '/cgi-bin/koha/tools/showdiffmarc.pl?batchid=%s&importid=%s&id=%s&type=%s';
568
                                    var match_citation = '';
569
                                    if( item.title  ){ match_citation += item.title + ' ' }
570
                                    if( item.author ){ match_citation += item.author }
571
                                $('td:eq(4) ul', nRow).append('<li><label for="import_record_id_'+aData['import_record_id']+'_match_'+index+'">'+match_option+
572
                                    matching_msg.format(item.candidate_match_id, item.score,
573
                                        '<a target="_blank" href="' + record_details_url
574
                                        + item.candidate_match_id + '">' + match_citation + '</a></label></li>')
575
                                );
576
                                $('td:eq(5) ul', nRow).append('<li><a href="'
577
                                        + diff_url.format( [% import_batch_id | html %], aData['import_record_id'], item.candidate_match_id, item.record_type) + '">' + _("View") + '</a></li>');
578
                            });
579
                            checked = "";
580
                            if( !any_checked ){ checked = 'checked="checked"'; }
581
                            $('td:eq(4) ul', nRow).prepend('<li><label><input type="radio" data-import_record_id="'+aData['import_record_id']+'" class="chosen" name="import_record_id_'+aData['import_record_id']+'_match" value="none" ' + checked + disabled + ' > Ignore matches</label></li>');
582
                            $('td:eq(5) ul', nRow).prepend('<li>&nbsp;</li>');
559
                        }
583
                        }
560
                        $('td:eq(6)', nRow).html(
584
                        $('td:eq(6)', nRow).html(
561
                            '<a target="_blank" href="' + record_details_url
585
                            '<a target="_blank" href="' + record_details_url
Lines 571-576 Link Here
571
                });
595
                });
572
            [% END %]
596
            [% END %]
573
597
598
            $("body").on("change", ".chosen", function(e) {
599
                let apimethod = "DELETE";
600
                let apidata ="";
601
                if( $(this).val() != 'none' ){
602
                    apimethod = 'PUT';
603
                    apidata = JSON.stringify({ candidate_match_id: $(this).val() });
604
                }
605
                $.ajax({
606
                    url: '/api/v1/import/[% import_batch_id | html %]/records/'+$(this).data('import_record_id')+'/matches/chosen',
607
                    method: apimethod,
608
                    data: apidata
609
                }).fail(function(){ alert("Unable to update match choices"); return false; });
610
            });
611
574
            $("body").on("click", ".previewMARC", function(e) {
612
            $("body").on("click", ".previewMARC", function(e) {
575
                e.preventDefault();
613
                e.preventDefault();
576
                var ltitle = $(this).text();
614
                var ltitle = $(this).text();
(-)a/reports/acquisitions_stats.pl (+1 lines)
Lines 496-501 sub calculate { Link Here
496
      if ( @$filters[10] );
496
      if ( @$filters[10] );
497
497
498
    $strcalc .= " GROUP BY $linefield, $colfield ORDER BY $linefield,$colfield";
498
    $strcalc .= " GROUP BY $linefield, $colfield ORDER BY $linefield,$colfield";
499
    warn $strcalc;
499
    my $dbcalc = $dbh->prepare($strcalc);
500
    my $dbcalc = $dbh->prepare($strcalc);
500
    $dbcalc->execute;
501
    $dbcalc->execute;
501
502
(-)a/t/db_dependent/ImportBatch.t (+29 lines)
Lines 213-216 subtest "RecordsFromMarcPlugin" => sub { Link Here
213
        'Checked one field in second record' );
213
        'Checked one field in second record' );
214
};
214
};
215
215
216
subtest "_get_commit_action" => sub {
217
    plan tests => 24;
218
    my $mock_import = Test::MockModule->new("C4::ImportBatch");
219
220
    $mock_import->mock( GetBestRecordMatch => sub { return 5; } );
221
    foreach my $record_type ( ('biblio','authority') ){
222
        foreach my $match_action ( ('replace','create_new','ignore') ){
223
            foreach my $no_match_action ( ('create_new','ignore') ){
224
                my ($result, $match, $item_result) =
225
                    C4::ImportBatch::_get_commit_action($match_action, $no_match_action, 'always_add', 'auto_match', 42, $record_type);
226
                is( $result, $match_action, "When match found amd chosen we return the match_action for $record_type records with match action $match_action and no match action $no_match_action");
227
            }
228
        }
229
    }
230
231
    $mock_import->mock( GetBestRecordMatch => sub { my $matches = undef; return $matches; } );
232
    foreach my $record_type ( ('biblio','authority') ){
233
        foreach my $match_action ( ('replace','create_new','ignore') ){
234
            foreach my $no_match_action ( ('create_new','ignore') ){
235
                my ($result, $match, $item_result) =
236
                    C4::ImportBatch::_get_commit_action($match_action, $no_match_action, 'always_add', 'auto_match', 42, $record_type);
237
                is( $result, $no_match_action, "When no match found or chosen we return the match_action for $record_type records with match action $match_action and no match action $no_match_action");
238
            }
239
        }
240
    }
241
242
};
243
244
216
$schema->storage->txn_rollback;
245
$schema->storage->txn_rollback;
(-)a/t/db_dependent/api/v1/import_record_matches.t (+167 lines)
Line 0 Link Here
1
#!/usr/bin/env perl
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it
6
# under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# Koha is distributed in the hope that it will be useful, but
11
# WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18
use Modern::Perl;
19
20
use Test::More tests => 1;
21
use Test::Mojo;
22
use Test::Warn;
23
24
use t::lib::TestBuilder;
25
use t::lib::Mocks;
26
27
use C4::Auth;
28
use Koha::Import::Record::Matches;
29
30
my $schema  = Koha::Database->new->schema;
31
my $builder = t::lib::TestBuilder->new;
32
33
# FIXME: sessionStorage defaults to mysql, but it seems to break transaction handling
34
# this affects the other REST api tests
35
t::lib::Mocks::mock_preference( 'SessionStorage', 'tmp' );
36
37
my $remote_address = '127.0.0.1';
38
my $t              = Test::Mojo->new('Koha::REST::V1');
39
40
subtest 'import record matches tests' => sub {
41
42
    plan tests => 13;
43
44
    $schema->storage->txn_begin;
45
46
    my ( $unauthorized_borrowernumber, $unauthorized_session_id ) =
47
      create_user_and_session( { authorized => 0 } );
48
    my ( $authorized_borrowernumber, $authorized_session_id ) =
49
      create_user_and_session( { authorized => 1 } );
50
51
    my $match_1 = $builder->build_object({
52
        class => 'Koha::Import::Record::Matches',
53
        value => {
54
            chosen => 0,
55
        }
56
    });
57
    my $match_2 = $builder->build_object({
58
        class => 'Koha::Import::Record::Matches',
59
        value => {
60
            import_record_id => $match_1->import_record_id,
61
            chosen => 1,
62
        }
63
    });
64
    my $del_match = $builder->build_object({ class => 'Koha::Import::Record::Matches' });
65
    my $del_import_batch_id = $del_match->import_record->import_batch_id;
66
    my $del_match_id = $del_match->import_record_id;
67
68
    # Unauthorized attempt to update
69
    my $tx = $t->ua->build_tx(
70
      PUT => "/api/v1/import/".$match_1->import_record->import_batch_id."/records/".$match_1->import_record_id."/matches/chosen"=>
71
      json => {
72
          candidate_match_id => $match_1->candidate_match_id
73
      }
74
    );
75
    $tx->req->cookies(
76
        { name => 'CGISESSID', value => $unauthorized_session_id } );
77
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
78
    $t->request_ok($tx)->status_is(403);
79
80
    # Invalid attempt to allow match on a non-existent record
81
    $tx = $t->ua->build_tx(
82
      PUT => "/api/v1/import/".$del_import_batch_id."/records/".$del_match_id."/matches/chosen" =>
83
      json => {
84
          candidate_match_id => $match_1->candidate_match_id
85
      }
86
    );
87
88
    $tx->req->cookies(
89
        { name => 'CGISESSID', value => $authorized_session_id } );
90
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
91
    $del_match->delete();
92
    $t->request_ok($tx)->status_is(404)
93
      ->json_is( '/error' => "Match not found" );
94
95
    # Valid, authorised update
96
    $tx = $t->ua->build_tx(
97
      PUT => "/api/v1/import/".$match_1->import_record->import_batch_id."/records/".$match_1->import_record_id."/matches/chosen" =>
98
      json => {
99
          candidate_match_id => $match_1->candidate_match_id
100
      }
101
    );
102
    $tx->req->cookies(
103
        { name => 'CGISESSID', value => $authorized_session_id } );
104
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
105
    $t->request_ok($tx)->status_is(200);
106
107
    $match_1->discard_changes;
108
    $match_2->discard_changes;
109
    ok( $match_1->chosen,"Match 1 is correctly set to chosen");
110
    ok( !$match_2->chosen,"Match 2 correctly unset when match 1 is set");
111
112
    # Valid unsetting
113
    $tx = $t->ua->build_tx(
114
      DELETE => "/api/v1/import/".$match_1->import_record->import_batch_id."/records/".$match_1->import_record_id."/matches/chosen" =>
115
      json => {
116
      }
117
    );
118
    $tx->req->cookies(
119
        { name => 'CGISESSID', value => $authorized_session_id } );
120
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
121
    $t->request_ok($tx)->status_is(204);
122
123
    $match_1->discard_changes;
124
    $match_2->discard_changes;
125
    ok( !$match_1->chosen,"Match 1 is correctly unset to chosen");
126
    ok( !$match_2->chosen,"Match 2 is correctly unset to chosen");
127
128
    $schema->storage->txn_rollback;
129
};
130
131
sub create_user_and_session {
132
133
    my $args  = shift;
134
    my $dbh   = C4::Context->dbh;
135
136
    my $user = $builder->build(
137
        {
138
            source => 'Borrower',
139
            value  => {
140
                flags => 0
141
            }
142
        }
143
    );
144
145
    # Create a session for the authorized user
146
    my $session = C4::Auth::get_session('');
147
    $session->param( 'number',   $user->{borrowernumber} );
148
    $session->param( 'id',       $user->{userid} );
149
    $session->param( 'ip',       '127.0.0.1' );
150
    $session->param( 'lasttime', time() );
151
    $session->flush;
152
153
    if ( $args->{authorized} ) {
154
        $builder->build({
155
            source => 'UserPermission',
156
            value  => {
157
                borrowernumber => $user->{borrowernumber},
158
                module_bit     => 13,
159
                code           => 'manage_staged_marc',
160
            }
161
        });
162
    }
163
164
    return ( $user->{borrowernumber}, $session->id );
165
}
166
167
1;
(-)a/tools/batch_records_ajax.pl (-18 / +18 lines)
Lines 69-89 my @list = (); Link Here
69
foreach my $record (@$records) {
69
foreach my $record (@$records) {
70
    my $citation = $record->{'title'} || $record->{'authorized_heading'};
70
    my $citation = $record->{'title'} || $record->{'authorized_heading'};
71
71
72
    my $match = GetImportRecordMatches( $record->{'import_record_id'}, 1 );
72
    my $matches = GetImportRecordMatches( $record->{'import_record_id'} );
73
    my $match_citation = '';
74
    my $match_id;
73
    my $match_id;
75
    if ( $#$match > -1 ) {
74
    if ( scalar @$matches > 0 ) {
76
        if ( $match->[0]->{'record_type'} eq 'biblio' ) {
75
        foreach my $match (@$matches){
77
            $match_citation .= $match->[0]->{'title'}
76
        my $match_citation = '';
78
              if defined( $match->[0]->{'title'} );
77
            if ( $match->{'record_type'} eq 'biblio' ) {
79
            $match_citation .= ' ' . $match->[0]->{'author'}
78
                $match_citation .= $match->{'title'}
80
              if defined( $match->[0]->{'author'} );
79
                  if defined( $match->{'title'} );
81
            $match_id = $match->[0]->{'biblionumber'};
80
                $match_citation .= ' ' . $match->{'author'}
82
        }
81
                  if defined( $match->{'author'} );
83
        elsif ( $match->[0]->{'record_type'} eq 'auth' ) {
82
                $match->{'match_citation'} = $match_citation;
84
            if ( defined( $match->[0]->{'authorized_heading'} ) ) {
83
            }
85
                $match_citation .= $match->[0]->{'authorized_heading'};
84
            elsif ( $match->{'record_type'} eq 'auth' ) {
86
                $match_id = $match->[0]->{'candidate_match_id'};
85
                if ( defined( $match->{'authorized_heading'} ) ) {
86
                    $match_citation .= $match->{'authorized_heading'};
87
                    $match->{'match_citation'} = $match_citation;
88
                }
87
            }
89
            }
88
        }
90
        }
89
    }
91
    }
Lines 98-109 foreach my $record (@$records) { Link Here
98
        isbn            => $record->{'isbn'},
100
        isbn            => $record->{'isbn'},
99
        status          => $record->{'status'},
101
        status          => $record->{'status'},
100
        overlay_status  => $record->{'overlay_status'},
102
        overlay_status  => $record->{'overlay_status'},
101
        match_citation  => $match_citation,
102
        matched         => $record->{'matched_biblionumber'}
103
        matched         => $record->{'matched_biblionumber'}
103
          || $record->{'matched_authid'}
104
          || $record->{'matched_authid'}
104
          || q{},
105
          || q{},
105
        score => $#$match > -1 ? $match->[0]->{'score'} : 0,
106
        score => scalar @$matches > 0 ? $matches->[0]->{'score'} : 0,
106
        match_id => $match_id,
107
        matches => $matches,
107
        diff_url => $match_id ? "/cgi-bin/koha/tools/showdiffmarc.pl?batchid=$import_batch_id&importid=$record->{import_record_id}&id=$match_id&type=$record->{record_type}" : undef
108
        diff_url => $match_id ? "/cgi-bin/koha/tools/showdiffmarc.pl?batchid=$import_batch_id&importid=$record->{import_record_id}&id=$match_id&type=$record->{record_type}" : undef
108
      };
109
      };
109
}
110
}
110
- 

Return to bug 22785