From ea40ab298705a92e86ec7a758cc4b7e76477cf81 Mon Sep 17 00:00:00 2001 From: Kyle M Hall Date: Wed, 13 Sep 2023 08:59:33 -0400 Subject: [PATCH] Bug 34784: Add REST API endpoints for updating single item or all applicable items on a bib Test Plan: 1) Apply this patch 2) prove t/db_dependent/api/v1/biblios.t --- Koha/REST/V1/Biblios.pm | 85 +++++++++++++++++++++ api/v1/swagger/paths/biblios.yaml | 119 ++++++++++++++++++++++++++++++ api/v1/swagger/swagger.yaml | 4 + t/db_dependent/api/v1/biblios.t | 79 +++++++++++++++++++- 4 files changed, 286 insertions(+), 1 deletion(-) diff --git a/Koha/REST/V1/Biblios.pm b/Koha/REST/V1/Biblios.pm index eef084bc2c7..d47cdb3eaee 100644 --- a/Koha/REST/V1/Biblios.pm +++ b/Koha/REST/V1/Biblios.pm @@ -152,6 +152,91 @@ sub delete { }; } +=head3 populate_empty_callnumbers + +Controller function that handles deleting a biblio object + +=cut + +sub populate_empty_callnumbers { + my $c = shift->openapi->valid_input or return; + + my $biblio = Koha::Biblios->find( $c->param('biblio_id') ); + + if ( not defined $biblio ) { + return $c->render( + status => 404, + openapi => { error => "Biblio not found" } + ); + } + + my $items; + if ( $c->param('item_id') ) { + $items = Koha::Items->search( + { + -and => [ + biblionumber => $biblio->id, + itemnumber => $c->param('item_id'), + -or => [ + itemcallnumber => undef, + itemcallnumber => q{}, + ] + ] + } + ); + } else { + $items = Koha::Items->search( + { + -and => [ + biblionumber => $biblio->id, + -or => [ + itemcallnumber => undef, + itemcallnumber => q{}, + ] + ] + } + ); + } + + my $cn_fields = C4::Context->preference('itemcallnumber'); + return $c->render( + status => 404, + openapi => { error => "Callnumber fields not found" } + ) unless $cn_fields; + + my $record = $biblio->record; + my $callnumber; + foreach my $callnumber_marc_field ( split( /,/, $cn_fields ) ) { + my $callnumber_tag = substr( $callnumber_marc_field, 0, 3 ); + my $callnumber_subfields = substr( $callnumber_marc_field, 3 ); + + next unless $callnumber_tag && $callnumber_subfields; + + my $field = $record->field($callnumber_tag); + + next unless $field; + + $callnumber = $field->as_string( $callnumber_subfields, ' ' ); + last if $callnumber; + } + + return $c->render( + status => 200, + openapi => { items_updated => 0, callnumber => $callnumber }, + ) unless $items->count; + + return try { + my $items_updated = $items->update( { itemcallnumber => $callnumber }, { no_triggers => 1 } ); + + return $c->render( + status => 200, + openapi => { items_updated => $items_updated, callnumber => $callnumber }, + ); + } catch { + $c->unhandled_exception($_); + }; +} + =head3 get_public Controller function that handles retrieving a single biblio object diff --git a/api/v1/swagger/paths/biblios.yaml b/api/v1/swagger/paths/biblios.yaml index d250237b687..a0123104326 100644 --- a/api/v1/swagger/paths/biblios.yaml +++ b/api/v1/swagger/paths/biblios.yaml @@ -462,6 +462,125 @@ x-koha-authorization: permissions: editcatalogue: edit_catalogue +"/biblios/{biblio_id}/items/populate_empty_callnumbers": + post: + x-mojo-to: Biblios#populate_empty_callnumbers + operationId: itemsPopulateEmptyCallnumbers + tags: + - biblios + - items + - callnumbers + summary: Populate empty item callnumbers from biblio callnumber for all items on record with no callnumber + parameters: + - $ref: "../swagger.yaml#/parameters/biblio_id_pp" + produces: + - application/json + responses: + "200": + description: Callnumbers updated + schema: + type: object + properties: + items_updated: + description: Number of items updated + type: integer + callnumber: + description: Callnumber added to updated items + type: string + "400": + description: Bad request + schema: + $ref: "../swagger.yaml#/definitions/error" + "401": + description: Authentication required + schema: + $ref: "../swagger.yaml#/definitions/error" + "403": + description: Access forbidden + schema: + $ref: "../swagger.yaml#/definitions/error" + "404": + description: Not found + schema: + $ref: "../swagger.yaml#/definitions/error" + "409": + description: Conflict + schema: + $ref: "../swagger.yaml#/definitions/error" + "500": + description: | + Internal server error. Possible `error_code` attribute values: + + * `internal_server_error` + schema: + $ref: "../swagger.yaml#/definitions/error" + "503": + description: Under maintenance + schema: + $ref: "../swagger.yaml#/definitions/error" + x-koha-authorization: + permissions: + editcatalogue: edit_catalogue +"/biblios/{biblio_id}/items/{item_id}/populate_empty_callnumbers": + post: + x-mojo-to: Biblios#populate_empty_callnumbers + operationId: itemPopulateEmptyCallnumbers + tags: + - biblios + - items + - callnumbers + summary: Populate empty item callnumber from biblio callnumber for a given item + parameters: + - $ref: "../swagger.yaml#/parameters/biblio_id_pp" + - $ref: "../swagger.yaml#/parameters/item_id_pp" + produces: + - application/json + responses: + "200": + description: Callnumbers updated + schema: + type: object + properties: + items_updated: + description: Number of items updated + type: integer + callnumber: + description: Callnumber added to updated items + type: string + "400": + description: Bad request + schema: + $ref: "../swagger.yaml#/definitions/error" + "401": + description: Authentication required + schema: + $ref: "../swagger.yaml#/definitions/error" + "403": + description: Access forbidden + schema: + $ref: "../swagger.yaml#/definitions/error" + "404": + description: Not found + schema: + $ref: "../swagger.yaml#/definitions/error" + "409": + description: Conflict + schema: + $ref: "../swagger.yaml#/definitions/error" + "500": + description: | + Internal server error. Possible `error_code` attribute values: + + * `internal_server_error` + schema: + $ref: "../swagger.yaml#/definitions/error" + "503": + description: Under maintenance + schema: + $ref: "../swagger.yaml#/definitions/error" + x-koha-authorization: + permissions: + editcatalogue: edit_catalogue "/biblios/{biblio_id}/items/{item_id}": put: x-mojo-to: Biblios#update_item diff --git a/api/v1/swagger/swagger.yaml b/api/v1/swagger/swagger.yaml index c52d03462ac..27e3ccce3f3 100644 --- a/api/v1/swagger/swagger.yaml +++ b/api/v1/swagger/swagger.yaml @@ -179,6 +179,10 @@ paths: $ref: "./paths/biblios.yaml#/~1biblios~1{biblio_id}~1checkouts" "/biblios/{biblio_id}/items": $ref: "./paths/biblios.yaml#/~1biblios~1{biblio_id}~1items" + "/biblios/{biblio_id}/items/populate_empty_callnumbers": + $ref: "./paths/biblios.yaml#/~1biblios~1{biblio_id}~1items~1populate_empty_callnumbers" + "/biblios/{biblio_id}/items/{item_id}/populate_empty_callnumbers": + $ref: "./paths/biblios.yaml#/~1biblios~1{biblio_id}~1items~1{item_id}~1populate_empty_callnumbers" "/biblios/{biblio_id}/items/{item_id}": $ref: "./paths/biblios.yaml#/~1biblios~1{biblio_id}~1items~1{item_id}" "/biblios/{biblio_id}/pickup_locations": diff --git a/t/db_dependent/api/v1/biblios.t b/t/db_dependent/api/v1/biblios.t index 53aeaaaa946..b19d0411a15 100755 --- a/t/db_dependent/api/v1/biblios.t +++ b/t/db_dependent/api/v1/biblios.t @@ -20,7 +20,7 @@ use Modern::Perl; use utf8; use Encode; -use Test::More tests => 13; +use Test::More tests => 14; use Test::MockModule; use Test::Mojo; use Test::Warn; @@ -1811,3 +1811,80 @@ subtest 'update_item() tests' => sub { $schema->storage->txn_rollback; }; + +subtest 'populate_empty_callnumbers() tests' => sub { + plan tests => 23; + + t::lib::Mocks::mock_preference( 'itemcallnumber', '' ); + + $schema->storage->txn_begin; + + my $biblio = $builder->build_sample_biblio(); + + my $patron = $builder->build_object( + { + class => 'Koha::Patrons', + value => { flags => 0 } + } + ); + my $password = 'thePassword123'; + $patron->set_password( { password => $password, skip_validation => 1 } ); + my $userid = $patron->userid; + + my $biblio_id = $biblio->id; + + $t->post_ok("//$userid:$password@/api/v1/biblios/$biblio_id/items/populate_empty_callnumbers" => json => { external_id => 'something' }) + ->status_is(403, 'Not enough permissions to update an item'); + + # Add permissions + $builder->build( + { + source => 'UserPermission', + value => { + borrowernumber => $patron->borrowernumber, + module_bit => 9, + code => 'edit_catalogue' + } + } + ); + + $t->post_ok("//$userid:$password@/api/v1/biblios/$biblio_id/items/populate_empty_callnumbers" => json => {} ) + ->status_is(200, 'Item updated') + ->json_is('/items_updated', 0); + + my $item_1 = $builder->build_sample_item( { biblionumber => $biblio->id, itemcallnumber => q{} } ); + my $item_2 = $builder->build_sample_item( { biblionumber => $biblio->id, itemcallnumber => q{} } ); + my $item_3 = $builder->build_sample_item( { biblionumber => $biblio->id, itemcallnumber => q{} } ); + my $item_4 = $builder->build_sample_item( { biblionumber => $biblio->id, itemcallnumber => q{someCallNumber} } ); + + my $item1_id = $item_1->id; + + $t->post_ok("//$userid:$password@/api/v1/biblios/$biblio_id/items/$item1_id/populate_empty_callnumbers" => json => {} ) + ->status_is(404, 'Callnumber fields not found'); + + t::lib::Mocks::mock_preference( 'itemcallnumber', '245$a' ); + + $t->post_ok("//$userid:$password@/api/v1/biblios/$biblio_id/items/$item1_id/populate_empty_callnumbers" => json => {} ) + ->status_is(200, 'Item updated') + ->json_is('/items_updated', 1) + ->json_is('/callnumber', 'Some boring read'); + + $t->post_ok("//$userid:$password@/api/v1/biblios/$biblio_id/items/populate_empty_callnumbers" => json => {} ) + ->status_is(200, 'Items updated') + ->json_is('/items_updated', 2) + ->json_is('/callnumber', 'Some boring read'); + + $t->post_ok("//$userid:$password@/api/v1/biblios/$biblio_id/items/populate_empty_callnumbers" => json => {} ) + ->status_is(200, 'Items updated') + ->json_is('/items_updated', 0); + + $t->post_ok("//$userid:$password@/api/v1/biblios/$biblio_id/items/$item1_id/populate_empty_callnumbers" => json => {} ) + ->status_is(200, 'Item updated') + ->json_is('/items_updated', 0); + + $t->post_ok("//$userid:$password@/api/v1/biblios/0/items/$item1_id/populate_empty_callnumbers" => json => {} ) + ->status_is(404, 'Record not found'); + + $schema->storage->txn_rollback; + +}; -- 2.30.2