Bug 7413: (follow-up) add direct unit tests
[koha.git] / t / db_dependent / Circulation.t
1 #!/usr/bin/perl
2
3 use Modern::Perl;
4
5 use DateTime;
6 use C4::Biblio;
7 use C4::Branch;
8 use C4::Items;
9 use C4::Members;
10 use C4::Reserves;
11
12 use Test::More tests => 48;
13
14 BEGIN {
15     use_ok('C4::Circulation');
16 }
17
18 my $dbh = C4::Context->dbh;
19
20 # Start transaction
21 $dbh->{AutoCommit} = 0;
22 $dbh->{RaiseError} = 1;
23
24 # Start with a clean slate
25 $dbh->do('DELETE FROM issues');
26
27 my $CircControl = C4::Context->preference('CircControl');
28 my $HomeOrHoldingBranch = C4::Context->preference('HomeOrHoldingBranch');
29
30 my $item = {
31     homebranch => 'MPL',
32     holdingbranch => 'MPL'
33 };
34
35 my $borrower = {
36     branchcode => 'MPL'
37 };
38
39 # No userenv, PickupLibrary
40 C4::Context->set_preference('CircControl', 'PickupLibrary');
41 is(
42     C4::Context->preference('CircControl'),
43     'PickupLibrary',
44     'CircControl changed to PickupLibrary'
45 );
46 is(
47     C4::Circulation::_GetCircControlBranch($item, $borrower),
48     $item->{$HomeOrHoldingBranch},
49     '_GetCircControlBranch returned item branch (no userenv defined)'
50 );
51
52 # No userenv, PatronLibrary
53 C4::Context->set_preference('CircControl', 'PatronLibrary');
54 is(
55     C4::Context->preference('CircControl'),
56     'PatronLibrary',
57     'CircControl changed to PatronLibrary'
58 );
59 is(
60     C4::Circulation::_GetCircControlBranch($item, $borrower),
61     $borrower->{branchcode},
62     '_GetCircControlBranch returned borrower branch'
63 );
64
65 # No userenv, ItemHomeLibrary
66 C4::Context->set_preference('CircControl', 'ItemHomeLibrary');
67 is(
68     C4::Context->preference('CircControl'),
69     'ItemHomeLibrary',
70     'CircControl changed to ItemHomeLibrary'
71 );
72 is(
73     $item->{$HomeOrHoldingBranch},
74     C4::Circulation::_GetCircControlBranch($item, $borrower),
75     '_GetCircControlBranch returned item branch'
76 );
77
78 diag('Now, set a userenv');
79 C4::Context->_new_userenv('xxx');
80 C4::Context::set_userenv(0,0,0,'firstname','surname', 'MPL', 'Midway Public Library', '', '', '');
81 is(C4::Context->userenv->{branch}, 'MPL', 'userenv set');
82
83 # Userenv set, PickupLibrary
84 C4::Context->set_preference('CircControl', 'PickupLibrary');
85 is(
86     C4::Context->preference('CircControl'),
87     'PickupLibrary',
88     'CircControl changed to PickupLibrary'
89 );
90 is(
91     C4::Circulation::_GetCircControlBranch($item, $borrower),
92     'MPL',
93     '_GetCircControlBranch returned current branch'
94 );
95
96 # Userenv set, PatronLibrary
97 C4::Context->set_preference('CircControl', 'PatronLibrary');
98 is(
99     C4::Context->preference('CircControl'),
100     'PatronLibrary',
101     'CircControl changed to PatronLibrary'
102 );
103 is(
104     C4::Circulation::_GetCircControlBranch($item, $borrower),
105     $borrower->{branchcode},
106     '_GetCircControlBranch returned borrower branch'
107 );
108
109 # Userenv set, ItemHomeLibrary
110 C4::Context->set_preference('CircControl', 'ItemHomeLibrary');
111 is(
112     C4::Context->preference('CircControl'),
113     'ItemHomeLibrary',
114     'CircControl changed to ItemHomeLibrary'
115 );
116 is(
117     C4::Circulation::_GetCircControlBranch($item, $borrower),
118     $item->{$HomeOrHoldingBranch},
119     '_GetCircControlBranch returned item branch'
120 );
121
122 # Reset initial configuration
123 C4::Context->set_preference('CircControl', $CircControl);
124 is(
125     C4::Context->preference('CircControl'),
126     $CircControl,
127     'CircControl reset to its initial value'
128 );
129
130 # Set a simple circ policy
131 $dbh->do('DELETE FROM issuingrules');
132 $dbh->do(
133     q{INSERT INTO issuingrules (categorycode, branchcode, itemtype, reservesallowed,
134                                 maxissueqty, issuelength, lengthunit,
135                                 renewalsallowed, renewalperiod,
136                                 fine, chargeperiod)
137       VALUES (?, ?, ?, ?,
138               ?, ?, ?,
139               ?, ?,
140               ?, ?
141              )
142     },
143     {},
144     '*', '*', '*', 25,
145     20, 14, 'days',
146     1, 7,
147     .10, 1
148 );
149
150 # Test C4::Circulation::ProcessOfflinePayment
151 my $sth = C4::Context->dbh->prepare("SELECT COUNT(*) FROM accountlines WHERE amount = '-123.45' AND accounttype = 'Pay'");
152 $sth->execute();
153 my ( $original_count ) = $sth->fetchrow_array();
154
155 C4::Context->dbh->do("INSERT INTO borrowers ( cardnumber, surname, firstname, categorycode, branchcode ) VALUES ( '99999999999', 'Hall', 'Kyle', 'S', 'MPL' )");
156
157 C4::Circulation::ProcessOfflinePayment({ cardnumber => '99999999999', amount => '123.45' });
158
159 $sth->execute();
160 my ( $new_count ) = $sth->fetchrow_array();
161
162 ok( $new_count == $original_count  + 1, 'ProcessOfflinePayment makes payment correctly' );
163
164 C4::Context->dbh->do("DELETE FROM accountlines WHERE borrowernumber IN ( SELECT borrowernumber FROM borrowers WHERE cardnumber = '99999999999' )");
165 C4::Context->dbh->do("DELETE FROM borrowers WHERE cardnumber = '99999999999'");
166 C4::Context->dbh->do("DELETE FROM accountlines");
167 {
168 # CanBookBeRenewed tests
169
170     # Generate test biblio
171     my $biblio = MARC::Record->new();
172     my $title = 'Silence in the library';
173     $biblio->append_fields(
174         MARC::Field->new('100', ' ', ' ', a => 'Moffat, Steven'),
175         MARC::Field->new('245', ' ', ' ', a => $title),
176     );
177
178     my ($biblionumber, $biblioitemnumber) = AddBiblio($biblio, '');
179
180     my $barcode = 'R00000342';
181     my $branch = 'MPL';
182
183     my ( $item_bibnum, $item_bibitemnum, $itemnumber ) = AddItem(
184         {
185             homebranch       => $branch,
186             holdingbranch    => $branch,
187             barcode          => $barcode,
188             replacementprice => 12.00
189         },
190         $biblionumber
191     );
192
193     my $barcode2 = 'R00000343';
194     my ( $item_bibnum2, $item_bibitemnum2, $itemnumber2 ) = AddItem(
195         {
196             homebranch       => $branch,
197             holdingbranch    => $branch,
198             barcode          => $barcode2,
199             replacementprice => 23.00
200         },
201         $biblionumber
202     );
203
204     my $barcode3 = 'R00000346';
205     my ( $item_bibnum3, $item_bibitemnum3, $itemnumber3 ) = AddItem(
206         {
207             homebranch       => $branch,
208             holdingbranch    => $branch,
209             barcode          => $barcode3,
210             replacementprice => 23.00
211         },
212         $biblionumber
213     );
214
215     # Create 2 borrowers
216     my %renewing_borrower_data = (
217         firstname =>  'John',
218         surname => 'Renewal',
219         categorycode => 'S',
220         branchcode => $branch,
221     );
222
223     my %reserving_borrower_data = (
224         firstname =>  'Katrin',
225         surname => 'Reservation',
226         categorycode => 'S',
227         branchcode => $branch,
228     );
229
230     my $renewing_borrowernumber = AddMember(%renewing_borrower_data);
231     my $reserving_borrowernumber = AddMember(%reserving_borrower_data);
232
233     my $renewing_borrower = GetMember( borrowernumber => $renewing_borrowernumber );
234
235     my $constraint     = 'a';
236     my $bibitems       = '';
237     my $priority       = '1';
238     my $resdate        = undef;
239     my $expdate        = undef;
240     my $notes          = '';
241     my $checkitem      = undef;
242     my $found          = undef;
243
244     my $datedue = AddIssue( $renewing_borrower, $barcode);
245     is (defined $datedue, 1, "Item 1 checked out, due date: $datedue");
246
247     my $datedue2 = AddIssue( $renewing_borrower, $barcode2);
248     is (defined $datedue2, 1, "Item 2 checked out, due date: $datedue2");
249
250     my $borrowing_borrowernumber = GetItemIssue($itemnumber)->{borrowernumber};
251     is ($borrowing_borrowernumber, $renewing_borrowernumber, "Item checked out to $renewing_borrower->{firstname} $renewing_borrower->{surname}");
252
253     my ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $itemnumber, 1);
254     is( $renewokay, 1, 'Can renew, no holds for this title or item');
255
256
257     diag("Biblio-level hold, renewal test");
258     AddReserve(
259         $branch, $reserving_borrowernumber, $biblionumber,
260         $constraint, $bibitems,  $priority, $resdate, $expdate, $notes,
261         $title, $checkitem, $found
262     );
263
264     ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $itemnumber);
265     is( $renewokay, 0, '(Bug 10663) Cannot renew, reserved');
266     is( $error, 'on_reserve', '(Bug 10663) Cannot renew, reserved (returned error is on_reserve)');
267
268     ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $itemnumber2);
269     is( $renewokay, 0, '(Bug 10663) Cannot renew, reserved');
270     is( $error, 'on_reserve', '(Bug 10663) Cannot renew, reserved (returned error is on_reserve)');
271
272     my $reserveid = C4::Reserves::GetReserveId({ biblionumber => $biblionumber, borrowernumber => $reserving_borrowernumber});
273     my $reserving_borrower = GetMember( borrowernumber => $reserving_borrowernumber );
274     AddIssue($reserving_borrower, $barcode3);
275     my $reserve = $dbh->selectrow_hashref(
276         'SELECT * FROM old_reserves WHERE reserve_id = ?',
277         { Slice => {} },
278         $reserveid
279     );
280     is($reserve->{found}, 'F', 'hold marked completed when checking out item that fills it');
281
282     diag("Item-level hold, renewal test");
283     AddReserve(
284         $branch, $reserving_borrowernumber, $biblionumber,
285         $constraint, $bibitems,  $priority, $resdate, $expdate, $notes,
286         $title, $itemnumber, $found
287     );
288
289     ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $itemnumber, 1);
290     is( $renewokay, 0, '(Bug 10663) Cannot renew, item reserved');
291     is( $error, 'on_reserve', '(Bug 10663) Cannot renew, item reserved (returned error is on_reserve)');
292
293     ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $itemnumber2, 1);
294     is( $renewokay, 1, 'Can renew item 2, item-level hold is on item 1');
295
296
297     diag("Items can't fill hold for reasons");
298     ModItem({ notforloan => 1 }, $biblionumber, $itemnumber);
299     ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $itemnumber, 1);
300     is( $renewokay, 1, 'Can renew, item is marked not for loan, hold does not block');
301     ModItem({ notforloan => 0, itype => '' }, $biblionumber, $itemnumber,1);
302
303     # FIXME: Add more for itemtype not for loan etc.
304
305     $reserveid = C4::Reserves::GetReserveId({ biblionumber => $biblionumber, itemnumber => $itemnumber, borrowernumber => $reserving_borrowernumber});
306     CancelReserve({ reserve_id => $reserveid });
307
308     # set policy to require that loans cannot be
309     # renewed until seven days prior to the due date
310     $dbh->do('UPDATE issuingrules SET norenewalbefore = 7');
311     ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $itemnumber);
312     is( $renewokay, 0, 'Cannot renew, renewal is premature');
313     is( $error, 'too_soon', 'Cannot renew, renewal is premature (returned code is too_soon)');
314     is(
315         GetSoonestRenewDate($renewing_borrowernumber, $itemnumber),
316         $datedue->clone->add(days => -7),
317         'renewals permitted 7 days before due date, as expected',
318     );
319
320     diag("Too many renewals");
321
322     # set policy to forbid renewals
323     $dbh->do('UPDATE issuingrules SET norenewalbefore = NULL, renewalsallowed = 0');
324
325     ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $itemnumber);
326     is( $renewokay, 0, 'Cannot renew, 0 renewals allowed');
327     is( $error, 'too_many', 'Cannot renew, 0 renewals allowed (returned code is too_many)');
328
329     # Test WhenLostForgiveFine and WhenLostChargeReplacementFee
330     diag("WhenLostForgiveFine and WhenLostChargeReplacementFee");
331     C4::Context->set_preference('WhenLostForgiveFine','1');
332     C4::Context->set_preference('WhenLostChargeReplacementFee','1');
333
334     C4::Overdues::UpdateFine( $itemnumber, $renewing_borrower->{borrowernumber},
335         15.00, q{}, Koha::DateUtils::output_pref($datedue) );
336
337     LostItem( $itemnumber, 1 );
338
339     my $total_due = $dbh->selectrow_array(
340         'SELECT SUM( amountoutstanding ) FROM accountlines WHERE borrowernumber = ?',
341         undef, $renewing_borrower->{borrowernumber}
342     );
343
344     ok( $total_due == 12, 'Borrower only charged replacement fee with both WhenLostForgiveFine and WhenLostChargeReplacementFee enabled' );
345
346     C4::Context->dbh->do("DELETE FROM accountlines");
347
348     C4::Context->set_preference('WhenLostForgiveFine','0');
349     C4::Context->set_preference('WhenLostChargeReplacementFee','0');
350
351     C4::Overdues::UpdateFine( $itemnumber2, $renewing_borrower->{borrowernumber},
352         15.00, q{}, Koha::DateUtils::output_pref($datedue) );
353
354     LostItem( $itemnumber2, 1 );
355
356     $total_due = $dbh->selectrow_array(
357         'SELECT SUM( amountoutstanding ) FROM accountlines WHERE borrowernumber = ?',
358         undef, $renewing_borrower->{borrowernumber}
359     );
360
361     ok( $total_due == 15, 'Borrower only charged fine with both WhenLostForgiveFine and WhenLostChargeReplacementFee disabled' );
362 }
363
364 {
365     # GetUpcomingDueIssues tests
366     my $barcode  = 'R00000342';
367     my $barcode2 = 'R00000343';
368     my $barcode3 = 'R00000344';
369     my $branch   = 'MPL';
370
371     #Create another record
372     my $biblio2 = MARC::Record->new();
373     my $title2 = 'Something is worng here';
374     $biblio2->append_fields(
375         MARC::Field->new('100', ' ', ' ', a => 'Anonymous'),
376         MARC::Field->new('245', ' ', ' ', a => $title2),
377     );
378     my ($biblionumber2, $biblioitemnumber2) = AddBiblio($biblio2, '');
379
380     #Create third item
381     AddItem(
382         {
383             homebranch       => $branch,
384             holdingbranch    => $branch,
385             barcode          => $barcode3
386         },
387         $biblionumber2
388     );
389
390     # Create a borrower
391     my %a_borrower_data = (
392         firstname =>  'Fridolyn',
393         surname => 'SOMERS',
394         categorycode => 'S',
395         branchcode => $branch,
396     );
397
398     my $a_borrower_borrowernumber = AddMember(%a_borrower_data);
399     my $a_borrower = GetMember( borrowernumber => $a_borrower_borrowernumber );
400
401     my $yesterday = DateTime->today(time_zone => C4::Context->tz())->add( days => -1 );
402     my $two_days_ahead = DateTime->today(time_zone => C4::Context->tz())->add( days => 2 );
403     my $today = DateTime->today(time_zone => C4::Context->tz());
404
405     my $datedue  = AddIssue( $a_borrower, $barcode, $yesterday );
406     my $datedue2 = AddIssue( $a_borrower, $barcode2, $two_days_ahead );
407
408     my $upcoming_dues;
409
410     diag( "GetUpcomingDueIssues tests" );
411
412     for my $i(0..1) {
413         $upcoming_dues = C4::Circulation::GetUpcomingDueIssues( { days_in_advance => $i } );
414         is ( scalar( @$upcoming_dues ), 0, "No items due in less than one day ($i days in advance)" );
415     }
416
417     #days_in_advance needs to be inclusive, so 1 matches items due tomorrow, 0 items due today etc.
418     $upcoming_dues = C4::Circulation::GetUpcomingDueIssues( { days_in_advance => 2 } );
419     is ( scalar ( @$upcoming_dues), 1, "Only one item due in 2 days or less" );
420
421     for my $i(3..5) {
422         $upcoming_dues = C4::Circulation::GetUpcomingDueIssues( { days_in_advance => $i } );
423         is ( scalar( @$upcoming_dues ), 1,
424             "Bug 9362: Only one item due in more than 2 days ($i days in advance)" );
425     }
426
427     # Bug 11218 - Due notices not generated - GetUpcomingDueIssues needs to select due today items as well
428
429     my $datedue3 = AddIssue( $a_borrower, $barcode3, $today );
430
431     $upcoming_dues = C4::Circulation::GetUpcomingDueIssues( { days_in_advance => -1 } );
432     is ( scalar ( @$upcoming_dues), 0, "Overdues can not be selected" );
433
434     $upcoming_dues = C4::Circulation::GetUpcomingDueIssues( { days_in_advance => 0 } );
435     is ( scalar ( @$upcoming_dues), 1, "1 item is due today" );
436
437     $upcoming_dues = C4::Circulation::GetUpcomingDueIssues( { days_in_advance => 1 } );
438     is ( scalar ( @$upcoming_dues), 1, "1 item is due today, none tomorrow" );
439
440     $upcoming_dues = C4::Circulation::GetUpcomingDueIssues( { days_in_advance => 2 }  );
441     is ( scalar ( @$upcoming_dues), 2, "2 items are due withing 2 days" );
442
443     $upcoming_dues = C4::Circulation::GetUpcomingDueIssues( { days_in_advance => 3 } );
444     is ( scalar ( @$upcoming_dues), 2, "2 items are due withing 2 days" );
445
446     $upcoming_dues = C4::Circulation::GetUpcomingDueIssues();
447     is ( scalar ( @$upcoming_dues), 2, "days_in_advance is 7 in GetUpcomingDueIssues if not provided" );
448
449 }
450
451 $dbh->rollback;