6 use t
::lib
::TestBuilder
;
10 use Test
::More tests
=> 67;
20 use Koha
::CirculationRules
;
22 use Koha
::DateUtils
qw( dt_from_string output_pref );
25 use Koha
::Item
::Transfer
::Limits
;
28 use Koha
::Library
::Groups
;
33 use lib
$FindBin::Bin
;
36 my $schema = Koha
::Database
->new->schema;
37 $schema->storage->txn_begin;
39 my $builder = t
::lib
::TestBuilder
->new();
40 my $dbh = C4
::Context
->dbh;
42 # Create two random branches
43 my $branch_1 = $builder->build({ source
=> 'Branch' })->{ branchcode
};
44 my $branch_2 = $builder->build({ source
=> 'Branch' })->{ branchcode
};
46 my $category = $builder->build({ source
=> 'Category' });
48 my $borrowers_count = 5;
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');
59 # Setup Test------------------------
60 my $biblio = $builder->build_sample_biblio({ itemtype
=> 'DUMMY' });
62 # Create item instance for testing.
63 my $itemnumber = $builder->build_sample_item({ library
=> $branch_1, biblionumber
=> $biblio->biblionumber })->itemnumber;
65 # Create some borrowers
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;
77 # Create five item level holds
78 foreach my $borrowernumber ( @borrowernumbers ) {
81 branchcode
=> $branch_1,
82 borrowernumber
=> $borrowernumber,
83 biblionumber
=> $biblio->biblionumber,
84 priority
=> C4
::Reserves
::CalculatePriority
( $biblio->biblionumber ),
85 itemnumber
=> $itemnumber,
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" );
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()");
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" );
126 my $patron = Koha
::Patrons
->find( $borrowernumbers[0] );
127 $holds = $patron->holds;
128 is
( $holds->next->borrowernumber, $borrowernumbers[0], "Test Koha::Patron->holds");
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;
138 reserve_id
=> $reserve_id,
140 branchcode
=> $branch_1,
141 itemnumber
=> $itemnumber,
142 suspend_until
=> output_pref
( { dt
=> dt_from_string
( "2013-01-01", "iso" ), dateonly
=> 1 } ),
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" );
150 ModReserve
({ # call without reserve_id
152 biblionumber
=> $biblio->biblionumber,
153 itemnumber
=> $itemnumber,
154 borrowernumber
=> $borrowernumber,
156 $hold = Koha
::Holds
->find( $reserve_id );
157 ok
( $hold->priority eq '3', "Test ModReserve, priority changed correctly" );
159 ToggleSuspend
( $reserve_id );
160 $hold = Koha
::Holds
->find( $reserve_id );
161 ok
( ! $hold->suspend, "Test ToggleSuspend(), no date" );
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" );
167 AutoUnsuspendReserves
();
168 $hold = Koha
::Holds
->find( $reserve_id );
169 ok
( ! $hold->suspend, "Test AutoUnsuspendReserves()" );
172 borrowernumber
=> $borrowernumber,
173 biblionumber
=> $biblio->biblionumber,
175 suspend_until
=> '2012-01-01',
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" );
182 borrowernumber
=> $borrowernumber,
183 biblionumber
=> $biblio->biblionumber,
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" );
190 # Add a new hold for the borrower whose hold we canceled earlier, this time at the bib level
193 branchcode
=> $branch_1,
194 borrowernumber
=> $borrowernumbers[0],
195 biblionumber
=> $biblio->biblionumber,
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()" );
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" );
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" );
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" );
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" );
224 # Regression test for bug 2394
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.
232 # Note that canreservefromotherbranches has no effect if
233 # IndependentBranches is OFF.
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(
239 categorycode
=> undef,
243 reservesallowed
=> 25,
244 holds_per_record
=> 99,
248 Koha
::CirculationRules
->set_rules(
250 categorycode
=> undef,
252 itemtype
=> 'CANNOT',
254 reservesallowed
=> 0,
255 holds_per_record
=> 99,
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);
264 # if IndependentBranches is OFF, a $branch_1 patron can reserve an $branch_2 item
265 t
::lib
::Mocks
::mock_preference
('IndependentBranches', 0);
268 CanItemBeReserved
($borrowernumbers[0], $foreign_itemnumber)->{status
}, 'OK',
269 '$branch_1 patron allowed to reserve $branch_2 item with IndependentBranches OFF (bug 2394)'
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);
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)'
280 # ... unless canreservefromotherbranches is ON
281 t
::lib
::Mocks
::mock_preference
('canreservefromotherbranches', 1);
283 CanItemBeReserved
($borrowernumbers[0], $foreign_itemnumber)->{status
} eq 'OK',
284 '... unless canreservefromotherbranches is ON (bug 2394)'
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
(
293 branchcode
=> $branch_1,
294 borrowernumber
=> $borrowernumbers[0],
295 biblionumber
=> $biblio->biblionumber,
300 $itemnumber = $builder->build_sample_item({ library
=> $branch_1, biblionumber
=> $biblio->biblionumber })->itemnumber;
301 my $reserveid2 = AddReserve
(
303 branchcode
=> $branch_1,
304 borrowernumber
=> $borrowernumbers[1],
305 biblionumber
=> $biblio->biblionumber,
310 $itemnumber = $builder->build_sample_item({ library
=> $branch_1, biblionumber
=> $biblio->biblionumber })->itemnumber;
311 my $reserveid3 = AddReserve
(
313 branchcode
=> $branch_1,
314 borrowernumber
=> $borrowernumbers[2],
315 biblionumber
=> $biblio->biblionumber,
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" );
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" );
333 $hold = Koha
::Hold
->new(
335 borrowernumber
=> $borrowernumbers[0],
336 itemnumber
=> $itemnumber,
337 biblionumber
=> $biblio->biblionumber,
340 is
( CanItemBeReserved
( $borrowernumbers[0], $itemnumber )->{status
},
342 "Patron cannot place a second item level hold for a given item" );
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" );
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(
356 borrowernumber
=> $borrowernumbers[0],
357 itemnumber
=> $itemnumber,
358 biblionumber
=> $biblio->biblionumber,
361 reservedate
=> dt_from_string
,
362 branchcode
=> $branch_1,
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" );
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});
379 branchcode
=> $branch_1,
380 borrowernumber
=> $borrowernumbers[0],
381 biblionumber
=> $biblio->biblionumber,
386 CanItemBeReserved
( $borrowernumbers[0], $item->itemnumber)->{status
}, 'tooManyReserves',
387 "cannot request item if policy that matches on item-level item type forbids it"
390 $item->itype('CAN')->store;
392 CanItemBeReserved
( $borrowernumbers[0], $item->itemnumber)->{status
} eq 'OK',
393 "can request item if policy that matches on item type allows it"
396 t
::lib
::Mocks
::mock_preference
('item-level_itypes', 0);
397 $item->itype(undef)->store;
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)"
404 # Test branch item rules
406 $dbh->do('DELETE FROM circulation_rules');
407 Koha
::CirculationRules
->set_rules(
409 categorycode
=> undef,
413 reservesallowed
=> 25,
414 holds_per_record
=> 99,
418 Koha
::CirculationRules
->set_rules(
420 branchcode
=> $branch_1,
421 itemtype
=> 'CANNOT',
424 returnbranch
=> 'homebranch',
428 Koha
::CirculationRules
->set_rules(
430 branchcode
=> $branch_1,
434 returnbranch
=> 'homebranch',
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'");
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
},
451 "CanItemBeReserved should use item home library rule when ReservesControlBranch set to 'ItemsHomeLibrary'");
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'");
458 t
::lib
::Mocks
::mock_preference
( 'item-level_itypes', 1 );
459 t
::lib
::Mocks
::mock_preference
( 'ReservesControlBranch', 'PatronLibrary' );
461 $dbh->do('DELETE FROM reserves');
462 $dbh->do('DELETE FROM issues');
463 $dbh->do('DELETE FROM items');
464 $dbh->do('DELETE FROM biblio');
466 $biblio = $builder->build_sample_biblio({ itemtype
=> 'ONLY1' });
467 $itemnumber = $builder->build_sample_item({ library
=> $branch_1, biblionumber
=> $biblio->biblionumber})->itemnumber;
469 Koha
::CirculationRules
->set_rules(
471 categorycode
=> undef,
475 reservesallowed
=> 1,
476 holds_per_record
=> 99,
480 is
( CanItemBeReserved
( $borrowernumbers[0], $itemnumber )->{status
},
481 'OK', 'Patron can reserve item with hold limit of 1, no holds placed' );
483 my $res_id = AddReserve
(
485 branchcode
=> $branch_1,
486 borrowernumber
=> $borrowernumbers[0],
487 biblionumber
=> $biblio->biblionumber,
492 is
( CanItemBeReserved
( $borrowernumbers[0], $itemnumber )->{status
},
493 'tooManyReserves', 'Patron cannot reserve item with hold limit of 1, 1 bib level hold placed' );
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' );
502 subtest
'Test max_holds per library/patron category' => sub {
505 $dbh->do('DELETE FROM reserves');
507 $biblio = $builder->build_sample_biblio;
508 $itemnumber = $builder->build_sample_item({ library
=> $branch_1, biblionumber
=> $biblio->biblionumber})->itemnumber;
509 Koha
::CirculationRules
->set_rules(
511 categorycode
=> undef,
513 itemtype
=> $biblio->itemtype,
515 reservesallowed
=> 99,
516 holds_per_record
=> 99,
524 branchcode
=> $branch_1,
525 borrowernumber
=> $borrowernumbers[0],
526 biblionumber
=> $biblio->biblionumber,
533 Koha
::Holds
->search( { borrowernumber
=> $borrowernumbers[0] } )->count();
534 is
( $count, 3, 'Patron now has 3 holds' );
536 my $ret = CanItemBeReserved
( $borrowernumbers[0], $itemnumber );
537 is
( $ret->{status
}, 'OK', 'Patron can place hold with no borrower circ rules' );
539 my $rule_all = Koha
::CirculationRules
->set_rule(
541 categorycode
=> $category->{categorycode
},
543 rule_name
=> 'max_holds',
548 my $rule_branch = Koha
::CirculationRules
->set_rule(
550 branchcode
=> $branch_1,
551 categorycode
=> $category->{categorycode
},
552 rule_name
=> 'max_holds',
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' );
560 $rule_branch->delete();
562 $ret = CanItemBeReserved
( $borrowernumbers[0], $itemnumber );
563 is
( $ret->{status
}, 'tooManyReserves', 'Patron cannot place hold with only a category rule of 3' );
566 $rule_branch->rule_value(3);
567 $rule_branch->store();
569 $ret = CanItemBeReserved
( $borrowernumbers[0], $itemnumber );
570 is
( $ret->{status
}, 'tooManyReserves', 'Patron cannot place hold with only a branch/category rule of 3' );
572 $rule_branch->rule_value(5);
573 $rule_branch->update();
574 $rule_branch->rule_value(5);
575 $rule_branch->store();
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' );
581 subtest
'Pickup location availability tests' => sub {
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
588 Koha
::CirculationRules
->set_rules(
591 categorycode
=> undef,
594 reservesallowed
=> 25,
595 holds_per_record
=> 99,
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
};
605 t
::lib
::Mocks
::mock_preference
('UseBranchTransferLimits', 1);
606 t
::lib
::Mocks
::mock_preference
('BranchTransferLimitsType', 'itemtype');
608 $library->pickup_location('1')->store;
609 is
(CanItemBeReserved
($patron, $item->itemnumber, $branch_to)->{status
},
610 'OK', 'Library is a pickup location');
612 my $limit = Koha
::Item
::Transfer
::Limit
->new({
613 fromBranch
=> $item->holdingbranch,
614 toBranch
=> $branch_to,
615 itemtype
=> $item->effective_itemtype,
617 is
(CanItemBeReserved
($patron, $item->itemnumber, $branch_to)->{status
},
618 'cannotBeTransferred', 'Item cannot be transferred');
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');
628 $schema->storage->txn_rollback;
630 subtest
'CanItemBeReserved / holds_per_day tests' => sub {
634 $schema->storage->txn_begin;
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' } );
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;
648 Koha
::CirculationRules
->set_rules(
652 itemtype
=> $itemtype->itemtype,
654 reservesallowed
=> 1,
655 holds_per_record
=> 99,
662 CanItemBeReserved
( $patron->borrowernumber, $itemnumber_1 ),
664 'Patron can reserve item with hold limit of 1, no holds placed'
669 branchcode
=> $library->branchcode,
670 borrowernumber
=> $patron->borrowernumber,
671 biblionumber
=> $biblio_1->biblionumber,
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'
682 # Raise reservesallowed to avoid tooManyReserves from it
683 Koha
::CirculationRules
->set_rule(
688 itemtype
=> $itemtype->itemtype,
689 rule_name
=> 'reservesallowed',
695 CanItemBeReserved
( $patron->borrowernumber, $itemnumber_2 ),
697 'Patron can reserve item with 2 reserves daily cap'
700 # Add a second reserve
701 my $res_id = AddReserve
(
703 branchcode
=> $library->branchcode,
704 borrowernumber
=> $patron->borrowernumber,
705 biblionumber
=> $biblio_2->biblionumber,
710 CanItemBeReserved
( $patron->borrowernumber, $itemnumber_2 ),
711 { status
=> 'tooManyReservesToday', limit
=> 2 },
712 'Patron cannot a third item with 2 reserves daily cap'
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;
721 CanItemBeReserved
( $patron->borrowernumber, $itemnumber_2 ),
723 'Patron can reserve item with 2 bib level hold placed on different days, 2 reserves daily cap'
726 # Set holds_per_day to 0
727 Koha
::CirculationRules
->set_rule(
732 itemtype
=> $itemtype->itemtype,
733 rule_name
=> 'holds_per_day',
739 # Delete existing holds
740 Koha
::Holds
->search->delete;
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)'
747 Koha
::CirculationRules
->set_rule(
752 itemtype
=> $itemtype->itemtype,
753 rule_name
=> 'holds_per_day',
758 Koha
::Holds
->search->delete;
760 CanItemBeReserved
( $patron->borrowernumber, $itemnumber_2 ),
762 'Patron can reserve if holds_per_day is undef (i.e. undef is unlimited daily cap)'
766 branchcode
=> $library->branchcode,
767 borrowernumber
=> $patron->borrowernumber,
768 biblionumber
=> $biblio_1->biblionumber,
774 branchcode
=> $library->branchcode,
775 borrowernumber
=> $patron->borrowernumber,
776 biblionumber
=> $biblio_2->biblionumber,
782 CanItemBeReserved
( $patron->borrowernumber, $itemnumber_3 ),
784 'Patron can reserve if holds_per_day is undef (i.e. undef is unlimited daily cap)'
788 branchcode
=> $library->branchcode,
789 borrowernumber
=> $patron->borrowernumber,
790 biblionumber
=> $biblio_3->biblionumber,
795 CanItemBeReserved
( $patron->borrowernumber, $itemnumber_3 ),
796 { status
=> 'tooManyReserves', limit
=> 3 },
797 'Unlimited daily holds, but reached reservesallowed'
799 #results should be the same for both ReservesControlBranch settings
800 t
::lib
::Mocks
::mock_preference
('ReservesControlBranch', 'ItemHomeLibrary');
802 CanItemBeReserved
( $patron->borrowernumber, $itemnumber_3 ),
803 { status
=> 'tooManyReserves', limit
=> 3 },
804 'Unlimited daily holds, but reached reservesallowed'
807 $schema->storage->txn_rollback;
810 subtest
'CanItemBeReserved / branch_not_in_hold_group' => sub {
813 $schema->storage->txn_begin;
815 Koha
::CirculationRules
->set_rule(
818 categorycode
=> undef,
820 rule_name
=> 'reservesallowed',
826 my $itemtype1 = $builder->build_object( { class => 'Koha::ItemTypes' } );
827 my $itemtype2 = $builder->build_object( { class => 'Koha::ItemTypes' } );
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' } );
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} } );
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} } );
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(
847 biblionumber
=> $biblio_1->biblionumber,
848 library
=> $library1->branchcode
851 my $biblio_2 = $builder->build_sample_biblio({ itemtype
=> $itemtype2->itemtype });
852 my $item_2 = $builder->build_sample_item(
854 biblionumber
=> $biblio_2->biblionumber,
855 library
=> $library2->branchcode
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(
862 biblionumber
=> $biblio_3->biblionumber,
863 library
=> $library1->branchcode
867 # Test 1: Patron 3 can place hold
869 CanItemBeReserved
( $patron3->borrowernumber, $itemnumber_2 ),
871 'Patron can place hold if no circ_rules where defined'
874 # Insert default circ rule of holds allowed only from local hold group for all libraries
875 Koha
::CirculationRules
->set_rules(
881 hold_fulfillment_policy
=> 'any',
882 returnbranch
=> 'any'
887 # Test 2: Patron 1 can place hold
889 CanItemBeReserved
( $patron1->borrowernumber, $itemnumber_2 ),
891 'Patron can place hold because patron\'s home library is part of hold group'
894 # Test 3: Patron 3 cannot place hold
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'
901 # Insert default circ rule to "any" for library 2
902 Koha
::CirculationRules
->set_rules(
904 branchcode
=> $library2->branchcode,
908 hold_fulfillment_policy
=> 'any',
909 returnbranch
=> 'any'
914 # Test 4: Patron 3 can place hold
916 CanItemBeReserved
( $patron3->borrowernumber, $itemnumber_2 ),
918 'Patron can place hold if holdallowed is set to "any" for library 2'
921 # Update default circ rule to "hold group" for library 2
922 Koha
::CirculationRules
->set_rules(
924 branchcode
=> $library2->branchcode,
928 hold_fulfillment_policy
=> 'any',
929 returnbranch
=> 'any'
934 # Test 5: Patron 3 cannot place hold
936 CanItemBeReserved
( $patron3->borrowernumber, $itemnumber_2 ),
937 { status
=> 'branchNotInHoldGroup' },
938 'Patron cannot place hold if holdallowed is set to "hold group" for library 2'
941 # Insert default item rule to "any" for itemtype 2
942 Koha
::CirculationRules
->set_rules(
944 branchcode
=> $library2->branchcode,
945 itemtype
=> $itemtype2->itemtype,
948 hold_fulfillment_policy
=> 'any',
949 returnbranch
=> 'any'
954 # Test 6: Patron 3 can place hold
956 CanItemBeReserved
( $patron3->borrowernumber, $itemnumber_2 ),
958 'Patron can place hold if holdallowed is set to "any" for itemtype 2'
961 # Update default item rule to "hold group" for itemtype 2
962 Koha
::CirculationRules
->set_rules(
964 branchcode
=> $library2->branchcode,
965 itemtype
=> $itemtype2->itemtype,
968 hold_fulfillment_policy
=> 'any',
969 returnbranch
=> 'any'
974 # Test 7: Patron 3 cannot place hold
976 CanItemBeReserved
( $patron3->borrowernumber, $itemnumber_2 ),
977 { status
=> 'branchNotInHoldGroup' },
978 'Patron cannot place hold if holdallowed is set to "hold group" for itemtype 2'
981 # Insert branch item rule to "any" for itemtype 2 and library 2
982 Koha
::CirculationRules
->set_rules(
984 branchcode
=> $library2->branchcode,
985 itemtype
=> $itemtype2->itemtype,
988 hold_fulfillment_policy
=> 'any',
989 returnbranch
=> 'any'
994 # Test 8: Patron 3 can place hold
996 CanItemBeReserved
( $patron3->borrowernumber, $itemnumber_2 ),
998 'Patron can place hold if holdallowed is set to "any" for itemtype 2 and library 2'
1001 # Update branch item rule to "hold group" for itemtype 2 and library 2
1002 Koha
::CirculationRules
->set_rules(
1004 branchcode
=> $library2->branchcode,
1005 itemtype
=> $itemtype2->itemtype,
1008 hold_fulfillment_policy
=> 'any',
1009 returnbranch
=> 'any'
1014 # Test 9: Patron 3 cannot place hold
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'
1021 $schema->storage->txn_rollback;
1025 subtest
'CanItemBeReserved / pickup_not_in_hold_group' => sub {
1028 $schema->storage->txn_begin;
1029 Koha
::CirculationRules
->set_rule(
1031 branchcode
=> undef,
1032 categorycode
=> undef,
1034 rule_name
=> 'reservesallowed',
1040 my $itemtype1 = $builder->build_object( { class => 'Koha::ItemTypes' } );
1041 my $itemtype2 = $builder->build_object( { class => 'Koha::ItemTypes' } );
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} } );
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} } );
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} } );
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(
1061 biblionumber
=> $biblio_1->biblionumber,
1062 library
=> $library1->branchcode
1065 my $biblio_2 = $builder->build_sample_biblio({ itemtype
=> $itemtype2->itemtype });
1066 my $item_2 = $builder->build_sample_item(
1068 biblionumber
=> $biblio_2->biblionumber,
1069 library
=> $library2->branchcode
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(
1076 biblionumber
=> $biblio_3->biblionumber,
1077 library
=> $library1->branchcode
1081 # Test 1: Patron 3 can place hold
1083 CanItemBeReserved
( $patron3->borrowernumber, $itemnumber_2, $library3->branchcode ),
1085 'Patron can place hold if no circ_rules where defined'
1088 # Insert default circ rule of holds allowed only from local hold group for all libraries
1089 Koha
::CirculationRules
->set_rules(
1091 branchcode
=> undef,
1095 hold_fulfillment_policy
=> 'holdgroup',
1096 returnbranch
=> 'any'
1101 # Test 2: Patron 1 can place hold
1103 CanItemBeReserved
( $patron3->borrowernumber, $itemnumber_2, $library1->branchcode ),
1105 'Patron can place hold because pickup location is part of hold group'
1108 # Test 3: Patron 3 cannot place hold
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'
1115 # Insert default circ rule to "any" for library 2
1116 Koha
::CirculationRules
->set_rules(
1118 branchcode
=> $library2->branchcode,
1122 hold_fulfillment_policy
=> 'any',
1123 returnbranch
=> 'any'
1128 # Test 4: Patron 3 can place hold
1130 CanItemBeReserved
( $patron3->borrowernumber, $itemnumber_2, $library3->branchcode ),
1132 'Patron can place hold if default_branch_circ_rules is set to "any" for library 2'
1135 # Update default circ rule to "hold group" for library 2
1136 Koha
::CirculationRules
->set_rules(
1138 branchcode
=> $library2->branchcode,
1142 hold_fulfillment_policy
=> 'holdgroup',
1143 returnbranch
=> 'any'
1148 # Test 5: Patron 3 cannot place hold
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'
1155 # Insert default item rule to "any" for itemtype 2
1156 Koha
::CirculationRules
->set_rules(
1158 branchcode
=> $library2->branchcode,
1159 itemtype
=> $itemtype2->itemtype,
1162 hold_fulfillment_policy
=> 'any',
1163 returnbranch
=> 'any'
1168 # Test 6: Patron 3 can place hold
1170 CanItemBeReserved
( $patron3->borrowernumber, $itemnumber_2, $library3->branchcode ),
1172 'Patron can place hold if hold_fulfillment_policy is set to "any" for itemtype 2'
1175 # Update default item rule to "hold group" for itemtype 2
1176 Koha
::CirculationRules
->set_rules(
1178 branchcode
=> $library2->branchcode,
1179 itemtype
=> $itemtype2->itemtype,
1182 hold_fulfillment_policy
=> 'holdgroup',
1183 returnbranch
=> 'any'
1188 # Test 7: Patron 3 cannot place hold
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'
1195 # Insert branch item rule to "any" for itemtype 2 and library 2
1196 Koha
::CirculationRules
->set_rules(
1198 branchcode
=> $library2->branchcode,
1199 itemtype
=> $itemtype2->itemtype,
1202 hold_fulfillment_policy
=> 'any',
1203 returnbranch
=> 'any'
1208 # Test 8: Patron 3 can place hold
1210 CanItemBeReserved
( $patron3->borrowernumber, $itemnumber_2, $library3->branchcode ),
1212 'Patron can place hold if hold_fulfillment_policy is set to "any" for itemtype 2 and library 2'
1215 # Update branch item rule to "hold group" for itemtype 2 and library 2
1216 Koha
::CirculationRules
->set_rules(
1218 branchcode
=> $library2->branchcode,
1219 itemtype
=> $itemtype2->itemtype,
1222 hold_fulfillment_policy
=> 'holdgroup',
1223 returnbranch
=> 'any'
1228 # Test 9: Patron 3 cannot place hold
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'
1235 $schema->storage->txn_rollback;
1238 subtest
'non priority holds' => sub {
1242 $schema->storage->txn_begin;
1244 Koha
::CirculationRules
->set_rules(
1246 branchcode
=> undef,
1247 categorycode
=> undef,
1250 renewalsallowed
=> 5,
1251 reservesallowed
=> 5,
1256 my $item = $builder->build_sample_item;
1258 my $patron1 = $builder->build_object(
1260 class => 'Koha::Patrons',
1261 value
=> { branchcode
=> $item->homebranch }
1264 my $patron2 = $builder->build_object(
1266 class => 'Koha::Patrons',
1267 value
=> { branchcode
=> $item->homebranch }
1271 Koha
::Checkout
->new(
1273 borrowernumber
=> $patron1->borrowernumber,
1274 itemnumber
=> $item->itemnumber,
1275 branchcode
=> $item->homebranch
1279 my $hid = AddReserve
(
1281 branchcode
=> $item->homebranch,
1282 borrowernumber
=> $patron2->borrowernumber,
1283 biblionumber
=> $item->biblionumber,
1285 itemnumber
=> $item->itemnumber,
1290 CanBookBeRenewed
( $patron1->borrowernumber, $item->itemnumber );
1292 ok
( !$ok, 'Cannot renew' );
1293 is
( $err, 'on_reserve', 'Item is on hold' );
1295 my $hold = Koha
::Holds
->find($hid);
1296 $hold->non_priority(1)->store;
1299 CanBookBeRenewed
( $patron1->borrowernumber, $item->itemnumber );
1301 ok
( $ok, 'Can renew' );
1302 is
( $err, undef, 'Item is on non priority hold' );
1304 my $patron3 = $builder->build_object(
1306 class => 'Koha::Patrons',
1307 value
=> { branchcode
=> $item->homebranch }
1311 # Add second hold with non_priority = 0
1314 branchcode
=> $item->homebranch,
1315 borrowernumber
=> $patron3->borrowernumber,
1316 biblionumber
=> $item->biblionumber,
1318 itemnumber
=> $item->itemnumber,
1323 CanBookBeRenewed
( $patron1->borrowernumber, $item->itemnumber );
1325 ok
( !$ok, 'Cannot renew' );
1326 is
( $err, 'on_reserve', 'Item is on hold' );
1328 $schema->storage->txn_rollback;