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