From f07728f74d21ac2e28dcea3e45d023af6263d774 Mon Sep 17 00:00:00 2001 From: Lari Taskula Date: Tue, 8 Nov 2016 15:48:10 +0200 Subject: [PATCH] Bug 16826: Add REST endpoint for displaying item availabilities GET /availability/items?itemnumber=123 GET /availability/items?itemnumber=123+456+789 GET /availability/items?biblionumber=321 GET /availability/items?biblionumber=321+654+987 This patch adds above routes for checking anonymous item availability. Patron status is not checked. The returned data is for example: { "checkout": { "available": false, "description": ["onloan"], "expected_available": "2016-07-13" }, "hold": { "available": true, "description": [], "expected_available": null }, "local_use": { "available": false, "description": ["onloan"], "expected_available": "2016-07-13" }, "onsite_checkout": { "available": false, "description": ["onsite_checkouts_disabled"], "expected_available": null }, "hold_queue_length": 1, + some basic item information on locations, bib/bib(item)numbers etc. } Possible values in availability description are an empty array and any of: "onloan", "reserved", "damaged", "withdrawn", "itemlost", "restricted", "notforloan", "ordered". (+"onsite_checkouts_disabled" for onsite_checkout) To test: 1. Apply patch 2. Play around with an item. Place a hold on it, checkout, set it damaged etc. 3. Make GET requests to /api/v1/availability/items?itemnumber=YYY, where YYY is an existing itemnumber. You can also try with biblionumber=XXX query parameter, where XXX is an existing biblionumber. 4. Check that the availability statuses are correct and that availability description lists the appropriate status for unavailability. 5. Repeat steps 2-4 until you are confident the route works as expected. 6. Run t/db_dependent/api/v1/availability.t --- Koha/REST/V1/Availability.pm | 107 ++++++++ api/v1/swagger/definitions.json | 6 + api/v1/swagger/definitions/availabilities.json | 4 + api/v1/swagger/definitions/availability.json | 65 +++++ api/v1/swagger/definitions/availabilitystatus.json | 16 ++ api/v1/swagger/parameters.json | 6 + api/v1/swagger/parameters/item.json | 10 + api/v1/swagger/paths.json | 3 + api/v1/swagger/paths/availability.json | 36 +++ t/db_dependent/api/v1/availability.t | 292 +++++++++++++++++++++ 10 files changed, 545 insertions(+) create mode 100644 Koha/REST/V1/Availability.pm create mode 100644 api/v1/swagger/definitions/availabilities.json create mode 100644 api/v1/swagger/definitions/availability.json create mode 100644 api/v1/swagger/definitions/availabilitystatus.json create mode 100644 api/v1/swagger/paths/availability.json create mode 100644 t/db_dependent/api/v1/availability.t diff --git a/Koha/REST/V1/Availability.pm b/Koha/REST/V1/Availability.pm new file mode 100644 index 0000000..9e1405b --- /dev/null +++ b/Koha/REST/V1/Availability.pm @@ -0,0 +1,107 @@ +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'}} + } }); + + push @items, _item_availability($found_items); + } + + return $c->$cb({ error => "Item(s) not found"}, 404) unless scalar @items; + return $c->$cb([ @items ], 200); +} + +sub _item_availability { + my ($items) = @_; + + if (ref($items) eq 'Koha::Items') { + $items = $items->as_list; + } + + my @item_availabilities; + foreach my $item (@$items) { + unless (ref($item) eq 'Koha::Item') { + $item = Koha::Items->find($item); + next unless $item; + } + + my $availabilities = _swaggerize_availabilities($item->availabilities); + + my $holds; + $holds->{'hold_queue_length'} = $item->hold_queue_length; + + my $iteminfo = { + itemnumber => $item->itemnumber, + barcode => $item->barcode, + biblionumber => $item->biblionumber, + biblioitemnumber => $item->biblioitemnumber, + enumchron => $item->enumchron, + holdingbranch => $item->holdingbranch, + homebranch => $item->homebranch, + location => $item->location, + itemcallnumber => $item->itemcallnumber, + itemnotes => $item->itemnotes, + }; + + # merge availability, hold information and item information + push @item_availabilities, { %{$availabilities}, %{$holds}, %{$iteminfo} }; + } + + return @item_availabilities; +} + +sub _swaggerize_availabilities { + my ($availabilities) = @_; + + $availabilities->use_stored_values(1); + my $avail_hash = { + hold => $availabilities->hold, + checkout => $availabilities->issue, + local_use => $availabilities->local_use, + onsite_checkout => $availabilities->onsite_checkout, + }; + + foreach my $availability (keys $avail_hash) { + delete $avail_hash->{$availability}->{availability_needs_confirmation}; + $avail_hash->{$availability}->available( + $avail_hash->{$availability}->available + ? Mojo::JSON->true + : Mojo::JSON->false); + $avail_hash->{$availability} = { %{$avail_hash->{$availability}} }; + } + + return $avail_hash; +} + +1; diff --git a/api/v1/swagger/definitions.json b/api/v1/swagger/definitions.json index a92fe4b..55940c6 100644 --- a/api/v1/swagger/definitions.json +++ b/api/v1/swagger/definitions.json @@ -1,4 +1,10 @@ { + "availability": { + "$ref": "definitions/availability.json" + }, + "availabilities": { + "$ref": "definitions/availabilities.json" + }, "city": { "$ref": "definitions/city.json" }, diff --git a/api/v1/swagger/definitions/availabilities.json b/api/v1/swagger/definitions/availabilities.json new file mode 100644 index 0000000..20fa958 --- /dev/null +++ b/api/v1/swagger/definitions/availabilities.json @@ -0,0 +1,4 @@ +{ + "type": "array", + "items": { "$ref": "availability.json" } +} diff --git a/api/v1/swagger/definitions/availability.json b/api/v1/swagger/definitions/availability.json new file mode 100644 index 0000000..f656383 --- /dev/null +++ b/api/v1/swagger/definitions/availability.json @@ -0,0 +1,65 @@ +{ + "type": "object", + "properties": { + "barcode": { + "type": ["string", "null"], + "description": "item barcode" + }, + "biblioitemnumber": { + "type": "string", + "description": "internally assigned biblio item identifier" + }, + "biblionumber": { + "type": "string", + "description": "internally assigned biblio identifier" + }, + "checkout": { + "$ref": "availabilitystatus.json" + }, + "enumchron": { + "type": ["string", "null"], + "description": "serial enumeration/chronology for the item" + }, + "expected_available": { + "type": ["string", "null"], + "description": "date this item is expected to be available" + }, + "hold": { + "$ref": "availabilitystatus.json" + }, + "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" + }, + "itemnotes": { + "type": ["string", "null"], + "description": "public notes on this item" + }, + "itemnumber": { + "type": "string", + "description": "internally assigned item identifier" + }, + "local_use": { + "$ref": "availabilitystatus.json" + }, + "location": { + "type": ["string", "null"], + "description": "authorized value for the shelving location for this item" + }, + "onsite_checkout": { + "$ref": "availabilitystatus.json" + } + } +} diff --git a/api/v1/swagger/definitions/availabilitystatus.json b/api/v1/swagger/definitions/availabilitystatus.json new file mode 100644 index 0000000..2bf1ea6 --- /dev/null +++ b/api/v1/swagger/definitions/availabilitystatus.json @@ -0,0 +1,16 @@ +{ + "type": "object", + "properties": { + "available": { + "type": "boolean", + "description": "availability status" + }, + "description": { + "type": "array", + "items": { + "type": ["string", "null"], + "description": "more information on availability" + } + } + } +} diff --git a/api/v1/swagger/parameters.json b/api/v1/swagger/parameters.json index f11031f..ab29778 100644 --- a/api/v1/swagger/parameters.json +++ b/api/v1/swagger/parameters.json @@ -1,4 +1,7 @@ { + "biblionumbersQueryParam": { + "$ref": "parameters/biblio.json#/biblionumbersQueryParam" + }, "borrowernumberPathParam": { "$ref": "parameters/patron.json#/borrowernumberPathParam" }, @@ -13,5 +16,8 @@ }, "itemnumberPathParam": { "$ref": "parameters/item.json#/itemnumberPathParam" + }, + "itemnumbersQueryParam": { + "$ref": "parameters/item.json#/itemnumbersQueryParam" } } diff --git a/api/v1/swagger/parameters/item.json b/api/v1/swagger/parameters/item.json index c215adb..409be6c 100644 --- a/api/v1/swagger/parameters/item.json +++ b/api/v1/swagger/parameters/item.json @@ -5,5 +5,15 @@ "description": "Internal item identifier", "required": true, "type": "integer" + }, + "itemnumbersQueryParam": { + "name": "itemnumber", + "in": "query", + "description": "Internal items identifier", + "type": "array", + "items": { + "type": "integer" + }, + "collectionFormat": "ssv" } } diff --git a/api/v1/swagger/paths.json b/api/v1/swagger/paths.json index 73729c1..8aa498a 100644 --- a/api/v1/swagger/paths.json +++ b/api/v1/swagger/paths.json @@ -1,4 +1,7 @@ { + "/availability/items": { + "$ref": "paths/availability.json#/~1availability~1items" + }, "/cities": { "$ref": "paths/cities.json#/~1cities" }, diff --git a/api/v1/swagger/paths/availability.json b/api/v1/swagger/paths/availability.json new file mode 100644 index 0000000..b482757 --- /dev/null +++ b/api/v1/swagger/paths/availability.json @@ -0,0 +1,36 @@ +{ + "/availability/items": { + "get": { + "operationId": "itemsAvailability", + "tags": ["items", "availability"], + "parameters": [{ + "$ref": "../parameters.json#/itemnumbersQueryParam" + }, { + "$ref": "../parameters.json#/biblionumbersQueryParam" + } + ], + "consumes": ["application/json"], + "produces": ["application/json"], + "responses": { + "200": { + "description": "Availability information on item(s)", + "schema": { + "$ref": "../definitions.json#/availabilities" + } + }, + "400": { + "description": "Missing or wrong parameters", + "schema": { + "$ref": "../definitions.json#/error" + } + }, + "404": { + "description": "No item(s) found", + "schema": { + "$ref": "../definitions.json#/error" + } + } + } + } + } +} diff --git a/t/db_dependent/api/v1/availability.t b/t/db_dependent/api/v1/availability.t new file mode 100644 index 0000000..0f11623 --- /dev/null +++ b/t/db_dependent/api/v1/availability.t @@ -0,0 +1,292 @@ +#!/usr/bin/env perl + +# Copyright Koha-Suomi Oy 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 => 165; +use Test::Mojo; +use t::lib::Mocks; +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 $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}; + +my $module = new Test::MockModule('C4::Context'); +$module->mock( 'userenv', sub { { branch => $borrower->{branchcode} } } ); + +# $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 $priority= C4::Reserves::CalculatePriority($items->{reserved}->{biblionumber}); +my $reserve_id = C4::Reserves::AddReserve( + $items->{reserved}->{homebranch}, + $borrower->{borrowernumber}, + $items->{reserved}->{biblionumber}, + undef, + $priority, + undef, undef, undef, + $$biblio{title}, + $items->{reserved}->{itemnumber}, + undef +); +my $reserve = Koha::Holds->find($reserve_id); + +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); + +t::lib::Mocks::mock_preference('OnSiteCheckouts', 0); +t::lib::Mocks::mock_preference('AllowHoldsOnDamagedItems', 0); +# 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/checkout/available', Mojo::JSON->true) + ->json_is('/0/checkout/description', []) + ->json_is('/0/hold/available', Mojo::JSON->true) + ->json_is('/0/hold/description', []) + ->json_is('/0/local_use/available', Mojo::JSON->true) + ->json_is('/0/local_use/description', []) + ->json_is('/0/onsite_checkout/available', Mojo::JSON->false) + ->json_is('/0/onsite_checkout/description', ["onsite_checkouts_disabled"]) + ->json_is('/0/hold_queue_length', 0); +t::lib::Mocks::mock_preference('OnSiteCheckouts', 1); +$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/checkout/available', Mojo::JSON->true) + ->json_is('/0/checkout/description', []) + ->json_is('/0/hold/available', Mojo::JSON->true) + ->json_is('/0/hold/description', []) + ->json_is('/0/local_use/available', Mojo::JSON->true) + ->json_is('/0/local_use/description', []) + ->json_is('/0/onsite_checkout/available', Mojo::JSON->true) + ->json_is('/0/onsite_checkout/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/checkout/available', Mojo::JSON->false) + ->json_is('/0/checkout/description/0', "notforloan") + ->json_is('/0/hold/available', Mojo::JSON->false) + ->json_is('/0/hold/description', ["notforloan"]) + ->json_is('/0/local_use/available', Mojo::JSON->true) + ->json_is('/0/local_use/description', []) + ->json_is('/0/onsite_checkout/available', Mojo::JSON->true) + ->json_is('/0/onsite_checkout/description', []) + ->json_is('/0/hold_queue_length', 0); +t::lib::Mocks::mock_preference('OnSiteCheckouts', 0); +$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/checkout/available', Mojo::JSON->false) + ->json_is('/0/checkout/description', ["notforloan"]) + ->json_is('/0/hold/available', Mojo::JSON->false) + ->json_is('/0/hold/description', ["notforloan"]) + ->json_is('/0/local_use/available', Mojo::JSON->true) + ->json_is('/0/local_use/description', []) + ->json_is('/0/onsite_checkout/available', Mojo::JSON->false) + ->json_is('/0/onsite_checkout/description', ["onsite_checkouts_disabled"]) + ->json_is('/0/hold_queue_length', 0); +t::lib::Mocks::mock_preference('OnSiteCheckouts', 1); + +# 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/checkout/available', Mojo::JSON->false) + ->json_is('/0/checkout/description', ["damaged"]) + ->json_is('/0/hold/available', Mojo::JSON->false) + ->json_is('/0/hold/description', ["damaged"]) + ->json_is('/0/local_use/available', Mojo::JSON->false) + ->json_is('/0/local_use/description', ["damaged"]) + ->json_is('/0/onsite_checkout/available', Mojo::JSON->false) + ->json_is('/0/onsite_checkout/description', ["damaged"]) + ->json_is('/0/hold_queue_length', 0); +t::lib::Mocks::mock_preference('AllowHoldsOnDamagedItems', 1); +$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/checkout/available', Mojo::JSON->true) + ->json_is('/0/checkout/description', ["damaged"]) + ->json_is('/0/hold/available', Mojo::JSON->true) + ->json_is('/0/hold/description', ["damaged"]) + ->json_is('/0/local_use/available', Mojo::JSON->true) + ->json_is('/0/local_use/description', ["damaged"]) + ->json_is('/0/onsite_checkout/available', Mojo::JSON->true) + ->json_is('/0/onsite_checkout/description', ["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/checkout/available', Mojo::JSON->false) + ->json_is('/0/checkout/description', ["withdrawn"]) + ->json_is('/0/hold/available', Mojo::JSON->false) + ->json_is('/0/hold/description', ["withdrawn"]) + ->json_is('/0/local_use/available', Mojo::JSON->false) + ->json_is('/0/local_use/description', ["withdrawn"]) + ->json_is('/0/onsite_checkout/available', Mojo::JSON->false) + ->json_is('/0/onsite_checkout/description', ["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/checkout/available', Mojo::JSON->false) + ->json_is('/0/checkout/description', ["itemlost"]) + ->json_is('/0/hold/available', Mojo::JSON->false) + ->json_is('/0/hold/description', ["itemlost"]) + ->json_is('/0/local_use/available', Mojo::JSON->false) + ->json_is('/0/local_use/description', ["itemlost"]) + ->json_is('/0/onsite_checkout/available', Mojo::JSON->false) + ->json_is('/0/onsite_checkout/description', ["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/checkout/available', Mojo::JSON->false) + ->json_is('/0/checkout/description', ["onloan"]) + ->json_is('/0/hold/available', Mojo::JSON->true) + ->json_is('/0/hold/description', []) + ->json_is('/0/local_use/available', Mojo::JSON->false) + ->json_is('/0/local_use/description', ["onloan"]) + ->json_is('/0/onsite_checkout/available', Mojo::JSON->false) + ->json_is('/0/onsite_checkout/description', ["onloan"]) + ->json_is('/0/checkout/expected_available', $issue->date_due) + ->json_is('/0/local_use/expected_available', $issue->date_due) + ->json_is('/0/onsite_checkout/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/checkout/available', Mojo::JSON->false) + ->json_is('/0/checkout/description', ["reserved"]) + ->json_is('/0/hold/available', Mojo::JSON->true) + ->json_is('/0/hold/description', []) + ->json_is('/0/local_use/available', Mojo::JSON->false) + ->json_is('/0/local_use/description', ["reserved"]) + ->json_is('/0/onsite_checkout/available', Mojo::JSON->false) + ->json_is('/0/onsite_checkout/description', ["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/checkout/available', Mojo::JSON->false) + ->json_is('/0/checkout/description/0', "notforloan") + ->json_is('/0/hold/available', Mojo::JSON->false) + ->json_is('/0/hold/description', ["notforloan"]) + ->json_is('/0/local_use/available', Mojo::JSON->true) + ->json_is('/0/local_use/description', []) + ->json_is('/0/onsite_checkout/available', Mojo::JSON->true) + ->json_is('/0/onsite_checkout/description', []) + ->json_is('/0/hold_queue_length', 0) + ->json_is('/1/itemnumber', $itemnumber) + ->json_is('/1/biblionumber', $biblionumber) + ->json_is('/1/checkout/available', Mojo::JSON->true) + ->json_is('/1/checkout/description', []) + ->json_is('/1/hold/available', Mojo::JSON->true) + ->json_is('/1/hold/description', []) + ->json_is('/1/local_use/available', Mojo::JSON->true) + ->json_is('/1/local_use/description', []) + ->json_is('/1/onsite_checkout/available', Mojo::JSON->true) + ->json_is('/1/onsite_checkout/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, + } + }); +} -- 1.9.1