Bug 40395: Preparation: suspension_bulk REST API endpoint

Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit ad66f7d7d3)
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
This commit is contained in:
Pedro Amorim 2025-07-15 12:36:09 +00:00 committed by Paul Derscheid
parent 52465feb11
commit 2124549447
No known key found for this signature in database
GPG Key ID: C25F9FD44CED595B
4 changed files with 195 additions and 1 deletions

View File

@ -378,6 +378,53 @@ sub suspend {
};
}
=head3 suspend_bulk
Method that handles suspending multiple holds
=cut
sub suspend_bulk {
my $c = shift->openapi->valid_input or return;
my $body = $c->req->json;
my $end_date = ($body) ? $body->{end_date} : undef;
my $hold_ids = ($body) ? $body->{hold_ids} : undef;
return $c->render_resource_not_found("Hold")
unless $hold_ids;
return try {
Koha::Database->new->schema->txn_do(
sub {
foreach my $hold_id (@$hold_ids) {
my $hold = Koha::Holds->find($hold_id);
return $c->render_resource_not_found( "Hold", "id", $hold_id )
unless $hold;
$hold->suspend_hold($end_date);
$hold->discard_changes;
}
$c->res->headers->location( $c->req->url->to_string );
return $c->render(
status => 201,
openapi => {
hold_ids => $hold_ids,
end_date => $end_date,
}
);
}
);
} catch {
if ( blessed $_ and $_->isa('Koha::Exceptions::Hold::CannotSuspendFound') ) {
return $c->render( status => 400, openapi => { error => "$_" } );
}
$c->unhandled_exception($_);
};
}
=head3 resume
Method that handles resuming a hold

View File

@ -280,6 +280,67 @@
x-koha-authorization:
permissions:
reserveforothers: "1"
"/holds/suspension_bulk":
post:
x-mojo-to: Holds#suspend_bulk
operationId: suspendHoldBulk
tags:
- holds
summary: Suspend the holds
parameters:
- name: body
in: body
description: Hold ids
required: true
schema:
type: object
properties:
end_date:
description: Date the hold suspension expires
type: string
format: date
hold_ids:
type: array
items:
type: integer
additionalProperties: false
consumes:
- application/json
produces:
- application/json
responses:
"201":
description: Holds suspended
"400":
description: Bad request
schema:
$ref: "../swagger.yaml#/definitions/error"
"401":
description: Authentication required
schema:
$ref: "../swagger.yaml#/definitions/error"
"403":
description: Hold not allowed
schema:
$ref: "../swagger.yaml#/definitions/error"
"404":
description: Hold not found
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:
reserveforothers: place_holds
"/holds/{hold_id}":
patch:
x-mojo-to: Holds#edit

View File

@ -403,6 +403,8 @@ paths:
$ref: ./paths/extended_attribute_types.yaml#/~1extended_attribute_types
/holds:
$ref: ./paths/holds.yaml#/~1holds
"/holds/suspension_bulk":
$ref: "./paths/holds.yaml#/~1holds~1suspension_bulk"
"/holds/{hold_id}":
$ref: "./paths/holds.yaml#/~1holds~1{hold_id}"
"/holds/{hold_id}/pickup_location":

View File

@ -17,7 +17,7 @@
use Modern::Perl;
use Test::More tests => 17;
use Test::More tests => 18;
use Test::NoWarnings;
use Test::MockModule;
use Test::Mojo;
@ -523,6 +523,90 @@ subtest 'suspend and resume tests' => sub {
$schema->storage->txn_rollback;
};
subtest 'suspend bulk tests' => sub {
plan tests => 16;
$schema->storage->txn_begin;
my $password = 'AbcdEFG123';
my $patron = $builder->build_object( { class => 'Koha::Patrons', value => { userid => 'tomasito', flags => 0 } } );
$builder->build(
{
source => 'UserPermission',
value => {
borrowernumber => $patron->borrowernumber,
module_bit => 6,
code => 'place_holds',
},
}
);
$patron->set_password( { password => $password, skip_validation => 1 } );
my $userid = $patron->userid;
# Disable logging
t::lib::Mocks::mock_preference( 'HoldsLog', 0 );
t::lib::Mocks::mock_preference( 'RESTBasicAuth', 1 );
my $hold = $builder->build_object(
{
class => 'Koha::Holds',
value => { suspend => 0, suspend_until => undef, waitingdate => undef, found => undef }
}
);
my $hold_2 = $builder->build_object(
{
class => 'Koha::Holds',
value => { suspend => 0, suspend_until => undef, waitingdate => undef, found => undef }
}
);
ok( !$hold->is_suspended, 'Hold is not suspended' );
ok( !$hold_2->is_suspended, 'Hold is not suspended' );
$t->post_ok(
"//$userid:$password@/api/v1/holds/suspension_bulk" => json => { hold_ids => [ $hold->id, $hold_2->id ] } )
->status_is( 201, 'Hold bulk suspension created' );
$hold->discard_changes;
$hold_2->discard_changes;
ok( $hold->is_suspended, 'Hold is suspended' );
ok( $hold_2->is_suspended, 'Hold is suspended' );
$hold->resume;
$hold_2->resume;
ok( !$hold->is_suspended, 'Hold is not suspended' );
ok( !$hold_2->is_suspended, 'Hold is not suspended' );
$t->post_ok( "//$userid:$password@/api/v1/holds/suspension_bulk" => json =>
{ end_date => "2024-07-30", hold_ids => [ $hold->id, $hold_2->id ] } )
->status_is( 201, 'Hold bulk suspension created' )
->json_is( { end_date => "2024-07-30", hold_ids => [ $hold->id, $hold_2->id ] } );
$hold->discard_changes;
$hold_2->discard_changes;
$hold_2->delete;
$hold->resume;
ok( !$hold->is_suspended, 'Hold is not suspended' );
$t->post_ok( "//$userid:$password@/api/v1/holds/suspension_bulk" => json => { hold_ids => [ $hold_2->id ] } )
->status_is( 404, 'Hold bulk suspension failed. Hold not found' );
ok( !$hold->is_suspended, 'Hold is not suspended. Bulk suspension failed' );
$hold->discard_changes;
ok( !$hold->is_suspended, 'Hold is not suspended. Bulk suspension failed' );
$schema->storage->txn_rollback;
};
subtest 'PUT /holds/{hold_id}/priority tests' => sub {
plan tests => 14;