From 41e6c9e0db3046cb36866196e4a2b8eed204907b Mon Sep 17 00:00:00 2001
From: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Date: Thu, 6 Apr 2023 17:00:21 +0200
Subject: [PATCH] Bug 30708: Duplicate/copy items to an opened train
Content-Type: text/plain; charset=utf-8
Sponsored-by: BULAC - http://www.bulac.fr/
Signed-off-by: BULAC - http://www.bulac.fr/
Signed-off-by: Heather Hernandez <heather_hernandez@nps.gov>
Signed-off-by: Laurence Rault <laurence.rault@biblibre.com>
Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
---
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 88e95aa0df..572516d670 100644
--- a/api/v1/swagger/swagger.yaml
+++ b/api/v1/swagger/swagger.yaml
@@ -331,6 +331,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.30.2