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