From eae94108e7ef8ca91deadb881f0722e360be2b7f Mon Sep 17 00:00:00 2001 From: Jonathan Druart Date: Thu, 6 Apr 2023 17:00:21 +0200 Subject: [PATCH] Bug 30708: Duplicate/copy items to an opened train Sponsored-by: BULAC - http://www.bulac.fr/ Signed-off-by: BULAC - http://www.bulac.fr/ Signed-off-by: Heather Hernandez Signed-off-by: Laurence Rault Signed-off-by: Marcel de Rooy Signed-off-by: Tomas Cohen Arazi --- Koha/Preservation/Train.pm | 21 +++-- Koha/REST/V1/Preservation/Trains.pm | 92 +++++++++++++++++++ api/v1/swagger/paths/preservation_trains.yaml | 75 +++++++++++++++ api/v1/swagger/swagger.yaml | 2 + .../components/Preservation/TrainsShow.vue | 38 +++----- .../js/vue/fetch/preservation-api-client.js | 5 + t/db_dependent/Koha/Preservation/Trains.t | 8 +- 7 files changed, 206 insertions(+), 35 deletions(-) diff --git a/Koha/Preservation/Train.pm b/Koha/Preservation/Train.pm index dadb1dd13d..b87f0eb878 100644 --- a/Koha/Preservation/Train.pm +++ b/Koha/Preservation/Train.pm @@ -56,35 +56,42 @@ sub default_processing { Add item to this train -my $train_item = $train->add_item({item_id => $itemnumber, processing_id => $processing_id}); -my $train_item = $train->add_item({barcode => $barcode, processing_id => $processing_id}); +my $train_item = $train->add_item({item_id => $itemnumber, processing_id => $processing_id}, { skip_waiting_list_check => 0|1 }); +my $train_item = $train->add_item({barcode => $barcode, processing_id => $processing_id, { skip_waiting_list_check => 0|1 }); + +skip_waitin_list_check can be set to true if the item can be added to the train even if the item is not in the waiting list. =cut sub add_item { - my ( $self, $train_item ) = @_; + my ( $self, $train_item, $params ) = @_; + + my $skip_waiting_list_check = $params->{skip_waiting_list_check} || 0; my $not_for_loan = C4::Context->preference('PreservationNotForLoanWaitingListIn'); my $key = exists $train_item->{item_id} ? 'itemnumber' : 'barcode'; my $item = Koha::Items->find( { $key => $train_item->{item_id} || $train_item->{barcode} } ); Koha::Exceptions::Preservation::ItemNotFound->throw unless $item; - Koha::Exceptions::Preservation::ItemNotInWaitingList->throw if $item->notforloan != $not_for_loan; + + Koha::Exceptions::Preservation::ItemNotInWaitingList->throw + if !$skip_waiting_list_check && $item->notforloan != $not_for_loan; # FIXME We need a LOCK here # Not important for now as we have add_items # Note that there are several other places in Koha with this max+1 problem my $max = $self->items->search->_resultset->get_column("user_train_item_id")->max || 0; - my $train_item_rs = $self->_result->add_to_preservation_trains_items( + my $train_item_object = Koha::Preservation::Train::Item->new( { + train_id => $self->train_id, item_id => $item->itemnumber, processing_id => $train_item->{processing_id} || $self->default_processing_id, user_train_item_id => $max + 1, added_on => \'NOW()', } - ); + )->store; $item->notforloan( $self->not_for_loan )->store; - return Koha::Preservation::Train::Item->_new_from_dbic($train_item_rs); + return $train_item_object->get_from_storage; } =head3 add_items diff --git a/Koha/REST/V1/Preservation/Trains.pm b/Koha/REST/V1/Preservation/Trains.pm index 644a7a9721..cb70d2f74b 100644 --- a/Koha/REST/V1/Preservation/Trains.pm +++ b/Koha/REST/V1/Preservation/Trains.pm @@ -389,6 +389,98 @@ sub add_item { }; } +=head3 copy_item + +=cut + +sub copy_item { + my $c = shift->openapi->valid_input or return; + + my $train_id = $c->validation->param('train_id'); + my $train = Koha::Preservation::Trains->find( $train_id ); + + unless ($train) { + return $c->render( + status => 404, + openapi => { error => "Train not found" } + ); + } + + my $train_item_id = $c->validation->param('train_item_id'); + + my $train_item = Koha::Preservation::Train::Items->search({ train_item_id => $train_item_id, train_id => $train_id })->single; + + unless ($train_item) { + return $c->render( + status => 404, + openapi => { error => "Item not found" } + ); + } + + my $body = $c->validation->param('body'); + return try { + Koha::Database->new->schema->txn_do( + sub { + my $new_train_id = delete $body->{train_id}; + my $new_train = Koha::Preservation::Trains->find( $new_train_id ); + unless ($train) { + return $c->render( + status => 404, + openapi => { error => "Train not found" } + ); + } + my $new_train_item = $new_train->add_item( + { + item_id => $train_item->item_id, + processing_id => $train_item->processing_id + }, + { + skip_waiting_list_check => 1, + }, + ); + my $attributes = [ + map { + { + processing_attribute_id => $_->processing_attribute_id, + train_item_id => $new_train_item->train_item_id, + value => $_->value + } + } $train_item->attributes->as_list + ]; + $new_train_item->attributes($attributes); + return $c->render( status => 201, openapi => $train_item ); + } + ); + } + catch { + if ( blessed $_ ) { + if ( $_->isa('Koha::Exceptions::Preservation::MissingSettings') ) { + return $c->render( + status => 400, + openapi => { error => "MissingSettings", parameter => $_->parameter } + ); + } elsif ( $_->isa('Koha::Exceptions::Preservation::ItemNotFound') ) { + return $c->render( + status => 404, + openapi => { error => "Item not found" } + ); + } elsif ( $_->isa('Koha::Exceptions::Object::DuplicateID') ) { + return $c->render( + status => 409, + openapi => { error => $_->error, conflict => $_->duplicate_id } + ); + } elsif ( $_->isa('Koha::Exceptions::Preservation::ItemNotInWaitingList') ) { + return $c->render( + status => 400, + openapi => { error => 'Item not in waiting list' } + ); + } + } + + $c->unhandled_exception($_); + }; +} + =head3 update_item Controller function that handles updating an item from a train diff --git a/api/v1/swagger/paths/preservation_trains.yaml b/api/v1/swagger/paths/preservation_trains.yaml index e7fc5c90f8..dd4058e475 100644 --- a/api/v1/swagger/paths/preservation_trains.yaml +++ b/api/v1/swagger/paths/preservation_trains.yaml @@ -619,3 +619,78 @@ x-koha-authorization: permissions: preservation: 1 + +"/preservation/trains/{train_id}/items/{train_item_id}/copy": + post: + x-mojo-to: Preservation::Trains#copy_item + operationId: copyItemToAnotherTrain + tags: + - preservation_train + summary: Copy an item to an other train + consumes: + - application/json + produces: + - application/json + parameters: + - $ref: "../swagger.yaml#/parameters/preservation_train_id_pp" + - $ref: "../swagger.yaml#/parameters/preservation_train_item_id_pp" + - description: The train_id of the new train + in: body + name: body + required: true + schema: + type: object + responses: + 201: + description: A successfully copied item + schema: + type: object + 400: + description: Bad parameter + 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: Ressource not found + schema: + $ref: "../swagger.yaml#/definitions/error" + 409: + description: Conflict in creating resource + schema: + $ref: "../swagger.yaml#/definitions/error" + 413: + description: Payload too large + 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: + preservation: 1 + 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: + preservation: 1 diff --git a/api/v1/swagger/swagger.yaml b/api/v1/swagger/swagger.yaml index 421274260d..b96c3928ef 100644 --- a/api/v1/swagger/swagger.yaml +++ b/api/v1/swagger/swagger.yaml @@ -347,6 +347,8 @@ paths: $ref: "./paths/preservation_trains.yaml#/~1preservation~1trains~1{train_id}~1items~1batch" "/preservation/trains/{train_id}/items/{train_item_id}": $ref: "./paths/preservation_trains.yaml#/~1preservation~1trains~1{train_id}~1items~1{train_item_id}" + "/preservation/trains/{train_id}/items/{train_item_id}/copy": + $ref: "./paths/preservation_trains.yaml#/~1preservation~1trains~1{train_id}~1items~1{train_item_id}~1copy" /preservation/processings: $ref: ./paths/preservation_processings.yaml#/~1preservation~1processings "/preservation/processings/{processing_id}": diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/components/Preservation/TrainsShow.vue b/koha-tmpl/intranet-tmpl/prog/js/vue/components/Preservation/TrainsShow.vue index 978b08986c..6d522f88da 100644 --- a/koha-tmpl/intranet-tmpl/prog/js/vue/components/Preservation/TrainsShow.vue +++ b/koha-tmpl/intranet-tmpl/prog/js/vue/components/Preservation/TrainsShow.vue @@ -410,34 +410,18 @@ export default { copyItem(event) { event.preventDefault() const client = APIClient.preservation - let new_train_item = {} client.train_items - .get(this.train.train_id, this.train_item_id_to_copy) - .then(train_item => { - new_train_item = { - attributes: train_item.attributes.map(attr => { - return { - processing_attribute_id: - attr.processing_attribute_id, - value: attr.value, - } - }), - item_id: train_item.item_id, - processing_id: train_item.processing_id, - } - }) - .then(() => - client.train_items - .create(new_train_item, this.train_id_selected_for_copy) - .then( - success => { - this.setMessage( - this.$__("Item copied successfully.") - ) - this.show_modal = false - }, - error => {} - ) + .copy( + this.train_id_selected_for_copy, + this.train.train_id, + this.train_item_id_to_copy + ) + .then( + success => { + this.setMessage(this.$__("Item copied successfully.")) + this.show_modal = false + }, + error => {} ) }, build_datatable: function () { diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/fetch/preservation-api-client.js b/koha-tmpl/intranet-tmpl/prog/js/vue/fetch/preservation-api-client.js index 455a80be5e..356adfac4b 100644 --- a/koha-tmpl/intranet-tmpl/prog/js/vue/fetch/preservation-api-client.js +++ b/koha-tmpl/intranet-tmpl/prog/js/vue/fetch/preservation-api-client.js @@ -121,6 +121,11 @@ export class PreservationAPIClient extends HttpClient { endpoint: "trains/" + train_id + "/items/batch", body: train_items, }), + copy: (new_train_id, train_id, id) => + this.post({ + endpoint: "trains/" + train_id + "/items/" + id + "/copy", + body: { train_id: new_train_id }, + }), update: (train_item, train_id, id) => this.put({ endpoint: "trains/" + train_id + "/items/" + id, diff --git a/t/db_dependent/Koha/Preservation/Trains.t b/t/db_dependent/Koha/Preservation/Trains.t index 9fd1ea2c3c..7bf7398ae4 100755 --- a/t/db_dependent/Koha/Preservation/Trains.t +++ b/t/db_dependent/Koha/Preservation/Trains.t @@ -52,7 +52,7 @@ subtest 'default_processing' => sub { }; subtest 'add_items & items' => sub { - plan tests => 9; + plan tests => 12; $schema->storage->txn_begin; @@ -98,5 +98,11 @@ subtest 'add_items & items' => sub { is( $item_2->get_from_storage->notforloan, 0 ); is( $item_3->get_from_storage->notforloan, $not_for_loan_train_in ); + warning_is { + $train->add_item( { item_id => $item_2->itemnumber }, { skip_waiting_list_check => 1 } ); + } ''; + is( $train->items->count, 3, 'the item has been added to the train' ); + is( $item_2->get_from_storage->notforloan, $not_for_loan_train_in ); + $schema->storage->txn_rollback; }; -- 2.39.5