Bug 25786: Holds Queue building may target the wrong item for item level requests...
[koha-equinox.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 => 53;
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 Koha::Holds->find( $reserve_id )->cancel;
666
667 # Holding branch matches pickup branch
668 $reserve_id = AddReserve(
669     {
670         branchcode     => $library_B,
671         borrowernumber => $borrowernumber,
672         biblionumber   => $biblionumber,
673         priority       => 1,
674     }
675 );
676
677
678 C4::HoldsQueue::CreateQueue();
679 $holds_queue = $dbh->selectall_arrayref( "SELECT * FROM tmp_holdsqueue", { Slice => {} } );
680 is( @$holds_queue, 0, "Hold where pickup ne home, pickup eq home not targeted" );
681 Koha::Holds->find( $reserve_id )->cancel;
682
683 # Neither branch matches pickup branch
684 $reserve_id = AddReserve(
685     {
686         branchcode     => $library_C,
687         borrowernumber => $borrowernumber,
688         biblionumber   => $biblionumber,
689         priority       => 1,
690     }
691 );
692
693 C4::HoldsQueue::CreateQueue();
694 $holds_queue = $dbh->selectall_arrayref( "SELECT * FROM tmp_holdsqueue", { Slice => {} } );
695 is( @$holds_queue, 0, "Hold where pickup ne home, pickup ne holding not targeted" );
696 Koha::Holds->find( $reserve_id )->cancel;
697
698 # With hold_fulfillment_policy = holdingbranch, hold should only be picked up if pickup branch = holdingbranch
699 $dbh->do("DELETE FROM circulation_rules");
700 Koha::CirculationRules->set_rules(
701     {
702         branchcode   => undef,
703         itemtype     => undef,
704         rules        => {
705             holdallowed             => 2,
706             hold_fulfillment_policy => 'holdingbranch',
707         }
708     }
709 );
710
711 # Home branch matches pickup branch
712 $reserve_id = AddReserve(
713     {
714         branchcode     => $library_A,
715         borrowernumber => $borrowernumber,
716         biblionumber   => $biblionumber,
717         priority       => 1,
718     }
719 );
720
721 C4::HoldsQueue::CreateQueue();
722 $holds_queue = $dbh->selectall_arrayref( "SELECT * FROM tmp_holdsqueue", { Slice => {} } );
723 is( @$holds_queue, 0, "Hold where pickup eq home, pickup ne holding not targeted" );
724 Koha::Holds->find( $reserve_id )->cancel;
725
726 # Holding branch matches pickup branch
727 $reserve_id = AddReserve(
728     {
729         branchcode     => $library_B,
730         borrowernumber => $borrowernumber,
731         biblionumber   => $biblionumber,
732         priority       => 1,
733     }
734 );
735
736 C4::HoldsQueue::CreateQueue();
737 $holds_queue = $dbh->selectall_arrayref( "SELECT * FROM tmp_holdsqueue", { Slice => {} } );
738 is( @$holds_queue, 1, "Hold where pickup ne home, pickup eq holding targeted" );
739 Koha::Holds->find( $reserve_id )->cancel;
740
741 # Neither branch matches pickup branch
742 $reserve_id = AddReserve(
743     {
744         branchcode     => $library_C,
745         borrowernumber => $borrowernumber,
746         biblionumber   => $biblionumber,
747         priority       => 1,
748     }
749 );
750
751 C4::HoldsQueue::CreateQueue();
752 $holds_queue = $dbh->selectall_arrayref( "SELECT * FROM tmp_holdsqueue", { Slice => {} } );
753 is( @$holds_queue, 0, "Hold where pickup ne home, pickup ne holding not targeted" );
754 Koha::Holds->find( $reserve_id )->cancel;
755
756 # With hold_fulfillment_policy = any, hold should be pikcup up reguardless of matching home or holding branch
757 $dbh->do("DELETE FROM circulation_rules");
758 Koha::CirculationRules->set_rules(
759     {
760         branchcode   => undef,
761         itemtype     => undef,
762         rules        => {
763             holdallowed             => 2,
764             hold_fulfillment_policy => 'any',
765         }
766     }
767 );
768
769 # Home branch matches pickup branch
770 $reserve_id = AddReserve(
771     {
772         branchcode     => $library_A,
773         borrowernumber => $borrowernumber,
774         biblionumber   => $biblionumber,
775         priority       => 1,
776     }
777 );
778
779 C4::HoldsQueue::CreateQueue();
780 $holds_queue = $dbh->selectall_arrayref( "SELECT * FROM tmp_holdsqueue", { Slice => {} } );
781 is( @$holds_queue, 1, "Hold where pickup eq home, pickup ne holding targeted" );
782 Koha::Holds->find( $reserve_id )->cancel;
783
784 # Holding branch matches pickup branch
785 $reserve_id = AddReserve(
786     {
787         branchcode     => $library_B,
788         borrowernumber => $borrowernumber,
789         biblionumber   => $biblionumber,
790         priority       => 1,
791     }
792 );
793
794 C4::HoldsQueue::CreateQueue();
795 $holds_queue = $dbh->selectall_arrayref( "SELECT * FROM tmp_holdsqueue", { Slice => {} } );
796 is( @$holds_queue, 1, "Hold where pickup ne home, pickup eq holding targeted" );
797 Koha::Holds->find( $reserve_id )->cancel;
798
799 # Neither branch matches pickup branch
800 $reserve_id = AddReserve(
801     {
802         branchcode     => $library_C,
803         borrowernumber => $borrowernumber,
804         biblionumber   => $biblionumber,
805         priority       => 1,
806     }
807 );
808
809 C4::HoldsQueue::CreateQueue();
810 $holds_queue = $dbh->selectall_arrayref( "SELECT * FROM tmp_holdsqueue", { Slice => {} } );
811 is( @$holds_queue, 1, "Hold where pickup ne home, pickup ne holding targeted" );
812 Koha::Holds->find( $reserve_id )->cancel;
813
814 # End testing hold_fulfillment_policy
815
816 # Test hold itemtype limit
817 t::lib::Mocks::mock_preference( "UseTransportCostMatrix", 0 );
818 my $wrong_itemtype = $builder->build({ source => 'Itemtype', value => { notforloan => 0 } })->{itemtype};
819 my $right_itemtype = $builder->build({ source => 'Itemtype', value => { notforloan => 0 } })->{itemtype};
820 $borrowernumber = $borrower3->{borrowernumber};
821 my $branchcode = $library1->{branchcode};
822 $dbh->do("DELETE FROM reserves");
823 $dbh->do("DELETE FROM issues");
824 $dbh->do("DELETE FROM items");
825 $dbh->do("DELETE FROM biblio");
826 $dbh->do("DELETE FROM biblioitems");
827 $dbh->do("DELETE FROM transport_cost");
828 $dbh->do("DELETE FROM tmp_holdsqueue");
829 $dbh->do("DELETE FROM hold_fill_targets");
830
831 $dbh->do("INSERT INTO biblio (frameworkcode, author, title, datecreated) VALUES ('', 'Koha test', '$TITLE', '2011-02-01')");
832
833 $biblionumber = $dbh->selectrow_array("SELECT biblionumber FROM biblio WHERE title = '$TITLE'")
834   or BAIL_OUT("Cannot find newly created biblio record");
835
836 $dbh->do("INSERT INTO biblioitems (biblionumber, itemtype) VALUES ($biblionumber, '$itemtype')");
837
838 $biblioitemnumber =
839   $dbh->selectrow_array("SELECT biblioitemnumber FROM biblioitems WHERE biblionumber = $biblionumber")
840   or BAIL_OUT("Cannot find newly created biblioitems record");
841
842 $dbh->do("
843     INSERT INTO items (biblionumber, biblioitemnumber, homebranch, holdingbranch, notforloan, damaged, itemlost, withdrawn, onloan, itype)
844     VALUES ($biblionumber, $biblioitemnumber, '$library_A', '$library_B', 0, 0, 0, 0, NULL, '$right_itemtype')
845 ");
846
847 # With hold_fulfillment_policy = homebranch, hold should only be picked up if pickup branch = homebranch
848 $dbh->do("DELETE FROM circulation_rules");
849 Koha::CirculationRules->set_rules(
850     {
851         branchcode   => undef,
852         itemtype     => undef,
853         rules        => {
854             holdallowed             => 2,
855             hold_fulfillment_policy => 'any',
856         }
857     }
858 );
859
860 # Home branch matches pickup branch
861 $reserve_id = AddReserve(
862     {
863         branchcode     => $library_A,
864         borrowernumber => $borrowernumber,
865         biblionumber   => $biblionumber,
866         priority       => 1,
867         itemtype       => $wrong_itemtype,
868     }
869 );
870
871 C4::HoldsQueue::CreateQueue();
872 $holds_queue = $dbh->selectall_arrayref( "SELECT * FROM tmp_holdsqueue", { Slice => {} } );
873 is( @$holds_queue, 0, "Item with incorrect itemtype not targeted" );
874 Koha::Holds->find( $reserve_id )->cancel;
875
876 # Holding branch matches pickup branch
877 $reserve_id = AddReserve(
878     {
879         branchcode     => $library_A,
880         borrowernumber => $borrowernumber,
881         biblionumber   => $biblionumber,
882         priority       => 1,
883         itemtype       => $right_itemtype,
884     }
885 );
886
887 C4::HoldsQueue::CreateQueue();
888 $holds_queue = $dbh->selectall_arrayref( "SELECT * FROM tmp_holdsqueue", { Slice => {} } );
889 is( @$holds_queue, 1, "Item with matching itemtype is targeted" );
890 Koha::Holds->find( $reserve_id )->cancel;
891
892 # Neither branch matches pickup branch
893 $reserve_id = AddReserve(
894     {
895         branchcode     => $library_A,
896         borrowernumber => $borrowernumber,
897         biblionumber   => $biblionumber,
898         priority       => 1,
899     }
900 );
901
902 C4::HoldsQueue::CreateQueue();
903 $holds_queue = $dbh->selectall_arrayref( "SELECT * FROM tmp_holdsqueue", { Slice => {} } );
904 is( @$holds_queue, 1, "Item targeted when hold itemtype is not set" );
905 Koha::Holds->find( $reserve_id )->cancel;
906
907 # End testing hold itemtype limit
908
909
910 subtest "Test Local Holds Priority - Bib level" => sub {
911     plan tests => 2;
912
913     Koha::Biblios->delete();
914     t::lib::Mocks::mock_preference( 'LocalHoldsPriority', 1 );
915     t::lib::Mocks::mock_preference( 'LocalHoldsPriorityPatronControl', 'PickupLibrary' );
916     t::lib::Mocks::mock_preference( 'LocalHoldsPriorityItemControl', 'homebranch' );
917     my $branch  = $builder->build_object( { class => 'Koha::Libraries' } );
918     my $branch2 = $builder->build_object( { class => 'Koha::Libraries' } );
919     my $local_patron = $builder->build_object(
920         {
921             class => "Koha::Patrons",
922             value => {
923                 branchcode => $branch->branchcode
924             }
925         }
926     );
927     my $other_patron = $builder->build_object(
928         {
929             class => "Koha::Patrons",
930             value => {
931                 branchcode => $branch2->branchcode
932             }
933         }
934     );
935     my $biblio = $builder->build_sample_biblio();
936     my $item   = $builder->build_sample_item(
937         {
938             biblionumber  => $biblio->biblionumber,
939             library    => $branch->branchcode,
940         }
941     );
942
943     my $reserve_id = AddReserve(
944         {
945             branchcode     => $branch2->branchcode,
946             borrowernumber => $other_patron->borrowernumber,
947             biblionumber   => $biblio->biblionumber,
948             priority       => 1,
949         }
950     );
951     my $reserve_id2 = AddReserve(
952         {
953             branchcode     => $item->homebranch,
954             borrowernumber => $local_patron->borrowernumber,
955             biblionumber   => $biblio->biblionumber,
956             priority       => 2,
957         }
958     );
959
960     C4::HoldsQueue::CreateQueue();
961
962     my $queue_rs = $schema->resultset('TmpHoldsqueue');
963     is( $queue_rs->count(), 1,
964         "Hold queue contains one hold" );
965     is(
966         $queue_rs->next->borrowernumber,
967         $local_patron->borrowernumber,
968         "We should pick the local hold over the next available"
969     );
970 };
971
972 subtest "Test Local Holds Priority - Item level" => sub {
973     plan tests => 2;
974
975     Koha::Biblios->delete();
976     t::lib::Mocks::mock_preference( 'LocalHoldsPriority', 1 );
977     t::lib::Mocks::mock_preference( 'LocalHoldsPriorityPatronControl', 'PickupLibrary' );
978     t::lib::Mocks::mock_preference( 'LocalHoldsPriorityItemControl', 'homebranch' );
979     my $branch  = $builder->build_object( { class => 'Koha::Libraries' } );
980     my $branch2 = $builder->build_object( { class => 'Koha::Libraries' } );
981     my $local_patron = $builder->build_object(
982         {
983             class => "Koha::Patrons",
984             value => {
985                 branchcode => $branch->branchcode
986             }
987         }
988     );
989     my $other_patron = $builder->build_object(
990         {
991             class => "Koha::Patrons",
992             value => {
993                 branchcode => $branch2->branchcode
994             }
995         }
996     );
997     my $biblio = $builder->build_sample_biblio();
998     my $item   = $builder->build_sample_item(
999         {
1000             biblionumber  => $biblio->biblionumber,
1001             library    => $branch->branchcode,
1002         }
1003     );
1004
1005     my $reserve_id = AddReserve(
1006         {
1007             branchcode     => $branch2->branchcode,
1008             borrowernumber => $other_patron->borrowernumber,
1009             biblionumber   => $biblio->biblionumber,
1010             priority       => 1,
1011             itemnumber     => $item->id,
1012         }
1013     );
1014     my $reserve_id2 = AddReserve(
1015         {
1016             branchcode     => $item->homebranch,
1017             borrowernumber => $local_patron->borrowernumber,
1018             biblionumber   => $biblio->biblionumber,
1019             priority       => 2,
1020             itemnumber     => $item->id,
1021         }
1022     );
1023
1024     C4::HoldsQueue::CreateQueue();
1025
1026     my $queue_rs = $schema->resultset('TmpHoldsqueue');
1027     my $q = $queue_rs->next;
1028     is( $queue_rs->count(), 1,
1029         "Hold queue contains one hold" );
1030     is(
1031         $q->borrowernumber,
1032         $local_patron->borrowernumber,
1033         "We should pick the local hold over the next available"
1034     );
1035 };
1036
1037 subtest "Test Local Holds Priority - Item level hold over Record level hold (Bug 23934)" => sub {
1038     plan tests => 2;
1039
1040     Koha::Biblios->delete();
1041     t::lib::Mocks::mock_preference( 'LocalHoldsPriority', 1 );
1042     t::lib::Mocks::mock_preference( 'LocalHoldsPriorityPatronControl', 'PickupLibrary' );
1043     t::lib::Mocks::mock_preference( 'LocalHoldsPriorityItemControl', 'homebranch' );
1044     my $branch  = $builder->build_object( { class => 'Koha::Libraries' } );
1045     my $branch2 = $builder->build_object( { class => 'Koha::Libraries' } );
1046     my $local_patron = $builder->build_object(
1047         {
1048             class => "Koha::Patrons",
1049             value => {
1050                 branchcode => $branch->branchcode
1051             }
1052         }
1053     );
1054     my $other_patron = $builder->build_object(
1055         {
1056             class => "Koha::Patrons",
1057             value => {
1058                 branchcode => $branch2->branchcode
1059             }
1060         }
1061     );
1062     my $biblio = $builder->build_sample_biblio();
1063     my $item   = $builder->build_sample_item(
1064         {
1065             biblionumber  => $biblio->biblionumber,
1066             library    => $branch->branchcode,
1067         }
1068     );
1069
1070     my $reserve_id = AddReserve(
1071         {
1072             branchcode     => $branch2->branchcode,
1073             borrowernumber => $other_patron->borrowernumber,
1074             biblionumber   => $biblio->biblionumber,
1075             priority       => 1,
1076         }
1077     );
1078     my $reserve_id2 = AddReserve(
1079         {
1080             branchcode     => $item->homebranch,
1081             borrowernumber => $local_patron->borrowernumber,
1082             biblionumber   => $biblio->biblionumber,
1083             priority       => 2,
1084             itemnumber     => $item->id,
1085         }
1086     );
1087
1088     C4::HoldsQueue::CreateQueue();
1089
1090     my $queue_rs = $schema->resultset('TmpHoldsqueue');
1091     my $q = $queue_rs->next;
1092     is( $queue_rs->count(), 1,
1093         "Hold queue contains one hold" );
1094     is(
1095         $q->borrowernumber,
1096         $local_patron->borrowernumber,
1097         "We should pick the local hold over the next available"
1098     );
1099 };
1100
1101 subtest "Test Local Holds Priority - Get correct item for item level hold" => sub {
1102     plan tests => 3;
1103
1104     Koha::Biblios->delete();
1105     t::lib::Mocks::mock_preference( 'LocalHoldsPriority', 1 );
1106     t::lib::Mocks::mock_preference( 'LocalHoldsPriorityPatronControl', 'PickupLibrary' );
1107     t::lib::Mocks::mock_preference( 'LocalHoldsPriorityItemControl', 'homebranch' );
1108     my $branch  = $builder->build_object( { class => 'Koha::Libraries' } );
1109     my $branch2 = $builder->build_object( { class => 'Koha::Libraries' } );
1110     my $local_patron = $builder->build_object(
1111         {
1112             class => "Koha::Patrons",
1113             value => {
1114                 branchcode => $branch->branchcode
1115             }
1116         }
1117     );
1118     my $other_patron = $builder->build_object(
1119         {
1120             class => "Koha::Patrons",
1121             value => {
1122                 branchcode => $branch2->branchcode
1123             }
1124         }
1125     );
1126     my $biblio = $builder->build_sample_biblio();
1127
1128     my $item1 = $builder->build_sample_item(
1129         {
1130             biblionumber  => $biblio->biblionumber,
1131             library    => $branch->branchcode,
1132         }
1133     );
1134     my $item2 = $builder->build_sample_item(
1135         {
1136             biblionumber  => $biblio->biblionumber,
1137             library    => $branch->branchcode,
1138         }
1139     );
1140     my $item3 = $builder->build_sample_item(
1141         {
1142             biblionumber  => $biblio->biblionumber,
1143             library    => $branch->branchcode,
1144         }
1145     );
1146
1147     my $reserve_id2 =
1148         AddReserve( $item2->homebranch, $local_patron->borrowernumber,
1149             $biblio->biblionumber, '', 2, undef, undef, undef, undef, $item2->id, undef, undef );
1150
1151     C4::HoldsQueue::CreateQueue();
1152
1153     my $queue_rs = $schema->resultset('TmpHoldsqueue');
1154     my $q = $queue_rs->next;
1155     is( $queue_rs->count(), 1,
1156         "Hold queue contains one hold" );
1157     is(
1158         $q->borrowernumber,
1159         $local_patron->borrowernumber,
1160         "We should pick the local hold over the next available"
1161     );
1162     is( $q->itemnumber->id, $item2->id, "Got the correct item for item level local holds priority" );
1163 };
1164
1165 subtest "Test Local Holds Priority - Ensure no duplicate requests in holds queue (Bug 18001)" => sub {
1166     plan tests => 1;
1167
1168     $dbh->do("DELETE FROM tmp_holdsqueue");
1169     $dbh->do("DELETE FROM hold_fill_targets");
1170     $dbh->do("DELETE FROM reserves");
1171     $dbh->do("DELETE FROM circulation_rules");
1172     Koha::Biblios->delete();
1173
1174     t::lib::Mocks::mock_preference( 'LocalHoldsPriority', 1 );
1175     t::lib::Mocks::mock_preference( 'LocalHoldsPriorityPatronControl', 'PickupLibrary' );
1176     t::lib::Mocks::mock_preference( 'LocalHoldsPriorityItemControl', 'homebranch' );
1177     my $branch  = $builder->build_object( { class => 'Koha::Libraries' } );
1178     my $branch2 = $builder->build_object( { class => 'Koha::Libraries' } );
1179     my $patron  = $builder->build_object(
1180         {
1181             class => "Koha::Patrons",
1182             value => {
1183                 branchcode => $branch->branchcode
1184             }
1185         }
1186     );
1187     my $biblio = $builder->build_sample_biblio();
1188     my $item1  = $builder->build_sample_item(
1189         {
1190             biblionumber => $biblio->biblionumber,
1191             library      => $branch->branchcode,
1192         }
1193     );
1194     my $item2 = $builder->build_sample_item(
1195         {
1196             biblionumber => $biblio->biblionumber,
1197             library      => $branch->branchcode,
1198         }
1199     );
1200
1201     my $item3 = $builder->build_sample_item(
1202         {
1203             biblionumber => $biblio->biblionumber,
1204             library      => $branch->branchcode,
1205         }
1206     );
1207
1208     $reserve_id = AddReserve(
1209         {
1210             branchcode     => $item1->homebranch,
1211             borrowernumber => $patron->borrowernumber,
1212             biblionumber   => $biblio->id,
1213             priority       => 1
1214         }
1215     );
1216
1217     C4::HoldsQueue::CreateQueue();
1218
1219     my $queue_rs = $schema->resultset('TmpHoldsqueue');
1220
1221     is( $queue_rs->count(), 1,
1222         "Hold queue contains one hold from chosen from three possible items" );
1223 };
1224
1225
1226 subtest "Item level holds info is preserved (Bug 25738)" => sub {
1227
1228     plan tests => 3;
1229
1230     $dbh->do("DELETE FROM tmp_holdsqueue");
1231     $dbh->do("DELETE FROM hold_fill_targets");
1232     $dbh->do("DELETE FROM reserves");
1233     $dbh->do("DELETE FROM circulation_rules");
1234
1235     my $library  = $builder->build_object({ class => 'Koha::Libraries' });
1236     my $patron_1 = $builder->build_object(
1237         {
1238             class => "Koha::Patrons",
1239             value => {
1240                 branchcode => $library->branchcode
1241             }
1242         }
1243     );
1244
1245     my $patron_2 = $builder->build_object(
1246         {
1247             class => "Koha::Patrons",
1248             value => {
1249                 branchcode => $library->branchcode
1250             }
1251         }
1252     );
1253
1254     my $biblio = $builder->build_sample_biblio();
1255     my $item_1 = $builder->build_sample_item(
1256         {
1257             biblionumber => $biblio->biblionumber,
1258             library      => $library->branchcode,
1259         }
1260     );
1261     my $item_2 = $builder->build_sample_item(
1262         {
1263             biblionumber => $biblio->biblionumber,
1264             library      => $library->branchcode,
1265         }
1266     );
1267
1268     # Add item-level hold for patron_1
1269     my $reserve_id_1 = AddReserve(
1270         {
1271             branchcode     => $library->branchcode,
1272             borrowernumber => $patron_1->borrowernumber,
1273             biblionumber   => $biblio->id,
1274             itemnumber     => $item_1->itemnumber,
1275             priority       => 1
1276         }
1277     );
1278
1279     my $reserve_id_2 = AddReserve(
1280         {
1281             branchcode     => $library->branchcode,
1282             borrowernumber => $patron_2->borrowernumber,
1283             biblionumber   => $biblio->id,
1284             priority       => 2
1285         }
1286     );
1287
1288     C4::HoldsQueue::CreateQueue();
1289
1290     my $queue_rs = $schema->resultset('TmpHoldsqueue');
1291
1292     is( $queue_rs->count(), 2, "Hold queue contains two holds" );
1293
1294     my $queue_line_1 = $queue_rs->next;
1295     is( $queue_line_1->item_level_request, 1, 'Request is correctly advertised as item-level' );
1296
1297     my $queue_line_2 = $queue_rs->next;
1298     is( $queue_line_2->item_level_request, 0, 'Request is correctly advertised as biblio-level' );
1299
1300 };
1301
1302 subtest 'Trivial test for UpdateTransportCostMatrix' => sub {
1303     plan tests => 1;
1304     my $recs = [
1305         { frombranch => $library1->{branchcode}, tobranch => $library2->{branchcode}, cost => 1, disable_transfer => 0 },
1306         { frombranch => $library2->{branchcode}, tobranch => $library3->{branchcode}, cost => 0, disable_transfer => 1 },
1307     ];
1308     C4::HoldsQueue::UpdateTransportCostMatrix( $recs );
1309     is( $schema->resultset('TransportCost')->count, 2, 'UpdateTransportCostMatrix added two records' );
1310 };
1311
1312 # Cleanup
1313 $schema->storage->txn_rollback;
1314
1315 ### END Test holds queue builder does not violate holds policy ###
1316
1317 sub test_queue {
1318     my ($test_name, $use_cost_matrix, $pick_branch, $hold_branch) = @_;
1319
1320     $test_name = "$test_name (".($use_cost_matrix ? "" : "don't ")."use cost matrix)";
1321
1322     $use_cost_matrix_sth->execute($use_cost_matrix);
1323     C4::Context->clear_syspref_cache();
1324     C4::HoldsQueue::CreateQueue();
1325
1326     my $results = $dbh->selectall_arrayref($test_sth, { Slice => {} }); # should be only one
1327     my $r = $results->[0];
1328
1329     my $ok = is( $r->{pickbranch}, $pick_branch, "$test_name pick up branch");
1330     $ok &&=  is( $r->{holdingbranch}, $hold_branch, "$test_name holding branch")
1331       if $hold_branch;
1332
1333     diag( "Wrong pick-up/hold for first target (pick_branch, hold_branch, reserves, hold_fill_targets, tmp_holdsqueue): "
1334         . Dumper ($pick_branch, $hold_branch, map dump_records($_), qw(reserves hold_fill_targets tmp_holdsqueue)) )
1335       unless $ok;
1336
1337     # Test enforcement of branch transfer limit
1338     if ( $r->{pickbranch} ne $r->{holdingbranch} ) {
1339         t::lib::Mocks::mock_preference( 'UseBranchTransferLimits', '1' );
1340         my $limit = Koha::Item::Transfer::Limit->new(
1341             {
1342                 toBranch   => $r->{pickbranch},
1343                 fromBranch => $r->{holdingbranch},
1344                 itemtype   => $r->{itype},
1345             }
1346         )->store();
1347         C4::Context->clear_syspref_cache();
1348         C4::HoldsQueue::CreateQueue();
1349         $results = $dbh->selectall_arrayref( $test_sth, { Slice => {} } )
1350           ;    # should be only one
1351         my $s = $results->[0];
1352         isnt( $r->{holdingbranch}, $s->{holdingbranch}, 'Hold is not trapped for pickup at a branch that cannot be transferred to');
1353
1354         $limit->delete();
1355         t::lib::Mocks::mock_preference( 'UseBranchTransferLimits', '0' );
1356         C4::Context->clear_syspref_cache();
1357         C4::HoldsQueue::CreateQueue();
1358     }
1359
1360 }
1361
1362 sub dump_records {
1363     my ($tablename) = @_;
1364     return $dbh->selectall_arrayref("SELECT * from $tablename where borrowernumber = ?", { Slice => {} }, $borrowernumber);
1365 }