From 48c2741ea9dc2012bc047ec6f80d2cd50054e0c5 Mon Sep 17 00:00:00 2001 From: Tomas Cohen Arazi Date: Mon, 21 Oct 2024 14:43:55 -0300 Subject: [PATCH] Bug 34784: Add RPC route for updating item callnumbers for a biblio This patch takes the idea implemented on the original patch, moves it to the /rpc namespace to make it clear this is not a RESTful endpoint. Code gets improved a bit, using some patterns we are using nowadays. The return value gets a bit more information and. A big change is it now uses `Koha::Items->batch_update`. It originally used `Koha::Items->update` with the `no_trigger` option. It felt like `batch_update` is a bit more smart in terms of triggering indexing at the end of the update, and only for the host biblio. It looks like that was the spirit of the original implementation, but missed to actually trigger indexing at the end of the call. To test: 1. Apply this patches 2.Run: $ ktd --shell k$ yarn api:bundle k$ restart_all 3. Run the tests: k$ prove t/db_dependent/api/v1/rpc/biblios.t => SUCCESS: Tests pass! 4. Try the new routes with Postman or your favourite tool (if they don't match) => SUCCESS: The desired behavior is what you get. 5. Sign off :-D Signed-off-by: Tomas Cohen Arazi --- Koha/REST/V1/RPC/Biblios.pm | 114 ++++++++++++++++++++++++++ t/db_dependent/api/v1/rpc/biblios.t | 121 ++++++++++++++++++++++++++++ 2 files changed, 235 insertions(+) create mode 100644 Koha/REST/V1/RPC/Biblios.pm create mode 100755 t/db_dependent/api/v1/rpc/biblios.t diff --git a/Koha/REST/V1/RPC/Biblios.pm b/Koha/REST/V1/RPC/Biblios.pm new file mode 100644 index 00000000000..b5ed5eee13d --- /dev/null +++ b/Koha/REST/V1/RPC/Biblios.pm @@ -0,0 +1,114 @@ +package Koha::REST::V1::RPC::Biblios; + +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Koha is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +use Modern::Perl; + +use Mojo::Base 'Mojolicious::Controller'; + +use Try::Tiny qw( catch try ); + +=head1 API + +=head2 Methods + +=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') ); + + return $c->render_resource_not_found("Bibliographic record") + unless $biblio; + + my $items = $biblio->items->search( + { + -or => [ + itemcallnumber => undef, + itemcallnumber => q{}, + ] + } + ); + + $items = $items->search( { itemnumber => $c->param('item_id') } ) + if $c->param('item_id'); + + return try { + + my $cn_fields = C4::Context->preference('itemcallnumber'); + return $c->render( + status => 409, + openapi => { + error => "Callnumber fields not found", + error_code => 'missing_configuration', + } + ) 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 => 409, + openapi => { + error => "Callnumber empty in bibliographic record", + error_code => 'callnumber_empty', + } + ) unless $callnumber; + + return $c->render( + status => 200, + openapi => { + updated_items_count => 0, + callnumber => $callnumber + }, + ) unless $items->count; + + my ($res) = $items->batch_update( { new_values => { itemcallnumber => $callnumber } } ); + my @modified_itemnumbers = @{ $res->{modified_itemnumbers} }; + + return $c->render( + status => 200, + openapi => { + updated_items_count => scalar @modified_itemnumbers, + callnumber => $callnumber, + modified_item_ids => \@modified_itemnumbers, + }, + ); + } catch { + $c->unhandled_exception($_); + }; +} + +1; diff --git a/t/db_dependent/api/v1/rpc/biblios.t b/t/db_dependent/api/v1/rpc/biblios.t new file mode 100755 index 00000000000..8d91f54cb62 --- /dev/null +++ b/t/db_dependent/api/v1/rpc/biblios.t @@ -0,0 +1,121 @@ +#!/usr/bin/env perl + +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Koha is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +use Modern::Perl; + +use Test::More tests => 1; +use Test::Mojo; + +use C4::Biblio qw{ModBiblio}; + +use t::lib::Mocks; +use t::lib::TestBuilder; + +my $schema = Koha::Database->new->schema; +my $builder = t::lib::TestBuilder->new; + +t::lib::Mocks::mock_preference( 'RESTBasicAuth', 1 ); + +my $t = Test::Mojo->new('Koha::REST::V1'); + +subtest 'populate_empty_callnumbers() tests' => sub { + + plan tests => 30; + + $schema->storage->txn_begin; + + my $biblio = $builder->build_sample_biblio(); + my $record = $biblio->record; + + my $cn = q{PA522}; + my $in = q{.M38 1993}; + + $record->insert_fields_ordered( MARC::Field->new( '050', ' ', ' ', a => $cn, b => $in ) ); + + my $expected_callnumber = $cn . $in; + + 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/rpc/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::lib::Mocks::mock_preference( 'itemcallnumber', '' ); + + $t->post_ok( "//$userid:$password@/api/v1/rpc/biblios/$biblio_id/items/populate_empty_callnumbers" => json => {} ) + ->status_is( 409, 'Wrong configuration' )->json_is( '/error_code', q{missing_configuration} ); + + t::lib::Mocks::mock_preference( 'itemcallnumber', '050ab' ); + + $t->post_ok( "//$userid:$password@/api/v1/rpc/biblios/$biblio_id/items/populate_empty_callnumbers" => json => {} ) + ->status_is( 409, 'Callnumber empty' )->json_is( '/error_code', q{callnumber_empty} ); + + ModBiblio( $record, $biblio_id ); + + $t->post_ok( "//$userid:$password@/api/v1/rpc/biblios/$biblio_id/items/populate_empty_callnumbers" => json => {} ) + ->status_is( 200, 'No items but request successful' )->json_is( '/updated_items_count', 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/rpc/biblios/$biblio_id/items/$item1_id/populate_empty_callnumbers" => json => {} ) + ->status_is( 200, 'Item updated' )->json_is( '/updated_items_count', 1 ) + ->json_is( '/callnumber', $expected_callnumber ); + + my @items_to_update = ( $item_2->id, $item_3->id ); + + $t->post_ok( "//$userid:$password@/api/v1/rpc/biblios/$biblio_id/items/populate_empty_callnumbers" => json => {} ) + ->status_is( 200, 'Items updated' )->json_is( '/updated_items_count', 2 ) + ->json_is( '/callnumber', $expected_callnumber )->json_is( '/modified_item_ids', \@items_to_update ); + + $t->post_ok( "//$userid:$password@/api/v1/rpc/biblios/$biblio_id/items/populate_empty_callnumbers" => json => {} ) + ->status_is( 200, 'Items updated' )->json_is( '/updated_items_count', 0 ); + + $t->post_ok( + "//$userid:$password@/api/v1/rpc/biblios/$biblio_id/items/$item1_id/populate_empty_callnumbers" => json => {} ) + ->status_is( 200, 'Item updated' )->json_is( '/updated_items_count', 0 ); + + $biblio->delete; + + $t->post_ok( "//$userid:$password@/api/v1/rpc/biblios/$biblio_id/items/populate_empty_callnumbers" => json => {} ) + ->status_is( 404, 'Record not found' )->json_is( '/error' => q{Bibliographic record not found} ) + ->json_is( '/error_code' => q{not_found} ); + + $schema->storage->txn_rollback; +}; -- 2.47.0