Bug 10226: Add unit tests for GetReservesFromItemnumber
[koha.git] / t / db_dependent / Holds.t
1 #!/usr/bin/perl
2
3 use Modern::Perl;
4
5 use t::lib::Mocks;
6 use C4::Context;
7 use C4::Branch;
8
9 use Test::More tests => 38;
10 use MARC::Record;
11 use C4::Biblio;
12 use C4::Items;
13 use C4::Members;
14 use C4::Calendar;
15
16 use Koha::DateUtils qw( dt_from_string output_pref );
17
18 BEGIN {
19     use FindBin;
20     use lib $FindBin::Bin;
21     use_ok('C4::Reserves');
22 }
23
24 my $dbh = C4::Context->dbh;
25
26 # Start transaction
27 $dbh->{AutoCommit} = 0;
28 $dbh->{RaiseError} = 1;
29
30 my $borrowers_count = 5;
31
32 $dbh->do('DELETE FROM itemtypes');
33 my $insert_sth = $dbh->prepare('INSERT INTO itemtypes (itemtype) VALUES (?)');
34 $insert_sth->execute('CAN');
35 $insert_sth->execute('CANNOT');
36
37 # Setup Test------------------------
38 # Helper biblio.
39 diag("Creating biblio instance for testing.");
40 my ($bibnum, $title, $bibitemnum) = create_helper_biblio();
41
42 # Helper item for that biblio.
43 diag("Creating item instance for testing.");
44 my ($item_bibnum, $item_bibitemnum, $itemnumber) = AddItem({ homebranch => 'CPL', holdingbranch => 'CPL' } , $bibnum);
45
46 # Create some borrowers
47 my @borrowernumbers;
48 foreach (1..$borrowers_count) {
49     my $borrowernumber = AddMember(
50         firstname =>  'my firstname',
51         surname => 'my surname ' . $_,
52         categorycode => 'S',
53         branchcode => 'CPL',
54     );
55     push @borrowernumbers, $borrowernumber;
56 }
57
58 my $biblionumber   = $bibnum;
59
60 my @branches = GetBranchesLoop();
61 my $branch = $branches[0][0]{value};
62
63 # Create five item level holds
64 foreach my $borrowernumber ( @borrowernumbers ) {
65     AddReserve(
66         $branch,
67         $borrowernumber,
68         $biblionumber,
69         my $constraint = 'a',
70         my $bibitems = q{},
71         my $priority,
72         my $resdate,
73         my $expdate,
74         my $notes = q{},
75         $title,
76         my $checkitem = $itemnumber,
77         my $found,
78     );
79 }
80
81
82 my $reserves = GetReservesFromBiblionumber({ biblionumber => $biblionumber });
83 is( scalar(@$reserves), $borrowers_count, "Test GetReserves()" );
84
85
86 my ( $reservedate, $borrowernumber, $branchcode, $reserve_id ) = GetReservesFromItemnumber($itemnumber);
87 is( $reservedate, output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 }), "GetReservesFromItemnumber should return a valid reserve date");
88 is( $borrowernumber, $borrowernumbers[0], "GetReservesFromItemnumber should return a valid borrowernumber");
89 is( $branchcode, 'CPL', "GetReservesFromItemnumber should return a valid branchcode");
90 ok($reserve_id, "Test GetReservesFromItemnumber()");
91
92
93 my ( $reserve ) = GetReservesFromBorrowernumber($borrowernumbers[0]);
94 ok( $reserve->{'borrowernumber'} eq $borrowernumbers[0], "Test GetReservesFromBorrowernumber()");
95
96
97 ok( GetReserveCount( $borrowernumbers[0] ), "Test GetReserveCount()" );
98
99
100 CancelReserve({ 'reserve_id' => $reserve_id });
101 $reserves = GetReservesFromBiblionumber({ biblionumber => $biblionumber });
102 is( scalar(@$reserves), $borrowers_count - 1, "Test CancelReserve()" );
103
104
105 ( $reservedate, $borrowernumber, $branchcode, $reserve_id ) = GetReservesFromItemnumber($itemnumber);
106 ModReserve({
107     reserve_id    => $reserve_id,
108     rank          => '4',
109     branchcode    => $branch,
110     itemnumber    => $itemnumber,
111     suspend_until => C4::Dates->new("2013-01-01","iso")->output(),
112 });
113 $reserve = GetReserve( $reserve_id );
114 ok( $reserve->{'priority'} eq '4', "Test GetReserve(), priority changed correctly" );
115 ok( $reserve->{'suspend'}, "Test GetReserve(), suspend hold" );
116 ok( $reserve->{'suspend_until'} eq '2013-01-01 00:00:00', "Test GetReserve(), suspend until date" );
117
118 ToggleSuspend( $reserve_id );
119 $reserve = GetReserve( $reserve_id );
120 ok( !$reserve->{'suspend'}, "Test ToggleSuspend(), no date" );
121
122 ToggleSuspend( $reserve_id, '2012-01-01' );
123 $reserve = GetReserve( $reserve_id );
124 ok( $reserve->{'suspend_until'} eq '2012-01-01 00:00:00', "Test ToggleSuspend(), with date" );
125
126 AutoUnsuspendReserves();
127 $reserve = GetReserve( $reserve_id );
128 ok( !$reserve->{'suspend'}, "Test AutoUnsuspendReserves()" );
129
130 # Add a new hold for the borrower whose hold we canceled earlier, this time at the bib level
131 AddReserve(
132     $branch,
133     $borrowernumber,
134     $biblionumber,
135     my $constraint = 'a',
136     my $bibitems = q{},
137     my $priority,
138     my $resdate,
139     my $expdate,
140     my $notes = q{},
141     $title,
142     my $checkitem,
143     my $found,
144 );
145 ( $reserve ) = GetReservesFromBorrowernumber($borrowernumber);
146 my $reserveid = C4::Reserves::GetReserveId(
147     {
148         biblionumber => $biblionumber,
149         borrowernumber => $borrowernumber
150     }
151 );
152 is( $reserveid, $reserve->{reserve_id}, "Test GetReserveId" );
153 ModReserveMinusPriority( $itemnumber, $reserve->{'reserve_id'} );
154 ( $reserve ) = GetReservesFromBorrowernumber($borrowernumber);
155 ok( $reserve->{'itemnumber'} eq $itemnumber, "Test ModReserveMinusPriority()" );
156
157
158 my $reserve2 = GetReserveInfo( $reserve->{'reserve_id'} );
159 ok( $reserve->{'reserve_id'} eq $reserve2->{'reserve_id'}, "Test GetReserveInfo()" );
160
161
162 $reserves = GetReservesFromBiblionumber({ biblionumber => $biblionumber, all_dates => 1 });
163 $reserve = $reserves->[1];
164 AlterPriority( 'top', $reserve->{'reserve_id'} );
165 $reserve = GetReserve( $reserve->{'reserve_id'} );
166 ok( $reserve->{'priority'} eq '1', "Test AlterPriority(), move to top" );
167
168 AlterPriority( 'down', $reserve->{'reserve_id'} );
169 $reserve = GetReserve( $reserve->{'reserve_id'} );
170 ok( $reserve->{'priority'} eq '2', "Test AlterPriority(), move down" );
171
172 AlterPriority( 'up', $reserve->{'reserve_id'} );
173 $reserve = GetReserve( $reserve->{'reserve_id'} );
174 ok( $reserve->{'priority'} eq '1', "Test AlterPriority(), move up" );
175
176 AlterPriority( 'bottom', $reserve->{'reserve_id'} );
177 $reserve = GetReserve( $reserve->{'reserve_id'} );
178 ok( $reserve->{'priority'} eq '5', "Test AlterPriority(), move to bottom" );
179
180 # Regression test for bug 2394
181 #
182 # If IndependentBranches is ON and canreservefromotherbranches is OFF,
183 # a patron is not permittedo to request an item whose homebranch (i.e.,
184 # owner of the item) is different from the patron's own library.
185 # However, if canreservefromotherbranches is turned ON, the patron can
186 # create such hold requests.
187 #
188 # Note that canreservefromotherbranches has no effect if
189 # IndependentBranches is OFF.
190
191 my ($foreign_bibnum, $foreign_title, $foreign_bibitemnum) = create_helper_biblio();
192 my ($foreign_item_bibnum, $foreign_item_bibitemnum, $foreign_itemnumber)
193   = AddItem({ homebranch => 'MPL', holdingbranch => 'MPL' } , $foreign_bibnum);
194 $dbh->do('DELETE FROM issuingrules');
195 $dbh->do(
196     q{INSERT INTO issuingrules (categorycode, branchcode, itemtype, reservesallowed)
197       VALUES (?, ?, ?, ?)}, 
198     {},
199     '*', '*', '*', 25
200 );
201 $dbh->do(
202     q{INSERT INTO issuingrules (categorycode, branchcode, itemtype, reservesallowed)
203       VALUES (?, ?, ?, ?)}, 
204     {},
205     '*', '*', 'CANNOT', 0 
206 );
207
208 # make sure some basic sysprefs are set
209 t::lib::Mocks::mock_preference('ReservesControlBranch', 'homebranch');
210 t::lib::Mocks::mock_preference('item-level_itypes', 1);
211
212 # if IndependentBranches is OFF, a CPL patron can reserve an MPL item
213 t::lib::Mocks::mock_preference('IndependentBranches', 0);
214 ok(
215     CanItemBeReserved($borrowernumbers[0], $foreign_itemnumber),
216     'CPL patron allowed to reserve MPL item with IndependentBranches OFF (bug 2394)'
217 );
218
219 # if IndependentBranches is OFF, a CPL patron cannot reserve an MPL item
220 t::lib::Mocks::mock_preference('IndependentBranches', 1);
221 t::lib::Mocks::mock_preference('canreservefromotherbranches', 0);
222 ok(
223     ! CanItemBeReserved($borrowernumbers[0], $foreign_itemnumber),
224     'CPL patron NOT allowed to reserve MPL item with IndependentBranches ON ... (bug 2394)'
225 );
226
227 # ... unless canreservefromotherbranches is ON
228 t::lib::Mocks::mock_preference('canreservefromotherbranches', 1);
229 ok(
230     CanItemBeReserved($borrowernumbers[0], $foreign_itemnumber),
231     '... unless canreservefromotherbranches is ON (bug 2394)'
232 );
233
234 # Regression test for bug 11336
235 ($bibnum, $title, $bibitemnum) = create_helper_biblio();
236 ($item_bibnum, $item_bibitemnum, $itemnumber) = AddItem({ homebranch => 'CPL', holdingbranch => 'CPL' } , $bibnum);
237 AddReserve(
238     $branch,
239     $borrowernumbers[0],
240     $bibnum,
241     'a',
242     '',
243     1,
244 );
245
246 my $reserveid1 = C4::Reserves::GetReserveId(
247     {
248         biblionumber => $bibnum,
249         borrowernumber => $borrowernumbers[0]
250     }
251 );
252
253 ($item_bibnum, $item_bibitemnum, $itemnumber) = AddItem({ homebranch => 'CPL', holdingbranch => 'CPL' } , $bibnum);
254 AddReserve(
255     $branch,
256     $borrowernumbers[1],
257     $bibnum,
258     'a',
259     '',
260     2,
261 );
262 my $reserveid2 = C4::Reserves::GetReserveId(
263     {
264         biblionumber => $bibnum,
265         borrowernumber => $borrowernumbers[1]
266     }
267 );
268
269 CancelReserve({ reserve_id => $reserveid1 });
270
271 $reserve2 = GetReserve( $reserveid2 );
272 is( $reserve2->{priority}, 1, "After cancelreserve, the 2nd reserve becomes the first on the waiting list" );
273
274 ($item_bibnum, $item_bibitemnum, $itemnumber) = AddItem({ homebranch => 'CPL', holdingbranch => 'CPL' } , $bibnum);
275 AddReserve(
276     $branch,
277     $borrowernumbers[0],
278     $bibnum,
279     'a',
280     '',
281     2,
282 );
283 my $reserveid3 = C4::Reserves::GetReserveId(
284     {
285         biblionumber => $bibnum,
286         borrowernumber => $borrowernumbers[0]
287     }
288 );
289
290 my $reserve3 = GetReserve( $reserveid3 );
291 is( $reserve3->{priority}, 2, "New reserve for patron 0, the reserve has a priority = 2" );
292
293 ModReserve({ reserve_id => $reserveid2, rank => 'del' });
294 $reserve3 = GetReserve( $reserveid3 );
295 is( $reserve3->{priority}, 1, "After ModReserve, the 3rd reserve becomes the first on the waiting list" );
296
297 ModItem({ damaged => 1 }, $item_bibnum, $itemnumber);
298 C4::Context->set_preference( 'AllowHoldsOnDamagedItems', 1 );
299 ok( CanItemBeReserved( $borrowernumbers[0], $itemnumber), "Patron can reserve damaged item with AllowHoldsOnDamagedItems enabled" );
300 ok( defined( ( CheckReserves($itemnumber) )[1] ), "Hold can be trapped for damaged item with AllowHoldsOnDamagedItems enabled" );
301 C4::Context->set_preference( 'AllowHoldsOnDamagedItems', 0 );
302 ok( !CanItemBeReserved( $borrowernumbers[0], $itemnumber), "Patron cannot reserve damaged item with AllowHoldsOnDamagedItems disabled" );
303 ok( !defined( ( CheckReserves($itemnumber) )[1] ), "Hold cannot be trapped for damaged item with AllowHoldsOnDamagedItems disabled" );
304
305 # Regression test for bug 9532
306 ($bibnum, $title, $bibitemnum) = create_helper_biblio('CANNOT');
307 ($item_bibnum, $item_bibitemnum, $itemnumber) = AddItem({ homebranch => 'CPL', holdingbranch => 'CPL', itype => 'CANNOT' } , $bibnum);
308 AddReserve(
309     $branch,
310     $borrowernumbers[0],
311     $bibnum,
312     'a',
313     '',
314     1,
315 );
316 ok(
317     !CanItemBeReserved( $borrowernumbers[0], $itemnumber),
318     "cannot request item if policy that matches on item-level item type forbids it"
319 );
320 ModItem({ itype => 'CAN' }, $item_bibnum, $itemnumber);
321 ok(
322     CanItemBeReserved( $borrowernumbers[0], $itemnumber),
323     "can request item if policy that matches on item type allows it"
324 );
325
326 t::lib::Mocks::mock_preference('item-level_itypes', 0);
327 ModItem({ itype => undef }, $item_bibnum, $itemnumber);
328 ok(
329     !CanItemBeReserved( $borrowernumbers[0], $itemnumber),
330     "cannot request item if policy that matches on bib-level item type forbids it (bug 9532)"
331 );
332
333 # Test CancelExpiredReserves
334 C4::Context->set_preference('ExpireReservesMaxPickUpDelay', 1);
335 C4::Context->set_preference('ReservesMaxPickUpDelay', 1);
336
337 my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) = localtime(time);
338 $year += 1900;
339 $mon += 1;
340 $reserves = $dbh->selectall_arrayref('SELECT * FROM reserves', { Slice => {} });
341 $reserve = $reserves->[0];
342 my $calendar = C4::Calendar->new(branchcode => $reserve->{branchcode});
343 $calendar->insert_single_holiday(
344     day         => $mday,
345     month       => $mon,
346     year        => $year,
347     title       => 'Test',
348     description => 'Test',
349 );
350 $reserve_id = $reserve->{reserve_id};
351 $dbh->do("UPDATE reserves SET waitingdate = DATE_SUB( NOW(), INTERVAL 5 DAY ), found = 'W', priority = 0 WHERE reserve_id = ?", undef, $reserve_id );
352 C4::Context->set_preference('ExpireReservesOnHolidays', 0);
353 CancelExpiredReserves();
354 my $count = $dbh->selectrow_array("SELECT COUNT(*) FROM reserves WHERE reserve_id = ?", undef, $reserve_id );
355 is( $count, 1, "Waiting reserve beyond max pickup delay *not* canceled on holiday" );
356 C4::Context->set_preference('ExpireReservesOnHolidays', 1);
357 CancelExpiredReserves();
358 $count = $dbh->selectrow_array("SELECT COUNT(*) FROM reserves WHERE reserve_id = ?", undef, $reserve_id );
359 is( $count, 0, "Waiting reserve beyond max pickup delay canceled on holiday" );
360
361 # Test expirationdate
362 $reserve = $reserves->[1];
363 $reserve_id = $reserve->{reserve_id};
364 $dbh->do("UPDATE reserves SET expirationdate = DATE_SUB( NOW(), INTERVAL 1 DAY ) WHERE reserve_id = ?", undef, $reserve_id );
365 CancelExpiredReserves();
366 $count = $dbh->selectrow_array("SELECT COUNT(*) FROM reserves WHERE reserve_id = ?", undef, $reserve_id );
367 is( $count, 0, "Reserve with manual expiration date canceled correctly" );
368
369 # Helper method to set up a Biblio.
370 sub create_helper_biblio {
371     my $itemtype = shift;
372     my $bib = MARC::Record->new();
373     my $title = 'Silence in the library';
374     $bib->append_fields(
375         MARC::Field->new('100', ' ', ' ', a => 'Moffat, Steven'),
376         MARC::Field->new('245', ' ', ' ', a => $title),
377         MARC::Field->new('942', ' ', ' ', c => $itemtype),
378     );
379     return ($bibnum, $title, $bibitemnum) = AddBiblio($bib, '');
380 }