Bug 27068: Add unit tests
[koha.git] / t / db_dependent / HoldsQueue.t
1 #!/usr/bin/perl
2
3 # Test C4::HoldsQueue::CreateQueue() for both transport cost matrix
4 # and StaticHoldsQueueWeight array (no RandomizeHoldsQueueWeight, no point)
5 # Wraps tests in transaction that's rolled back, so no data is destroyed
6 # MySQL WARNING: This makes sense only if your tables are InnoDB, otherwise
7 # transactions are not supported and mess is left behind
8
9 use Modern::Perl;
10
11 use Test::More tests => 55;
12 use Data::Dumper;
13
14 use C4::Calendar;
15 use C4::Context;
16 use C4::Members;
17 use Koha::Database;
18 use Koha::DateUtils;
19 use Koha::Items;
20 use Koha::Holds;
21 use Koha::CirculationRules;
22
23 use t::lib::TestBuilder;
24 use t::lib::Mocks;
25
26 BEGIN {
27     use FindBin;
28     use lib $FindBin::Bin;
29     use_ok('C4::Reserves');
30     use_ok('C4::HoldsQueue');
31 }
32
33 my $schema = Koha::Database->schema;
34 $schema->storage->txn_begin;
35 my $dbh = C4::Context->dbh;
36 $dbh->do("DELETE FROM circulation_rules");
37
38 my $builder = t::lib::TestBuilder->new;
39
40 t::lib::Mocks::mock_preference( 'UseBranchTransferLimits',  '0' );
41 t::lib::Mocks::mock_preference( 'BranchTransferLimitsType', 'itemtype' );
42
43 my $library1 = $builder->build({
44     source => 'Branch',
45 });
46 my $library2 = $builder->build({
47     source => 'Branch',
48 });
49 my $library3 = $builder->build({
50     source => 'Branch',
51 });
52
53 my $TITLE = "Test Holds Queue XXX";
54
55 my $borrower = $builder->build({
56     source => 'Borrower',
57     value => {
58         branchcode => $library1->{branchcode},
59     }
60 });
61
62 my $borrowernumber = $borrower->{borrowernumber};
63 # Set special (for this test) branches
64 my $borrower_branchcode = $borrower->{branchcode};
65 my @branchcodes = ( $library1->{branchcode}, $library2->{branchcode}, $library3->{branchcode} );
66 my @other_branches = ( $library2->{branchcode}, $library3->{branchcode} );
67 my $least_cost_branch_code = pop @other_branches;
68 my $itemtype = $builder->build({ source => 'Itemtype', value => { notforloan => 0 } })->{itemtype};
69
70 #Set up the stage
71 # Sysprefs and cost matrix
72 t::lib::Mocks::mock_preference('HoldsQueueSkipClosed', 0);
73 t::lib::Mocks::mock_preference('LocalHoldsPriority', 0);
74 $dbh->do("UPDATE systempreferences SET value = ? WHERE variable = 'StaticHoldsQueueWeight'", undef,
75          join( ',', @other_branches, $borrower_branchcode, $least_cost_branch_code));
76 $dbh->do("UPDATE systempreferences SET value = '0' WHERE variable = 'RandomizeHoldsQueueWeight'");
77
78 $dbh->do("DELETE FROM transport_cost");
79 my $transport_cost_insert_sth = $dbh->prepare("insert into transport_cost (frombranch, tobranch, cost) values (?, ?, ?)");
80 # Favour $least_cost_branch_code
81 $transport_cost_insert_sth->execute($borrower_branchcode, $least_cost_branch_code, 0.2);
82 $transport_cost_insert_sth->execute($least_cost_branch_code, $borrower_branchcode, 0.2);
83 my @b = @other_branches;
84 while ( my $b1 = shift @b ) {
85     foreach my $b2 ($borrower_branchcode, $least_cost_branch_code, @b) {
86         $transport_cost_insert_sth->execute($b1, $b2, 0.5);
87         $transport_cost_insert_sth->execute($b2, $b1, 0.5);
88     }
89 }
90
91
92 # Loanable items - all possible combinations of homebranch and holdingbranch
93 $dbh->do("INSERT INTO biblio (frameworkcode, author, title, datecreated)
94           VALUES             ('SER', 'Koha test', '$TITLE', '2011-02-01')");
95 my $biblionumber = $dbh->selectrow_array("SELECT biblionumber FROM biblio WHERE title = '$TITLE'")
96   or BAIL_OUT("Cannot find newly created biblio record");
97 $dbh->do("INSERT INTO biblioitems (biblionumber, itemtype)
98           VALUES                  ($biblionumber, '$itemtype')");
99 my $biblioitemnumber = $dbh->selectrow_array("SELECT biblioitemnumber FROM biblioitems WHERE biblionumber = $biblionumber")
100   or BAIL_OUT("Cannot find newly created biblioitems record");
101
102 my $items_insert_sth = $dbh->prepare("INSERT INTO items (biblionumber, biblioitemnumber, barcode, homebranch, holdingbranch, notforloan, damaged, itemlost, withdrawn, onloan, itype)
103                                       VALUES            ($biblionumber, $biblioitemnumber, ?, ?, ?, 0, 0, 0, 0, NULL, '$itemtype')"); # CURRENT_DATE - 3)");
104 my $first_barcode = int(rand(1000000000000)); # XXX
105 my $barcode = $first_barcode;
106 foreach ( $borrower_branchcode, $least_cost_branch_code, @other_branches ) {
107     $items_insert_sth->execute($barcode++, $borrower_branchcode, $_);
108     $items_insert_sth->execute($barcode++, $_, $_);
109     $items_insert_sth->execute($barcode++, $_, $borrower_branchcode);
110 }
111
112 # Remove existing reserves, makes debugging easier
113 $dbh->do("DELETE FROM reserves");
114 my $bibitems = undef;
115 my $priority = 1;
116 # Make a reserve
117 AddReserve(
118     {
119         branchcode     => $borrower_branchcode,
120         borrowernumber => $borrowernumber,
121         biblionumber   => $biblionumber,
122         priority       => $priority,
123     }
124 );
125 #                           $resdate, $expdate, $notes, $title, $checkitem, $found
126 $dbh->do("UPDATE reserves SET reservedate = DATE_SUB( reservedate, INTERVAL 1 DAY )");
127
128 # Tests
129 my $use_cost_matrix_sth = $dbh->prepare("UPDATE systempreferences SET value = ? WHERE variable = 'UseTransportCostMatrix'");
130 my $test_sth = $dbh->prepare("SELECT * FROM hold_fill_targets
131                               JOIN tmp_holdsqueue USING (borrowernumber, biblionumber, itemnumber)
132                               JOIN items USING (itemnumber)
133                               WHERE borrowernumber = $borrowernumber");
134
135 # We have a book available homed in borrower branch, no point fiddling with AutomaticItemReturn
136 t::lib::Mocks::mock_preference('AutomaticItemReturn', 0);
137 test_queue ('take from homebranch',  0, $borrower_branchcode, $borrower_branchcode);
138 test_queue ('take from homebranch',  1, $borrower_branchcode, $borrower_branchcode);
139
140 $dbh->do("DELETE FROM tmp_holdsqueue");
141 $dbh->do("DELETE FROM hold_fill_targets");
142 $dbh->do("DELETE FROM issues WHERE itemnumber IN (SELECT itemnumber FROM items WHERE homebranch = '$borrower_branchcode' AND holdingbranch = '$borrower_branchcode')");
143 $dbh->do("DELETE FROM items WHERE homebranch = '$borrower_branchcode' AND holdingbranch = '$borrower_branchcode'");
144 # test_queue will flush
145 t::lib::Mocks::mock_preference('AutomaticItemReturn', 1);
146 # Not sure how to make this test more difficult - holding branch does not matter
147
148 $dbh->do("DELETE FROM tmp_holdsqueue");
149 $dbh->do("DELETE FROM hold_fill_targets");
150 $dbh->do("DELETE FROM issues WHERE itemnumber IN (SELECT itemnumber FROM items WHERE homebranch = '$borrower_branchcode')");
151 $dbh->do("DELETE FROM items WHERE homebranch = '$borrower_branchcode'");
152 t::lib::Mocks::mock_preference('AutomaticItemReturn', 0);
153 # We have a book available held in borrower branch
154 test_queue ('take from holdingbranch', 0, $borrower_branchcode, $borrower_branchcode);
155 test_queue ('take from holdingbranch', 1, $borrower_branchcode, $borrower_branchcode);
156
157 $dbh->do("DELETE FROM tmp_holdsqueue");
158 $dbh->do("DELETE FROM hold_fill_targets");
159 $dbh->do("DELETE FROM issues WHERE itemnumber IN (SELECT itemnumber FROM items WHERE holdingbranch = '$borrower_branchcode')");
160 $dbh->do("DELETE FROM items WHERE holdingbranch = '$borrower_branchcode'");
161 # No book available in borrower branch, pick according to the rules
162 # Frst branch from StaticHoldsQueueWeight
163 test_queue ('take from lowest cost branch', 0, $borrower_branchcode, $other_branches[0]);
164 test_queue ('take from lowest cost branch', 1, $borrower_branchcode, $least_cost_branch_code);
165 my $queue = C4::HoldsQueue::GetHoldsQueueItems($least_cost_branch_code) || [];
166 my $queue_item = $queue->[0];
167 ok( $queue_item
168  && $queue_item->{pickbranch} eq $borrower_branchcode
169  && $queue_item->{holdingbranch} eq $least_cost_branch_code, "GetHoldsQueueItems" )
170   or diag( "Expected item for pick $borrower_branchcode, hold $least_cost_branch_code, got ".Dumper($queue_item) );
171 ok( exists($queue_item->{itype}), 'item type included in queued items list (bug 5825)' );
172
173 ok(
174     C4::HoldsQueue::least_cost_branch( 'B', [ 'A', 'B', 'C' ] ) eq 'B',
175     'C4::HoldsQueue::least_cost_branch returns the local branch if it is in the list of branches to pull from'
176 );
177
178 # XXX All this tests are for borrower branch pick-up.
179 # Maybe needs expanding to homebranch or holdingbranch pick-up.
180
181 $schema->txn_rollback;
182 $schema->txn_begin;
183
184 ### Test holds queue builder does not violate holds policy ###
185
186 # Clear out existing rules relating to holdallowed
187 $dbh->do("DELETE FROM circulation_rules");
188
189 t::lib::Mocks::mock_preference('UseTransportCostMatrix', 0);
190
191 $itemtype = $builder->build({ source => 'Itemtype', value => { notforloan => 0 } })->{itemtype};
192
193 $library1 = $builder->build({
194     source => 'Branch',
195 });
196 $library2 = $builder->build({
197     source => 'Branch',
198 });
199 $library3 = $builder->build({
200     source => 'Branch',
201 });
202 @branchcodes = ( $library1->{branchcode}, $library2->{branchcode}, $library3->{branchcode} );
203
204 my $borrower1 = $builder->build({
205     source => 'Borrower',
206     value => {
207         branchcode => $branchcodes[0],
208     },
209 });
210 my $borrower2 = $builder->build({
211     source => 'Borrower',
212     value => {
213         branchcode => $branchcodes[1],
214     },
215 });
216 my $borrower3 = $builder->build({
217     source => 'Borrower',
218     value => {
219         branchcode => $branchcodes[2],
220     },
221 });
222
223 $dbh->do(qq{
224     INSERT INTO biblio (
225         frameworkcode, 
226         author, 
227         title, 
228         datecreated
229     ) VALUES (
230         'SER', 
231         'Koha test', 
232         '$TITLE', 
233         '2011-02-01'
234     )
235 });
236 $biblionumber = $dbh->selectrow_array("SELECT biblionumber FROM biblio WHERE title = '$TITLE'")
237   or BAIL_OUT("Cannot find newly created biblio record");
238
239 $dbh->do(qq{
240     INSERT INTO biblioitems (
241         biblionumber, 
242         itemtype
243     ) VALUES (
244         $biblionumber, 
245         '$itemtype'
246     )
247 });
248 $biblioitemnumber = $dbh->selectrow_array("SELECT biblioitemnumber FROM biblioitems WHERE biblionumber = $biblionumber")
249   or BAIL_OUT("Cannot find newly created biblioitems record");
250
251 $items_insert_sth = $dbh->prepare(qq{
252     INSERT INTO items (
253         biblionumber, 
254         biblioitemnumber,
255         barcode,
256         homebranch,
257         holdingbranch,
258         notforloan,
259         damaged,
260         itemlost,
261         withdrawn,
262         onloan,
263         itype
264     ) VALUES (
265         $biblionumber,
266         $biblioitemnumber,
267         ?,
268         ?,
269         ?,
270         0,
271         0,
272         0,
273         0,
274         NULL,
275         '$itemtype'
276     )
277 });
278 # Create 3 items from 2 branches ( branches are for borrowers 1 and 2 respectively )
279 $barcode = int( rand(1000000000000) );
280 $items_insert_sth->execute( $barcode + 0, $branchcodes[0], $branchcodes[0] );
281 $items_insert_sth->execute( $barcode + 1, $branchcodes[1], $branchcodes[1] );
282 $items_insert_sth->execute( $barcode + 2, $branchcodes[1], $branchcodes[1] );
283
284 $dbh->do("DELETE FROM reserves");
285 my $sth = $dbh->prepare(q{
286     INSERT INTO reserves ( 
287         borrowernumber,
288         biblionumber,
289         branchcode,
290         priority,
291         reservedate
292     ) VALUES ( ?,?,?,?, CURRENT_DATE() )
293 });
294 $sth->execute( $borrower1->{borrowernumber}, $biblionumber, $branchcodes[0], 1 );
295 $sth->execute( $borrower2->{borrowernumber}, $biblionumber, $branchcodes[0], 2 );
296 $sth->execute( $borrower3->{borrowernumber}, $biblionumber, $branchcodes[0], 3 );
297
298 my $holds_queue;
299
300 $dbh->do("DELETE FROM circulation_rules");
301 Koha::CirculationRules->set_rule(
302     {
303         rule_name    => 'holdallowed',
304         rule_value   => 1,
305         branchcode   => undef,
306         itemtype     => undef,
307     }
308 );
309 C4::HoldsQueue::CreateQueue();
310 $holds_queue = $dbh->selectall_arrayref("SELECT * FROM tmp_holdsqueue", { Slice => {} });
311 is( @$holds_queue, 2, "Holds queue filling correct number for default holds policy 'from home library'" );
312 is( $holds_queue->[0]->{cardnumber}, $borrower1->{cardnumber}, "Holds queue filling 1st correct hold for default holds policy 'from home library'");
313 is( $holds_queue->[1]->{cardnumber}, $borrower2->{cardnumber}, "Holds queue filling 2nd correct hold for default holds policy 'from home library'");
314
315 # Test skipping hold picks for closed libraries.
316 # At this point in the test, we have 2 rows in the holds queue
317 # 1 of which is coming from MPL. Let's enable HoldsQueueSkipClosed
318 # and make today a holiday for MPL. When we run it again we should only
319 # have 1 row in the holds queue
320 t::lib::Mocks::mock_preference('HoldsQueueSkipClosed', 1);
321 my $today = dt_from_string();
322 C4::Calendar->new( branchcode => $branchcodes[0] )->insert_single_holiday(
323     day         => $today->day(),
324     month       => $today->month(),
325     year        => $today->year(),
326     title       => "$today",
327     description => "$today",
328 );
329 # If the test below is removed, aother tests using the holiday will fail. For some reason if we call is_holiday now
330 # the holiday will get set in cache correctly, but not if we let C4::HoldsQueue call is_holiday instead.
331 is( Koha::Calendar->new( branchcode => $branchcodes[0] )->is_holiday( $today ), 1, 'Is today a holiday for pickup branch' );
332 C4::HoldsQueue::CreateQueue();
333 $holds_queue = $dbh->selectall_arrayref("SELECT * FROM tmp_holdsqueue", { Slice => {} });
334 is( scalar( @$holds_queue ), 1, "Holds not filled with items from closed libraries" );
335 t::lib::Mocks::mock_preference('HoldsQueueSkipClosed', 0);
336
337 $dbh->do("DELETE FROM circulation_rules");
338 Koha::CirculationRules->set_rule(
339     {
340         rule_name    => 'holdallowed',
341         rule_value   => 2,
342         branchcode   => undef,
343         itemtype     => undef,
344     }
345 );
346 C4::HoldsQueue::CreateQueue();
347 $holds_queue = $dbh->selectall_arrayref("SELECT * FROM tmp_holdsqueue", { Slice => {} });
348 is( @$holds_queue, 3, "Holds queue filling correct number for holds for default holds policy 'from any library'" );
349
350 # Test skipping hold picks for closed libraries without transport cost matrix
351 # At this point in the test, we have 3 rows in the holds queue
352 # one of which is coming from MPL. Let's enable HoldsQueueSkipClosed
353 # and use our previously created holiday for MPL
354 # When we run it again we should only have 2 rows in the holds queue
355 t::lib::Mocks::mock_preference( 'HoldsQueueSkipClosed', 1 );
356 C4::HoldsQueue::CreateQueue();
357 $holds_queue = $dbh->selectall_arrayref("SELECT * FROM tmp_holdsqueue", { Slice => {} });
358 is( scalar( @$holds_queue ), 2, "Holds not filled with items from closed libraries" );
359 t::lib::Mocks::mock_preference( 'HoldsQueueSkipClosed', 0 );
360
361 ## Test LocalHoldsPriority
362 t::lib::Mocks::mock_preference('LocalHoldsPriority', 1);
363
364 $dbh->do("DELETE FROM circulation_rules");
365 Koha::CirculationRules->set_rule(
366     {
367         rule_name    => 'holdallowed',
368         rule_value   => 2,
369         branchcode   => undef,
370         itemtype     => undef,
371     }
372 );
373 $dbh->do("DELETE FROM issues");
374
375 # Test homebranch = patron branch
376 t::lib::Mocks::mock_preference('LocalHoldsPriorityPatronControl', 'HomeLibrary');
377 t::lib::Mocks::mock_preference('LocalHoldsPriorityItemControl', 'homebranch');
378 C4::Context->clear_syspref_cache();
379 $dbh->do("DELETE FROM reserves");
380 $sth->execute( $borrower1->{borrowernumber}, $biblionumber, $branchcodes[0], 1 );
381 $sth->execute( $borrower2->{borrowernumber}, $biblionumber, $branchcodes[0], 2 );
382 $sth->execute( $borrower3->{borrowernumber}, $biblionumber, $branchcodes[0], 3 );
383
384 $dbh->do("DELETE FROM items");
385 # barcode, homebranch, holdingbranch, itemtype
386 $items_insert_sth->execute( $barcode + 4, $branchcodes[2], $branchcodes[0] );
387
388 C4::HoldsQueue::CreateQueue();
389 $holds_queue = $dbh->selectall_arrayref("SELECT * FROM tmp_holdsqueue", { Slice => {} });
390 is( $holds_queue->[0]->{cardnumber}, $borrower3->{cardnumber}, "Holds queue giving priority to patron who's home library matches item's home library");
391
392 ### Test branch transfer limits ###
393 t::lib::Mocks::mock_preference('LocalHoldsPriorityPatronControl', 'HomeLibrary');
394 t::lib::Mocks::mock_preference('LocalHoldsPriorityItemControl', 'holdingbranch');
395 t::lib::Mocks::mock_preference( 'UseBranchTransferLimits', '1' );
396 C4::Context->clear_syspref_cache();
397 $dbh->do("DELETE FROM reserves");
398 $sth->execute( $borrower1->{borrowernumber}, $biblionumber, $branchcodes[0], 1 );
399 $sth->execute( $borrower2->{borrowernumber}, $biblionumber, $branchcodes[1], 2 );
400
401 $dbh->do("DELETE FROM items");
402 # barcode, homebranch, holdingbranch, itemtype
403 $items_insert_sth->execute( $barcode, $branchcodes[2], $branchcodes[2] );
404 my $item = Koha::Items->find( { barcode => $barcode } );
405
406 my $limit1 = Koha::Item::Transfer::Limit->new(
407     {
408         toBranch   => $branchcodes[0],
409         fromBranch => $branchcodes[2],
410         itemtype   => $item->effective_itemtype,
411     }
412 )->store();
413
414 C4::HoldsQueue::CreateQueue();
415 $holds_queue = $dbh->selectall_arrayref("SELECT * FROM tmp_holdsqueue", { Slice => {} });
416 is( $holds_queue->[0]->{cardnumber}, $borrower2->{cardnumber}, "Holds queue skips hold transfer that would violate branch transfer limits");
417
418 my $limit2 = Koha::Item::Transfer::Limit->new(
419     {
420         toBranch   => $branchcodes[1],
421         fromBranch => $branchcodes[2],
422         itemtype   => $item->effective_itemtype,
423     }
424 )->store();
425
426 C4::HoldsQueue::CreateQueue();
427 $holds_queue = $dbh->selectall_arrayref("SELECT * FROM tmp_holdsqueue", { Slice => {} });
428 is( $holds_queue->[0]->{cardnumber}, undef, "Holds queue doesn't fill hold where all available items would violate branch transfer limits");
429
430 $limit1->delete();
431 $limit2->delete();
432 t::lib::Mocks::mock_preference( 'UseBranchTransferLimits', '0' );
433 ### END Test branch transfer limits ###
434
435 # Test holdingbranch = patron branch
436 t::lib::Mocks::mock_preference('LocalHoldsPriorityPatronControl', 'HomeLibrary');
437 t::lib::Mocks::mock_preference('LocalHoldsPriorityItemControl', 'holdingbranch');
438 C4::Context->clear_syspref_cache();
439 $dbh->do("DELETE FROM reserves");
440 $sth->execute( $borrower1->{borrowernumber}, $biblionumber, $branchcodes[0], 1 );
441 $sth->execute( $borrower2->{borrowernumber}, $biblionumber, $branchcodes[0], 2 );
442 $sth->execute( $borrower3->{borrowernumber}, $biblionumber, $branchcodes[0], 3 );
443
444 $dbh->do("DELETE FROM items");
445 # barcode, homebranch, holdingbranch, itemtype
446 $items_insert_sth->execute( $barcode + 4, $branchcodes[0], $branchcodes[2] );
447
448 C4::HoldsQueue::CreateQueue();
449 $holds_queue = $dbh->selectall_arrayref("SELECT * FROM tmp_holdsqueue", { Slice => {} });
450 is( $holds_queue->[0]->{cardnumber}, $borrower3->{cardnumber}, "Holds queue giving priority to patron who's home library matches item's holding library");
451
452 # Test holdingbranch = pickup branch
453 t::lib::Mocks::mock_preference('LocalHoldsPriorityPatronControl', 'PickupLibrary');
454 t::lib::Mocks::mock_preference('LocalHoldsPriorityItemControl', 'holdingbranch');
455 C4::Context->clear_syspref_cache();
456 $dbh->do("DELETE FROM reserves");
457 $sth->execute( $borrower1->{borrowernumber}, $biblionumber, $branchcodes[0], 1 );
458 $sth->execute( $borrower2->{borrowernumber}, $biblionumber, $branchcodes[0], 2 );
459 $sth->execute( $borrower3->{borrowernumber}, $biblionumber, $branchcodes[2], 3 );
460
461 $dbh->do("DELETE FROM items");
462 # barcode, homebranch, holdingbranch, itemtype
463 $items_insert_sth->execute( $barcode + 4, $branchcodes[0], $branchcodes[2] );
464
465 C4::HoldsQueue::CreateQueue();
466 $holds_queue = $dbh->selectall_arrayref("SELECT * FROM tmp_holdsqueue", { Slice => {} });
467 is( $holds_queue->[0]->{cardnumber}, $borrower3->{cardnumber}, "Holds queue giving priority to patron who's home library matches item's holding library");
468
469 # Test homebranch = pickup branch
470 t::lib::Mocks::mock_preference('LocalHoldsPriorityPatronControl', 'PickupLibrary');
471 t::lib::Mocks::mock_preference('LocalHoldsPriorityItemControl', 'homebranch');
472 C4::Context->clear_syspref_cache();
473 $dbh->do("DELETE FROM reserves");
474 $sth->execute( $borrower1->{borrowernumber}, $biblionumber, $branchcodes[0], 1 );
475 $sth->execute( $borrower2->{borrowernumber}, $biblionumber, $branchcodes[0], 2 );
476 $sth->execute( $borrower3->{borrowernumber}, $biblionumber, $branchcodes[2], 3 );
477
478 $dbh->do("DELETE FROM items");
479 # barcode, homebranch, holdingbranch, itemtype
480 $items_insert_sth->execute( $barcode + 4, $branchcodes[2], $branchcodes[0] );
481
482 C4::HoldsQueue::CreateQueue();
483 $holds_queue = $dbh->selectall_arrayref("SELECT * FROM tmp_holdsqueue", { Slice => {} });
484 is( $holds_queue->[0]->{cardnumber}, $borrower3->{cardnumber}, "Holds queue giving priority to patron who's home library matches item's holding library");
485
486 t::lib::Mocks::mock_preference('LocalHoldsPriority', 0);
487 ## End testing of LocalHoldsPriority
488
489
490 # Bug 14297
491 $itemtype = $builder->build({ source => 'Itemtype', value => { notforloan => 0 } })->{itemtype};
492 $borrowernumber = $borrower3->{borrowernumber};
493 my $library_A = $library1->{branchcode};
494 my $library_B = $library2->{branchcode};
495 my $library_C = $borrower3->{branchcode};
496 $dbh->do("DELETE FROM reserves");
497 $dbh->do("DELETE FROM issues");
498 $dbh->do("DELETE FROM items");
499 $dbh->do("DELETE FROM biblio");
500 $dbh->do("DELETE FROM biblioitems");
501 $dbh->do("DELETE FROM transport_cost");
502 $dbh->do("DELETE FROM tmp_holdsqueue");
503 $dbh->do("DELETE FROM hold_fill_targets");
504
505 $dbh->do("
506     INSERT INTO biblio (frameworkcode, author, title, datecreated) VALUES ('', 'Koha test', '$TITLE', '2011-02-01')
507 ");
508
509 $biblionumber = $dbh->selectrow_array("SELECT biblionumber FROM biblio WHERE title = '$TITLE'")
510   or BAIL_OUT("Cannot find newly created biblio record");
511
512 $dbh->do("INSERT INTO biblioitems (biblionumber, itemtype) VALUES ($biblionumber, '$itemtype')");
513
514 $biblioitemnumber =
515   $dbh->selectrow_array("SELECT biblioitemnumber FROM biblioitems WHERE biblionumber = $biblionumber")
516   or BAIL_OUT("Cannot find newly created biblioitems record");
517
518 $dbh->do("
519     INSERT INTO items (biblionumber, biblioitemnumber, homebranch, holdingbranch, notforloan, damaged, itemlost, withdrawn, onloan, itype)
520     VALUES ($biblionumber, $biblioitemnumber, '$library_A', '$library_A', 0, 0, 0, 0, NULL, '$itemtype')
521 ");
522
523 $dbh->do("
524     INSERT INTO items (biblionumber, biblioitemnumber, homebranch, holdingbranch, notforloan, damaged, itemlost, withdrawn, onloan, itype)
525     VALUES ($biblionumber, $biblioitemnumber, '$library_B', '$library_B', 0, 0, 0, 0, NULL, '$itemtype')
526 ");
527
528 Koha::CirculationRules->set_rules(
529     {
530         branchcode   => $library_A,
531         itemtype     => $itemtype,
532         rules        => {
533             holdallowed  => 2,
534             returnbranch => 'homebranch',
535         }
536     }
537 );
538
539 $dbh->do( "UPDATE systempreferences SET value = ? WHERE variable = 'StaticHoldsQueueWeight'",
540     undef, join( ',', $library_B, $library_A, $library_C ) );
541 $dbh->do( "UPDATE systempreferences SET value = 0 WHERE variable = 'RandomizeHoldsQueueWeight'" );
542
543 my $reserve_id = AddReserve(
544     {
545         branchcode     => $library_C,
546         borrowernumber => $borrowernumber,
547         biblionumber   => $biblionumber,
548         priority       => 1,
549     }
550 );
551 C4::HoldsQueue::CreateQueue();
552 $holds_queue = $dbh->selectall_arrayref("SELECT * FROM tmp_holdsqueue", { Slice => {} });
553 is( @$holds_queue, 1, "Bug 14297 - Holds Queue building ignoring holds where pickup & home branch don't match and item is not from le");
554 # End Bug 14297
555
556 # Bug 15062
557 $itemtype = $builder->build({ source => 'Itemtype', value => { notforloan => 0 } })->{itemtype};
558 $borrowernumber = $borrower2->{borrowernumber};
559 $library_A = $library1->{branchcode};
560 $library_B = $library2->{branchcode};
561 $dbh->do("DELETE FROM reserves");
562 $dbh->do("DELETE FROM issues");
563 $dbh->do("DELETE FROM items");
564 $dbh->do("DELETE FROM biblio");
565 $dbh->do("DELETE FROM biblioitems");
566 $dbh->do("DELETE FROM transport_cost");
567 $dbh->do("DELETE FROM tmp_holdsqueue");
568 $dbh->do("DELETE FROM hold_fill_targets");
569
570 t::lib::Mocks::mock_preference("UseTransportCostMatrix",1);
571
572 my $tc_rs = $schema->resultset('TransportCost');
573 $tc_rs->create({ frombranch => $library_A, tobranch => $library_B, cost => 0, disable_transfer => 1 });
574 $tc_rs->create({ frombranch => $library_B, tobranch => $library_A, cost => 0, disable_transfer => 1 });
575
576 $dbh->do("
577     INSERT INTO biblio (frameworkcode, author, title, datecreated) VALUES ('', 'Koha test', '$TITLE', '2011-02-01')
578 ");
579
580 $biblionumber = $dbh->selectrow_array("SELECT biblionumber FROM biblio WHERE title = '$TITLE'")
581   or BAIL_OUT("Cannot find newly created biblio record");
582
583 $dbh->do("INSERT INTO biblioitems (biblionumber, itemtype) VALUES ($biblionumber, '$itemtype')");
584
585 $biblioitemnumber =
586   $dbh->selectrow_array("SELECT biblioitemnumber FROM biblioitems WHERE biblionumber = $biblionumber")
587   or BAIL_OUT("Cannot find newly created biblioitems record");
588
589 $dbh->do("
590     INSERT INTO items (biblionumber, biblioitemnumber, homebranch, holdingbranch, notforloan, damaged, itemlost, withdrawn, onloan, itype)
591     VALUES ($biblionumber, $biblioitemnumber, '$library_A', '$library_A', 0, 0, 0, 0, NULL, '$itemtype')
592 ");
593
594 $reserve_id = AddReserve(
595     {
596         branchcode     => $library_B,
597         borrowernumber => $borrowernumber,
598         biblionumber   => $biblionumber,
599         priority       => 1,
600     }
601 );
602
603 C4::HoldsQueue::CreateQueue();
604 $holds_queue = $dbh->selectall_arrayref("SELECT * FROM tmp_holdsqueue", { Slice => {} });
605 is( @$holds_queue, 0, "Bug 15062 - Holds queue with Transport Cost Matrix will transfer item even if transfers disabled");
606 # End Bug 15062
607
608 # Test hold_fulfillment_policy
609 t::lib::Mocks::mock_preference( "UseTransportCostMatrix", 0 );
610 $borrowernumber = $borrower3->{borrowernumber};
611 $library_A = $library1->{branchcode};
612 $library_B = $library2->{branchcode};
613 $library_C = $library3->{branchcode};
614 $dbh->do("DELETE FROM reserves");
615 $dbh->do("DELETE FROM issues");
616 $dbh->do("DELETE FROM items");
617 $dbh->do("DELETE FROM biblio");
618 $dbh->do("DELETE FROM biblioitems");
619 $dbh->do("DELETE FROM transport_cost");
620 $dbh->do("DELETE FROM tmp_holdsqueue");
621 $dbh->do("DELETE FROM hold_fill_targets");
622
623 $dbh->do("INSERT INTO biblio (frameworkcode, author, title, datecreated) VALUES ('', 'Koha test', '$TITLE', '2011-02-01')");
624
625 $biblionumber = $dbh->selectrow_array("SELECT biblionumber FROM biblio WHERE title = '$TITLE'")
626   or BAIL_OUT("Cannot find newly created biblio record");
627
628 $dbh->do("INSERT INTO biblioitems (biblionumber, itemtype) VALUES ($biblionumber, '$itemtype')");
629
630 $biblioitemnumber =
631   $dbh->selectrow_array("SELECT biblioitemnumber FROM biblioitems WHERE biblionumber = $biblionumber")
632   or BAIL_OUT("Cannot find newly created biblioitems record");
633
634 $dbh->do("
635     INSERT INTO items (biblionumber, biblioitemnumber, homebranch, holdingbranch, notforloan, damaged, itemlost, withdrawn, onloan, itype)
636     VALUES ($biblionumber, $biblioitemnumber, '$library_A', '$library_B', 0, 0, 0, 0, NULL, '$itemtype')
637 ");
638
639 # With hold_fulfillment_policy = homebranch, hold should only be picked up if pickup branch = homebranch
640 $dbh->do("DELETE FROM circulation_rules");
641 Koha::CirculationRules->set_rules(
642     {
643         branchcode   => undef,
644         itemtype     => undef,
645         rules        => {
646             holdallowed             => 2,
647             hold_fulfillment_policy => 'homebranch',
648         }
649     }
650 );
651
652 # Home branch matches pickup branch
653 $reserve_id = AddReserve(
654     {
655         branchcode     => $library_A,
656         borrowernumber => $borrowernumber,
657         biblionumber   => $biblionumber,
658         priority       => 1,
659     }
660 );
661
662 C4::HoldsQueue::CreateQueue();
663 $holds_queue = $dbh->selectall_arrayref( "SELECT * FROM tmp_holdsqueue", { Slice => {} } );
664 is( @$holds_queue, 1, "Hold where pickup branch matches home branch targeted" );
665 my $target_rs = $schema->resultset('HoldFillTarget');
666 is( $target_rs->next->reserve_id, $reserve_id, "Reserve id correctly set in hold fill target for title level hold" );
667 Koha::Holds->find( $reserve_id )->cancel;
668
669 # Holding branch matches pickup branch
670 $reserve_id = AddReserve(
671     {
672         branchcode     => $library_B,
673         borrowernumber => $borrowernumber,
674         biblionumber   => $biblionumber,
675         priority       => 1,
676     }
677 );
678
679
680 C4::HoldsQueue::CreateQueue();
681 $holds_queue = $dbh->selectall_arrayref( "SELECT * FROM tmp_holdsqueue", { Slice => {} } );
682 is( @$holds_queue, 0, "Hold where pickup ne home, pickup eq home not targeted" );
683 Koha::Holds->find( $reserve_id )->cancel;
684
685 # Neither branch matches pickup branch
686 $reserve_id = AddReserve(
687     {
688         branchcode     => $library_C,
689         borrowernumber => $borrowernumber,
690         biblionumber   => $biblionumber,
691         priority       => 1,
692     }
693 );
694
695 C4::HoldsQueue::CreateQueue();
696 $holds_queue = $dbh->selectall_arrayref( "SELECT * FROM tmp_holdsqueue", { Slice => {} } );
697 is( @$holds_queue, 0, "Hold where pickup ne home, pickup ne holding not targeted" );
698 Koha::Holds->find( $reserve_id )->cancel;
699
700 # With hold_fulfillment_policy = holdingbranch, hold should only be picked up if pickup branch = holdingbranch
701 $dbh->do("DELETE FROM circulation_rules");
702 Koha::CirculationRules->set_rules(
703     {
704         branchcode   => undef,
705         itemtype     => undef,
706         rules        => {
707             holdallowed             => 2,
708             hold_fulfillment_policy => 'holdingbranch',
709         }
710     }
711 );
712
713 # Home branch matches pickup branch
714 $reserve_id = AddReserve(
715     {
716         branchcode     => $library_A,
717         borrowernumber => $borrowernumber,
718         biblionumber   => $biblionumber,
719         priority       => 1,
720     }
721 );
722
723 C4::HoldsQueue::CreateQueue();
724 $holds_queue = $dbh->selectall_arrayref( "SELECT * FROM tmp_holdsqueue", { Slice => {} } );
725 is( @$holds_queue, 0, "Hold where pickup eq home, pickup ne holding not targeted" );
726 Koha::Holds->find( $reserve_id )->cancel;
727
728 # Holding branch matches pickup branch
729 $reserve_id = AddReserve(
730     {
731         branchcode     => $library_B,
732         borrowernumber => $borrowernumber,
733         biblionumber   => $biblionumber,
734         priority       => 1,
735     }
736 );
737
738 C4::HoldsQueue::CreateQueue();
739 $holds_queue = $dbh->selectall_arrayref( "SELECT * FROM tmp_holdsqueue", { Slice => {} } );
740 is( @$holds_queue, 1, "Hold where pickup ne home, pickup eq holding targeted" );
741 Koha::Holds->find( $reserve_id )->cancel;
742
743 # Neither branch matches pickup branch
744 $reserve_id = AddReserve(
745     {
746         branchcode     => $library_C,
747         borrowernumber => $borrowernumber,
748         biblionumber   => $biblionumber,
749         priority       => 1,
750     }
751 );
752
753 C4::HoldsQueue::CreateQueue();
754 $holds_queue = $dbh->selectall_arrayref( "SELECT * FROM tmp_holdsqueue", { Slice => {} } );
755 is( @$holds_queue, 0, "Hold where pickup ne home, pickup ne holding not targeted" );
756 Koha::Holds->find( $reserve_id )->cancel;
757
758 # With hold_fulfillment_policy = any, hold should be pikcup up reguardless of matching home or holding branch
759 $dbh->do("DELETE FROM circulation_rules");
760 Koha::CirculationRules->set_rules(
761     {
762         branchcode   => undef,
763         itemtype     => undef,
764         rules        => {
765             holdallowed             => 2,
766             hold_fulfillment_policy => 'any',
767         }
768     }
769 );
770
771 # Home branch matches pickup branch
772 $reserve_id = AddReserve(
773     {
774         branchcode     => $library_A,
775         borrowernumber => $borrowernumber,
776         biblionumber   => $biblionumber,
777         priority       => 1,
778     }
779 );
780
781 C4::HoldsQueue::CreateQueue();
782 $holds_queue = $dbh->selectall_arrayref( "SELECT * FROM tmp_holdsqueue", { Slice => {} } );
783 is( @$holds_queue, 1, "Hold where pickup eq home, pickup ne holding targeted" );
784 Koha::Holds->find( $reserve_id )->cancel;
785
786 # Holding branch matches pickup branch
787 $reserve_id = AddReserve(
788     {
789         branchcode     => $library_B,
790         borrowernumber => $borrowernumber,
791         biblionumber   => $biblionumber,
792         priority       => 1,
793     }
794 );
795
796 C4::HoldsQueue::CreateQueue();
797 $holds_queue = $dbh->selectall_arrayref( "SELECT * FROM tmp_holdsqueue", { Slice => {} } );
798 is( @$holds_queue, 1, "Hold where pickup ne home, pickup eq holding targeted" );
799 Koha::Holds->find( $reserve_id )->cancel;
800
801 # Neither branch matches pickup branch
802 $reserve_id = AddReserve(
803     {
804         branchcode     => $library_C,
805         borrowernumber => $borrowernumber,
806         biblionumber   => $biblionumber,
807         priority       => 1,
808     }
809 );
810
811 C4::HoldsQueue::CreateQueue();
812 $holds_queue = $dbh->selectall_arrayref( "SELECT * FROM tmp_holdsqueue", { Slice => {} } );
813 is( @$holds_queue, 1, "Hold where pickup ne home, pickup ne holding targeted" );
814 Koha::Holds->find( $reserve_id )->cancel;
815
816 # End testing hold_fulfillment_policy
817
818 # Test hold itemtype limit
819 t::lib::Mocks::mock_preference( "UseTransportCostMatrix", 0 );
820 my $wrong_itemtype = $builder->build({ source => 'Itemtype', value => { notforloan => 0 } })->{itemtype};
821 my $right_itemtype = $builder->build({ source => 'Itemtype', value => { notforloan => 0 } })->{itemtype};
822 $borrowernumber = $borrower3->{borrowernumber};
823 my $branchcode = $library1->{branchcode};
824 $dbh->do("DELETE FROM reserves");
825 $dbh->do("DELETE FROM issues");
826 $dbh->do("DELETE FROM items");
827 $dbh->do("DELETE FROM biblio");
828 $dbh->do("DELETE FROM biblioitems");
829 $dbh->do("DELETE FROM transport_cost");
830 $dbh->do("DELETE FROM tmp_holdsqueue");
831 $dbh->do("DELETE FROM hold_fill_targets");
832
833 $dbh->do("INSERT INTO biblio (frameworkcode, author, title, datecreated) VALUES ('', 'Koha test', '$TITLE', '2011-02-01')");
834
835 $biblionumber = $dbh->selectrow_array("SELECT biblionumber FROM biblio WHERE title = '$TITLE'")
836   or BAIL_OUT("Cannot find newly created biblio record");
837
838 $dbh->do("INSERT INTO biblioitems (biblionumber, itemtype) VALUES ($biblionumber, '$itemtype')");
839
840 $biblioitemnumber =
841   $dbh->selectrow_array("SELECT biblioitemnumber FROM biblioitems WHERE biblionumber = $biblionumber")
842   or BAIL_OUT("Cannot find newly created biblioitems record");
843
844 $dbh->do("
845     INSERT INTO items (biblionumber, biblioitemnumber, homebranch, holdingbranch, notforloan, damaged, itemlost, withdrawn, onloan, itype)
846     VALUES ($biblionumber, $biblioitemnumber, '$library_A', '$library_B', 0, 0, 0, 0, NULL, '$right_itemtype')
847 ");
848
849 # With hold_fulfillment_policy = homebranch, hold should only be picked up if pickup branch = homebranch
850 $dbh->do("DELETE FROM circulation_rules");
851 Koha::CirculationRules->set_rules(
852     {
853         branchcode   => undef,
854         itemtype     => undef,
855         rules        => {
856             holdallowed             => 2,
857             hold_fulfillment_policy => 'any',
858         }
859     }
860 );
861
862 # Home branch matches pickup branch
863 $reserve_id = AddReserve(
864     {
865         branchcode     => $library_A,
866         borrowernumber => $borrowernumber,
867         biblionumber   => $biblionumber,
868         priority       => 1,
869         itemtype       => $wrong_itemtype,
870     }
871 );
872
873 C4::HoldsQueue::CreateQueue();
874 $holds_queue = $dbh->selectall_arrayref( "SELECT * FROM tmp_holdsqueue", { Slice => {} } );
875 is( @$holds_queue, 0, "Item with incorrect itemtype not targeted" );
876 Koha::Holds->find( $reserve_id )->cancel;
877
878 # Holding branch matches pickup branch
879 $reserve_id = AddReserve(
880     {
881         branchcode     => $library_A,
882         borrowernumber => $borrowernumber,
883         biblionumber   => $biblionumber,
884         priority       => 1,
885         itemtype       => $right_itemtype,
886     }
887 );
888
889 C4::HoldsQueue::CreateQueue();
890 $holds_queue = $dbh->selectall_arrayref( "SELECT * FROM tmp_holdsqueue", { Slice => {} } );
891 is( @$holds_queue, 1, "Item with matching itemtype is targeted" );
892 Koha::Holds->find( $reserve_id )->cancel;
893
894 # Neither branch matches pickup branch
895 $reserve_id = AddReserve(
896     {
897         branchcode     => $library_A,
898         borrowernumber => $borrowernumber,
899         biblionumber   => $biblionumber,
900         priority       => 1,
901     }
902 );
903
904 C4::HoldsQueue::CreateQueue();
905 $holds_queue = $dbh->selectall_arrayref( "SELECT * FROM tmp_holdsqueue", { Slice => {} } );
906 is( @$holds_queue, 1, "Item targeted when hold itemtype is not set" );
907 Koha::Holds->find( $reserve_id )->cancel;
908
909 # End testing hold itemtype limit
910
911
912 subtest "Test Local Holds Priority - Bib level" => sub {
913     plan tests => 3;
914
915     Koha::Biblios->delete();
916     t::lib::Mocks::mock_preference( 'LocalHoldsPriority', 1 );
917     t::lib::Mocks::mock_preference( 'LocalHoldsPriorityPatronControl', 'PickupLibrary' );
918     t::lib::Mocks::mock_preference( 'LocalHoldsPriorityItemControl', 'homebranch' );
919     my $branch  = $builder->build_object( { class => 'Koha::Libraries' } );
920     my $branch2 = $builder->build_object( { class => 'Koha::Libraries' } );
921     my $local_patron = $builder->build_object(
922         {
923             class => "Koha::Patrons",
924             value => {
925                 branchcode => $branch->branchcode
926             }
927         }
928     );
929     my $other_patron = $builder->build_object(
930         {
931             class => "Koha::Patrons",
932             value => {
933                 branchcode => $branch2->branchcode
934             }
935         }
936     );
937     my $biblio = $builder->build_sample_biblio();
938     my $item   = $builder->build_sample_item(
939         {
940             biblionumber  => $biblio->biblionumber,
941             library    => $branch->branchcode,
942         }
943     );
944
945     my $reserve_id = AddReserve(
946         {
947             branchcode     => $branch2->branchcode,
948             borrowernumber => $other_patron->borrowernumber,
949             biblionumber   => $biblio->biblionumber,
950             priority       => 1,
951         }
952     );
953     my $reserve_id2 = AddReserve(
954         {
955             branchcode     => $item->homebranch,
956             borrowernumber => $local_patron->borrowernumber,
957             biblionumber   => $biblio->biblionumber,
958             priority       => 2,
959         }
960     );
961
962     C4::HoldsQueue::CreateQueue();
963
964     my $queue_rs = $schema->resultset('TmpHoldsqueue');
965     my $target_rs = $schema->resultset('HoldFillTarget');
966     is( $queue_rs->count(), 1,
967         "Hold queue contains one hold" );
968     is(
969         $queue_rs->next->borrowernumber,
970         $local_patron->borrowernumber,
971         "We should pick the local hold over the next available"
972     );
973     is( $target_rs->next->reserve_id, $reserve_id2, "Reserve id correctly set in hold fill target" );
974 };
975
976 subtest "Test Local Holds Priority - Item level" => sub {
977     plan tests => 2;
978
979     Koha::Biblios->delete();
980     t::lib::Mocks::mock_preference( 'LocalHoldsPriority', 1 );
981     t::lib::Mocks::mock_preference( 'LocalHoldsPriorityPatronControl', 'PickupLibrary' );
982     t::lib::Mocks::mock_preference( 'LocalHoldsPriorityItemControl', 'homebranch' );
983     my $branch  = $builder->build_object( { class => 'Koha::Libraries' } );
984     my $branch2 = $builder->build_object( { class => 'Koha::Libraries' } );
985     my $local_patron = $builder->build_object(
986         {
987             class => "Koha::Patrons",
988             value => {
989                 branchcode => $branch->branchcode
990             }
991         }
992     );
993     my $other_patron = $builder->build_object(
994         {
995             class => "Koha::Patrons",
996             value => {
997                 branchcode => $branch2->branchcode
998             }
999         }
1000     );
1001     my $biblio = $builder->build_sample_biblio();
1002     my $item   = $builder->build_sample_item(
1003         {
1004             biblionumber  => $biblio->biblionumber,
1005             library    => $branch->branchcode,
1006         }
1007     );
1008
1009     my $reserve_id = AddReserve(
1010         {
1011             branchcode     => $branch2->branchcode,
1012             borrowernumber => $other_patron->borrowernumber,
1013             biblionumber   => $biblio->biblionumber,
1014             priority       => 1,
1015             itemnumber     => $item->id,
1016         }
1017     );
1018     my $reserve_id2 = AddReserve(
1019         {
1020             branchcode     => $item->homebranch,
1021             borrowernumber => $local_patron->borrowernumber,
1022             biblionumber   => $biblio->biblionumber,
1023             priority       => 2,
1024             itemnumber     => $item->id,
1025         }
1026     );
1027
1028     C4::HoldsQueue::CreateQueue();
1029
1030     my $queue_rs = $schema->resultset('TmpHoldsqueue');
1031     my $q = $queue_rs->next;
1032     is( $queue_rs->count(), 1,
1033         "Hold queue contains one hold" );
1034     is(
1035         $q->borrowernumber,
1036         $local_patron->borrowernumber,
1037         "We should pick the local hold over the next available"
1038     );
1039 };
1040
1041 subtest "Test Local Holds Priority - Item level hold over Record level hold (Bug 23934)" => sub {
1042     plan tests => 2;
1043
1044     Koha::Biblios->delete();
1045     t::lib::Mocks::mock_preference( 'LocalHoldsPriority', 1 );
1046     t::lib::Mocks::mock_preference( 'LocalHoldsPriorityPatronControl', 'PickupLibrary' );
1047     t::lib::Mocks::mock_preference( 'LocalHoldsPriorityItemControl', 'homebranch' );
1048     my $branch  = $builder->build_object( { class => 'Koha::Libraries' } );
1049     my $branch2 = $builder->build_object( { class => 'Koha::Libraries' } );
1050     my $local_patron = $builder->build_object(
1051         {
1052             class => "Koha::Patrons",
1053             value => {
1054                 branchcode => $branch->branchcode
1055             }
1056         }
1057     );
1058     my $other_patron = $builder->build_object(
1059         {
1060             class => "Koha::Patrons",
1061             value => {
1062                 branchcode => $branch2->branchcode
1063             }
1064         }
1065     );
1066     my $biblio = $builder->build_sample_biblio();
1067     my $item   = $builder->build_sample_item(
1068         {
1069             biblionumber  => $biblio->biblionumber,
1070             library    => $branch->branchcode,
1071         }
1072     );
1073
1074     my $reserve_id = AddReserve(
1075         {
1076             branchcode     => $branch2->branchcode,
1077             borrowernumber => $other_patron->borrowernumber,
1078             biblionumber   => $biblio->biblionumber,
1079             priority       => 1,
1080         }
1081     );
1082     my $reserve_id2 = AddReserve(
1083         {
1084             branchcode     => $item->homebranch,
1085             borrowernumber => $local_patron->borrowernumber,
1086             biblionumber   => $biblio->biblionumber,
1087             priority       => 2,
1088             itemnumber     => $item->id,
1089         }
1090     );
1091
1092     C4::HoldsQueue::CreateQueue();
1093
1094     my $queue_rs = $schema->resultset('TmpHoldsqueue');
1095     my $q = $queue_rs->next;
1096     is( $queue_rs->count(), 1,
1097         "Hold queue contains one hold" );
1098     is(
1099         $q->borrowernumber,
1100         $local_patron->borrowernumber,
1101         "We should pick the local hold over the next available"
1102     );
1103 };
1104
1105 subtest "Test Local Holds Priority - Get correct item for item level hold" => sub {
1106     plan tests => 3;
1107
1108     Koha::Biblios->delete();
1109     t::lib::Mocks::mock_preference( 'LocalHoldsPriority', 1 );
1110     t::lib::Mocks::mock_preference( 'LocalHoldsPriorityPatronControl', 'PickupLibrary' );
1111     t::lib::Mocks::mock_preference( 'LocalHoldsPriorityItemControl', 'homebranch' );
1112     my $branch  = $builder->build_object( { class => 'Koha::Libraries' } );
1113     my $branch2 = $builder->build_object( { class => 'Koha::Libraries' } );
1114     my $local_patron = $builder->build_object(
1115         {
1116             class => "Koha::Patrons",
1117             value => {
1118                 branchcode => $branch->branchcode
1119             }
1120         }
1121     );
1122     my $other_patron = $builder->build_object(
1123         {
1124             class => "Koha::Patrons",
1125             value => {
1126                 branchcode => $branch2->branchcode
1127             }
1128         }
1129     );
1130     my $biblio = $builder->build_sample_biblio();
1131
1132     my $item1 = $builder->build_sample_item(
1133         {
1134             biblionumber  => $biblio->biblionumber,
1135             library    => $branch->branchcode,
1136         }
1137     );
1138     my $item2 = $builder->build_sample_item(
1139         {
1140             biblionumber  => $biblio->biblionumber,
1141             library    => $branch->branchcode,
1142         }
1143     );
1144     my $item3 = $builder->build_sample_item(
1145         {
1146             biblionumber  => $biblio->biblionumber,
1147             library    => $branch->branchcode,
1148         }
1149     );
1150
1151     my $reserve_id2 =
1152         AddReserve(
1153             {
1154                 branchcode     => $item2->homebranch,
1155                 borrowernumber => $local_patron->borrowernumber,
1156                 biblionumber   => $biblio->biblionumber,
1157                 priority       => 2,
1158                 itemnumber     => $item2->id,
1159             }
1160         );
1161
1162
1163     C4::HoldsQueue::CreateQueue();
1164
1165     my $queue_rs = $schema->resultset('TmpHoldsqueue');
1166     my $q = $queue_rs->next;
1167     is( $queue_rs->count(), 1,
1168         "Hold queue contains one hold" );
1169     is(
1170         $q->borrowernumber,
1171         $local_patron->borrowernumber,
1172         "We should pick the local hold over the next available"
1173     );
1174     is( $q->itemnumber->id, $item2->id, "Got the correct item for item level local holds priority" );
1175 };
1176
1177 subtest "Test Local Holds Priority - Ensure no duplicate requests in holds queue (Bug 18001)" => sub {
1178     plan tests => 1;
1179
1180     $dbh->do("DELETE FROM tmp_holdsqueue");
1181     $dbh->do("DELETE FROM hold_fill_targets");
1182     $dbh->do("DELETE FROM reserves");
1183     $dbh->do("DELETE FROM circulation_rules");
1184     Koha::Biblios->delete();
1185
1186     t::lib::Mocks::mock_preference( 'LocalHoldsPriority', 1 );
1187     t::lib::Mocks::mock_preference( 'LocalHoldsPriorityPatronControl', 'PickupLibrary' );
1188     t::lib::Mocks::mock_preference( 'LocalHoldsPriorityItemControl', 'homebranch' );
1189     my $branch  = $builder->build_object( { class => 'Koha::Libraries' } );
1190     my $branch2 = $builder->build_object( { class => 'Koha::Libraries' } );
1191     my $patron  = $builder->build_object(
1192         {
1193             class => "Koha::Patrons",
1194             value => {
1195                 branchcode => $branch->branchcode
1196             }
1197         }
1198     );
1199     my $biblio = $builder->build_sample_biblio();
1200     my $item1  = $builder->build_sample_item(
1201         {
1202             biblionumber => $biblio->biblionumber,
1203             library      => $branch->branchcode,
1204         }
1205     );
1206     my $item2 = $builder->build_sample_item(
1207         {
1208             biblionumber => $biblio->biblionumber,
1209             library      => $branch->branchcode,
1210         }
1211     );
1212
1213     my $item3 = $builder->build_sample_item(
1214         {
1215             biblionumber => $biblio->biblionumber,
1216             library      => $branch->branchcode,
1217         }
1218     );
1219
1220     $reserve_id = AddReserve(
1221         {
1222             branchcode     => $item1->homebranch,
1223             borrowernumber => $patron->borrowernumber,
1224             biblionumber   => $biblio->id,
1225             priority       => 1
1226         }
1227     );
1228
1229     C4::HoldsQueue::CreateQueue();
1230
1231     my $queue_rs = $schema->resultset('TmpHoldsqueue');
1232
1233     is( $queue_rs->count(), 1,
1234         "Hold queue contains one hold from chosen from three possible items" );
1235 };
1236
1237
1238 subtest "Item level holds info is preserved (Bug 25738)" => sub {
1239
1240     plan tests => 4;
1241
1242     $dbh->do("DELETE FROM tmp_holdsqueue");
1243     $dbh->do("DELETE FROM hold_fill_targets");
1244     $dbh->do("DELETE FROM reserves");
1245     $dbh->do("DELETE FROM circulation_rules");
1246
1247     my $library  = $builder->build_object({ class => 'Koha::Libraries' });
1248     my $patron_1 = $builder->build_object(
1249         {
1250             class => "Koha::Patrons",
1251             value => {
1252                 branchcode => $library->branchcode
1253             }
1254         }
1255     );
1256
1257     my $patron_2 = $builder->build_object(
1258         {
1259             class => "Koha::Patrons",
1260             value => {
1261                 branchcode => $library->branchcode
1262             }
1263         }
1264     );
1265
1266     my $biblio = $builder->build_sample_biblio();
1267     my $item_1 = $builder->build_sample_item(
1268         {
1269             biblionumber => $biblio->biblionumber,
1270             library      => $library->branchcode,
1271         }
1272     );
1273     my $item_2 = $builder->build_sample_item(
1274         {
1275             biblionumber => $biblio->biblionumber,
1276             library      => $library->branchcode,
1277         }
1278     );
1279
1280     # Add item-level hold for patron_1
1281     my $reserve_id_1 = AddReserve(
1282         {
1283             branchcode     => $library->branchcode,
1284             borrowernumber => $patron_1->borrowernumber,
1285             biblionumber   => $biblio->id,
1286             itemnumber     => $item_1->itemnumber,
1287             priority       => 1
1288         }
1289     );
1290
1291     my $reserve_id_2 = AddReserve(
1292         {
1293             branchcode     => $library->branchcode,
1294             borrowernumber => $patron_2->borrowernumber,
1295             biblionumber   => $biblio->id,
1296             priority       => 2
1297         }
1298     );
1299
1300     C4::HoldsQueue::CreateQueue();
1301
1302     my $queue_rs = $schema->resultset('TmpHoldsqueue');
1303
1304     is( $queue_rs->count(), 2, "Hold queue contains two holds" );
1305
1306     my $queue_line_1 = $queue_rs->next;
1307     is( $queue_line_1->item_level_request, 1, 'Request is correctly advertised as item-level' );
1308     my $target_rs = $schema->resultset('HoldFillTarget')->search({borrowernumber=>$patron_1->borrowernumber});;
1309     is( $target_rs->next->reserve_id, $reserve_id_1, "Reserve id correctly set in hold fill target for item level hold" );
1310
1311     my $queue_line_2 = $queue_rs->next;
1312     is( $queue_line_2->item_level_request, 0, 'Request is correctly advertised as biblio-level' );
1313
1314 };
1315
1316 subtest 'Trivial test for UpdateTransportCostMatrix' => sub {
1317     plan tests => 1;
1318     my $recs = [
1319         { frombranch => $library1->{branchcode}, tobranch => $library2->{branchcode}, cost => 1, disable_transfer => 0 },
1320         { frombranch => $library2->{branchcode}, tobranch => $library3->{branchcode}, cost => 0, disable_transfer => 1 },
1321     ];
1322     C4::HoldsQueue::UpdateTransportCostMatrix( $recs );
1323     is( $schema->resultset('TransportCost')->count, 2, 'UpdateTransportCostMatrix added two records' );
1324 };
1325
1326 # Cleanup
1327 $schema->storage->txn_rollback;
1328
1329 ### END Test holds queue builder does not violate holds policy ###
1330
1331 sub test_queue {
1332     my ($test_name, $use_cost_matrix, $pick_branch, $hold_branch) = @_;
1333
1334     $test_name = "$test_name (".($use_cost_matrix ? "" : "don't ")."use cost matrix)";
1335
1336     $use_cost_matrix_sth->execute($use_cost_matrix);
1337     C4::Context->clear_syspref_cache();
1338     C4::HoldsQueue::CreateQueue();
1339
1340     my $results = $dbh->selectall_arrayref($test_sth, { Slice => {} }); # should be only one
1341     my $r = $results->[0];
1342
1343     my $ok = is( $r->{pickbranch}, $pick_branch, "$test_name pick up branch");
1344     $ok &&=  is( $r->{holdingbranch}, $hold_branch, "$test_name holding branch")
1345       if $hold_branch;
1346
1347     diag( "Wrong pick-up/hold for first target (pick_branch, hold_branch, reserves, hold_fill_targets, tmp_holdsqueue): "
1348         . Dumper ($pick_branch, $hold_branch, map dump_records($_), qw(reserves hold_fill_targets tmp_holdsqueue)) )
1349       unless $ok;
1350
1351     # Test enforcement of branch transfer limit
1352     if ( $r->{pickbranch} ne $r->{holdingbranch} ) {
1353         t::lib::Mocks::mock_preference( 'UseBranchTransferLimits', '1' );
1354         my $limit = Koha::Item::Transfer::Limit->new(
1355             {
1356                 toBranch   => $r->{pickbranch},
1357                 fromBranch => $r->{holdingbranch},
1358                 itemtype   => $r->{itype},
1359             }
1360         )->store();
1361         C4::Context->clear_syspref_cache();
1362         C4::HoldsQueue::CreateQueue();
1363         $results = $dbh->selectall_arrayref( $test_sth, { Slice => {} } )
1364           ;    # should be only one
1365         my $s = $results->[0];
1366         isnt( $r->{holdingbranch}, $s->{holdingbranch}, 'Hold is not trapped for pickup at a branch that cannot be transferred to');
1367
1368         $limit->delete();
1369         t::lib::Mocks::mock_preference( 'UseBranchTransferLimits', '0' );
1370         C4::Context->clear_syspref_cache();
1371         C4::HoldsQueue::CreateQueue();
1372     }
1373
1374 }
1375
1376 subtest "Test _checkHoldPolicy" => sub {
1377     plan tests => 25;
1378
1379     my $library1  = $builder->build_object( { class => 'Koha::Libraries' } );
1380     my $library2  = $builder->build_object( { class => 'Koha::Libraries' } );
1381     my $library_nongroup = $builder->build_object( { class => 'Koha::Libraries' } );
1382     my $category = $builder->build_object( { class => 'Koha::Patron::Categories', value => {exclude_from_local_holds_priority => 0} });
1383     my $patron  = $builder->build_object(
1384         {
1385             class => "Koha::Patrons",
1386             value => {
1387                 branchcode => $library1->branchcode,
1388                 categorycode => $category->categorycode,
1389             }
1390         }
1391     );
1392     my $biblio = $builder->build_sample_biblio();
1393     my $item1  = $builder->build_sample_item(
1394         {
1395             biblionumber => $biblio->biblionumber,
1396             library      => $library1->branchcode,
1397             exclude_from_local_holds_priority => 0,
1398         }
1399     );
1400
1401     $reserve_id = AddReserve(
1402         {
1403             branchcode     => $item1->homebranch,
1404             borrowernumber => $patron->borrowernumber,
1405             biblionumber   => $biblio->id,
1406             priority       => 1
1407         }
1408     );
1409     ok( $reserve_id, "Hold was created");
1410     my $requests = C4::HoldsQueue::GetPendingHoldRequestsForBib($biblio->biblionumber);
1411     is( @$requests, 1, "Got correct number of holds");
1412
1413     my $request = $requests->[0];
1414     is( $request->{biblionumber}, $biblio->id, "Hold has correct biblio");
1415     is( $request->{borrowernumber}, $patron->id, "Hold has correct borrower");
1416     is( $request->{borrowerbranch}, $patron->branchcode, "Hold has correct borrowerbranch");
1417
1418     my $hold = Koha::Holds->find( $reserve_id );
1419     ok( $hold, "Found hold" );
1420
1421     my $item = {
1422         holdallowed              => 1,
1423         homebranch               => $request->{borrowerbranch}, # library1
1424         hold_fulfillment_policy  => 'any'
1425     };
1426
1427     # Base case should work
1428     is( C4::HoldsQueue::_checkHoldPolicy( $item, $request ), 1, "_checkHoldPolicy returns true" );
1429
1430     # Test holdallowed = 0
1431     $item->{holdallowed} = 0;
1432     is( C4::HoldsQueue::_checkHoldPolicy( $item, $request ), 0, "_checkHoldPolicy returns false if holdallowed = 0" );
1433
1434     # Test holdallowed = 1
1435     $item->{holdallowed} = 1;
1436     $item->{homebranch} = $library_nongroup->id;
1437     is( C4::HoldsQueue::_checkHoldPolicy( $item, $request ), 0, "_checkHoldPolicy returns false if holdallowed = 1 and branches do not match" );
1438
1439     $item->{homebranch} = $request->{borrowerbranch};
1440     is( C4::HoldsQueue::_checkHoldPolicy( $item, $request ), 1, "_checkHoldPolicy returns true if holdallowed = 1 and branches do match" );
1441
1442     # Test holdallowed = 3
1443     $item->{holdallowed} = 3;
1444     $item->{homebranch} = $library_nongroup->id;
1445     is( C4::HoldsQueue::_checkHoldPolicy( $item, $request ), 0, "_checkHoldPolicy returns false if branchode doesn't match, holdallowed = 3 and no group branches exist" );
1446     $item->{homebranch} = $request->{borrowerbranch};
1447     is( C4::HoldsQueue::_checkHoldPolicy( $item, $request ), 1, "_checkHoldPolicy returns true if branchode matches, holdallowed = 3 and no group branches exist" );
1448
1449     # Create library groups hierarchy
1450     my $rootgroup = $builder->build_object( { class => 'Koha::Library::Groups', value => {ft_local_hold_group => 1} } );
1451     my $group1 = $builder->build_object( { class => 'Koha::Library::Groups', value => {parent_id => $rootgroup->id, branchcode => $library1->branchcode}} );
1452     my $group2 = $builder->build_object( { class => 'Koha::Library::Groups', value => {parent_id => $rootgroup->id, branchcode => $library2->branchcode}} );
1453
1454     is( C4::HoldsQueue::_checkHoldPolicy( $item, $request ), 1, "_checkHoldPolicy returns true if holdallowed = 3 and no group branches exist" );
1455
1456     $group1->delete;
1457
1458     # Test hold_fulfillment_policy = holdgroup
1459     $item->{hold_fulfillment_policy} = 'holdgroup';
1460     $item->{homebranch} = $library_nongroup->id;
1461     is( C4::HoldsQueue::_checkHoldPolicy( $item, $request ), 0, "_checkHoldPolicy returns true if library is not part of hold group, branches don't match and hfp = holdgroup" );
1462     $item->{homebranch} = $request->{borrowerbranch};
1463     is( C4::HoldsQueue::_checkHoldPolicy( $item, $request ), 1, "_checkHoldPolicy returns true if library is not part of hold group, branches match and hfp = holdgroup" );
1464
1465     $group1 = $builder->build_object( { class => 'Koha::Library::Groups', value => {parent_id => $rootgroup->id, branchcode => $library1->branchcode}} );
1466     is( C4::HoldsQueue::_checkHoldPolicy( $item, $request ), 1, "_checkHoldPolicy returns true if library is part of hold group with hfp = holdgroup" );
1467
1468     $item->{homebranch} = $library2->id;
1469     is( C4::HoldsQueue::_checkHoldPolicy( $item, $request ), 1, "_checkHoldPolicy returns true if library is part of hold group with hfp = holdgroup" );
1470     $item->{homebranch} = $library1->id;
1471
1472     $group1->delete;
1473
1474     # Test hold_fulfillment_policy = homebranch
1475     $item->{hold_fulfillment_policy} = 'homebranch';
1476     $item->{homebranch} = $library_nongroup->id;
1477     is( C4::HoldsQueue::_checkHoldPolicy( $item, $request ), 0, "_checkHoldPolicy returns false if hfp = homebranch and pickup branch != item homebranch" );
1478
1479     $item->{homebranch} = $request->{borrowerbranch};
1480     is( C4::HoldsQueue::_checkHoldPolicy( $item, $request ), 1, "_checkHoldPolicy returns true if hfp = homebranch and pickup branch = item homebranch" );
1481
1482     # Test hold_fulfillment_policy = holdingbranch
1483     $item->{hold_fulfillment_policy} = 'holdingbranch';
1484     $item->{holdingbranch} = $library_nongroup->id;
1485     is( C4::HoldsQueue::_checkHoldPolicy( $item, $request ), 0, "_checkHoldPolicy returns false if hfp = holdingbranch and pickup branch != item holdingbranch" );
1486
1487     $item->{holdingbranch} = $request->{borrowerbranch};
1488     is( C4::HoldsQueue::_checkHoldPolicy( $item, $request ), 1, "_checkHoldPolicy returns true if hfp = holdingbranch and pickup branch = item holdingbranch" );
1489
1490     # Test hold_fulfillment_policy = patrongroup
1491     $item->{hold_fulfillment_policy} = 'patrongroup';
1492     $item->{borrowerbranch} = $library1->id;
1493
1494     $item->{homebranch} = $library_nongroup->id;
1495     is( C4::HoldsQueue::_checkHoldPolicy( $item, $request ), 0, "_checkHoldPolicy returns false if library is not part of hold group, branches don't match, hfp = patrongroup" );
1496     $item->{homebranch} = $request->{borrowerbranch};
1497     is( C4::HoldsQueue::_checkHoldPolicy( $item, $request ), 1, "_checkHoldPolicy returns false if library is not part of hold group, branches match, hfp = patrongroup" );
1498
1499     $group1 = $builder->build_object( { class => 'Koha::Library::Groups', value => {parent_id => $rootgroup->id, branchcode => $library1->branchcode}} );
1500     is( C4::HoldsQueue::_checkHoldPolicy( $item, $request ), 1, "_checkHoldPolicy returns true if library is part of hold group with hfp = holdgroup" );
1501
1502     $item->{borrowerbranch} = $library2->id;
1503     is( C4::HoldsQueue::_checkHoldPolicy( $item, $request ), 1, "_checkHoldPolicy returns true if library is part of hold group with hfp = holdgroup" );
1504     $item->{borrowerbranch} = $library1->id;
1505 };
1506
1507 sub dump_records {
1508     my ($tablename) = @_;
1509     return $dbh->selectall_arrayref("SELECT * from $tablename where borrowernumber = ?", { Slice => {} }, $borrowernumber);
1510 }