Bug 17708: Fix use statements
[koha.git] / t / db_dependent / Circulation.t
1 #!/usr/bin/perl
2
3 # This file is part of Koha.
4 #
5 # Koha is free software; you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 3 of the License, or
8 # (at your option) any later version.
9 #
10 # Koha is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18 use Modern::Perl;
19
20 use Test::More tests => 93;
21
22 use DateTime;
23
24 use t::lib::Mocks;
25 use t::lib::TestBuilder;
26
27 use C4::Circulation;
28 use C4::Biblio;
29 use C4::Items;
30 use C4::Log;
31 use C4::Members;
32 use C4::Reserves;
33 use C4::Overdues qw(UpdateFine CalcFine);
34 use Koha::DateUtils;
35 use Koha::Database;
36 use Koha::IssuingRules;
37 use Koha::Subscriptions;
38
39 my $schema = Koha::Database->schema;
40 $schema->storage->txn_begin;
41 my $builder = t::lib::TestBuilder->new;
42 my $dbh = C4::Context->dbh;
43
44 # Start transaction
45 $dbh->{RaiseError} = 1;
46
47 # Start with a clean slate
48 $dbh->do('DELETE FROM issues');
49
50 my $library = $builder->build({
51     source => 'Branch',
52 });
53 my $library2 = $builder->build({
54     source => 'Branch',
55 });
56 my $itemtype = $builder->build(
57     {   source => 'Itemtype',
58         value  => { notforloan => undef, rentalcharge => 0 }
59     }
60 )->{itemtype};
61
62 my $CircControl = C4::Context->preference('CircControl');
63 my $HomeOrHoldingBranch = C4::Context->preference('HomeOrHoldingBranch');
64
65 my $item = {
66     homebranch => $library2->{branchcode},
67     holdingbranch => $library2->{branchcode}
68 };
69
70 my $borrower = {
71     branchcode => $library2->{branchcode}
72 };
73
74 # No userenv, PickupLibrary
75 t::lib::Mocks::mock_preference('CircControl', 'PickupLibrary');
76 is(
77     C4::Context->preference('CircControl'),
78     'PickupLibrary',
79     'CircControl changed to PickupLibrary'
80 );
81 is(
82     C4::Circulation::_GetCircControlBranch($item, $borrower),
83     $item->{$HomeOrHoldingBranch},
84     '_GetCircControlBranch returned item branch (no userenv defined)'
85 );
86
87 # No userenv, PatronLibrary
88 t::lib::Mocks::mock_preference('CircControl', 'PatronLibrary');
89 is(
90     C4::Context->preference('CircControl'),
91     'PatronLibrary',
92     'CircControl changed to PatronLibrary'
93 );
94 is(
95     C4::Circulation::_GetCircControlBranch($item, $borrower),
96     $borrower->{branchcode},
97     '_GetCircControlBranch returned borrower branch'
98 );
99
100 # No userenv, ItemHomeLibrary
101 t::lib::Mocks::mock_preference('CircControl', 'ItemHomeLibrary');
102 is(
103     C4::Context->preference('CircControl'),
104     'ItemHomeLibrary',
105     'CircControl changed to ItemHomeLibrary'
106 );
107 is(
108     $item->{$HomeOrHoldingBranch},
109     C4::Circulation::_GetCircControlBranch($item, $borrower),
110     '_GetCircControlBranch returned item branch'
111 );
112
113 # Now, set a userenv
114 C4::Context->_new_userenv('xxx');
115 C4::Context->set_userenv(0,0,0,'firstname','surname', $library2->{branchcode}, 'Midway Public Library', '', '', '');
116 is(C4::Context->userenv->{branch}, $library2->{branchcode}, 'userenv set');
117
118 # Userenv set, PickupLibrary
119 t::lib::Mocks::mock_preference('CircControl', 'PickupLibrary');
120 is(
121     C4::Context->preference('CircControl'),
122     'PickupLibrary',
123     'CircControl changed to PickupLibrary'
124 );
125 is(
126     C4::Circulation::_GetCircControlBranch($item, $borrower),
127     $library2->{branchcode},
128     '_GetCircControlBranch returned current branch'
129 );
130
131 # Userenv set, PatronLibrary
132 t::lib::Mocks::mock_preference('CircControl', 'PatronLibrary');
133 is(
134     C4::Context->preference('CircControl'),
135     'PatronLibrary',
136     'CircControl changed to PatronLibrary'
137 );
138 is(
139     C4::Circulation::_GetCircControlBranch($item, $borrower),
140     $borrower->{branchcode},
141     '_GetCircControlBranch returned borrower branch'
142 );
143
144 # Userenv set, ItemHomeLibrary
145 t::lib::Mocks::mock_preference('CircControl', 'ItemHomeLibrary');
146 is(
147     C4::Context->preference('CircControl'),
148     'ItemHomeLibrary',
149     'CircControl changed to ItemHomeLibrary'
150 );
151 is(
152     C4::Circulation::_GetCircControlBranch($item, $borrower),
153     $item->{$HomeOrHoldingBranch},
154     '_GetCircControlBranch returned item branch'
155 );
156
157 # Reset initial configuration
158 t::lib::Mocks::mock_preference('CircControl', $CircControl);
159 is(
160     C4::Context->preference('CircControl'),
161     $CircControl,
162     'CircControl reset to its initial value'
163 );
164
165 # Set a simple circ policy
166 $dbh->do('DELETE FROM issuingrules');
167 $dbh->do(
168     q{INSERT INTO issuingrules (categorycode, branchcode, itemtype, reservesallowed,
169                                 maxissueqty, issuelength, lengthunit,
170                                 renewalsallowed, renewalperiod,
171                                 norenewalbefore, auto_renew,
172                                 fine, chargeperiod)
173       VALUES (?, ?, ?, ?,
174               ?, ?, ?,
175               ?, ?,
176               ?, ?,
177               ?, ?
178              )
179     },
180     {},
181     '*', '*', '*', 25,
182     20, 14, 'days',
183     1, 7,
184     undef, 0,
185     .10, 1
186 );
187
188 # Test C4::Circulation::ProcessOfflinePayment
189 my $sth = C4::Context->dbh->prepare("SELECT COUNT(*) FROM accountlines WHERE amount = '-123.45' AND accounttype = 'Pay'");
190 $sth->execute();
191 my ( $original_count ) = $sth->fetchrow_array();
192
193 C4::Context->dbh->do("INSERT INTO borrowers ( cardnumber, surname, firstname, categorycode, branchcode ) VALUES ( '99999999999', 'Hall', 'Kyle', 'S', ? )", undef, $library2->{branchcode} );
194
195 C4::Circulation::ProcessOfflinePayment({ cardnumber => '99999999999', amount => '123.45' });
196
197 $sth->execute();
198 my ( $new_count ) = $sth->fetchrow_array();
199
200 ok( $new_count == $original_count  + 1, 'ProcessOfflinePayment makes payment correctly' );
201
202 C4::Context->dbh->do("DELETE FROM accountlines WHERE borrowernumber IN ( SELECT borrowernumber FROM borrowers WHERE cardnumber = '99999999999' )");
203 C4::Context->dbh->do("DELETE FROM borrowers WHERE cardnumber = '99999999999'");
204 C4::Context->dbh->do("DELETE FROM accountlines");
205 {
206 # CanBookBeRenewed tests
207
208     # Generate test biblio
209     my $biblio = MARC::Record->new();
210     my $title = 'Silence in the library';
211     $biblio->append_fields(
212         MARC::Field->new('100', ' ', ' ', a => 'Moffat, Steven'),
213         MARC::Field->new('245', ' ', ' ', a => $title),
214     );
215
216     my ($biblionumber, $biblioitemnumber) = AddBiblio($biblio, '');
217
218     my $barcode = 'R00000342';
219     my $branch = $library2->{branchcode};
220
221     my ( $item_bibnum, $item_bibitemnum, $itemnumber ) = AddItem(
222         {
223             homebranch       => $branch,
224             holdingbranch    => $branch,
225             barcode          => $barcode,
226             replacementprice => 12.00,
227             itype            => $itemtype
228         },
229         $biblionumber
230     );
231
232     my $barcode2 = 'R00000343';
233     my ( $item_bibnum2, $item_bibitemnum2, $itemnumber2 ) = AddItem(
234         {
235             homebranch       => $branch,
236             holdingbranch    => $branch,
237             barcode          => $barcode2,
238             replacementprice => 23.00,
239             itype            => $itemtype
240         },
241         $biblionumber
242     );
243
244     my $barcode3 = 'R00000346';
245     my ( $item_bibnum3, $item_bibitemnum3, $itemnumber3 ) = AddItem(
246         {
247             homebranch       => $branch,
248             holdingbranch    => $branch,
249             barcode          => $barcode3,
250             replacementprice => 23.00,
251             itype            => $itemtype
252         },
253         $biblionumber
254     );
255
256
257
258
259     # Create borrowers
260     my %renewing_borrower_data = (
261         firstname =>  'John',
262         surname => 'Renewal',
263         categorycode => 'S',
264         branchcode => $branch,
265     );
266
267     my %reserving_borrower_data = (
268         firstname =>  'Katrin',
269         surname => 'Reservation',
270         categorycode => 'S',
271         branchcode => $branch,
272     );
273
274     my %hold_waiting_borrower_data = (
275         firstname =>  'Kyle',
276         surname => 'Reservation',
277         categorycode => 'S',
278         branchcode => $branch,
279     );
280
281     my %restricted_borrower_data = (
282         firstname =>  'Alice',
283         surname => 'Reservation',
284         categorycode => 'S',
285         debarred => '3228-01-01',
286         branchcode => $branch,
287     );
288
289     my $renewing_borrowernumber = AddMember(%renewing_borrower_data);
290     my $reserving_borrowernumber = AddMember(%reserving_borrower_data);
291     my $hold_waiting_borrowernumber = AddMember(%hold_waiting_borrower_data);
292     my $restricted_borrowernumber = AddMember(%restricted_borrower_data);
293
294     my $renewing_borrower = GetMember( borrowernumber => $renewing_borrowernumber );
295     my $restricted_borrower = GetMember( borrowernumber => $restricted_borrowernumber );
296
297     my $bibitems       = '';
298     my $priority       = '1';
299     my $resdate        = undef;
300     my $expdate        = undef;
301     my $notes          = '';
302     my $checkitem      = undef;
303     my $found          = undef;
304
305     my $issue = AddIssue( $renewing_borrower, $barcode);
306     my $datedue = dt_from_string( $issue->date_due() );
307     is (defined $issue->date_due(), 1, "Item 1 checked out, due date: " . $issue->date_due() );
308
309     my $issue2 = AddIssue( $renewing_borrower, $barcode2);
310     $datedue = dt_from_string( $issue->date_due() );
311     is (defined $issue2, 1, "Item 2 checked out, due date: " . $issue2->date_due());
312
313
314     my $borrowing_borrowernumber = GetItemIssue($itemnumber)->{borrowernumber};
315     is ($borrowing_borrowernumber, $renewing_borrowernumber, "Item checked out to $renewing_borrower->{firstname} $renewing_borrower->{surname}");
316
317     my ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $itemnumber, 1);
318     is( $renewokay, 1, 'Can renew, no holds for this title or item');
319
320
321     # Biblio-level hold, renewal test
322     AddReserve(
323         $branch, $reserving_borrowernumber, $biblionumber,
324         $bibitems,  $priority, $resdate, $expdate, $notes,
325         $title, $checkitem, $found
326     );
327
328     # Testing of feature to allow the renewal of reserved items if other items on the record can fill all needed holds
329     C4::Context->dbh->do("UPDATE issuingrules SET onshelfholds = 1");
330     t::lib::Mocks::mock_preference('AllowRenewalIfOtherItemsAvailable', 1 );
331     ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $itemnumber);
332     is( $renewokay, 1, 'Bug 11634 - Allow renewal of item with unfilled holds if other available items can fill those holds');
333     ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $itemnumber2);
334     is( $renewokay, 1, 'Bug 11634 - Allow renewal of item with unfilled holds if other available items can fill those holds');
335
336     # Now let's add an item level hold, we should no longer be able to renew the item
337     my $hold = Koha::Database->new()->schema()->resultset('Reserve')->create(
338         {
339             borrowernumber => $hold_waiting_borrowernumber,
340             biblionumber   => $biblionumber,
341             itemnumber     => $itemnumber,
342             branchcode     => $branch,
343             priority       => 3,
344         }
345     );
346     ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $itemnumber);
347     is( $renewokay, 0, 'Bug 13919 - Renewal possible with item level hold on item');
348     $hold->delete();
349
350     # Now let's add a waiting hold on the 3rd item, it's no longer available tp check out by just anyone, so we should no longer
351     # be able to renew these items
352     $hold = Koha::Database->new()->schema()->resultset('Reserve')->create(
353         {
354             borrowernumber => $hold_waiting_borrowernumber,
355             biblionumber   => $biblionumber,
356             itemnumber     => $itemnumber3,
357             branchcode     => $branch,
358             priority       => 0,
359             found          => 'W'
360         }
361     );
362     ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $itemnumber);
363     is( $renewokay, 0, 'Bug 11634 - Allow renewal of item with unfilled holds if other available items can fill those holds');
364     ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $itemnumber2);
365     is( $renewokay, 0, 'Bug 11634 - Allow renewal of item with unfilled holds if other available items can fill those holds');
366     t::lib::Mocks::mock_preference('AllowRenewalIfOtherItemsAvailable', 0 );
367
368     ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $itemnumber);
369     is( $renewokay, 0, '(Bug 10663) Cannot renew, reserved');
370     is( $error, 'on_reserve', '(Bug 10663) Cannot renew, reserved (returned error is on_reserve)');
371
372     ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $itemnumber2);
373     is( $renewokay, 0, '(Bug 10663) Cannot renew, reserved');
374     is( $error, 'on_reserve', '(Bug 10663) Cannot renew, reserved (returned error is on_reserve)');
375
376     my $reserveid = C4::Reserves::GetReserveId({ biblionumber => $biblionumber, borrowernumber => $reserving_borrowernumber});
377     my $reserving_borrower = GetMember( borrowernumber => $reserving_borrowernumber );
378     AddIssue($reserving_borrower, $barcode3);
379     my $reserve = $dbh->selectrow_hashref(
380         'SELECT * FROM old_reserves WHERE reserve_id = ?',
381         { Slice => {} },
382         $reserveid
383     );
384     is($reserve->{found}, 'F', 'hold marked completed when checking out item that fills it');
385
386     # Item-level hold, renewal test
387     AddReserve(
388         $branch, $reserving_borrowernumber, $biblionumber,
389         $bibitems,  $priority, $resdate, $expdate, $notes,
390         $title, $itemnumber, $found
391     );
392
393     ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $itemnumber, 1);
394     is( $renewokay, 0, '(Bug 10663) Cannot renew, item reserved');
395     is( $error, 'on_reserve', '(Bug 10663) Cannot renew, item reserved (returned error is on_reserve)');
396
397     ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $itemnumber2, 1);
398     is( $renewokay, 1, 'Can renew item 2, item-level hold is on item 1');
399
400     # Items can't fill hold for reasons
401     ModItem({ notforloan => 1 }, $biblionumber, $itemnumber);
402     ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $itemnumber, 1);
403     is( $renewokay, 1, 'Can renew, item is marked not for loan, hold does not block');
404     ModItem({ notforloan => 0, itype => $itemtype }, $biblionumber, $itemnumber,1);
405
406     # FIXME: Add more for itemtype not for loan etc.
407
408     # Restricted users cannot renew when RestrictionBlockRenewing is enabled
409     my $barcode5 = 'R00000347';
410     my ( $item_bibnum5, $item_bibitemnum5, $itemnumber5 ) = AddItem(
411         {
412             homebranch       => $branch,
413             holdingbranch    => $branch,
414             barcode          => $barcode5,
415             replacementprice => 23.00,
416             itype            => $itemtype
417         },
418         $biblionumber
419     );
420     my $datedue5 = AddIssue($restricted_borrower, $barcode5);
421     is (defined $datedue5, 1, "Item with date due checked out, due date: $datedue5");
422
423     t::lib::Mocks::mock_preference('RestrictionBlockRenewing','1');
424     ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $itemnumber2);
425     is( $renewokay, 1, '(Bug 8236), Can renew, user is not restricted');
426     ( $renewokay, $error ) = CanBookBeRenewed($restricted_borrowernumber, $itemnumber5);
427     is( $renewokay, 0, '(Bug 8236), Cannot renew, user is restricted');
428
429     # Users cannot renew an overdue item
430     my $barcode6 = 'R00000348';
431     my ( $item_bibnum6, $item_bibitemnum6, $itemnumber6 ) = AddItem(
432         {
433             homebranch       => $branch,
434             holdingbranch    => $branch,
435             barcode          => $barcode6,
436             replacementprice => 23.00,
437             itype            => $itemtype
438         },
439         $biblionumber
440     );
441
442     my $barcode7 = 'R00000349';
443     my ( $item_bibnum7, $item_bibitemnum7, $itemnumber7 ) = AddItem(
444         {
445             homebranch       => $branch,
446             holdingbranch    => $branch,
447             barcode          => $barcode7,
448             replacementprice => 23.00,
449             itype            => $itemtype
450         },
451         $biblionumber
452     );
453     my $datedue6 = AddIssue( $renewing_borrower, $barcode6);
454     is (defined $datedue6, 1, "Item 2 checked out, due date: $datedue6");
455
456     my $now = dt_from_string();
457     my $five_weeks = DateTime::Duration->new(weeks => 5);
458     my $five_weeks_ago = $now - $five_weeks;
459
460     my $passeddatedue1 = AddIssue($renewing_borrower, $barcode7, $five_weeks_ago);
461     is (defined $passeddatedue1, 1, "Item with passed date due checked out, due date: " . $passeddatedue1->date_due);
462
463     my ( $fine ) = CalcFine( GetItem(undef, $barcode7), $renewing_borrower->{categorycode}, $branch, $five_weeks_ago, $now );
464     C4::Overdues::UpdateFine(
465         {
466             issue_id       => $passeddatedue1->id(),
467             itemnumber     => $itemnumber7,
468             borrowernumber => $renewing_borrower->{borrowernumber},
469             amount         => $fine,
470             type           => 'FU',
471             due            => Koha::DateUtils::output_pref($five_weeks_ago)
472         }
473     );
474     t::lib::Mocks::mock_preference('RenewalLog', 0);
475     my $date = output_pref( { dt => dt_from_string(), datenonly => 1, dateformat => 'iso' } );
476     my $old_log_size =  scalar(@{GetLogs( $date, $date, undef,["CIRCULATION"], ["RENEW"]) } );
477     AddRenewal( $renewing_borrower->{borrowernumber}, $itemnumber7, $branch );
478     my $new_log_size =  scalar(@{GetLogs( $date, $date, undef,["CIRCULATION"], ["RENEW"]) } );
479     is ($new_log_size, $old_log_size, 'renew log not added because of the syspref RenewalLog');
480
481     t::lib::Mocks::mock_preference('RenewalLog', 1);
482     $date = output_pref( { dt => dt_from_string(), datenonly => 1, dateformat => 'iso' } );
483     $old_log_size =  scalar(@{GetLogs( $date, $date, undef,["CIRCULATION"], ["RENEW"]) } );
484     AddRenewal( $renewing_borrower->{borrowernumber}, $itemnumber7, $branch );
485     $new_log_size =  scalar(@{GetLogs( $date, $date, undef,["CIRCULATION"], ["RENEW"]) } );
486     is ($new_log_size, $old_log_size + 1, 'renew log successfully added');
487
488
489     $fine = $schema->resultset('Accountline')->single( { borrowernumber => $renewing_borrower->{borrowernumber}, itemnumber => $itemnumber7 } );
490     is( $fine->accounttype, 'F', 'Fine on renewed item is closed out properly' );
491     $fine->delete();
492
493     t::lib::Mocks::mock_preference('OverduesBlockRenewing','blockitem');
494     ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $itemnumber6);
495     is( $renewokay, 1, '(Bug 8236), Can renew, this item is not overdue');
496     ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $itemnumber7);
497     is( $renewokay, 0, '(Bug 8236), Cannot renew, this item is overdue');
498
499
500     $reserveid = C4::Reserves::GetReserveId({ biblionumber => $biblionumber, itemnumber => $itemnumber, borrowernumber => $reserving_borrowernumber});
501     CancelReserve({ reserve_id => $reserveid });
502
503     # Bug 14101
504     # Test automatic renewal before value for "norenewalbefore" in policy is set
505     # In this case automatic renewal is not permitted prior to due date
506     my $barcode4 = '11235813';
507     my ( $item_bibnum4, $item_bibitemnum4, $itemnumber4 ) = AddItem(
508         {
509             homebranch       => $branch,
510             holdingbranch    => $branch,
511             barcode          => $barcode4,
512             replacementprice => 16.00,
513             itype            => $itemtype
514         },
515         $biblionumber
516     );
517
518     $issue = AddIssue( $renewing_borrower, $barcode4, undef, undef, undef, undef, { auto_renew => 1 } );
519     ( $renewokay, $error ) =
520       CanBookBeRenewed( $renewing_borrowernumber, $itemnumber4 );
521     is( $renewokay, 0, 'Bug 14101: Cannot renew, renewal is automatic and premature' );
522     is( $error, 'auto_too_soon',
523         'Bug 14101: Cannot renew, renewal is automatic and premature, "No renewal before" = undef (returned code is auto_too_soon)' );
524
525     # Bug 7413
526     # Test premature manual renewal
527     $dbh->do('UPDATE issuingrules SET norenewalbefore = 7');
528
529     ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $itemnumber);
530     is( $renewokay, 0, 'Bug 7413: Cannot renew, renewal is premature');
531     is( $error, 'too_soon', 'Bug 7413: Cannot renew, renewal is premature (returned code is too_soon)');
532
533     # Bug 14395
534     # Test 'exact time' setting for syspref NoRenewalBeforePrecision
535     t::lib::Mocks::mock_preference( 'NoRenewalBeforePrecision', 'exact_time' );
536     is(
537         GetSoonestRenewDate( $renewing_borrowernumber, $itemnumber ),
538         $datedue->clone->add( days => -7 ),
539         'Bug 14395: Renewals permitted 7 days before due date, as expected'
540     );
541
542     # Bug 14395
543     # Test 'date' setting for syspref NoRenewalBeforePrecision
544     t::lib::Mocks::mock_preference( 'NoRenewalBeforePrecision', 'date' );
545     is(
546         GetSoonestRenewDate( $renewing_borrowernumber, $itemnumber ),
547         $datedue->clone->add( days => -7 )->truncate( to => 'day' ),
548         'Bug 14395: Renewals permitted 7 days before due date, as expected'
549     );
550
551     # Bug 14101
552     # Test premature automatic renewal
553     ( $renewokay, $error ) =
554       CanBookBeRenewed( $renewing_borrowernumber, $itemnumber4 );
555     is( $renewokay, 0, 'Bug 14101: Cannot renew, renewal is automatic and premature' );
556     is( $error, 'auto_too_soon',
557         'Bug 14101: Cannot renew, renewal is automatic and premature (returned code is auto_too_soon)'
558     );
559
560     # Change policy so that loans can only be renewed exactly on due date (0 days prior to due date)
561     # and test automatic renewal again
562     $dbh->do('UPDATE issuingrules SET norenewalbefore = 0');
563     ( $renewokay, $error ) =
564       CanBookBeRenewed( $renewing_borrowernumber, $itemnumber4 );
565     is( $renewokay, 0, 'Bug 14101: Cannot renew, renewal is automatic and premature' );
566     is( $error, 'auto_too_soon',
567         'Bug 14101: Cannot renew, renewal is automatic and premature, "No renewal before" = 0 (returned code is auto_too_soon)'
568     );
569
570     # Change policy so that loans can be renewed 99 days prior to the due date
571     # and test automatic renewal again
572     $dbh->do('UPDATE issuingrules SET norenewalbefore = 99');
573     ( $renewokay, $error ) =
574       CanBookBeRenewed( $renewing_borrowernumber, $itemnumber4 );
575     is( $renewokay, 0, 'Bug 14101: Cannot renew, renewal is automatic' );
576     is( $error, 'auto_renew',
577         'Bug 14101: Cannot renew, renewal is automatic (returned code is auto_renew)'
578     );
579
580     subtest "too_late_renewal / no_auto_renewal_after" => sub {
581         plan tests => 8;
582         my $item_to_auto_renew = $builder->build(
583             {   source => 'Item',
584                 value  => {
585                     biblionumber  => $biblionumber,
586                     homebranch    => $branch,
587                     holdingbranch => $branch,
588                 }
589             }
590         );
591
592         my $ten_days_before = dt_from_string->add( days => -10 );
593         my $ten_days_ahead  = dt_from_string->add( days => 10 );
594         AddIssue( $renewing_borrower, $item_to_auto_renew->{barcode}, $ten_days_ahead, undef, $ten_days_before, undef, { auto_renew => 1 } );
595
596         $dbh->do('UPDATE issuingrules SET norenewalbefore = 7, no_auto_renewal_after = 9');
597         ( $renewokay, $error ) =
598           CanBookBeRenewed( $renewing_borrowernumber, $item_to_auto_renew->{itemnumber} );
599         is( $renewokay, 0, 'Do not renew, renewal is automatic' );
600         is( $error, 'auto_too_late', 'Cannot renew, too late(returned code is auto_too_late)' );
601
602         $dbh->do('UPDATE issuingrules SET norenewalbefore = 7, no_auto_renewal_after = 10');
603         ( $renewokay, $error ) =
604           CanBookBeRenewed( $renewing_borrowernumber, $item_to_auto_renew->{itemnumber} );
605         is( $renewokay, 0, 'Do not renew, renewal is automatic' );
606         is( $error, 'auto_too_late', 'Cannot auto renew, too late - no_auto_renewal_after is inclusive(returned code is auto_too_late)' );
607
608         $dbh->do('UPDATE issuingrules SET norenewalbefore = 7, no_auto_renewal_after = 11');
609         ( $renewokay, $error ) =
610           CanBookBeRenewed( $renewing_borrowernumber, $item_to_auto_renew->{itemnumber} );
611         is( $renewokay, 0, 'Do not renew, renewal is automatic' );
612         is( $error, 'auto_too_soon', 'Cannot auto renew, too soon - no_auto_renewal_after is defined(returned code is auto_too_soon)' );
613
614         $dbh->do('UPDATE issuingrules SET norenewalbefore = 10, no_auto_renewal_after = 11');
615         ( $renewokay, $error ) =
616           CanBookBeRenewed( $renewing_borrowernumber, $item_to_auto_renew->{itemnumber} );
617         is( $renewokay, 0,            'Do not renew, renewal is automatic' );
618         is( $error,     'auto_renew', 'Cannot renew, renew is automatic' );
619     };
620
621     subtest "GetLatestAutoRenewDate" => sub {
622         plan tests => 3;
623         my $item_to_auto_renew = $builder->build(
624             {   source => 'Item',
625                 value  => {
626                     biblionumber  => $biblionumber,
627                     homebranch    => $branch,
628                     holdingbranch => $branch,
629                 }
630             }
631         );
632
633         my $ten_days_before = dt_from_string->add( days => -10 );
634         my $ten_days_ahead  = dt_from_string->add( days => 10 );
635         AddIssue( $renewing_borrower, $item_to_auto_renew->{barcode}, $ten_days_ahead, undef, $ten_days_before, undef, { auto_renew => 1 } );
636         $dbh->do('UPDATE issuingrules SET norenewalbefore = 7, no_auto_renewal_after = ""');
637         my $latest_auto_renew_date = GetLatestAutoRenewDate( $renewing_borrowernumber, $item_to_auto_renew->{itemnumber} );
638         is( $latest_auto_renew_date, undef, 'GetLatestAutoRenewDate should return undef if no_auto_renewal_after is not defined' );
639         my $five_days_before = dt_from_string->add( days => -5 );
640         $dbh->do('UPDATE issuingrules SET norenewalbefore = 10, no_auto_renewal_after = 5');
641         $latest_auto_renew_date = GetLatestAutoRenewDate( $renewing_borrowernumber, $item_to_auto_renew->{itemnumber} );
642         is( $latest_auto_renew_date->truncate( to => 'minute' ),
643             $five_days_before->truncate( to => 'minute' ),
644             'GetLatestAutoRenewDate should return -5 days if no_auto_renewal_after = 5 and date_due is 10 days before'
645         );
646         my $five_days_ahead = dt_from_string->add( days => 5 );
647         $dbh->do('UPDATE issuingrules SET norenewalbefore = 10, no_auto_renewal_after = 15');
648         $latest_auto_renew_date = GetLatestAutoRenewDate( $renewing_borrowernumber, $item_to_auto_renew->{itemnumber} );
649         is( $latest_auto_renew_date->truncate( to => 'minute' ),
650             $five_days_ahead->truncate( to => 'minute' ),
651             'GetLatestAutoRenewDate should return +5 days if no_auto_renewal_after = 15 and date_due is 10 days before'
652         );
653     };
654
655     # Too many renewals
656
657     # set policy to forbid renewals
658     $dbh->do('UPDATE issuingrules SET norenewalbefore = NULL, renewalsallowed = 0');
659
660     ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $itemnumber);
661     is( $renewokay, 0, 'Cannot renew, 0 renewals allowed');
662     is( $error, 'too_many', 'Cannot renew, 0 renewals allowed (returned code is too_many)');
663
664     # Test WhenLostForgiveFine and WhenLostChargeReplacementFee
665     t::lib::Mocks::mock_preference('WhenLostForgiveFine','1');
666     t::lib::Mocks::mock_preference('WhenLostChargeReplacementFee','1');
667
668     C4::Overdues::UpdateFine(
669         {
670             issue_id       => $issue->id(),
671             itemnumber     => $itemnumber,
672             borrowernumber => $renewing_borrower->{borrowernumber},
673             amount         => 15.00,
674             type           => q{},
675             due            => Koha::DateUtils::output_pref($datedue)
676         }
677     );
678
679     LostItem( $itemnumber, 1 );
680
681     my $item = Koha::Database->new()->schema()->resultset('Item')->find($itemnumber);
682     ok( !$item->onloan(), "Lost item marked as returned has false onloan value" );
683
684     my $total_due = $dbh->selectrow_array(
685         'SELECT SUM( amountoutstanding ) FROM accountlines WHERE borrowernumber = ?',
686         undef, $renewing_borrower->{borrowernumber}
687     );
688
689     ok( $total_due == 12, 'Borrower only charged replacement fee with both WhenLostForgiveFine and WhenLostChargeReplacementFee enabled' );
690
691     C4::Context->dbh->do("DELETE FROM accountlines");
692
693     t::lib::Mocks::mock_preference('WhenLostForgiveFine','0');
694     t::lib::Mocks::mock_preference('WhenLostChargeReplacementFee','0');
695
696     C4::Overdues::UpdateFine(
697         {
698             issue_id       => $issue2->id(),
699             itemnumber     => $itemnumber2,
700             borrowernumber => $renewing_borrower->{borrowernumber},
701             amount         => 15.00,
702             type           => q{},
703             due            => Koha::DateUtils::output_pref($datedue)
704         }
705     );
706
707     LostItem( $itemnumber2, 0 );
708
709     my $item2 = Koha::Database->new()->schema()->resultset('Item')->find($itemnumber2);
710     ok( $item2->onloan(), "Lost item *not* marked as returned has true onloan value" );
711
712     $total_due = $dbh->selectrow_array(
713         'SELECT SUM( amountoutstanding ) FROM accountlines WHERE borrowernumber = ?',
714         undef, $renewing_borrower->{borrowernumber}
715     );
716
717     ok( $total_due == 15, 'Borrower only charged fine with both WhenLostForgiveFine and WhenLostChargeReplacementFee disabled' );
718
719     my $future = dt_from_string();
720     $future->add( days => 7 );
721     my $units = C4::Overdues::get_chargeable_units('days', $future, $now, $library2->{branchcode});
722     ok( $units == 0, '_get_chargeable_units returns 0 for items not past due date (Bug 12596)' );
723
724     # Users cannot renew any item if there is an overdue item
725     t::lib::Mocks::mock_preference('OverduesBlockRenewing','block');
726     ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $itemnumber6);
727     is( $renewokay, 0, '(Bug 8236), Cannot renew, one of the items is overdue');
728     ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $itemnumber7);
729     is( $renewokay, 0, '(Bug 8236), Cannot renew, one of the items is overdue');
730
731   }
732
733 {
734     # GetUpcomingDueIssues tests
735     my $barcode  = 'R00000342';
736     my $barcode2 = 'R00000343';
737     my $barcode3 = 'R00000344';
738     my $branch   = $library2->{branchcode};
739
740     #Create another record
741     my $biblio2 = MARC::Record->new();
742     my $title2 = 'Something is worng here';
743     $biblio2->append_fields(
744         MARC::Field->new('100', ' ', ' ', a => 'Anonymous'),
745         MARC::Field->new('245', ' ', ' ', a => $title2),
746     );
747     my ($biblionumber2, $biblioitemnumber2) = AddBiblio($biblio2, '');
748
749     #Create third item
750     AddItem(
751         {
752             homebranch       => $branch,
753             holdingbranch    => $branch,
754             barcode          => $barcode3,
755             itype            => $itemtype
756         },
757         $biblionumber2
758     );
759
760     # Create a borrower
761     my %a_borrower_data = (
762         firstname =>  'Fridolyn',
763         surname => 'SOMERS',
764         categorycode => 'S',
765         branchcode => $branch,
766     );
767
768     my $a_borrower_borrowernumber = AddMember(%a_borrower_data);
769     my $a_borrower = GetMember( borrowernumber => $a_borrower_borrowernumber );
770
771     my $yesterday = DateTime->today(time_zone => C4::Context->tz())->add( days => -1 );
772     my $two_days_ahead = DateTime->today(time_zone => C4::Context->tz())->add( days => 2 );
773     my $today = DateTime->today(time_zone => C4::Context->tz());
774
775     my $issue = AddIssue( $a_borrower, $barcode, $yesterday );
776     my $datedue = dt_from_string( $issue->date_due() );
777     my $issue2 = AddIssue( $a_borrower, $barcode2, $two_days_ahead );
778     my $datedue2 = dt_from_string( $issue->date_due() );
779
780     my $upcoming_dues;
781
782     # GetUpcomingDueIssues tests
783     for my $i(0..1) {
784         $upcoming_dues = C4::Circulation::GetUpcomingDueIssues( { days_in_advance => $i } );
785         is ( scalar( @$upcoming_dues ), 0, "No items due in less than one day ($i days in advance)" );
786     }
787
788     #days_in_advance needs to be inclusive, so 1 matches items due tomorrow, 0 items due today etc.
789     $upcoming_dues = C4::Circulation::GetUpcomingDueIssues( { days_in_advance => 2 } );
790     is ( scalar ( @$upcoming_dues), 1, "Only one item due in 2 days or less" );
791
792     for my $i(3..5) {
793         $upcoming_dues = C4::Circulation::GetUpcomingDueIssues( { days_in_advance => $i } );
794         is ( scalar( @$upcoming_dues ), 1,
795             "Bug 9362: Only one item due in more than 2 days ($i days in advance)" );
796     }
797
798     # Bug 11218 - Due notices not generated - GetUpcomingDueIssues needs to select due today items as well
799
800     my $issue3 = AddIssue( $a_borrower, $barcode3, $today );
801
802     $upcoming_dues = C4::Circulation::GetUpcomingDueIssues( { days_in_advance => -1 } );
803     is ( scalar ( @$upcoming_dues), 0, "Overdues can not be selected" );
804
805     $upcoming_dues = C4::Circulation::GetUpcomingDueIssues( { days_in_advance => 0 } );
806     is ( scalar ( @$upcoming_dues), 1, "1 item is due today" );
807
808     $upcoming_dues = C4::Circulation::GetUpcomingDueIssues( { days_in_advance => 1 } );
809     is ( scalar ( @$upcoming_dues), 1, "1 item is due today, none tomorrow" );
810
811     $upcoming_dues = C4::Circulation::GetUpcomingDueIssues( { days_in_advance => 2 }  );
812     is ( scalar ( @$upcoming_dues), 2, "2 items are due withing 2 days" );
813
814     $upcoming_dues = C4::Circulation::GetUpcomingDueIssues( { days_in_advance => 3 } );
815     is ( scalar ( @$upcoming_dues), 2, "2 items are due withing 2 days" );
816
817     $upcoming_dues = C4::Circulation::GetUpcomingDueIssues();
818     is ( scalar ( @$upcoming_dues), 2, "days_in_advance is 7 in GetUpcomingDueIssues if not provided" );
819
820 }
821
822 {
823     my $barcode  = '1234567890';
824     my $branch   = $library2->{branchcode};
825
826     my $biblio = MARC::Record->new();
827     my ($biblionumber, $biblioitemnumber) = AddBiblio($biblio, '');
828
829     #Create third item
830     my ( undef, undef, $itemnumber ) = AddItem(
831         {
832             homebranch       => $branch,
833             holdingbranch    => $branch,
834             barcode          => $barcode,
835             itype            => $itemtype
836         },
837         $biblionumber
838     );
839
840     # Create a borrower
841     my %a_borrower_data = (
842         firstname =>  'Kyle',
843         surname => 'Hall',
844         categorycode => 'S',
845         branchcode => $branch,
846     );
847
848     my $borrowernumber = AddMember(%a_borrower_data);
849
850     my $issue = AddIssue( GetMember( borrowernumber => $borrowernumber ), $barcode );
851     UpdateFine(
852         {
853             issue_id       => $issue->id(),
854             itemnumber     => $itemnumber,
855             borrowernumber => $borrowernumber,
856             amount         => 0,
857             type           => q{}
858         }
859     );
860
861     my $hr = $dbh->selectrow_hashref(q{SELECT COUNT(*) AS count FROM accountlines WHERE borrowernumber = ? AND itemnumber = ?}, undef, $borrowernumber, $itemnumber );
862     my $count = $hr->{count};
863
864     is ( $count, 0, "Calling UpdateFine on non-existant fine with an amount of 0 does not result in an empty fine" );
865 }
866
867 {
868     $dbh->do('DELETE FROM issues');
869     $dbh->do('DELETE FROM items');
870     $dbh->do('DELETE FROM issuingrules');
871     $dbh->do(
872         q{
873         INSERT INTO issuingrules ( categorycode, branchcode, itemtype, reservesallowed, maxissueqty, issuelength, lengthunit, renewalsallowed, renewalperiod,
874                     norenewalbefore, auto_renew, fine, chargeperiod ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )
875         },
876         {},
877         '*', '*', '*', 25,
878         20,  14,  'days',
879         1,   7,
880         undef,  0,
881         .10, 1
882     );
883     my $biblio = MARC::Record->new();
884     my ( $biblionumber, $biblioitemnumber ) = AddBiblio( $biblio, '' );
885
886     my $barcode1 = '1234';
887     my ( undef, undef, $itemnumber1 ) = AddItem(
888         {
889             homebranch    => $library2->{branchcode},
890             holdingbranch => $library2->{branchcode},
891             barcode       => $barcode1,
892             itype         => $itemtype
893         },
894         $biblionumber
895     );
896     my $barcode2 = '4321';
897     my ( undef, undef, $itemnumber2 ) = AddItem(
898         {
899             homebranch    => $library2->{branchcode},
900             holdingbranch => $library2->{branchcode},
901             barcode       => $barcode2,
902             itype         => $itemtype
903         },
904         $biblionumber
905     );
906
907     my $borrowernumber1 = AddMember(
908         firstname    => 'Kyle',
909         surname      => 'Hall',
910         categorycode => 'S',
911         branchcode   => $library2->{branchcode},
912     );
913     my $borrowernumber2 = AddMember(
914         firstname    => 'Chelsea',
915         surname      => 'Hall',
916         categorycode => 'S',
917         branchcode   => $library2->{branchcode},
918     );
919
920     my $borrower1 = GetMember( borrowernumber => $borrowernumber1 );
921     my $borrower2 = GetMember( borrowernumber => $borrowernumber2 );
922
923     my $issue = AddIssue( $borrower1, $barcode1 );
924
925     my ( $renewokay, $error ) = CanBookBeRenewed( $borrowernumber1, $itemnumber1 );
926     is( $renewokay, 1, 'Bug 14337 - Verify the borrower can renew with no hold on the record' );
927
928     AddReserve(
929         $library2->{branchcode}, $borrowernumber2, $biblionumber,
930         '',  1, undef, undef, '',
931         undef, undef, undef
932     );
933
934     C4::Context->dbh->do("UPDATE issuingrules SET onshelfholds = 0");
935     t::lib::Mocks::mock_preference( 'AllowRenewalIfOtherItemsAvailable', 0 );
936     ( $renewokay, $error ) = CanBookBeRenewed( $borrowernumber1, $itemnumber1 );
937     is( $renewokay, 0, 'Bug 14337 - Verify the borrower cannot renew with a hold on the record if AllowRenewalIfOtherItemsAvailable and onshelfholds are disabled' );
938
939     C4::Context->dbh->do("UPDATE issuingrules SET onshelfholds = 0");
940     t::lib::Mocks::mock_preference( 'AllowRenewalIfOtherItemsAvailable', 1 );
941     ( $renewokay, $error ) = CanBookBeRenewed( $borrowernumber1, $itemnumber1 );
942     is( $renewokay, 0, 'Bug 14337 - Verify the borrower cannot renew with a hold on the record if AllowRenewalIfOtherItemsAvailable is enabled and onshelfholds is disabled' );
943
944     C4::Context->dbh->do("UPDATE issuingrules SET onshelfholds = 1");
945     t::lib::Mocks::mock_preference( 'AllowRenewalIfOtherItemsAvailable', 0 );
946     ( $renewokay, $error ) = CanBookBeRenewed( $borrowernumber1, $itemnumber1 );
947     is( $renewokay, 0, 'Bug 14337 - Verify the borrower cannot renew with a hold on the record if AllowRenewalIfOtherItemsAvailable is disabled and onshelfhold is enabled' );
948
949     C4::Context->dbh->do("UPDATE issuingrules SET onshelfholds = 1");
950     t::lib::Mocks::mock_preference( 'AllowRenewalIfOtherItemsAvailable', 1 );
951     ( $renewokay, $error ) = CanBookBeRenewed( $borrowernumber1, $itemnumber1 );
952     is( $renewokay, 1, 'Bug 14337 - Verify the borrower can renew with a hold on the record if AllowRenewalIfOtherItemsAvailable and onshelfhold are enabled' );
953
954     # Setting item not checked out to be not for loan but holdable
955     ModItem({ notforloan => -1 }, $biblionumber, $itemnumber2);
956
957     ( $renewokay, $error ) = CanBookBeRenewed( $borrowernumber1, $itemnumber1 );
958     is( $renewokay, 0, 'Bug 14337 - Verify the borrower can not renew with a hold on the record if AllowRenewalIfOtherItemsAvailable is enabled but the only available item is notforloan' );
959 }
960
961 {
962     # Don't allow renewing onsite checkout
963     my $barcode  = 'R00000XXX';
964     my $branch   = $library->{branchcode};
965
966     #Create another record
967     my $biblio = MARC::Record->new();
968     $biblio->append_fields(
969         MARC::Field->new('100', ' ', ' ', a => 'Anonymous'),
970         MARC::Field->new('245', ' ', ' ', a => 'A title'),
971     );
972     my ($biblionumber, $biblioitemnumber) = AddBiblio($biblio, '');
973
974     my (undef, undef, $itemnumber) = AddItem(
975         {
976             homebranch       => $branch,
977             holdingbranch    => $branch,
978             barcode          => $barcode,
979             itype            => $itemtype
980         },
981         $biblionumber
982     );
983
984     my $borrowernumber = AddMember(
985         firstname =>  'fn',
986         surname => 'dn',
987         categorycode => 'S',
988         branchcode => $branch,
989     );
990
991     my $borrower = GetMember( borrowernumber => $borrowernumber );
992     my $issue = AddIssue( $borrower, $barcode, undef, undef, undef, undef, { onsite_checkout => 1 } );
993     my ( $renewed, $error ) = CanBookBeRenewed( $borrowernumber, $itemnumber );
994     is( $renewed, 0, 'CanBookBeRenewed should not allow to renew on-site checkout' );
995     is( $error, 'onsite_checkout', 'A correct error code should be returned by CanBookBeRenewed for on-site checkout' );
996 }
997
998 {
999     my $library = $builder->build({ source => 'Branch' });
1000
1001     my $biblio = MARC::Record->new();
1002     my ($biblionumber, $biblioitemnumber) = AddBiblio($biblio, '');
1003
1004     my $barcode = 'just a barcode';
1005     my ( undef, undef, $itemnumber ) = AddItem(
1006         {
1007             homebranch       => $library->{branchcode},
1008             holdingbranch    => $library->{branchcode},
1009             barcode          => $barcode,
1010             itype            => $itemtype
1011         },
1012         $biblionumber,
1013     );
1014
1015     my $patron = $builder->build({ source => 'Borrower', value => { branchcode => $library->{branchcode} } } );
1016
1017     my $issue = AddIssue( GetMember( borrowernumber => $patron->{borrowernumber} ), $barcode );
1018     UpdateFine(
1019         {
1020             issue_id       => $issue->id(),
1021             itemnumber     => $itemnumber,
1022             borrowernumber => $patron->{borrowernumber},
1023             amount         => 1,
1024             type           => q{}
1025         }
1026     );
1027     UpdateFine(
1028         {
1029             issue_id       => $issue->id(),
1030             itemnumber     => $itemnumber,
1031             borrowernumber => $patron->{borrowernumber},
1032             amount         => 2,
1033             type           => q{}
1034         }
1035     );
1036     is( Koha::Account::Lines->search({ issue_id => $issue->id })->count, 1, 'UpdateFine should not create a new accountline when updating an existing fine');
1037 }
1038
1039 subtest 'CanBookBeIssued & AllowReturnToBranch' => sub {
1040     plan tests => 23;
1041
1042     my $homebranch    = $builder->build( { source => 'Branch' } );
1043     my $holdingbranch = $builder->build( { source => 'Branch' } );
1044     my $otherbranch   = $builder->build( { source => 'Branch' } );
1045     my $patron_1      = $builder->build( { source => 'Borrower' } );
1046     my $patron_2      = $builder->build( { source => 'Borrower' } );
1047
1048     my $biblioitem = $builder->build( { source => 'Biblioitem' } );
1049     my $item = $builder->build(
1050         {   source => 'Item',
1051             value  => {
1052                 homebranch    => $homebranch->{branchcode},
1053                 holdingbranch => $holdingbranch->{branchcode},
1054                 notforloan    => 0,
1055                 itemlost      => 0,
1056                 withdrawn     => 0,
1057                 biblionumber  => $biblioitem->{biblionumber}
1058             }
1059         }
1060     );
1061
1062     set_userenv($holdingbranch);
1063
1064     my $issue = AddIssue( $patron_1, $item->{barcode} );
1065     is( ref($issue), 'Koha::Schema::Result::Issue' );    # FIXME Should be Koha::Checkout
1066
1067     my ( $error, $question, $alerts );
1068
1069     # AllowReturnToBranch == anywhere
1070     t::lib::Mocks::mock_preference( 'AllowReturnToBranch', 'anywhere' );
1071     ## Can be issued from homebranch
1072     set_userenv($homebranch);
1073     ( $error, $question, $alerts ) = CanBookBeIssued( $patron_2, $item->{barcode} );
1074     is( keys(%$error) + keys(%$alerts),        0 );
1075     is( exists $question->{ISSUED_TO_ANOTHER}, 1 );
1076     ## Can be issued from holdingbranch
1077     set_userenv($holdingbranch);
1078     ( $error, $question, $alerts ) = CanBookBeIssued( $patron_2, $item->{barcode} );
1079     is( keys(%$error) + keys(%$alerts),        0 );
1080     is( exists $question->{ISSUED_TO_ANOTHER}, 1 );
1081     ## Can be issued from another branch
1082     ( $error, $question, $alerts ) = CanBookBeIssued( $patron_2, $item->{barcode} );
1083     is( keys(%$error) + keys(%$alerts),        0 );
1084     is( exists $question->{ISSUED_TO_ANOTHER}, 1 );
1085
1086     # AllowReturnToBranch == holdingbranch
1087     t::lib::Mocks::mock_preference( 'AllowReturnToBranch', 'holdingbranch' );
1088     ## Cannot be issued from homebranch
1089     set_userenv($homebranch);
1090     ( $error, $question, $alerts ) = CanBookBeIssued( $patron_2, $item->{barcode} );
1091     is( keys(%$question) + keys(%$alerts),  0 );
1092     is( exists $error->{RETURN_IMPOSSIBLE}, 1 );
1093     is( $error->{branch_to_return},         $holdingbranch->{branchcode} );
1094     ## Can be issued from holdinbranch
1095     set_userenv($holdingbranch);
1096     ( $error, $question, $alerts ) = CanBookBeIssued( $patron_2, $item->{barcode} );
1097     is( keys(%$error) + keys(%$alerts),        0 );
1098     is( exists $question->{ISSUED_TO_ANOTHER}, 1 );
1099     ## Cannot be issued from another branch
1100     set_userenv($otherbranch);
1101     ( $error, $question, $alerts ) = CanBookBeIssued( $patron_2, $item->{barcode} );
1102     is( keys(%$question) + keys(%$alerts),  0 );
1103     is( exists $error->{RETURN_IMPOSSIBLE}, 1 );
1104     is( $error->{branch_to_return},         $holdingbranch->{branchcode} );
1105
1106     # AllowReturnToBranch == homebranch
1107     t::lib::Mocks::mock_preference( 'AllowReturnToBranch', 'homebranch' );
1108     ## Can be issued from holdinbranch
1109     set_userenv($homebranch);
1110     ( $error, $question, $alerts ) = CanBookBeIssued( $patron_2, $item->{barcode} );
1111     is( keys(%$error) + keys(%$alerts),        0 );
1112     is( exists $question->{ISSUED_TO_ANOTHER}, 1 );
1113     ## Cannot be issued from holdinbranch
1114     set_userenv($holdingbranch);
1115     ( $error, $question, $alerts ) = CanBookBeIssued( $patron_2, $item->{barcode} );
1116     is( keys(%$question) + keys(%$alerts),  0 );
1117     is( exists $error->{RETURN_IMPOSSIBLE}, 1 );
1118     is( $error->{branch_to_return},         $homebranch->{branchcode} );
1119     ## Cannot be issued from holdinbranch
1120     set_userenv($otherbranch);
1121     ( $error, $question, $alerts ) = CanBookBeIssued( $patron_2, $item->{barcode} );
1122     is( keys(%$question) + keys(%$alerts),  0 );
1123     is( exists $error->{RETURN_IMPOSSIBLE}, 1 );
1124     is( $error->{branch_to_return},         $homebranch->{branchcode} );
1125
1126     # TODO t::lib::Mocks::mock_preference('AllowReturnToBranch', 'homeorholdingbranch');
1127 };
1128
1129 subtest 'AddIssue & AllowReturnToBranch' => sub {
1130     plan tests => 9;
1131
1132     my $homebranch    = $builder->build( { source => 'Branch' } );
1133     my $holdingbranch = $builder->build( { source => 'Branch' } );
1134     my $otherbranch   = $builder->build( { source => 'Branch' } );
1135     my $patron_1      = $builder->build( { source => 'Borrower' } );
1136     my $patron_2      = $builder->build( { source => 'Borrower' } );
1137
1138     my $biblioitem = $builder->build( { source => 'Biblioitem' } );
1139     my $item = $builder->build(
1140         {   source => 'Item',
1141             value  => {
1142                 homebranch    => $homebranch->{branchcode},
1143                 holdingbranch => $holdingbranch->{branchcode},
1144                 notforloan    => 0,
1145                 itemlost      => 0,
1146                 withdrawn     => 0,
1147                 biblionumber  => $biblioitem->{biblionumber}
1148             }
1149         }
1150     );
1151
1152     set_userenv($holdingbranch);
1153
1154     my $ref_issue = 'Koha::Schema::Result::Issue'; # FIXME Should be Koha::Checkout
1155     my $issue = AddIssue( $patron_1, $item->{barcode} );
1156
1157     my ( $error, $question, $alerts );
1158
1159     # AllowReturnToBranch == homebranch
1160     t::lib::Mocks::mock_preference( 'AllowReturnToBranch', 'anywhere' );
1161     ## Can be issued from homebranch
1162     set_userenv($homebranch);
1163     is ( ref( AddIssue( $patron_2, $item->{barcode} ) ), $ref_issue );
1164     set_userenv($holdingbranch); AddIssue( $patron_1, $item->{barcode} ); # Reinsert the original issue
1165     ## Can be issued from holdinbranch
1166     set_userenv($holdingbranch);
1167     is ( ref( AddIssue( $patron_2, $item->{barcode} ) ), $ref_issue );
1168     set_userenv($holdingbranch); AddIssue( $patron_1, $item->{barcode} ); # Reinsert the original issue
1169     ## Can be issued from another branch
1170     set_userenv($otherbranch);
1171     is ( ref( AddIssue( $patron_2, $item->{barcode} ) ), $ref_issue );
1172     set_userenv($holdingbranch); AddIssue( $patron_1, $item->{barcode} ); # Reinsert the original issue
1173
1174     # AllowReturnToBranch == holdinbranch
1175     t::lib::Mocks::mock_preference( 'AllowReturnToBranch', 'holdingbranch' );
1176     ## Cannot be issued from homebranch
1177     set_userenv($homebranch);
1178     is ( ref( AddIssue( $patron_2, $item->{barcode} ) ), '' );
1179     ## Can be issued from holdingbranch
1180     set_userenv($holdingbranch);
1181     is ( ref( AddIssue( $patron_2, $item->{barcode} ) ), $ref_issue );
1182     set_userenv($holdingbranch); AddIssue( $patron_1, $item->{barcode} ); # Reinsert the original issue
1183     ## Cannot be issued from another branch
1184     set_userenv($otherbranch);
1185     is ( ref( AddIssue( $patron_2, $item->{barcode} ) ), '' );
1186
1187     # AllowReturnToBranch == homebranch
1188     t::lib::Mocks::mock_preference( 'AllowReturnToBranch', 'homebranch' );
1189     ## Can be issued from homebranch
1190     set_userenv($homebranch);
1191     is ( ref( AddIssue( $patron_2, $item->{barcode} ) ), $ref_issue );
1192     set_userenv($holdingbranch); AddIssue( $patron_1, $item->{barcode} ); # Reinsert the original issue
1193     ## Cannot be issued from holdinbranch
1194     set_userenv($holdingbranch);
1195     is ( ref( AddIssue( $patron_2, $item->{barcode} ) ), '' );
1196     ## Cannot be issued from another branch
1197     set_userenv($otherbranch);
1198     is ( ref( AddIssue( $patron_2, $item->{barcode} ) ), '' );
1199     # TODO t::lib::Mocks::mock_preference('AllowReturnToBranch', 'homeorholdingbranch');
1200 };
1201
1202 subtest 'CanBookBeIssued + Koha::Patron->is_debarred|has_overdues' => sub {
1203     plan tests => 8;
1204
1205     my $library = $builder->build( { source => 'Branch' } );
1206     my $patron  = $builder->build( { source => 'Borrower' } );
1207
1208     my $biblioitem_1 = $builder->build( { source => 'Biblioitem' } );
1209     my $item_1 = $builder->build(
1210         {   source => 'Item',
1211             value  => {
1212                 homebranch    => $library->{branchcode},
1213                 holdingbranch => $library->{branchcode},
1214                 notforloan    => 0,
1215                 itemlost      => 0,
1216                 withdrawn     => 0,
1217                 biblionumber  => $biblioitem_1->{biblionumber}
1218             }
1219         }
1220     );
1221     my $biblioitem_2 = $builder->build( { source => 'Biblioitem' } );
1222     my $item_2 = $builder->build(
1223         {   source => 'Item',
1224             value  => {
1225                 homebranch    => $library->{branchcode},
1226                 holdingbranch => $library->{branchcode},
1227                 notforloan    => 0,
1228                 itemlost      => 0,
1229                 withdrawn     => 0,
1230                 biblionumber  => $biblioitem_2->{biblionumber}
1231             }
1232         }
1233     );
1234
1235     my ( $error, $question, $alerts );
1236
1237     # Patron cannot issue item_1, he has overdues
1238     my $yesterday = DateTime->today( time_zone => C4::Context->tz() )->add( days => -1 );
1239     my $issue = AddIssue( $patron, $item_1->{barcode}, $yesterday );    # Add an overdue
1240
1241     t::lib::Mocks::mock_preference( 'OverduesBlockCirc', 'confirmation' );
1242     ( $error, $question, $alerts ) = CanBookBeIssued( $patron, $item_2->{barcode} );
1243     is( keys(%$error) + keys(%$alerts),  0 );
1244     is( $question->{USERBLOCKEDOVERDUE}, 1 );
1245
1246     t::lib::Mocks::mock_preference( 'OverduesBlockCirc', 'block' );
1247     ( $error, $question, $alerts ) = CanBookBeIssued( $patron, $item_2->{barcode} );
1248     is( keys(%$question) + keys(%$alerts), 0 );
1249     is( $error->{USERBLOCKEDOVERDUE},      1 );
1250
1251     # Patron cannot issue item_1, he is debarred
1252     my $tomorrow = DateTime->today( time_zone => C4::Context->tz() )->add( days => 1 );
1253     Koha::Patron::Debarments::AddDebarment( { borrowernumber => $patron->{borrowernumber}, expiration => $tomorrow } );
1254     ( $error, $question, $alerts ) = CanBookBeIssued( $patron, $item_2->{barcode} );
1255     is( keys(%$question) + keys(%$alerts), 0 );
1256     is( $error->{USERBLOCKEDWITHENDDATE}, output_pref( { dt => $tomorrow, dateformat => 'sql', dateonly => 1 } ) );
1257
1258     Koha::Patron::Debarments::AddDebarment( { borrowernumber => $patron->{borrowernumber} } );
1259     ( $error, $question, $alerts ) = CanBookBeIssued( $patron, $item_2->{barcode} );
1260     is( keys(%$question) + keys(%$alerts), 0 );
1261     is( $error->{USERBLOCKEDNOENDDATE},    '9999-12-31' );
1262 };
1263
1264 subtest 'MultipleReserves' => sub {
1265     plan tests => 3;
1266
1267     my $biblio = MARC::Record->new();
1268     my $title = 'Silence in the library';
1269     $biblio->append_fields(
1270         MARC::Field->new('100', ' ', ' ', a => 'Moffat, Steven'),
1271         MARC::Field->new('245', ' ', ' ', a => $title),
1272     );
1273
1274     my ($biblionumber, $biblioitemnumber) = AddBiblio($biblio, '');
1275
1276     my $branch = $library2->{branchcode};
1277
1278     my $barcode1 = 'R00110001';
1279     my ( $item_bibnum1, $item_bibitemnum1, $itemnumber1 ) = AddItem(
1280         {
1281             homebranch       => $branch,
1282             holdingbranch    => $branch,
1283             barcode          => $barcode1,
1284             replacementprice => 12.00,
1285             itype            => $itemtype
1286         },
1287         $biblionumber
1288     );
1289
1290     my $barcode2 = 'R00110002';
1291     my ( $item_bibnum2, $item_bibitemnum2, $itemnumber2 ) = AddItem(
1292         {
1293             homebranch       => $branch,
1294             holdingbranch    => $branch,
1295             barcode          => $barcode2,
1296             replacementprice => 12.00,
1297             itype            => $itemtype
1298         },
1299         $biblionumber
1300     );
1301
1302     my $bibitems       = '';
1303     my $priority       = '1';
1304     my $resdate        = undef;
1305     my $expdate        = undef;
1306     my $notes          = '';
1307     my $checkitem      = undef;
1308     my $found          = undef;
1309
1310     my %renewing_borrower_data = (
1311         firstname =>  'John',
1312         surname => 'Renewal',
1313         categorycode => 'S',
1314         branchcode => $branch,
1315     );
1316     my $renewing_borrowernumber = AddMember(%renewing_borrower_data);
1317     my $renewing_borrower = GetMember( borrowernumber => $renewing_borrowernumber );
1318     my $issue = AddIssue( $renewing_borrower, $barcode1);
1319     my $datedue = dt_from_string( $issue->date_due() );
1320     is (defined $issue->date_due(), 1, "item 1 checked out");
1321     my $borrowing_borrowernumber = GetItemIssue($itemnumber1)->{borrowernumber};
1322
1323     my %reserving_borrower_data1 = (
1324         firstname =>  'Katrin',
1325         surname => 'Reservation',
1326         categorycode => 'S',
1327         branchcode => $branch,
1328     );
1329     my $reserving_borrowernumber1 = AddMember(%reserving_borrower_data1);
1330     AddReserve(
1331         $branch, $reserving_borrowernumber1, $biblionumber,
1332         $bibitems,  $priority, $resdate, $expdate, $notes,
1333         $title, $checkitem, $found
1334     );
1335
1336     my %reserving_borrower_data2 = (
1337         firstname =>  'Kirk',
1338         surname => 'Reservation',
1339         categorycode => 'S',
1340         branchcode => $branch,
1341     );
1342     my $reserving_borrowernumber2 = AddMember(%reserving_borrower_data2);
1343     AddReserve(
1344         $branch, $reserving_borrowernumber2, $biblionumber,
1345         $bibitems,  $priority, $resdate, $expdate, $notes,
1346         $title, $checkitem, $found
1347     );
1348
1349     {
1350         my ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $itemnumber1, 1);
1351         is($renewokay, 0, 'Bug 17641 - should cover the case where 2 books are both reserved, so failing');
1352     }
1353
1354     my $barcode3 = 'R00110003';
1355     my ( $item_bibnum3, $item_bibitemnum3, $itemnumber3 ) = AddItem(
1356         {
1357             homebranch       => $branch,
1358             holdingbranch    => $branch,
1359             barcode          => $barcode3,
1360             replacementprice => 12.00,
1361             itype            => $itemtype
1362         },
1363         $biblionumber
1364     );
1365
1366     {
1367         my ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $itemnumber1, 1);
1368         is($renewokay, 1, 'Bug 17641 - should cover the case where 2 books are reserved, but a third one is available');
1369     }
1370 };
1371
1372 subtest 'CanBookBeIssued + AllowMultipleIssuesOnABiblio' => sub {
1373     plan tests => 5;
1374
1375     my $library = $builder->build( { source => 'Branch' } );
1376     my $patron  = $builder->build( { source => 'Borrower' } );
1377
1378     my $biblioitem = $builder->build( { source => 'Biblioitem' } );
1379     my $biblionumber = $biblioitem->{biblionumber};
1380     my $item_1 = $builder->build(
1381         {   source => 'Item',
1382             value  => {
1383                 homebranch    => $library->{branchcode},
1384                 holdingbranch => $library->{branchcode},
1385                 notforloan    => 0,
1386                 itemlost      => 0,
1387                 withdrawn     => 0,
1388                 biblionumber  => $biblionumber,
1389             }
1390         }
1391     );
1392     my $item_2 = $builder->build(
1393         {   source => 'Item',
1394             value  => {
1395                 homebranch    => $library->{branchcode},
1396                 holdingbranch => $library->{branchcode},
1397                 notforloan    => 0,
1398                 itemlost      => 0,
1399                 withdrawn     => 0,
1400                 biblionumber  => $biblionumber,
1401             }
1402         }
1403     );
1404
1405     my ( $error, $question, $alerts );
1406     my $issue = AddIssue( $patron, $item_1->{barcode}, dt_from_string->add( days => 1 ) );
1407
1408     t::lib::Mocks::mock_preference('AllowMultipleIssuesOnABiblio', 0);
1409     ( $error, $question, $alerts ) = CanBookBeIssued( $patron, $item_2->{barcode} );
1410     is( keys(%$error) + keys(%$alerts),  0, 'No error or alert should be raised' );
1411     is( $question->{BIBLIO_ALREADY_ISSUED}, 1, 'BIBLIO_ALREADY_ISSUED question flag should be set if AllowMultipleIssuesOnABiblio=0 and issue already exists' );
1412
1413     t::lib::Mocks::mock_preference('AllowMultipleIssuesOnABiblio', 1);
1414     ( $error, $question, $alerts ) = CanBookBeIssued( $patron, $item_2->{barcode} );
1415     is( keys(%$error) + keys(%$question) + keys(%$alerts),  0, 'No BIBLIO_ALREADY_ISSUED flag should be set if AllowMultipleIssuesOnABiblio=1' );
1416
1417     # Add a subscription
1418     Koha::Subscription->new({ biblionumber => $biblionumber })->store;
1419
1420     t::lib::Mocks::mock_preference('AllowMultipleIssuesOnABiblio', 0);
1421     ( $error, $question, $alerts ) = CanBookBeIssued( $patron, $item_2->{barcode} );
1422     is( keys(%$error) + keys(%$question) + keys(%$alerts),  0, 'No BIBLIO_ALREADY_ISSUED flag should be set if it is a subscription' );
1423
1424     t::lib::Mocks::mock_preference('AllowMultipleIssuesOnABiblio', 1);
1425     ( $error, $question, $alerts ) = CanBookBeIssued( $patron, $item_2->{barcode} );
1426     is( keys(%$error) + keys(%$question) + keys(%$alerts),  0, 'No BIBLIO_ALREADY_ISSUED flag should be set if it is a subscription' );
1427 };
1428
1429 subtest 'AddReturn + CumulativeRestrictionPeriods' => sub {
1430     plan tests => 8;
1431
1432     my $library = $builder->build( { source => 'Branch' } );
1433     my $patron  = $builder->build( { source => 'Borrower' } );
1434
1435     # Add 2 items
1436     my $biblioitem_1 = $builder->build( { source => 'Biblioitem' } );
1437     my $item_1 = $builder->build(
1438         {
1439             source => 'Item',
1440             value  => {
1441                 homebranch    => $library->{branchcode},
1442                 holdingbranch => $library->{branchcode},
1443                 notforloan    => 0,
1444                 itemlost      => 0,
1445                 withdrawn     => 0,
1446                 biblionumber  => $biblioitem_1->{biblionumber}
1447             }
1448         }
1449     );
1450     my $biblioitem_2 = $builder->build( { source => 'Biblioitem' } );
1451     my $item_2 = $builder->build(
1452         {
1453             source => 'Item',
1454             value  => {
1455                 homebranch    => $library->{branchcode},
1456                 holdingbranch => $library->{branchcode},
1457                 notforloan    => 0,
1458                 itemlost      => 0,
1459                 withdrawn     => 0,
1460                 biblionumber  => $biblioitem_2->{biblionumber}
1461             }
1462         }
1463     );
1464
1465     # And the issuing rule
1466     Koha::IssuingRules->search->delete;
1467     my $rule = Koha::IssuingRule->new(
1468         {
1469             categorycode => '*',
1470             itemtype     => '*',
1471             branchcode   => '*',
1472             maxissueqty  => 99,
1473             issuelength  => 1,
1474             firstremind  => 1,        # 1 day of grace
1475             finedays     => 2,        # 2 days of fine per day of overdue
1476             lengthunit   => 'days',
1477         }
1478     );
1479     $rule->store();
1480
1481     # Patron cannot issue item_1, he has overdues
1482     my $five_days_ago = dt_from_string->subtract( days => 5 );
1483     my $ten_days_ago  = dt_from_string->subtract( days => 10 );
1484     AddIssue( $patron, $item_1->{barcode}, $five_days_ago );    # Add an overdue
1485     AddIssue( $patron, $item_2->{barcode}, $ten_days_ago )
1486       ;    # Add another overdue
1487
1488     t::lib::Mocks::mock_preference( 'CumulativeRestrictionPeriods', '0' );
1489     AddReturn( $item_1->{barcode}, $library->{branchcode},
1490         undef, undef, dt_from_string );
1491     my $debarments = Koha::Patron::Debarments::GetDebarments(
1492         { borrowernumber => $patron->{borrowernumber}, type => 'SUSPENSION' } );
1493     is( scalar(@$debarments), 1 );
1494
1495     # FIXME Is it right? I'd have expected 5 * 2 - 1 instead
1496     # Same for the others
1497     my $expected_expiration = output_pref(
1498         {
1499             dt         => dt_from_string->add( days => ( 5 - 1 ) * 2 ),
1500             dateformat => 'sql',
1501             dateonly   => 1
1502         }
1503     );
1504     is( $debarments->[0]->{expiration}, $expected_expiration );
1505
1506     AddReturn( $item_2->{barcode}, $library->{branchcode},
1507         undef, undef, dt_from_string );
1508     $debarments = Koha::Patron::Debarments::GetDebarments(
1509         { borrowernumber => $patron->{borrowernumber}, type => 'SUSPENSION' } );
1510     is( scalar(@$debarments), 1 );
1511     $expected_expiration = output_pref(
1512         {
1513             dt         => dt_from_string->add( days => ( 10 - 1 ) * 2 ),
1514             dateformat => 'sql',
1515             dateonly   => 1
1516         }
1517     );
1518     is( $debarments->[0]->{expiration}, $expected_expiration );
1519
1520     Koha::Patron::Debarments::DelUniqueDebarment(
1521         { borrowernumber => $patron->{borrowernumber}, type => 'SUSPENSION' } );
1522
1523     t::lib::Mocks::mock_preference( 'CumulativeRestrictionPeriods', '1' );
1524     AddIssue( $patron, $item_1->{barcode}, $five_days_ago );    # Add an overdue
1525     AddIssue( $patron, $item_2->{barcode}, $ten_days_ago )
1526       ;    # Add another overdue
1527     AddReturn( $item_1->{barcode}, $library->{branchcode},
1528         undef, undef, dt_from_string );
1529     $debarments = Koha::Patron::Debarments::GetDebarments(
1530         { borrowernumber => $patron->{borrowernumber}, type => 'SUSPENSION' } );
1531     is( scalar(@$debarments), 1 );
1532     $expected_expiration = output_pref(
1533         {
1534             dt         => dt_from_string->add( days => ( 5 - 1 ) * 2 ),
1535             dateformat => 'sql',
1536             dateonly   => 1
1537         }
1538     );
1539     is( $debarments->[0]->{expiration}, $expected_expiration );
1540
1541     AddReturn( $item_2->{barcode}, $library->{branchcode},
1542         undef, undef, dt_from_string );
1543     $debarments = Koha::Patron::Debarments::GetDebarments(
1544         { borrowernumber => $patron->{borrowernumber}, type => 'SUSPENSION' } );
1545     is( scalar(@$debarments), 1 );
1546     $expected_expiration = output_pref(
1547         {
1548             dt => dt_from_string->add( days => ( 5 - 1 ) * 2 + ( 10 - 1 ) * 2 ),
1549             dateformat => 'sql',
1550             dateonly   => 1
1551         }
1552     );
1553     is( $debarments->[0]->{expiration}, $expected_expiration );
1554 };
1555
1556 sub set_userenv {
1557     my ( $library ) = @_;
1558     C4::Context->set_userenv(0,0,0,'firstname','surname', $library->{branchcode}, $library->{branchname}, '', '', '');
1559 }
1560
1561 1;