From 4a69337fd7d5f692b5c115f5f4f003a863ac4514 Mon Sep 17 00:00:00 2001
From: Lari Taskula <lari.taskula@jns.fi>
Date: Fri, 18 Nov 2016 13:09:12 +0200
Subject: [PATCH] Bug 17712: Add base for item and biblio availability

This patch adds two new classes:
- Koha::Biblio::Availability
- Koha::Item::Availability

These classes represent biblio- and item-level availabilities and the purpose
of this patch is simply to provide constructors that validate given biblio/item
and patron parameters.

Hold / checkout / search view availabilities (the actual answers to availability)
will be provided in the next patches.

To test:
1. Run t/db_dependent/Koha/Item/Availability.t
2. Run t/db_dependent/Koha/Biblio/Availability.t
---
 Koha/Biblio/Availability.pm               | 132 +++++++++++++++++++++++++
 Koha/Item/Availability.pm                 | 126 +++++++++++++++++++++++
 t/db_dependent/Koha/Biblio/Availability.t | 159 ++++++++++++++++++++++++++++++
 t/db_dependent/Koha/Item/Availability.t   | 159 ++++++++++++++++++++++++++++++
 4 files changed, 576 insertions(+)
 create mode 100644 Koha/Biblio/Availability.pm
 create mode 100644 Koha/Item/Availability.pm
 create mode 100644 t/db_dependent/Koha/Biblio/Availability.t
 create mode 100644 t/db_dependent/Koha/Item/Availability.t

diff --git a/Koha/Biblio/Availability.pm b/Koha/Biblio/Availability.pm
new file mode 100644
index 0000000..654457b
--- /dev/null
+++ b/Koha/Biblio/Availability.pm
@@ -0,0 +1,132 @@
+package Koha::Biblio::Availability;
+
+# 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 Scalar::Util qw(looks_like_number);
+
+use base qw(Koha::Availability);
+
+use Koha::Exceptions;
+use Koha::Exceptions::Biblio;
+use Koha::Exceptions::Patron;
+
+=head1 NAME
+
+Koha::Biblio::Availability - Koha Biblio Availability object class
+
+=head1 SYNOPSIS
+
+Parent class for different types of biblio availabilities.
+
+=head1 DESCRIPTION
+
+=head2 Class Methods
+
+This class is for storing biblio availability information. It is a subclass of
+Koha::Availability. For more documentation on usage, see Koha::Availability.
+
+=cut
+
+=head3 new
+
+my $availability = Koha::Biblio::Availability->new({
+    biblionumber => 123
+});
+
+REQUIRED PARAMETERS:
+    biblio (Koha::Biblio) / biblionumber
+
+OPTIONAL PARAMETERS:
+    patron (Koha::Patron) / borrowernumber
+
+Creates a new Koha::Biblio::Availability object.
+
+=cut
+
+sub new {
+    my $class = shift;
+    my ($params) = @_;
+    my $self = $class->SUPER::new(@_);
+
+    $self->{'biblio'} = undef;
+    $self->{'patron'} = undef;
+
+    # ARRAYref of Koha::Item::Availability objects
+    # ...for available items
+    $self->{'item_availabilities'} = [];
+    # ...for unavailabile items
+    $self->{'item_unavailabilities'} = [];
+
+    if (exists $params->{'biblio'}) {
+        unless (ref($params->{'biblio'}) eq 'Koha::Biblio') {
+            Koha::Exceptions::BadParameter->throw(
+                error => 'Parameter must be a Koha::Biblio object.',
+                parameter => 'biblio',
+            );
+        }
+        $self->biblio($params->{'biblio'});
+    } elsif (exists $params->{'biblionumber'}) {
+        unless (looks_like_number($params->{'biblionumber'})) {
+            Koha::Exceptions::BadParameter->throw(
+                error => 'Parameter must be a numeric value.',
+                parameter => 'biblionumber',
+            );
+        }
+        $self->biblio(Koha::Biblios->find($params->{'biblionumber'}));
+        unless ($self->biblio) {
+            Koha::Exceptions::Biblio::NotFound->throw(
+                error => 'Biblio not found.',
+                biblionumber => $params->{'biblionumber'},
+            );
+        }
+    } else {
+        Koha::Exceptions::MissingParameter->throw(
+            error => "Missing one of parameters 'biblionumber, 'biblio'.",
+            parameter => ["biblionumber", "biblio"],
+        );
+    }
+
+    if (exists $params->{'patron'}) {
+        unless (ref($params->{'patron'}) eq 'Koha::Patron') {
+            Koha::Exceptions::BadParameter->throw(
+                error => 'Parameter must be a Koha::Patron object.',
+                parameter => 'patron',
+            );
+        }
+        $self->patron($params->{'patron'});
+    } elsif (exists $params->{'borrowernumber'}) {
+        unless (looks_like_number($params->{'borrowernumber'})) {
+            Koha::Exceptions::BadParameter->throw(
+                error => 'Parameter must be a numeric value.',
+                parameter => 'borrowernumber',
+            );
+        }
+        $self->patron(Koha::Patrons->find($params->{'borrowernumber'}));
+        unless ($self->patron) {
+            Koha::Exceptions::Patron::NotFound->throw(
+                error => 'Patron not found.',
+                borrowernumber => $params->{'borrowernumber'},
+            );
+        }
+    }
+
+    return $self;
+}
+
+1;
diff --git a/Koha/Item/Availability.pm b/Koha/Item/Availability.pm
new file mode 100644
index 0000000..c26d79b
--- /dev/null
+++ b/Koha/Item/Availability.pm
@@ -0,0 +1,126 @@
+package Koha::Item::Availability;
+
+# 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 Scalar::Util qw(looks_like_number);
+
+use base qw(Koha::Availability);
+
+use Koha::Exceptions;
+use Koha::Exceptions::Item;
+use Koha::Exceptions::Patron;
+
+=head1 NAME
+
+Koha::Item::Availability - Koha Item Availability object class
+
+=head1 SYNOPSIS
+
+Parent class for different types of item availabilities.
+
+=head1 DESCRIPTION
+
+=head2 Class Methods
+
+This class is for storing item availability information. It is a subclass of
+Koha::Availability. For more documentation on usage, see Koha::Availability.
+
+=cut
+
+=head3 new
+
+my $availability = Koha::Item::Availability->new({
+    itemnumber => 123
+});
+
+REQUIRED PARAMETERS:
+    item (Koha::Item) / itemnumber
+
+OPTIONAL PARAMETERS:
+    patron (Koha::Patron) / borrowernumber
+
+Creates a new Koha::Item::Availability object.
+
+=cut
+
+sub new {
+    my $class = shift;
+    my ($params) = @_;
+    my $self = $class->SUPER::new(@_);
+
+    $self->{'item'} = undef;
+    $self->{'patron'} = undef;
+
+    if (exists $params->{'item'}) {
+        unless (ref($params->{'item'}) eq 'Koha::Item') {
+            Koha::Exceptions::BadParameter->throw(
+                error => 'Parameter must be a Koha::Item object.',
+                parameter => 'item',
+            );
+        }
+        $self->item($params->{'item'});
+    } elsif (exists $params->{'itemnumber'}) {
+        unless (looks_like_number($params->{'itemnumber'})) {
+            Koha::Exceptions::BadParameter->throw(
+                error => 'Parameter must be a numeric value.',
+                parameter => 'itemnumber',
+            );
+        }
+        $self->item(Koha::Items->find($params->{'itemnumber'}));
+        unless ($self->item) {
+            Koha::Exceptions::Item::NotFound->throw(
+                error => 'Item not found.',
+                itemnumber => $params->{'itemnumber'},
+            );
+        }
+    } else {
+        Koha::Exceptions::MissingParameter->throw(
+            error => "Missing one of parameters 'itemnumber, 'item'.",
+            parameter => ["itemnumber", "item"],
+        );
+    }
+
+    if (exists $params->{'patron'}) {
+        unless (ref($params->{'patron'}) eq 'Koha::Patron') {
+            Koha::Exceptions::BadParameter->throw(
+                error => 'Parameter must be a Koha::Patron object.',
+                parameter => 'patron',
+            );
+        }
+        $self->patron($params->{'patron'});
+    } elsif (exists $params->{'borrowernumber'}) {
+        unless (looks_like_number($params->{'borrowernumber'})) {
+            Koha::Exceptions::BadParameter->throw(
+                error => 'Parameter must be a numeric value.',
+                parameter => 'borrowernumber',
+            );
+        }
+        $self->patron(Koha::Patrons->find($params->{'borrowernumber'}));
+        unless ($self->patron) {
+            Koha::Exceptions::Patron::NotFound->throw(
+                error => 'Patron not found.',
+                borrowernumber => $params->{'borrowernumber'},
+            );
+        }
+    }
+
+    return $self;
+}
+
+1;
diff --git a/t/db_dependent/Koha/Biblio/Availability.t b/t/db_dependent/Koha/Biblio/Availability.t
new file mode 100644
index 0000000..9e4c412
--- /dev/null
+++ b/t/db_dependent/Koha/Biblio/Availability.t
@@ -0,0 +1,159 @@
+#!/usr/bin/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, see <http://www.gnu.org/licenses>.
+
+use Modern::Perl;
+use Test::More tests => 14;
+use t::lib::TestBuilder;
+
+use Koha::Database;
+use Koha::Biblios;
+
+use Koha::Exceptions;
+
+use_ok('Koha::Biblio::Availability');
+
+my $schema = Koha::Database->new->schema;
+$schema->storage->txn_begin;
+
+my $builder = t::lib::TestBuilder->new;
+my $biblio = Koha::Biblios->find($builder->build({source => 'Biblio'})->{'biblionumber'});
+my $patron = Koha::Patrons->find($builder->build({source => 'Borrower'})->{'borrowernumber'});
+
+subtest 'Check Koha::Biblio::Availability -object default values' => \&check_default_values;
+
+subtest 'Attempt to instantiate holdability class with valid biblio' => sub {
+    plan tests => 1;
+
+    my $availability = Koha::Biblio::Availability->new({ biblio => $biblio });
+    ok($availability->available, 'When I instantiate class with a valid biblio, then it is available.');
+};
+
+subtest 'Attempt to instantiate holdability class with valid biblionumber' => sub {
+    plan tests => 1;
+
+    my $availability = Koha::Biblio::Availability->new({ biblionumber => $biblio->biblionumber });
+    ok($availability->available, 'When I instantiate class with a valid biblionumber, then it is available.');
+};
+
+subtest 'Attempt to instantiate holdability class without specifying an biblio' => sub {
+    plan tests => 2;
+
+    my $availability = eval{Koha::Biblio::Availability->new};
+    ok($@, 'When I instantiate class without giving an biblio,'
+       .' an exception is thrown.');
+    is(ref($@), 'Koha::Exceptions::MissingParameter',
+       'Then I see that Koha::Exceptions::MissingParameter is thrown.');
+};
+
+subtest 'Attempt to instantiate holdability class with notfound biblionumber' => sub {
+    plan tests => 2;
+
+    my $availability = eval{Koha::Biblio::Availability->new({ biblionumber => -9001 })};
+    ok($@, 'When I instantiate class with valid biblionumber that does not'
+       .' refer to any stored biblio, an exception is thrown.');
+    is(ref($@), 'Koha::Exceptions::Biblio::NotFound',
+       'Then I see that Koha::Exceptions::Biblio::NotFound is thrown.');
+};
+
+subtest 'Attempt to instantiate holdability class with invalid biblionumber' => sub {
+    plan tests => 2;
+
+    my $availability = eval{Koha::Biblio::Availability->new({ biblionumber => 'lol' })};
+    ok($@, 'When I instantiate class with invalid biblionumber that is not'
+       .' a number, an exception is thrown.');
+    is(ref($@), 'Koha::Exceptions::BadParameter',
+       'Then I see that Koha::Exceptions::BadParameter is thrown.');
+};
+
+subtest 'Attempt to instantiate holdability class with invalid biblio' => sub {
+    plan tests => 2;
+
+    my $availability = eval{Koha::Biblio::Availability->new({ biblio => {'nonsense'=>'yeah'} })};
+    ok($@, 'When I instantiate class with invalid biblio,'
+       .' an exception is thrown.');
+    is(ref($@), 'Koha::Exceptions::BadParameter',
+       'Then I see that Koha::Exceptions::BadParameter is thrown.');
+};
+
+subtest 'Attempt to instantiate holdability class with valid patron' => sub {
+    plan tests => 1;
+
+    my $availability = Koha::Biblio::Availability->new({ biblio => $biblio, patron => $patron });
+    ok($availability->available, 'When I instantiate class with a valid patron, then it is available.');
+};
+
+subtest 'Attempt to instantiate holdability class with valid borrowernumber' => sub {
+    plan tests => 1;
+
+    my $availability = Koha::Biblio::Availability->new({ biblio => $biblio, borrowernumber => $patron->borrowernumber });
+    ok($availability->available, 'When I instantiate class with a valid borrowernumber, then it is available.');
+};
+
+subtest 'Attempt to instantiate holdability class without specifying a patron' => sub {
+    plan tests => 2;
+
+    my $availability = eval{Koha::Biblio::Availability->new({ biblio => $biblio })};
+    is($availability->available, 1, 'When I request availability without specifying patron,'
+       .' then the biblio is available.');
+    is(ref($@), '', 'Then holdability can be checked without giving a patron.');
+};
+
+subtest 'Attempt to instantiate holdability class with notfound borrowernumber' => sub {
+    plan tests => 2;
+
+    my $availability = eval{Koha::Biblio::Availability->new({ biblio => $biblio, borrowernumber => -9001 })};
+    ok($@, 'When I instantiate class with valid borrowernumber that does not'
+       .' refer to any stored biblio, an exception is thrown.');
+    is(ref($@), 'Koha::Exceptions::Patron::NotFound',
+       'Then I see that Koha::Exceptions::Patron::NotFound is thrown.');
+};
+
+subtest 'Attempt to instantiate holdability class with invalid borrowernumber' => sub {
+    plan tests => 2;
+
+    my $availability = eval{Koha::Biblio::Availability->new({ biblio => $biblio, borrowernumber => 'lol' })};
+    ok($@, 'When I instantiate class with invalid borrowernumber that is not'
+       .' a number, an exception is thrown.');
+    is(ref($@), 'Koha::Exceptions::BadParameter',
+       'Then I see that Koha::Exceptions::BadParameter is thrown.');
+};
+
+subtest 'Attempt to instantiate holdability class with invalid patron' => sub {
+    plan tests => 1;
+
+    my $availability = eval{Koha::Biblio::Availability->new({ biblio => $biblio, patron => $biblio })};
+    is(ref($@), 'Koha::Exceptions::BadParameter', 'Patron not found');
+};
+
+$schema->storage->txn_rollback;
+
+sub check_default_values {
+    plan tests => 7;
+
+    my $availability = Koha::Biblio::Availability->new({ biblio => $biblio});
+    is($availability->available, 1, 'Koha::Biblio::Availability -object is available.');
+    is(keys %{$availability->unavailabilities}, 0, 'There are no unavailabilities.');
+    is(keys %{$availability->confirmations}, 0, 'Nothing needs to be confirmed.');
+    is(keys %{$availability->notes}, 0, 'There are no additional notes.');
+    is(ref($availability->biblio), 'Koha::Biblio', 'This availability is related to an biblio.');
+    is($availability->patron, undef, 'This availability is not related to any patron.');
+    is($availability->expected_available, undef, 'There is no expectation of future availability');
+}
+
+1;
diff --git a/t/db_dependent/Koha/Item/Availability.t b/t/db_dependent/Koha/Item/Availability.t
new file mode 100644
index 0000000..a07a487
--- /dev/null
+++ b/t/db_dependent/Koha/Item/Availability.t
@@ -0,0 +1,159 @@
+#!/usr/bin/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, see <http://www.gnu.org/licenses>.
+
+use Modern::Perl;
+use Test::More tests => 14;
+use t::lib::TestBuilder;
+
+use Koha::Database;
+use Koha::Items;
+
+use Koha::Exceptions;
+
+use_ok('Koha::Item::Availability');
+
+my $schema = Koha::Database->new->schema;
+$schema->storage->txn_begin;
+
+my $builder = t::lib::TestBuilder->new;
+my $item = Koha::Items->find($builder->build({source => 'Item'})->{'itemnumber'});
+my $patron = Koha::Patrons->find($builder->build({source => 'Borrower'})->{'borrowernumber'});
+
+subtest 'Check Koha::Item::Availability -object default values' => \&check_default_values;
+
+subtest 'Attempt to instantiate holdability class with valid item' => sub {
+    plan tests => 1;
+
+    my $availability = Koha::Item::Availability->new({ item => $item });
+    ok($availability->available, 'When I instantiate class with a valid item, then it is available.');
+};
+
+subtest 'Attempt to instantiate holdability class with valid itemnumber' => sub {
+    plan tests => 1;
+
+    my $availability = Koha::Item::Availability->new({ itemnumber => $item->itemnumber });
+    ok($availability->available, 'When I instantiate class with a valid itemnumber, then it is available.');
+};
+
+subtest 'Attempt to instantiate holdability class without specifying an item' => sub {
+    plan tests => 2;
+
+    my $availability = eval{Koha::Item::Availability->new};
+    ok($@, 'When I instantiate class without giving an item,'
+       .' an exception is thrown.');
+    is(ref($@), 'Koha::Exceptions::MissingParameter',
+       'Then I see that Koha::Exceptions::MissingParameter is thrown.');
+};
+
+subtest 'Attempt to instantiate holdability class with notfound itemnumber' => sub {
+    plan tests => 2;
+
+    my $availability = eval{Koha::Item::Availability->new({ itemnumber => -9001 })};
+    ok($@, 'When I instantiate class with valid itemnumber that does not'
+       .' refer to any stored item, an exception is thrown.');
+    is(ref($@), 'Koha::Exceptions::Item::NotFound',
+       'Then I see that Koha::Exceptions::Item::NotFound is thrown.');
+};
+
+subtest 'Attempt to instantiate holdability class with invalid itemnumber' => sub {
+    plan tests => 2;
+
+    my $availability = eval{Koha::Item::Availability->new({ itemnumber => 'lol' })};
+    ok($@, 'When I instantiate class with invalid itemnumber that is not'
+       .' a number, an exception is thrown.');
+    is(ref($@), 'Koha::Exceptions::BadParameter',
+       'Then I see that Koha::Exceptions::BadParameter is thrown.');
+};
+
+subtest 'Attempt to instantiate holdability class with invalid item' => sub {
+    plan tests => 2;
+
+    my $availability = eval{Koha::Item::Availability->new({ item => {'nonsense'=>'yeah'} })};
+    ok($@, 'When I instantiate class with invalid item,'
+       .' an exception is thrown.');
+    is(ref($@), 'Koha::Exceptions::BadParameter',
+       'Then I see that Koha::Exceptions::BadParameter is thrown.');
+};
+
+subtest 'Attempt to instantiate holdability class with valid patron' => sub {
+    plan tests => 1;
+
+    my $availability = Koha::Item::Availability->new({ item => $item, patron => $patron });
+    ok($availability->available, 'When I instantiate class with a valid patron, then it is available.');
+};
+
+subtest 'Attempt to instantiate holdability class with valid borrowernumber' => sub {
+    plan tests => 1;
+
+    my $availability = Koha::Item::Availability->new({ item => $item, borrowernumber => $patron->borrowernumber });
+    ok($availability->available, 'When I instantiate class with a valid borrowernumber, then it is available.');
+};
+
+subtest 'Attempt to instantiate holdability class without specifying a patron' => sub {
+    plan tests => 2;
+
+    my $availability = eval{Koha::Item::Availability->new({ item => $item })};
+    is($availability->available, 1, 'When I request availability without specifying patron,'
+       .' then the item is available.');
+    is(ref($@), '', 'Then holdability can be checked without giving a patron.');
+};
+
+subtest 'Attempt to instantiate holdability class with notfound borrowernumber' => sub {
+    plan tests => 2;
+
+    my $availability = eval{Koha::Item::Availability->new({ item => $item, borrowernumber => -9001 })};
+    ok($@, 'When I instantiate class with valid borrowernumber that does not'
+       .' refer to any stored item, an exception is thrown.');
+    is(ref($@), 'Koha::Exceptions::Patron::NotFound',
+       'Then I see that Koha::Exceptions::Patron::NotFound is thrown.');
+};
+
+subtest 'Attempt to instantiate holdability class with invalid borrowernumber' => sub {
+    plan tests => 2;
+
+    my $availability = eval{Koha::Item::Availability->new({ item => $item, borrowernumber => 'lol' })};
+    ok($@, 'When I instantiate class with invalid borrowernumber that is not'
+       .' a number, an exception is thrown.');
+    is(ref($@), 'Koha::Exceptions::BadParameter',
+       'Then I see that Koha::Exceptions::BadParameter is thrown.');
+};
+
+subtest 'Attempt to instantiate holdability class with invalid patron' => sub {
+    plan tests => 1;
+
+    my $availability = eval{Koha::Item::Availability->new({ item => $item, patron => $item })};
+    is(ref($@), 'Koha::Exceptions::BadParameter', 'Patron not found');
+};
+
+$schema->storage->txn_rollback;
+
+sub check_default_values {
+    plan tests => 7;
+
+    my $availability = Koha::Item::Availability->new({ item => $item});
+    is($availability->available, 1, 'Koha::Item::Availability -object is available.');
+    is(keys %{$availability->unavailabilities}, 0, 'There are no unavailabilities.');
+    is(keys %{$availability->confirmations}, 0, 'Nothing needs to be confirmed.');
+    is(keys %{$availability->notes}, 0, 'There are no additional notes.');
+    is(ref($availability->item), 'Koha::Item', 'This availability is related to an item.');
+    is($availability->patron, undef, 'This availability is not related to any patron.');
+    is($availability->expected_available, undef, 'There is no expectation of future availability');
+}
+
+1;
-- 
1.9.1