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

(-)a/C4/ImportBatch.pm (-33 / +25 lines)
Lines 711-717 sub BatchCommitRecords { Link Here
711
        } elsif ($record_result eq 'ignore') {
711
        } elsif ($record_result eq 'ignore') {
712
            $recordid = $record_match;
712
            $recordid = $record_match;
713
            $num_ignored++;
713
            $num_ignored++;
714
            $recordid = $record_match;
715
            if ($record_type eq 'biblio' and defined $recordid and ( $item_result eq 'create_new' || $item_result eq 'replace' ) ) {
714
            if ($record_type eq 'biblio' and defined $recordid and ( $item_result eq 'create_new' || $item_result eq 'replace' ) ) {
716
                my ($bib_items_added, $bib_items_replaced, $bib_items_errored) = BatchCommitItems($rowref->{'import_record_id'}, $recordid, $item_result);
715
                my ($bib_items_added, $bib_items_replaced, $bib_items_errored) = BatchCommitItems($rowref->{'import_record_id'}, $recordid, $item_result);
717
                $num_items_added += $bib_items_added;
716
                $num_items_added += $bib_items_added;
Lines 1199-1204 sub GetBestRecordMatch { Link Here
1199
                             WHERE  import_record_matches.import_record_id = ? AND
1198
                             WHERE  import_record_matches.import_record_id = ? AND
1200
                             (  (import_records.record_type = 'biblio' AND biblio.biblionumber IS NOT NULL) OR
1199
                             (  (import_records.record_type = 'biblio' AND biblio.biblionumber IS NOT NULL) OR
1201
                                (import_records.record_type = 'auth' AND auth_header.authid IS NOT NULL) )
1200
                                (import_records.record_type = 'auth' AND auth_header.authid IS NOT NULL) )
1201
                             AND chosen = 1
1202
                             ORDER BY score DESC, candidate_match_id DESC");
1202
                             ORDER BY score DESC, candidate_match_id DESC");
1203
    $sth->execute($import_record_id);
1203
    $sth->execute($import_record_id);
1204
    my ($record_id) = $sth->fetchrow_array();
1204
    my ($record_id) = $sth->fetchrow_array();
Lines 1479-1490 sub GetImportRecordMatches { Link Here
1479
    my $dbh = C4::Context->dbh;
1479
    my $dbh = C4::Context->dbh;
1480
    # FIXME currently biblio only
1480
    # FIXME currently biblio only
1481
    my $sth = $dbh->prepare_cached("SELECT title, author, biblionumber,
1481
    my $sth = $dbh->prepare_cached("SELECT title, author, biblionumber,
1482
                                    candidate_match_id, score, record_type
1482
                                    candidate_match_id, score, record_type,
1483
                                    chosen
1483
                                    FROM import_records
1484
                                    FROM import_records
1484
                                    JOIN import_record_matches USING (import_record_id)
1485
                                    JOIN import_record_matches USING (import_record_id)
1485
                                    LEFT JOIN biblio ON (biblionumber = candidate_match_id)
1486
                                    LEFT JOIN biblio ON (biblionumber = candidate_match_id)
1486
                                    WHERE import_record_id = ?
1487
                                    WHERE import_record_id = ?
1487
                                    ORDER BY score DESC, biblionumber DESC");
1488
                                    ORDER BY score DESC, chosen DESC, biblionumber DESC");
1488
    $sth->bind_param(1, $import_record_id);
1489
    $sth->bind_param(1, $import_record_id);
1489
    my $results = [];
1490
    my $results = [];
1490
    $sth->execute();
1491
    $sth->execute();
Lines 1517-1526 sub SetImportRecordMatches { Link Here
1517
    $delsth->execute($import_record_id);
1518
    $delsth->execute($import_record_id);
1518
    $delsth->finish();
1519
    $delsth->finish();
1519
1520
1520
    my $sth = $dbh->prepare("INSERT INTO import_record_matches (import_record_id, candidate_match_id, score)
1521
    my $sth = $dbh->prepare("INSERT INTO import_record_matches (import_record_id, candidate_match_id, score, chosen)
1521
                                    VALUES (?, ?, ?)");
1522
                                    VALUES (?, ?, ?, ?)");
1523
    my $chosen = 1; #The first match is defaulted to be chosen
1522
    foreach my $match (@matches) {
1524
    foreach my $match (@matches) {
1523
        $sth->execute($import_record_id, $match->{'record_id'}, $match->{'score'});
1525
        $sth->execute($import_record_id, $match->{'record_id'}, $match->{'score'}, $chosen);
1526
        $chosen = 0; #After the first we do not default to other matches
1524
    }
1527
    }
1525
}
1528
}
1526
1529
Lines 1737-1777 sub _get_commit_action { Link Here
1737
    if ($record_type eq 'biblio') {
1740
    if ($record_type eq 'biblio') {
1738
        my ($bib_result, $bib_match, $item_result);
1741
        my ($bib_result, $bib_match, $item_result);
1739
1742
1740
        if ($overlay_status ne 'no_match') {
1743
        $bib_match = GetBestRecordMatch($import_record_id);
1741
            $bib_match = GetBestRecordMatch($import_record_id);
1744
        if ($overlay_status ne 'no_match' && defined($bib_match)) {
1742
            if ($overlay_action eq 'replace') {
1745
1743
                $bib_result  = defined($bib_match) ? 'replace' : 'create_new';
1746
            $bib_result = $overlay_action;
1744
            } elsif ($overlay_action eq 'create_new') {
1747
1745
                $bib_result  = 'create_new';
1748
            if($item_action eq 'always_add' or $item_action eq 'add_only_for_matches'){
1746
            } elsif ($overlay_action eq 'ignore') {
1747
                $bib_result  = 'ignore';
1748
            }
1749
         if($item_action eq 'always_add' or $item_action eq 'add_only_for_matches'){
1750
                $item_result = 'create_new';
1749
                $item_result = 'create_new';
1751
       }
1750
            } elsif($item_action eq 'replace'){
1752
      elsif($item_action eq 'replace'){
1751
                $item_result = 'replace';
1753
          $item_result = 'replace';
1752
            } else {
1754
          }
1753
                $item_result = 'ignore';
1755
      else {
1754
            }
1756
             $item_result = 'ignore';
1755
1757
           }
1758
        } else {
1756
        } else {
1759
            $bib_result = $nomatch_action;
1757
            $bib_result = $nomatch_action;
1760
            $item_result = ($item_action eq 'always_add' or $item_action eq 'add_only_for_new')     ? 'create_new' : 'ignore';
1758
            $item_result = ($item_action eq 'always_add' or $item_action eq 'add_only_for_new') ? 'create_new' : 'ignore';
1761
        }
1759
        }
1762
        return ($bib_result, $item_result, $bib_match);
1760
        return ($bib_result, $item_result, $bib_match);
1763
    } else { # must be auths
1761
    } else { # must be auths
1764
        my ($auth_result, $auth_match);
1762
        my ($auth_result, $auth_match);
1765
1763
1766
        if ($overlay_status ne 'no_match') {
1764
        $auth_match = GetBestRecordMatch($import_record_id);
1767
            $auth_match = GetBestRecordMatch($import_record_id);
1765
        if ($overlay_status ne 'no_match' && defined($auth_match)) {
1768
            if ($overlay_action eq 'replace') {
1766
            $auth_result = $overlay_action;
1769
                $auth_result  = defined($auth_match) ? 'replace' : 'create_new';
1770
            } elsif ($overlay_action eq 'create_new') {
1771
                $auth_result  = 'create_new';
1772
            } elsif ($overlay_action eq 'ignore') {
1773
                $auth_result  = 'ignore';
1774
            }
1775
        } else {
1767
        } else {
1776
            $auth_result = $nomatch_action;
1768
            $auth_result = $nomatch_action;
1777
        }
1769
        }
(-)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->update({ chosen => 0 });
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->update({ chosen => 0}) 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.yaml (+2 lines)
Lines 33-38 import_batch_profile: Link Here
33
  $ref: definitions/import_batch_profile.yaml
33
  $ref: definitions/import_batch_profile.yaml
34
import_batch_profiles:
34
import_batch_profiles:
35
  $ref: definitions/import_batch_profiles.yaml
35
  $ref: definitions/import_batch_profiles.yaml
36
import_record_match:
37
  $ref: definitions/import_record_match.yaml
36
invoice:
38
invoice:
37
  $ref: definitions/invoice.yaml
39
  $ref: definitions/invoice.yaml
38
item:
40
item:
(-)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/definitions/import_record_match.yaml (+16 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
  candidate_match_id:
8
    type: integer
9
    description: Internal import record match candidate identifier
10
  chosen:
11
    type: boolean
12
    description: Whether match has been chosen for overlay
13
  score:
14
    type: integer
15
    description: Ranking value for this match calculated by the matching rules
16
additionalProperties: false
(-)a/api/v1/swagger/parameters.yaml (+4 lines)
Lines 3-8 advancededitormacro_id_pp: Link Here
3
  $ref: parameters/advancededitormacro.yaml#/advancededitormacro_id_pp
3
  $ref: parameters/advancededitormacro.yaml#/advancededitormacro_id_pp
4
biblio_id_pp:
4
biblio_id_pp:
5
  $ref: parameters/biblio.yaml#/biblio_id_pp
5
  $ref: parameters/biblio.yaml#/biblio_id_pp
6
candidate_match_id_pp:
7
  $ref: parameters/import_record_match.yaml#/candidate_match_id_pp
6
cash_register_id_pp:
8
cash_register_id_pp:
7
  $ref: parameters/cash_register.yaml#/cash_register_id_pp
9
  $ref: parameters/cash_register.yaml#/cash_register_id_pp
8
cashup_id_pp:
10
cashup_id_pp:
Lines 19-24 hold_id_pp: Link Here
19
  $ref: parameters/hold.yaml#/hold_id_pp
21
  $ref: parameters/hold.yaml#/hold_id_pp
20
import_batch_profile_id_pp:
22
import_batch_profile_id_pp:
21
  $ref: parameters/import_batch_profile.yaml#/import_batch_profile_id_pp
23
  $ref: parameters/import_batch_profile.yaml#/import_batch_profile_id_pp
24
import_record_id_pp:
25
  $ref: parameters/import_record_match.yaml#/import_record_id_pp
22
item_id_pp:
26
item_id_pp:
23
  $ref: parameters/item.yaml#/item_id_pp
27
  $ref: parameters/item.yaml#/item_id_pp
24
library_id_pp:
28
library_id_pp:
(-)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/parameters/import_record_match.yaml (+13 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
candidate_match_id_pp:
9
  name: candidate_match_id
10
  in: path
11
  description: Internal import_record_match_candidate identifier
12
  required: true
13
  type: integer
(-)a/api/v1/swagger/paths.yaml (+2 lines)
Lines 55-60 Link Here
55
  $ref: paths/holds.yaml#/~1holds~1{hold_id}~1pickup_locations
55
  $ref: paths/holds.yaml#/~1holds~1{hold_id}~1pickup_locations
56
"/holds/{hold_id}/pickup_location":
56
"/holds/{hold_id}/pickup_location":
57
  $ref: paths/holds.yaml#/~1holds~1{hold_id}~1pickup_location
57
  $ref: paths/holds.yaml#/~1holds~1{hold_id}~1pickup_location
58
"/import/{import_batch_id}/records/{import_record_id}/matches/chosen":
59
  $ref: paths/import_record_matches.yaml#/~1import~1{import_batch_id}~1records~1{import_record_id}~1matches~1chosen
58
/items:
60
/items:
59
  $ref: paths/items.yaml#/~1items
61
  $ref: paths/items.yaml#/~1items
60
"/items/{item_id}":
62
"/items/{item_id}":
(-)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/paths/import_record_matches.yaml (+106 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:
7
      - import_record_matches
8
    parameters:
9
      - name: import_batch_id
10
        in: path
11
        required: true
12
        description: An import_batch ID
13
        type: integer
14
      - name: import_record_id
15
        in: path
16
        required: true
17
        description: An import_record ID
18
        type: integer
19
      - name: body
20
        in: body
21
        description: A JSON object containing fields to modify
22
        required: true
23
        schema:
24
          type: object
25
          properties:
26
            candidate_match_id:
27
              description: Candidate match to choose
28
              type: integer
29
    consumes:
30
      - application/json
31
    produces:
32
      - application/json
33
    responses:
34
      "200":
35
        description: Match updated
36
      "400":
37
        description: Missing or wrong parameters
38
        schema:
39
          $ref: ../definitions.yaml#/error
40
      "401":
41
        description: Authentication required
42
        schema:
43
          $ref: ../definitions.yaml#/error
44
      "403":
45
        description: Match management not allowed
46
        schema:
47
          $ref: ../definitions.yaml#/error
48
      "404":
49
        description: Import record match not found
50
        schema:
51
          $ref: ../definitions.yaml#/error
52
      "500":
53
        description: Internal server error
54
        schema:
55
          $ref: ../definitions.yaml#/error
56
      "503":
57
        description: Under maintenance
58
        schema:
59
          $ref: ../definitions.yaml#/error
60
    x-koha-authorization:
61
      permissions:
62
        tools: manage_staged_marc
63
  delete:
64
    x-mojo-to: ImportRecordMatches#unset_chosen
65
    operationId: unsetChosen
66
    tags:
67
      - import_record_matches
68
    parameters:
69
      - name: import_batch_id
70
        in: path
71
        required: true
72
        description: An import_batch ID
73
        type: integer
74
      - name: import_record_id
75
        in: path
76
        required: true
77
        description: An import_record ID
78
        type: integer
79
    produces:
80
      - application/json
81
    responses:
82
      "204":
83
        description: Matches unchosen
84
      "401":
85
        description: Authentication required
86
        schema:
87
          $ref: ../definitions.yaml#/error
88
      "403":
89
        description: Match management not allowed
90
        schema:
91
          $ref: ../definitions.yaml#/error
92
      "404":
93
        description: Import record matches not found
94
        schema:
95
          $ref: ../definitions.yaml#/error
96
      "500":
97
        description: Internal server error
98
        schema:
99
          $ref: ../definitions.yaml#/error
100
      "503":
101
        description: Under maintenance
102
        schema:
103
          $ref: ../definitions.yaml#/error
104
    x-koha-authorization:
105
      permissions:
106
        tools: manage_staged_marc
(-)a/api/v1/swagger/x-primitives.yaml (+6 lines)
Lines 2-7 Link Here
2
biblio_id:
2
biblio_id:
3
  type: integer
3
  type: integer
4
  description: Internal biblio identifier
4
  description: Internal biblio identifier
5
import_record_id:
6
  type: integer
7
  description: Internal import record identifier
8
candidate_match_id:
9
  type: integer
10
  description: Internal import record match identifier
5
advancededitormacro_id:
11
advancededitormacro_id:
6
  type: integer
12
  type: integer
7
  description: Internal advanced editor macro identifier
13
  description: Internal advanced editor macro identifier
(-)a/installer/data/mysql/atomicupdate/bug_22785.pl (+16 lines)
Line 0 Link Here
1
use Modern::Perl;
2
3
return {
4
    bug_number => "22785",
5
    description => "Add chosen column to import_record_matches",
6
    up => sub {
7
        my ($args) = @_;
8
        my ($dbh, $out) = @$args{qw(dbh out)};
9
        unless( column_exists('import_record_matches','chosen') ){
10
            $dbh->do(q{
11
                ALTER TABLE import_record_matches ADD COLUMN chosen TINYINT null DEFAULT null AFTER score
12
            });
13
            say $out "chosen column added to import_record_matches";
14
        }
15
    },
16
}
(-)a/installer/data/mysql/kohastructure.sql (+1 lines)
Lines 2934-2939 CREATE TABLE `import_record_matches` ( Link Here
2934
  `import_record_id` int(11) NOT NULL COMMENT 'the id given to the imported bib record (import_records.import_record_id)',
2934
  `import_record_id` int(11) NOT NULL COMMENT 'the id given to the imported bib record (import_records.import_record_id)',
2935
  `candidate_match_id` int(11) NOT NULL COMMENT 'the biblio the imported record matches (biblio.biblionumber)',
2935
  `candidate_match_id` int(11) NOT NULL COMMENT 'the biblio the imported record matches (biblio.biblionumber)',
2936
  `score` int(11) NOT NULL DEFAULT 0 COMMENT 'the match score',
2936
  `score` int(11) NOT NULL DEFAULT 0 COMMENT 'the match score',
2937
  `chosen` tinyint(1) NULL DEFAULT NULL COMMENT 'whether this match has been allowed or denied',
2937
  PRIMARY KEY (`import_record_id`,`candidate_match_id`),
2938
  PRIMARY KEY (`import_record_id`,`candidate_match_id`),
2938
  KEY `record_score` (`import_record_id`,`score`),
2939
  KEY `record_score` (`import_record_id`,`score`),
2939
  CONSTRAINT `import_record_matches_ibfk_1` FOREIGN KEY (`import_record_id`) REFERENCES `import_records` (`import_record_id`) ON DELETE CASCADE ON UPDATE CASCADE
2940
  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 (-13 / +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 485-492 Link Here
485
                        { "mDataProp": "citation" },
486
                        { "mDataProp": "citation" },
486
                        { "mDataProp": "status" },
487
                        { "mDataProp": "status" },
487
                        { "mDataProp": "overlay_status" },
488
                        { "mDataProp": "overlay_status" },
488
                        { "mDataProp": "match_citation" },
489
                        { "mDataProp": "" },
489
                        { "mDataProp": "diff_url" },
490
                        { "mDataProp": "" },
490
                        { "mDataProp": "matched" }
491
                        { "mDataProp": "matched" }
491
                    ],
492
                    ],
492
                    "fnServerData": function(sSource, aoData, fnCallback) {
493
                    "fnServerData": function(sSource, aoData, fnCallback) {
Lines 547-563 Link Here
547
                            aData['overlay_status']
548
                            aData['overlay_status']
548
                        );
549
                        );
549
550
550
                        if (aData['match_id']) {
551
                        if ( aData['matches'].length > 0 ) {
551
                            [% IF(record_type == 'auth') -%]
552
552
                                var matching_msg = _("Matches authority %s (score=%s):%s");
553
                            var any_checked = 0;
553
                            [%- ELSE -%]
554
                            $('td:eq(4)', nRow).html('<ul class="matches"></ul>');
554
                                var matching_msg = _("Matches bibliographic record %s (score=%s):%s");
555
                            $('td:eq(5)', nRow).html('<ul class="matches"></ul>');
555
                            [%- END %]
556
                            var checked = "";
556
                            $('td:eq(4)', nRow).html(
557
                            var disabled = "";
557
                                matching_msg.format(aData['match_id'], aData['score'],
558
                            if( aData['status'] == "imported" || aData['status'] == "ignored" ){
558
                                    '<a target="_blank" href="' + record_details_url
559
                                disabled = ' disabled ';
559
                                    + aData['match_id'] + '">' + aData['match_citation'] + '</a>')
560
                            }
560
                            );
561
                            aData['matches'].forEach(function(item,index){
562
                                if( item.chosen == 1 ){
563
                                    checked = 'checked="checked"';
564
                                    any_checked = 1;
565
                                }
566
                                [% IF(item.record_type == 'auth') -%]
567
                                    var matching_msg = _("Matches authority %s (score=%s):%s");
568
                                [%- ELSE -%]
569
                                    var matching_msg = _("Matches bibliographic record %s (score=%s):%s");
570
                                [%- END %]
571
                                    var match_option = "";
572
                                    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 + '> ';
573
574
                                    var diff_url = '/cgi-bin/koha/tools/showdiffmarc.pl?batchid=%s&importid=%s&id=%s&type=%s';
575
                                    var match_citation = '';
576
                                    if( item.title  ){ match_citation += item.title + ' ' }
577
                                    if( item.author ){ match_citation += item.author }
578
                                $('td:eq(4) ul', nRow).append('<li><label for="import_record_id_'+aData['import_record_id']+'_match_'+index+'">'+match_option+
579
                                    matching_msg.format(item.candidate_match_id, item.score,
580
                                        '<a target="_blank" href="' + record_details_url
581
                                        + item.candidate_match_id + '">' + match_citation + '</a></label></li>')
582
                                );
583
                                $('td:eq(5) ul', nRow).append('<li><a href="'
584
                                        + diff_url.format( [% import_batch_id | html %], aData['import_record_id'], item.candidate_match_id, item.record_type) + '">' + _("View") + '</a></li>');
585
                            });
586
                            checked = "";
587
                            if( !any_checked ){ checked = 'checked="checked"'; }
588
                            $('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>');
589
                            $('td:eq(5) ul', nRow).prepend('<li>&nbsp;</li>');
561
                        }
590
                        }
562
                        if (aData['diff_url']) {
591
                        if (aData['diff_url']) {
563
                            $('td:eq(5)', nRow).html(
592
                            $('td:eq(5)', nRow).html(
Lines 584-589 Link Here
584
                });
613
                });
585
            [% END %]
614
            [% END %]
586
615
616
            $("body").on("change", ".chosen", function(e) {
617
                let apimethod = "DELETE";
618
                let apidata ="";
619
                if( $(this).val() != 'none' ){
620
                    apimethod = 'PUT';
621
                    apidata = JSON.stringify({ candidate_match_id: $(this).val() });
622
                }
623
                $.ajax({
624
                    url: '/api/v1/import/[% import_batch_id | html %]/records/'+$(this).data('import_record_id')+'/matches/chosen',
625
                    method: apimethod,
626
                    data: apidata
627
                }).fail(function(){ alert("Unable to update match choices"); return false; });
628
            });
629
587
            $("body").on("click", ".previewMARC", function(e) {
630
            $("body").on("click", ".previewMARC", function(e) {
588
                e.preventDefault();
631
                e.preventDefault();
589
                var ltitle = $(this).text();
632
                var ltitle = $(this).text();
(-)a/t/db_dependent/ImportBatch.t (-1 / +29 lines)
Lines 1-7 Link Here
1
#!/usr/bin/perl
1
#!/usr/bin/perl
2
2
3
use Modern::Perl;
3
use Modern::Perl;
4
use Test::More tests => 17;
4
use Test::More tests => 18;
5
use utf8;
5
use utf8;
6
use File::Basename;
6
use File::Basename;
7
use File::Temp qw/tempfile/;
7
use File::Temp qw/tempfile/;
Lines 214-219 subtest "RecordsFromMarcPlugin" => sub { Link Here
214
        'Checked one field in second record' );
214
        'Checked one field in second record' );
215
};
215
};
216
216
217
subtest "_get_commit_action" => sub {
218
    plan tests => 24;
219
    my $mock_import = Test::MockModule->new("C4::ImportBatch");
220
221
    $mock_import->mock( GetBestRecordMatch => sub { return 5; } );
222
    foreach my $record_type ( ('biblio','authority') ){
223
        foreach my $match_action ( ('replace','create_new','ignore') ){
224
            foreach my $no_match_action ( ('create_new','ignore') ){
225
                my ($result, $match, $item_result) =
226
                    C4::ImportBatch::_get_commit_action($match_action, $no_match_action, 'always_add', 'auto_match', 42, $record_type);
227
                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");
228
            }
229
        }
230
    }
231
232
    $mock_import->mock( GetBestRecordMatch => sub { my $matches = undef; return $matches; } );
233
    foreach my $record_type ( ('biblio','authority') ){
234
        foreach my $match_action ( ('replace','create_new','ignore') ){
235
            foreach my $no_match_action ( ('create_new','ignore') ){
236
                my ($result, $match, $item_result) =
237
                    C4::ImportBatch::_get_commit_action($match_action, $no_match_action, 'always_add', 'auto_match', 42, $record_type);
238
                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");
239
            }
240
        }
241
    }
242
243
};
244
217
sub get_import_record {
245
sub get_import_record {
218
    my $id_import_batch = shift;
246
    my $id_import_batch = shift;
219
    return $dbh->do('SELECT * FROM import_records WHERE import_batch_id = ?', undef, $id_import_batch);
247
    return $dbh->do('SELECT * FROM import_records WHERE import_batch_id = ?', undef, $id_import_batch);
(-)a/t/db_dependent/Koha/Import/Record/Matches.t (-3 / +6 lines)
Lines 19-25 Link Here
19
19
20
use Modern::Perl;
20
use Modern::Perl;
21
21
22
use Test::More tests => 5;
22
use Test::More tests => 6;
23
23
24
use Koha::Import::Record::Matches;
24
use Koha::Import::Record::Matches;
25
use Koha::Database;
25
use Koha::Database;
Lines 31-42 $schema->storage->txn_begin; Link Here
31
31
32
my $builder = t::lib::TestBuilder->new;
32
my $builder = t::lib::TestBuilder->new;
33
my $nb_of_matches = Koha::Import::Record::Matches->search->count;
33
my $nb_of_matches = Koha::Import::Record::Matches->search->count;
34
my $nb_of_chosen_matches = Koha::Import::Record::Matches->search({ chosen => 1 })->count;
34
35
35
my $match_1 = $builder->build({ source => 'ImportRecordMatch', });
36
my $match_1 = $builder->build({ source => 'ImportRecordMatch', value => { chosen => 1 } });
36
my $match_2 = $builder->build({ source => 'ImportRecordMatch', value => { import_record_id => $match_1->{import_record_id} } });
37
my $match_2 = $builder->build({ source => 'ImportRecordMatch', value => { chosen => 1, import_record_id => $match_1->{import_record_id} } });
37
38
38
is( Koha::Import::Record::Matches->search->count, $nb_of_matches + 2, 'The 2 matches should have been added' );
39
is( Koha::Import::Record::Matches->search->count, $nb_of_matches + 2, 'The 2 matches should have been added' );
39
40
41
is( Koha::Import::Record::Matches->search({chosen => 1 })->count, $nb_of_chosen_matches + 2, 'The 2 chosen matches should have been added' );
42
40
my $retrieved_match_1 = Koha::Import::Record::Matches->search({ import_record_id => $match_1->{import_record_id}, candidate_match_id => $match_1->{candidate_match_id} })->next;
43
my $retrieved_match_1 = Koha::Import::Record::Matches->search({ import_record_id => $match_1->{import_record_id}, candidate_match_id => $match_1->{candidate_match_id} })->next;
41
is_deeply( $retrieved_match_1->unblessed, $match_1, 'Find a match by import record id and candidate should return the correct match' );
44
is_deeply( $retrieved_match_1->unblessed, $match_1, 'Find a match by import record id and candidate should return the correct match' );
42
45
(-)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 68-88 my @list = (); Link Here
68
foreach my $record (@$records) {
68
foreach my $record (@$records) {
69
    my $citation = $record->{'title'} || $record->{'authorized_heading'};
69
    my $citation = $record->{'title'} || $record->{'authorized_heading'};
70
70
71
    my $match = GetImportRecordMatches( $record->{'import_record_id'}, 1 );
71
    my $matches = GetImportRecordMatches( $record->{'import_record_id'} );
72
    my $match_citation = '';
73
    my $match_id;
72
    my $match_id;
74
    if ( $#$match > -1 ) {
73
    if ( scalar @$matches > 0 ) {
75
        if ( $match->[0]->{'record_type'} eq 'biblio' ) {
74
        foreach my $match (@$matches){
76
            $match_citation .= $match->[0]->{'title'}
75
        my $match_citation = '';
77
              if defined( $match->[0]->{'title'} );
76
            if ( $match->{'record_type'} eq 'biblio' ) {
78
            $match_citation .= ' ' . $match->[0]->{'author'}
77
                $match_citation .= $match->{'title'}
79
              if defined( $match->[0]->{'author'} );
78
                  if defined( $match->{'title'} );
80
            $match_id = $match->[0]->{'biblionumber'};
79
                $match_citation .= ' ' . $match->{'author'}
81
        }
80
                  if defined( $match->{'author'} );
82
        elsif ( $match->[0]->{'record_type'} eq 'auth' ) {
81
                $match->{'match_citation'} = $match_citation;
83
            if ( defined( $match->[0]->{'authorized_heading'} ) ) {
82
            }
84
                $match_citation .= $match->[0]->{'authorized_heading'};
83
            elsif ( $match->{'record_type'} eq 'auth' ) {
85
                $match_id = $match->[0]->{'candidate_match_id'};
84
                if ( defined( $match->{'authorized_heading'} ) ) {
85
                    $match_citation .= $match->{'authorized_heading'};
86
                    $match->{'match_citation'} = $match_citation;
87
                }
86
            }
88
            }
87
        }
89
        }
88
    }
90
    }
Lines 97-108 foreach my $record (@$records) { Link Here
97
        isbn            => $record->{'isbn'},
99
        isbn            => $record->{'isbn'},
98
        status          => $record->{'status'},
100
        status          => $record->{'status'},
99
        overlay_status  => $record->{'overlay_status'},
101
        overlay_status  => $record->{'overlay_status'},
100
        match_citation  => $match_citation,
101
        matched         => $record->{'matched_biblionumber'}
102
        matched         => $record->{'matched_biblionumber'}
102
          || $record->{'matched_authid'}
103
          || $record->{'matched_authid'}
103
          || q{},
104
          || q{},
104
        score => $#$match > -1 ? $match->[0]->{'score'} : 0,
105
        score => scalar @$matches > 0 ? $matches->[0]->{'score'} : 0,
105
        match_id => $match_id,
106
        matches => $matches,
106
        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
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
107
      };
108
      };
108
}
109
}
109
- 

Return to bug 22785