Bug 26481: Add Koha::Item::Transfer->in_transit method
[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 => 68;
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     my $itemtype = $builder->build_object( { class => 'Koha::ItemTypes' } );
637     my $library  = $builder->build_object( { class => 'Koha::Libraries' } );
638     my $patron   = $builder->build_object( { class => 'Koha::Patrons' } );
639
640     # Create 3 biblios with items
641     my $biblio_1 = $builder->build_sample_biblio({ itemtype => $itemtype->itemtype });
642     my $itemnumber_1 = $builder->build_sample_item({ library => $library->branchcode, biblionumber => $biblio_1->biblionumber})->itemnumber;
643     my $biblio_2 = $builder->build_sample_biblio({ itemtype => $itemtype->itemtype });
644     my $itemnumber_2 = $builder->build_sample_item({ library => $library->branchcode, biblionumber => $biblio_2->biblionumber})->itemnumber;
645     my $biblio_3 = $builder->build_sample_biblio({ itemtype => $itemtype->itemtype });
646     my $itemnumber_3 = $builder->build_sample_item({ library => $library->branchcode, biblionumber => $biblio_3->biblionumber})->itemnumber;
647
648     Koha::CirculationRules->set_rules(
649         {
650             categorycode => '*',
651             branchcode   => '*',
652             itemtype     => $itemtype->itemtype,
653             rules        => {
654                 reservesallowed  => 1,
655                 holds_per_record => 99,
656                 holds_per_day    => 2
657             }
658         }
659     );
660
661     is_deeply(
662         CanItemBeReserved( $patron->borrowernumber, $itemnumber_1 ),
663         { status => 'OK' },
664         'Patron can reserve item with hold limit of 1, no holds placed'
665     );
666
667     AddReserve(
668         {
669             branchcode     => $library->branchcode,
670             borrowernumber => $patron->borrowernumber,
671             biblionumber   => $biblio_1->biblionumber,
672             priority       => 1,
673         }
674     );
675
676     is_deeply(
677         CanItemBeReserved( $patron->borrowernumber, $itemnumber_1 ),
678         { status => 'tooManyReserves', limit => 1 },
679         'Patron cannot reserve item with hold limit of 1, 1 bib level hold placed'
680     );
681
682     # Raise reservesallowed to avoid tooManyReserves from it
683     Koha::CirculationRules->set_rule(
684         {
685
686             categorycode => '*',
687             branchcode   => '*',
688             itemtype     => $itemtype->itemtype,
689             rule_name  => 'reservesallowed',
690             rule_value => 3,
691         }
692     );
693
694     is_deeply(
695         CanItemBeReserved( $patron->borrowernumber, $itemnumber_2 ),
696         { status => 'OK' },
697         'Patron can reserve item with 2 reserves daily cap'
698     );
699
700     # Add a second reserve
701     my $res_id = AddReserve(
702         {
703             branchcode     => $library->branchcode,
704             borrowernumber => $patron->borrowernumber,
705             biblionumber   => $biblio_2->biblionumber,
706             priority       => 1,
707         }
708     );
709     is_deeply(
710         CanItemBeReserved( $patron->borrowernumber, $itemnumber_2 ),
711         { status => 'tooManyReservesToday', limit => 2 },
712         'Patron cannot a third item with 2 reserves daily cap'
713     );
714
715     # Update last hold so reservedate is in the past, so 2 holds, but different day
716     $hold = Koha::Holds->find($res_id);
717     my $yesterday = dt_from_string() - DateTime::Duration->new( days => 1 );
718     $hold->reservedate($yesterday)->store;
719
720     is_deeply(
721         CanItemBeReserved( $patron->borrowernumber, $itemnumber_2 ),
722         { status => 'OK' },
723         'Patron can reserve item with 2 bib level hold placed on different days, 2 reserves daily cap'
724     );
725
726     # Set holds_per_day to 0
727     Koha::CirculationRules->set_rule(
728         {
729
730             categorycode => '*',
731             branchcode   => '*',
732             itemtype     => $itemtype->itemtype,
733             rule_name  => 'holds_per_day',
734             rule_value => 0,
735         }
736     );
737
738
739     # Delete existing holds
740     Koha::Holds->search->delete;
741     is_deeply(
742         CanItemBeReserved( $patron->borrowernumber, $itemnumber_2 ),
743         { status => 'tooManyReservesToday', limit => 0 },
744         'Patron cannot reserve if holds_per_day is 0 (i.e. 0 is 0)'
745     );
746
747     Koha::CirculationRules->set_rule(
748         {
749
750             categorycode => '*',
751             branchcode   => '*',
752             itemtype     => $itemtype->itemtype,
753             rule_name  => 'holds_per_day',
754             rule_value => undef,
755         }
756     );
757
758     Koha::Holds->search->delete;
759     is_deeply(
760         CanItemBeReserved( $patron->borrowernumber, $itemnumber_2 ),
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_1->biblionumber,
769             priority       => 1,
770         }
771     );
772     AddReserve(
773         {
774             branchcode     => $library->branchcode,
775             borrowernumber => $patron->borrowernumber,
776             biblionumber   => $biblio_2->biblionumber,
777             priority       => 1,
778         }
779     );
780
781     is_deeply(
782         CanItemBeReserved( $patron->borrowernumber, $itemnumber_3 ),
783         { status => 'OK' },
784         'Patron can reserve if holds_per_day is undef (i.e. undef is unlimited daily cap)'
785     );
786     AddReserve(
787         {
788             branchcode     => $library->branchcode,
789             borrowernumber => $patron->borrowernumber,
790             biblionumber   => $biblio_3->biblionumber,
791             priority       => 1,
792         }
793     );
794     is_deeply(
795         CanItemBeReserved( $patron->borrowernumber, $itemnumber_3 ),
796         { status => 'tooManyReserves', limit => 3 },
797         'Unlimited daily holds, but reached reservesallowed'
798     );
799     #results should be the same for both ReservesControlBranch settings
800     t::lib::Mocks::mock_preference('ReservesControlBranch', 'ItemHomeLibrary');
801     is_deeply(
802         CanItemBeReserved( $patron->borrowernumber, $itemnumber_3 ),
803         { status => 'tooManyReserves', limit => 3 },
804         'Unlimited daily holds, but reached reservesallowed'
805     );
806
807     $schema->storage->txn_rollback;
808 };
809
810 subtest 'CanItemBeReserved / branch_not_in_hold_group' => sub {
811     plan tests => 9;
812
813     $schema->storage->txn_begin;
814
815     Koha::CirculationRules->set_rule(
816         {
817             branchcode   => undef,
818             categorycode => undef,
819             itemtype     => undef,
820             rule_name    => 'reservesallowed',
821             rule_value   => 25,
822         }
823     );
824
825     # Create item types
826     my $itemtype1 = $builder->build_object( { class => 'Koha::ItemTypes' } );
827     my $itemtype2 = $builder->build_object( { class => 'Koha::ItemTypes' } );
828
829     # Create libraries
830     my $library1  = $builder->build_object( { class => 'Koha::Libraries' } );
831     my $library2  = $builder->build_object( { class => 'Koha::Libraries' } );
832     my $library3  = $builder->build_object( { class => 'Koha::Libraries' } );
833
834     # Create library groups hierarchy
835     my $rootgroup  = $builder->build_object( { class => 'Koha::Library::Groups', value => {ft_local_hold_group => 1} } );
836     my $group1  = $builder->build_object( { class => 'Koha::Library::Groups', value => {parent_id => $rootgroup->id, branchcode => $library1->branchcode}} );
837     my $group2  = $builder->build_object( { class => 'Koha::Library::Groups', value => {parent_id => $rootgroup->id, branchcode => $library2->branchcode} } );
838
839     # Create 2 patrons
840     my $patron1   = $builder->build_object( { class => 'Koha::Patrons', value => {branchcode => $library1->branchcode} } );
841     my $patron3   = $builder->build_object( { class => 'Koha::Patrons', value => {branchcode => $library3->branchcode} } );
842
843     # Create 3 biblios with items
844     my $biblio_1 = $builder->build_sample_biblio({ itemtype => $itemtype1->itemtype });
845     my $item_1   = $builder->build_sample_item(
846         {
847             biblionumber => $biblio_1->biblionumber,
848             library      => $library1->branchcode
849         }
850     );
851     my $biblio_2 = $builder->build_sample_biblio({ itemtype => $itemtype2->itemtype });
852     my $item_2   = $builder->build_sample_item(
853         {
854             biblionumber => $biblio_2->biblionumber,
855             library      => $library2->branchcode
856         }
857     );
858     my $itemnumber_2 = $item_2->itemnumber;
859     my $biblio_3 = $builder->build_sample_biblio({ itemtype => $itemtype1->itemtype });
860     my $item_3   = $builder->build_sample_item(
861         {
862             biblionumber => $biblio_3->biblionumber,
863             library      => $library1->branchcode
864         }
865     );
866
867     # Test 1: Patron 3 can place hold
868     is_deeply(
869         CanItemBeReserved( $patron3->borrowernumber, $itemnumber_2 ),
870         { status => 'OK' },
871         'Patron can place hold if no circ_rules where defined'
872     );
873
874     # Insert default circ rule of holds allowed only from local hold group for all libraries
875     Koha::CirculationRules->set_rules(
876         {
877             branchcode => undef,
878             itemtype   => undef,
879             rules => {
880                 holdallowed => 3,
881                 hold_fulfillment_policy => 'any',
882                 returnbranch => 'any'
883             }
884         }
885     );
886
887     # Test 2: Patron 1 can place hold
888     is_deeply(
889         CanItemBeReserved( $patron1->borrowernumber, $itemnumber_2 ),
890         { status => 'OK' },
891         'Patron can place hold because patron\'s home library is part of hold group'
892     );
893
894     # Test 3: Patron 3 cannot place hold
895     is_deeply(
896         CanItemBeReserved( $patron3->borrowernumber, $itemnumber_2 ),
897         { status => 'branchNotInHoldGroup' },
898         'Patron cannot place hold because patron\'s home library is not part of hold group'
899     );
900
901     # Insert default circ rule to "any" for library 2
902     Koha::CirculationRules->set_rules(
903         {
904             branchcode => $library2->branchcode,
905             itemtype   => undef,
906             rules => {
907                 holdallowed => 2,
908                 hold_fulfillment_policy => 'any',
909                 returnbranch => 'any'
910             }
911         }
912     );
913
914     # Test 4: Patron 3 can place hold
915     is_deeply(
916         CanItemBeReserved( $patron3->borrowernumber, $itemnumber_2 ),
917         { status => 'OK' },
918         'Patron can place hold if holdallowed is set to "any" for library 2'
919     );
920
921     # Update default circ rule to "hold group" for library 2
922     Koha::CirculationRules->set_rules(
923         {
924             branchcode => $library2->branchcode,
925             itemtype   => undef,
926             rules => {
927                 holdallowed => 3,
928                 hold_fulfillment_policy => 'any',
929                 returnbranch => 'any'
930             }
931         }
932     );
933
934     # Test 5: Patron 3 cannot place hold
935     is_deeply(
936         CanItemBeReserved( $patron3->borrowernumber, $itemnumber_2 ),
937         { status => 'branchNotInHoldGroup' },
938         'Patron cannot place hold if holdallowed is set to "hold group" for library 2'
939     );
940
941     # Insert default item rule to "any" for itemtype 2
942     Koha::CirculationRules->set_rules(
943         {
944             branchcode => $library2->branchcode,
945             itemtype   => $itemtype2->itemtype,
946             rules => {
947                 holdallowed => 2,
948                 hold_fulfillment_policy => 'any',
949                 returnbranch => 'any'
950             }
951         }
952     );
953
954     # Test 6: Patron 3 can place hold
955     is_deeply(
956         CanItemBeReserved( $patron3->borrowernumber, $itemnumber_2 ),
957         { status => 'OK' },
958         'Patron can place hold if holdallowed is set to "any" for itemtype 2'
959     );
960
961     # Update default item rule to "hold group" for itemtype 2
962     Koha::CirculationRules->set_rules(
963         {
964             branchcode => $library2->branchcode,
965             itemtype   => $itemtype2->itemtype,
966             rules => {
967                 holdallowed => 3,
968                 hold_fulfillment_policy => 'any',
969                 returnbranch => 'any'
970             }
971         }
972     );
973
974     # Test 7: Patron 3 cannot place hold
975     is_deeply(
976         CanItemBeReserved( $patron3->borrowernumber, $itemnumber_2 ),
977         { status => 'branchNotInHoldGroup' },
978         'Patron cannot place hold if holdallowed is set to "hold group" for itemtype 2'
979     );
980
981     # Insert branch item rule to "any" for itemtype 2 and library 2
982     Koha::CirculationRules->set_rules(
983         {
984             branchcode => $library2->branchcode,
985             itemtype   => $itemtype2->itemtype,
986             rules => {
987                 holdallowed => 2,
988                 hold_fulfillment_policy => 'any',
989                 returnbranch => 'any'
990             }
991         }
992     );
993
994     # Test 8: Patron 3 can place hold
995     is_deeply(
996         CanItemBeReserved( $patron3->borrowernumber, $itemnumber_2 ),
997         { status => 'OK' },
998         'Patron can place hold if holdallowed is set to "any" for itemtype 2 and library 2'
999     );
1000
1001     # Update branch item rule to "hold group" for itemtype 2 and library 2
1002     Koha::CirculationRules->set_rules(
1003         {
1004             branchcode => $library2->branchcode,
1005             itemtype   => $itemtype2->itemtype,
1006             rules => {
1007                 holdallowed => 3,
1008                 hold_fulfillment_policy => 'any',
1009                 returnbranch => 'any'
1010             }
1011         }
1012     );
1013
1014     # Test 9: Patron 3 cannot place hold
1015     is_deeply(
1016         CanItemBeReserved( $patron3->borrowernumber, $itemnumber_2 ),
1017         { status => 'branchNotInHoldGroup' },
1018         'Patron cannot place hold if holdallowed is set to "hold group" for itemtype 2 and library 2'
1019     );
1020
1021     $schema->storage->txn_rollback;
1022
1023 };
1024
1025 subtest 'CanItemBeReserved / pickup_not_in_hold_group' => sub {
1026     plan tests => 9;
1027
1028     $schema->storage->txn_begin;
1029     Koha::CirculationRules->set_rule(
1030         {
1031             branchcode   => undef,
1032             categorycode => undef,
1033             itemtype     => undef,
1034             rule_name    => 'reservesallowed',
1035             rule_value   => 25,
1036         }
1037     );
1038
1039     # Create item types
1040     my $itemtype1 = $builder->build_object( { class => 'Koha::ItemTypes' } );
1041     my $itemtype2 = $builder->build_object( { class => 'Koha::ItemTypes' } );
1042
1043     # Create libraries
1044     my $library1  = $builder->build_object( { class => 'Koha::Libraries', value => {pickup_location => 1} } );
1045     my $library2  = $builder->build_object( { class => 'Koha::Libraries', value => {pickup_location => 1} } );
1046     my $library3  = $builder->build_object( { class => 'Koha::Libraries', value => {pickup_location => 1} } );
1047
1048     # Create library groups hierarchy
1049     my $rootgroup  = $builder->build_object( { class => 'Koha::Library::Groups', value => {ft_local_hold_group => 1} } );
1050     my $group1  = $builder->build_object( { class => 'Koha::Library::Groups', value => {parent_id => $rootgroup->id, branchcode => $library1->branchcode}} );
1051     my $group2  = $builder->build_object( { class => 'Koha::Library::Groups', value => {parent_id => $rootgroup->id, branchcode => $library2->branchcode} } );
1052
1053     # Create 2 patrons
1054     my $patron1   = $builder->build_object( { class => 'Koha::Patrons', value => {branchcode => $library1->branchcode} } );
1055     my $patron3   = $builder->build_object( { class => 'Koha::Patrons', value => {branchcode => $library3->branchcode} } );
1056
1057     # Create 3 biblios with items
1058     my $biblio_1 = $builder->build_sample_biblio({ itemtype => $itemtype1->itemtype });
1059     my $item_1   = $builder->build_sample_item(
1060         {
1061             biblionumber => $biblio_1->biblionumber,
1062             library      => $library1->branchcode
1063         }
1064     );
1065     my $biblio_2 = $builder->build_sample_biblio({ itemtype => $itemtype2->itemtype });
1066     my $item_2   = $builder->build_sample_item(
1067         {
1068             biblionumber => $biblio_2->biblionumber,
1069             library      => $library2->branchcode
1070         }
1071     );
1072     my $itemnumber_2 = $item_2->itemnumber;
1073     my $biblio_3 = $builder->build_sample_biblio({ itemtype => $itemtype1->itemtype });
1074     my $item_3   = $builder->build_sample_item(
1075         {
1076             biblionumber => $biblio_3->biblionumber,
1077             library      => $library1->branchcode
1078         }
1079     );
1080
1081     # Test 1: Patron 3 can place hold
1082     is_deeply(
1083         CanItemBeReserved( $patron3->borrowernumber, $itemnumber_2, $library3->branchcode ),
1084         { status => 'OK' },
1085         'Patron can place hold if no circ_rules where defined'
1086     );
1087
1088     # Insert default circ rule of holds allowed only from local hold group for all libraries
1089     Koha::CirculationRules->set_rules(
1090         {
1091             branchcode => undef,
1092             itemtype   => undef,
1093             rules => {
1094                 holdallowed => 2,
1095                 hold_fulfillment_policy => 'holdgroup',
1096                 returnbranch => 'any'
1097             }
1098         }
1099     );
1100
1101     # Test 2: Patron 1 can place hold
1102     is_deeply(
1103         CanItemBeReserved( $patron3->borrowernumber, $itemnumber_2, $library1->branchcode ),
1104         { status => 'OK' },
1105         'Patron can place hold because pickup location is part of hold group'
1106     );
1107
1108     # Test 3: Patron 3 cannot place hold
1109     is_deeply(
1110         CanItemBeReserved( $patron3->borrowernumber, $itemnumber_2, $library3->branchcode ),
1111         { status => 'pickupNotInHoldGroup' },
1112         'Patron cannot place hold because pickup location is not part of hold group'
1113     );
1114
1115     # Insert default circ rule to "any" for library 2
1116     Koha::CirculationRules->set_rules(
1117         {
1118             branchcode => $library2->branchcode,
1119             itemtype   => undef,
1120             rules => {
1121                 holdallowed => 2,
1122                 hold_fulfillment_policy => 'any',
1123                 returnbranch => 'any'
1124             }
1125         }
1126     );
1127
1128     # Test 4: Patron 3 can place hold
1129     is_deeply(
1130         CanItemBeReserved( $patron3->borrowernumber, $itemnumber_2, $library3->branchcode ),
1131         { status => 'OK' },
1132         'Patron can place hold if default_branch_circ_rules is set to "any" for library 2'
1133     );
1134
1135     # Update default circ rule to "hold group" for library 2
1136     Koha::CirculationRules->set_rules(
1137         {
1138             branchcode => $library2->branchcode,
1139             itemtype   => undef,
1140             rules => {
1141                 holdallowed => 2,
1142                 hold_fulfillment_policy => 'holdgroup',
1143                 returnbranch => 'any'
1144             }
1145         }
1146     );
1147
1148     # Test 5: Patron 3 cannot place hold
1149     is_deeply(
1150         CanItemBeReserved( $patron3->borrowernumber, $itemnumber_2, $library3->branchcode ),
1151         { status => 'pickupNotInHoldGroup' },
1152         'Patron cannot place hold if hold_fulfillment_policy is set to "hold group" for library 2'
1153     );
1154
1155     # Insert default item rule to "any" for itemtype 2
1156     Koha::CirculationRules->set_rules(
1157         {
1158             branchcode => $library2->branchcode,
1159             itemtype   => $itemtype2->itemtype,
1160             rules => {
1161                 holdallowed => 2,
1162                 hold_fulfillment_policy => 'any',
1163                 returnbranch => 'any'
1164             }
1165         }
1166     );
1167
1168     # Test 6: Patron 3 can place hold
1169     is_deeply(
1170         CanItemBeReserved( $patron3->borrowernumber, $itemnumber_2, $library3->branchcode ),
1171         { status => 'OK' },
1172         'Patron can place hold if hold_fulfillment_policy is set to "any" for itemtype 2'
1173     );
1174
1175     # Update default item rule to "hold group" for itemtype 2
1176     Koha::CirculationRules->set_rules(
1177         {
1178             branchcode => $library2->branchcode,
1179             itemtype   => $itemtype2->itemtype,
1180             rules => {
1181                 holdallowed => 2,
1182                 hold_fulfillment_policy => 'holdgroup',
1183                 returnbranch => 'any'
1184             }
1185         }
1186     );
1187
1188     # Test 7: Patron 3 cannot place hold
1189     is_deeply(
1190         CanItemBeReserved( $patron3->borrowernumber, $itemnumber_2, $library3->branchcode ),
1191         { status => 'pickupNotInHoldGroup' },
1192         'Patron cannot place hold if hold_fulfillment_policy is set to "hold group" for itemtype 2'
1193     );
1194
1195     # Insert branch item rule to "any" for itemtype 2 and library 2
1196     Koha::CirculationRules->set_rules(
1197         {
1198             branchcode => $library2->branchcode,
1199             itemtype   => $itemtype2->itemtype,
1200             rules => {
1201                 holdallowed => 2,
1202                 hold_fulfillment_policy => 'any',
1203                 returnbranch => 'any'
1204             }
1205         }
1206     );
1207
1208     # Test 8: Patron 3 can place hold
1209     is_deeply(
1210         CanItemBeReserved( $patron3->borrowernumber, $itemnumber_2, $library3->branchcode ),
1211         { status => 'OK' },
1212         'Patron can place hold if hold_fulfillment_policy is set to "any" for itemtype 2 and library 2'
1213     );
1214
1215     # Update branch item rule to "hold group" for itemtype 2 and library 2
1216     Koha::CirculationRules->set_rules(
1217         {
1218             branchcode => $library2->branchcode,
1219             itemtype   => $itemtype2->itemtype,
1220             rules => {
1221                 holdallowed => 2,
1222                 hold_fulfillment_policy => 'holdgroup',
1223                 returnbranch => 'any'
1224             }
1225         }
1226     );
1227
1228     # Test 9: Patron 3 cannot place hold
1229     is_deeply(
1230         CanItemBeReserved( $patron3->borrowernumber, $itemnumber_2, $library3->branchcode ),
1231         { status => 'pickupNotInHoldGroup' },
1232         'Patron cannot place hold if hold_fulfillment_policy is set to "hold group" for itemtype 2 and library 2'
1233     );
1234
1235     $schema->storage->txn_rollback;
1236 };
1237
1238 subtest 'non priority holds' => sub {
1239
1240     plan tests => 6;
1241
1242     $schema->storage->txn_begin;
1243
1244     Koha::CirculationRules->set_rules(
1245         {
1246             branchcode   => undef,
1247             categorycode => undef,
1248             itemtype     => undef,
1249             rules        => {
1250                 renewalsallowed => 5,
1251                 reservesallowed => 5,
1252             }
1253         }
1254     );
1255
1256     my $item = $builder->build_sample_item;
1257
1258     my $patron1 = $builder->build_object(
1259         {
1260             class => 'Koha::Patrons',
1261             value => { branchcode => $item->homebranch }
1262         }
1263     );
1264     my $patron2 = $builder->build_object(
1265         {
1266             class => 'Koha::Patrons',
1267             value => { branchcode => $item->homebranch }
1268         }
1269     );
1270
1271     Koha::Checkout->new(
1272         {
1273             borrowernumber => $patron1->borrowernumber,
1274             itemnumber     => $item->itemnumber,
1275             branchcode     => $item->homebranch
1276         }
1277     )->store;
1278
1279     my $hid = AddReserve(
1280         {
1281             branchcode     => $item->homebranch,
1282             borrowernumber => $patron2->borrowernumber,
1283             biblionumber   => $item->biblionumber,
1284             priority       => 1,
1285             itemnumber     => $item->itemnumber,
1286         }
1287     );
1288
1289     my ( $ok, $err ) =
1290       CanBookBeRenewed( $patron1->borrowernumber, $item->itemnumber );
1291
1292     ok( !$ok, 'Cannot renew' );
1293     is( $err, 'on_reserve', 'Item is on hold' );
1294
1295     my $hold = Koha::Holds->find($hid);
1296     $hold->non_priority(1)->store;
1297
1298     ( $ok, $err ) =
1299       CanBookBeRenewed( $patron1->borrowernumber, $item->itemnumber );
1300
1301     ok( $ok, 'Can renew' );
1302     is( $err, undef, 'Item is on non priority hold' );
1303
1304     my $patron3 = $builder->build_object(
1305         {
1306             class => 'Koha::Patrons',
1307             value => { branchcode => $item->homebranch }
1308         }
1309     );
1310
1311     # Add second hold with non_priority = 0
1312     AddReserve(
1313         {
1314             branchcode     => $item->homebranch,
1315             borrowernumber => $patron3->borrowernumber,
1316             biblionumber   => $item->biblionumber,
1317             priority       => 2,
1318             itemnumber     => $item->itemnumber,
1319         }
1320     );
1321
1322     ( $ok, $err ) =
1323       CanBookBeRenewed( $patron1->borrowernumber, $item->itemnumber );
1324
1325     ok( !$ok, 'Cannot renew' );
1326     is( $err, 'on_reserve', 'Item is on hold' );
1327
1328     $schema->storage->txn_rollback;
1329
1330 };
1331
1332 subtest 'CanItemBeReserved rule precedence tests' => sub {
1333
1334     plan tests => 3;
1335
1336     t::lib::Mocks::mock_preference('ReservesControlBranch', 'ItemHomeLibrary');
1337     $schema->storage->txn_begin;
1338     my $library  = $builder->build_object( { class => 'Koha::Libraries', value => {
1339         pickup_location => 1,
1340     }});
1341     my $item = $builder->build_sample_item({
1342         homebranch    => $library->branchcode,
1343         holdingbranch => $library->branchcode
1344     });
1345     my $item2 = $builder->build_sample_item({
1346         homebranch    => $library->branchcode,
1347         holdingbranch => $library->branchcode,
1348         itype         => $item->itype
1349     });
1350     my $patron   = $builder->build_object({ class => 'Koha::Patrons', value => {
1351         branchcode => $library->branchcode
1352     }});
1353     Koha::CirculationRules->set_rules(
1354         {
1355             branchcode   => undef,
1356             categorycode => $patron->categorycode,
1357             itemtype     => $item->itype,
1358             rules        => {
1359                 reservesallowed  => 1,
1360             }
1361         }
1362     );
1363     is_deeply(
1364         CanItemBeReserved( $patron->borrowernumber, $item->itemnumber, $library->branchcode ),
1365         { status => 'OK' },
1366         'Patron of specified category can place 1 hold on specified itemtype'
1367     );
1368     my $hold = $builder->build_object({ class => 'Koha::Holds', value => {
1369         biblionumber   => $item2->biblionumber,
1370         itemnumber     => $item2->itemnumber,
1371         found          => undef,
1372         priority       => 1,
1373         branchcode     => $library->branchcode,
1374         borrowernumber => $patron->borrowernumber,
1375     }});
1376     is_deeply(
1377         CanItemBeReserved( $patron->borrowernumber, $item->itemnumber, $library->branchcode ),
1378         { status => 'tooManyReserves', limit => 1 },
1379         'Patron of specified category can place 1 hold on specified itemtype, cannot place a second'
1380     );
1381     Koha::CirculationRules->set_rules(
1382         {
1383             branchcode   => $library->branchcode,
1384             categorycode => undef,
1385             itemtype     => undef,
1386             rules        => {
1387                 reservesallowed  => 2,
1388             }
1389         }
1390     );
1391     is_deeply(
1392         CanItemBeReserved( $patron->borrowernumber, $item->itemnumber, $library->branchcode ),
1393         { status => 'OK' },
1394         'Patron of specified category can place 1 hold on specified itemtype if library rule for all types and categories set to 2'
1395     );
1396
1397     $schema->storage->txn_rollback;
1398
1399 };