Bug 25184: Items with a negative notforloan status should not be captured for holds
[koha-ffzg.git] / t / db_dependent / Holds.t
1 #!/usr/bin/perl
2
3 use Modern::Perl;
4
5 use t::lib::Mocks;
6 use t::lib::TestBuilder;
7
8 use C4::Context;
9
10 use Test::More tests => 63;
11 use MARC::Record;
12
13 use C4::Biblio;
14 use C4::Calendar;
15 use C4::Items;
16 use C4::Reserves;
17
18 use Koha::Biblios;
19 use Koha::CirculationRules;
20 use Koha::Database;
21 use Koha::DateUtils qw( dt_from_string output_pref );
22 use Koha::Holds;
23 use Koha::Item::Transfer::Limits;
24 use Koha::Items;
25 use Koha::Libraries;
26 use Koha::Library::Groups;
27 use Koha::Patrons;
28
29 BEGIN {
30     use FindBin;
31     use lib $FindBin::Bin;
32 }
33
34 my $schema  = Koha::Database->new->schema;
35 $schema->storage->txn_begin;
36
37 my $builder = t::lib::TestBuilder->new();
38 my $dbh     = C4::Context->dbh;
39
40 # Create two random branches
41 my $branch_1 = $builder->build({ source => 'Branch' })->{ branchcode };
42 my $branch_2 = $builder->build({ source => 'Branch' })->{ branchcode };
43
44 my $category = $builder->build({ source => 'Category' });
45
46 my $borrowers_count = 5;
47
48 $dbh->do('DELETE FROM itemtypes');
49 $dbh->do('DELETE FROM reserves');
50 $dbh->do('DELETE FROM circulation_rules');
51 my $insert_sth = $dbh->prepare('INSERT INTO itemtypes (itemtype) VALUES (?)');
52 $insert_sth->execute('CAN');
53 $insert_sth->execute('CANNOT');
54 $insert_sth->execute('DUMMY');
55 $insert_sth->execute('ONLY1');
56
57 # Setup Test------------------------
58 my $biblio = $builder->build_sample_biblio({ itemtype => 'DUMMY' });
59
60 # Create item instance for testing.
61 my $itemnumber = $builder->build_sample_item({ library => $branch_1, biblionumber => $biblio->biblionumber })->itemnumber;
62
63 # Create some borrowers
64 my @borrowernumbers;
65 foreach (1..$borrowers_count) {
66     my $borrowernumber = Koha::Patron->new({
67         firstname =>  'my firstname',
68         surname => 'my surname ' . $_,
69         categorycode => $category->{categorycode},
70         branchcode => $branch_1,
71     })->store->borrowernumber;
72     push @borrowernumbers, $borrowernumber;
73 }
74
75 # Create five item level holds
76 foreach my $borrowernumber ( @borrowernumbers ) {
77     AddReserve(
78         {
79             branchcode     => $branch_1,
80             borrowernumber => $borrowernumber,
81             biblionumber   => $biblio->biblionumber,
82             priority       => C4::Reserves::CalculatePriority( $biblio->biblionumber ),
83             itemnumber     => $itemnumber,
84         }
85     );
86 }
87
88 my $holds = $biblio->holds;
89 is( $holds->count, $borrowers_count, 'Test GetReserves()' );
90 is( $holds->next->priority, 1, "Reserve 1 has a priority of 1" );
91 is( $holds->next->priority, 2, "Reserve 2 has a priority of 2" );
92 is( $holds->next->priority, 3, "Reserve 3 has a priority of 3" );
93 is( $holds->next->priority, 4, "Reserve 4 has a priority of 4" );
94 is( $holds->next->priority, 5, "Reserve 5 has a priority of 5" );
95
96 my $item = Koha::Items->find( $itemnumber );
97 $holds = $item->current_holds;
98 my $first_hold = $holds->next;
99 my $reservedate = $first_hold->reservedate;
100 my $borrowernumber = $first_hold->borrowernumber;
101 my $branch_1code = $first_hold->branchcode;
102 my $reserve_id = $first_hold->reserve_id;
103 is( $reservedate, output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 }), "holds_placed_today should return a valid reserve date");
104 is( $borrowernumber, $borrowernumbers[0], "holds_placed_today should return a valid borrowernumber");
105 is( $branch_1code, $branch_1, "holds_placed_today should return a valid branchcode");
106 ok($reserve_id, "Test holds_placed_today()");
107
108 my $hold = Koha::Holds->find( $reserve_id );
109 ok( $hold, "Koha::Holds found the hold" );
110 my $hold_biblio = $hold->biblio();
111 ok( $hold_biblio, "Got biblio using biblio() method" );
112 ok( $hold_biblio == $hold->biblio(), "biblio method returns stashed biblio" );
113 my $hold_item = $hold->item();
114 ok( $hold_item, "Got item using item() method" );
115 ok( $hold_item == $hold->item(), "item method returns stashed item" );
116 my $hold_branch = $hold->branch();
117 ok( $hold_branch, "Got branch using branch() method" );
118 ok( $hold_branch == $hold->branch(), "branch method returns stashed branch" );
119 my $hold_found = $hold->found();
120 $hold->set({ found => 'W'})->store();
121 is( Koha::Holds->waiting()->count(), 1, "Koha::Holds->waiting returns waiting holds" );
122 is( Koha::Holds->unfilled()->count(), 4, "Koha::Holds->unfilled returns unfilled holds" );
123
124 my $patron = Koha::Patrons->find( $borrowernumbers[0] );
125 $holds = $patron->holds;
126 is( $holds->next->borrowernumber, $borrowernumbers[0], "Test Koha::Patron->holds");
127
128
129 $holds = $item->current_holds;
130 $first_hold = $holds->next;
131 $borrowernumber = $first_hold->borrowernumber;
132 $branch_1code = $first_hold->branchcode;
133 $reserve_id = $first_hold->reserve_id;
134
135 ModReserve({
136     reserve_id    => $reserve_id,
137     rank          => '4',
138     branchcode    => $branch_1,
139     itemnumber    => $itemnumber,
140     suspend_until => output_pref( { dt => dt_from_string( "2013-01-01", "iso" ), dateonly => 1 } ),
141 });
142
143 $hold = Koha::Holds->find( $reserve_id );
144 ok( $hold->priority eq '4', "Test ModReserve, priority changed correctly" );
145 ok( $hold->suspend, "Test ModReserve, suspend hold" );
146 is( $hold->suspend_until, '2013-01-01 00:00:00', "Test ModReserve, suspend until date" );
147
148 ModReserve({ # call without reserve_id
149     rank          => '3',
150     biblionumber  => $biblio->biblionumber,
151     itemnumber    => $itemnumber,
152     borrowernumber => $borrowernumber,
153 });
154 $hold = Koha::Holds->find( $reserve_id );
155 ok( $hold->priority eq '3', "Test ModReserve, priority changed correctly" );
156
157 ToggleSuspend( $reserve_id );
158 $hold = Koha::Holds->find( $reserve_id );
159 ok( ! $hold->suspend, "Test ToggleSuspend(), no date" );
160
161 ToggleSuspend( $reserve_id, '2012-01-01' );
162 $hold = Koha::Holds->find( $reserve_id );
163 is( $hold->suspend_until, '2012-01-01 00:00:00', "Test ToggleSuspend(), with date" );
164
165 AutoUnsuspendReserves();
166 $hold = Koha::Holds->find( $reserve_id );
167 ok( ! $hold->suspend, "Test AutoUnsuspendReserves()" );
168
169 SuspendAll(
170     borrowernumber => $borrowernumber,
171     biblionumber   => $biblio->biblionumber,
172     suspend => 1,
173     suspend_until => '2012-01-01',
174 );
175 $hold = Koha::Holds->find( $reserve_id );
176 is( $hold->suspend, 1, "Test SuspendAll()" );
177 is( $hold->suspend_until, '2012-01-01 00:00:00', "Test SuspendAll(), with date" );
178
179 SuspendAll(
180     borrowernumber => $borrowernumber,
181     biblionumber   => $biblio->biblionumber,
182     suspend => 0,
183 );
184 $hold = Koha::Holds->find( $reserve_id );
185 is( $hold->suspend, 0, "Test resuming with SuspendAll()" );
186 is( $hold->suspend_until, undef, "Test resuming with SuspendAll(), should have no suspend until date" );
187
188 # Add a new hold for the borrower whose hold we canceled earlier, this time at the bib level
189     AddReserve(
190         {
191             branchcode     => $branch_1,
192             borrowernumber => $borrowernumbers[0],
193             biblionumber   => $biblio->biblionumber,
194         }
195     );
196
197 $patron = Koha::Patrons->find( $borrowernumber );
198 $holds = $patron->holds;
199 my $reserveid = Koha::Holds->search({ biblionumber => $biblio->biblionumber, borrowernumber => $borrowernumbers[0] })->next->reserve_id;
200 ModReserveMinusPriority( $itemnumber, $reserveid );
201 $holds = $patron->holds;
202 is( $holds->next->itemnumber, $itemnumber, "Test ModReserveMinusPriority()" );
203
204 $holds = $biblio->holds;
205 $hold = $holds->next;
206 AlterPriority( 'top', $hold->reserve_id, undef, 2, 1, 6 );
207 $hold = Koha::Holds->find( $reserveid );
208 is( $hold->priority, '1', "Test AlterPriority(), move to top" );
209
210 AlterPriority( 'down', $hold->reserve_id, undef, 2, 1, 6 );
211 $hold = Koha::Holds->find( $reserveid );
212 is( $hold->priority, '2', "Test AlterPriority(), move down" );
213
214 AlterPriority( 'up', $hold->reserve_id, 1, 3, 1, 6 );
215 $hold = Koha::Holds->find( $reserveid );
216 is( $hold->priority, '1', "Test AlterPriority(), move up" );
217
218 AlterPriority( 'bottom', $hold->reserve_id, undef, 2, 1, 6 );
219 $hold = Koha::Holds->find( $reserveid );
220 is( $hold->priority, '6', "Test AlterPriority(), move to bottom" );
221
222 # Regression test for bug 2394
223 #
224 # If IndependentBranches is ON and canreservefromotherbranches is OFF,
225 # a patron is not permittedo to request an item whose homebranch (i.e.,
226 # owner of the item) is different from the patron's own library.
227 # However, if canreservefromotherbranches is turned ON, the patron can
228 # create such hold requests.
229 #
230 # Note that canreservefromotherbranches has no effect if
231 # IndependentBranches is OFF.
232
233 my $foreign_biblio = $builder->build_sample_biblio({ itemtype => 'DUMMY' });
234 my $foreign_itemnumber = $builder->build_sample_item({ library => $branch_2, biblionumber => $foreign_biblio->biblionumber })->itemnumber;
235 Koha::CirculationRules->set_rules(
236     {
237         categorycode => undef,
238         branchcode   => undef,
239         itemtype     => undef,
240         rules        => {
241             reservesallowed  => 25,
242             holds_per_record => 99,
243         }
244     }
245 );
246 Koha::CirculationRules->set_rules(
247     {
248         categorycode => undef,
249         branchcode   => undef,
250         itemtype     => 'CANNOT',
251         rules        => {
252             reservesallowed  => 0,
253             holds_per_record => 99,
254         }
255     }
256 );
257
258 # make sure some basic sysprefs are set
259 t::lib::Mocks::mock_preference('ReservesControlBranch', 'ItemHomeLibrary');
260 t::lib::Mocks::mock_preference('item-level_itypes', 1);
261
262 # if IndependentBranches is OFF, a $branch_1 patron can reserve an $branch_2 item
263 t::lib::Mocks::mock_preference('IndependentBranches', 0);
264
265 is(
266     CanItemBeReserved($borrowernumbers[0], $foreign_itemnumber)->{status}, 'OK',
267     '$branch_1 patron allowed to reserve $branch_2 item with IndependentBranches OFF (bug 2394)'
268 );
269
270 # if IndependentBranches is OFF, a $branch_1 patron cannot reserve an $branch_2 item
271 t::lib::Mocks::mock_preference('IndependentBranches', 1);
272 t::lib::Mocks::mock_preference('canreservefromotherbranches', 0);
273 ok(
274     CanItemBeReserved($borrowernumbers[0], $foreign_itemnumber)->{status} eq 'cannotReserveFromOtherBranches',
275     '$branch_1 patron NOT allowed to reserve $branch_2 item with IndependentBranches ON ... (bug 2394)'
276 );
277
278 # ... unless canreservefromotherbranches is ON
279 t::lib::Mocks::mock_preference('canreservefromotherbranches', 1);
280 ok(
281     CanItemBeReserved($borrowernumbers[0], $foreign_itemnumber)->{status} eq 'OK',
282     '... unless canreservefromotherbranches is ON (bug 2394)'
283 );
284
285 {
286     # Regression test for bug 11336 # Test if ModReserve correctly recalculate the priorities
287     $biblio = $builder->build_sample_biblio({ itemtype => 'DUMMY' });
288     $itemnumber = $builder->build_sample_item({ library => $branch_1, biblionumber => $biblio->biblionumber })->itemnumber;
289     my $reserveid1 = AddReserve(
290         {
291             branchcode     => $branch_1,
292             borrowernumber => $borrowernumbers[0],
293             biblionumber   => $biblio->biblionumber,
294             priority       => 1
295         }
296     );
297
298     $itemnumber = $builder->build_sample_item({ library => $branch_1, biblionumber => $biblio->biblionumber })->itemnumber;
299     my $reserveid2 = AddReserve(
300         {
301             branchcode     => $branch_1,
302             borrowernumber => $borrowernumbers[1],
303             biblionumber   => $biblio->biblionumber,
304             priority       => 2
305         }
306     );
307
308     $itemnumber = $builder->build_sample_item({ library => $branch_1, biblionumber => $biblio->biblionumber })->itemnumber;
309     my $reserveid3 = AddReserve(
310         {
311             branchcode     => $branch_1,
312             borrowernumber => $borrowernumbers[2],
313             biblionumber   => $biblio->biblionumber,
314             priority       => 3
315         }
316     );
317
318     my $hhh = Koha::Holds->search({ biblionumber => $biblio->biblionumber });
319     my $hold3 = Koha::Holds->find( $reserveid3 );
320     is( $hold3->priority, 3, "The 3rd hold should have a priority set to 3" );
321     ModReserve({ reserve_id => $reserveid1, rank => 'del' });
322     ModReserve({ reserve_id => $reserveid2, rank => 'del' });
323     is( $hold3->discard_changes->priority, 1, "After ModReserve, the 3rd reserve becomes the first on the waiting list" );
324 }
325
326 Koha::Items->find($itemnumber)->damaged(1)->store; # FIXME The $itemnumber is a bit confusing here
327 t::lib::Mocks::mock_preference( 'AllowHoldsOnDamagedItems', 1 );
328 is( CanItemBeReserved( $borrowernumbers[0], $itemnumber)->{status}, 'OK', "Patron can reserve damaged item with AllowHoldsOnDamagedItems enabled" );
329 ok( defined( ( CheckReserves($itemnumber) )[1] ), "Hold can be trapped for damaged item with AllowHoldsOnDamagedItems enabled" );
330
331 $hold = Koha::Hold->new(
332     {
333         borrowernumber => $borrowernumbers[0],
334         itemnumber     => $itemnumber,
335         biblionumber   => $biblio->biblionumber,
336     }
337 )->store();
338 is( CanItemBeReserved( $borrowernumbers[0], $itemnumber )->{status},
339     'itemAlreadyOnHold',
340     "Patron cannot place a second item level hold for a given item" );
341 $hold->delete();
342
343 t::lib::Mocks::mock_preference( 'AllowHoldsOnDamagedItems', 0 );
344 ok( CanItemBeReserved( $borrowernumbers[0], $itemnumber)->{status} eq 'damaged', "Patron cannot reserve damaged item with AllowHoldsOnDamagedItems disabled" );
345 ok( !defined( ( CheckReserves($itemnumber) )[1] ), "Hold cannot be trapped for damaged item with AllowHoldsOnDamagedItems disabled" );
346
347 # Items that are not for loan, but holdable should not be trapped until they are available for loan
348 Koha::Items->find($itemnumber)->damaged(0)->notforloan(-1)->store;
349 Koha::Holds->search({ biblionumber => $biblio->id })->delete();
350 is( CanItemBeReserved( $borrowernumbers[0], $itemnumber)->{status}, 'OK', "Patron can place hold on item that is not for loan but holdable ( notforloan < 0 )" );
351 $hold = Koha::Hold->new(
352     {
353         borrowernumber => $borrowernumbers[0],
354         itemnumber     => $itemnumber,
355         biblionumber   => $biblio->biblionumber,
356     }
357 )->store();
358 ok( !defined( ( CheckReserves($itemnumber) )[1] ), "Hold cannot be trapped for item that is not for loan but holdable ( notforloan < 0 )" );
359 $hold->delete();
360
361 # Regression test for bug 9532
362 $biblio = $builder->build_sample_biblio({ itemtype => 'CANNOT' });
363 $item = $builder->build_sample_item({ library => $branch_1, itype => 'CANNOT', biblionumber => $biblio->biblionumber});
364 AddReserve(
365     {
366         branchcode     => $branch_1,
367         borrowernumber => $borrowernumbers[0],
368         biblionumber   => $biblio->biblionumber,
369         priority       => 1,
370     }
371 );
372 is(
373     CanItemBeReserved( $borrowernumbers[0], $item->itemnumber)->{status}, 'tooManyReserves',
374     "cannot request item if policy that matches on item-level item type forbids it"
375 );
376
377 $item->itype('CAN')->store;
378 ok(
379     CanItemBeReserved( $borrowernumbers[0], $item->itemnumber)->{status} eq 'OK',
380     "can request item if policy that matches on item type allows it"
381 );
382
383 t::lib::Mocks::mock_preference('item-level_itypes', 0);
384 $item->itype(undef)->store;
385 ok(
386     CanItemBeReserved( $borrowernumbers[0], $item->itemnumber)->{status} eq 'tooManyReserves',
387     "cannot request item if policy that matches on bib-level item type forbids it (bug 9532)"
388 );
389
390
391 # Test branch item rules
392
393 $dbh->do('DELETE FROM circulation_rules');
394 Koha::CirculationRules->set_rules(
395     {
396         categorycode => undef,
397         branchcode   => undef,
398         itemtype     => undef,
399         rules        => {
400             reservesallowed  => 25,
401             holds_per_record => 99,
402         }
403     }
404 );
405 Koha::CirculationRules->set_rules(
406     {
407         branchcode => $branch_1,
408         itemtype   => 'CANNOT',
409         rules => {
410             holdallowed => 0,
411             returnbranch => 'homebranch',
412         }
413     }
414 );
415 Koha::CirculationRules->set_rules(
416     {
417         branchcode => $branch_1,
418         itemtype   => 'CAN',
419         rules => {
420             holdallowed => 1,
421             returnbranch => 'homebranch',
422         }
423     }
424 );
425 $biblio = $builder->build_sample_biblio({ itemtype => 'CANNOT' });
426 $itemnumber = $builder->build_sample_item({ library => $branch_1, itype => 'CANNOT', biblionumber => $biblio->biblionumber})->itemnumber;
427 is(CanItemBeReserved($borrowernumbers[0], $itemnumber)->{status}, 'notReservable',
428     "CanItemBeReserved should return 'notReservable'");
429
430 t::lib::Mocks::mock_preference( 'ReservesControlBranch', 'PatronLibrary' );
431 $itemnumber = $builder->build_sample_item({ library => $branch_2, itype => 'CAN', biblionumber => $biblio->biblionumber})->itemnumber;
432 is(CanItemBeReserved($borrowernumbers[0], $itemnumber)->{status},
433     'cannotReserveFromOtherBranches',
434     "CanItemBeReserved should use PatronLibrary rule when ReservesControlBranch set to 'PatronLibrary'");
435 t::lib::Mocks::mock_preference( 'ReservesControlBranch', 'ItemHomeLibrary' );
436 is(CanItemBeReserved($borrowernumbers[0], $itemnumber)->{status},
437     'OK',
438     "CanItemBeReserved should use item home library rule when ReservesControlBranch set to 'ItemsHomeLibrary'");
439
440 $itemnumber = $builder->build_sample_item({ library => $branch_1, itype => 'CAN', biblionumber => $biblio->biblionumber})->itemnumber;
441 is(CanItemBeReserved($borrowernumbers[0], $itemnumber)->{status}, 'OK',
442     "CanItemBeReserved should return 'OK'");
443
444 # Bug 12632
445 t::lib::Mocks::mock_preference( 'item-level_itypes',     1 );
446 t::lib::Mocks::mock_preference( 'ReservesControlBranch', 'PatronLibrary' );
447
448 $dbh->do('DELETE FROM reserves');
449 $dbh->do('DELETE FROM issues');
450 $dbh->do('DELETE FROM items');
451 $dbh->do('DELETE FROM biblio');
452
453 $biblio = $builder->build_sample_biblio({ itemtype => 'ONLY1' });
454 $itemnumber = $builder->build_sample_item({ library => $branch_1, biblionumber => $biblio->biblionumber})->itemnumber;
455
456 Koha::CirculationRules->set_rules(
457     {
458         categorycode => undef,
459         branchcode   => undef,
460         itemtype     => 'ONLY1',
461         rules        => {
462             reservesallowed  => 1,
463             holds_per_record => 99,
464         }
465     }
466 );
467 is( CanItemBeReserved( $borrowernumbers[0], $itemnumber )->{status},
468     'OK', 'Patron can reserve item with hold limit of 1, no holds placed' );
469
470 my $res_id = AddReserve(
471     {
472         branchcode     => $branch_1,
473         borrowernumber => $borrowernumbers[0],
474         biblionumber   => $biblio->biblionumber,
475         priority       => 1,
476     }
477 );
478
479 is( CanItemBeReserved( $borrowernumbers[0], $itemnumber )->{status},
480     'tooManyReserves', 'Patron cannot reserve item with hold limit of 1, 1 bib level hold placed' );
481
482     #results should be the same for both ReservesControlBranch settings
483 t::lib::Mocks::mock_preference( 'ReservesControlBranch', 'ItemHomeLibrary' );
484 is( CanItemBeReserved( $borrowernumbers[0], $itemnumber )->{status},
485     'tooManyReserves', 'Patron cannot reserve item with hold limit of 1, 1 bib level hold placed' );
486 #reset for further tests
487 t::lib::Mocks::mock_preference( 'ReservesControlBranch', 'PatronLibrary' );
488
489 subtest 'Test max_holds per library/patron category' => sub {
490     plan tests => 6;
491
492     $dbh->do('DELETE FROM reserves');
493
494     $biblio = $builder->build_sample_biblio;
495     $itemnumber = $builder->build_sample_item({ library => $branch_1, biblionumber => $biblio->biblionumber})->itemnumber;
496     Koha::CirculationRules->set_rules(
497         {
498             categorycode => undef,
499             branchcode   => undef,
500             itemtype     => $biblio->itemtype,
501             rules        => {
502                 reservesallowed  => 99,
503                 holds_per_record => 99,
504             }
505         }
506     );
507
508     for ( 1 .. 3 ) {
509         AddReserve(
510             {
511                 branchcode     => $branch_1,
512                 borrowernumber => $borrowernumbers[0],
513                 biblionumber   => $biblio->biblionumber,
514                 priority       => 1,
515             }
516         );
517     }
518
519     my $count =
520       Koha::Holds->search( { borrowernumber => $borrowernumbers[0] } )->count();
521     is( $count, 3, 'Patron now has 3 holds' );
522
523     my $ret = CanItemBeReserved( $borrowernumbers[0], $itemnumber );
524     is( $ret->{status}, 'OK', 'Patron can place hold with no borrower circ rules' );
525
526     my $rule_all = Koha::CirculationRules->set_rule(
527         {
528             categorycode => $category->{categorycode},
529             branchcode   => undef,
530             rule_name    => 'max_holds',
531             rule_value   => 3,
532         }
533     );
534
535     my $rule_branch = Koha::CirculationRules->set_rule(
536         {
537             branchcode   => $branch_1,
538             categorycode => $category->{categorycode},
539             rule_name    => 'max_holds',
540             rule_value   => 5,
541         }
542     );
543
544     $ret = CanItemBeReserved( $borrowernumbers[0], $itemnumber );
545     is( $ret->{status}, 'OK', 'Patron can place hold with branch/category rule of 5, category rule of 3' );
546
547     $rule_branch->delete();
548
549     $ret = CanItemBeReserved( $borrowernumbers[0], $itemnumber );
550     is( $ret->{status}, 'tooManyReserves', 'Patron cannot place hold with only a category rule of 3' );
551
552     $rule_all->delete();
553     $rule_branch->rule_value(3);
554     $rule_branch->store();
555
556     $ret = CanItemBeReserved( $borrowernumbers[0], $itemnumber );
557     is( $ret->{status}, 'tooManyReserves', 'Patron cannot place hold with only a branch/category rule of 3' );
558
559     $rule_branch->rule_value(5);
560     $rule_branch->update();
561     $rule_branch->rule_value(5);
562     $rule_branch->store();
563
564     $ret = CanItemBeReserved( $borrowernumbers[0], $itemnumber );
565     is( $ret->{status}, 'OK', 'Patron can place hold with branch/category rule of 5, category rule of 5' );
566 };
567
568 subtest 'Pickup location availability tests' => sub {
569     plan tests => 4;
570
571     $biblio = $builder->build_sample_biblio({ itemtype => 'ONLY1' });
572     $itemnumber = $builder->build_sample_item({ library => $branch_1, biblionumber => $biblio->biblionumber})->itemnumber;
573     #Add a default rule to allow some holds
574
575     Koha::CirculationRules->set_rules(
576         {
577             branchcode   => undef,
578             categorycode => undef,
579             itemtype     => undef,
580             rules        => {
581                 reservesallowed  => 25,
582                 holds_per_record => 99,
583             }
584         }
585     );
586     my $item = Koha::Items->find($itemnumber);
587     my $branch_to = $builder->build({ source => 'Branch' })->{ branchcode };
588     my $library = Koha::Libraries->find($branch_to);
589     $library->pickup_location('1')->store;
590     my $patron = $builder->build({ source => 'Borrower' })->{ borrowernumber };
591
592     t::lib::Mocks::mock_preference('UseBranchTransferLimits', 1);
593     t::lib::Mocks::mock_preference('BranchTransferLimitsType', 'itemtype');
594
595     $library->pickup_location('1')->store;
596     is(CanItemBeReserved($patron, $item->itemnumber, $branch_to)->{status},
597        'OK', 'Library is a pickup location');
598
599     my $limit = Koha::Item::Transfer::Limit->new({
600         fromBranch => $item->holdingbranch,
601         toBranch => $branch_to,
602         itemtype => $item->effective_itemtype,
603     })->store;
604     is(CanItemBeReserved($patron, $item->itemnumber, $branch_to)->{status},
605        'cannotBeTransferred', 'Item cannot be transferred');
606     $limit->delete;
607
608     $library->pickup_location('0')->store;
609     is(CanItemBeReserved($patron, $item->itemnumber, $branch_to)->{status},
610        'libraryNotPickupLocation', 'Library is not a pickup location');
611     is(CanItemBeReserved($patron, $item->itemnumber, 'nonexistent')->{status},
612        'libraryNotFound', 'Cannot set unknown library as pickup location');
613 };
614
615 $schema->storage->txn_rollback;
616
617 subtest 'CanItemBeReserved / holds_per_day tests' => sub {
618
619     plan tests => 10;
620
621     $schema->storage->txn_begin;
622
623     Koha::Holds->search->delete;
624     $dbh->do('DELETE FROM issues');
625     Koha::Items->search->delete;
626     Koha::Biblios->search->delete;
627
628     my $itemtype = $builder->build_object( { class => 'Koha::ItemTypes' } );
629     my $library  = $builder->build_object( { class => 'Koha::Libraries' } );
630     my $patron   = $builder->build_object( { class => 'Koha::Patrons' } );
631
632     # Create 3 biblios with items
633     my $biblio_1 = $builder->build_sample_biblio({ itemtype => $itemtype->itemtype });
634     my $itemnumber_1 = $builder->build_sample_item({ library => $library->branchcode, biblionumber => $biblio_1->biblionumber})->itemnumber;
635     my $biblio_2 = $builder->build_sample_biblio({ itemtype => $itemtype->itemtype });
636     my $itemnumber_2 = $builder->build_sample_item({ library => $library->branchcode, biblionumber => $biblio_2->biblionumber})->itemnumber;
637     my $biblio_3 = $builder->build_sample_biblio({ itemtype => $itemtype->itemtype });
638     my $itemnumber_3 = $builder->build_sample_item({ library => $library->branchcode, biblionumber => $biblio_3->biblionumber})->itemnumber;
639
640     Koha::CirculationRules->search->delete;
641     Koha::CirculationRules->set_rules(
642         {
643             categorycode => '*',
644             branchcode   => '*',
645             itemtype     => $itemtype->itemtype,
646             rules        => {
647                 reservesallowed  => 1,
648                 holds_per_record => 99,
649                 holds_per_day    => 2
650             }
651         }
652     );
653
654     is_deeply(
655         CanItemBeReserved( $patron->borrowernumber, $itemnumber_1 ),
656         { status => 'OK' },
657         'Patron can reserve item with hold limit of 1, no holds placed'
658     );
659
660     AddReserve(
661         {
662             branchcode     => $library->branchcode,
663             borrowernumber => $patron->borrowernumber,
664             biblionumber   => $biblio_1->biblionumber,
665             priority       => 1,
666         }
667     );
668
669     is_deeply(
670         CanItemBeReserved( $patron->borrowernumber, $itemnumber_1 ),
671         { status => 'tooManyReserves', limit => 1 },
672         'Patron cannot reserve item with hold limit of 1, 1 bib level hold placed'
673     );
674
675     # Raise reservesallowed to avoid tooManyReserves from it
676     Koha::CirculationRules->set_rule(
677         {
678
679             categorycode => '*',
680             branchcode   => '*',
681             itemtype     => $itemtype->itemtype,
682             rule_name  => 'reservesallowed',
683             rule_value => 3,
684         }
685     );
686
687     is_deeply(
688         CanItemBeReserved( $patron->borrowernumber, $itemnumber_2 ),
689         { status => 'OK' },
690         'Patron can reserve item with 2 reserves daily cap'
691     );
692
693     # Add a second reserve
694     my $res_id = AddReserve(
695         {
696             branchcode     => $library->branchcode,
697             borrowernumber => $patron->borrowernumber,
698             biblionumber   => $biblio_2->biblionumber,
699             priority       => 1,
700         }
701     );
702     is_deeply(
703         CanItemBeReserved( $patron->borrowernumber, $itemnumber_2 ),
704         { status => 'tooManyReservesToday', limit => 2 },
705         'Patron cannot a third item with 2 reserves daily cap'
706     );
707
708     # Update last hold so reservedate is in the past, so 2 holds, but different day
709     $hold = Koha::Holds->find($res_id);
710     my $yesterday = dt_from_string() - DateTime::Duration->new( days => 1 );
711     $hold->reservedate($yesterday)->store;
712
713     is_deeply(
714         CanItemBeReserved( $patron->borrowernumber, $itemnumber_2 ),
715         { status => 'OK' },
716         'Patron can reserve item with 2 bib level hold placed on different days, 2 reserves daily cap'
717     );
718
719     # Set holds_per_day to 0
720     Koha::CirculationRules->set_rule(
721         {
722
723             categorycode => '*',
724             branchcode   => '*',
725             itemtype     => $itemtype->itemtype,
726             rule_name  => 'holds_per_day',
727             rule_value => 0,
728         }
729     );
730
731
732     # Delete existing holds
733     Koha::Holds->search->delete;
734     is_deeply(
735         CanItemBeReserved( $patron->borrowernumber, $itemnumber_2 ),
736         { status => 'tooManyReservesToday', limit => 0 },
737         'Patron cannot reserve if holds_per_day is 0 (i.e. 0 is 0)'
738     );
739
740     Koha::CirculationRules->set_rule(
741         {
742
743             categorycode => '*',
744             branchcode   => '*',
745             itemtype     => $itemtype->itemtype,
746             rule_name  => 'holds_per_day',
747             rule_value => undef,
748         }
749     );
750
751     Koha::Holds->search->delete;
752     is_deeply(
753         CanItemBeReserved( $patron->borrowernumber, $itemnumber_2 ),
754         { status => 'OK' },
755         'Patron can reserve if holds_per_day is undef (i.e. undef is unlimited daily cap)'
756     );
757     AddReserve(
758         {
759             branchcode     => $library->branchcode,
760             borrowernumber => $patron->borrowernumber,
761             biblionumber   => $biblio_1->biblionumber,
762             priority       => 1,
763         }
764     );
765     AddReserve(
766         {
767             branchcode     => $library->branchcode,
768             borrowernumber => $patron->borrowernumber,
769             biblionumber   => $biblio_2->biblionumber,
770             priority       => 1,
771         }
772     );
773
774     is_deeply(
775         CanItemBeReserved( $patron->borrowernumber, $itemnumber_3 ),
776         { status => 'OK' },
777         'Patron can reserve if holds_per_day is undef (i.e. undef is unlimited daily cap)'
778     );
779     AddReserve(
780         {
781             branchcode     => $library->branchcode,
782             borrowernumber => $patron->borrowernumber,
783             biblionumber   => $biblio_3->biblionumber,
784             priority       => 1,
785         }
786     );
787     is_deeply(
788         CanItemBeReserved( $patron->borrowernumber, $itemnumber_3 ),
789         { status => 'tooManyReserves', limit => 3 },
790         'Unlimited daily holds, but reached reservesallowed'
791     );
792     #results should be the same for both ReservesControlBranch settings
793     t::lib::Mocks::mock_preference('ReservesControlBranch', 'ItemHomeLibrary');
794     is_deeply(
795         CanItemBeReserved( $patron->borrowernumber, $itemnumber_3 ),
796         { status => 'tooManyReserves', limit => 3 },
797         'Unlimited daily holds, but reached reservesallowed'
798     );
799
800     $schema->storage->txn_rollback;
801 };
802
803 subtest 'CanItemBeReserved / branch_not_in_hold_group' => sub {
804     plan tests => 9;
805
806     $schema->storage->txn_begin;
807
808     # Cleanup database
809     Koha::Holds->search->delete;
810     $dbh->do('DELETE FROM issues');
811     Koha::CirculationRules->set_rule(
812         {
813             branchcode   => undef,
814             categorycode => undef,
815             itemtype     => undef,
816             rule_name    => 'reservesallowed',
817             rule_value   => 25,
818         }
819     );
820
821     Koha::Items->search->delete;
822     Koha::Biblios->search->delete;
823
824     # Create item types
825     my $itemtype1 = $builder->build_object( { class => 'Koha::ItemTypes' } );
826     my $itemtype2 = $builder->build_object( { class => 'Koha::ItemTypes' } );
827
828     # Create libraries
829     my $library1  = $builder->build_object( { class => 'Koha::Libraries' } );
830     my $library2  = $builder->build_object( { class => 'Koha::Libraries' } );
831     my $library3  = $builder->build_object( { class => 'Koha::Libraries' } );
832
833     # Create library groups hierarchy
834     my $rootgroup  = $builder->build_object( { class => 'Koha::Library::Groups', value => {ft_local_hold_group => 1} } );
835     my $group1  = $builder->build_object( { class => 'Koha::Library::Groups', value => {parent_id => $rootgroup->id, branchcode => $library1->branchcode}} );
836     my $group2  = $builder->build_object( { class => 'Koha::Library::Groups', value => {parent_id => $rootgroup->id, branchcode => $library2->branchcode} } );
837
838     # Create 2 patrons
839     my $patron1   = $builder->build_object( { class => 'Koha::Patrons', value => {branchcode => $library1->branchcode} } );
840     my $patron3   = $builder->build_object( { class => 'Koha::Patrons', value => {branchcode => $library3->branchcode} } );
841
842     # Create 3 biblios with items
843     my $biblio_1 = $builder->build_sample_biblio({ itemtype => $itemtype1->itemtype });
844     my $item_1   = $builder->build_sample_item(
845         {
846             biblionumber => $biblio_1->biblionumber,
847             library      => $library1->branchcode
848         }
849     );
850     my $biblio_2 = $builder->build_sample_biblio({ itemtype => $itemtype2->itemtype });
851     my $item_2   = $builder->build_sample_item(
852         {
853             biblionumber => $biblio_2->biblionumber,
854             library      => $library2->branchcode
855         }
856     );
857     my $itemnumber_2 = $item_2->itemnumber;
858     my $biblio_3 = $builder->build_sample_biblio({ itemtype => $itemtype1->itemtype });
859     my $item_3   = $builder->build_sample_item(
860         {
861             biblionumber => $biblio_3->biblionumber,
862             library      => $library1->branchcode
863         }
864     );
865
866     # Test 1: Patron 3 can place hold
867     is_deeply(
868         CanItemBeReserved( $patron3->borrowernumber, $itemnumber_2 ),
869         { status => 'OK' },
870         'Patron can place hold if no circ_rules where defined'
871     );
872
873     # Insert default circ rule of holds allowed only from local hold group for all libraries
874     Koha::CirculationRules->set_rules(
875         {
876             branchcode => undef,
877             itemtype   => undef,
878             rules => {
879                 holdallowed => 3,
880                 hold_fulfillment_policy => 'any',
881                 returnbranch => 'any'
882             }
883         }
884     );
885
886     # Test 2: Patron 1 can place hold
887     is_deeply(
888         CanItemBeReserved( $patron1->borrowernumber, $itemnumber_2 ),
889         { status => 'OK' },
890         'Patron can place hold because patron\'s home library is part of hold group'
891     );
892
893     # Test 3: Patron 3 cannot place hold
894     is_deeply(
895         CanItemBeReserved( $patron3->borrowernumber, $itemnumber_2 ),
896         { status => 'branchNotInHoldGroup' },
897         'Patron cannot place hold because patron\'s home library is not part of hold group'
898     );
899
900     # Insert default circ rule to "any" for library 2
901     Koha::CirculationRules->set_rules(
902         {
903             branchcode => $library2->branchcode,
904             itemtype   => undef,
905             rules => {
906                 holdallowed => 2,
907                 hold_fulfillment_policy => 'any',
908                 returnbranch => 'any'
909             }
910         }
911     );
912
913     # Test 4: Patron 3 can place hold
914     is_deeply(
915         CanItemBeReserved( $patron3->borrowernumber, $itemnumber_2 ),
916         { status => 'OK' },
917         'Patron can place hold if holdallowed is set to "any" for library 2'
918     );
919
920     # Update default circ rule to "hold group" for library 2
921     Koha::CirculationRules->set_rules(
922         {
923             branchcode => $library2->branchcode,
924             itemtype   => undef,
925             rules => {
926                 holdallowed => 3,
927                 hold_fulfillment_policy => 'any',
928                 returnbranch => 'any'
929             }
930         }
931     );
932
933     # Test 5: Patron 3 cannot place hold
934     is_deeply(
935         CanItemBeReserved( $patron3->borrowernumber, $itemnumber_2 ),
936         { status => 'branchNotInHoldGroup' },
937         'Patron cannot place hold if holdallowed is set to "hold group" for library 2'
938     );
939
940     # Insert default item rule to "any" for itemtype 2
941     Koha::CirculationRules->set_rules(
942         {
943             branchcode => $library2->branchcode,
944             itemtype   => $itemtype2->itemtype,
945             rules => {
946                 holdallowed => 2,
947                 hold_fulfillment_policy => 'any',
948                 returnbranch => 'any'
949             }
950         }
951     );
952
953     # Test 6: Patron 3 can place hold
954     is_deeply(
955         CanItemBeReserved( $patron3->borrowernumber, $itemnumber_2 ),
956         { status => 'OK' },
957         'Patron can place hold if holdallowed is set to "any" for itemtype 2'
958     );
959
960     # Update default item rule to "hold group" for itemtype 2
961     Koha::CirculationRules->set_rules(
962         {
963             branchcode => $library2->branchcode,
964             itemtype   => $itemtype2->itemtype,
965             rules => {
966                 holdallowed => 3,
967                 hold_fulfillment_policy => 'any',
968                 returnbranch => 'any'
969             }
970         }
971     );
972
973     # Test 7: Patron 3 cannot place hold
974     is_deeply(
975         CanItemBeReserved( $patron3->borrowernumber, $itemnumber_2 ),
976         { status => 'branchNotInHoldGroup' },
977         'Patron cannot place hold if holdallowed is set to "hold group" for itemtype 2'
978     );
979
980     # Insert branch item rule to "any" for itemtype 2 and library 2
981     Koha::CirculationRules->set_rules(
982         {
983             branchcode => $library2->branchcode,
984             itemtype   => $itemtype2->itemtype,
985             rules => {
986                 holdallowed => 2,
987                 hold_fulfillment_policy => 'any',
988                 returnbranch => 'any'
989             }
990         }
991     );
992
993     # Test 8: Patron 3 can place hold
994     is_deeply(
995         CanItemBeReserved( $patron3->borrowernumber, $itemnumber_2 ),
996         { status => 'OK' },
997         'Patron can place hold if holdallowed is set to "any" for itemtype 2 and library 2'
998     );
999
1000     # Update branch item rule to "hold group" for itemtype 2 and library 2
1001     Koha::CirculationRules->set_rules(
1002         {
1003             branchcode => $library2->branchcode,
1004             itemtype   => $itemtype2->itemtype,
1005             rules => {
1006                 holdallowed => 3,
1007                 hold_fulfillment_policy => 'any',
1008                 returnbranch => 'any'
1009             }
1010         }
1011     );
1012
1013     # Test 9: Patron 3 cannot place hold
1014     is_deeply(
1015         CanItemBeReserved( $patron3->borrowernumber, $itemnumber_2 ),
1016         { status => 'branchNotInHoldGroup' },
1017         'Patron cannot place hold if holdallowed is set to "hold group" for itemtype 2 and library 2'
1018     );
1019
1020     $schema->storage->txn_rollback;
1021
1022 };
1023
1024 subtest 'CanItemBeReserved / pickup_not_in_hold_group' => sub {
1025     plan tests => 9;
1026
1027     $schema->storage->txn_begin;
1028
1029     # Cleanup database
1030     Koha::Holds->search->delete;
1031     $dbh->do('DELETE FROM issues');
1032     Koha::CirculationRules->set_rule(
1033         {
1034             branchcode   => undef,
1035             categorycode => undef,
1036             itemtype     => undef,
1037             rule_name    => 'reservesallowed',
1038             rule_value   => 25,
1039         }
1040     );
1041
1042     Koha::Items->search->delete;
1043     Koha::Biblios->search->delete;
1044
1045     # Create item types
1046     my $itemtype1 = $builder->build_object( { class => 'Koha::ItemTypes' } );
1047     my $itemtype2 = $builder->build_object( { class => 'Koha::ItemTypes' } );
1048
1049     # Create libraries
1050     my $library1  = $builder->build_object( { class => 'Koha::Libraries', value => {pickup_location => 1} } );
1051     my $library2  = $builder->build_object( { class => 'Koha::Libraries', value => {pickup_location => 1} } );
1052     my $library3  = $builder->build_object( { class => 'Koha::Libraries', value => {pickup_location => 1} } );
1053
1054     # Create library groups hierarchy
1055     my $rootgroup  = $builder->build_object( { class => 'Koha::Library::Groups', value => {ft_local_hold_group => 1} } );
1056     my $group1  = $builder->build_object( { class => 'Koha::Library::Groups', value => {parent_id => $rootgroup->id, branchcode => $library1->branchcode}} );
1057     my $group2  = $builder->build_object( { class => 'Koha::Library::Groups', value => {parent_id => $rootgroup->id, branchcode => $library2->branchcode} } );
1058
1059     # Create 2 patrons
1060     my $patron1   = $builder->build_object( { class => 'Koha::Patrons', value => {branchcode => $library1->branchcode} } );
1061     my $patron3   = $builder->build_object( { class => 'Koha::Patrons', value => {branchcode => $library3->branchcode} } );
1062
1063     # Create 3 biblios with items
1064     my $biblio_1 = $builder->build_sample_biblio({ itemtype => $itemtype1->itemtype });
1065     my $item_1   = $builder->build_sample_item(
1066         {
1067             biblionumber => $biblio_1->biblionumber,
1068             library      => $library1->branchcode
1069         }
1070     );
1071     my $biblio_2 = $builder->build_sample_biblio({ itemtype => $itemtype2->itemtype });
1072     my $item_2   = $builder->build_sample_item(
1073         {
1074             biblionumber => $biblio_2->biblionumber,
1075             library      => $library2->branchcode
1076         }
1077     );
1078     my $itemnumber_2 = $item_2->itemnumber;
1079     my $biblio_3 = $builder->build_sample_biblio({ itemtype => $itemtype1->itemtype });
1080     my $item_3   = $builder->build_sample_item(
1081         {
1082             biblionumber => $biblio_3->biblionumber,
1083             library      => $library1->branchcode
1084         }
1085     );
1086
1087     # Test 1: Patron 3 can place hold
1088     is_deeply(
1089         CanItemBeReserved( $patron3->borrowernumber, $itemnumber_2, $library3->branchcode ),
1090         { status => 'OK' },
1091         'Patron can place hold if no circ_rules where defined'
1092     );
1093
1094     # Insert default circ rule of holds allowed only from local hold group for all libraries
1095     Koha::CirculationRules->set_rules(
1096         {
1097             branchcode => undef,
1098             itemtype   => undef,
1099             rules => {
1100                 holdallowed => 2,
1101                 hold_fulfillment_policy => 'holdgroup',
1102                 returnbranch => 'any'
1103             }
1104         }
1105     );
1106
1107     # Test 2: Patron 1 can place hold
1108     is_deeply(
1109         CanItemBeReserved( $patron3->borrowernumber, $itemnumber_2, $library1->branchcode ),
1110         { status => 'OK' },
1111         'Patron can place hold because pickup location is part of hold group'
1112     );
1113
1114     # Test 3: Patron 3 cannot place hold
1115     is_deeply(
1116         CanItemBeReserved( $patron3->borrowernumber, $itemnumber_2, $library3->branchcode ),
1117         { status => 'pickupNotInHoldGroup' },
1118         'Patron cannot place hold because pickup location is not part of hold group'
1119     );
1120
1121     # Insert default circ rule to "any" for library 2
1122     Koha::CirculationRules->set_rules(
1123         {
1124             branchcode => $library2->branchcode,
1125             itemtype   => undef,
1126             rules => {
1127                 holdallowed => 2,
1128                 hold_fulfillment_policy => 'any',
1129                 returnbranch => 'any'
1130             }
1131         }
1132     );
1133
1134     # Test 4: Patron 3 can place hold
1135     is_deeply(
1136         CanItemBeReserved( $patron3->borrowernumber, $itemnumber_2, $library3->branchcode ),
1137         { status => 'OK' },
1138         'Patron can place hold if default_branch_circ_rules is set to "any" for library 2'
1139     );
1140
1141     # Update default circ rule to "hold group" for library 2
1142     Koha::CirculationRules->set_rules(
1143         {
1144             branchcode => $library2->branchcode,
1145             itemtype   => undef,
1146             rules => {
1147                 holdallowed => 2,
1148                 hold_fulfillment_policy => 'holdgroup',
1149                 returnbranch => 'any'
1150             }
1151         }
1152     );
1153
1154     # Test 5: Patron 3 cannot place hold
1155     is_deeply(
1156         CanItemBeReserved( $patron3->borrowernumber, $itemnumber_2, $library3->branchcode ),
1157         { status => 'pickupNotInHoldGroup' },
1158         'Patron cannot place hold if hold_fulfillment_policy is set to "hold group" for library 2'
1159     );
1160
1161     # Insert default item rule to "any" for itemtype 2
1162     Koha::CirculationRules->set_rules(
1163         {
1164             branchcode => $library2->branchcode,
1165             itemtype   => $itemtype2->itemtype,
1166             rules => {
1167                 holdallowed => 2,
1168                 hold_fulfillment_policy => 'any',
1169                 returnbranch => 'any'
1170             }
1171         }
1172     );
1173
1174     # Test 6: Patron 3 can place hold
1175     is_deeply(
1176         CanItemBeReserved( $patron3->borrowernumber, $itemnumber_2, $library3->branchcode ),
1177         { status => 'OK' },
1178         'Patron can place hold if hold_fulfillment_policy is set to "any" for itemtype 2'
1179     );
1180
1181     # Update default item rule to "hold group" for itemtype 2
1182     Koha::CirculationRules->set_rules(
1183         {
1184             branchcode => $library2->branchcode,
1185             itemtype   => $itemtype2->itemtype,
1186             rules => {
1187                 holdallowed => 2,
1188                 hold_fulfillment_policy => 'holdgroup',
1189                 returnbranch => 'any'
1190             }
1191         }
1192     );
1193
1194     # Test 7: Patron 3 cannot place hold
1195     is_deeply(
1196         CanItemBeReserved( $patron3->borrowernumber, $itemnumber_2, $library3->branchcode ),
1197         { status => 'pickupNotInHoldGroup' },
1198         'Patron cannot place hold if hold_fulfillment_policy is set to "hold group" for itemtype 2'
1199     );
1200
1201     # Insert branch item rule to "any" for itemtype 2 and library 2
1202     Koha::CirculationRules->set_rules(
1203         {
1204             branchcode => $library2->branchcode,
1205             itemtype   => $itemtype2->itemtype,
1206             rules => {
1207                 holdallowed => 2,
1208                 hold_fulfillment_policy => 'any',
1209                 returnbranch => 'any'
1210             }
1211         }
1212     );
1213
1214     # Test 8: Patron 3 can place hold
1215     is_deeply(
1216         CanItemBeReserved( $patron3->borrowernumber, $itemnumber_2, $library3->branchcode ),
1217         { status => 'OK' },
1218         'Patron can place hold if hold_fulfillment_policy is set to "any" for itemtype 2 and library 2'
1219     );
1220
1221     # Update branch item rule to "hold group" for itemtype 2 and library 2
1222     Koha::CirculationRules->set_rules(
1223         {
1224             branchcode => $library2->branchcode,
1225             itemtype   => $itemtype2->itemtype,
1226             rules => {
1227                 holdallowed => 2,
1228                 hold_fulfillment_policy => 'holdgroup',
1229                 returnbranch => 'any'
1230             }
1231         }
1232     );
1233
1234     # Test 9: Patron 3 cannot place hold
1235     is_deeply(
1236         CanItemBeReserved( $patron3->borrowernumber, $itemnumber_2, $library3->branchcode ),
1237         { status => 'pickupNotInHoldGroup' },
1238         'Patron cannot place hold if hold_fulfillment_policy is set to "hold group" for itemtype 2 and library 2'
1239     );
1240
1241     $schema->storage->txn_rollback;
1242 };