View | Details | Raw Unified | Return to bug 17712
Collapse All | Expand All

(-)a/Koha/Availability/Hold.pm (+38 lines)
Line 0 Link Here
1
package Koha::Availability::Hold;
2
3
# Copyright Koha-Suomi Oy 2016
4
#
5
# This file is part of Koha
6
#
7
# Koha is free software; you can redistribute it and/or modify it
8
# under the terms of the GNU General Public License as published by
9
# the Free Software Foundation; either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# Koha is distributed in the hope that it will be useful, but
13
# WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General Public License
18
# along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20
use Modern::Perl;
21
22
use Koha::Item::Availability::Hold;
23
24
use Koha::Exceptions;
25
26
sub new {
27
    my ($class, $params) = @_;
28
29
    bless $params, $class;
30
}
31
32
sub item {
33
    my ($self, $params) = @_;
34
35
    return Koha::Item::Availability::Hold->new($params);
36
}
37
38
1;
(-)a/Koha/Item/Availability/Hold.pm (+364 lines)
Line 0 Link Here
1
package Koha::Item::Availability::Hold;
2
3
# Copyright Koha-Suomi Oy 2016
4
#
5
# This file is part of Koha
6
#
7
# Koha is free software; you can redistribute it and/or modify it
8
# under the terms of the GNU General Public License as published by
9
# the Free Software Foundation; either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# Koha is distributed in the hope that it will be useful, but
13
# WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General Public License
18
# along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20
use Modern::Perl;
21
22
use base qw(Koha::Item::Availability);
23
24
use Koha::Items;
25
use Koha::Patrons;
26
27
use Koha::Availability::Checks::Biblio;
28
use Koha::Availability::Checks::Biblioitem;
29
use Koha::Availability::Checks::IssuingRule;
30
use Koha::Availability::Checks::Item;
31
use Koha::Availability::Checks::LibraryItemRule;
32
use Koha::Availability::Checks::Patron;
33
34
=head1 NAME
35
36
Koha::Item::Availability::Hold - Koha Item Availability Hold object class
37
38
=head1 SYNOPSIS
39
40
my $holdability = Koha::Item::Availability::Hold->new({
41
    item => $item,               # which item this availability is for
42
    patron => $patron,           # check item availability for this patron
43
})
44
45
=head1 DESCRIPTION
46
47
Class for checking item hold availability.
48
49
This class contains different levels of "recipes" that determine whether or not
50
an item should be considered available.
51
52
=head2 Class Methods
53
54
=cut
55
56
=head3 new
57
58
Constructs an item hold availability object. Item is always required. Patron is
59
required if patron related checks are needed.
60
61
MANDATORY PARAMETERS
62
63
    item (or itemnumber)
64
65
Item is a Koha::Item -object.
66
67
OPTIONAL PARAMETERS
68
69
    patron (or borrowernumber)
70
    to_branch
71
72
Patron is a Koha::Patron -object. To_branch is a branchcode of pickup library.
73
74
Returns a Koha::Item::Availability::Hold -object.
75
76
=cut
77
78
sub new {
79
    my ($class, $params) = @_;
80
81
    my $self = $class->SUPER::new($params);
82
83
    # Additionally, consider any transfer limits to pickup library by
84
    # providing to_branch parameter with branchcode of pickup library
85
    $self->{'to_branch'} = $params->{'to_branch'};
86
87
    return $self;
88
}
89
90
sub in_intranet {
91
    my ($self) = @_;
92
    my $reason;
93
94
    $self->reset;
95
96
    my $item = $self->item;
97
    my $patron;
98
    unless ($patron = $self->patron) {
99
        Koha::Exceptions::MissingParameter->throw(
100
            error => 'Missing parameter patron. This level of availability query '
101
            .'requires Koha::Item::Availability::Hold to have a patron parameter.'
102
        );
103
    }
104
105
    $self->common_biblio_checks;
106
    $self->common_biblioitem_checks;
107
    $self->common_issuing_rule_checks;
108
    $self->common_item_checks;
109
    $self->common_library_item_rule_checks;
110
    $self->common_patron_checks;
111
112
    # Additionally, a librarian can override any unavailabilities if system
113
    # preference AllowHoldPolicyOverride is enabled
114
    if (C4::Context->preference('AllowHoldPolicyOverride')) {
115
        # Copy unavailabilities to reasons to ask for confirmation, and reset
116
        # reasons of unavailabilities
117
        $self->confirmations({ %{$self->unavailabilities}, %{$self->confirmations} });
118
        $self->unavailabilities({});
119
        $self->available(1);
120
    }
121
122
    return $self;
123
}
124
125
sub in_opac {
126
    my ($self) = @_;
127
    my $reason;
128
129
    $self->reset;
130
131
    my $item = $self->item;
132
    my $patron;
133
    unless ($patron = $self->patron) {
134
        Koha::Exceptions::MissingParameter->throw(
135
            error => 'Missing parameter patron. This level of availability query '
136
            .'requires Koha::Item::Availability::Hold to have a patron parameter.'
137
        );
138
    }
139
140
    # Check if holds are allowed in OPAC
141
    if (!C4::Context->preference('RequestOnOpac')) {
142
        $self->unavailable(Koha::Exceptions::Hold::NotAllowedInOPAC->new);
143
        return $self;
144
    }
145
146
    $self->common_biblio_checks;
147
    $self->common_biblioitem_checks;
148
    $self->common_issuing_rule_checks;
149
    $self->common_item_checks;
150
    $self->common_library_item_rule_checks;
151
    $self->common_patron_checks;
152
    $self->opac_specific_issuing_rule_checks;
153
154
    return $self;
155
}
156
157
=head3 common_biblio_checks
158
159
Common checks for both OPAC and intranet.
160
161
=cut
162
163
sub common_biblio_checks {
164
    my ($self, $biblio) = @_;
165
    my $reason;
166
167
    unless ($biblio) {
168
        $biblio = Koha::Biblios->find($self->item->biblionumber);
169
    }
170
171
    my $bibcalc = Koha::Availability::Checks::Biblio->new($biblio);
172
173
    if ($reason = $bibcalc->forbid_holds_on_patrons_possessions($self->patron)) {
174
        $self->unavailable($reason);
175
    }
176
177
    return $self;
178
}
179
180
=head3 common_biblioitem_checks
181
182
Common checks for both OPAC and intranet.
183
184
=cut
185
186
sub common_biblioitem_checks {
187
    my ($self, $bibitem) = @_;
188
    my $reason;
189
190
    unless ($bibitem) {
191
        $bibitem = Koha::Biblioitems->find($self->item->biblioitemnumber);
192
    }
193
194
    my $bibitemcalc = Koha::Availability::Checks::Biblioitem->new($bibitem);
195
196
    if ($reason = $bibitemcalc->age_restricted($self->patron)) {
197
        $self->unavailable($reason);
198
    }
199
200
    return $self;
201
}
202
203
=head3 common_issuing_rule_checks
204
205
Common checks for both OPAC and intranet.
206
207
=cut
208
209
sub common_issuing_rule_checks {
210
    my ($self, $params) = @_;
211
    my $reason;
212
213
    my $item = $self->item;
214
    my $patron = $self->patron;
215
    my $branchcode = $params->{'branchcode'} ? $params->{'branchcode'}
216
                : $self->_get_reservescontrol_branchcode($item, $patron);
217
    my $holdrulecalc = Koha::Availability::Checks::IssuingRule->new({
218
        item => $item,
219
        patron => $patron,
220
        branchcode => $branchcode,
221
        use_cache => $params->{'use_cache'},
222
    });
223
224
    if ($reason = $holdrulecalc->zero_holds_allowed) {
225
        $self->unavailable($reason);
226
    } else {
227
        if ($reason = $holdrulecalc->maximum_holds_reached) {
228
            $self->unavailable($reason);
229
        }
230
        if ($reason = $holdrulecalc->maximum_holds_for_record_reached($params)) {
231
            $self->unavailable($reason);
232
        }
233
    }
234
235
    return $self;
236
}
237
238
=head3 common_item_checks
239
240
Common checks for both OPAC and intranet.
241
242
=cut
243
244
sub common_item_checks {
245
    my ($self, $params) = @_;
246
    my $reason;
247
248
    my $item = $self->item;
249
    my $patron = $self->patron;
250
    my $itemcalc = Koha::Availability::Checks::Item->new($item);
251
252
    $self->unavailable($reason) if $reason = $itemcalc->damaged;
253
    $self->unavailable($reason) if $reason = $itemcalc->lost;
254
    $self->unavailable($reason) if $reason = $itemcalc->restricted;
255
    $self->unavailable($reason) if $reason = $itemcalc->unknown_barcode;
256
    $self->unavailable($reason) if $reason = $itemcalc->withdrawn;
257
    if ($reason = $itemcalc->notforloan) {
258
        unless ($reason->status < 0) {
259
            $self->unavailable($reason);
260
        } else {
261
            $self->note($reason);
262
        }
263
    }
264
    $self->unavailable($reason) if $reason = $itemcalc->held_by_patron($patron, $params);
265
    $self->unavailable($reason) if $reason = $itemcalc->from_another_library;
266
    if ($self->to_branch && ($reason = $itemcalc->transfer_limit($self->to_branch))) {
267
        $self->unavailable($reason);
268
    }
269
270
    return $self;
271
}
272
273
=head3 common_library_item_rule_checks
274
275
Common checks for both OPAC and intranet.
276
277
=cut
278
279
sub common_library_item_rule_checks {
280
    my ($self) = @_;
281
    my $reason;
282
283
    my $item = $self->item;
284
    my $patron = $self->patron;
285
    my $libitemrule = Koha::Availability::Checks::LibraryItemRule->new({
286
        item => $item,
287
        patron => $patron,
288
    });
289
290
    if ($reason = $libitemrule->hold_not_allowed_by_library) {
291
        $self->unavailable($reason);
292
    } elsif ($reason = $libitemrule->hold_not_allowed_by_other_library) {
293
        $self->unavailable($reason);
294
    }
295
296
    return $self;
297
}
298
299
=head3 common_patron_checks
300
301
Common checks for both OPAC and intranet.
302
303
=cut
304
305
sub common_patron_checks {
306
    my ($self) = @_;
307
    my $reason;
308
309
    my $patron = $self->patron;
310
    my $patroncalc = Koha::Availability::Checks::Patron->new($patron);
311
312
    $self->unavailable($reason) if $reason = $patroncalc->debt_hold;
313
    $self->unavailable($reason) if $reason = $patroncalc->debarred;
314
    $self->unavailable($reason) if $reason = $patroncalc->exceeded_maxreserves;
315
    $self->unavailable($reason) if $reason = $patroncalc->gonenoaddress;
316
    $self->unavailable($reason) if $reason = $patroncalc->lost;
317
    if (($reason = $patroncalc->expired)
318
        && C4::Context->preference('BlockExpiredPatronOpacActions')) {
319
        $self->unavailable($reason);
320
    }
321
322
    return $self;
323
}
324
325
=head3 opac_specific_checks
326
327
OPAC-specific holdability checks.
328
329
=cut
330
331
sub opac_specific_issuing_rule_checks {
332
    my ($self, $branchcode) = @_;
333
    my $reason;
334
335
    my $item = $self->item;
336
    my $patron = $self->patron;
337
    $branchcode ||= $self->_get_reservescontrol_branchcode($item, $patron);
338
    my $holdrulecalc = Koha::Availability::Checks::IssuingRule->new({
339
        item => $item,
340
        patron => $patron,
341
        branchcode => $branchcode,
342
    });
343
    if ($reason = $holdrulecalc->opac_item_level_hold_forbidden) {
344
        $self->unavailable($reason);
345
    }
346
347
    return $self;
348
}
349
350
351
sub _get_reservescontrol_branchcode {
352
    my ($self, $item, $patron) = @_;
353
354
    my $branchcode;
355
    my $controlbranch = C4::Context->preference('ReservesControlBranch');
356
    if ($patron && $controlbranch eq 'PatronLibrary') {
357
        $branchcode = $patron->branchcode;
358
    } elsif ($item && $controlbranch eq 'ItemHomeLibrary') {
359
        $branchcode = $item->homebranch;
360
    }
361
    return $branchcode;
362
}
363
364
1;
(-)a/t/db_dependent/Koha/Item/Availability/Hold/Intranet/HoldPolicyOverride.t (+90 lines)
Line 0 Link Here
1
#!/usr/bin/perl
2
3
# Copyright Koha-Suomi Oy 2016
4
#
5
# This file is part of Koha
6
#
7
# Koha is free software; you can redistribute it and/or modify it
8
# under the terms of the GNU General Public License as published by
9
# the Free Software Foundation; either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# Koha is distributed in the hope that it will be useful, but
13
# WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General Public License
18
# along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20
use Modern::Perl;
21
use Test::More tests => 9;
22
use t::lib::Mocks;
23
use t::lib::TestBuilder;
24
require t::db_dependent::Koha::Availability::Helpers;
25
26
use Koha::Database;
27
use Koha::IssuingRules;
28
use Koha::Items;
29
use Koha::ItemTypes;
30
31
use Koha::Item::Availability::Hold;
32
33
my $schema = Koha::Database->new->schema;
34
$schema->storage->txn_begin;
35
36
my $builder = t::lib::TestBuilder->new;
37
38
set_default_system_preferences();
39
set_default_circulation_rules();
40
41
t::lib::Mocks::mock_preference('AllowHoldPolicyOverride', 1);
42
t::lib::Mocks::mock_preference('AllowHoldsOnDamagedItems', 0);
43
44
# Create test item and patron, and add some reasons that will be need confirmation
45
my $item = build_a_test_item();
46
$item->set({
47
    barcode => '',
48
    damaged => 1,
49
    itemlost => 1,
50
    notforloan => 1,
51
    restricted => 1,
52
    withdrawn => 1,
53
})->store; # 6 reasons
54
my $patron = build_a_test_patron();
55
Koha::Account::Line->new({
56
    borrowernumber => $patron->borrowernumber,
57
    amountoutstanding => 999999999,
58
    accounttype => 'F',
59
})->store; # 1 reason
60
61
Koha::IssuingRules->search->delete;
62
my $rule = Koha::IssuingRule->new({
63
    branchcode   => $item->homebranch,
64
    itemtype     => $item->effective_itemtype,
65
    categorycode => '*',
66
    holds_per_record => 0,
67
    reservesallowed => 0,
68
})->store; # 1 reason
69
my $availability = Koha::Item::Availability::Hold->new({item => $item, patron => $patron})->in_intranet;
70
ok($availability->can('in_intranet'), 'Attempt to check availability in intranet'
71
   .' while considering AllowHoldPolicyOverride system preference.');
72
is(C4::Context->preference('AllowHoldPolicyOverride'), 1, 'Given librarians are '
73
   .'allowed to override hold policy restrictions.');
74
ok($availability->available, 'When librarian checks item availability for '
75
   .'patron, they see that the status is available.');
76
ok(!$availability->unavailable, 'There are no reasons for unavailability.');
77
is($availability->confirm, 8, 'There are 8 things to be confirmed.');
78
79
t::lib::Mocks::mock_preference('AllowHoldPolicyOverride', 0);
80
$availability = Koha::Item::Availability::Hold->new({item => $item, patron => $patron})->in_intranet;
81
is(C4::Context->preference('AllowHoldPolicyOverride'), 0, 'Changed setting - '
82
   .' librarians are no long allowed to override hold policy restrictions.');
83
ok(!$availability->available, 'When librarian checks item availability for '
84
   .'patron, they see that the it is NOT available.');
85
ok(!$availability->confirm, 'There are no to be confirmed.');
86
is($availability->unavailable, 8, 'There are 8 reasons for unavailability.');
87
88
$schema->storage->txn_rollback;
89
90
1;
(-)a/t/db_dependent/Koha/Item/Availability/Hold/Opac/HoldRules.t (+290 lines)
Line 0 Link Here
1
#!/usr/bin/perl
2
3
# Copyright Koha-Suomi Oy 2016
4
#
5
# This file is part of Koha
6
#
7
# Koha is free software; you can redistribute it and/or modify it
8
# under the terms of the GNU General Public License as published by
9
# the Free Software Foundation; either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# Koha is distributed in the hope that it will be useful, but
13
# WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General Public License
18
# along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20
use Modern::Perl;
21
use Test::More tests => 5;
22
use t::lib::Mocks;
23
use t::lib::TestBuilder;
24
require t::db_dependent::Koha::Availability::Helpers;
25
26
use Koha::Database;
27
use Koha::IssuingRules;
28
use Koha::Items;
29
use Koha::ItemTypes;
30
31
use Koha::Item::Availability::Hold;
32
33
my $schema = Koha::Database->new->schema;
34
$schema->storage->txn_begin;
35
36
my $builder = t::lib::TestBuilder->new;
37
38
set_default_system_preferences();
39
set_default_circulation_rules();
40
41
subtest 'Given there are no hold rules blocking a hold from me' => \&t_hold_rules_nothing_blocking;
42
sub t_hold_rules_nothing_blocking {
43
    plan tests => 7;
44
45
    my $item = build_a_test_item();
46
    my $patron = build_a_test_patron();
47
    Koha::IssuingRules->search->delete;
48
    my $rule = Koha::IssuingRule->new({
49
        branchcode   => $patron->branchcode,
50
        itemtype     => $item->effective_itemtype,
51
        categorycode => '*',
52
        holds_per_record => 1,
53
        reservesallowed => 1,
54
        opacitemholds => 'Y',
55
    })->store;
56
57
    my $availability = Koha::Item::Availability::Hold->new({item => $item, patron => $patron})->in_opac;
58
59
    is($rule->reservesallowed, 1, 'As I look at hold rules, I match a rule says reservesallowed is 1.');
60
    is($rule->holds_per_record, 1, 'This rule also says holds_per_record is 1.');
61
    is($rule->opacitemholds, 'Y', 'This rule also says OPAC item holds are allowed.');
62
    ok($availability->available, 'When they request availability, then the item is available.');
63
    ok(!$availability->unavailable, 'Then there are no reasons for unavailability.');
64
    ok(!$availability->confirm, 'Then there is nothing to be confirmed.');
65
    ok(!$availability->note, 'Then there are no additional availability notes.');
66
};
67
68
subtest 'Given zero holds are allowed' => sub {
69
    plan tests => 4;
70
71
    my $item = build_a_test_item();
72
    my $patron = build_a_test_patron();
73
    Koha::IssuingRules->search->delete;
74
    my $rule = Koha::IssuingRule->new({
75
        branchcode   => '*',
76
        itemtype     => '*',
77
        categorycode => '*',
78
        holds_per_record => 0,
79
        reservesallowed => 0,
80
        opacitemholds => 'Y',
81
    })->store;
82
83
    sub t_zero_holds_allowed {
84
        my ($item, $patron, $rule) = @_;
85
86
        my $availability = Koha::Item::Availability::Hold->new({item => $item, patron => $patron})->in_opac;
87
        my $expecting = 'Koha::Exceptions::Hold::ZeroHoldsAllowed';
88
89
        is($rule->reservesallowed, 0, 'As I study this rule, it says zero reserves are allowed.');
90
        ok(!$availability->available, 'When I request availability, then the item is not available.');
91
        ok(!$availability->confirm, 'Then there are nothing to be confirmed.');
92
        ok(!$availability->note, 'Then there are no additional notes.');
93
        is($availability->unavailable, 1, 'Then there is only one reason for unavailability.');
94
        is(ref($availability->unavailabilities->{$expecting}), $expecting,
95
            'Then there is an unavailability status indicating that holds are not allowed at all.');
96
    }
97
    subtest '...on any item type or in any library' => sub {
98
        plan tests => 6;
99
        \&t_zero_holds_allowed($item, $patron, $rule);
100
    };
101
    subtest '...in item home library' => sub {
102
        plan tests => 2;
103
104
        subtest '...while ReservesControlBranch = ItemHomeLibrary' => sub {
105
            plan tests => 7;
106
107
            t::lib::Mocks::mock_preference('ReservesControlBranch', 'ItemHomeLibrary');
108
            $rule->branchcode($item->homebranch)->store;
109
            is($rule->branchcode, $item->homebranch, 'There is a hold rule matching item homebranch.');
110
            t_zero_holds_allowed($item, $patron, $rule);
111
            $rule->branchcode('*')->store;
112
            set_default_system_preferences();
113
        };
114
        subtest '...while ReservesControlBranch = PatronLibrary' => sub {
115
            plan tests => 7;
116
117
            t::lib::Mocks::mock_preference('ReservesControlBranch', 'PatronLibrary');
118
            $rule->branchcode($item->homebranch)->store;
119
120
            my $availability = Koha::Item::Availability::Hold->new({item => $item, patron => $patron})->in_opac;
121
122
            is($rule->branchcode, $item->homebranch, 'There is a hold rule matching item homebranch.');
123
            is($rule->reservesallowed, 0, 'As I study this rule, it says zero reserves are allowed.');
124
            is(C4::Context->preference('ReservesControlBranch'), 'PatronLibrary', 'However, system preference '
125
               .'ReserveControlBranch says we should use PatronLibrary for matching hold rules.');
126
            ok($availability->available, 'When I availability, then the item is available.');
127
            ok(!$availability->confirm, 'Then there are nothing to be confirmed.');
128
            ok(!$availability->note, 'Then there are no additional notes.');
129
            ok(!$availability->unavailable, 'Then there are no reasons for unavailability.');
130
131
            $rule->branchcode('*')->store;
132
        };
133
    };
134
    subtest '...in patron library' => sub {
135
        plan tests => 2;
136
137
        subtest '...while ReservesControlBranch = ItemHomeLibrary' => sub {
138
            plan tests => 7;
139
140
            t::lib::Mocks::mock_preference('ReservesControlBranch', 'ItemHomeLibrary');
141
            $rule->branchcode($patron->branchcode)->store;
142
143
            my $availability = Koha::Item::Availability::Hold->new({item => $item, patron => $patron})->in_opac;
144
145
            is($rule->branchcode, $patron->branchcode, 'There is a hold rule matching patron branchcode.');
146
            is($rule->reservesallowed, 0, 'As I study this rule, it says zero reserves are allowed.');
147
            is(C4::Context->preference('ReservesControlBranch'), 'ItemHomeLibrary', 'However, system preference '
148
               .'ReserveControlBranch says we should use ItemHomeLibrary for matching hold rules.');
149
            ok($availability->available, 'When I availability, then the item is available.');
150
            ok(!$availability->confirm, 'Then there are nothing to be confirmed.');
151
            ok(!$availability->note, 'Then there are no additional notes.');
152
            ok(!$availability->unavailable, 'Then there are no reasons for unavailability.');
153
154
            $rule->branchcode('*')->store;
155
        };
156
        subtest '...while ReservesControlBranch = PatronLibrary' => sub {
157
            plan tests => 6;
158
159
            t::lib::Mocks::mock_preference('ReservesControlBranch', 'PatronLibrary');
160
            $rule->branchcode($patron->branchcode)->store;
161
162
            my $availability = Koha::Item::Availability::Hold->new({item => $item, patron => $patron})->in_opac;
163
            my $expecting = 'Koha::Exceptions::Hold::ZeroHoldsAllowed';
164
165
            is($rule->branchcode, $patron->branchcode, 'There is a hold rule matching patron branchcode.');
166
            is($rule->reservesallowed, 0, 'As I study this rule, it says zero reserves are allowed.');
167
            ok(!$availability->available, 'When I request availability, then the item is not available.');
168
            ok(!$availability->confirm,  'Then there are nothing to be confirmed.');
169
            ok(!$availability->note, 'Then there are no additional notes.');
170
            is($availability->unavailable, 1, 'Then there is one reason for unavailability.');
171
172
            $rule->branchcode('*')->store;
173
        };
174
    };
175
176
    subtest '...on effective item type' => sub {
177
        plan tests => 7;
178
        $rule->itemtype($item->effective_itemtype)->store;
179
        is($rule->itemtype, $item->effective_itemtype, 'There is a hold rule matching effective itemtype.');
180
        t_zero_holds_allowed($item, $patron, $rule);
181
        $rule->itemtype('*')->store;
182
    };
183
};
184
185
subtest 'Given OPAC item holds are not allowed' => \&t_opac_item_hold_not_allowed;
186
sub t_opac_item_hold_not_allowed {
187
    plan tests => 6;
188
189
    my $item = build_a_test_item();
190
    my $patron = build_a_test_patron();
191
    Koha::IssuingRules->search->delete;
192
    my $rule = Koha::IssuingRule->new({
193
        branchcode   => '*',
194
        itemtype     => '*',
195
        categorycode => '*',
196
        holds_per_record => 1,
197
        reservesallowed => 1,
198
        opacitemholds => 'N',
199
    })->store;
200
201
    is($rule->opacitemholds, 'N', 'As I look at issuing rules, I find out that OPAC item holds are not allowed.');
202
203
    my $availability = Koha::Item::Availability::Hold->new({item => $item, patron => $patron})->in_opac;
204
    my $expecting = 'Koha::Exceptions::Hold::ItemLevelHoldNotAllowed';
205
206
    ok(!$availability->available, 'When I request availability, then the item is not available.');
207
    ok(!$availability->confirm, 'Then there are nothing to be confirmed.');
208
    ok(!$availability->note, 'Then there are no additional notes.');
209
    is($availability->unavailable, 1, 'Then there is only one reason for unavailability.');
210
    is(ref($availability->unavailabilities->{$expecting}), $expecting,
211
        'Then there is an unavailability status indicating that item level holds are not allowed.');
212
};
213
214
subtest 'Given I have too many holds in my library' => \&t_too_many_holds_patron_library;
215
sub t_too_many_holds_patron_library {
216
    plan tests => 8;
217
218
    t::lib::Mocks::mock_preference('ReservesControlBranch', 'PatronLibrary');
219
    my $patron = build_a_test_patron();
220
    my $item = build_a_test_item();
221
    my $item2 = build_a_test_item();
222
    $item->homebranch($item2->homebranch)->store;
223
    $item->itype($item2->itype)->store;
224
    my $reserve_id = add_item_level_hold($item2, $patron, $item2->homebranch);
225
    my $hold = Koha::Holds->find({ borrowernumber => $patron->borrowernumber });
226
    Koha::IssuingRules->search->delete;
227
    my $rule = Koha::IssuingRule->new({
228
        branchcode   => $patron->branchcode,
229
        itemtype     => $item->effective_itemtype,
230
        categorycode => $patron->categorycode,
231
        holds_per_record => 3,
232
        reservesallowed => 1,
233
        opacitemholds => 'Y',
234
    })->store;
235
    my $availability = Koha::Item::Availability::Hold->new({item => $item, patron => $patron})->in_opac;
236
    my $expecting = 'Koha::Exceptions::Hold::MaximumHoldsReached';
237
238
    is(C4::Context->preference('ReservesControlBranch'), 'PatronLibrary', 'We will be checking my library\'s rules for holdability.');
239
    is($rule->reservesallowed, 1, 'As I look at circulation rules, I can see that only one reserve is allowed.');
240
    is($hold->reserve_id, $reserve_id, 'I have placed one hold already.');
241
    ok(!$availability->available, 'When I request availability, then the item is not available.');
242
    is($availability->unavailable, 1, 'Then there is only one reason for unavailability.');
243
    is(ref($availability->unavailabilities->{$expecting}), $expecting,
244
        'Then there is an unavailability status indicating that maximum holds have been reached.');
245
246
    my $ex = $availability->unavailabilities->{$expecting};
247
    is($ex->max_holds_allowed, 1, 'Then, from the status, I can see the maximum holds allowed.');
248
    is($ex->current_hold_count, 1, 'Then, from the status, I can see my current hold count.');
249
};
250
251
subtest 'Given I have too many holds in item\'s library' => \&t_too_many_holds_item_home_library ;
252
sub t_too_many_holds_item_home_library {
253
    plan tests => 8;
254
255
    t::lib::Mocks::mock_preference('ReservesControlBranch', 'ItemHomeLibrary');
256
    my $patron = build_a_test_patron();
257
    my $item = build_a_test_item();
258
    my $item2 = build_a_test_item();
259
    $item->homebranch($item2->homebranch)->store;
260
    $item->itype($item2->itype)->store;
261
    my $reserve_id = add_item_level_hold($item2, $patron, $item2->homebranch);
262
    my $hold = Koha::Holds->find({ borrowernumber => $patron->borrowernumber });
263
    Koha::IssuingRules->search->delete;
264
    my $rule = Koha::IssuingRule->new({
265
        branchcode   => $item2->homebranch,
266
        itemtype     => $item2->effective_itemtype,
267
        categorycode => $patron->categorycode,
268
        holds_per_record => 3,
269
        reservesallowed => 1,
270
        opacitemholds => 'Y',
271
    })->store;
272
    my $availability = Koha::Item::Availability::Hold->new({item => $item, patron => $patron})->in_opac;
273
    my $expecting = 'Koha::Exceptions::Hold::MaximumHoldsReached';
274
275
    is(C4::Context->preference('ReservesControlBranch'), 'ItemHomeLibrary', 'We will be checking item\'s home library rules for holdability.');
276
    is($rule->reservesallowed, 1, 'As I look at circulation rules, I can see that only one reserve is allowed.');
277
    is($hold->reserve_id, $reserve_id, 'I have placed one hold already.');
278
    ok(!$availability->available, 'When I request availability, then the item is not available.');
279
    is($availability->unavailable, 1, 'Then there is only one reason for unavailability.');
280
    is(ref($availability->unavailabilities->{$expecting}), $expecting,
281
        'Then there is an unavailability status indicating that maximum holds have been reached.');
282
283
    my $ex = $availability->unavailabilities->{$expecting};
284
    is($ex->max_holds_allowed, 1, 'Then, from the status, I can see the maximum holds allowed.');
285
    is($ex->current_hold_count, 1, 'Then, from the status, I can see my current hold count.');
286
};
287
288
$schema->storage->txn_rollback;
289
290
1;
(-)a/t/db_dependent/Koha/Item/Availability/Hold/Opac/ItemStatus.t (+371 lines)
Line 0 Link Here
1
#!/usr/bin/perl
2
3
# Copyright Koha-Suomi Oy 2016
4
#
5
# This file is part of Koha
6
#
7
# Koha is free software; you can redistribute it and/or modify it
8
# under the terms of the GNU General Public License as published by
9
# the Free Software Foundation; either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# Koha is distributed in the hope t1hat it will be useful, but
13
# WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General Public License
18
# along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20
use Modern::Perl;
21
use Test::More tests => 14;
22
use t::lib::Mocks;
23
use t::lib::TestBuilder;
24
require t::db_dependent::Koha::Availability::Helpers;
25
26
use Koha::Database;
27
use Koha::IssuingRules;
28
use Koha::Items;
29
use Koha::ItemTypes;
30
31
use Koha::Item::Availability::Hold;
32
33
my $schema = Koha::Database->new->schema;
34
$schema->storage->txn_begin;
35
36
my $builder = t::lib::TestBuilder->new;
37
38
set_default_system_preferences();
39
set_default_circulation_rules();
40
41
subtest 'Given item is in a good state for availability' => \&t_ok_availability;
42
sub t_ok_availability {
43
    plan tests => 3;
44
45
    my $patron = build_a_test_patron();
46
    my $item = build_a_test_item();
47
    my $availability = Koha::Item::Availability::Hold->new({item => $item, patron => $patron})->in_opac;
48
49
    ok($availability->available, 'When I request availability, then the item is available.');
50
    ok(!$availability->confirm, 'Then nothing needs to be confirmed.');
51
    ok(!$availability->unavailable, 'Then there are no reasons to be unavailable.');
52
}
53
54
subtest 'Given item is damaged' => sub {
55
    plan tests => 2;
56
57
    subtest 'Given AllowHoldsOnDamagedItems is disabled' => \&t_damaged_item_allow_disabled;
58
    subtest 'Given AllowHoldsOnDamagedItems is enabled' => \&t_damaged_item_allow_enabled;
59
    sub t_damaged_item_allow_disabled {
60
        plan tests => 4;
61
62
        t::lib::Mocks::mock_preference('AllowHoldsOnDamagedItems', 0);
63
64
        my $patron = build_a_test_patron();
65
        my $item = build_a_test_item()->set({damaged=>1})->store;
66
        my $availability = Koha::Item::Availability::Hold->new({item => $item, patron => $patron})->in_opac;
67
        my $expecting = 'Koha::Exceptions::Item::Damaged';
68
69
        is($item->damaged, 1, 'When I look at the item, I see that it is damaged.');
70
        ok(!$availability->available, 'When I request availability, then the item is not available.');
71
        is($availability->unavailable, 1, 'Then there is only one unavailability reason.');
72
        is(ref($availability->unavailabilities->{$expecting}), $expecting,
73
            'Then there is an unavailability status indicating damaged item.');
74
    };
75
    sub t_damaged_item_allow_enabled {
76
        plan tests => 4;
77
78
        t::lib::Mocks::mock_preference('AllowHoldsOnDamagedItems', 1);
79
80
        my $patron = build_a_test_patron();
81
        my $item = build_a_test_item()->set({damaged=>1})->store;
82
        my $availability = Koha::Item::Availability::Hold->new({item => $item, patron => $patron})->in_opac;
83
84
        is($item->damaged, 1, 'When I look at the item, I see that it is damaged.');
85
        ok($availability->available, 'When I request availability, then the item is available.');
86
        ok(!$availability->unavailable, 'Then there are no statuses for unavailability.');
87
        ok(!$availability->confirm, 'Then there is no reason to have availability confirmed.');
88
    };
89
};
90
91
subtest 'Given item is lost' => \&t_lost;
92
sub t_lost {
93
    plan tests => 4;
94
95
    my $patron = build_a_test_patron();
96
    my $item = build_a_test_item()->set({itemlost=>1})->store;
97
    my $availability = Koha::Item::Availability::Hold->new({item => $item, patron => $patron})->in_opac;
98
    my $expecting = 'Koha::Exceptions::Item::Lost';
99
100
    is($item->itemlost, 1, 'When I try to look at the item, I find out that it is lost.');
101
    ok(!$availability->available, 'When I request availability, then the item is not available.');
102
    is($availability->unavailable, 1, 'Then there is only one unavailability reason.');
103
    is(ref($availability->unavailabilities->{$expecting}), $expecting,
104
        'Then there is an unavailability status indicating lost item.');
105
};
106
107
subtest 'Given item is not for loan' => \&t_notforloan;
108
sub t_notforloan {
109
    plan tests => 4;
110
111
    my $patron = build_a_test_patron();
112
    my $item = build_a_test_item()->set({notforloan=>1})->store;
113
    my $availability = Koha::Item::Availability::Hold->new({item => $item, patron => $patron})->in_opac;
114
    my $expecting = 'Koha::Exceptions::Item::NotForLoan';
115
116
    is($item->notforloan, 1, 'When I look at the item, I see that it is not for loan.');
117
    ok(!$availability->available, 'When I request availability, then the item is not available.');
118
    is($availability->unavailable, 1, 'Then there is only one unavailability reason.');
119
    is(ref($availability->unavailabilities->{$expecting}), $expecting,
120
        'Then there is an unavailability status indicating the item is not for loan.');
121
};
122
123
subtest 'Given item type is not for loan' => sub {
124
125
    subtest 'Given item-level_itypes is on (item-itemtype)' => \&t_itemlevel_itemtype_notforloan_item_level_itypes_on;
126
    subtest 'Given item-level_itypes is off (biblioitem-itemtype)' => \&t_itemlevel_itemtype_notforloan_item_level_itypes_off;
127
    sub t_itemlevel_itemtype_notforloan_item_level_itypes_on {
128
        plan tests => 5;
129
130
        t::lib::Mocks::mock_preference('item-level_itypes', 1);
131
132
        my $patron = build_a_test_patron();
133
        my $item = build_a_test_item();
134
        my $biblioitem = Koha::Biblioitems->find($item->biblioitemnumber);
135
        my $itemtype = Koha::ItemTypes->find($item->itype);
136
        $itemtype->set({notforloan=>1})->store;
137
        my $availability = Koha::Item::Availability::Hold->new({item => $item, patron => $patron})->in_opac;
138
        my $expecting = 'Koha::Exceptions::ItemType::NotForLoan';
139
140
        is(Koha::ItemTypes->find($biblioitem->itemtype)->notforloan, 0, 'Biblioitem itemtype is for loan.');
141
        is(Koha::ItemTypes->find($item->itype)->notforloan, 1, 'Item itemtype is not for loan.');
142
        ok(!$availability->available, 'When I request availability, then the item is not available.');
143
        is($availability->unavailable, 1, 'Then there is only one unavailability reason.');
144
        is(ref($availability->unavailabilities->{$expecting}), $expecting,
145
            "Then there is an unavailability status indicating the itemtype is not forloan.");
146
    };
147
    sub t_itemlevel_itemtype_notforloan_item_level_itypes_off {
148
        plan tests => 5;
149
150
        t::lib::Mocks::mock_preference('item-level_itypes', 0);
151
152
        my $patron = build_a_test_patron();
153
        my $item = build_a_test_item();
154
        my $biblioitem = Koha::Biblioitems->find($item->biblioitemnumber);
155
        my $itemtype = Koha::ItemTypes->find($biblioitem->itemtype);
156
        $itemtype->set({notforloan=>1})->store;
157
        my $availability = Koha::Item::Availability::Hold->new({item => $item, patron => $patron})->in_opac;
158
        my $expecting = 'Koha::Exceptions::ItemType::NotForLoan';
159
160
        is(Koha::ItemTypes->find($biblioitem->itemtype)->notforloan, 1, 'Biblioitem itemtype is not for loan.');
161
        is(Koha::ItemTypes->find($item->itype)->notforloan, 0, 'Item itemtype is for loan.');
162
        ok(!$availability->available, 'When I request availability, then the item is not available.');
163
        is($availability->unavailable, 1, 'Then there is only one unavailability reason.');
164
        is(ref($availability->unavailabilities->{$expecting}), $expecting,
165
            "Then there is an unavailability status indicating the itemtype is not forloan.");
166
    };
167
};
168
169
subtest 'Given item is ordered' => \&t_ordered;
170
sub t_ordered {
171
    plan tests => 4;
172
173
    my $patron = build_a_test_patron();
174
    my $item = build_a_test_item()->set({notforloan=>-1})->store;
175
    my $availability = Koha::Item::Availability::Hold->new({item => $item, patron => $patron})->in_opac;
176
    my $expecting = 'Koha::Exceptions::Item::NotForLoan';
177
178
    ok($availability->available, 'When I request availability, then the item is available.');
179
    ok(!$availability->unavailable, 'Then there are no reasons for unavailability.');
180
    is(ref($availability->notes->{$expecting}), $expecting,
181
        'Then there is an additional note indicating not for loan status.');
182
    is($availability->notes->{$expecting}->code, 'Ordered', 'Not for loan code says the item is ordered.')
183
};
184
185
subtest 'Given item is restricted' => \&t_restricted;
186
sub t_restricted {
187
    plan tests => 4;
188
189
    my $patron = build_a_test_patron();
190
    my $item = build_a_test_item()->set({restricted=>1})->store;
191
    my $availability = Koha::Item::Availability::Hold->new({item => $item, patron => $patron})->in_opac;
192
    my $expecting = 'Koha::Exceptions::Item::Restricted';
193
194
    is($item->restricted, 1, 'When I look at the item, I see that it is restricted.');
195
    ok(!$availability->available, 'When I request availability, then the item is not available.');
196
    is($availability->unavailable, 1, 'Then there is only one unavailability reason.');
197
    is(ref($availability->unavailabilities->{$expecting}), $expecting,
198
        'Then there is an unavailability status indicating restricted item.');
199
};
200
201
subtest 'Transfer is limited' => \&t_transfer_limit;
202
sub t_transfer_limit {
203
    plan tests => 4;
204
205
    t::lib::Mocks::mock_preference('UseBranchTransferLimits', 1);
206
    t::lib::Mocks::mock_preference('BranchTransferLimitsType', 'itemtype');
207
208
    my $patron = build_a_test_patron();
209
    my $item = build_a_test_item();
210
    my $branch2 = Koha::Libraries->find($builder->build({ source => 'Branch' })->{branchcode});
211
    is(C4::Circulation::CreateBranchTransferLimit(
212
        $branch2->branchcode,
213
        $item->holdingbranch,
214
        $item->effective_itemtype
215
    ), 1, 'There is a branch transfer limit for itemtype from '
216
       .$item->holdingbranch.' to '.$branch2->branchcode .'.');
217
    my $availability = Koha::Item::Availability::Hold->new({
218
        item => $item,
219
        patron => $patron,
220
        to_branch => $branch2->branchcode,
221
    })->in_opac;
222
    my $expecting = 'Koha::Exceptions::Item::CannotBeTransferred';
223
    ok(!$availability->available, 'When I check availability for hold, then item'
224
       .' is not available');
225
    is($availability->unavailable, 1, 'Then there is one reason for unavailability');
226
    is(ref($availability->unavailabilities->{$expecting}), $expecting, 'Then there'
227
       .' is an unavailability status indicating unability to transfer the item.');
228
};
229
230
subtest 'Given item has no barcode' => \&t_unknown_barcode;
231
sub t_unknown_barcode {
232
    plan tests => 4;
233
234
    my $patron = build_a_test_patron();
235
    my $item = build_a_test_item()->set({barcode=>undef})->store;
236
    my $expecting = 'Koha::Exceptions::Item::UnknownBarcode';
237
    my $availability = Koha::Item::Availability::Hold->new({item => $item, patron => $patron})->in_opac;
238
239
    is($item->barcode, undef, 'When I look at the item, we see that it has undefined barcode.');
240
    ok($availability->unavailable, 'When I request availability, then the item is not available.');
241
    is($availability->unavailable, 1, 'Then there is only one unavailability reason.');
242
    is(ref($availability->unavailabilities->{$expecting}), $expecting,
243
       'Then there is an unavailability status indicating unknown barcode.');
244
};
245
246
subtest 'Given item is withdrawn' => \&t_withdrawn;
247
sub t_withdrawn {
248
    plan tests => 4;
249
250
    my $patron = build_a_test_patron();
251
    my $item = build_a_test_item()->set({restricted=>1})->store;
252
    my $availability = Koha::Item::Availability::Hold->new({item => $item, patron => $patron})->in_opac;
253
    my $expecting = 'Koha::Exceptions::Item::Restricted';
254
255
    is($item->restricted, 1, 'When I look at the item, I see that it is restricted.');
256
    ok(!$availability->available, 'When I request availability, then the item is not available.');
257
    is($availability->unavailable, 1, 'Then there is only one unavailability reason.');
258
    is(ref($availability->unavailabilities->{$expecting}), $expecting,
259
        'Then there is an unavailability status indicating restricted item.');
260
};
261
262
subtest 'Already held' => \&t_already_held;
263
sub t_already_held {
264
    plan tests => 8;
265
266
    my $patron = build_a_test_patron();
267
    my $item = build_a_test_item();
268
    my $reserve_id = add_item_level_hold($item, $patron, $item->homebranch);
269
    my $hold = Koha::Holds->find({ borrowernumber => $patron->borrowernumber });
270
    Koha::IssuingRules->search->delete;
271
    my $rule = Koha::IssuingRule->new({
272
        branchcode   => $item->homebranch,
273
        itemtype     => $item->effective_itemtype,
274
        categorycode => $patron->categorycode,
275
        holds_per_record => 9001,
276
        reservesallowed => 9001,
277
        opacitemholds => 'Y',
278
    })->store;
279
    my $availability = Koha::Item::Availability::Hold->new({item => $item, patron => $patron})->in_opac;
280
    my $expecting = 'Koha::Exceptions::Item::AlreadyHeldForThisPatron';
281
282
    is($rule->reservesallowed, 9001, 'As I look at circulation rules, I can see that many reserves are allowed.');
283
    ok($reserve_id, 'I have placed a hold on an item.');
284
    is($hold->itemnumber, $item->itemnumber, 'The item I have hold for is the same item I will check availability for.');
285
    ok(!$availability->available, 'When I request availability, then the item is not available.');
286
    ok(!$availability->confirm, 'Then there is nothing to be confirmed.');
287
    ok(!$availability->note, 'Then there are no additional notes.');
288
    is($availability->unavailable, 1, 'Then there is only one reason for unavailability.');
289
    is(ref($availability->unavailabilities->{$expecting}), $expecting,
290
        'Then there is an unavailability status indicating that I have already held this.');
291
};
292
293
subtest 'Less than maxreserves' => \&t_less_than_maxreserves;
294
sub t_less_than_maxreserves {
295
    plan tests => 5;
296
297
    t::lib::Mocks::mock_preference('maxreserves', 50);
298
299
    my $patron = build_a_test_patron();
300
    my $item = build_a_test_item();
301
    my $item2 = build_a_test_item();
302
    my $reserve_id = add_item_level_hold($item2, $patron, $item2->homebranch);
303
    my $holdcount = Koha::Holds->search({ borrowernumber => $patron->borrowernumber })->count;
304
    my $availability = Koha::Item::Availability::Hold->new({item => $item, patron => $patron})->in_opac;
305
306
    ok(C4::Context->preference('maxreserves') > $holdcount, 'When I check my holds, I can see that I have less than maximum allowed.');
307
    ok($availability->available, 'When I request availability, then the item is available.');
308
    ok(!$availability->confirm, 'Then there is nothing to be confirmed.');
309
    ok(!$availability->note, 'Then there are no additional notes.');
310
    ok(!$availability->unavailable, 'Then there are no reasons for unavailability.');
311
};
312
313
subtest 'Equal to maxreserves' => \&t_equal_to_maxreserves;
314
sub t_equal_to_maxreserves {
315
    plan tests => 8;
316
317
    t::lib::Mocks::mock_preference('maxreserves', 1);
318
319
    my $patron = build_a_test_patron();
320
    my $item = build_a_test_item();
321
    my $item2 = build_a_test_item();
322
    my $reserve_id = add_item_level_hold($item2, $patron, $item2->homebranch);
323
    my $holdcount = Koha::Holds->search({ borrowernumber => $patron->borrowernumber })->count;
324
    my $availability = Koha::Item::Availability::Hold->new({item => $item, patron => $patron})->in_opac;
325
    my $expecting = 'Koha::Exceptions::Hold::MaximumHoldsReached';
326
327
    ok(C4::Context->preference('maxreserves') == $holdcount, 'When I check my holds, I can see that I maximum allowed holds.');
328
    ok(!$availability->available, 'When I request availability, then the item is not available.');
329
    ok(!$availability->confirm, 'Then there is nothing to be confirmed.');
330
    ok(!$availability->note, 'Then there are no additional notes.');
331
    is($availability->unavailable, 1, 'Then there are is one reason for unavailability.');
332
    is(ref($availability->unavailabilities->{$expecting}), $expecting, 'Then there is an unavailability'
333
       .' status indicating maximum holds have been reached.');
334
335
    my $ex = $availability->unavailabilities->{$expecting};
336
    is($ex->max_holds_allowed, 1, 'Then, from the status, I can see the maximum holds allowed.');
337
    is($ex->current_hold_count, 1, 'Then, from the status, I can see my current hold count.');
338
};
339
340
subtest 'More than maxreserves' => \&t_more_than_maxreserves;
341
sub t_more_than_maxreserves {
342
    plan tests => 8;
343
344
    t::lib::Mocks::mock_preference('maxreserves', 1);
345
346
    my $patron = build_a_test_patron();
347
    my $item = build_a_test_item();
348
    my $item2 = build_a_test_item();
349
    my $item3 = build_a_test_item();
350
    my $reserve_id = add_item_level_hold($item2, $patron, $item2->homebranch);
351
    my $reserve_id2 = add_item_level_hold($item3, $patron, $item3->homebranch);
352
    my $holdcount = Koha::Holds->search({ borrowernumber => $patron->borrowernumber })->count;
353
    my $availability = Koha::Item::Availability::Hold->new({item => $item, patron => $patron})->in_opac;
354
    my $expecting = 'Koha::Exceptions::Hold::MaximumHoldsReached';
355
356
    ok(C4::Context->preference('maxreserves') < $holdcount, 'When I check my holds, I can see that I have more holds than allowed. How?!');
357
    ok(!$availability->available, 'When I request availability, then the item is not available.');
358
    ok(!$availability->confirm, 'Then there is nothing to be confirmed.');
359
    ok(!$availability->note, 'Then there are no additional notes.');
360
    is($availability->unavailable, 1, 'Then there are is one reason for unavailability.');
361
    is(ref($availability->unavailabilities->{$expecting}), $expecting, 'Then there is an unavailability'
362
       .' status indicating maximum holds have been reached.');
363
364
    my $ex = $availability->unavailabilities->{$expecting};
365
    is($ex->max_holds_allowed, 1, 'Then, from the status, I can see the maximum holds allowed.');
366
    is($ex->current_hold_count, 2, 'Then, from the status, I can see my current hold count.');
367
};
368
369
$schema->storage->txn_rollback;
370
371
1;
(-)a/t/db_dependent/Koha/Item/Availability/Hold/Opac/LibraryItemRules.t (-1 / +260 lines)
Line 0 Link Here
0
- 
1
#!/usr/bin/perl
2
3
# Copyright Koha-Suomi Oy 2016
4
#
5
# This file is part of Koha
6
#
7
# Koha is free software; you can redistribute it and/or modify it
8
# under the terms of the GNU General Public License as published by
9
# the Free Software Foundation; either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# Koha is distributed in the hope that it will be useful, but
13
# WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General Public License
18
# along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20
use Modern::Perl;
21
use Test::More tests => 6;
22
use t::lib::Mocks;
23
use t::lib::TestBuilder;
24
require t::db_dependent::Koha::Availability::Helpers;
25
26
use Koha::Database;
27
use Koha::IssuingRules;
28
use Koha::Items;
29
use Koha::ItemTypes;
30
31
use Koha::Item::Availability::Hold;
32
33
my $schema = Koha::Database->new->schema;
34
$schema->storage->txn_begin;
35
36
my $dbh = C4::Context->dbh;
37
$dbh->{RaiseError} = 1;
38
39
my $builder = t::lib::TestBuilder->new;
40
41
set_default_system_preferences();
42
set_default_circulation_rules();
43
44
subtest 'Given my library does not allow holds in branch item rules' => \&t_holdnotallowed_patronlibrary;
45
sub t_holdnotallowed_patronlibrary {
46
    plan tests => 7;
47
48
    set_default_circulation_rules();
49
    t::lib::Mocks::mock_preference('CircControl', 'PatronLibrary');
50
51
    my $item = build_a_test_item();
52
    my $patron = build_a_test_patron();
53
    ok($dbh->do(q{
54
        INSERT INTO branch_item_rules (branchcode, itemtype, holdallowed, returnbranch)
55
        VALUES (?, ?, ?, ?)
56
    }, {}, $patron->branchcode, $item->effective_itemtype, 0, 'homebranch'), 'There is a branch item'
57
       .' rule that says holds are not allowed from my library.');
58
59
    my $availability = Koha::Item::Availability::Hold->new({item => $item, patron => $patron})->in_opac;
60
    my $expecting = 'Koha::Exceptions::Hold::NotAllowedByLibrary';
61
62
    is(C4::Context->preference('CircControl'), 'PatronLibrary', 'Koha is configured to use patron\'s library for checkout rules.');
63
    ok(!$availability->available, 'When I request availability, then the item is not available.');
64
    is($availability->unavailable, 1, 'Then there is one reason for unavailability.');
65
    ok(!$availability->confirm, 'Then there is nothing to be confirmed.');
66
    ok(!$availability->note, 'Then there are no additional notes.');
67
    is(ref($availability->unavailabilities->{$expecting}), $expecting, 'Then there is an unavailability'
68
       .' note indicating library does not allow item to be held.');
69
};
70
71
subtest 'Given item\'s home library does not allow holds in branch item rules' => \&t_holdnotallowed_itemhomelibrary;
72
sub t_holdnotallowed_itemhomelibrary {
73
    plan tests => 7;
74
75
    set_default_circulation_rules();
76
    t::lib::Mocks::mock_preference('CircControl', 'ItemHomeLibrary');
77
78
    my $item = build_a_test_item();
79
    my $patron = build_a_test_patron();
80
    ok($dbh->do(q{
81
        INSERT INTO branch_item_rules (branchcode, itemtype, holdallowed, returnbranch)
82
        VALUES (?, ?, ?, ?)
83
    }, {}, $item->homebranch, $item->effective_itemtype, 0, 'homebranch'), 'There is a branch item'
84
       .' rule that says item\'s library forbids holds.');
85
86
    my $availability = Koha::Item::Availability::Hold->new({item => $item, patron => $patron})->in_opac;
87
    my $expecting = 'Koha::Exceptions::Hold::NotAllowedByLibrary';
88
89
    is(C4::Context->preference('CircControl'), 'ItemHomeLibrary', 'Koha is configured to use item\'s library for checkout rules.');
90
    ok(!$availability->available, 'When I request availability, then the item is not available.');
91
    is($availability->unavailable, 1, 'Then there is one reason for unavailability.');
92
    ok(!$availability->confirm, 'Then there is nothing to be confirmed.');
93
    ok(!$availability->note, 'Then there are no additional notes.');
94
    is(ref($availability->unavailabilities->{$expecting}), $expecting, 'Then there is an unavailability'
95
       .' note indicating library does not allow item to be held.');
96
};
97
98
subtest 'Given my library allows holds only from my library' => \&t_holdallowed_only_from_patronlibrary;
99
sub t_holdallowed_only_from_patronlibrary {
100
    plan tests => 8;
101
102
    set_default_circulation_rules();
103
    t::lib::Mocks::mock_preference('CircControl', 'PatronLibrary');
104
105
    my $item = build_a_test_item();
106
    my $patron = build_a_test_patron();
107
    ok($dbh->do(q{
108
        INSERT INTO branch_item_rules (branchcode, itemtype, holdallowed, returnbranch)
109
        VALUES (?, ?, ?, ?)
110
    }, {}, $patron->branchcode, $item->effective_itemtype, 1, 'homebranch'), 'There is a branch item'
111
       .' rule that says holds are allowed only from my library.');
112
113
    my $availability = Koha::Item::Availability::Hold->new({item => $item, patron => $patron})->in_opac;
114
    my $expecting = 'Koha::Exceptions::Hold::NotAllowedFromOtherLibraries';
115
116
    is(C4::Context->preference('CircControl'), 'PatronLibrary', 'Koha is configured to use patron\'s library for checkout rules.');
117
    ok($item->homebranch ne $patron->branchcode, 'I am from different library than the item.');
118
    ok(!$availability->available, 'When I request availability, then the item is not available.');
119
    is($availability->unavailable, 1, 'Then there is one reason for unavailability.');
120
    ok(!$availability->confirm, 'Then there is nothing to be confirmed.');
121
    ok(!$availability->note, 'Then there are no additional notes.');
122
    is(ref($availability->unavailabilities->{$expecting}), $expecting, 'Then there is an unavailability'
123
       .' status indicating library does not allow item to be held from other libraries.');
124
};
125
126
subtest 'Given item\'s library allows holds only its library' => \&t_holdallowed_only_from_itemhomelibrary;
127
sub t_holdallowed_only_from_itemhomelibrary {
128
    plan tests => 8;
129
130
    set_default_circulation_rules();
131
    t::lib::Mocks::mock_preference('CircControl', 'ItemHomeLibrary');
132
133
    my $item = build_a_test_item();
134
    my $patron = build_a_test_patron();
135
    ok($dbh->do(q{
136
        INSERT INTO branch_item_rules (branchcode, itemtype, holdallowed, returnbranch)
137
        VALUES (?, ?, ?, ?)
138
    }, {}, $item->homebranch, $item->effective_itemtype, 1, 'homebranch'), 'There is a branch item'
139
       .' rule that says holds are allowed only in item home branch.');
140
141
    my $availability = Koha::Item::Availability::Hold->new({item => $item, patron => $patron})->in_opac;
142
    my $expecting = 'Koha::Exceptions::Hold::NotAllowedFromOtherLibraries';
143
144
    is(C4::Context->preference('CircControl'), 'ItemHomeLibrary', 'Koha is configured to use item\'s library for checkout rules.');
145
    ok($item->homebranch ne $patron->branchcode, 'I am from different library than the item.');
146
    ok(!$availability->available, 'When I request availability, then the item is not available.');
147
    is($availability->unavailable, 1, 'Then there is one reason for unavailability.');
148
    ok(!$availability->confirm, 'Then there is nothing to be confirmed.');
149
    ok(!$availability->note, 'Then there are no additional notes.');
150
    is(ref($availability->unavailabilities->{$expecting}), $expecting, 'Then there is an unavailability'
151
       .' status indicating library does not allow item to be held from other libraries.');
152
};
153
154
subtest 'Given my library allows holds from any other libraries' => \&t_holdallowed_from_any_library_patronlibrary;
155
sub t_holdallowed_from_any_library_patronlibrary {
156
    plan tests => 3;
157
158
    set_default_circulation_rules();
159
    t::lib::Mocks::mock_preference('CircControl', 'PatronLibrary');
160
161
    my $item = build_a_test_item();
162
    my $patron = build_a_test_patron();
163
    ok($dbh->do(q{
164
        INSERT INTO branch_item_rules (branchcode, itemtype, holdallowed, returnbranch)
165
        VALUES (?, ?, ?, ?)
166
    }, {}, $patron->branchcode, $item->effective_itemtype, 2, 'homebranch'), 'There is a branch item'
167
       .' rule that says holds are allowed from any library.');
168
169
       subtest 'Given IndependentBranches is on and canreservefromotherbranches is off' => sub {
170
            plan tests => 9;
171
172
            t::lib::Mocks::mock_preference('canreservefromotherbranches', 0);
173
            t::lib::Mocks::mock_preference('IndependentBranches', 1);
174
175
            my $availability = Koha::Item::Availability::Hold->new({item => $item, patron => $patron})->in_opac;
176
            my $expecting = 'Koha::Exceptions::Hold::NotAllowedFromOtherLibraries';
177
178
            is(C4::Context->preference('CircControl'), 'PatronLibrary', 'Koha is configured to use patron\'s library for checkout rules.');
179
            is(C4::Context->preference('canreservefromotherbranches'), 0, 'People cannot reserve from other libraries.');
180
            is(C4::Context->preference('IndependentBranches'), 1, 'Libraries are independent.');
181
            ok($item->homebranch ne $patron->branchcode, 'I am from different library than the item.');
182
            ok(!$availability->available, 'When I request availability, then the item is not available.');
183
            is($availability->unavailable, 1, 'Then there are no reasons for unavailability.');
184
            ok(!$availability->confirm, 'Then there is nothing to be confirmed.');
185
            ok(!$availability->note, 'Then there are no additional notes.');
186
            is(ref($availability->unavailabilities->{$expecting}), $expecting, 'Then there is an unavailability'
187
               .' status indicating library does not allow item to be held from other libraries.');
188
       };
189
190
       subtest 'Given IndependentBranches is off and canreservefromotherbranches is on' => sub {
191
            plan tests => 8;
192
193
            t::lib::Mocks::mock_preference('canreservefromotherbranches', 1);
194
            t::lib::Mocks::mock_preference('IndependentBranches', 0);
195
            my $availability = Koha::Item::Availability::Hold->new({item => $item, patron => $patron})->in_opac;
196
197
            is(C4::Context->preference('CircControl'), 'PatronLibrary', 'Koha is configured to use patron\'s library for checkout rules.');
198
            is(C4::Context->preference('canreservefromotherbranches'), 1, 'People can reserve from other libraries.');
199
            is(C4::Context->preference('IndependentBranches'), 0, 'Libraries are not independent.');
200
            ok($item->homebranch ne $patron->branchcode, 'I am from different library than the item.');
201
            ok($availability->available, 'When I request availability, then the item is available.');
202
            ok(!$availability->unavailable, 'Then there are no reasons for unavailability.');
203
            ok(!$availability->confirm, 'Then there is nothing to be confirmed.');
204
            ok(!$availability->note, 'Then there are no additional notes.');
205
       };
206
};
207
208
subtest 'Given item\'s library allows holds from any other libraries' => \&t_holdallowed_from_any_library_itemhomelibrary;
209
sub t_holdallowed_from_any_library_itemhomelibrary {
210
    plan tests => 3;
211
212
    set_default_circulation_rules();
213
    t::lib::Mocks::mock_preference('CircControl', 'ItemHomeLibrary');
214
215
    my $item = build_a_test_item();
216
    my $patron = build_a_test_patron();
217
    ok($dbh->do(q{
218
        INSERT INTO branch_item_rules (branchcode, itemtype, holdallowed, returnbranch)
219
        VALUES (?, ?, ?, ?)
220
    }, {}, $item->homebranch, $item->effective_itemtype, 2, 'homebranch'), 'There is a branch item'
221
       .' rule in item\'s homebranch that says holds are allowed from any library.');
222
223
       subtest 'Given IndependentBranches is on and canreservefromotherbranches is off' => sub {
224
            plan tests => 9;
225
226
            t::lib::Mocks::mock_preference('canreservefromotherbranches', 0);
227
            t::lib::Mocks::mock_preference('IndependentBranches', 1);
228
229
            my $availability = Koha::Item::Availability::Hold->new({item => $item, patron => $patron})->in_opac;
230
            my $expecting = 'Koha::Exceptions::Hold::NotAllowedFromOtherLibraries';
231
232
            is(C4::Context->preference('CircControl'), 'ItemHomeLibrary', 'Koha is configured to use item\'s library for checkout rules.');
233
            is(C4::Context->preference('canreservefromotherbranches'), 0, 'People cannot reserve from other libraries.');
234
            is(C4::Context->preference('IndependentBranches'), 1, 'Libraries are independent.');
235
            ok($item->homebranch ne $patron->branchcode, 'I am from different library than the item.');
236
            ok(!$availability->available, 'When I request availability, then the item is not available.');
237
            is($availability->unavailable, 1, 'Then there are no reasons for unavailability.');
238
            ok(!$availability->confirm, 'Then there is nothing to be confirmed.');
239
            ok(!$availability->note, 'Then there are no additional notes.');
240
            is(ref($availability->unavailabilities->{$expecting}), $expecting, 'Then there is an unavailability'
241
               .' status indicating library does not allow item to be held from other libraries.');
242
       };
243
244
       subtest 'Given IndependentBranches is off and canreservefromotherbranches is on' => sub {
245
            plan tests => 8;
246
247
            t::lib::Mocks::mock_preference('canreservefromotherbranches', 1);
248
            t::lib::Mocks::mock_preference('IndependentBranches', 0);
249
            my $availability = Koha::Item::Availability::Hold->new({item => $item, patron => $patron})->in_opac;
250
251
            is(C4::Context->preference('CircControl'), 'ItemHomeLibrary', 'Koha is configured to use item\'s library for checkout rules.');
252
            is(C4::Context->preference('canreservefromotherbranches'), 1, 'People can reserve from other libraries.');
253
            is(C4::Context->preference('IndependentBranches'), 0, 'Libraries are not independent.');
254
            ok($item->homebranch ne $patron->branchcode, 'I am from different library than the item.');
255
            ok($availability->available, 'When I request availability, then the item is available.');
256
            ok(!$availability->unavailable, 'Then there are no reasons for unavailability.');
257
            ok(!$availability->confirm, 'Then there is nothing to be confirmed.');
258
            ok(!$availability->note, 'Then there are no additional notes.');
259
       };
260
};

Return to bug 17712