@@ -, +, @@ - item on loan, - item reserved, - item withdrawn, - item lost, - item restricted, - item or itemtype not for loan - item damaged (depending on AllowHoldsOnDamagedItems), - available (bool) - availability_description ((array of strings) reasons for possible restrictions to availability, such as ["notforloan"]) - hold_queue_length (int) - expected_available ((string) due date if onloan) YYY is an existing itemnumber. You can also try with biblionumber=XXX query parameter, where XXX is an existing biblionumber. description above. Also make sure reasons for unavailability are in availability_description. - t/Koha/Item/Availability.t - t/db_dependent/Items.t - t/db_dependent/api/v1/availability.t --- Koha/Item.pm | 97 ++++++++++++++++ Koha/Item/Availability.pm | 198 +++++++++++++++++++++++++++++++++ Koha/REST/V1/Availability.pm | 88 +++++++++++++++ api/v1/definitions/availabilities.json | 4 + api/v1/definitions/availability.json | 57 ++++++++++ api/v1/definitions/index.json | 2 + api/v1/swagger.json | 50 ++++++++- t/Koha/Item/Availability.t | 42 +++++++ t/db_dependent/Items.t | 83 +++++++++++++- t/db_dependent/api/v1/availability.t | 195 ++++++++++++++++++++++++++++++++ 10 files changed, 813 insertions(+), 3 deletions(-) create mode 100644 Koha/Item/Availability.pm create mode 100644 Koha/REST/V1/Availability.pm create mode 100644 api/v1/definitions/availabilities.json create mode 100644 api/v1/definitions/availability.json create mode 100644 t/Koha/Item/Availability.t create mode 100644 t/db_dependent/api/v1/availability.t --- a/Koha/Item.pm +++ a/Koha/Item.pm @@ -23,6 +23,13 @@ use Carp; use Koha::Database; +use C4::Circulation; +use C4::Context; +use C4::Members; +use C4::Reserves; +use Koha::Holds; +use Koha::Issues; +use Koha::Item::Availability; use Koha::Patrons; use Koha::Libraries; @@ -50,6 +57,80 @@ sub effective_itemtype { return $self->_result()->effective_itemtype(); } +=head3 get_availability + +my $available = $item->get_availability(); + +Gets availability of the item. Note that this subroutine does not check patron +status. Since availability is a broad term, we cannot claim the item is +"available" and cover all the possible cases. Instead, this subroutine simply +checks if the item is not onloan, not reserved, not notforloan, not withdrawn, +not lost or not damaged. + +Returns Koha::Item::Availability object. + +Use $item->is_available() for a simple Boolean value. + +=cut + +sub get_availability { + my ( $self ) = @_; + + my $availability = Koha::Item::Availability->new->set_available; + + if ($self->onloan) { + my $issue = Koha::Issues->search({ itemnumber => $self->itemnumber })->next; + $availability->set_unavailable("onloan", $issue->date_due) if $issue; + } + + $availability->set_unavailable("withdrawn") if $self->withdrawn; + $availability->set_unavailable("itemlost") if $self->itemlost; + $availability->set_unavailable("restricted") if $self->restricted; + + if ($self->damaged) { + if (C4::Context->preference('AllowHoldsOnDamagedItems')) { + $availability->push_description("damaged"); + } else { + $availability->set_unavailable("damaged"); + } + } + + my $itemtype; + if (C4::Context->preference('item-level_itypes')) { + $itemtype = Koha::ItemTypes->find( $self->itype ); + } else { + my $biblioitem = Koha::Biblioitems->find( $self->biblioitemnumber ); + $itemtype = Koha::ItemTypes->find( $biblioitem->itemype ); + } + + if ($self->notforloan > 0 || $itemtype && $itemtype->notforloan) { + $availability->set_unavailable("notforloan"); + } elsif ($self->notforloan < 0) { + $availability->set_unavailable("ordered"); + } + + if (Koha::Holds->search( [ + { itemnumber => $self->itemnumber }, + { found => [ '=', 'W', 'T' ] } + ], { order_by => 'priority' } )->count()) { + $availability->set_unavailable("reserved"); + } + + return $availability; +} + +=head3 hold_queue_length + +=cut + +sub hold_queue_length { + my ( $self ) = @_; + + my $reserves = Koha::Holds->search({ itemnumber => $self->itemnumber }); + return $reserves->count() if $reserves; + return 0; +} + =head3 home_branch =cut @@ -74,6 +155,22 @@ sub holding_branch { return $self->{_holding_branch}; } +=head3 is_available + +my $available = $item->is_available(); + +See Koha::Item->get_availability() for documentation. + +=cut + +sub is_available { + my ( $self, $status ) = @_; + + my $availability = $self->get_availability(); + + return $availability->{available}; +} + =head3 last_returned_by Gets and sets the last borrower to return an item. --- a/Koha/Item/Availability.pm +++ a/Koha/Item/Availability.pm @@ -0,0 +1,198 @@ +package Koha::Item::Availability; + +# Copyright KohaSuomi 2016 +# +# 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, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +use Modern::Perl; + +use Koha::Logger; + +=head1 NAME + +Koha::Item::Availability - Koha Item Availability object class + +=head1 SYNOPSIS + + my $item = Koha::Items->find(1337); + my $availability = $item->get_availability(); + # ref($availability) eq 'Koha::Item::Availability' + + print $availability->get_description() unless $availability->{available} + +=head1 DESCRIPTION + +This class stores item availability information aiming to having one consistent +item availability object instead of many different types of HASHES and Boolean +values. + +See Koha::Item for availability subroutines. + +=head2 Class Methods + +=cut + +=head3 new + +Returns a new Koha::Item::Availability object. + +=cut + +sub new { + my ( $class ) = @_; + + my $self = { + availability_description => [], + availability_needs_confirmation => undef, + available => undef, + expected_available => undef, + }; + + bless( $self, $class ); +} + +=head3 push_description + +$availability->push_description("notforloan"); +$availability->push_description("withdrawn); + +# $availability->{availability_description} = ["notforloan", "withdrawn"] + +Pushes a new description to $availability object. Does not duplicate existing +descriptions. + +Returns updated Koha::Item::Availability object. + +=cut + +sub push_description { + my ($self, $description) = @_; + + return $self unless $description; + + if (ref($description) eq 'ARRAY') { + foreach my $desc (@$description) { + if (grep(/^$desc$/, $self->{availability_description})){ + next; + } + push $self->{availability_description}, $desc; + } + } else { + if (!grep(/^$description$/, $self->{availability_description})){ + push $self->{availability_description}, $description; + } + } + + return $self; +} + +=head3 reset + +$availability->reset; + +Resets the object. + +=cut + +sub reset { + my ( $self ) = @_; + + $self->{available} = undef; + $self->{availability_needs_confirmation} = undef; + $self->{expected_available} = undef; + $self->{availability_description} = []; + return $self; +} + +=head3 set_available + +$availability->set_available; + +Sets the Koha::Item::Availability object status to available. + $availability->{available} == 1 + +Overrides old availability status, but does not override other stored data in +the object. Create a new Koha::Item::Availability object to get a fresh start. +Appends any previously defined availability descriptions with push_description(). + +Returns updated Koha::Item::Availability object. + +=cut + +sub set_available { + my ($self, $description) = @_; + + return $self->_update_availability_status(1, 0, $description); +} + +=head3 set_needs_confirmation + +$availability->set_needs_confirmation("unbelieveable_reason", "2016-07-07"); + +Sets the Koha::Item::Availability object status to unavailable, +but needs confirmation. + $availability->{available} == 0 + $availability->{availability_needs_confirmation} == 1 + +Overrides old availability statuses, but does not override other stored data in +the object. Create a new Koha::Item::Availability object to get a fresh start. +Appends any previously defined availability descriptions with push_description(). +Allows you to define expected availability date in C<$expected>. + +Returns updated Koha::Item::Availability object. + +=cut + +sub set_needs_confirmation { + my ($self, $description, $expected) = @_; + + return $self->_update_availability_status(0, 1, $description, $expected); +} + +=head3 set_unavailable + +$availability->set_unavailable("onloan", "2016-07-07"); + +Sets the Koha::Item::Availability object status to unavailable. + $availability->{available} == 0 + +Overrides old availability status, but does not override other stored data in +the object. Create a new Koha::Item::Availability object to get a fresh start. +Appends any previously defined availability descriptions with push_description(). +Allows you to define expected availability date in C<$expected>. + +Returns updated Koha::Item::Availability object. + +=cut + +sub set_unavailable { + my ($self, $description, $expected) = @_; + + return $self->_update_availability_status(0, 0, $description, $expected); +} + +sub _update_availability_status { + my ( $self, $available, $needs, $desc, $expected ) = @_; + + $self->{available} = $available; + $self->{availability_needs_confirmation} = $needs; + $self->{expected_available} = $expected; + $self->push_description($desc); + + return $self; +} + +1; --- a/Koha/REST/V1/Availability.pm +++ a/Koha/REST/V1/Availability.pm @@ -0,0 +1,88 @@ +package Koha::REST::V1::Availability; + +# 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, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +use Modern::Perl; + +use Mojo::Base 'Mojolicious::Controller'; +use Mojo::JSON; + +use Koha::Holds; +use Koha::Items; + +sub items { + my ($c, $args, $cb) = @_; + + my @items; + if ($args->{'itemnumber'}) { + push @items, _item_availability(@{$args->{'itemnumber'}}); + } + if ($args->{'biblionumber'}) { + my $found_items = Koha::Items->search({ biblionumber => { + '=', \@{$args->{'biblionumber'}} + } })->as_list(); + my @itemnumbers; + foreach my $item (@$found_items) { + push @itemnumbers, $item->itemnumber; + } + + push @items, _item_availability(@itemnumbers); + } + + return $c->$cb({ error => "Item(s) not found"}, 404) unless scalar @items; + return $c->$cb([ @items ], 200); +} + +sub _item_availability { + my (@itemnumbers) = @_; + + my @items; + + foreach my $itemnumber (@itemnumbers) { + my $item = Koha::Items->find($itemnumber); + + unless ($item) { + next; + } + + my $status = $item->get_availability(); + delete $status->{availability_needs_confirmation}; + $status->{available} = $status->{available} + ? Mojo::JSON->true + : Mojo::JSON->false; + + my $holds; + $holds->{'hold_queue_length'} = $item->hold_queue_length(); + + my $iteminfo = { + itemnumber => $item->itemnumber, + barcode => $item->barcode, + biblionumber => $item->biblionumber, + biblioitemnumber => $item->biblioitemnumber, + holdingbranch => $item->holdingbranch, + homebranch => $item->homebranch, + location => $item->location, + itemcallnumber => $item->itemcallnumber, + }; + + # merge availability, hold information and item information + push @items, { %{$status}, %{$holds}, %{$iteminfo} }; + } + + return @items; +} + +1; --- a/api/v1/definitions/availabilities.json +++ a/api/v1/definitions/availabilities.json @@ -0,0 +1,4 @@ +{ + "type": "array", + "items": { "$ref": "availability.json" } +} --- a/api/v1/definitions/availability.json +++ a/api/v1/definitions/availability.json @@ -0,0 +1,57 @@ +{ + "type": "object", + "properties": { + "available": { + "type": "boolean", + "description": "availability status" + }, + "availability_description": { + "type": "array", + "items": { + "type": ["string"], + "description": "information on item's availability, reasons for unavailability" + }, + "description": "more information on item's availability" + }, + "barcode": { + "type": ["string", "null"], + "description": "item barcode" + }, + "biblioitemnumber": { + "type": "string", + "description": "internally assigned biblio item identifier" + }, + "biblionumber": { + "type": "string", + "description": "internally assigned biblio identifier" + }, + "expected_available": { + "type": ["string", "null"], + "description": "date this item is expected to be available" + }, + "holdQueueLength": { + "type": ["integer", "null"], + "description": "number of holdings placed on title/item" + }, + "holdingbranch": { + "type": ["string", "null"], + "description": "library that is currently in possession item" + }, + "homebranch": { + "type": ["string", "null"], + "description": "library that owns this item" + }, + "itemcallnumber": { + "type": ["string", "null"], + "description": "call number for this item" + }, + "itemnumber": { + "type": "string", + "description": "internally assigned item identifier" + }, + "location": { + "type": ["string", "null"], + "description": "authorized value for the shelving location for this item" + } + } +} --- a/api/v1/definitions/index.json +++ a/api/v1/definitions/index.json @@ -1,4 +1,6 @@ { + "availability": { "$ref": "availability.json" }, + "availabilities": { "$ref": "availabilities.json" }, "patron": { "$ref": "patron.json" }, "holds": { "$ref": "holds.json" }, "hold": { "$ref": "hold.json" }, --- a/api/v1/swagger.json +++ a/api/v1/swagger.json @@ -14,6 +14,34 @@ }, "basePath": "/api/v1", "paths": { + "/availability/items": { + "get": { + "operationId": "itemsAvailability", + "tags": ["items", "availability"], + "parameters": [ + { "$ref": "#/parameters/itemnumbersQueryParam" }, + { "$ref": "#/parameters/biblionumbersQueryParam" } + ], + "consumes": ["application/json"], + "produces": ["application/json"], + "responses": { + "200": { + "description": "Availability information on item(s)", + "schema": { + "$ref": "#/definitions/availabilities" + } + }, + "400": { + "description": "Missing or wrong parameters", + "schema": { "$ref": "#/definitions/error" } + }, + "404": { + "description": "No item(s) found", + "schema": { "$ref": "#/definitions/error" } + } + } + } + }, "/patrons": { "get": { "operationId": "listPatrons", @@ -360,9 +388,19 @@ } }, "definitions": { - "$ref": "./definitions/index.json" + "$ref": "definitions/index.json" }, "parameters": { + "biblionumbersQueryParam": { + "name": "biblionumber", + "in": "query", + "description": "Internal biblios identifier", + "type": "array", + "items": { + "type": "integer" + }, + "collectionFormat": "ssv" + }, "borrowernumberPathParam": { "name": "borrowernumber", "in": "path", @@ -383,6 +421,16 @@ "description": "Internal item identifier", "required": true, "type": "integer" + }, + "itemnumbersQueryParam": { + "name": "itemnumber", + "in": "query", + "description": "Internal items identifier", + "type": "array", + "items": { + "type": "integer" + }, + "collectionFormat": "ssv" } } } --- a/t/Koha/Item/Availability.t +++ a/t/Koha/Item/Availability.t @@ -0,0 +1,42 @@ +#!/usr/bin/perl + +# Copyright KohaSuomi 2016 +# +# 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 => 7; + +use C4::Context; + +use_ok('Koha::Item::Availability'); + +my $availability = Koha::Item::Availability->new->set_available; + +is($availability->{available}, 1, "Available"); +$availability->set_needs_confirmation; +is($availability->{availability_needs_confirmation}, 1, "Needs confirmation"); +$availability->set_unavailable; +is($availability->{available}, 0, "Not available"); + +$availability->push_description("such available"); +$availability->push_description("wow"); + +is($availability->{availability_description}[0], "such available", "Found correct description 1/2"); +is($availability->{availability_description}[1], "wow", "Found correct description 2/2"); + +$availability->reset; +is($availability->{available}, undef, "Availability reset"); --- a/t/db_dependent/Items.t +++ a/t/db_dependent/Items.t @@ -20,7 +20,12 @@ use Modern::Perl; use MARC::Record; use C4::Biblio; +use C4::Circulation; +use C4::Reserves; use Koha::Database; +use Koha::Hold; +use Koha::Issue; +use Koha::Item::Availability; use Koha::Library; use t::lib::Mocks; @@ -30,6 +35,11 @@ use Test::More tests => 10; use Test::Warn; +my @USERENV = (1,'test','MASTERTEST','Test','Test','t','Test',0,); +my $BRANCH_IDX = 5; +C4::Context->_new_userenv ('DUMMY_SESSION_ID'); +C4::Context->set_userenv ( @USERENV ); + BEGIN { use_ok('C4::Items'); use_ok('Koha::Items'); @@ -432,7 +442,7 @@ subtest 'SearchItems test' => sub { subtest 'Koha::Item(s) tests' => sub { - plan tests => 5; + plan tests => 25; $schema->storage->txn_begin(); @@ -443,6 +453,15 @@ subtest 'Koha::Item(s) tests' => sub { my $library2 = $builder->build({ source => 'Branch', }); + my $borrower = $builder->build({ + source => 'Borrower', + }); + my $itemtype = $builder->build({ + source => 'Itemtype', + value => { + notforloan => 1 + } + }); # Create a biblio and item for testing t::lib::Mocks::mock_preference('marcflavour', 'MARC21'); @@ -461,6 +480,67 @@ subtest 'Koha::Item(s) tests' => sub { is( ref($holdingbranch), 'Koha::Library', "Got Koha::Library from holding_branch method" ); is( $holdingbranch->branchcode(), $library2->{branchcode}, "Home branch code matches holdingbranch" ); + # Availability tests + my $availability = $item->get_availability(); + is (ref($availability), 'Koha::Item::Availability', 'Got Koha::Item::Availability'); + is( $availability->{available}, 1, "Item is available" ); + + $item->set({ onloan => "", damaged => 1 })->store(); + $availability = $item->get_availability(); + is( $availability->{available}, C4::Context->preference('AllowHoldsOnDamagedItems'), "Good availability for damaged item" ); + is( $availability->{availability_description}[0], "damaged", "Availability description is 'damaged'" ); + + $item->set({ damaged => 0, withdrawn => 1 })->store(); + $availability = $item->get_availability(); + is( $availability->{available}, 0, "Item is not available" ); + is( $availability->{availability_description}[0], "withdrawn", "Availability description is 'withdrawn'" ); + + $item->set({ withdrawn => 0, itemlost => 1 })->store(); + $availability = $item->get_availability(); + is( $availability->{available}, 0, "Item is not available" ); + is( $availability->{availability_description}[0], "itemlost", "Availability description is 'itemlost'" ); + + $item->set({ itemlost => 0, restricted => 1 })->store(); + $availability = $item->get_availability(); + is( $availability->{available}, 0, "Item is not available" ); + is( $availability->{availability_description}[0], "restricted", "Availability description is 'restricted'" ); + + $item->set({ restricted => 0, notforloan => 1 })->store(); + $availability = $item->get_availability(); + is( $availability->{available}, 0, "Item is not available" ); + is( $availability->{availability_description}[0], "notforloan", "Availability description is 'notforloan'" ); + + $item->set({ notforloan => 0, itype => $itemtype->{itemtype} })->store(); + $availability = $item->get_availability(); + is( $availability->{available}, 0, "Item is not available" ); + is( $availability->{availability_description}[0], "notforloan", "Availability description is 'notforloan' (itemtype)" ); + + $item->set({ itype => undef, barcode => "test" })->store(); + my $reserve = Koha::Hold->new( + { + biblionumber => $item->biblionumber, + itemnumber => $item->itemnumber, + waitingdate => '2000-01-01', + borrowernumber => $borrower->{borrowernumber}, + branchcode => $item->homebranch, + suspend => 0, + } + )->store(); + $availability = $item->get_availability(); + is( $availability->{available}, 0, "Item is not available" ); + is( $availability->{availability_description}[0], "reserved", "Availability description is 'reserved'" ); + CancelReserve({ reserve_id => $reserve->reserve_id }); + + $availability = $item->get_availability(); + is( $availability->{available}, 1, "Item is available" ); + + my $issue = AddIssue($borrower, $item->barcode, undef, 1); + $item = Koha::Items->find($item->itemnumber); # refresh item + $availability = $item->get_availability(); + is( $availability->{available}, 0, "Item is not available" ); + is( $availability->{availability_description}[0], "onloan", "Availability description is 'onloan'" ); + is( $availability->{expected_available}, $issue->date_due, "Expected to be available '".$issue->date_due."'"); + $schema->storage->txn_rollback; }; @@ -468,7 +548,6 @@ subtest 'C4::Biblio::EmbedItemsInMarcBiblio' => sub { plan tests => 7; $schema->storage->txn_begin(); - my $builder = t::lib::TestBuilder->new; my $library1 = $builder->build({ source => 'Branch', --- a/t/db_dependent/api/v1/availability.t +++ a/t/db_dependent/api/v1/availability.t @@ -0,0 +1,195 @@ +#!/usr/bin/env perl + +# Copyright KohaSuomi 2016 +# +# 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, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +use Modern::Perl; + +use Test::More tests => 76; +use Test::Mojo; +use t::lib::TestBuilder; + +use Mojo::JSON; + +use C4::Auth; +use C4::Circulation; +use C4::Context; + +use Koha::Database; +use Koha::Items; +use Koha::Patron; + +my $builder = t::lib::TestBuilder->new(); + +my $dbh = C4::Context->dbh; +$dbh->{AutoCommit} = 0; +$dbh->{RaiseError} = 1; + +$ENV{REMOTE_ADDR} = '127.0.0.1'; +my $t = Test::Mojo->new('Koha::REST::V1'); + +my @USERENV = (1,'test','MASTERTEST','Test','Test','t','Test',0,); +my $BRANCH_IDX = 5; +C4::Context->_new_userenv ('DUMMY_SESSION_ID'); +C4::Context->set_userenv ( @USERENV ); + +my $categorycode = $builder->build({ source => 'Category' })->{ categorycode }; +my $branchcode = $builder->build({ source => 'Branch' })->{ branchcode }; + +my $borrower = $builder->build({ source => 'Borrower' }); +my $biblio = $builder->build({ source => 'Biblio' }); +my $biblio2 = $builder->build({ source => 'Biblio' }); +my $biblionumber = $biblio->{biblionumber}; +my $biblionumber2 = $biblio2->{biblionumber}; + +# $item = available, $item2 = unavailable +my $items; +$items->{available} = build_item($biblionumber); +$items->{notforloan} = build_item($biblionumber2, { notforloan => 1 }); +$items->{damaged} = build_item($biblionumber2, { damaged => 1 }); +$items->{withdrawn} = build_item($biblionumber2, { withdrawn => 1 }); +$items->{onloan} = build_item($biblionumber2, { onloan => undef }); +$items->{itemlost} = build_item($biblionumber2, { itemlost => 1 }); +$items->{reserved} = build_item($biblionumber2); +my $reserve = Koha::Hold->new( + { + biblionumber => $items->{reserved}->{biblionumber}, + itemnumber => $items->{reserved}->{itemnumber}, + waitingdate => '2000-01-01', + borrowernumber => $borrower->{borrowernumber}, + branchcode => $items->{reserved}->{homebranch}, + suspend => 0, + } + )->store(); + +my $itemnumber = $items->{available}->{itemnumber}; + +$t->get_ok("/api/v1/availability/items?itemnumber=-500382") + ->status_is(404); + +$t->get_ok("/api/v1/availability/items?itemnumber=-500382+-500383") + ->status_is(404); + +$t->get_ok("/api/v1/availability/items?biblionumber=-500382") + ->status_is(404); + +$t->get_ok("/api/v1/availability/items?biblionumber=-500382+-500383") + ->status_is(404); + +# available item +$t->get_ok("/api/v1/availability/items?itemnumber=$itemnumber") + ->status_is(200) + ->json_is('/0/itemnumber', $itemnumber) + ->json_is('/0/biblionumber', $biblionumber) + ->json_is('/0/available', Mojo::JSON->true) + ->json_is('/0/availability_description', []) + ->json_is('/0/hold_queue_length', 0); +$t->get_ok("/api/v1/availability/items?biblionumber=$biblionumber") + ->status_is(200) + ->json_is('/0/itemnumber', $itemnumber) + ->json_is('/0/biblionumber', $biblionumber) + ->json_is('/0/available', Mojo::JSON->true) + ->json_is('/0/availability_description', []) + ->json_is('/0/hold_queue_length', 0); + +# notforloan item +$t->get_ok("/api/v1/availability/items?itemnumber=".$items->{notforloan}->{itemnumber}) + ->status_is(200) + ->json_is('/0/itemnumber', $items->{notforloan}->{itemnumber}) + ->json_is('/0/biblionumber', $biblionumber2) + ->json_is('/0/available', Mojo::JSON->false) + ->json_is('/0/availability_description/0', "notforloan") + ->json_is('/0/hold_queue_length', 0); + +# damaged item +$t->get_ok("/api/v1/availability/items?itemnumber=".$items->{damaged}->{itemnumber}) + ->status_is(200) + ->json_is('/0/itemnumber', $items->{damaged}->{itemnumber}) + ->json_is('/0/biblionumber', $biblionumber2) + ->json_is('/0/available', C4::Context->preference('AllowHoldsOnDamagedItems') ? Mojo::JSON->true : Mojo::JSON->false) + ->json_is('/0/availability_description/0', "damaged") + ->json_is('/0/hold_queue_length', 0); + +# withdrawn item +$t->get_ok("/api/v1/availability/items?itemnumber=".$items->{withdrawn}->{itemnumber}) + ->status_is(200) + ->json_is('/0/itemnumber', $items->{withdrawn}->{itemnumber}) + ->json_is('/0/biblionumber', $biblionumber2) + ->json_is('/0/available', Mojo::JSON->false) + ->json_is('/0/availability_description/0', "withdrawn") + ->json_is('/0/hold_queue_length', 0); + +# lost item +$t->get_ok("/api/v1/availability/items?itemnumber=".$items->{itemlost}->{itemnumber}) + ->status_is(200) + ->json_is('/0/itemnumber', $items->{itemlost}->{itemnumber}) + ->json_is('/0/biblionumber', $biblionumber2) + ->json_is('/0/available', Mojo::JSON->false) + ->json_is('/0/availability_description/0', "itemlost") + ->json_is('/0/hold_queue_length', 0); + +my $issue = AddIssue($borrower, $items->{onloan}->{barcode}, undef, 1); + +# issued item +$t->get_ok("/api/v1/availability/items?itemnumber=".$items->{onloan}->{itemnumber}) + ->status_is(200) + ->json_is('/0/itemnumber', $items->{onloan}->{itemnumber}) + ->json_is('/0/biblionumber', $biblionumber2) + ->json_is('/0/available', Mojo::JSON->false) + ->json_is('/0/availability_description/0', "onloan") + ->json_is('/0/expected_available', $issue->date_due) + ->json_is('/0/hold_queue_length', 0); + +# reserved item +$t->get_ok("/api/v1/availability/items?itemnumber=".$items->{reserved}->{itemnumber}) + ->status_is(200) + ->json_is('/0/itemnumber', $items->{reserved}->{itemnumber}) + ->json_is('/0/biblionumber', $biblionumber2) + ->json_is('/0/available', Mojo::JSON->false) + ->json_is('/0/availability_description/0', "reserved") + ->json_is('/0/hold_queue_length', 1); + +# multiple in one request +$t->get_ok("/api/v1/availability/items?itemnumber=".$items->{notforloan}->{itemnumber}."+$itemnumber+-500382") + ->status_is(200) + ->json_is('/0/itemnumber', $items->{notforloan}->{itemnumber}) + ->json_is('/0/biblionumber', $biblionumber2) + ->json_is('/0/available', Mojo::JSON->false) + ->json_is('/0/availability_description/0', "notforloan") + ->json_is('/1/itemnumber', $itemnumber) + ->json_is('/1/biblionumber', $biblionumber) + ->json_is('/1/available', Mojo::JSON->true) + ->json_is('/1/availability_description', []) + ->json_is('/1/hold_queue_length', 0); + +sub build_item { + my ($biblionumber, $field) = @_; + + return $builder->build({ + source => 'Item', + value => { + biblionumber => $biblionumber, + notforloan => $field->{notforloan} || 0, + damaged => $field->{damaged} || 0, + withdrawn => $field->{withdrawn} || 0, + itemlost => $field->{itemlost} || 0, + restricted => $field->{restricted} || undef, + onloan => $field->{onloan} || undef, + itype => $field->{itype} || undef, + } + }); +} --