Bug 24350: ->pickup_locations returns unblessed objects
[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 # Cleanup circulation rules
248 $dbh->do('DELETE FROM circulation_rules');
249 # $dbh->do(
250 #     q{INSERT INTO issuingrules (categorycode, branchcode, itemtype, reservesallowed, holds_per_record)
251 #       VALUES (?, ?, ?, ?, ?)},
252 #     {},
253 #     '*', '*', '*', 25, 99
254 # );
255 $dbh->do(
256     q{INSERT INTO issuingrules (categorycode, branchcode, itemtype, reservesallowed, holds_per_record)
257       VALUES (?, ?, ?, ?, ?)},
258     {},
259     '*', '*', 'CANNOT', 0, 99
260 );
261
262 # make sure some basic sysprefs are set
263 t::lib::Mocks::mock_preference('ReservesControlBranch', 'ItemHomeLibrary');
264 t::lib::Mocks::mock_preference('item-level_itypes', 1);
265
266 # if IndependentBranches is OFF, a $branch_1 patron can reserve an $branch_2 item
267 t::lib::Mocks::mock_preference('IndependentBranches', 0);
268
269 ok(
270     CanItemBeReserved($borrowernumbers[0], $foreign_itemnumber)->{status} eq 'OK',
271     '$branch_1 patron allowed to reserve $branch_2 item with IndependentBranches OFF (bug 2394)'
272 );
273
274 # if IndependentBranches is OFF, a $branch_1 patron cannot reserve an $branch_2 item
275 t::lib::Mocks::mock_preference('IndependentBranches', 1);
276 t::lib::Mocks::mock_preference('canreservefromotherbranches', 0);
277 ok(
278     CanItemBeReserved($borrowernumbers[0], $foreign_itemnumber)->{status} eq 'cannotReserveFromOtherBranches',
279     '$branch_1 patron NOT allowed to reserve $branch_2 item with IndependentBranches ON ... (bug 2394)'
280 );
281
282 # ... unless canreservefromotherbranches is ON
283 t::lib::Mocks::mock_preference('canreservefromotherbranches', 1);
284 ok(
285     CanItemBeReserved($borrowernumbers[0], $foreign_itemnumber)->{status} eq 'OK',
286     '... unless canreservefromotherbranches is ON (bug 2394)'
287 );
288
289 {
290     # Regression test for bug 11336 # Test if ModReserve correctly recalculate the priorities
291     $biblio = $builder->build_sample_biblio({ itemtype => 'DUMMY' });
292     ($item_bibnum, $item_bibitemnum, $itemnumber) = AddItem({ homebranch => $branch_1, holdingbranch => $branch_1 } , $biblio->biblionumber);
293     my $reserveid1 = AddReserve($branch_1, $borrowernumbers[0], $biblio->biblionumber, '', 1);
294     ($item_bibnum, $item_bibitemnum, $itemnumber) = AddItem({ homebranch => $branch_1, holdingbranch => $branch_1 } , $biblio->biblionumber);
295     my $reserveid2 = AddReserve($branch_1, $borrowernumbers[1], $biblio->biblionumber, '', 2);
296     ($item_bibnum, $item_bibitemnum, $itemnumber) = AddItem({ homebranch => $branch_1, holdingbranch => $branch_1 } , $biblio->biblionumber);
297     my $reserveid3 = AddReserve($branch_1, $borrowernumbers[2], $biblio->biblionumber, '', 3);
298     my $hhh = Koha::Holds->search({ biblionumber => $biblio->biblionumber });
299     my $hold3 = Koha::Holds->find( $reserveid3 );
300     is( $hold3->priority, 3, "The 3rd hold should have a priority set to 3" );
301     ModReserve({ reserve_id => $reserveid1, rank => 'del' });
302     ModReserve({ reserve_id => $reserveid2, rank => 'del' });
303     is( $hold3->discard_changes->priority, 1, "After ModReserve, the 3rd reserve becomes the first on the waiting list" );
304 }
305
306 ModItem({ damaged => 1 }, $item_bibnum, $itemnumber);
307 t::lib::Mocks::mock_preference( 'AllowHoldsOnDamagedItems', 1 );
308 is( CanItemBeReserved( $borrowernumbers[0], $itemnumber)->{status}, 'OK', "Patron can reserve damaged item with AllowHoldsOnDamagedItems enabled" );
309 ok( defined( ( CheckReserves($itemnumber) )[1] ), "Hold can be trapped for damaged item with AllowHoldsOnDamagedItems enabled" );
310
311 $hold = Koha::Hold->new(
312     {
313         borrowernumber => $borrowernumbers[0],
314         itemnumber     => $itemnumber,
315         biblionumber   => $item_bibnum,
316     }
317 )->store();
318 is( CanItemBeReserved( $borrowernumbers[0], $itemnumber )->{status},
319     'itemAlreadyOnHold',
320     "Patron cannot place a second item level hold for a given item" );
321 $hold->delete();
322
323 t::lib::Mocks::mock_preference( 'AllowHoldsOnDamagedItems', 0 );
324 ok( CanItemBeReserved( $borrowernumbers[0], $itemnumber)->{status} eq 'damaged', "Patron cannot reserve damaged item with AllowHoldsOnDamagedItems disabled" );
325 ok( !defined( ( CheckReserves($itemnumber) )[1] ), "Hold cannot be trapped for damaged item with AllowHoldsOnDamagedItems disabled" );
326
327 # Regression test for bug 9532
328 $biblio = $builder->build_sample_biblio({ itemtype => 'CANNOT' });
329 ($item_bibnum, $item_bibitemnum, $itemnumber) = AddItem({ homebranch => $branch_1, holdingbranch => $branch_1, itype => 'CANNOT' } , $biblio->biblionumber);
330 AddReserve(
331     $branch_1,
332     $borrowernumbers[0],
333     $biblio->biblionumber,
334     '',
335     1,
336 );
337 is(
338     CanItemBeReserved( $borrowernumbers[0], $itemnumber)->{status}, 'tooManyReserves',
339     "cannot request item if policy that matches on item-level item type forbids it"
340 );
341 ModItem({ itype => 'CAN' }, $item_bibnum, $itemnumber);
342 ok(
343     CanItemBeReserved( $borrowernumbers[0], $itemnumber)->{status} eq 'OK',
344     "can request item if policy that matches on item type allows it"
345 );
346
347 t::lib::Mocks::mock_preference('item-level_itypes', 0);
348 ModItem({ itype => undef }, $item_bibnum, $itemnumber);
349 ok(
350     CanItemBeReserved( $borrowernumbers[0], $itemnumber)->{status} eq 'tooManyReserves',
351     "cannot request item if policy that matches on bib-level item type forbids it (bug 9532)"
352 );
353
354
355 # Test branch item rules
356
357 $dbh->do('DELETE FROM issuingrules');
358 $dbh->do('DELETE FROM circulation_rules');
359 $dbh->do(
360     q{INSERT INTO issuingrules (categorycode, branchcode, itemtype, reservesallowed)
361       VALUES (?, ?, ?, ?)},
362     {},
363     '*', '*', '*', 25
364 );
365 Koha::CirculationRules->set_rules(
366     {
367         branchcode => $branch_1,
368         itemtype   => 'CANNOT',
369         categorycode => undef,
370         rules => {
371             holdallowed => 0,
372             returnbranch => 'homebranch',
373         }
374     }
375 );
376 Koha::CirculationRules->set_rules(
377     {
378         branchcode => $branch_1,
379         itemtype   => 'CAN',
380         categorycode => undef,
381         rules => {
382             holdallowed => 1,
383             returnbranch => 'homebranch',
384         }
385     }
386 );
387 $biblio = $builder->build_sample_biblio({ itemtype => 'CANNOT' });
388 ($item_bibnum, $item_bibitemnum, $itemnumber) = AddItem(
389     { homebranch => $branch_1, holdingbranch => $branch_1, itype => 'CANNOT' } , $biblio->biblionumber);
390 is(CanItemBeReserved($borrowernumbers[0], $itemnumber)->{status}, 'notReservable',
391     "CanItemBeReserved should return 'notReservable'");
392
393 t::lib::Mocks::mock_preference( 'ReservesControlBranch', 'PatronLibrary' );
394 ($item_bibnum, $item_bibitemnum, $itemnumber) = AddItem(
395     { homebranch => $branch_2, holdingbranch => $branch_1, itype => 'CAN' } , $biblio->biblionumber);
396 is(CanItemBeReserved($borrowernumbers[0], $itemnumber)->{status},
397     'cannotReserveFromOtherBranches',
398     "CanItemBeReserved should use PatronLibrary rule when ReservesControlBranch set to 'PatronLibrary'");
399 t::lib::Mocks::mock_preference( 'ReservesControlBranch', 'ItemHomeLibrary' );
400 is(CanItemBeReserved($borrowernumbers[0], $itemnumber)->{status},
401     'OK',
402     "CanItemBeReserved should use item home library rule when ReservesControlBranch set to 'ItemsHomeLibrary'");
403
404 ($item_bibnum, $item_bibitemnum, $itemnumber) = AddItem(
405     { homebranch => $branch_1, holdingbranch => $branch_1, itype => 'CAN' } , $biblio->biblionumber);
406 is(CanItemBeReserved($borrowernumbers[0], $itemnumber)->{status}, 'OK',
407     "CanItemBeReserved should return 'OK'");
408
409 # Bug 12632
410 t::lib::Mocks::mock_preference( 'item-level_itypes',     1 );
411 t::lib::Mocks::mock_preference( 'ReservesControlBranch', 'PatronLibrary' );
412
413 $dbh->do('DELETE FROM reserves');
414 $dbh->do('DELETE FROM issues');
415 $dbh->do('DELETE FROM items');
416 $dbh->do('DELETE FROM biblio');
417
418 $biblio = $builder->build_sample_biblio({ itemtype => 'ONLY1' });
419 ( $item_bibnum, $item_bibitemnum, $itemnumber )
420     = AddItem( { homebranch => $branch_1, holdingbranch => $branch_1 }, $biblio->biblionumber );
421
422 $dbh->do(
423     q{INSERT INTO issuingrules (categorycode, branchcode, itemtype, reservesallowed, holds_per_record)
424       VALUES (?, ?, ?, ?, ?)},
425     {},
426     '*', '*', 'ONLY1', 1, 99
427 );
428 is( CanItemBeReserved( $borrowernumbers[0], $itemnumber )->{status},
429     'OK', 'Patron can reserve item with hold limit of 1, no holds placed' );
430
431 my $res_id = AddReserve( $branch_1, $borrowernumbers[0], $biblio->biblionumber, '', 1, );
432
433 is( CanItemBeReserved( $borrowernumbers[0], $itemnumber )->{status},
434     'tooManyReserves', 'Patron cannot reserve item with hold limit of 1, 1 bib level hold placed' );
435
436     #results should be the same for both ReservesControlBranch settings
437 t::lib::Mocks::mock_preference( 'ReservesControlBranch', 'ItemHomeLibrary' );
438 is( CanItemBeReserved( $borrowernumbers[0], $itemnumber )->{status},
439     'tooManyReserves', 'Patron cannot reserve item with hold limit of 1, 1 bib level hold placed' );
440 #reset for further tests
441 t::lib::Mocks::mock_preference( 'ReservesControlBranch', 'PatronLibrary' );
442
443 subtest 'Test max_holds per library/patron category' => sub {
444     plan tests => 6;
445
446     $dbh->do('DELETE FROM reserves');
447     $dbh->do('DELETE FROM issuingrules');
448     $dbh->do('DELETE FROM circulation_rules');
449
450     $biblio = $builder->build_sample_biblio({ itemtype => 'TEST' });
451     ( $item_bibnum, $item_bibitemnum, $itemnumber ) =
452       AddItem( { homebranch => $branch_1, holdingbranch => $branch_1 },
453         $biblio->biblionumber );
454     $dbh->do(
455         q{
456             INSERT INTO issuingrules (categorycode, branchcode, itemtype, reservesallowed, holds_per_record)
457             VALUES (?, ?, ?, ?, ?)
458         },
459         {},
460         '*', '*', 'TEST', 99, 99
461     );
462     AddReserve( $branch_1, $borrowernumbers[0], $biblio->biblionumber, '', 1, );
463     AddReserve( $branch_1, $borrowernumbers[0], $biblio->biblionumber, '', 1, );
464     AddReserve( $branch_1, $borrowernumbers[0], $biblio->biblionumber, '', 1, );
465
466     my $count =
467       Koha::Holds->search( { borrowernumber => $borrowernumbers[0] } )->count();
468     is( $count, 3, 'Patron now has 3 holds' );
469
470     my $ret = CanItemBeReserved( $borrowernumbers[0], $itemnumber );
471     is( $ret->{status}, 'OK', 'Patron can place hold with no borrower circ rules' );
472
473     my $rule_all = Koha::CirculationRules->set_rule(
474         {
475             categorycode => $category->{categorycode},
476             branchcode   => undef,
477             itemtype     => undef,
478             rule_name    => 'max_holds',
479             rule_value   => 3,
480         }
481     );
482
483     my $rule_branch = Koha::CirculationRules->set_rule(
484         {
485             branchcode   => $branch_1,
486             categorycode => $category->{categorycode},
487             itemtype     => undef,
488             rule_name    => 'max_holds',
489             rule_value   => 5,
490         }
491     );
492
493     $ret = CanItemBeReserved( $borrowernumbers[0], $itemnumber );
494     is( $ret->{status}, 'OK', 'Patron can place hold with branch/category rule of 5, category rule of 3' );
495
496     $rule_branch->delete();
497
498     $ret = CanItemBeReserved( $borrowernumbers[0], $itemnumber );
499     is( $ret->{status}, 'tooManyReserves', 'Patron cannot place hold with only a category rule of 3' );
500
501     $rule_all->delete();
502     $rule_branch->rule_value(3);
503     $rule_branch->store();
504
505     $ret = CanItemBeReserved( $borrowernumbers[0], $itemnumber );
506     is( $ret->{status}, 'tooManyReserves', 'Patron cannot place hold with only a branch/category rule of 3' );
507
508     $rule_branch->rule_value(5);
509     $rule_branch->update();
510     $rule_branch->rule_value(5);
511     $rule_branch->store();
512
513     $ret = CanItemBeReserved( $borrowernumbers[0], $itemnumber );
514     is( $ret->{status}, 'OK', 'Patron can place hold with branch/category rule of 5, category rule of 5' );
515 };
516
517 subtest 'Pickup location availability tests' => sub {
518     plan tests => 4;
519
520     $biblio = $builder->build_sample_biblio({ itemtype => 'ONLY1' });
521     my ( $item_bibnum, $item_bibitemnum, $itemnumber )
522     = AddItem( { homebranch => $branch_1, holdingbranch => $branch_1 }, $biblio->biblionumber );
523     #Add a default rule to allow some holds
524     $dbh->do(
525         q{INSERT INTO issuingrules (categorycode, branchcode, itemtype, reservesallowed, holds_per_record)
526           VALUES (?, ?, ?, ?, ?)},
527         {},
528         '*', '*', '*', 25, 99
529     );
530     my $item = Koha::Items->find($itemnumber);
531     my $branch_to = $builder->build({ source => 'Branch' })->{ branchcode };
532     my $library = Koha::Libraries->find($branch_to);
533     $library->pickup_location('1')->store;
534     my $patron = $builder->build({ source => 'Borrower' })->{ borrowernumber };
535
536     t::lib::Mocks::mock_preference('UseBranchTransferLimits', 1);
537     t::lib::Mocks::mock_preference('BranchTransferLimitsType', 'itemtype');
538
539     $library->pickup_location('1')->store;
540     is(CanItemBeReserved($patron, $item->itemnumber, $branch_to)->{status},
541        'OK', 'Library is a pickup location');
542
543     my $limit = Koha::Item::Transfer::Limit->new({
544         fromBranch => $item->holdingbranch,
545         toBranch => $branch_to,
546         itemtype => $item->effective_itemtype,
547     })->store;
548     is(CanItemBeReserved($patron, $item->itemnumber, $branch_to)->{status},
549        'cannotBeTransferred', 'Item cannot be transferred');
550     $limit->delete;
551
552     $library->pickup_location('0')->store;
553     is(CanItemBeReserved($patron, $item->itemnumber, $branch_to)->{status},
554        'libraryNotPickupLocation', 'Library is not a pickup location');
555     is(CanItemBeReserved($patron, $item->itemnumber, 'nonexistent')->{status},
556        'libraryNotFound', 'Cannot set unknown library as pickup location');
557 };
558
559 $schema->storage->txn_rollback;
560
561 subtest 'CanItemBeReserved / holds_per_day tests' => sub {
562
563     plan tests => 10;
564
565     $schema->storage->txn_begin;
566
567     Koha::Holds->search->delete;
568     $dbh->do('DELETE FROM issues');
569     $dbh->do('DELETE FROM issuingrules');
570     $dbh->do('DELETE FROM circulation_rules');
571     Koha::Items->search->delete;
572     Koha::Biblios->search->delete;
573
574     my $itemtype = $builder->build_object( { class => 'Koha::ItemTypes' } );
575     my $library  = $builder->build_object( { class => 'Koha::Libraries' } );
576     my $patron   = $builder->build_object( { class => 'Koha::Patrons' } );
577
578     # Create 3 biblios with items
579     my $biblio_1 = $builder->build_sample_biblio({ itemtype => $itemtype->itemtype });
580     my ( undef, undef, $itemnumber_1 ) = AddItem(
581         {   homebranch    => $library->branchcode,
582             holdingbranch => $library->branchcode
583         },
584         $biblio_1->biblionumber
585     );
586     my $biblio_2 = $builder->build_sample_biblio({ itemtype => $itemtype->itemtype });
587     my ( undef, undef, $itemnumber_2 ) = AddItem(
588         {   homebranch    => $library->branchcode,
589             holdingbranch => $library->branchcode
590         },
591         $biblio_2->biblionumber
592     );
593     my $biblio_3 = $builder->build_sample_biblio({ itemtype => $itemtype->itemtype });
594     my ( undef, undef, $itemnumber_3 ) = AddItem(
595         {   homebranch    => $library->branchcode,
596             holdingbranch => $library->branchcode
597         },
598         $biblio_3->biblionumber
599     );
600
601     Koha::IssuingRules->search->delete;
602     my $issuingrule = Koha::IssuingRule->new(
603         {   categorycode     => '*',
604             branchcode       => '*',
605             itemtype         => $itemtype->itemtype,
606             reservesallowed  => 1,
607             holds_per_record => 99,
608             holds_per_day    => 2
609         }
610     )->store;
611
612     is_deeply(
613         CanItemBeReserved( $patron->borrowernumber, $itemnumber_1 ),
614         { status => 'OK' },
615         'Patron can reserve item with hold limit of 1, no holds placed'
616     );
617
618     AddReserve( $library->branchcode, $patron->borrowernumber, $biblio_1->biblionumber, '', 1, );
619
620     is_deeply(
621         CanItemBeReserved( $patron->borrowernumber, $itemnumber_1 ),
622         { status => 'tooManyReserves', limit => 1 },
623         'Patron cannot reserve item with hold limit of 1, 1 bib level hold placed'
624     );
625
626     # Raise reservesallowed to avoid tooManyReserves from it
627     $issuingrule->set( { reservesallowed => 3 } )->store;
628
629     is_deeply(
630         CanItemBeReserved( $patron->borrowernumber, $itemnumber_2 ),
631         { status => 'OK' },
632         'Patron can reserve item with 2 reserves daily cap'
633     );
634
635     # Add a second reserve
636     my $res_id = AddReserve( $library->branchcode, $patron->borrowernumber, $biblio_2->biblionumber, '', 1, );
637     is_deeply(
638         CanItemBeReserved( $patron->borrowernumber, $itemnumber_2 ),
639         { status => 'tooManyReservesToday', limit => 2 },
640         'Patron cannot a third item with 2 reserves daily cap'
641     );
642
643     # Update last hold so reservedate is in the past, so 2 holds, but different day
644     $hold = Koha::Holds->find($res_id);
645     my $yesterday = dt_from_string() - DateTime::Duration->new( days => 1 );
646     $hold->reservedate($yesterday)->store;
647
648     is_deeply(
649         CanItemBeReserved( $patron->borrowernumber, $itemnumber_2 ),
650         { status => 'OK' },
651         'Patron can reserve item with 2 bib level hold placed on different days, 2 reserves daily cap'
652     );
653
654     # Set holds_per_day to 0
655     $issuingrule->set( { holds_per_day => 0 } )->store;
656
657     # Delete existing holds
658     Koha::Holds->search->delete;
659     is_deeply(
660         CanItemBeReserved( $patron->borrowernumber, $itemnumber_2 ),
661         { status => 'tooManyReservesToday', limit => 0 },
662         'Patron cannot reserve if holds_per_day is 0 (i.e. 0 is 0)'
663     );
664
665     $issuingrule->set( { holds_per_day => undef } )->store;
666     Koha::Holds->search->delete;
667     is_deeply(
668         CanItemBeReserved( $patron->borrowernumber, $itemnumber_2 ),
669         { status => 'OK' },
670         'Patron can reserve if holds_per_day is undef (i.e. undef is unlimited daily cap)'
671     );
672     AddReserve( $library->branchcode, $patron->borrowernumber, $biblio_1->biblionumber, '', 1, );
673     AddReserve( $library->branchcode, $patron->borrowernumber, $biblio_2->biblionumber, '', 1, );
674     is_deeply(
675         CanItemBeReserved( $patron->borrowernumber, $itemnumber_3 ),
676         { status => 'OK' },
677         'Patron can reserve if holds_per_day is undef (i.e. undef is unlimited daily cap)'
678     );
679     AddReserve( $library->branchcode, $patron->borrowernumber, $biblio_3->biblionumber, '', 1, );
680     is_deeply(
681         CanItemBeReserved( $patron->borrowernumber, $itemnumber_3 ),
682         { status => 'tooManyReserves', limit => 3 },
683         'Unlimited daily holds, but reached reservesallowed'
684     );
685     #results should be the same for both ReservesControlBranch settings
686     t::lib::Mocks::mock_preference('ReservesControlBranch', 'ItemHomeLibrary');
687     is_deeply(
688         CanItemBeReserved( $patron->borrowernumber, $itemnumber_3 ),
689         { status => 'tooManyReserves', limit => 3 },
690         'Unlimited daily holds, but reached reservesallowed'
691     );
692
693     $schema->storage->txn_rollback;
694 };
695
696 subtest 'CanItemBeReserved / branch_not_in_hold_group' => sub {
697     plan tests => 9;
698
699     $schema->storage->txn_begin;
700
701     # Cleanup database
702     Koha::Holds->search->delete;
703     $dbh->do('DELETE FROM issues');
704     $dbh->do('DELETE FROM issuingrules');
705     $dbh->do(
706         q{INSERT INTO issuingrules (categorycode, branchcode, itemtype, reservesallowed)
707         VALUES (?, ?, ?, ?)},
708         {},
709         '*', '*', '*', 25
710     );
711     $dbh->do('DELETE FROM circulation_rules');
712
713     Koha::Items->search->delete;
714     Koha::Biblios->search->delete;
715
716     # Create item types
717     my $itemtype1 = $builder->build_object( { class => 'Koha::ItemTypes' } );
718     my $itemtype2 = $builder->build_object( { class => 'Koha::ItemTypes' } );
719
720     # Create libraries
721     my $library1  = $builder->build_object( { class => 'Koha::Libraries' } );
722     my $library2  = $builder->build_object( { class => 'Koha::Libraries' } );
723     my $library3  = $builder->build_object( { class => 'Koha::Libraries' } );
724
725     # Create library groups hierarchy
726     my $rootgroup  = $builder->build_object( { class => 'Koha::Library::Groups', value => {ft_local_hold_group => 1} } );
727     my $group1  = $builder->build_object( { class => 'Koha::Library::Groups', value => {parent_id => $rootgroup->id, branchcode => $library1->branchcode}} );
728     my $group2  = $builder->build_object( { class => 'Koha::Library::Groups', value => {parent_id => $rootgroup->id, branchcode => $library2->branchcode} } );
729
730     # Create 2 patrons
731     my $patron1   = $builder->build_object( { class => 'Koha::Patrons', value => {branchcode => $library1->branchcode} } );
732     my $patron3   = $builder->build_object( { class => 'Koha::Patrons', value => {branchcode => $library3->branchcode} } );
733
734     # Create 3 biblios with items
735     my $biblio_1 = $builder->build_sample_biblio({ itemtype => $itemtype1->itemtype });
736     my ( undef, undef, $itemnumber_1 ) = AddItem(
737         {   homebranch    => $library1->branchcode,
738             holdingbranch => $library1->branchcode
739         },
740         $biblio_1->biblionumber
741     );
742     my $biblio_2 = $builder->build_sample_biblio({ itemtype => $itemtype2->itemtype });
743     my ( undef, undef, $itemnumber_2 ) = AddItem(
744         {   homebranch    => $library2->branchcode,
745             holdingbranch => $library2->branchcode
746         },
747         $biblio_2->biblionumber
748     );
749     my $biblio_3 = $builder->build_sample_biblio({ itemtype => $itemtype1->itemtype });
750     my ( undef, undef, $itemnumber_3 ) = AddItem(
751         {   homebranch    => $library1->branchcode,
752             holdingbranch => $library1->branchcode
753         },
754         $biblio_3->biblionumber
755     );
756
757     # Test 1: Patron 3 can place hold
758     is_deeply(
759         CanItemBeReserved( $patron3->borrowernumber, $itemnumber_2 ),
760         { status => 'OK' },
761         'Patron can place hold if no circ_rules where defined'
762     );
763
764     # Insert default circ rule of holds allowed only from local hold group for all libraries
765     Koha::CirculationRules->set_rules(
766         {
767             branchcode => undef,
768             itemtype   => undef,
769             categorycode => undef,
770             rules => {
771                 holdallowed => 3,
772                 hold_fulfillment_policy => 'any',
773                 returnbranch => 'any'
774             }
775         }
776     );
777
778     # Test 2: Patron 1 can place hold
779     is_deeply(
780         CanItemBeReserved( $patron1->borrowernumber, $itemnumber_2 ),
781         { status => 'OK' },
782         'Patron can place hold because patron\'s home library is part of hold group'
783     );
784
785     # Test 3: Patron 3 cannot place hold
786     is_deeply(
787         CanItemBeReserved( $patron3->borrowernumber, $itemnumber_2 ),
788         { status => 'branchNotInHoldGroup' },
789         'Patron cannot place hold because patron\'s home library is not part of hold group'
790     );
791
792     # Cleanup default_cirt_rules
793     $dbh->do('DELETE FROM circulation_rules');
794
795     # Insert default circ rule to "any" for library 2
796     Koha::CirculationRules->set_rules(
797         {
798             branchcode => $library2->branchcode,
799             itemtype   => undef,
800             categorycode => undef,
801             rules => {
802                 holdallowed => 2,
803                 hold_fulfillment_policy => 'any',
804                 returnbranch => 'any'
805             }
806         }
807     );
808
809     # Test 4: Patron 3 can place hold
810     is_deeply(
811         CanItemBeReserved( $patron3->borrowernumber, $itemnumber_2 ),
812         { status => 'OK' },
813         'Patron can place hold if holdallowed is set to "any" for library 2'
814     );
815
816     # Update default circ rule to "hold group" for library 2
817     Koha::CirculationRules->set_rules(
818         {
819             branchcode => $library2->branchcode,
820             itemtype   => undef,
821             categorycode => undef,
822             rules => {
823                 holdallowed => 3,
824                 hold_fulfillment_policy => 'any',
825                 returnbranch => 'any'
826             }
827         }
828     );
829
830     # Test 5: Patron 3 cannot place hold
831     is_deeply(
832         CanItemBeReserved( $patron3->borrowernumber, $itemnumber_2 ),
833         { status => 'branchNotInHoldGroup' },
834         'Patron cannot place hold if holdallowed is set to "hold group" for library 2'
835     );
836
837     # Cleanup default_branch_cirt_rules
838     $dbh->do('DELETE FROM circulation_rules');
839
840     # Insert default item rule to "any" for itemtype 2
841     Koha::CirculationRules->set_rules(
842         {
843             branchcode => undef,
844             itemtype   => $itemtype2->itemtype,
845             categorycode => undef,
846             rules => {
847                 holdallowed => 2,
848                 hold_fulfillment_policy => 'any',
849                 returnbranch => 'any'
850             }
851         }
852     );
853
854     # Test 6: Patron 3 can place hold
855     is_deeply(
856         CanItemBeReserved( $patron3->borrowernumber, $itemnumber_2 ),
857         { status => 'OK' },
858         'Patron can place hold if holdallowed is set to "any" for itemtype 2'
859     );
860
861     # Update default item rule to "hold group" for itemtype 2
862     Koha::CirculationRules->set_rules(
863         {
864             branchcode => undef,
865             itemtype   => $itemtype2->itemtype,
866             categorycode => undef,
867             rules => {
868                 holdallowed => 3,
869                 hold_fulfillment_policy => 'any',
870                 returnbranch => 'any'
871             }
872         }
873     );
874
875     # Test 7: Patron 3 cannot place hold
876     is_deeply(
877         CanItemBeReserved( $patron3->borrowernumber, $itemnumber_2 ),
878         { status => 'branchNotInHoldGroup' },
879         'Patron cannot place hold if holdallowed is set to "hold group" for itemtype 2'
880     );
881
882     # Cleanup default_branch_item_rules
883     $dbh->do('DELETE FROM circulation_rules');
884
885     # Insert branch item rule to "any" for itemtype 2 and library 2
886     Koha::CirculationRules->set_rules(
887         {
888             branchcode => $library2->branchcode,
889             itemtype   => $itemtype2->itemtype,
890             categorycode => undef,
891             rules => {
892                 holdallowed => 2,
893                 hold_fulfillment_policy => 'any',
894                 returnbranch => 'any'
895             }
896         }
897     );
898
899     # Test 8: Patron 3 can place hold
900     is_deeply(
901         CanItemBeReserved( $patron3->borrowernumber, $itemnumber_2 ),
902         { status => 'OK' },
903         'Patron can place hold if holdallowed is set to "any" for itemtype 2 and library 2'
904     );
905
906     # Update branch item rule to "hold group" for itemtype 2 and library 2
907     Koha::CirculationRules->set_rules(
908         {
909             branchcode => $library2->branchcode,
910             itemtype   => $itemtype2->itemtype,
911             categorycode => undef,
912             rules => {
913                 holdallowed => 3,
914                 hold_fulfillment_policy => 'any',
915                 returnbranch => 'any'
916             }
917         }
918     );
919
920     # Test 9: Patron 3 cannot place hold
921     is_deeply(
922         CanItemBeReserved( $patron3->borrowernumber, $itemnumber_2 ),
923         { status => 'branchNotInHoldGroup' },
924         'Patron cannot place hold if holdallowed is set to "hold group" for itemtype 2 and library 2'
925     );
926
927     $schema->storage->txn_rollback;
928
929 };
930
931 subtest 'CanItemBeReserved / pickup_not_in_hold_group' => sub {
932     plan tests => 9;
933
934     $schema->storage->txn_begin;
935
936     # Cleanup database
937     Koha::Holds->search->delete;
938     $dbh->do('DELETE FROM issues');
939     $dbh->do('DELETE FROM issuingrules');
940     $dbh->do(
941         q{INSERT INTO issuingrules (categorycode, branchcode, itemtype, reservesallowed)
942         VALUES (?, ?, ?, ?)},
943         {},
944         '*', '*', '*', 25
945     );
946     $dbh->do('DELETE FROM circulation_rules');
947
948     Koha::Items->search->delete;
949     Koha::Biblios->search->delete;
950
951     # Create item types
952     my $itemtype1 = $builder->build_object( { class => 'Koha::ItemTypes' } );
953     my $itemtype2 = $builder->build_object( { class => 'Koha::ItemTypes' } );
954
955     # Create libraries
956     my $library1  = $builder->build_object( { class => 'Koha::Libraries' } );
957     my $library2  = $builder->build_object( { class => 'Koha::Libraries' } );
958     my $library3  = $builder->build_object( { class => 'Koha::Libraries' } );
959
960     # Create library groups hierarchy
961     my $rootgroup  = $builder->build_object( { class => 'Koha::Library::Groups', value => {ft_local_hold_group => 1} } );
962     my $group1  = $builder->build_object( { class => 'Koha::Library::Groups', value => {parent_id => $rootgroup->id, branchcode => $library1->branchcode}} );
963     my $group2  = $builder->build_object( { class => 'Koha::Library::Groups', value => {parent_id => $rootgroup->id, branchcode => $library2->branchcode} } );
964
965     # Create 2 patrons
966     my $patron1   = $builder->build_object( { class => 'Koha::Patrons', value => {branchcode => $library1->branchcode} } );
967     my $patron3   = $builder->build_object( { class => 'Koha::Patrons', value => {branchcode => $library3->branchcode} } );
968
969     # Create 3 biblios with items
970     my $biblio_1 = $builder->build_sample_biblio({ itemtype => $itemtype1->itemtype });
971     my ( undef, undef, $itemnumber_1 ) = AddItem(
972         {   homebranch    => $library1->branchcode,
973             holdingbranch => $library1->branchcode
974         },
975         $biblio_1->biblionumber
976     );
977     my $biblio_2 = $builder->build_sample_biblio({ itemtype => $itemtype2->itemtype });
978     my ( undef, undef, $itemnumber_2 ) = AddItem(
979         {   homebranch    => $library2->branchcode,
980             holdingbranch => $library2->branchcode
981         },
982         $biblio_2->biblionumber
983     );
984     my $biblio_3 = $builder->build_sample_biblio({ itemtype => $itemtype1->itemtype });
985     my ( undef, undef, $itemnumber_3 ) = AddItem(
986         {   homebranch    => $library1->branchcode,
987             holdingbranch => $library1->branchcode
988         },
989         $biblio_3->biblionumber
990     );
991
992     # Test 1: Patron 3 can place hold
993     is_deeply(
994         CanItemBeReserved( $patron3->borrowernumber, $itemnumber_2, $library3->branchcode ),
995         { status => 'OK' },
996         'Patron can place hold if no circ_rules where defined'
997     );
998
999     # Insert default circ rule of holds allowed only from local hold group for all libraries
1000     Koha::CirculationRules->set_rules(
1001         {
1002             branchcode => undef,
1003             itemtype   => undef,
1004             categorycode => undef,
1005             rules => {
1006                 holdallowed => 2,
1007                 hold_fulfillment_policy => 'holdgroup',
1008                 returnbranch => 'any'
1009             }
1010         }
1011     );
1012
1013     # Test 2: Patron 1 can place hold
1014     is_deeply(
1015         CanItemBeReserved( $patron3->borrowernumber, $itemnumber_2, $library1->branchcode ),
1016         { status => 'OK' },
1017         'Patron can place hold because pickup location is part of hold group'
1018     );
1019
1020     # Test 3: Patron 3 cannot place hold
1021     is_deeply(
1022         CanItemBeReserved( $patron3->borrowernumber, $itemnumber_2, $library3->branchcode ),
1023         { status => 'pickupNotInHoldGroup' },
1024         'Patron cannot place hold because pickup location is not part of hold group'
1025     );
1026
1027     # Cleanup default_cirt_rules
1028     $dbh->do('DELETE FROM circulation_rules');
1029
1030     # Insert default circ rule to "any" for library 2
1031     Koha::CirculationRules->set_rules(
1032         {
1033             branchcode => $library2->branchcode,
1034             itemtype   => undef,
1035             categorycode => undef,
1036             rules => {
1037                 holdallowed => 2,
1038                 hold_fulfillment_policy => 'any',
1039                 returnbranch => 'any'
1040             }
1041         }
1042     );
1043
1044     # Test 4: Patron 3 can place hold
1045     is_deeply(
1046         CanItemBeReserved( $patron3->borrowernumber, $itemnumber_2, $library3->branchcode ),
1047         { status => 'OK' },
1048         'Patron can place hold if default_branch_circ_rules is set to "any" for library 2'
1049     );
1050
1051     # Update default circ rule to "hold group" for library 2
1052     Koha::CirculationRules->set_rules(
1053         {
1054             branchcode => $library2->branchcode,
1055             itemtype   => undef,
1056             categorycode => undef,
1057             rules => {
1058                 holdallowed => 2,
1059                 hold_fulfillment_policy => 'holdgroup',
1060                 returnbranch => 'any'
1061             }
1062         }
1063     );
1064
1065     # Test 5: Patron 3 cannot place hold
1066     is_deeply(
1067         CanItemBeReserved( $patron3->borrowernumber, $itemnumber_2, $library3->branchcode ),
1068         { status => 'pickupNotInHoldGroup' },
1069         'Patron cannot place hold if hold_fulfillment_policy is set to "hold group" for library 2'
1070     );
1071
1072     # Cleanup default_branch_cirt_rules
1073     $dbh->do('DELETE FROM circulation_rules');
1074
1075     # Insert default item rule to "any" for itemtype 2
1076     Koha::CirculationRules->set_rules(
1077         {
1078             branchcode => undef,
1079             itemtype   => $itemtype2->itemtype,
1080             categorycode => undef,
1081             rules => {
1082                 holdallowed => 2,
1083                 hold_fulfillment_policy => 'any',
1084                 returnbranch => 'any'
1085             }
1086         }
1087     );
1088
1089     # Test 6: Patron 3 can place hold
1090     is_deeply(
1091         CanItemBeReserved( $patron3->borrowernumber, $itemnumber_2, $library3->branchcode ),
1092         { status => 'OK' },
1093         'Patron can place hold if hold_fulfillment_policy is set to "any" for itemtype 2'
1094     );
1095
1096     # Update default item rule to "hold group" for itemtype 2
1097     Koha::CirculationRules->set_rules(
1098         {
1099             branchcode => undef,
1100             itemtype   => $itemtype2->itemtype,
1101             categorycode => undef,
1102             rules => {
1103                 holdallowed => 2,
1104                 hold_fulfillment_policy => 'holdgroup',
1105                 returnbranch => 'any'
1106             }
1107         }
1108     );
1109
1110     # Test 7: Patron 3 cannot place hold
1111     is_deeply(
1112         CanItemBeReserved( $patron3->borrowernumber, $itemnumber_2, $library3->branchcode ),
1113         { status => 'pickupNotInHoldGroup' },
1114         'Patron cannot place hold if hold_fulfillment_policy is set to "hold group" for itemtype 2'
1115     );
1116
1117     # Cleanup default_branch_item_rules
1118     $dbh->do('DELETE FROM circulation_rules');
1119
1120     # Insert branch item rule to "any" for itemtype 2 and library 2
1121     Koha::CirculationRules->set_rules(
1122         {
1123             branchcode => $library2->branchcode,
1124             itemtype   => $itemtype2->itemtype,
1125             categorycode => undef,
1126             rules => {
1127                 holdallowed => 2,
1128                 hold_fulfillment_policy => 'any',
1129                 returnbranch => 'any'
1130             }
1131         }
1132     );
1133
1134     # Test 8: 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 hold_fulfillment_policy is set to "any" for itemtype 2 and library 2'
1139     );
1140
1141     # Update branch item rule to "hold group" for itemtype 2 and library 2
1142     Koha::CirculationRules->set_rules(
1143         {
1144             branchcode => $library2->branchcode,
1145             itemtype   => $itemtype2->itemtype,
1146             categorycode => undef,
1147             rules => {
1148                 holdallowed => 2,
1149                 hold_fulfillment_policy => 'holdgroup',
1150                 returnbranch => 'any'
1151             }
1152         }
1153     );
1154
1155     # Test 9: Patron 3 cannot place hold
1156     is_deeply(
1157         CanItemBeReserved( $patron3->borrowernumber, $itemnumber_2, $library3->branchcode ),
1158         { status => 'pickupNotInHoldGroup' },
1159         'Patron cannot place hold if hold_fulfillment_policy is set to "hold group" for itemtype 2 and library 2'
1160     );
1161
1162     $schema->storage->txn_rollback;
1163 };