Bugzilla – Attachment 183069 Details for
Bug 35722
Item transfer operations should be available from the API
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 35722: Create item transfer REST api
0001-Bug-35722-Create-item-transfer-REST-api.patch (text/plain), 29.16 KB, created by
Johanna Räisä
on 2025-06-06 11:58:28 UTC
(
hide
)
Description:
Bug 35722: Create item transfer REST api
Filename:
MIME Type:
Creator:
Johanna Räisä
Created:
2025-06-06 11:58:28 UTC
Size:
29.16 KB
patch
obsolete
>From 342a6dfdf22e9959b84272b8e9d7086c40f26e8b Mon Sep 17 00:00:00 2001 >From: =?UTF-8?q?Johanna=20R=C3=A4is=C3=A4?= <johanna.raisa@gmail.com> >Date: Wed, 4 Jun 2025 09:09:00 +0300 >Subject: [PATCH] Bug 35722: Create item transfer REST api > >This patch creates a REST API for item transfers, allowing users to >transfer items between libraries via the API. > >Test Plan: >1. Apply the patch. >2. perl build-resources.PL >3. prove t/db_dependent/api/v1/items/item_transfers.t >4. Verify that the API endpoints for item transfers work as expected. > >Sponsored-by: Koha-Suomi Oy >--- > Koha/Item/Transfer.pm | 23 ++ > Koha/REST/V1/Items/Transfers.pm | 185 +++++++++++ > api/v1/swagger/definitions/item_transfer.yaml | 70 +++++ > api/v1/swagger/paths/items.yaml | 259 ++++++++++++++++ > api/v1/swagger/swagger.yaml | 6 + > t/db_dependent/api/v1/items/item_transfers.t | 288 ++++++++++++++++++ > 6 files changed, 831 insertions(+) > create mode 100644 Koha/REST/V1/Items/Transfers.pm > create mode 100644 api/v1/swagger/definitions/item_transfer.yaml > create mode 100755 t/db_dependent/api/v1/items/item_transfers.t > >diff --git a/Koha/Item/Transfer.pm b/Koha/Item/Transfer.pm >index 1cacb68475..61cef69901 100644 >--- a/Koha/Item/Transfer.pm >+++ b/Koha/Item/Transfer.pm >@@ -216,6 +216,29 @@ sub strings_map { > }; > } > >+=head3 to_api_mapping >+ >+This method returns the mapping for representing a Koha::Item::Transfer object >+on the API. >+ >+=cut >+ >+sub to_api_mapping { >+ return { >+ branchtransfer_id => 'library_transfer_id', >+ itemnumber => 'item_id', >+ frombranch => 'from_library_id', >+ tobranch => 'to_library_id', >+ daterequested => 'date_requested', >+ datesent => 'date_sent', >+ datecancelled => 'date_cancelled', >+ datearrived => 'date_arrived', >+ reason => 'reason', >+ cancellation_reason => 'cancellation_reason', >+ comments => 'comments', >+ }; >+} >+ > =head3 type > > =cut >diff --git a/Koha/REST/V1/Items/Transfers.pm b/Koha/REST/V1/Items/Transfers.pm >new file mode 100644 >index 0000000000..77bebf0854 >--- /dev/null >+++ b/Koha/REST/V1/Items/Transfers.pm >@@ -0,0 +1,185 @@ >+package Koha::REST::V1::Items::Transfers; >+ >+# 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 <http://www.gnu.org/licenses>. >+ >+use Modern::Perl; >+ >+use Mojo::Base 'Mojolicious::Controller'; >+use Koha::Item::Transfers; >+use Koha::Libraries; >+use Koha::Items; >+use Koha::Patrons; >+use Koha::Recalls; >+use C4::Context; >+ >+use Koha::Exceptions::Item::Transfer; >+ >+use Scalar::Util qw( blessed ); >+ >+use Try::Tiny qw( catch try ); >+ >+=head1 NAME >+ >+Koha::REST::V1::Items::Transfers - Koha REST API for handling item transfers (V1) >+ >+=head1 API >+ >+=head2 Methods >+ >+=cut >+ >+=head3 list >+ >+Controller function that handles listing Koha::Item::Transfers objects >+ >+=cut >+ >+sub list { >+ my $c = shift->openapi->valid_input or return; >+ >+ return try { >+ my $transfers = $c->objects->search( Koha::Item::Transfers->new ); >+ return $c->render( status => 200, openapi => $transfers ); >+ } catch { >+ $c->unhandled_exception($_); >+ }; >+} >+ >+=head3 get >+ >+Controller function that handles retrieving a single Koha::Item::Transfer >+ >+=cut >+ >+sub get { >+ my $c = shift->openapi->valid_input or return; >+ >+ my $item_id = $c->param('item_id'); >+ my $item = Koha::Items->find($item_id); >+ >+ return $c->render_resource_not_found("Item") >+ unless $item; >+ try { >+ my $transfer = $item->transfer; >+ >+ return $c->render( >+ status => 404, >+ openapi => { error => "Item is not in transfer" }, >+ ) unless $transfer; >+ >+ return $c->render( >+ status => 200, >+ openapi => $c->objects->to_api($transfer), >+ ); >+ } catch { >+ $c->unhandled_exception($_); >+ }; >+} >+ >+=head3 add >+ >+Controller function that handles adding a new transfer >+ >+=cut >+ >+sub add { >+ my $c = shift->openapi->valid_input or return; >+ >+ my $item_id = $c->param('item_id'); >+ my $item = Koha::Items->find($item_id); >+ my $ignore_limits = $c->param('ignore_limits'); >+ my $enqueue = $c->param('enqueue'); >+ my $replace = $c->param('replace'); >+ >+ return $c->render_resource_not_found("Item") >+ unless $item; >+ >+ my $body = $c->req->json; >+ >+ my $to_library = Koha::Libraries->find( $body->{to_library_id} ); >+ >+ return $c->render_resource_not_found("Library") >+ unless $to_library; >+ return try { >+ my $params = { >+ to => $to_library, >+ reason => $body->{reason}, >+ comment => $body->{comments}, >+ }; >+ $params->{ignore_limits} = $ignore_limits if defined $ignore_limits; >+ $params->{enqueue} = $enqueue if defined $enqueue; >+ $params->{replace} = $replace if defined $replace; >+ >+ my $transfer = $item->request_transfer($params); >+ $transfer->transit; >+ return $c->render( >+ status => 201, >+ openapi => $c->objects->to_api($transfer), >+ ); >+ } catch { >+ if ( blessed $_ && $_->isa('Koha::Exceptions::Item::Transfer::InQueue') ) { >+ return $c->render( >+ status => 409, >+ openapi => { error => "Item is already in transfer queue" }, >+ ); >+ } >+ $c->unhandled_exception($_); >+ }; >+} >+ >+=head3 cancel >+ >+Controller function that handles deleting a transfer >+ >+=cut >+ >+sub cancel { >+ my $c = shift->openapi->valid_input or return; >+ >+ my $item_id = $c->param('item_id'); >+ my $item = Koha::Items->find($item_id); >+ >+ return $c->render_resource_not_found("Item") >+ unless $item; >+ >+ my $reason = $c->param('reason') || "Manual"; >+ >+ return try { >+ my $transfer = $item->transfer; >+ return $c->render( >+ status => 404, >+ openapi => { error => "Item is not in transfer" }, >+ ) unless $transfer; >+ >+ $transfer->cancel( >+ { >+ reason => $reason, >+ force => 1, >+ } >+ ); >+ if ( C4::Context->preference('UseRecalls') ) { >+ my $recall_transfer_deleted = Koha::Recalls->find( { item_id => $item_id, status => 'in_transit' } ); >+ if ( defined $recall_transfer_deleted ) { >+ $recall_transfer_deleted->revert_transfer; >+ } >+ } >+ return $c->render_resource_deleted; >+ } catch { >+ $c->unhandled_exception($_); >+ }; >+} >+ >+1; >diff --git a/api/v1/swagger/definitions/item_transfer.yaml b/api/v1/swagger/definitions/item_transfer.yaml >new file mode 100644 >index 0000000000..2b8af5d35d >--- /dev/null >+++ b/api/v1/swagger/definitions/item_transfer.yaml >@@ -0,0 +1,70 @@ >+--- >+type: object >+properties: >+ library_transfer_id: >+ type: integer >+ description: Internal transfer limit identifier >+ item_id: >+ type: integer >+ description: Internal item identifier >+ date_requested: >+ type: >+ - string >+ - 'null' >+ format: date-time >+ description: Date when the transfer was requested >+ date_sent: >+ type: >+ - string >+ - 'null' >+ format: date-time >+ description: Date when the transfer was sent >+ to_library_id: >+ type: string >+ description: Internal library id for which library the item is going to >+ date_arrived: >+ type: >+ - string >+ - 'null' >+ format: date-time >+ description: Date when the transfer arrived at the destination library >+ date_cancelled: >+ type: >+ - string >+ - 'null' >+ format: date-time >+ description: Date when the transfer was cancelled >+ from_library_id: >+ type: string >+ description: Internal library id for which library the item is coming from >+ comments: >+ type: >+ - string >+ - 'null' >+ description: Comments associated with the transfer >+ reason: >+ type: >+ - string >+ description: Reason for the transfer >+ enum: >+ - 'Manual' >+ - 'StockrotationAdvance' >+ - 'StockrotationRepatriation' >+ - 'ReturnToHome' >+ - 'ReturnToHolding' >+ - 'RotatingCollection' >+ - 'Reserve' >+ - 'LostReserve' >+ - 'CancelReserve' >+ - 'TransferCancellation' >+ - 'Recall' >+ - 'RecallCancellation' >+ cancellation_reason: >+ type: >+ - string >+ - 'null' >+ description: Reason for cancelling the transfer >+additionalProperties: false >+required: >+ - to_library_id >+ - reason >diff --git a/api/v1/swagger/paths/items.yaml b/api/v1/swagger/paths/items.yaml >index 5acc18ad28..bb16260fd4 100644 >--- a/api/v1/swagger/paths/items.yaml >+++ b/api/v1/swagger/paths/items.yaml >@@ -473,6 +473,265 @@ > x-koha-authorization: > permissions: > reserveforothers: place_holds >+/items/transfers: >+ get: >+ x-mojo-to: Items::Transfers#list >+ operationId: listItemsTransfers >+ description: This resource returns a list of existing transfers. >+ summary: List transfers >+ tags: >+ - transfers >+ parameters: >+ - $ref: "../swagger.yaml#/parameters/match" >+ - $ref: "../swagger.yaml#/parameters/order_by" >+ - $ref: "../swagger.yaml#/parameters/page" >+ - $ref: "../swagger.yaml#/parameters/per_page" >+ - $ref: "../swagger.yaml#/parameters/q_param" >+ - $ref: "../swagger.yaml#/parameters/q_body" >+ - $ref: "../swagger.yaml#/parameters/request_id_header" >+ produces: >+ - application/json >+ responses: >+ "200": >+ description: A list of transfers >+ schema: >+ type: array >+ items: >+ $ref: "../swagger.yaml#/definitions/item_transfer" >+ "400": >+ description: | >+ Bad request. Possible `error_code` attribute values: >+ >+ * `invalid_query` >+ 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: Resource 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: >+ parameters: manage_transfers >+/items/{item_id}/transfers: >+ get: >+ x-mojo-to: Items::Transfers#get >+ operationId: getItemTransfer >+ description: This resource returns the active transfer for an item. >+ summary: Get active item transfer >+ tags: >+ - transfers >+ parameters: >+ - $ref: "../swagger.yaml#/parameters/item_id_pp" >+ - $ref: "../swagger.yaml#/parameters/match" >+ - $ref: "../swagger.yaml#/parameters/order_by" >+ - $ref: "../swagger.yaml#/parameters/page" >+ - $ref: "../swagger.yaml#/parameters/per_page" >+ - $ref: "../swagger.yaml#/parameters/q_param" >+ - $ref: "../swagger.yaml#/parameters/q_body" >+ - $ref: "../swagger.yaml#/parameters/request_id_header" >+ produces: >+ - application/json >+ responses: >+ "200": >+ description: An active transfer for the item >+ schema: >+ type: object >+ items: >+ $ref: "../swagger.yaml#/definitions/item_transfer" >+ "400": >+ description: | >+ Bad request. Possible `error_code` attribute values: >+ >+ * `invalid_query` >+ 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: Resource 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: >+ parameters: manage_transfers >+ post: >+ x-mojo-to: Items::Transfers#add >+ operationId: addItemTransfer >+ description: This resource creates a new transfer for the specified item. >+ tags: >+ - transfers >+ summary: Add an item transfer >+ parameters: >+ - $ref: "../swagger.yaml#/parameters/item_id_pp" >+ - name: ignore_limits >+ in: query >+ description: | >+ If set to true, the transfer will be created even if it exceeds the transfer limits. >+ required: false >+ type: boolean >+ - name: enqueue >+ in: query >+ description: | >+ Used to queue up the transfer when the existing transfer is found to be in transit. >+ required: false >+ type: boolean >+ - name: replace >+ in: query >+ description: | >+ Used to replace the existing transfer request with your own. Reason for the cancellation of the existing transfer must be provided here. >+ required: false >+ type: string >+ enum: >+ - Manual >+ - StockrotationAdvance >+ - StockrotationRepatriation >+ - ReturnToHome >+ - ReturnToHolding >+ - RotatingCollection >+ - Reserve >+ - LostReserve >+ - CancelReserve >+ - ItemLost >+ - WrongTransfer >+ - RecallCancellation >+ - name: body >+ in: body >+ description: A JSON object containing information about a new transfer >+ required: true >+ schema: >+ $ref: "../swagger.yaml#/definitions/item_transfer" >+ produces: >+ - application/json >+ responses: >+ "201": >+ description: Transfer added >+ schema: >+ $ref: "../swagger.yaml#/definitions/item_transfer" >+ "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: Resource not found >+ schema: >+ $ref: "../swagger.yaml#/definitions/error" >+ "409": >+ description: Conflict in creating resource >+ 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: >+ parameters: manage_transfers >+ delete: >+ x-mojo-to: Items::Transfers#cancel >+ operationId: cancelItemTransfer >+ description: This resource cancels the active transfer for the specified item. >+ tags: >+ - transfers >+ summary: Cancel an item transfer >+ parameters: >+ - $ref: "../swagger.yaml#/parameters/item_id_pp" >+ - name: reason >+ in: query >+ description: | >+ Reason for cancelling the transfer. If not provided, the transfer will be cancelled with the default reason. >+ required: false >+ type: string >+ default: "Manual" >+ produces: >+ - application/json >+ responses: >+ "204": >+ description: Transfer cancelled >+ "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: Resource not found >+ schema: >+ $ref: "../swagger.yaml#/definitions/error" >+ "409": >+ description: Conflict in creating resource >+ 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: >+ parameters: manage_transfers > "/public/items": > get: > x-mojo-to: Items#list_public >diff --git a/api/v1/swagger/swagger.yaml b/api/v1/swagger/swagger.yaml >index fb36579cea..604235dad1 100644 >--- a/api/v1/swagger/swagger.yaml >+++ b/api/v1/swagger/swagger.yaml >@@ -126,6 +126,8 @@ definitions: > $ref: ./definitions/invoice.yaml > item: > $ref: ./definitions/item.yaml >+ item_transfer: >+ $ref: ./definitions/item_transfer.yaml > item_group: > $ref: ./definitions/item_group.yaml > item_type: >@@ -445,6 +447,10 @@ paths: > $ref: ./paths/items.yaml#/~1items~1{item_id}~1bundled_items~1{bundled_item_id} > "/items/{item_id}/pickup_locations": > $ref: "./paths/items.yaml#/~1items~1{item_id}~1pickup_locations" >+ "/items/transfers": >+ $ref: "./paths/items.yaml#/~1items~1transfers" >+ "/items/{item_id}/transfers": >+ $ref: "./paths/items.yaml#/~1items~1{item_id}~1transfers" > /jobs: > $ref: ./paths/jobs.yaml#/~1jobs > "/jobs/{job_id}": >diff --git a/t/db_dependent/api/v1/items/item_transfers.t b/t/db_dependent/api/v1/items/item_transfers.t >new file mode 100755 >index 0000000000..94f8af2280 >--- /dev/null >+++ b/t/db_dependent/api/v1/items/item_transfers.t >@@ -0,0 +1,288 @@ >+#!/usr/bin/env perl >+ >+use Modern::Perl; >+ >+use Test::NoWarnings; >+use Test::More tests => 5; >+use Test::MockModule; >+use Test::Mojo; >+ >+use t::lib::TestBuilder; >+use t::lib::Mocks; >+ >+use Koha::Database; >+ >+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 'list()' => sub { >+ plan tests => 9; >+ >+ $schema->storage->txn_begin; >+ >+ my $library1 = $builder->build_object( { class => 'Koha::Libraries' } ); >+ my $library2 = $builder->build_object( { class => 'Koha::Libraries' } ); >+ my $item = $builder->build_sample_item( >+ { >+ homebranch => $library1->branchcode, >+ holdingbranch => $library1->branchcode, >+ } >+ ); >+ >+ my $transfer = $item->request_transfer( { to => $library2, reason => 'Manual' } ); >+ >+ my $patron = $builder->build_object( >+ { >+ class => 'Koha::Patrons', >+ value => { flags => 1 } >+ } >+ ); >+ >+ my $nonprivilegedpatron = $builder->build_object( >+ { >+ class => 'Koha::Patrons', >+ value => { flags => 0 } >+ } >+ ); >+ >+ my $password = 'thePassword123'; >+ >+ $nonprivilegedpatron->set_password( { password => $password, skip_validation => 1 } ); >+ my $userid = $nonprivilegedpatron->userid; >+ >+ $t->get_ok("//$userid:$password@/api/v1/items/transfers")->status_is(403) >+ ->json_is( '/error' => 'Authorization failure. Missing required permission(s).' ); >+ >+ $patron->set_password( { password => $password, skip_validation => 1 } ); >+ $userid = $patron->userid; >+ >+ $t->get_ok("//$userid:$password@/api/v1/items/transfers")->status_is( 200, 'Transfer list retrieved successfully' ); >+ >+ my $res = $t->tx->res->json; >+ my $json = pop @$res; >+ is( $json->{item_id}, $item->itemnumber, 'Item number matches' ); >+ is( $json->{from_library_id}, $library1->branchcode, 'From library matches' ); >+ is( $json->{to_library_id}, $library2->branchcode, 'To library matches' ); >+ is( $json->{reason}, 'Manual', 'Reason matches' ); >+ >+ $schema->storage->txn_rollback; >+}; >+ >+subtest 'get()' => sub { >+ plan tests => 9; >+ >+ $schema->storage->txn_begin; >+ >+ my $library1 = $builder->build_object( { class => 'Koha::Libraries' } ); >+ my $library2 = $builder->build_object( { class => 'Koha::Libraries' } ); >+ my $item = $builder->build_sample_item( >+ { >+ homebranch => $library1->branchcode, >+ holdingbranch => $library1->branchcode, >+ } >+ ); >+ >+ my $transfer = $item->request_transfer( { to => $library2, reason => 'Manual' } ); >+ >+ my $patron = $builder->build_object( >+ { >+ class => 'Koha::Patrons', >+ value => { flags => 1 } >+ } >+ ); >+ >+ my $nonprivilegedpatron = $builder->build_object( >+ { >+ class => 'Koha::Patrons', >+ value => { flags => 0 } >+ } >+ ); >+ >+ my $password = 'thePassword123'; >+ >+ $nonprivilegedpatron->set_password( { password => $password, skip_validation => 1 } ); >+ my $userid = $nonprivilegedpatron->userid; >+ >+ $t->get_ok("//$userid:$password@/api/v1/items/transfers")->status_is(403) >+ ->json_is( '/error' => 'Authorization failure. Missing required permission(s).' ); >+ >+ $patron->set_password( { password => $password, skip_validation => 1 } ); >+ $userid = $patron->userid; >+ my $itemnumber = $item->itemnumber; >+ $t->get_ok("//$userid:$password@/api/v1/items/$itemnumber/transfers") >+ ->status_is( 200, 'Transfer retrieved successfully' ); >+ >+ my $res = $t->tx->res->json; >+ is( $res->{item_id}, $item->itemnumber, 'Item number matches' ); >+ is( $res->{from_library_id}, $library1->branchcode, 'From library matches' ); >+ is( $res->{to_library_id}, $library2->branchcode, 'To library matches' ); >+ is( $res->{reason}, 'Manual', 'Reason matches' ); >+ >+ $schema->storage->txn_rollback; >+}; >+ >+subtest 'add()' => sub { >+ plan tests => 33; >+ >+ $schema->storage->txn_begin; >+ >+ my $library1 = $builder->build_object( { class => 'Koha::Libraries' } ); >+ my $library2 = $builder->build_object( { class => 'Koha::Libraries' } ); >+ my $library3 = $builder->build_object( { class => 'Koha::Libraries' } ); >+ my $item = $builder->build_sample_item( >+ { >+ homebranch => $library1->branchcode, >+ holdingbranch => $library1->branchcode, >+ } >+ ); >+ >+ my $patron = $builder->build_object( >+ { >+ class => 'Koha::Patrons', >+ value => { flags => 1 } >+ } >+ ); >+ >+ my $nonprivilegedpatron = $builder->build_object( >+ { >+ class => 'Koha::Patrons', >+ value => { flags => 0 } >+ } >+ ); >+ >+ my $password = 'thePassword123'; >+ >+ $nonprivilegedpatron->set_password( { password => $password, skip_validation => 1 } ); >+ my $userid = $nonprivilegedpatron->userid; >+ my $itemnumber = $item->itemnumber; >+ >+ my $params = { >+ to_library_id => $library2->branchcode, >+ reason => 'Manual', >+ }; >+ >+ $t->post_ok( "//$userid:$password@/api/v1/items/$itemnumber/transfers" => json => $params )->status_is(403) >+ ->json_is( '/error' => 'Authorization failure. Missing required permission(s).' ); >+ >+ $patron->set_password( { password => $password, skip_validation => 1 } ); >+ $userid = $patron->userid; >+ >+ $t->post_ok( "//$userid:$password@/api/v1/items/$itemnumber/transfers" => json => $params ) >+ ->status_is( 201, 'Transfer is created successfully' ); >+ >+ my $res = $t->tx->res->json; >+ is( $res->{item_id}, $item->itemnumber, 'Item number matches' ); >+ is( $res->{from_library_id}, $library1->branchcode, 'From library matches' ); >+ is( $res->{to_library_id}, $library2->branchcode, 'To library matches' ); >+ is( $res->{reason}, 'Manual', 'Reason matches' ); >+ >+ # Check transfer with limits >+ my $limit = $builder->build_object( { class => 'Koha::Item::Transfer::Limits' } ); >+ $limit->set( { toBranch => $library3->branchcode, fromBranch => $library1->branchcode } )->store; >+ >+ $params->{to_library_id} = $library3->branchcode; >+ $t->post_ok( "//$userid:$password@/api/v1/items/$itemnumber/transfers" => json => $params ) >+ ->status_is( 409, 'Transfer is not created due to transfer limit' ); >+ >+ $t->post_ok( >+ "//$userid:$password@/api/v1/items/$itemnumber/transfers?ignore_limits=1&enqueue=1" => json => $params ) >+ ->status_is( 201, 'Transfer is created successfully ignoring transfer limit' ); >+ >+ # Duplicate transfer >+ $params->{to_library_id} = $library1->branchcode; >+ $params->{comments} = 'Test comment'; >+ >+ $t->post_ok( "//$userid:$password@/api/v1/items/$itemnumber/transfers" => json => $params )->status_is(409) >+ ->json_is( '/error' => 'Item is already in transfer queue' ); >+ >+ # Add to enqueue even if already in transfer queue >+ $t->post_ok( "//$userid:$password@/api/v1/items/$itemnumber/transfers?enqueue=1" => json => $params ) >+ ->status_is( 201, 'Transfer is created successfully to enqueue up transfer' ); >+ $res = $t->tx->res->json; >+ >+ is( $res->{to_library_id}, $library1->branchcode, 'To library matches' ); >+ is( $res->{reason}, 'Manual', 'Reason matches' ); >+ is( $res->{comments}, 'Test comment', 'Comments matches' ); >+ >+ my $library_transfer_id = $res->{library_transfer_id}; >+ >+ # Add to enqueue with replace >+ $params->{to_library_id} = $library2->branchcode; >+ $params->{reason} = 'Reserve'; >+ $t->post_ok( "//$userid:$password@/api/v1/items/$itemnumber/transfers?replace=WrongTransfer" => json => $params ) >+ ->status_is( 201, 'Transfer is created successfully to replace existing transfer' ); >+ $res = $t->tx->res->json; >+ is( $res->{to_library_id}, $library2->branchcode, 'To library matches after replace' ); >+ is( $res->{reason}, 'Reserve', 'Reason matches after replace' ); >+ >+ # Invalid item number >+ my $invalid_itemnumber = $itemnumber + 1; >+ $t->post_ok( "//$userid:$password@/api/v1/items/$invalid_itemnumber/transfers" => json => $params )->status_is(404) >+ ->json_is( '/error' => 'Item not found' ); >+ >+ # Invalid library >+ $params->{to_library_id} = 'InvalidLibrary'; >+ $t->post_ok( "//$userid:$password@/api/v1/items/$itemnumber/transfers" => json => $params )->status_is(404) >+ ->json_is( '/error' => 'Library not found' ); >+ >+ # Invalid reason >+ $params->{reason} = 'InvalidReason'; >+ $t->post_ok( "//$userid:$password@/api/v1/items/$itemnumber/transfers" => json => $params )->status_is(400); >+ >+ $schema->storage->txn_rollback; >+}; >+ >+subtest 'delete()' => sub { >+ plan tests => 7; >+ >+ $schema->storage->txn_begin; >+ >+ my $library1 = $builder->build_object( { class => 'Koha::Libraries' } ); >+ my $library2 = $builder->build_object( { class => 'Koha::Libraries' } ); >+ my $item = $builder->build_sample_item( >+ { >+ homebranch => $library1->branchcode, >+ holdingbranch => $library1->branchcode, >+ } >+ ); >+ >+ my $transfer = $item->request_transfer( { to => $library2, reason => 'Manual' } ); >+ >+ my $patron = $builder->build_object( >+ { >+ class => 'Koha::Patrons', >+ value => { flags => 1 } >+ } >+ ); >+ >+ my $nonprivilegedpatron = $builder->build_object( >+ { >+ class => 'Koha::Patrons', >+ value => { flags => 0 } >+ } >+ ); >+ >+ my $password = 'thePassword123'; >+ >+ $nonprivilegedpatron->set_password( { password => $password, skip_validation => 1 } ); >+ my $userid = $nonprivilegedpatron->userid; >+ my $itemnumber = $item->itemnumber; >+ $t->delete_ok("//$userid:$password@/api/v1/items/$itemnumber/transfers")->status_is(403) >+ ->json_is( '/error' => 'Authorization failure. Missing required permission(s).' ); >+ >+ is( $item->transfer->branchtransfer_id, $transfer->branchtransfer_id, 'Item transfer exists' ); >+ >+ $patron->set_password( { password => $password, skip_validation => 1 } ); >+ $userid = $patron->userid; >+ >+ $t->delete_ok("//$userid:$password@/api/v1/items/$itemnumber/transfers") >+ ->status_is( 204, 'Transfer deleted successfully' ); >+ >+ is( $item->transfer, undef, 'Item transfer is removed' ); >+ >+ $schema->storage->txn_rollback; >+}; >-- >2.34.1 >
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 35722
: 183069