Bug 41901: Allow duplicate check when adding authority via API
POST /api/v1/authorities currently offers only the heading-based
C4::AuthoritiesMarc::FindDuplicateAuthority check, and the only
escape hatch is the `x-koha-override` header.
Koha already ships configurable record matching rules (see
Administration > Record matching rules) and C4::Matcher already
supports `record_type = authority`. This patch lets an API caller
opt into one of those rules when creating an authority by sending
its numeric id in a new `x-koha-matchrule` header.
Behaviour:
* If no `x-koha-matchrule` header is sent the existing heading-based
duplicate check is used. Existing clients are unaffected.
* If `x-koha-matchrule: <id>` is sent:
* non-existent id -> 400 `matching_rule_not_found`
* matcher not record_type=authority
-> 400 `invalid_matching_rule_type`
* matcher returns matches -> 409 `duplicate`; the response
body additionally contains a
`matches: [{authid, score}, ...]`
array listing every hit returned
by the matcher
* matcher returns nothing -> record is created (201)
* `x-koha-override: any|duplicate` continues to bypass all duplicate
checks, including the matcher.
A non-integer header value is rejected by the OpenAPI schema
validator before the controller runs.
Test plan:
1) Apply the patch series.
2) Run `prove t/db_dependent/api/v1/authorities.t`
=> all subtests pass.
3) Log in to the staff interface as a user with the
'manage_matching_rules' subpermission and go to
Administration > Record matching rules > New record matching
rule:
a) Set Matching rule code e.g. `AUTH_HEADING`
b) Set Description e.g. `Authority heading match`
c) Set Record type to `authority`
d) Set Threshold to e.g. `1000`
e) Add a match point:
* Search index: `Heading-Main` (or your authorities index)
* Score: `1000`
* Tag: `110` (or the heading tag used by your
authority type)
* Subfields: `a`
f) Save. Note the numeric matching rule id in the edit URL
(`admin/matching-rules.pl?op=edit_matching_rule&matcher_id=N`).
4) Export an existing authority record as MARCXML (for example
from the authority details page). Save it as `duplicate.xml`.
5) From the command line, POST the record back to Koha using the
new header (replace <N>, credentials and host):
curl -u <user>:<pass> -X POST \
-H 'Content-Type: application/marcxml+xml' \
-H 'x-authority-type: CORPO_NAME' \
-H 'x-koha-matchrule: <N>' \
--data-binary @duplicate.xml \
http://<your-koha>/api/v1/authorities
=> Expect HTTP 409. The JSON response body contains
`error_code: "duplicate"` and a `matches` array listing the
authid(s) that were found along with their score.
6) Edit `duplicate.xml` so the main heading is something that is
not already in the catalogue. POST it again with the same
header.
=> Expect HTTP 201 and a `Location` header pointing to the new
authority.
7) Re-run the POST from step 5 but additionally send
`-H 'x-koha-override: duplicate'`.
=> Expect HTTP 201 (override bypasses the matcher).
8) Send a POST with `x-koha-matchrule: 999999`.
=> Expect HTTP 400 with `error_code: "matching_rule_not_found"`.
9) Create a second matching rule with Record type `biblio` and
note its id. POST with `x-koha-matchrule` set to that id.
=> Expect HTTP 400 with
`error_code: "invalid_matching_rule_type"`.
10) POST with `x-koha-matchrule: abc`.
=> Expect HTTP 400 from the OpenAPI schema validator.
11) POST without `x-koha-matchrule`.
=> Expect the existing heading-based duplicate check to run
exactly as before (409 for a known duplicate, 201 otherwise).
12) Sign off.
Signed-off-by: Brian J. Barr <brian.barr@churchofjesuschrist.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
This commit is contained in:
parent
5feb1e03eb
commit
cb8f1ff9d5
|
|
@ -21,6 +21,7 @@ use Mojo::Base 'Mojolicious::Controller';
|
|||
|
||||
use Koha::Authorities;
|
||||
use C4::AuthoritiesMarc qw( DelAuthority AddAuthority FindDuplicateAuthority ModAuthority);
|
||||
use C4::Matcher;
|
||||
|
||||
use List::MoreUtils qw( any );
|
||||
use MARC::Record::MiJ;
|
||||
|
|
@ -156,17 +157,63 @@ sub add {
|
|||
);
|
||||
}
|
||||
|
||||
unless ( $overrides->{any} || $overrides->{duplicate} ) {
|
||||
my ( $duplicateauthid, $duplicateauthvalue ) =
|
||||
C4::AuthoritiesMarc::FindDuplicateAuthority( $record, $authtypecode );
|
||||
my $matcher_id = $headers->header('x-koha-matchrule');
|
||||
my $matcher;
|
||||
if ( defined $matcher_id && $matcher_id ne '' ) {
|
||||
$matcher = C4::Matcher->fetch($matcher_id);
|
||||
unless ($matcher) {
|
||||
return $c->render(
|
||||
status => 400,
|
||||
openapi => {
|
||||
error => "Matching rule $matcher_id not found",
|
||||
error_code => 'matching_rule_not_found',
|
||||
}
|
||||
);
|
||||
}
|
||||
unless ( $matcher->record_type eq 'authority' ) {
|
||||
return $c->render(
|
||||
status => 400,
|
||||
openapi => {
|
||||
error => "Matching rule $matcher_id is not an authority matcher",
|
||||
error_code => 'invalid_matching_rule_type',
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $c->render(
|
||||
status => 409,
|
||||
openapi => {
|
||||
error => "Duplicate record ($duplicateauthid)",
|
||||
error_code => 'duplicate',
|
||||
unless ( $overrides->{any} || $overrides->{duplicate} ) {
|
||||
if ($matcher) {
|
||||
my @matches = $matcher->get_matches( $record, 10 );
|
||||
if (@matches) {
|
||||
my $first = $matches[0]->{record_id};
|
||||
return $c->render(
|
||||
status => 409,
|
||||
openapi => {
|
||||
error => "Duplicate record ($first)",
|
||||
error_code => 'duplicate',
|
||||
matches => [
|
||||
map {
|
||||
{
|
||||
authid => $_->{record_id} + 0,
|
||||
score => $_->{score} + 0,
|
||||
}
|
||||
} @matches
|
||||
],
|
||||
}
|
||||
);
|
||||
}
|
||||
) unless !$duplicateauthid;
|
||||
} else {
|
||||
my ( $duplicateauthid, $duplicateauthvalue ) =
|
||||
C4::AuthoritiesMarc::FindDuplicateAuthority( $record, $authtypecode );
|
||||
|
||||
return $c->render(
|
||||
status => 409,
|
||||
openapi => {
|
||||
error => "Duplicate record ($duplicateauthid)",
|
||||
error_code => 'duplicate',
|
||||
}
|
||||
) unless !$duplicateauthid;
|
||||
}
|
||||
}
|
||||
|
||||
my $authid = AddAuthority( $record, undef, $authtypecode );
|
||||
|
|
|
|||
|
|
@ -94,11 +94,24 @@
|
|||
- any
|
||||
- duplicate
|
||||
collectionFormat: csv
|
||||
- name: x-koha-matchrule
|
||||
in: header
|
||||
required: false
|
||||
description: |
|
||||
Numeric id of a Koha record matching rule (record_type=authority) to
|
||||
use for duplicate detection instead of the default heading-based
|
||||
check. If the id does not reference an authority matcher a 400
|
||||
response is returned.
|
||||
type: integer
|
||||
responses:
|
||||
"201":
|
||||
description: An authority
|
||||
"400":
|
||||
description: Bad request
|
||||
description: |
|
||||
Bad request. Possible `error_code` attribute values:
|
||||
|
||||
* `matching_rule_not_found`
|
||||
* `invalid_matching_rule_type`
|
||||
schema:
|
||||
$ref: "../swagger.yaml#/definitions/error"
|
||||
"401":
|
||||
|
|
@ -121,6 +134,10 @@
|
|||
Conflict creating the resource. Possible `error_code` attribute values:
|
||||
|
||||
* `duplicate`
|
||||
|
||||
When a matching rule is requested via `x-koha-matchrule`, the response
|
||||
body additionally contains a `matches` array of objects with
|
||||
`authid` and `score` for every authority returned by the matcher.
|
||||
schema:
|
||||
$ref: "../swagger.yaml#/definitions/error"
|
||||
"500":
|
||||
|
|
|
|||
Loading…
Reference in New Issue