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