Bug 11218: regression tests for Due notice generation
[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 => 44;
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     # Create 2 borrowers
205     my %renewing_borrower_data = (
206         firstname =>  'John',
207         surname => 'Renewal',
208         categorycode => 'S',
209         branchcode => $branch,
210     );
211
212     my %reserving_borrower_data = (
213         firstname =>  'Katrin',
214         surname => 'Reservation',
215         categorycode => 'S',
216         branchcode => $branch,
217     );
218
219     my $renewing_borrowernumber = AddMember(%renewing_borrower_data);
220     my $reserving_borrowernumber = AddMember(%reserving_borrower_data);
221
222     my $renewing_borrower = GetMember( borrowernumber => $renewing_borrowernumber );
223
224     my $constraint     = 'a';
225     my $bibitems       = '';
226     my $priority       = '1';
227     my $resdate        = undef;
228     my $expdate        = undef;
229     my $notes          = '';
230     my $checkitem      = undef;
231     my $found          = undef;
232
233     my $datedue = AddIssue( $renewing_borrower, $barcode);
234     is (defined $datedue, 1, "Item 1 checked out, due date: $datedue");
235
236     my $datedue2 = AddIssue( $renewing_borrower, $barcode2);
237     is (defined $datedue2, 1, "Item 2 checked out, due date: $datedue2");
238
239     my $borrowing_borrowernumber = GetItemIssue($itemnumber)->{borrowernumber};
240     is ($borrowing_borrowernumber, $renewing_borrowernumber, "Item checked out to $renewing_borrower->{firstname} $renewing_borrower->{surname}");
241
242     my ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $itemnumber, 1);
243     is( $renewokay, 1, 'Can renew, no holds for this title or item');
244
245
246     diag("Biblio-level hold, renewal test");
247     AddReserve(
248         $branch, $reserving_borrowernumber, $biblionumber,
249         $constraint, $bibitems,  $priority, $resdate, $expdate, $notes,
250         $title, $checkitem, $found
251     );
252
253     ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $itemnumber);
254     is( $renewokay, 0, '(Bug 10663) Cannot renew, reserved');
255     is( $error, 'on_reserve', '(Bug 10663) Cannot renew, reserved (returned error is on_reserve)');
256
257     ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $itemnumber2);
258     is( $renewokay, 0, '(Bug 10663) Cannot renew, reserved');
259     is( $error, 'on_reserve', '(Bug 10663) Cannot renew, reserved (returned error is on_reserve)');
260
261     my $reserveid = C4::Reserves::GetReserveId({ biblionumber => $biblionumber, borrowernumber => $reserving_borrowernumber});
262     CancelReserve({ reserve_id => $reserveid });
263
264
265     diag("Item-level hold, renewal test");
266     AddReserve(
267         $branch, $reserving_borrowernumber, $biblionumber,
268         $constraint, $bibitems,  $priority, $resdate, $expdate, $notes,
269         $title, $itemnumber, $found
270     );
271
272     ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $itemnumber, 1);
273     is( $renewokay, 0, '(Bug 10663) Cannot renew, item reserved');
274     is( $error, 'on_reserve', '(Bug 10663) Cannot renew, item reserved (returned error is on_reserve)');
275
276     ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $itemnumber2, 1);
277     is( $renewokay, 1, 'Can renew item 2, item-level hold is on item 1');
278
279
280     diag("Items can't fill hold for reasons");
281     ModItem({ notforloan => 1 }, $biblionumber, $itemnumber);
282     ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $itemnumber, 1);
283     is( $renewokay, 1, 'Can renew, item is marked not for loan, hold does not block');
284     ModItem({ notforloan => 0, itype => '' }, $biblionumber, $itemnumber,1);
285
286     # FIXME: Add more for itemtype not for loan etc.
287
288     $reserveid = C4::Reserves::GetReserveId({ biblionumber => $biblionumber, itemnumber => $itemnumber, borrowernumber => $reserving_borrowernumber});
289     CancelReserve({ reserve_id => $reserveid });
290
291     diag("Too many renewals");
292
293     # set policy to forbid renewals
294     $dbh->do('UPDATE issuingrules SET renewalsallowed = 0');
295
296     ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $itemnumber);
297     is( $renewokay, 0, 'Cannot renew, 0 renewals allowed');
298     is( $error, 'too_many', 'Cannot renew, 0 renewals allowed (returned code is too_many)');
299
300     # Test WhenLostForgiveFine and WhenLostChargeReplacementFee
301     diag("WhenLostForgiveFine and WhenLostChargeReplacementFee");
302     C4::Context->set_preference('WhenLostForgiveFine','1');
303     C4::Context->set_preference('WhenLostChargeReplacementFee','1');
304
305     C4::Overdues::UpdateFine( $itemnumber, $renewing_borrower->{borrowernumber},
306         15.00, q{}, Koha::DateUtils::output_pref($datedue) );
307
308     LostItem( $itemnumber, 1 );
309
310     my $total_due = $dbh->selectrow_array(
311         'SELECT SUM( amountoutstanding ) FROM accountlines WHERE borrowernumber = ?',
312         undef, $renewing_borrower->{borrowernumber}
313     );
314
315     ok( $total_due == 12, 'Borrower only charged replacement fee with both WhenLostForgiveFine and WhenLostChargeReplacementFee enabled' );
316
317     C4::Context->dbh->do("DELETE FROM accountlines");
318
319     C4::Context->set_preference('WhenLostForgiveFine','0');
320     C4::Context->set_preference('WhenLostChargeReplacementFee','0');
321
322     C4::Overdues::UpdateFine( $itemnumber2, $renewing_borrower->{borrowernumber},
323         15.00, q{}, Koha::DateUtils::output_pref($datedue) );
324
325     LostItem( $itemnumber2, 1 );
326
327     $total_due = $dbh->selectrow_array(
328         'SELECT SUM( amountoutstanding ) FROM accountlines WHERE borrowernumber = ?',
329         undef, $renewing_borrower->{borrowernumber}
330     );
331
332     ok( $total_due == 15, 'Borrower only charged fine with both WhenLostForgiveFine and WhenLostChargeReplacementFee disabled' );
333 }
334
335 {
336     # GetUpcomingDueIssues tests
337     my $barcode  = 'R00000342';
338     my $barcode2 = 'R00000343';
339     my $barcode3 = 'R00000344';
340     my $branch   = 'MPL';
341
342     #Create another record
343     my $biblio2 = MARC::Record->new();
344     my $title2 = 'Something is worng here';
345     $biblio2->append_fields(
346         MARC::Field->new('100', ' ', ' ', a => 'Anonymous'),
347         MARC::Field->new('245', ' ', ' ', a => $title2),
348     );
349     my ($biblionumber2, $biblioitemnumber2) = AddBiblio($biblio2, '');
350
351     #Create third item
352     AddItem(
353         {
354             homebranch       => $branch,
355             holdingbranch    => $branch,
356             barcode          => $barcode3
357         },
358         $biblionumber2
359     );
360
361     # Create a borrower
362     my %a_borrower_data = (
363         firstname =>  'Fridolyn',
364         surname => 'SOMERS',
365         categorycode => 'S',
366         branchcode => $branch,
367     );
368
369     my $a_borrower_borrowernumber = AddMember(%a_borrower_data);
370     my $a_borrower = GetMember( borrowernumber => $a_borrower_borrowernumber );
371
372     my $yesterday = DateTime->today(time_zone => C4::Context->tz())->add( days => -1 );
373     my $two_days_ahead = DateTime->today(time_zone => C4::Context->tz())->add( days => 2 );
374     my $today = DateTime->today(time_zone => C4::Context->tz());
375
376     my $datedue  = AddIssue( $a_borrower, $barcode, $yesterday );
377     my $datedue2 = AddIssue( $a_borrower, $barcode2, $two_days_ahead );
378
379     my $upcoming_dues;
380
381     diag( "GetUpcomingDueIssues tests" );
382
383     for my $i(0..1) {
384         $upcoming_dues = C4::Circulation::GetUpcomingDueIssues( { days_in_advance => $i } );
385         is ( scalar( @$upcoming_dues ), 0, "No items due in less than one day ($i days in advance)" );
386     }
387
388     #days_in_advance needs to be inclusive, so 1 matches items due tomorrow, 0 items due today etc.
389         $upcoming_dues = C4::Circulation::GetUpcomingDueIssues( { days_in_advance => 2 } );
390     is ( scalar ( @$upcoming_dues), 1, "Only one item due in 2 days or less" );
391
392     for my $i(3..5) {
393         $upcoming_dues = C4::Circulation::GetUpcomingDueIssues( { days_in_advance => $i } );
394         is ( scalar( @$upcoming_dues ), 1,
395             "Bug 9362: Only one item due in more than 2 days ($i days in advance)" );
396     }
397
398     # Bug 11218 - Due notices not generated - GetUpcomingDueIssues needs to select due today items as well
399
400     my $datedue2 = AddIssue( $a_borrower, $barcode3, $today );
401
402     $upcoming_dues = C4::Circulation::GetUpcomingDueIssues( { days_in_advance => -1 } );
403     is ( scalar ( @$upcoming_dues), 0, "Overdues can not be selected" );
404
405     $upcoming_dues = C4::Circulation::GetUpcomingDueIssues( { days_in_advance => 0 } );
406     is ( scalar ( @$upcoming_dues), 1, "1 item is due today" );
407
408     $upcoming_dues = C4::Circulation::GetUpcomingDueIssues( { days_in_advance => 1 } );
409     is ( scalar ( @$upcoming_dues), 1, "1 item is due today, none tomorrow" );
410
411     $upcoming_dues = C4::Circulation::GetUpcomingDueIssues( { days_in_advance => 2 }  );
412     is ( scalar ( @$upcoming_dues), 2, "2 items are due withing 2 days" );
413
414     $upcoming_dues = C4::Circulation::GetUpcomingDueIssues( { days_in_advance => 3 } );
415     is ( scalar ( @$upcoming_dues), 2, "2 items are due withing 2 days" );
416
417     $upcoming_dues = C4::Circulation::GetUpcomingDueIssues();
418     is ( scalar ( @$upcoming_dues), 2, "days_in_advance is 7 in GetUpcomingDueIssues if not provided" );
419
420 }
421
422 $dbh->rollback;