Bug 11947 - renumber reserves when hold is confirmed
[koha_fer] / t / db_dependent / Reserves.t
1 #!/usr/bin/perl
2
3 use Modern::Perl;
4
5 use Test::More tests => 34;
6 use MARC::Record;
7 use DateTime::Duration;
8
9 use C4::Branch;
10 use C4::Biblio;
11 use C4::Items;
12 use C4::Members;
13 use C4::Circulation;
14 use t::lib::Mocks;
15
16 use Koha::DateUtils;
17
18 use Data::Dumper;
19 BEGIN {
20     use_ok('C4::Reserves');
21 }
22
23 # a very minimal mack of userenv for use by the test of DelItemCheck
24 *C4::Context::userenv = sub {
25     return {};
26 };
27
28 my $dbh = C4::Context->dbh;
29
30 # Start transaction
31 $dbh->{AutoCommit} = 0;
32 $dbh->{RaiseError} = 1;
33
34 # Setup Test------------------------
35
36 # Add branches if not existing
37 foreach my $addbra ('CPL', 'FPL', 'RPL') {
38     $dbh->do("INSERT INTO branches (branchcode,branchname) VALUES (?,?)", undef, ($addbra,"$addbra branch")) unless GetBranchName($addbra);
39 }
40
41 # Add categories if not existing
42 foreach my $addcat ('S', 'PT') {
43     $dbh->do("INSERT INTO categories (categorycode,hidelostitems,category_type) VALUES (?,?,?)",undef,($addcat, 0, $addcat eq 'S'? 'S': 'A')) unless GetBorrowercategory($addcat);
44 }
45
46 # Helper biblio.
47 diag("\nCreating biblio instance for testing.");
48 my $bib = MARC::Record->new();
49 my $title = 'Silence in the library';
50 if( C4::Context->preference('marcflavour') eq 'UNIMARC' ) {
51     $bib->append_fields(
52         MARC::Field->new('600', '', '1', a => 'Moffat, Steven'),
53         MARC::Field->new('200', '', '', a => $title),
54     );
55 }
56 else {
57     $bib->append_fields(
58         MARC::Field->new('100', '', '', a => 'Moffat, Steven'),
59         MARC::Field->new('245', '', '', a => $title),
60     );
61 }
62 my ($bibnum, $bibitemnum);
63 ($bibnum, $title, $bibitemnum) = AddBiblio($bib, '');
64
65 # Helper item for that biblio.
66 diag("Creating item instance for testing.");
67 my ($item_bibnum, $item_bibitemnum, $itemnumber) = AddItem({ homebranch => 'CPL', holdingbranch => 'CPL' } , $bibnum);
68
69 # Modify item; setting barcode.
70 my $testbarcode = '97531';
71 ModItem({ barcode => $testbarcode }, $bibnum, $itemnumber);
72
73 # Create a borrower
74 my %data = (
75     firstname =>  'my firstname',
76     surname => 'my surname',
77     categorycode => 'S',
78     branchcode => 'CPL',
79 );
80 my $borrowernumber = AddMember(%data);
81 my $borrower = GetMember( borrowernumber => $borrowernumber );
82 my $biblionumber   = $bibnum;
83 my $barcode        = $testbarcode;
84
85 my $constraint     = 'a';
86 my $bibitems       = '';
87 my $priority       = '1';
88 my $resdate        = undef;
89 my $expdate        = undef;
90 my $notes          = '';
91 my $checkitem      = undef;
92 my $found          = undef;
93
94 my @branches = GetBranchesLoop();
95 my $branch = $branches[0][0]{value};
96
97 AddReserve($branch,    $borrowernumber, $biblionumber,
98         $constraint, $bibitems,  $priority, $resdate, $expdate, $notes,
99         $title,      $checkitem, $found);
100
101 my ($status, $reserve, $all_reserves) = CheckReserves($itemnumber, $barcode);
102
103 is($status, "Reserved", "CheckReserves Test 1");
104
105 ($status, $reserve, $all_reserves) = CheckReserves($itemnumber);
106 is($status, "Reserved", "CheckReserves Test 2");
107
108 ($status, $reserve, $all_reserves) = CheckReserves(undef, $barcode);
109 is($status, "Reserved", "CheckReserves Test 3");
110
111 my $ReservesControlBranch = C4::Context->preference('ReservesControlBranch');
112 C4::Context->set_preference( 'ReservesControlBranch', 'ItemHomeLibrary' );
113 ok(
114     'ItemHomeLib' eq GetReservesControlBranch(
115         { homebranch => 'ItemHomeLib' },
116         { branchcode => 'PatronHomeLib' }
117     ), "GetReservesControlBranch returns item home branch when set to ItemHomeLibrary"
118 );
119 C4::Context->set_preference( 'ReservesControlBranch', 'PatronLibrary' );
120 ok(
121     'PatronHomeLib' eq GetReservesControlBranch(
122         { homebranch => 'ItemHomeLib' },
123         { branchcode => 'PatronHomeLib' }
124     ), "GetReservesControlBranch returns patron home branch when set to PatronLibrary"
125 );
126 C4::Context->set_preference( 'ReservesControlBranch', $ReservesControlBranch );
127
128 ###
129 ### Regression test for bug 10272
130 ###
131 my %requesters = ();
132 $requesters{'CPL'} = AddMember(
133     branchcode   => 'CPL',
134     categorycode => 'PT',
135     surname      => 'borrower from CPL',
136 );
137 $requesters{'FPL'} = AddMember(
138     branchcode   => 'FPL',
139     categorycode => 'PT',
140     surname      => 'borrower from FPL',
141 );
142 $requesters{'RPL'} = AddMember(
143     branchcode   => 'RPL',
144     categorycode => 'PT',
145     surname      => 'borrower from RPL',
146 );
147
148 # Configure rules so that CPL allows only CPL patrons
149 # to request its items, while FPL will allow its items
150 # to fill holds from anywhere.
151
152 $dbh->do('DELETE FROM issuingrules');
153 $dbh->do('DELETE FROM branch_item_rules');
154 $dbh->do('DELETE FROM branch_borrower_circ_rules');
155 $dbh->do('DELETE FROM default_borrower_circ_rules');
156 $dbh->do('DELETE FROM default_branch_item_rules');
157 $dbh->do('DELETE FROM default_branch_circ_rules');
158 $dbh->do('DELETE FROM default_circ_rules');
159 $dbh->do(
160     q{INSERT INTO issuingrules (categorycode, branchcode, itemtype, reservesallowed)
161       VALUES (?, ?, ?, ?)},
162     {},
163     '*', '*', '*', 25
164 );
165
166 # CPL allows only its own patrons to request its items
167 $dbh->do(
168     q{INSERT INTO default_branch_circ_rules (branchcode, maxissueqty, holdallowed, returnbranch)
169       VALUES (?, ?, ?, ?)},
170     {},
171     'CPL', 10, 1, 'homebranch',
172 );
173
174 # ... while FPL allows anybody to request its items
175 $dbh->do(
176     q{INSERT INTO default_branch_circ_rules (branchcode, maxissueqty, holdallowed, returnbranch)
177       VALUES (?, ?, ?, ?)},
178     {},
179     'FPL', 10, 2, 'homebranch',
180 );
181
182 # helper biblio for the bug 10272 regression test
183 my $bib2 = MARC::Record->new();
184 $bib2->append_fields(
185     MARC::Field->new('100', ' ', ' ', a => 'Moffat, Steven'),
186     MARC::Field->new('245', ' ', ' ', a => $title),
187 );
188
189 # create one item belonging to FPL and one belonging to CPL
190 my ($bibnum2, $bibitemnum2) = AddBiblio($bib, '');
191 my ($itemnum_cpl, $itemnum_fpl);
192 (undef, undef, $itemnum_cpl) = AddItem({
193         homebranch => 'CPL',
194         holdingbranch => 'CPL',
195         barcode => 'bug10272_CPL'
196     } , $bibnum2);
197 (undef, undef, $itemnum_fpl) = AddItem({
198         homebranch => 'FPL',
199         holdingbranch => 'FPL',
200         barcode => 'bug10272_FPL'
201     } , $bibnum2);
202
203 # Ensure that priorities are numbered correcly when a hold is moved to waiting
204 # (bug 11947)
205 $dbh->do("DELETE FROM reserves WHERE biblionumber=?",undef,($bibnum2));
206 AddReserve('RPL',  $requesters{'RPL'}, $bibnum2,
207            $constraint, $bibitems,  1, $resdate, $expdate, $notes,
208            $title,      $checkitem, $found);
209 AddReserve('FPL',  $requesters{'FPL'}, $bibnum2,
210            $constraint, $bibitems,  2, $resdate, $expdate, $notes,
211            $title,      $checkitem, $found);
212 AddReserve('CPL',  $requesters{'CPL'}, $bibnum2,
213            $constraint, $bibitems,  3, $resdate, $expdate, $notes,
214            $title,      $checkitem, $found);
215 ModReserveAffect($itemnum_cpl, $requesters{'RPL'}, 0);
216
217 # Now it should have different priorities.
218 my $title_reserves = GetReservesFromBiblionumber({biblionumber => $bibnum2});
219 # Sort by reserve number in case the database gives us oddly ordered results
220 my @reserves = sort { $a->{reserve_id} <=> $b->{reserve_id} } @$title_reserves;
221 is($reserves[0]{priority}, 0, 'Item is correctly waiting');
222 is($reserves[1]{priority}, 1, 'Item is correctly priority 1');
223 is($reserves[2]{priority}, 2, 'Item is correctly priority 2');
224
225
226 $dbh->do("DELETE FROM reserves WHERE biblionumber=?",undef,($bibnum2));
227 AddReserve('RPL',  $requesters{'RPL'}, $bibnum2,
228            $constraint, $bibitems,  1, $resdate, $expdate, $notes,
229            $title,      $checkitem, $found);
230 AddReserve('FPL',  $requesters{'FPL'}, $bibnum2,
231            $constraint, $bibitems,  2, $resdate, $expdate, $notes,
232            $title,      $checkitem, $found);
233 AddReserve('CPL',  $requesters{'CPL'}, $bibnum2,
234            $constraint, $bibitems,  3, $resdate, $expdate, $notes,
235            $title,      $checkitem, $found);
236
237 # Ensure that the item's home library controls hold policy lookup
238 C4::Context->set_preference( 'ReservesControlBranch', 'ItemHomeLibrary' );
239
240 my $messages;
241 # Return the CPL item at FPL.  The hold that should be triggered is
242 # the one placed by the CPL patron, as the other two patron's hold
243 # requests cannot be filled by that item per policy.
244 (undef, $messages, undef, undef) = AddReturn('bug10272_CPL', 'FPL');
245 is( $messages->{ResFound}->{borrowernumber},
246     $requesters{'CPL'},
247     'restrictive library\'s items only fill requests by own patrons (bug 10272)');
248
249 # Return the FPL item at FPL.  The hold that should be triggered is
250 # the one placed by the RPL patron, as that patron is first in line
251 # and RPL imposes no restrictions on whose holds its items can fill.
252 (undef, $messages, undef, undef) = AddReturn('bug10272_FPL', 'FPL');
253 is( $messages->{ResFound}->{borrowernumber},
254     $requesters{'RPL'},
255     'for generous library, its items fill first hold request in line (bug 10272)');
256
257 # Tests for bug 9761 (ConfirmFutureHolds): new CheckReserves lookahead parameter, and corresponding change in AddReturn
258 # Note that CheckReserve uses its lookahead parameter and does not check ConfirmFutureHolds pref (it should be passed if needed like AddReturn does)
259 # Test 9761a: Add a reserve without date, CheckReserve should return it
260 $resdate= undef; #defaults to today in AddReserve
261 $expdate= undef; #no expdate
262 $dbh->do("DELETE FROM reserves WHERE biblionumber=?",undef,($bibnum));
263 AddReserve('CPL',  $requesters{'CPL'}, $bibnum,
264            $constraint, $bibitems,  1, $resdate, $expdate, $notes,
265            $title,      $checkitem, $found);
266 ($status)=CheckReserves($itemnumber,undef,undef);
267 is( $status, 'Reserved', 'CheckReserves returns reserve without lookahead');
268 ($status)=CheckReserves($itemnumber,undef,7);
269 is( $status, 'Reserved', 'CheckReserves also returns reserve with lookahead');
270
271 # Test 9761b: Add a reserve with future date, CheckReserve should not return it
272 $dbh->do("DELETE FROM reserves WHERE biblionumber=?",undef,($bibnum));
273 C4::Context->set_preference('AllowHoldDateInFuture', 1);
274 $resdate= dt_from_string();
275 $resdate->add_duration(DateTime::Duration->new(days => 4));
276 $resdate=output_pref($resdate);
277 $expdate= undef; #no expdate
278 AddReserve('CPL',  $requesters{'CPL'}, $bibnum,
279            $constraint, $bibitems,  1, $resdate, $expdate, $notes,
280            $title,      $checkitem, $found);
281 ($status)=CheckReserves($itemnumber,undef,undef);
282 is( $status, '', 'CheckReserves returns no future reserve without lookahead');
283
284 # Test 9761c: Add a reserve with future date, CheckReserve should return it if lookahead is high enough
285 ($status)=CheckReserves($itemnumber,undef,3);
286 is( $status, '', 'CheckReserves returns no future reserve with insufficient lookahead');
287 ($status)=CheckReserves($itemnumber,undef,4);
288 is( $status, 'Reserved', 'CheckReserves returns future reserve with sufficient lookahead');
289
290 # Test 9761d: Check ResFound message of AddReturn for future hold
291 # Note that AddReturn is in Circulation.pm, but this test really pertains to reserves; AddReturn uses the ConfirmFutureHolds pref when calling CheckReserves
292 # In this test we do not need an issued item; it is just a 'checkin'
293 C4::Context->set_preference('ConfirmFutureHolds', 0);
294 (my $doreturn, $messages)= AddReturn('97531','CPL');
295 is($messages->{ResFound}//'', '', 'AddReturn does not care about future reserve when ConfirmFutureHolds is off');
296 C4::Context->set_preference('ConfirmFutureHolds', 3);
297 ($doreturn, $messages)= AddReturn('97531','CPL');
298 is(exists $messages->{ResFound}?1:0, 0, 'AddReturn ignores future reserve beyond ConfirmFutureHolds days');
299 C4::Context->set_preference('ConfirmFutureHolds', 7);
300 ($doreturn, $messages)= AddReturn('97531','CPL');
301 is(exists $messages->{ResFound}?1:0, 1, 'AddReturn considers future reserve within ConfirmFutureHolds days');
302
303 # End of tests for bug 9761 (ConfirmFutureHolds)
304
305 # test marking a hold as captured
306 my $hold_notice_count = count_hold_print_messages();
307 ModReserveAffect($itemnumber, $requesters{'CPL'}, 0);
308 my $new_count = count_hold_print_messages();
309 is($new_count, $hold_notice_count + 1, 'patron notified when item set to waiting');
310
311 # test that duplicate notices aren't generated
312 ModReserveAffect($itemnumber, $requesters{'CPL'}, 0);
313 $new_count = count_hold_print_messages();
314 is($new_count, $hold_notice_count + 1, 'patron not notified a second time (bug 11445)');
315
316 # avoiding the not_same_branch error
317 t::lib::Mocks::mock_preference('IndependentBranches', 0);
318 is(
319     DelItemCheck($dbh, $bibnum, $itemnumber),
320     'book_reserved',
321     'item that is captured to fill a hold cannot be deleted',
322 );
323
324 my $letter = ReserveSlip('CPL', $requesters{'CPL'}, $bibnum);
325 ok(defined($letter), 'can successfully generate hold slip (bug 10949)');
326
327 # Tests for bug 9788: Does GetReservesFromItemnumber return a future wait?
328 # 9788a: GetReservesFromItemnumber does not return future next available hold
329 $dbh->do("DELETE FROM reserves WHERE biblionumber=?",undef,($bibnum));
330 C4::Context->set_preference('ConfirmFutureHolds', 2);
331 C4::Context->set_preference('AllowHoldDateInFuture', 1);
332 $resdate= dt_from_string();
333 $resdate->add_duration(DateTime::Duration->new(days => 2));
334 $resdate=output_pref($resdate);
335 AddReserve('CPL',  $requesters{'CPL'}, $bibnum,
336            $constraint, $bibitems,  1, $resdate, $expdate, $notes,
337            $title,      $checkitem, $found);
338 my @results= GetReservesFromItemnumber($itemnumber);
339 is(defined $results[3]?1:0, 0, 'GetReservesFromItemnumber does not return a future next available hold');
340 # 9788b: GetReservesFromItemnumber does not return future item level hold
341 $dbh->do("DELETE FROM reserves WHERE biblionumber=?",undef,($bibnum));
342 AddReserve('CPL',  $requesters{'CPL'}, $bibnum,
343            $constraint, $bibitems,  1, $resdate, $expdate, $notes,
344            $title,      $itemnumber, $found); #item level hold
345 @results= GetReservesFromItemnumber($itemnumber);
346 is(defined $results[3]?1:0, 0, 'GetReservesFromItemnumber does not return a future item level hold');
347 # 9788c: GetReservesFromItemnumber returns future wait (confirmed future hold)
348 ModReserveAffect( $itemnumber,  $requesters{'CPL'} , 0); #confirm hold
349 @results= GetReservesFromItemnumber($itemnumber);
350 is(defined $results[3]?1:0, 1, 'GetReservesFromItemnumber returns a future wait (confirmed future hold)');
351 # End of tests for bug 9788
352
353 # Tests for CalculatePriority (bug 8918)
354 my $p = C4::Reserves::CalculatePriority($bibnum2);
355 is($p, 4, 'CalculatePriority should now return priority 4');
356 $resdate=undef;
357 AddReserve('CPL',  $requesters{'CPL'}, $bibnum2,
358            $constraint, $bibitems,  $p, $resdate, $expdate, $notes,
359            $title,      $checkitem, $found);
360 $p = C4::Reserves::CalculatePriority($bibnum2);
361 is($p, 5, 'CalculatePriority should now return priority 5');
362 #some tests on bibnum
363 $dbh->do("DELETE FROM reserves WHERE biblionumber=?",undef,($bibnum));
364 $p = C4::Reserves::CalculatePriority($bibnum);
365 is($p, 1, 'CalculatePriority should now return priority 1');
366 #add a new reserve and confirm it to waiting
367 AddReserve('CPL',  $requesters{'CPL'}, $bibnum,
368            $constraint, $bibitems,  $p, $resdate, $expdate, $notes,
369            $title,      $itemnumber, $found);
370 $p = C4::Reserves::CalculatePriority($bibnum);
371 is($p, 2, 'CalculatePriority should now return priority 2');
372 ModReserveAffect( $itemnumber,  $requesters{'CPL'} , 0);
373 $p = C4::Reserves::CalculatePriority($bibnum);
374 is($p, 1, 'CalculatePriority should now return priority 1');
375 #add another biblio hold, no resdate
376 AddReserve('CPL',  $requesters{'CPL'}, $bibnum,
377            $constraint, $bibitems,  $p, $resdate, $expdate, $notes,
378            $title,      $checkitem, $found);
379 $p = C4::Reserves::CalculatePriority($bibnum);
380 is($p, 2, 'CalculatePriority should now return priority 2');
381 #add another future hold
382 C4::Context->set_preference('AllowHoldDateInFuture', 1);
383 $resdate= dt_from_string();
384 $resdate->add_duration(DateTime::Duration->new(days => 1));
385 AddReserve('CPL',  $requesters{'CPL'}, $bibnum,
386            $constraint, $bibitems,  $p, output_pref($resdate), $expdate, $notes,
387            $title,      $checkitem, $found);
388 $p = C4::Reserves::CalculatePriority($bibnum);
389 is($p, 2, 'CalculatePriority should now still return priority 2');
390 #calc priority with future resdate
391 $p = C4::Reserves::CalculatePriority($bibnum, $resdate);
392 is($p, 3, 'CalculatePriority should now return priority 3');
393 # End of tests for bug 8918
394
395 $dbh->rollback;
396
397 sub count_hold_print_messages {
398     my $message_count = $dbh->selectall_arrayref(q{
399         SELECT COUNT(*) FROM message_queue WHERE letter_code = 'HOLD_PRINT'
400     });
401     return $message_count->[0]->[0];
402 }