Bug 35979: (follow-up) Add check in ->enqueue
[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 use utf8;
20
21 use Test::More tests => 70;
22 use Test::Exception;
23 use Test::MockModule;
24 use Test::Deep qw( cmp_deeply );
25 use Test::Warn;
26
27 use Data::Dumper;
28 use DateTime;
29 use Time::Fake;
30 use POSIX qw( floor );
31 use t::lib::Mocks;
32 use t::lib::TestBuilder;
33
34 use C4::Accounts;
35 use C4::Calendar qw( new insert_single_holiday insert_week_day_holiday delete_holiday );
36 use C4::Circulation qw( AddIssue AddReturn CanBookBeRenewed GetIssuingCharges AddRenewal GetSoonestRenewDate GetLatestAutoRenewDate LostItem GetUpcomingDueIssues CanBookBeIssued AddIssuingCharge MarkIssueReturned ProcessOfflinePayment transferbook updateWrongTransfer );
37 use C4::Biblio;
38 use C4::Items qw( ModItemTransfer );
39 use C4::Log;
40 use C4::Reserves qw( AddReserve ModReserve ModReserveCancelAll ModReserveAffect CheckReserves );
41 use C4::Overdues qw( CalcFine UpdateFine get_chargeable_units );
42 use C4::Members::Messaging qw( SetMessagingPreference );
43 use Koha::DateUtils qw( dt_from_string output_pref );
44 use Koha::Database;
45 use Koha::Bookings;
46 use Koha::Items;
47 use Koha::Item::Transfers;
48 use Koha::Checkouts;
49 use Koha::Patrons;
50 use Koha::Patron::Debarments qw( AddDebarment DelUniqueDebarment );
51 use Koha::Holds;
52 use Koha::CirculationRules;
53 use Koha::Subscriptions;
54 use Koha::Account::Lines;
55 use Koha::Account::Offsets;
56 use Koha::ActionLogs;
57 use Koha::Notice::Messages;
58 use Koha::Cache::Memory::Lite;
59
60 my $builder = t::lib::TestBuilder->new;
61 sub set_userenv {
62     my ( $library ) = @_;
63     my $staff = $builder->build_object({ class => "Koha::Patrons" });
64     t::lib::Mocks::mock_userenv({ patron => $staff, branchcode => $library->{branchcode} });
65 }
66
67 sub str {
68     my ( $error, $question, $alert ) = @_;
69     my $s;
70     $s  = %$error    ? ' (error: '    . join( ' ', keys %$error    ) . ')' : '';
71     $s .= %$question ? ' (question: ' . join( ' ', keys %$question ) . ')' : '';
72     $s .= %$alert    ? ' (alert: '    . join( ' ', keys %$alert    ) . ')' : '';
73     return $s;
74 }
75
76 sub test_debarment_on_checkout {
77     my ($params) = @_;
78     my $item     = $params->{item};
79     my $library  = $params->{library};
80     my $patron   = $params->{patron};
81     my $due_date = $params->{due_date} || dt_from_string;
82     my $return_date = $params->{return_date} || dt_from_string;
83     my $expected_expiration_date = $params->{expiration_date};
84
85     $expected_expiration_date = output_pref(
86         {
87             dt         => $expected_expiration_date,
88             dateformat => 'sql',
89             dateonly   => 1,
90         }
91     );
92     my @caller      = caller;
93     my $line_number = $caller[2];
94     AddIssue( $patron, $item->barcode, $due_date );
95
96     my ( undef, $message ) = AddReturn( $item->barcode, $library->{branchcode}, undef, $return_date );
97     is( $message->{WasReturned} && exists $message->{Debarred}, 1, 'AddReturn must have debarred the patron' )
98         or diag('AddReturn returned message ' . Dumper $message );
99     my $suspensions = $patron->restrictions->search({ type => 'SUSPENSION' } );
100     is( $suspensions->count, 1, 'Test at line ' . $line_number );
101
102     my $THE_suspension = $suspensions->next;
103     is( $THE_suspension->expiration,
104         $expected_expiration_date, 'Test at line ' . $line_number );
105     Koha::Patron::Debarments::DelUniqueDebarment(
106         { borrowernumber => $patron->borrowernumber, type => 'SUSPENSION' } );
107 };
108
109 my $schema = Koha::Database->schema;
110 $schema->storage->txn_begin;
111 my $dbh = C4::Context->dbh;
112
113 # Prevent random failures by mocking ->now
114 my $now_value       = dt_from_string;
115 my $mocked_datetime = Test::MockModule->new('DateTime');
116 $mocked_datetime->mock( 'now', sub { return $now_value->clone; } );
117
118 my $cache = Koha::Caches->get_instance();
119 $dbh->do(q|DELETE FROM special_holidays|);
120 $dbh->do(q|DELETE FROM repeatable_holidays|);
121 my $branches = Koha::Libraries->search();
122 for my $branch ( $branches->next ) {
123     my $key = $branch->branchcode . "_holidays";
124     $cache->clear_from_cache($key);
125 }
126
127 # Start with a clean slate
128 $dbh->do('DELETE FROM issues');
129 $dbh->do('DELETE FROM borrowers');
130
131 # Disable recording of the staff who checked out an item until we're ready for it
132 t::lib::Mocks::mock_preference('RecordStaffUserOnCheckout', 0);
133
134 my $module = Test::MockModule->new('C4::Context');
135
136 my $library = $builder->build({
137     source => 'Branch',
138 });
139 my $library2 = $builder->build({
140     source => 'Branch',
141 });
142 my $itemtype = $builder->build(
143     {
144         source => 'Itemtype',
145         value  => {
146             notforloan          => undef,
147             rentalcharge        => 0,
148             rentalcharge_daily => 0,
149             defaultreplacecost  => undef,
150             processfee          => undef
151         }
152     }
153 )->{itemtype};
154 my $patron_category = $builder->build(
155     {
156         source => 'Category',
157         value  => {
158             category_type                 => 'P',
159             enrolmentfee                  => 0,
160             BlockExpiredPatronOpacActions => -1, # Pick the pref value
161         }
162     }
163 );
164
165 my $CircControl = C4::Context->preference('CircControl');
166 my $HomeOrHoldingBranch = C4::Context->preference('HomeOrHoldingBranch');
167
168 my $item = $builder->build_object({
169     class => 'Koha::Items',
170     value => {
171         homebranch => $library2->{branchcode},
172         holdingbranch => $library2->{branchcode}
173     }
174 });
175
176 my $borrower = $builder->build_object({
177     class => 'Koha::Patrons',
178     value => { branchcode => $library2->{branchcode} }
179 });
180
181 t::lib::Mocks::mock_preference('AutoReturnCheckedOutItems', 0);
182
183 # No userenv, PickupLibrary
184 t::lib::Mocks::mock_preference('IndependentBranches', '0');
185 t::lib::Mocks::mock_preference('CircControl', 'PickupLibrary');
186 is(
187     C4::Context->preference('CircControl'),
188     'PickupLibrary',
189     'CircControl changed to PickupLibrary'
190 );
191 is(
192     C4::Circulation::_GetCircControlBranch($item, $borrower),
193     $item->get_column($HomeOrHoldingBranch),
194     '_GetCircControlBranch returned item branch (no userenv defined)'
195 );
196
197 # No userenv, PatronLibrary
198 t::lib::Mocks::mock_preference('CircControl', 'PatronLibrary');
199 is(
200     C4::Context->preference('CircControl'),
201     'PatronLibrary',
202     'CircControl changed to PatronLibrary'
203 );
204 is(
205     C4::Circulation::_GetCircControlBranch($item, $borrower),
206     $borrower->branchcode,
207     '_GetCircControlBranch returned borrower branch'
208 );
209
210 # No userenv, ItemHomeLibrary
211 t::lib::Mocks::mock_preference('CircControl', 'ItemHomeLibrary');
212 is(
213     C4::Context->preference('CircControl'),
214     'ItemHomeLibrary',
215     'CircControl changed to ItemHomeLibrary'
216 );
217 is(
218     $item->get_column($HomeOrHoldingBranch),
219     C4::Circulation::_GetCircControlBranch($item, $borrower),
220     '_GetCircControlBranch returned item branch'
221 );
222
223 # Now, set a userenv
224 t::lib::Mocks::mock_userenv({ branchcode => $library2->{branchcode} });
225 is(C4::Context->userenv->{branch}, $library2->{branchcode}, 'userenv set');
226
227 # Userenv set, PickupLibrary
228 t::lib::Mocks::mock_preference('CircControl', 'PickupLibrary');
229 is(
230     C4::Context->preference('CircControl'),
231     'PickupLibrary',
232     'CircControl changed to PickupLibrary'
233 );
234 is(
235     C4::Circulation::_GetCircControlBranch($item, $borrower),
236     $library2->{branchcode},
237     '_GetCircControlBranch returned current branch'
238 );
239
240 # Userenv set, PatronLibrary
241 t::lib::Mocks::mock_preference('CircControl', 'PatronLibrary');
242 is(
243     C4::Context->preference('CircControl'),
244     'PatronLibrary',
245     'CircControl changed to PatronLibrary'
246 );
247 is(
248     C4::Circulation::_GetCircControlBranch($item, $borrower),
249     $borrower->branchcode,
250     '_GetCircControlBranch returned borrower branch'
251 );
252
253 # Userenv set, ItemHomeLibrary
254 t::lib::Mocks::mock_preference('CircControl', 'ItemHomeLibrary');
255 is(
256     C4::Context->preference('CircControl'),
257     'ItemHomeLibrary',
258     'CircControl changed to ItemHomeLibrary'
259 );
260 is(
261     C4::Circulation::_GetCircControlBranch($item, $borrower),
262     $item->get_column($HomeOrHoldingBranch),
263     '_GetCircControlBranch returned item branch'
264 );
265
266 # Reset initial configuration
267 t::lib::Mocks::mock_preference('CircControl', $CircControl);
268 is(
269     C4::Context->preference('CircControl'),
270     $CircControl,
271     'CircControl reset to its initial value'
272 );
273
274 # Set a simple circ policy
275 $dbh->do('DELETE FROM circulation_rules');
276 Koha::CirculationRules->set_rules(
277     {
278         categorycode => undef,
279         branchcode   => undef,
280         itemtype     => undef,
281         rules        => {
282             reservesallowed     => 25,
283             issuelength         => 14,
284             lengthunit          => 'days',
285             renewalsallowed     => 1,
286             renewalperiod       => 7,
287             norenewalbefore     => undef,
288             noautorenewalbefore => undef,
289             auto_renew          => 0,
290             fine                => .10,
291             chargeperiod        => 1,
292         }
293     }
294 );
295
296 subtest "CanBookBeRenewed AllowRenewalIfOtherItemsAvailable multiple borrowers and items tests" => sub {
297     plan tests => 7;
298
299     #Can only reserve from home branch
300     Koha::CirculationRules->set_rule(
301         {
302             branchcode   => undef,
303             itemtype     => undef,
304             rule_name    => 'holdallowed',
305             rule_value   => 1
306         }
307     );
308     Koha::CirculationRules->set_rule(
309         {
310             branchcode   => undef,
311             categorycode   => undef,
312             itemtype     => undef,
313             rule_name    => 'onshelfholds',
314             rule_value   => 1
315         }
316     );
317
318     # Patrons from three different branches
319     my $patron = $builder->build_object({ class => 'Koha::Patrons' });
320     my $patron_hold_1   = $builder->build_object({ class => 'Koha::Patrons' });
321     my $patron_hold_2   = $builder->build_object({ class => 'Koha::Patrons' });
322     my $biblio = $builder->build_sample_biblio();
323
324     # Item at each patron branch
325     my $item_1 = $builder->build_sample_item({
326         biblionumber => $biblio->biblionumber,
327         homebranch   => $patron->branchcode
328     });
329     my $item_2 = $builder->build_sample_item({
330         biblionumber => $biblio->biblionumber,
331         homebranch   => $patron_hold_2->branchcode
332     });
333     my $item_3 = $builder->build_sample_item({
334         biblionumber => $biblio->biblionumber,
335         homebranch   => $patron_hold_1->branchcode
336     });
337
338     my $issue = AddIssue( $patron, $item_1->barcode);
339     my $datedue = dt_from_string( $issue->date_due() );
340     is (defined $issue->date_due(), 1, "Item 1 checked out, due date: " . $issue->date_due() );
341
342     # Biblio-level holds
343     my $reserve_1 = AddReserve(
344         {
345             branchcode       => $patron_hold_1->branchcode,
346             borrowernumber   => $patron_hold_1->borrowernumber,
347             biblionumber     => $biblio->biblionumber,
348             priority         => 1,
349             reservation_date => dt_from_string(),
350             expiration_date  => undef,
351             itemnumber       => undef,
352             found            => undef,
353         }
354     );
355     AddReserve(
356         {
357             branchcode       => $patron_hold_2->branchcode,
358             borrowernumber   => $patron_hold_2->borrowernumber,
359             biblionumber     => $biblio->biblionumber,
360             priority         => 2,
361             reservation_date => dt_from_string(),
362             expiration_date  => undef,
363             itemnumber       => undef,
364             found            => undef,
365         }
366     );
367     t::lib::Mocks::mock_preference('AllowRenewalIfOtherItemsAvailable', 0 );
368
369     my ( $renewokay, $error ) = CanBookBeRenewed($patron, $issue);
370     is( $renewokay, 0, 'Cannot renew, reserved');
371     is( $error, 'on_reserve', 'Cannot renew, reserved (returned error is on_reserve)');
372
373     t::lib::Mocks::mock_preference('AllowRenewalIfOtherItemsAvailable', 1 );
374
375     ( $renewokay, $error ) = CanBookBeRenewed($patron, $issue);
376     is( $renewokay, 1, 'Can renew, two items available for two holds');
377     is( $error, undef, 'Can renew, each reserve has an item');
378
379     # Item level hold
380     my $hold = Koha::Holds->find( $reserve_1 );
381     $hold->itemnumber( $item_1->itemnumber )->store;
382
383     ( $renewokay, $error ) = CanBookBeRenewed($patron, $issue);
384     is( $renewokay, 0, 'Cannot renew when there is an item specific hold');
385     is( $error, 'on_reserve', 'Cannot renew, only this item can fill the reserve');
386 };
387
388 subtest "GetIssuingCharges tests" => sub {
389     plan tests => 4;
390     my $branch_discount = $builder->build_object({ class => 'Koha::Libraries' });
391     my $branch_no_discount = $builder->build_object({ class => 'Koha::Libraries' });
392     Koha::CirculationRules->set_rule(
393         {
394             categorycode => undef,
395             branchcode   => $branch_discount->branchcode,
396             itemtype     => undef,
397             rule_name    => 'rentaldiscount',
398             rule_value   => 15
399         }
400     );
401     my $itype_charge = $builder->build_object({
402         class => 'Koha::ItemTypes',
403         value => {
404             rentalcharge => 10
405         }
406     });
407     my $itype_no_charge = $builder->build_object({
408         class => 'Koha::ItemTypes',
409         value => {
410             rentalcharge => 0
411         }
412     });
413     my $patron = $builder->build_object({ class => 'Koha::Patrons' });
414     my $item_1 = $builder->build_sample_item({ itype => $itype_charge->itemtype });
415     my $item_2 = $builder->build_sample_item({ itype => $itype_no_charge->itemtype });
416
417     t::lib::Mocks::mock_userenv({ branchcode => $branch_no_discount->branchcode });
418     # For now the sub always uses the env branch, this should follow CircControl instead
419     my ($charge, $itemtype) = GetIssuingCharges( $item_1->itemnumber, $patron->borrowernumber);
420     is( $charge + 0, 10.00, "Charge fetched correctly when no discount exists");
421     ($charge, $itemtype) = GetIssuingCharges( $item_2->itemnumber, $patron->borrowernumber);
422     is( $charge + 0, 0.00, "Charge fetched correctly when no discount exists and no charge");
423
424     t::lib::Mocks::mock_userenv({ branchcode => $branch_discount->branchcode });
425     # For now the sub always uses the env branch, this should follow CircControl instead
426     ($charge, $itemtype) = GetIssuingCharges( $item_1->itemnumber, $patron->borrowernumber);
427     is( $charge + 0, 8.50, "Charge fetched correctly when discount exists");
428     ($charge, $itemtype) = GetIssuingCharges( $item_2->itemnumber, $patron->borrowernumber);
429     is( $charge + 0, 0.00, "Charge fetched correctly when discount exists and no charge");
430
431 };
432
433 my ( $reused_itemnumber_1, $reused_itemnumber_2 );
434 subtest "CanBookBeRenewed tests" => sub {
435     plan tests => 114;
436
437     C4::Context->set_preference('ItemsDeniedRenewal','');
438     # Generate test biblio
439     my $biblio = $builder->build_sample_biblio();
440
441     my $branch = $library2->{branchcode};
442
443     my $item_1 = $builder->build_sample_item(
444         {
445             biblionumber     => $biblio->biblionumber,
446             library          => $branch,
447             replacementprice => 12.00,
448             itype            => $itemtype
449         }
450     );
451     $reused_itemnumber_1 = $item_1->itemnumber;
452
453     my $item_2 = $builder->build_sample_item(
454         {
455             biblionumber     => $biblio->biblionumber,
456             library          => $branch,
457             replacementprice => 23.00,
458             itype            => $itemtype
459         }
460     );
461     $reused_itemnumber_2 = $item_2->itemnumber;
462
463     my $item_3 = $builder->build_sample_item(
464         {
465             biblionumber     => $biblio->biblionumber,
466             library          => $branch,
467             replacementprice => 23.00,
468             itype            => $itemtype
469         }
470     );
471
472     # Create borrowers
473     my %renewing_borrower_data = (
474         firstname =>  'John',
475         surname => 'Renewal',
476         categorycode => $patron_category->{categorycode},
477         branchcode => $branch,
478         autorenew_checkouts => 1,
479     );
480
481     my %reserving_borrower_data = (
482         firstname =>  'Katrin',
483         surname => 'Reservation',
484         categorycode => $patron_category->{categorycode},
485         branchcode => $branch,
486     );
487
488     my %hold_waiting_borrower_data = (
489         firstname =>  'Kyle',
490         surname => 'Reservation',
491         categorycode => $patron_category->{categorycode},
492         branchcode => $branch,
493     );
494
495     my %restricted_borrower_data = (
496         firstname =>  'Alice',
497         surname => 'Reservation',
498         categorycode => $patron_category->{categorycode},
499         debarred => '3228-01-01',
500         branchcode => $branch,
501     );
502
503     my %expired_borrower_data = (
504         firstname =>  'Ça',
505         surname => 'Glisse',
506         categorycode => $patron_category->{categorycode},
507         branchcode => $branch,
508         dateexpiry => dt_from_string->subtract( months => 1 ),
509         autorenew_checkouts => 1,
510     );
511
512     my $renewing_borrower_obj = Koha::Patron->new(\%renewing_borrower_data)->store;
513     my $renewing_borrowernumber = $renewing_borrower_obj->borrowernumber;
514     my $reserving_borrowernumber = Koha::Patron->new(\%reserving_borrower_data)->store->borrowernumber;
515     my $hold_waiting_borrowernumber = Koha::Patron->new(\%hold_waiting_borrower_data)->store->borrowernumber;
516     my $restricted_borrower_obj = Koha::Patron->new(\%restricted_borrower_data)->store;
517
518     my $expired_borrower_obj = Koha::Patron->new(\%expired_borrower_data)->store;
519
520     my $bibitems       = '';
521     my $priority       = '1';
522     my $resdate        = undef;
523     my $expdate        = undef;
524     my $notes          = '';
525     my $checkitem      = undef;
526     my $found          = undef;
527
528     my $issue_1 = AddIssue( $renewing_borrower_obj, $item_1->barcode);
529     my $datedue = dt_from_string( $issue_1->date_due() );
530     is (defined $issue_1->date_due(), 1, "Item 1 checked out, due date: " . $issue_1->date_due() );
531
532     my $issue_2 = AddIssue( $renewing_borrower_obj, $item_2->barcode);
533     is (defined $issue_2, 1, "Item 2 checked out, due date: " . $issue_2->date_due());
534
535     my $borrowing_borrowernumber = Koha::Checkouts->find( { itemnumber => $item_1->itemnumber } )->borrowernumber;
536     is ($borrowing_borrowernumber, $renewing_borrowernumber, "Item checked out to ".$renewing_borrower_obj->firstname." ".$renewing_borrower_obj->surname);
537
538     my ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrower_obj, $issue_1, 1);
539     is( $renewokay, 1, 'Can renew, no holds for this title or item');
540
541
542     # Biblio-level hold, renewal test
543     AddReserve(
544         {
545             branchcode       => $branch,
546             borrowernumber   => $reserving_borrowernumber,
547             biblionumber     => $biblio->biblionumber,
548             priority         => $priority,
549             reservation_date => $resdate,
550             expiration_date  => $expdate,
551             notes            => $notes,
552             itemnumber       => $checkitem,
553             found            => $found,
554         }
555     );
556
557     # Testing of feature to allow the renewal of reserved items if other items on the record can fill all needed holds
558     Koha::CirculationRules->set_rule(
559         {
560             categorycode => undef,
561             branchcode   => undef,
562             itemtype     => undef,
563             rule_name    => 'onshelfholds',
564             rule_value   => '1',
565         }
566     );
567     Koha::CirculationRules->set_rule(
568         {
569             categorycode => undef,
570             branchcode   => undef,
571             itemtype     => undef,
572             rule_name    => 'renewalsallowed',
573             rule_value   => '5',
574         }
575     );
576     t::lib::Mocks::mock_preference('AllowRenewalIfOtherItemsAvailable', 1 );
577     ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrower_obj, $issue_1);
578     is( $renewokay, 1, 'Bug 11634 - Allow renewal of item with unfilled holds if other available items can fill those holds');
579     ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrower_obj, $issue_2);
580     is( $renewokay, 1, 'Bug 11634 - Allow renewal of item with unfilled holds if other available items can fill those holds');
581
582
583     # Second biblio-level hold
584     my $reserve_id = AddReserve(
585         {
586             branchcode       => $branch,
587             borrowernumber   => $reserving_borrowernumber,
588             biblionumber     => $biblio->biblionumber,
589             priority         => $priority,
590             reservation_date => $resdate,
591             expiration_date  => $expdate,
592             notes            => $notes,
593             itemnumber       => $checkitem,
594             found            => $found,
595         }
596     );
597     ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrower_obj, $issue_1);
598     is( $renewokay, 0, 'Renewal not possible when single patron\'s holds exceed the number of available items');
599     Koha::Holds->find($reserve_id)->delete;
600
601     # Now let's add an item level hold, we should no longer be able to renew the item
602     my $hold = Koha::Database->new()->schema()->resultset('Reserve')->create(
603         {
604             borrowernumber => $hold_waiting_borrowernumber,
605             biblionumber   => $biblio->biblionumber,
606             itemnumber     => $item_1->itemnumber,
607             branchcode     => $branch,
608             priority       => 3,
609             reservedate    => '1999-01-01',
610         }
611     );
612     ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrower_obj, $issue_1);
613     is( $renewokay, 0, 'Bug 13919 - Renewal possible with item level hold on item');
614     $hold->delete();
615
616     # 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
617     # be able to renew these items
618     $hold = Koha::Database->new()->schema()->resultset('Reserve')->create(
619         {
620             borrowernumber => $hold_waiting_borrowernumber,
621             biblionumber   => $biblio->biblionumber,
622             itemnumber     => $item_3->itemnumber,
623             branchcode     => $branch,
624             priority       => 0,
625             found          => 'W'
626         }
627     );
628     ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrower_obj, $issue_1);
629     is( $renewokay, 0, 'Bug 11634 - Allow renewal of item with unfilled holds if other available items can fill those holds');
630     ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrower_obj, $issue_2);
631     is( $renewokay, 0, 'Bug 11634 - Allow renewal of item with unfilled holds if other available items can fill those holds');
632     t::lib::Mocks::mock_preference('AllowRenewalIfOtherItemsAvailable', 0 );
633
634     ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrower_obj, $issue_1);
635     is( $renewokay, 0, '(Bug 10663) Cannot renew, reserved');
636     is( $error, 'on_reserve', '(Bug 10663) Cannot renew, reserved (returned error is on_reserve)');
637
638     ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrower_obj, $issue_2);
639     is( $renewokay, 0, '(Bug 10663) Cannot renew, reserved');
640     is( $error, 'on_reserve', '(Bug 10663) Cannot renew, reserved (returned error is on_reserve)');
641
642     my $reserveid = Koha::Holds->search({ biblionumber => $biblio->biblionumber, borrowernumber => $reserving_borrowernumber })->next->reserve_id;
643     my $reserving_borrower = Koha::Patrons->find( $reserving_borrowernumber );
644     AddIssue($reserving_borrower, $item_3->barcode);
645     my $reserve = $dbh->selectrow_hashref(
646         'SELECT * FROM old_reserves WHERE reserve_id = ?',
647         { Slice => {} },
648         $reserveid
649     );
650     is($reserve->{found}, 'F', 'hold marked completed when checking out item that fills it');
651
652     # Item-level hold, renewal test
653     AddReserve(
654         {
655             branchcode       => $branch,
656             borrowernumber   => $reserving_borrowernumber,
657             biblionumber     => $biblio->biblionumber,
658             priority         => $priority,
659             reservation_date => $resdate,
660             expiration_date  => $expdate,
661             notes            => $notes,
662             itemnumber       => $item_1->itemnumber,
663             found            => $found,
664         }
665     );
666
667     ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrower_obj, $issue_1, 1);
668     is( $renewokay, 0, '(Bug 10663) Cannot renew, item reserved');
669     is( $error, 'on_reserve', '(Bug 10663) Cannot renew, item reserved (returned error is on_reserve)');
670
671     ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrower_obj, $issue_2, 1);
672     is( $renewokay, 1, 'Can renew item 2, item-level hold is on item 1');
673
674     # Items can't fill hold for reasons
675     $issue_1->item->notforloan(1)->store;
676     ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrower_obj, $issue_1, 1);
677     is( $renewokay, 0, 'Cannot renew, item is marked not for loan, but an item specific hold always blocks');
678     $item_1->set({notforloan => 0, itype => $itemtype })->store;
679
680     # FIXME: Add more for itemtype not for loan etc.
681
682     # Restricted users cannot renew when RestrictionBlockRenewing is enabled
683     my $item_5 = $builder->build_sample_item(
684         {
685             biblionumber     => $biblio->biblionumber,
686             library          => $branch,
687             replacementprice => 23.00,
688             itype            => $itemtype,
689         }
690     );
691     my $issue_5 = AddIssue($restricted_borrower_obj, $item_5->barcode);
692     is (defined $issue_5, 1, "Item with date due checked out, due date: ". $issue_5->date_due);
693
694     t::lib::Mocks::mock_preference('RestrictionBlockRenewing','1');
695     ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrower_obj, $issue_2);
696     is( $renewokay, 1, '(Bug 8236), Can renew, user is not restricted');
697     ( $renewokay, $error ) = CanBookBeRenewed($restricted_borrower_obj, $issue_5);
698     is( $renewokay, 0, '(Bug 8236), Cannot renew, user is restricted');
699     is( $error, 'restriction', "Correct error returned");
700
701     # Users cannot renew an overdue item
702     my $item_6 = $builder->build_sample_item(
703         {
704             biblionumber     => $biblio->biblionumber,
705             library          => $branch,
706             replacementprice => 23.00,
707             itype            => $itemtype,
708         }
709     );
710
711     my $item_7 = $builder->build_sample_item(
712         {
713             biblionumber     => $biblio->biblionumber,
714             library          => $branch,
715             replacementprice => 23.00,
716             itype            => $itemtype,
717         }
718     );
719
720     my $issue_6 = AddIssue( $renewing_borrower_obj, $item_6->barcode);
721     is (defined $issue_6, 1, "Item 2 checked out, due date: ".$issue_6->date_due);
722
723     my $now = dt_from_string();
724     my $five_weeks = DateTime::Duration->new(weeks => 5);
725     my $five_weeks_ago = $now - $five_weeks;
726     t::lib::Mocks::mock_preference('finesMode', 'production');
727
728     my $issue_7 = AddIssue($renewing_borrower_obj, $item_7->barcode, $five_weeks_ago);
729     is (defined $issue_7, 1, "Item with passed date due checked out, due date: " . $issue_7->date_due);
730
731     t::lib::Mocks::mock_preference('OverduesBlockRenewing','allow');
732     ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrower_obj, $issue_6);
733     is( $renewokay, 1, '(Bug 8236), Can renew, this item is not overdue');
734     ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrower_obj, $issue_7);
735     is( $renewokay, 1, '(Bug 8236), Can renew, this item is overdue but not pref does not block');
736
737     t::lib::Mocks::mock_preference('OverduesBlockRenewing','block');
738     ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrower_obj, $issue_6);
739     is( $renewokay, 0, '(Bug 8236), Cannot renew, this item is not overdue but patron has overdues');
740     is( $error, 'overdue', "Correct error returned");
741     ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrower_obj, $issue_7);
742     is( $renewokay, 0, '(Bug 8236), Cannot renew, this item is overdue so patron has overdues');
743     is( $error, 'overdue', "Correct error returned");
744
745     t::lib::Mocks::mock_preference('OverduesBlockRenewing','blockitem');
746     ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrower_obj, $issue_6);
747     is( $renewokay, 1, '(Bug 8236), Can renew, this item is not overdue');
748     ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrower_obj, $issue_7);
749     is( $renewokay, 0, '(Bug 8236), Cannot renew, this item is overdue');
750     is( $error, 'overdue', "Correct error returned");
751
752     my ( $fine ) = CalcFine( $item_7->unblessed, $renewing_borrower_obj->categorycode, $branch, $five_weeks_ago, $now );
753     C4::Overdues::UpdateFine(
754         {
755             issue_id       => $issue_7->id(),
756             itemnumber     => $item_7->itemnumber,
757             borrowernumber => $renewing_borrower_obj->borrowernumber,
758             amount         => $fine,
759             due            => Koha::DateUtils::output_pref($five_weeks_ago)
760         }
761     );
762
763     # Make sure fine calculation isn't skipped when adding renewal
764     t::lib::Mocks::mock_preference('CalculateFinesOnReturn', 1);
765
766     # Calculate new due-date based on the present date not to incur
767     # multiple fees
768     t::lib::Mocks::mock_preference('RenewalPeriodBase', 'now');
769
770     my $staff = $builder->build_object({ class => "Koha::Patrons" });
771     t::lib::Mocks::mock_userenv({ patron => $staff });
772
773     t::lib::Mocks::mock_preference('RenewalLog', 0);
774     my $date = output_pref( { dt => dt_from_string(), dateonly => 1, dateformat => 'iso' } );
775     my %params_renewal = (
776         timestamp => { -like => $date . "%" },
777         module => "CIRCULATION",
778         action => "RENEWAL",
779     );
780     my %params_issue = (
781         timestamp => { -like => $date . "%" },
782         module => "CIRCULATION",
783         action => "ISSUE"
784     );
785     my $old_log_size = Koha::ActionLogs->count( \%params_renewal );
786     my $dt = dt_from_string();
787     Time::Fake->offset( $dt->epoch );
788     my $datedue1 = AddRenewal(
789         {
790             borrowernumber => $renewing_borrower_obj->borrowernumber,
791             itemnumber     => $item_7->itemnumber,
792             branch         => $branch
793         }
794     );
795     my $new_log_size = Koha::ActionLogs->count( \%params_renewal );
796     is ($new_log_size, $old_log_size, 'renew log not added because of the syspref RenewalLog');
797     isnt (DateTime->compare($datedue1, $dt), 0, "AddRenewal returned a good duedate");
798     Time::Fake->reset;
799
800     t::lib::Mocks::mock_preference('RenewalLog', 1);
801     $date = output_pref( { dt => dt_from_string(), dateonly => 1, dateformat => 'iso' } );
802     $old_log_size = Koha::ActionLogs->count( \%params_renewal );
803     AddRenewal(
804         {
805             borrowernumber => $renewing_borrower_obj->borrowernumber,
806             itemnumber     => $item_7->itemnumber,
807             branch         => $branch
808         }
809     );
810     $new_log_size = Koha::ActionLogs->count( \%params_renewal );
811     is ($new_log_size, $old_log_size + 1, 'renew log successfully added');
812
813     my $fines = Koha::Account::Lines->search( { borrowernumber => $renewing_borrower_obj->borrowernumber, itemnumber => $item_7->itemnumber } );
814     is( $fines->count, 1, 'AddRenewal left fine' );
815     is( $fines->next->status, 'RENEWED', 'Fine on renewed item is closed out properly' );
816     $fines->delete();
817
818     my $old_issue_log_size = Koha::ActionLogs->count( \%params_issue );
819     my $old_renew_log_size = Koha::ActionLogs->count( \%params_renewal );
820     AddIssue(
821         $renewing_borrower_obj,
822         $item_7->barcode,
823         Koha::DateUtils::output_pref({str=>$issue_6->date_due, dateformat =>'iso'}),
824         0,
825         $date,
826         0,
827         undef
828     ); # TODO: Already issued???
829     $new_log_size = Koha::ActionLogs->count( \%params_renewal );
830     is ($new_log_size, $old_renew_log_size + 1, 'renew log successfully added when renewed via issuing');
831     $new_log_size = Koha::ActionLogs->count( \%params_issue );
832     is ($new_log_size, $old_issue_log_size, 'renew not logged as issue when renewed via issuing');
833
834     $hold = Koha::Holds->search({ biblionumber => $biblio->biblionumber, borrowernumber => $reserving_borrowernumber })->next;
835     $hold->cancel;
836
837     # Bug 14101
838     # Test automatic renewal before value for "norenewalbefore" in policy is set
839     # In this case automatic renewal should still be permitted
840     my $item_4 = $builder->build_sample_item(
841         {
842             biblionumber     => $biblio->biblionumber,
843             library          => $branch,
844             replacementprice => 16.00,
845             itype            => $itemtype,
846         }
847     );
848
849     my $auto_renew_issue =
850         AddIssue( $renewing_borrower_obj, $item_4->barcode, undef, undef, undef, undef, { auto_renew => 1 } );
851     my $info;
852     ( $renewokay, $error, $info ) = CanBookBeRenewed( $renewing_borrower_obj, $auto_renew_issue );
853     is( $info->{soonest_renew_date}, undef, "soonest_renew_date is not returned because this issue can be renewed" );
854     is( $renewokay, 1, 'Bug 25393: Can do a manual renew, even if renewal is automatic and premature' );
855     is(
856         $error, 'auto_renew',
857         'Bug 25393: Can do a manual renew, even if renewal is automatic and premature, norenewalbefore = undef -> manual renewals are allowed at any time'
858     );
859
860     my ( $renewokay_cron, $error_cron, $info_cron ) =
861         CanBookBeRenewed( $renewing_borrower_obj, $auto_renew_issue, undef, 1 );
862     is(
863         $info_cron->{soonest_renew_date}, dt_from_string( $auto_renew_issue->date_due ),
864         "Due date is returned as earliest renewal date when the CanBookBeRenewed check is done by cron and error is 'auto_too_soon'"
865     );
866     AddReserve(
867         {
868             branchcode       => $branch,
869             borrowernumber   => $reserving_borrowernumber,
870             biblionumber     => $biblio->biblionumber,
871             itemnumber       => $bibitems,
872             priority         => $priority,
873             reservation_date => $resdate,
874             expiration_date  => $expdate,
875             notes            => $notes,
876             title            => 'a title',
877             itemnumber       => $item_4->itemnumber,
878             found            => $found
879         }
880     );
881     ( $renewokay, $error ) = CanBookBeRenewed( $renewing_borrower_obj, $auto_renew_issue );
882     is( $renewokay, 0, 'Still should not be able to renew' );
883     is( $error, 'on_reserve', 'returned code is on_reserve, reserve checked when not checking for cron' );
884     ( $renewokay, $error, $info ) = CanBookBeRenewed( $renewing_borrower_obj, $auto_renew_issue, undef, 1 );
885     is( $renewokay, 0, 'Still should not be able to renew' );
886     is( $error, 'auto_too_soon', 'returned code is auto_too_soon, reserve not checked when checking for cron' );
887     is( $info->{soonest_renew_date}, dt_from_string($auto_renew_issue->date_due), "Due date is returned as earliest renewal date when error is 'auto_too_soon'" );
888     ( $renewokay, $error ) = CanBookBeRenewed( $renewing_borrower_obj, $auto_renew_issue, 1 );
889     is( $renewokay, 0, 'Still should not be able to renew' );
890     is( $error, 'on_reserve', 'returned code is on_reserve, auto_too_soon limit is overridden' );
891     ( $renewokay, $error ) = CanBookBeRenewed( $renewing_borrower_obj, $auto_renew_issue, 1, 1 );
892     is( $renewokay, 0, 'Still should not be able to renew' );
893     is( $error, 'on_reserve', 'returned code is on_reserve, auto_too_soon limit is overridden' );
894     $dbh->do('UPDATE circulation_rules SET rule_value = 0 where rule_name = "norenewalbefore"');
895     Koha::Cache::Memory::Lite->flush();
896     ( $renewokay, $error ) = CanBookBeRenewed( $renewing_borrower_obj, $auto_renew_issue, 1 );
897     is( $renewokay, 0, 'Still should not be able to renew' );
898     is( $error, 'on_reserve', 'returned code is on_reserve, auto_renew only happens if not on reserve' );
899     ModReserveCancelAll($item_4->itemnumber, $reserving_borrowernumber);
900
901     $renewing_borrower_obj = Koha::Patrons->find($renewing_borrower_obj->borrowernumber);
902     $renewing_borrower_obj->autorenew_checkouts(0)->store;
903     ( $renewokay, $error ) = CanBookBeRenewed( $renewing_borrower_obj, $auto_renew_issue );
904     is( $renewokay, 1, 'No renewal before is undef, but patron opted out of auto_renewal' );
905     $renewing_borrower_obj->autorenew_checkouts(1)->store;
906
907
908     # Bug 7413
909     # Test premature manual renewal
910     Koha::CirculationRules->set_rule(
911         {
912             categorycode => undef,
913             branchcode   => undef,
914             itemtype     => undef,
915             rule_name    => 'norenewalbefore',
916             rule_value   => '7',
917         }
918     );
919
920     ( $renewokay, $error, $info ) = CanBookBeRenewed($renewing_borrower_obj, $issue_1);
921     is( $renewokay, 0, 'Bug 7413: Cannot renew, renewal is premature');
922     is( $error, 'too_soon', 'Bug 7413: Cannot renew, renewal is premature (returned code is too_soon)');
923     is( $info->{soonest_renew_date}, dt_from_string($issue_1->date_due)->subtract( days => 7 ), "Soonest renew date returned when error is 'too_soon'");
924
925     # Bug 25393
926     # Test premature automatic renewal
927     Koha::CirculationRules->set_rule(
928         {
929             categorycode => undef,
930             branchcode   => undef,
931             itemtype     => undef,
932             rule_name    => 'noautorenewalbefore',
933             rule_value   => '7',
934         }
935     );
936
937     ( $renewokay, $error, $info ) =
938     CanBookBeRenewed( $renewing_borrower_obj, $auto_renew_issue );
939     is( $renewokay, 0, 'Bug 25393: Cannot renew, renewal is premature' );
940     is( $error, 'too_soon',
941         'Bug 25393: Cannot renew, renewal is premature (returned code is too_soon)'
942     );
943     is(
944         $info->{soonest_renew_date}, dt_from_string( $auto_renew_issue->date_due )->subtract( days => 7 ),
945         "Soonest renew date returned when error is 'auto_too_soon'"
946     );
947
948     ( $renewokay_cron, $error_cron, $info_cron ) =
949         CanBookBeRenewed( $renewing_borrower_obj, $auto_renew_issue, undef, 1 );
950     is(
951         $error_cron, 'auto_too_soon',
952         'Bug 25393: Cannot renew, renewal is automatic and premature (returned code is auto_too_soon)'
953     );
954     $renewing_borrower_obj->autorenew_checkouts(0)->store;
955     ( $renewokay, $error, $info ) = CanBookBeRenewed( $renewing_borrower_obj, $auto_renew_issue );
956     is( $renewokay, 0, 'No renewal before is 7, patron opted out of auto_renewal still cannot renew early' );
957     is( $error, 'too_soon', 'Error is too_soon, no auto' );
958     is( $info->{soonest_renew_date}, dt_from_string($auto_renew_issue->date_due)->subtract( days => 7 ), "Soonest renew date returned when error is 'too_soon'");
959     $renewing_borrower_obj->autorenew_checkouts(1)->store;
960
961     # Change policy so that loans can only be renewed exactly on due date (0 days prior to due date)
962     # and test automatic renewal again
963     $dbh->do(q{UPDATE circulation_rules SET rule_value = '0' WHERE rule_name = 'norenewalbefore'});
964     Koha::Cache::Memory::Lite->flush();
965     ( $renewokay, $error, $info ) =
966       CanBookBeRenewed( $renewing_borrower_obj, $auto_renew_issue );
967     is( $renewokay, 0, 'Bug 25393: Cannot manual renew, renewal is too soon' );
968     is( $error, 'too_soon',
969         'Bug 25393: Cannot manual renew, renewal is too soon, even though it\'s also scheduled for auto renewal'
970     );
971     is( $info->{soonest_renew_date}, dt_from_string($auto_renew_issue->date_due), "Soonest renew date returned when error is 'auto_too_soon'");
972
973     # Now we test for noautorenewalbefore if cron
974     $dbh->do(q{UPDATE circulation_rules SET rule_value = '0' WHERE rule_name = 'noautorenewalbefore'});
975     Koha::Cache::Memory::Lite->flush();
976     ( $renewokay, $error, $info ) =
977       CanBookBeRenewed( $renewing_borrower_obj, $auto_renew_issue, undef, 1 );
978     is( $renewokay, 0, 'Bug 25393: Cannot auto renew, renewal is too soon' );
979     is( $error, 'auto_too_soon',
980         'Bug 25393: Cannot auto renew, auto renewal is too soon'
981     );
982     is( $info->{soonest_renew_date}, dt_from_string($auto_renew_issue->date_due), "Soonest renew date returned when error is 'auto_too_soon'");
983
984     $renewing_borrower_obj->autorenew_checkouts(0)->store;
985     ( $renewokay, $error, $info ) = CanBookBeRenewed( $renewing_borrower_obj, $auto_renew_issue );
986     is( $renewokay, 0, 'No renewal before is 0, patron opted out of auto_renewal still cannot renew early' );
987     is( $error, 'too_soon', 'Error is too_soon, no auto' );
988     is( $info->{soonest_renew_date}, dt_from_string($auto_renew_issue->date_due), "Soonest renew date returned when error is 'auto_too_soon'");
989     $renewing_borrower_obj->autorenew_checkouts(1)->store;
990
991     # Change policy so that loans can be renewed 99 days prior to the due date
992     # and test automatic renewal again
993     $dbh->do(q{UPDATE circulation_rules SET rule_value = '99' WHERE rule_name = 'norenewalbefore'});
994     Koha::Cache::Memory::Lite->flush();
995     ( $renewokay, $error ) =
996       CanBookBeRenewed( $renewing_borrower_obj, $auto_renew_issue );
997     is( $renewokay, 1, 'Bug 25393: Can do a manual renew, even if renewal is automatic and premature' );
998     is( $error, 'auto_renew',
999         'Bug 14101: Cannot renew, renewal is automatic (returned code is auto_renew)'
1000     );
1001
1002     # Now we test for noautorenewalbefore if cron
1003     $dbh->do(q{UPDATE circulation_rules SET rule_value = '99' WHERE rule_name = 'noautorenewalbefore'});
1004     Koha::Cache::Memory::Lite->flush();
1005     ( $renewokay, $error, $info ) = CanBookBeRenewed( $renewing_borrower_obj, $auto_renew_issue, undef, 1 );
1006     is( $renewokay, 1, 'Bug 25393: Can auto renew' );
1007     is( $error, 'auto_renew', 'Bug 25393: Can auto renew' );
1008     is( $info->{soonest_renew_date}, undef, "soonest_renew_date is not returned because this issue can be renewed" );
1009
1010     $renewing_borrower_obj->autorenew_checkouts(0)->store;
1011     ( $renewokay, $error ) = CanBookBeRenewed( $renewing_borrower_obj, $auto_renew_issue );
1012     is( $renewokay, 1, 'No renewal before is 99, patron opted out of auto_renewal so can renew' );
1013     $renewing_borrower_obj->autorenew_checkouts(1)->store;
1014
1015
1016     # Bug 31427
1017     # Ensure autorenewal errors always take highest precedence
1018     subtest "auto_renewal errors first" => sub {
1019         plan tests => 4;
1020
1021         my $auto_renew_item = $builder->build_sample_item(
1022             {
1023                 biblionumber => $biblio->biblionumber,
1024                 library      => $branch,
1025             }
1026         );
1027
1028         my $ten_days_ahead = dt_from_string->add( days => 10 );
1029         my $issue          = AddIssue(
1030             $renewing_borrower_obj, $auto_renew_item->barcode, $ten_days_ahead, undef, undef, undef,
1031             { auto_renew => 1 }
1032         );
1033
1034         Koha::CirculationRules->set_rules(
1035             {
1036                 categorycode => undef,
1037                 branchcode   => undef,
1038                 itemtype     => undef,
1039                 rules        => {
1040                     noautorenewalbefore => 7,
1041                     renewalsallowed     => 2,
1042                 }
1043             }
1044         );
1045         my ( $renewokay, $error ) = CanBookBeRenewed( $renewing_borrower_obj, $issue, undef, 'cron' );
1046         is( $renewokay, 0,               'Do not renew, renewal is automatic' );
1047         is( $error,     'auto_too_soon', 'Cannot auto renew, too soon - returned code is auto_too_soon' );
1048
1049         $issue->renewals_count(2)->store;
1050         ( $renewokay, $error ) = CanBookBeRenewed( $renewing_borrower_obj, $issue, undef, 'cron' );
1051         is( $renewokay, 0, 'Do not renew, renewal is automatic' );
1052         is(
1053             $error, 'auto_too_soon',
1054             'Cannot auto renew, too soon - auto renewal error takes precedence over non-autorenewal error too_many'
1055         );
1056     };
1057
1058     subtest "too_late_renewal / no_auto_renewal_after" => sub {
1059         plan tests => 16;
1060         my $item_to_auto_renew = $builder->build_sample_item(
1061             {
1062                 biblionumber => $biblio->biblionumber,
1063                 library      => $branch,
1064             }
1065         );
1066
1067         my $ten_days_before = dt_from_string->add( days => -10 );
1068         my $ten_days_ahead  = dt_from_string->add( days => 10 );
1069         my $issue = AddIssue( $renewing_borrower_obj, $item_to_auto_renew->barcode, $ten_days_ahead, undef, $ten_days_before, undef, { auto_renew => 1 } );
1070
1071         Koha::CirculationRules->set_rules(
1072             {
1073                 categorycode => undef,
1074                 branchcode   => undef,
1075                 itemtype     => undef,
1076                 rules        => {
1077                     norenewalbefore       => '7',
1078                     no_auto_renewal_after => '9',
1079                 }
1080             }
1081         );
1082         ( $renewokay, $error ) =
1083           CanBookBeRenewed( $renewing_borrower_obj, $issue );
1084         is( $renewokay, 0, 'Do not renew, renewal is automatic' );
1085         is( $error, 'auto_too_late', 'Cannot renew, too late(returned code is auto_too_late)' );
1086
1087         Koha::CirculationRules->set_rules(
1088             {
1089                 categorycode => undef,
1090                 branchcode   => undef,
1091                 itemtype     => undef,
1092                 rules        => {
1093                     norenewalbefore       => '7',
1094                     no_auto_renewal_after => '10',
1095                 }
1096             }
1097         );
1098         ( $renewokay, $error ) =
1099           CanBookBeRenewed( $renewing_borrower_obj, $issue );
1100         is( $renewokay, 0, 'Do not renew, renewal is automatic' );
1101         is( $error, 'auto_too_late', 'Cannot auto renew, too late - no_auto_renewal_after is inclusive(returned code is auto_too_late)' );
1102
1103         Koha::CirculationRules->set_rules(
1104             {
1105                 categorycode => undef,
1106                 branchcode   => undef,
1107                 itemtype     => undef,
1108                 rules        => {
1109                     norenewalbefore       => '7',
1110                     no_auto_renewal_after => '11',
1111                 }
1112             }
1113         );
1114         ( $renewokay, $error ) =
1115           CanBookBeRenewed( $renewing_borrower_obj, $issue );
1116         is( $renewokay, 0, 'Do not renew, too_soon' );
1117         is( $error, 'too_soon', 'Cannot renew, too_soon - no_auto_renewal_after is defined(returned code is too_soon)' );
1118
1119         # Now check for cron
1120         Koha::CirculationRules->set_rules(
1121             {
1122                 categorycode => undef,
1123                 branchcode   => undef,
1124                 itemtype     => undef,
1125                 rules        => {
1126                     noautorenewalbefore   => '9',
1127                     no_auto_renewal_after => '11',
1128                 }
1129             }
1130         );
1131         ( $renewokay, $error ) = CanBookBeRenewed( $renewing_borrower_obj, $issue, undef, 1 );
1132         is( $renewokay, 0, 'Do not renew, too_soon' );
1133         is(
1134             $error, 'auto_too_soon',
1135             'Cannot renew, too_soon - no_auto_renewal_after is defined(returned code is too_soon)'
1136         );
1137
1138         Koha::CirculationRules->set_rules(
1139             {
1140                 categorycode => undef,
1141                 branchcode   => undef,
1142                 itemtype     => undef,
1143                 rules        => {
1144                     norenewalbefore       => '10',
1145                     no_auto_renewal_after => '11',
1146                 }
1147             }
1148         );
1149         ( $renewokay, $error ) =
1150           CanBookBeRenewed( $renewing_borrower_obj, $issue );
1151         is( $renewokay, 1,            'Renew, even if renewal is automatic' );
1152         is( $error,     'auto_renew', 'Cannot renew, renew is automatic' );
1153
1154         Koha::CirculationRules->set_rules(
1155             {
1156                 categorycode => undef,
1157                 branchcode   => undef,
1158                 itemtype     => undef,
1159                 rules        => {
1160                     norenewalbefore       => '10',
1161                     no_auto_renewal_after => undef,
1162                     no_auto_renewal_after_hard_limit => dt_from_string->add( days => -1 ),
1163                 }
1164             }
1165         );
1166         ( $renewokay, $error ) =
1167           CanBookBeRenewed( $renewing_borrower_obj, $issue );
1168         is( $renewokay, 0, 'Do not renew, renewal is automatic' );
1169         is( $error, 'auto_too_late', 'Cannot renew, too late(returned code is auto_too_late)' );
1170
1171         Koha::CirculationRules->set_rules(
1172             {
1173                 categorycode => undef,
1174                 branchcode   => undef,
1175                 itemtype     => undef,
1176                 rules        => {
1177                     norenewalbefore       => '7',
1178                     no_auto_renewal_after => '15',
1179                     no_auto_renewal_after_hard_limit => dt_from_string->add( days => -1 ),
1180                 }
1181             }
1182         );
1183         ( $renewokay, $error ) =
1184           CanBookBeRenewed( $renewing_borrower_obj, $issue );
1185         is( $renewokay, 0, 'Do not renew, renewal is automatic' );
1186         is( $error, 'auto_too_late', 'Cannot renew, too late(returned code is auto_too_late)' );
1187
1188         Koha::CirculationRules->set_rules(
1189             {
1190                 categorycode => undef,
1191                 branchcode   => undef,
1192                 itemtype     => undef,
1193                 rules        => {
1194                     norenewalbefore       => '10',
1195                     no_auto_renewal_after => undef,
1196                     no_auto_renewal_after_hard_limit => dt_from_string->add( days => 1 ),
1197                 }
1198             }
1199         );
1200         ( $renewokay, $error ) =
1201           CanBookBeRenewed( $renewing_borrower_obj, $issue );
1202         is( $renewokay, 1, 'Renew, even if renewal is automatic' );
1203         is( $error, 'auto_renew', 'Cannot renew, renew is automatic' );
1204     };
1205
1206     subtest "auto_too_much_oweing | OPACFineNoRenewalsBlockAutoRenew & OPACFineNoRenewalsIncludeCredit" => sub {
1207         plan tests => 10;
1208         my $item_to_auto_renew = $builder->build_sample_item(
1209             {
1210                 biblionumber => $biblio->biblionumber,
1211                 library      => $branch,
1212             }
1213         );
1214
1215         my $ten_days_before = dt_from_string->add( days => -10 );
1216         my $ten_days_ahead = dt_from_string->add( days => 10 );
1217         my $issue = AddIssue( $renewing_borrower_obj, $item_to_auto_renew->barcode, $ten_days_ahead, undef, $ten_days_before, undef, { auto_renew => 1 } );
1218
1219         Koha::CirculationRules->set_rules(
1220             {
1221                 categorycode => undef,
1222                 branchcode   => undef,
1223                 itemtype     => undef,
1224                 rules        => {
1225                     norenewalbefore       => '10',
1226                     no_auto_renewal_after => '11',
1227                 }
1228             }
1229         );
1230         C4::Context->set_preference('OPACFineNoRenewalsBlockAutoRenew','1');
1231         C4::Context->set_preference('OPACFineNoRenewals','10');
1232         C4::Context->set_preference('OPACFineNoRenewalsIncludeCredit','1');
1233         my $fines_amount = 5;
1234         my $account = Koha::Account->new({patron_id => $renewing_borrowernumber});
1235         $account->add_debit(
1236             {
1237                 amount      => $fines_amount,
1238                 interface   => 'test',
1239                 type        => 'OVERDUE',
1240                 item_id     => $item_to_auto_renew->itemnumber,
1241                 description => "Some fines"
1242             }
1243         )->status('RETURNED')->store;
1244         ( $renewokay, $error ) =
1245           CanBookBeRenewed( $renewing_borrower_obj, $issue );
1246         is( $renewokay, 1, 'Renew, even if renewal is automatic' );
1247         is( $error, 'auto_renew', 'Can auto renew, OPACFineNoRenewals=10, patron has 5' );
1248
1249         $account->add_debit(
1250             {
1251                 amount      => $fines_amount,
1252                 interface   => 'test',
1253                 type        => 'OVERDUE',
1254                 item_id     => $item_to_auto_renew->itemnumber,
1255                 description => "Some fines"
1256             }
1257         )->status('RETURNED')->store;
1258         ( $renewokay, $error ) =
1259           CanBookBeRenewed( $renewing_borrower_obj, $issue );
1260         is( $renewokay, 1, 'Renew, even if renewal is automatic' );
1261         is( $error, 'auto_renew', 'Can auto renew, OPACFineNoRenewals=10, patron has 10' );
1262
1263         $account->add_debit(
1264             {
1265                 amount      => $fines_amount,
1266                 interface   => 'test',
1267                 type        => 'OVERDUE',
1268                 item_id     => $item_to_auto_renew->itemnumber,
1269                 description => "Some fines"
1270             }
1271         )->status('RETURNED')->store;
1272         ( $renewokay, $error ) =
1273           CanBookBeRenewed( $renewing_borrower_obj, $issue );
1274         is( $renewokay, 0, 'Do not renew, renewal is automatic' );
1275         is( $error, 'auto_too_much_oweing', 'Cannot auto renew, OPACFineNoRenewals=10, patron has 15' );
1276
1277         $account->add_credit(
1278             {
1279                 amount      => $fines_amount,
1280                 interface   => 'test',
1281                 type        => 'PAYMENT',
1282                 description => "Some payment"
1283             }
1284         )->store;
1285         ( $renewokay, $error ) =
1286           CanBookBeRenewed( $renewing_borrower_obj, $issue );
1287         is( $renewokay, 1, 'Renew, even if renewal is automatic' );
1288         is( $error, 'auto_renew', 'Can auto renew, OPACFineNoRenewals=10, OPACFineNoRenewalsIncludeCredit=1, patron has 15 debt, 5 credit'  );
1289
1290         C4::Context->set_preference('OPACFineNoRenewalsIncludeCredit','0');
1291         ( $renewokay, $error ) =
1292           CanBookBeRenewed( $renewing_borrower_obj, $issue );
1293         is( $renewokay, 0, 'Do not renew, renewal is automatic' );
1294         is( $error, 'auto_too_much_oweing', 'Cannot auto renew, OPACFineNoRenewals=10, OPACFineNoRenewalsIncludeCredit=1, patron has 15 debt, 5 credit'  );
1295
1296         $dbh->do('DELETE FROM accountlines WHERE borrowernumber=?', undef, $renewing_borrowernumber);
1297         C4::Context->set_preference('OPACFineNoRenewalsIncludeCredit','1');
1298     };
1299
1300     subtest "auto_account_expired | BlockExpiredPatronOpacActions" => sub {
1301         plan tests => 6;
1302         my $item_to_auto_renew = $builder->build_sample_item(
1303             {
1304                 biblionumber => $biblio->biblionumber,
1305                 library      => $branch,
1306             }
1307         );
1308
1309         Koha::CirculationRules->set_rules(
1310             {
1311                 categorycode => undef,
1312                 branchcode   => undef,
1313                 itemtype     => undef,
1314                 rules        => {
1315                     norenewalbefore       => 10,
1316                     no_auto_renewal_after => 11,
1317                 }
1318             }
1319         );
1320
1321         my $ten_days_before = dt_from_string->add( days => -10 );
1322         my $ten_days_ahead = dt_from_string->add( days => 10 );
1323
1324         # Patron is expired and BlockExpiredPatronOpacActions=0
1325         # => auto renew is allowed
1326         t::lib::Mocks::mock_preference('BlockExpiredPatronOpacActions', 0);
1327         my $issue = AddIssue( $expired_borrower_obj, $item_to_auto_renew->barcode, $ten_days_ahead, undef, $ten_days_before, undef, { auto_renew => 1 } );
1328         ( $renewokay, $error ) =
1329           CanBookBeRenewed( $expired_borrower_obj, $issue );
1330         is( $renewokay, 1, 'Renew, even if renewal is automatic' );
1331         is( $error, 'auto_renew', 'Can auto renew, patron is expired but BlockExpiredPatronOpacActions=0' );
1332         Koha::Checkouts->find( $issue->issue_id )->delete;
1333
1334
1335         # Patron is expired and BlockExpiredPatronOpacActions=1
1336         # => auto renew is not allowed
1337         t::lib::Mocks::mock_preference('BlockExpiredPatronOpacActions', 1);
1338         $issue = AddIssue( $expired_borrower_obj, $item_to_auto_renew->barcode, $ten_days_ahead, undef, $ten_days_before, undef, { auto_renew => 1 } );
1339         ( $renewokay, $error ) =
1340           CanBookBeRenewed( $expired_borrower_obj, $issue );
1341         is( $renewokay, 0, 'Do not renew, renewal is automatic' );
1342         is( $error, 'auto_account_expired', 'Can not auto renew, lockExpiredPatronOpacActions=1 and patron is expired' );
1343         $issue->delete;
1344
1345         # Patron is not expired and BlockExpiredPatronOpacActions=1
1346         # => auto renew is allowed
1347         t::lib::Mocks::mock_preference('BlockExpiredPatronOpacActions', 1);
1348         $issue = AddIssue( $renewing_borrower_obj, $item_to_auto_renew->barcode, $ten_days_ahead, undef, $ten_days_before, undef, { auto_renew => 1 } );
1349         ( $renewokay, $error ) =
1350           CanBookBeRenewed( $renewing_borrower_obj, $issue );
1351         is( $renewokay, 1, 'Renew, even if renewal is automatic' );
1352         is( $error, 'auto_renew', 'Can auto renew, BlockExpiredPatronOpacActions=1 but patron is not expired' );
1353         $issue->delete;
1354     };
1355
1356     subtest "GetLatestAutoRenewDate" => sub {
1357         plan tests => 5;
1358         my $item_to_auto_renew = $builder->build_sample_item(
1359             {
1360                 biblionumber => $biblio->biblionumber,
1361                 library      => $branch,
1362             }
1363         );
1364
1365         my $ten_days_before = dt_from_string->add( days => -10 );
1366         my $ten_days_ahead  = dt_from_string->add( days => 10 );
1367         my $issue = AddIssue( $renewing_borrower_obj, $item_to_auto_renew->barcode, $ten_days_ahead, undef, $ten_days_before, undef, { auto_renew => 1 } );
1368         Koha::CirculationRules->set_rules(
1369             {
1370                 categorycode => undef,
1371                 branchcode   => undef,
1372                 itemtype     => undef,
1373                 rules        => {
1374                     norenewalbefore       => '7',
1375                     no_auto_renewal_after => '',
1376                     no_auto_renewal_after_hard_limit => undef,
1377                 }
1378             }
1379         );
1380         my $latest_auto_renew_date = GetLatestAutoRenewDate( $renewing_borrower_obj, $issue );
1381         is( $latest_auto_renew_date, undef, 'GetLatestAutoRenewDate should return undef if no_auto_renewal_after or no_auto_renewal_after_hard_limit are not defined' );
1382         my $five_days_before = dt_from_string->add( days => -5 );
1383         Koha::CirculationRules->set_rules(
1384             {
1385                 categorycode => undef,
1386                 branchcode   => undef,
1387                 itemtype     => undef,
1388                 rules        => {
1389                     norenewalbefore       => '10',
1390                     no_auto_renewal_after => '5',
1391                     no_auto_renewal_after_hard_limit => undef,
1392                 }
1393             }
1394         );
1395         $latest_auto_renew_date = GetLatestAutoRenewDate( $renewing_borrower_obj,, $issue );
1396         is( $latest_auto_renew_date->truncate( to => 'minute' ),
1397             $five_days_before->truncate( to => 'minute' ),
1398             'GetLatestAutoRenewDate should return -5 days if no_auto_renewal_after = 5 and date_due is 10 days before'
1399         );
1400         my $five_days_ahead = dt_from_string->add( days => 5 );
1401         $dbh->do(q{UPDATE circulation_rules SET rule_value = '10' WHERE rule_name = 'norenewalbefore'});
1402         $dbh->do(q{UPDATE circulation_rules SET rule_value = '15' WHERE rule_name = 'no_auto_renewal_after'});
1403         $dbh->do(q{UPDATE circulation_rules SET rule_value = NULL WHERE rule_name = 'no_auto_renewal_after_hard_limit'});
1404         Koha::Cache::Memory::Lite->flush();
1405         Koha::CirculationRules->set_rules(
1406             {
1407                 categorycode => undef,
1408                 branchcode   => undef,
1409                 itemtype     => undef,
1410                 rules        => {
1411                     norenewalbefore       => '10',
1412                     no_auto_renewal_after => '15',
1413                     no_auto_renewal_after_hard_limit => undef,
1414                 }
1415             }
1416         );
1417         $latest_auto_renew_date = GetLatestAutoRenewDate( $renewing_borrower_obj, $issue );
1418         is( $latest_auto_renew_date->truncate( to => 'minute' ),
1419             $five_days_ahead->truncate( to => 'minute' ),
1420             'GetLatestAutoRenewDate should return +5 days if no_auto_renewal_after = 15 and date_due is 10 days before'
1421         );
1422         my $two_days_ahead = dt_from_string->add( days => 2 );
1423         Koha::CirculationRules->set_rules(
1424             {
1425                 categorycode => undef,
1426                 branchcode   => undef,
1427                 itemtype     => undef,
1428                 rules        => {
1429                     norenewalbefore       => '10',
1430                     no_auto_renewal_after => '',
1431                     no_auto_renewal_after_hard_limit => dt_from_string->add( days => 2 ),
1432                 }
1433             }
1434         );
1435         $latest_auto_renew_date = GetLatestAutoRenewDate( $renewing_borrower_obj, $issue );
1436         is( $latest_auto_renew_date->truncate( to => 'day' ),
1437             $two_days_ahead->truncate( to => 'day' ),
1438             'GetLatestAutoRenewDate should return +2 days if no_auto_renewal_after_hard_limit is defined and not no_auto_renewal_after'
1439         );
1440         Koha::CirculationRules->set_rules(
1441             {
1442                 categorycode => undef,
1443                 branchcode   => undef,
1444                 itemtype     => undef,
1445                 rules        => {
1446                     norenewalbefore       => '10',
1447                     no_auto_renewal_after => '15',
1448                     no_auto_renewal_after_hard_limit => dt_from_string->add( days => 2 ),
1449                 }
1450             }
1451         );
1452         $latest_auto_renew_date = GetLatestAutoRenewDate( $renewing_borrower_obj, $issue );
1453         is( $latest_auto_renew_date->truncate( to => 'day' ),
1454             $two_days_ahead->truncate( to => 'day' ),
1455             'GetLatestAutoRenewDate should return +2 days if no_auto_renewal_after_hard_limit is < no_auto_renewal_after'
1456         );
1457
1458     };
1459
1460     subtest "auto_renew_final" => sub {
1461         plan tests => 6;
1462         my $item_to_auto_renew = $builder->build_sample_item();
1463         Koha::CirculationRules->set_rules(
1464             {
1465                 categorycode => undef,
1466                 branchcode   => undef,
1467                 itemtype     => $item_to_auto_renew->itype,
1468                 rules        => {
1469                     norenewalbefore => undef,
1470                     renewalsallowed => 1,
1471                 }
1472             }
1473         );
1474
1475         my $ten_days_before = dt_from_string->add( days => -10 );
1476         my $ten_days_ahead  = dt_from_string->add( days => 10 );
1477         my $issue           = AddIssue(
1478             $renewing_borrower_obj, $item_to_auto_renew->barcode, $ten_days_ahead, undef, $ten_days_before,
1479             undef, { auto_renew => 1 }
1480         );
1481         $issue->renewals_count(0)->store;
1482
1483         ( $renewokay, $error ) = CanBookBeRenewed( $renewing_borrower_obj, $issue );
1484         is( $renewokay, 1,                  'Success for auto_renewal' );
1485         is( $error,     'auto_renew_final', 'Auto renewal allowed, but it is the last one' );
1486
1487         # Final unseen renewal
1488         Koha::CirculationRules->set_rules(
1489             {
1490                 categorycode => undef,
1491                 branchcode   => undef,
1492                 itemtype     => $item_to_auto_renew->itype,
1493                 rules        => {
1494                     unseen_renewals_allowed => 2,
1495                     renewalsallowed         => 10,
1496                 }
1497             }
1498         );
1499         t::lib::Mocks::mock_preference( 'UnseenRenewals', 1 );
1500         $issue->unseen_renewals(1)->renewals_count(1)->store;
1501
1502         ( $renewokay, $error ) = CanBookBeRenewed( $renewing_borrower_obj, $issue );
1503         is( $renewokay, 1,                   'Success for auto_renewal' );
1504         is( $error,     'auto_unseen_final', 'Final unseen renewal reported, not final overall' );
1505
1506         # Final unseen renewal
1507         Koha::CirculationRules->set_rules(
1508             {
1509                 categorycode => undef,
1510                 branchcode   => undef,
1511                 itemtype     => $item_to_auto_renew->itype,
1512                 rules        => {
1513                     unseen_renewals_allowed => 2,
1514                     renewalsallowed         => 2,
1515                 }
1516             }
1517         );
1518         ( $renewokay, $error ) = CanBookBeRenewed( $renewing_borrower_obj, $issue );
1519         is( $renewokay, 1,                  'Success for auto_renewal' );
1520         is( $error,     'auto_renew_final', 'Final auto renewal reported' );
1521
1522     };
1523
1524     # Too many renewals
1525     # set policy to forbid renewals
1526     Koha::CirculationRules->set_rules(
1527         {
1528             categorycode => undef,
1529             branchcode   => undef,
1530             itemtype     => undef,
1531             rules        => {
1532                 norenewalbefore => undef,
1533                 renewalsallowed => 0,
1534             }
1535         }
1536     );
1537
1538     ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrower_obj, $issue_1);
1539     is( $renewokay, 0, 'Cannot renew, 0 renewals allowed');
1540     is( $error, 'too_many', 'Cannot renew, 0 renewals allowed (returned code is too_many)');
1541
1542     # Too many unseen renewals
1543     Koha::CirculationRules->set_rules(
1544         {
1545             categorycode => undef,
1546             branchcode   => undef,
1547             itemtype     => undef,
1548             rules        => {
1549                 unseen_renewals_allowed => 2,
1550                 renewalsallowed => 10,
1551             }
1552         }
1553     );
1554     t::lib::Mocks::mock_preference('UnseenRenewals', 1);
1555
1556     $issue_1->unseen_renewals(2)->store;
1557
1558     ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrower_obj, $issue_1);
1559     is( $renewokay, 0, 'Cannot renew, 0 unseen renewals allowed');
1560     is( $error, 'too_unseen', 'Cannot renew, returned code is too_unseen');
1561     Koha::CirculationRules->set_rules(
1562         {
1563             categorycode => undef,
1564             branchcode   => undef,
1565             itemtype     => undef,
1566             rules        => {
1567                 norenewalbefore => undef,
1568                 renewalsallowed => 0,
1569             }
1570         }
1571     );
1572     t::lib::Mocks::mock_preference('UnseenRenewals', 0);
1573
1574     # Test WhenLostForgiveFine and WhenLostChargeReplacementFee
1575     t::lib::Mocks::mock_preference('WhenLostForgiveFine','1');
1576     t::lib::Mocks::mock_preference('WhenLostChargeReplacementFee','1');
1577
1578     C4::Overdues::UpdateFine(
1579         {
1580             issue_id       => $issue_1->id(),
1581             itemnumber     => $item_1->itemnumber,
1582             borrowernumber => $renewing_borrower_obj->borrowernumber,
1583             amount         => 15.00,
1584             type           => q{},
1585             due            => Koha::DateUtils::output_pref($datedue)
1586         }
1587     );
1588
1589     my $line = Koha::Account::Lines->search({ borrowernumber => $renewing_borrower_obj->borrowernumber })->next();
1590     is( $line->debit_type_code, 'OVERDUE', 'Account line type is OVERDUE' );
1591     is( $line->status, 'UNRETURNED', 'Account line status is UNRETURNED' );
1592     is( $line->amountoutstanding+0, 15, 'Account line amount outstanding is 15.00' );
1593     is( $line->amount+0, 15, 'Account line amount is 15.00' );
1594     is( $line->issue_id, $issue_1->id, 'Account line issue id matches' );
1595
1596     my $offset = Koha::Account::Offsets->search({ debit_id => $line->id })->next();
1597     is( $offset->type, 'CREATE', 'Account offset type is CREATE' );
1598     is( $offset->amount+0, 15, 'Account offset amount is 15.00' );
1599
1600     t::lib::Mocks::mock_preference('WhenLostForgiveFine','0');
1601     t::lib::Mocks::mock_preference('WhenLostChargeReplacementFee','0');
1602
1603     LostItem( $item_1->itemnumber, 'test', 1 );
1604
1605     $line = Koha::Account::Lines->find($line->id);
1606     is( $line->debit_type_code, 'OVERDUE', 'Account type remains as OVERDUE' );
1607     isnt( $line->status, 'UNRETURNED', 'Account status correctly changed from UNRETURNED to RETURNED' );
1608
1609     my $item = Koha::Items->find($item_1->itemnumber);
1610     ok( !$item->onloan(), "Lost item marked as returned has false onloan value" );
1611     my $checkout = Koha::Checkouts->find({ itemnumber => $item_1->itemnumber });
1612     is( $checkout, undef, 'LostItem called with forced return has checked in the item' );
1613
1614     my $total_due = $dbh->selectrow_array(
1615         'SELECT SUM( amountoutstanding ) FROM accountlines WHERE borrowernumber = ?',
1616         undef, $renewing_borrower_obj->borrowernumber
1617     );
1618
1619     is( $total_due+0, 15, 'Borrower only charged replacement fee with both WhenLostForgiveFine and WhenLostChargeReplacementFee enabled' );
1620
1621     C4::Context->dbh->do("DELETE FROM accountlines");
1622
1623     C4::Overdues::UpdateFine(
1624         {
1625             issue_id       => $issue_2->id(),
1626             itemnumber     => $item_2->itemnumber,
1627             borrowernumber => $renewing_borrower_obj->borrowernumber,
1628             amount         => 15.00,
1629             type           => q{},
1630             due            => Koha::DateUtils::output_pref($datedue)
1631         }
1632     );
1633
1634     LostItem( $item_2->itemnumber, 'test', 0 );
1635
1636     my $item2 = Koha::Items->find($item_2->itemnumber);
1637     ok( $item2->onloan(), "Lost item *not* marked as returned has true onloan value" );
1638     ok( Koha::Checkouts->find({ itemnumber => $item_2->itemnumber }), 'LostItem called without forced return has checked in the item' );
1639
1640     $total_due = $dbh->selectrow_array(
1641         'SELECT SUM( amountoutstanding ) FROM accountlines WHERE borrowernumber = ?',
1642         undef, $renewing_borrower_obj->borrowernumber
1643     );
1644
1645     ok( $total_due == 15, 'Borrower only charged fine with both WhenLostForgiveFine and WhenLostChargeReplacementFee disabled' );
1646
1647     my $future = dt_from_string();
1648     $future->add( days => 7 );
1649     my $units = C4::Overdues::get_chargeable_units('days', $future, $now, $library2->{branchcode});
1650     ok( $units == 0, '_get_chargeable_units returns 0 for items not past due date (Bug 12596)' );
1651
1652     my $manager = $builder->build_object({ class => "Koha::Patrons" });
1653     t::lib::Mocks::mock_userenv({ patron => $manager,branchcode => $manager->branchcode });
1654     t::lib::Mocks::mock_preference('WhenLostChargeReplacementFee','1');
1655     $checkout = Koha::Checkouts->find( { itemnumber => $item_3->itemnumber } );
1656     LostItem( $item_3->itemnumber, 'test', 0 );
1657     my $accountline = Koha::Account::Lines->find( { itemnumber => $item_3->itemnumber } );
1658     is( $accountline->issue_id, $checkout->id, "Issue id added for lost replacement fee charge" );
1659     is(
1660         $accountline->description,
1661         sprintf( "%s %s %s",
1662             $item_3->biblio->title  || '',
1663             $item_3->barcode        || '',
1664             $item_3->itemcallnumber || '' ),
1665         "Account line description must not contain 'Lost Items ', but be title, barcode, itemcallnumber"
1666     );
1667
1668     # Recalls
1669     t::lib::Mocks::mock_preference('UseRecalls', 1);
1670     Koha::CirculationRules->set_rules({
1671         categorycode => undef,
1672         branchcode => undef,
1673         itemtype => undef,
1674         rules => {
1675             recalls_allowed => 10,
1676             renewalsallowed => 5,
1677         },
1678     });
1679     my $recall_borrower = $builder->build_object({ class => 'Koha::Patrons' });
1680     my $recall_biblio = $builder->build_sample_biblio();
1681     my $recall_item1 = $builder->build_sample_item({ biblionumber => $recall_biblio->biblionumber });
1682     my $recall_item2 = $builder->build_sample_item({ biblionumber => $recall_biblio->biblionumber });
1683
1684     my $recall_issue = AddIssue( $renewing_borrower_obj, $recall_item1->barcode );
1685
1686     # item-level and this item: renewal not allowed
1687     my $recall = Koha::Recall->new({
1688         biblio_id => $recall_item1->biblionumber,
1689         item_id => $recall_item1->itemnumber,
1690         patron_id => $recall_borrower->borrowernumber,
1691         pickup_library_id => $recall_borrower->branchcode,
1692         item_level => 1,
1693     })->store;
1694     ( $renewokay, $error ) = CanBookBeRenewed( $renewing_borrower_obj, $recall_issue );
1695     is( $error, 'recalled', 'Cannot renew item that has been recalled' );
1696     $recall->set_cancelled;
1697
1698     # biblio-level requested recall: renewal not allowed
1699     $recall = Koha::Recall->new({
1700         biblio_id => $recall_item1->biblionumber,
1701         item_id => undef,
1702         patron_id => $recall_borrower->borrowernumber,
1703         pickup_library_id => $recall_borrower->branchcode,
1704         item_level => 0,
1705     })->store;
1706     ( $renewokay, $error ) = CanBookBeRenewed( $renewing_borrower_obj, $recall_issue );
1707     is( $error, 'recalled', 'Cannot renew item if biblio is recalled and has no item allocated' );
1708     $recall->set_cancelled;
1709
1710     # item-level and not this item: renewal allowed
1711     $recall = Koha::Recall->new({
1712         biblio_id => $recall_item2->biblionumber,
1713         item_id => $recall_item2->itemnumber,
1714         patron_id => $recall_borrower->borrowernumber,
1715         pickup_library_id => $recall_borrower->branchcode,
1716         item_level => 1,
1717     })->store;
1718     ( $renewokay, $error ) = CanBookBeRenewed( $renewing_borrower_obj, $recall_issue );
1719     is( $renewokay, 1, 'Can renew item if item-level recall on biblio is not on this item' );
1720     $recall->set_cancelled;
1721
1722     # biblio-level waiting recall: renewal allowed
1723     $recall = Koha::Recall->new({
1724         biblio_id => $recall_item1->biblionumber,
1725         item_id => undef,
1726         patron_id => $recall_borrower->borrowernumber,
1727         pickup_library_id => $recall_borrower->branchcode,
1728         item_level => 0,
1729     })->store;
1730     $recall->set_waiting({ item => $recall_item1 });
1731     ( $renewokay, $error ) = CanBookBeRenewed( $renewing_borrower_obj, $recall_issue );
1732     is( $renewokay, 1, 'Can renew item if biblio-level recall has already been allocated an item' );
1733     $recall->set_cancelled;
1734 };
1735
1736 subtest "CanBookBeRenewed | bookings" => sub {
1737     plan tests => 3;
1738
1739     my $schema = Koha::Database->schema;
1740     $schema->storage->txn_begin;
1741
1742     t::lib::Mocks::mock_preference( 'RenewalPeriodBase', 'date_due' );
1743
1744     my $renewing_patron = $builder->build_object( { class => 'Koha::Patrons' } );
1745     my $booked_patron   = $builder->build_object( { class => 'Koha::Patrons' } );
1746     my $item            = $builder->build_sample_item( { bookable => 1 } );
1747
1748     # issue
1749     my $issue   = AddIssue( $renewing_patron, $item->barcode );
1750     my $datedue = dt_from_string( $issue->date_due() );
1751     is( defined $issue->date_due(), 1, "Item checked out, due date: " . $issue->date_due() );
1752
1753     # item-level booking
1754     my $booking = Koha::Booking->new(
1755         {
1756             patron_id  => $booked_patron->borrowernumber,
1757             item_id    => $item->itemnumber,
1758             biblio_id  => $item->biblio->biblionumber,
1759             start_date => $datedue->clone()->add( days => 2 ),
1760             end_date   => $datedue->clone()->add( days => 10 ),
1761         }
1762     )->store();
1763
1764     # Proposed renewal would encroach on booking
1765     my ( $renewok, $error ) = CanBookBeRenewed( $renewing_patron, $issue, 0 );
1766     is( $renewok, 0,        "Renewal not allowed as it would mean the item was not returned before the next booking" );
1767     is( $error,   'booked', "Error is 'booked'" );
1768
1769     $schema->storage->txn_rollback;
1770 };
1771
1772 subtest "GetUpcomingDueIssues" => sub {
1773     plan tests => 12;
1774
1775     my $branch   = $library2->{branchcode};
1776
1777     #Create another record
1778     my $biblio2 = $builder->build_sample_biblio();
1779
1780     #Create third item
1781     my $item_1 = Koha::Items->find($reused_itemnumber_1);
1782     my $item_2 = Koha::Items->find($reused_itemnumber_2);
1783     my $item_3 = $builder->build_sample_item(
1784         {
1785             biblionumber     => $biblio2->biblionumber,
1786             library          => $branch,
1787             itype            => $itemtype,
1788         }
1789     );
1790
1791
1792     # Create a borrower
1793     my %a_borrower_data = (
1794         firstname =>  'Fridolyn',
1795         surname => 'SOMERS',
1796         categorycode => $patron_category->{categorycode},
1797         branchcode => $branch,
1798     );
1799
1800     my $a_borrower_borrowernumber = Koha::Patron->new(\%a_borrower_data)->store->borrowernumber;
1801     my $a_borrower = Koha::Patrons->find( $a_borrower_borrowernumber );
1802
1803     my $yesterday = DateTime->today(time_zone => C4::Context->tz())->add( days => -1 );
1804     my $two_days_ahead = DateTime->today(time_zone => C4::Context->tz())->add( days => 2 );
1805     my $today = DateTime->today(time_zone => C4::Context->tz());
1806
1807     my $issue = AddIssue( $a_borrower, $item_1->barcode, $yesterday );
1808     my $datedue = dt_from_string( $issue->date_due() );
1809     my $issue_2 = AddIssue( $a_borrower, $item_2->barcode, $two_days_ahead );
1810     my $datedue2 = dt_from_string( $issue->date_due() );
1811
1812     my $upcoming_dues;
1813
1814     # GetUpcomingDueIssues tests
1815     for my $i(0..1) {
1816         $upcoming_dues = C4::Circulation::GetUpcomingDueIssues( { days_in_advance => $i } );
1817         is ( scalar( @$upcoming_dues ), 0, "No items due in less than one day ($i days in advance)" );
1818     }
1819
1820     #days_in_advance needs to be inclusive, so 1 matches items due tomorrow, 0 items due today etc.
1821     $upcoming_dues = C4::Circulation::GetUpcomingDueIssues( { days_in_advance => 2 } );
1822     is ( scalar ( @$upcoming_dues), 1, "Only one item due in 2 days or less" );
1823
1824     for my $i(3..5) {
1825         $upcoming_dues = C4::Circulation::GetUpcomingDueIssues( { days_in_advance => $i } );
1826         is ( scalar( @$upcoming_dues ), 1,
1827             "Bug 9362: Only one item due in more than 2 days ($i days in advance)" );
1828     }
1829
1830     # Bug 11218 - Due notices not generated - GetUpcomingDueIssues needs to select due today items as well
1831
1832     my $issue3 = AddIssue( $a_borrower, $item_3->barcode, $today );
1833
1834     $upcoming_dues = C4::Circulation::GetUpcomingDueIssues( { days_in_advance => -1 } );
1835     is ( scalar ( @$upcoming_dues), 0, "Overdues can not be selected" );
1836
1837     $upcoming_dues = C4::Circulation::GetUpcomingDueIssues( { days_in_advance => 0 } );
1838     is ( scalar ( @$upcoming_dues), 1, "1 item is due today" );
1839
1840     $upcoming_dues = C4::Circulation::GetUpcomingDueIssues( { days_in_advance => 1 } );
1841     is ( scalar ( @$upcoming_dues), 1, "1 item is due today, none tomorrow" );
1842
1843     $upcoming_dues = C4::Circulation::GetUpcomingDueIssues( { days_in_advance => 2 }  );
1844     is ( scalar ( @$upcoming_dues), 2, "2 items are due withing 2 days" );
1845
1846     $upcoming_dues = C4::Circulation::GetUpcomingDueIssues( { days_in_advance => 3 } );
1847     is ( scalar ( @$upcoming_dues), 2, "2 items are due withing 2 days" );
1848
1849     $upcoming_dues = C4::Circulation::GetUpcomingDueIssues();
1850     is ( scalar ( @$upcoming_dues), 2, "days_in_advance is 7 in GetUpcomingDueIssues if not provided" );
1851
1852 };
1853
1854 subtest "Bug 13841 - Do not create new 0 amount fines" => sub {
1855     my $branch   = $library2->{branchcode};
1856
1857     my $biblio = $builder->build_sample_biblio();
1858
1859     #Create third item
1860     my $item = $builder->build_sample_item(
1861         {
1862             biblionumber     => $biblio->biblionumber,
1863             library          => $branch,
1864             itype            => $itemtype,
1865         }
1866     );
1867
1868     # Create a borrower
1869     my %a_borrower_data = (
1870         firstname =>  'Kyle',
1871         surname => 'Hall',
1872         categorycode => $patron_category->{categorycode},
1873         branchcode => $branch,
1874     );
1875
1876     my $borrowernumber = Koha::Patron->new(\%a_borrower_data)->store->borrowernumber;
1877
1878     my $borrower = Koha::Patrons->find( $borrowernumber );
1879     my $issue = AddIssue( $borrower, $item->barcode );
1880     UpdateFine(
1881         {
1882             issue_id       => $issue->id(),
1883             itemnumber     => $item->itemnumber,
1884             borrowernumber => $borrowernumber,
1885             amount         => 0,
1886             type           => q{}
1887         }
1888     );
1889
1890     my $hr = $dbh->selectrow_hashref(q{SELECT COUNT(*) AS count FROM accountlines WHERE borrowernumber = ? AND itemnumber = ?}, undef, $borrowernumber, $item->itemnumber );
1891     my $count = $hr->{count};
1892
1893     is ( $count, 0, "Calling UpdateFine on non-existant fine with an amount of 0 does not result in an empty fine" );
1894 };
1895
1896 subtest "AllowRenewalIfOtherItemsAvailable tests" => sub {
1897     plan tests => 13;
1898     my $biblio = $builder->build_sample_biblio();
1899     my $item_1 = $builder->build_sample_item(
1900         {
1901             biblionumber     => $biblio->biblionumber,
1902             library          => $library2->{branchcode},
1903         }
1904     );
1905     my $item_2= $builder->build_sample_item(
1906         {
1907             biblionumber     => $biblio->biblionumber,
1908             library          => $library2->{branchcode},
1909             itype            => $item_1->effective_itemtype,
1910         }
1911     );
1912
1913     Koha::CirculationRules->set_rules(
1914         {
1915             categorycode => undef,
1916             itemtype     => $item_1->effective_itemtype,
1917             branchcode   => undef,
1918             rules        => {
1919                 reservesallowed => 25,
1920                 holds_per_record => 25,
1921                 issuelength     => 14,
1922                 lengthunit      => 'days',
1923                 renewalsallowed => 1,
1924                 renewalperiod   => 7,
1925                 norenewalbefore => undef,
1926                 auto_renew      => 0,
1927                 fine            => .10,
1928                 chargeperiod    => 1,
1929                 maxissueqty     => 20
1930             }
1931         }
1932     );
1933
1934
1935     my $borrower1 = Koha::Patron->new({
1936         firstname    => 'Kyle',
1937         surname      => 'Hall',
1938         categorycode => $patron_category->{categorycode},
1939         branchcode   => $library2->{branchcode},
1940     })->store;
1941     my $borrowernumber2 = Koha::Patron->new({
1942         firstname    => 'Chelsea',
1943         surname      => 'Hall',
1944         categorycode => $patron_category->{categorycode},
1945         branchcode   => $library2->{branchcode},
1946     })->store->borrowernumber;
1947     my $patron_category_2 = $builder->build(
1948         {
1949             source => 'Category',
1950             value  => {
1951                 category_type                 => 'P',
1952                 enrolmentfee                  => 0,
1953                 BlockExpiredPatronOpacActions => -1, # Pick the pref value
1954             }
1955         }
1956     );
1957     my $borrowernumber3 = Koha::Patron->new({
1958         firstname    => 'Carnegie',
1959         surname      => 'Hall',
1960         categorycode => $patron_category_2->{categorycode},
1961         branchcode   => $library2->{branchcode},
1962     })->store->borrowernumber;
1963
1964     my $issue = AddIssue( $borrower1, $item_1->barcode );
1965
1966     my ( $renewokay, $error ) = CanBookBeRenewed( $borrower1, $issue );
1967     is( $renewokay, 1, 'Bug 14337 - Verify the borrower can renew with no hold on the record' );
1968
1969     AddReserve(
1970         {
1971             branchcode     => $library2->{branchcode},
1972             borrowernumber => $borrowernumber2,
1973             biblionumber   => $biblio->biblionumber,
1974             priority       => 1,
1975         }
1976     );
1977
1978     Koha::CirculationRules->set_rules(
1979         {
1980             categorycode => undef,
1981             itemtype     => $item_1->effective_itemtype,
1982             branchcode   => undef,
1983             rules        => {
1984                 onshelfholds => 0,
1985             }
1986         }
1987     );
1988     t::lib::Mocks::mock_preference( 'AllowRenewalIfOtherItemsAvailable', 0 );
1989     ( $renewokay, $error ) = CanBookBeRenewed( $borrower1, $issue );
1990     is( $renewokay, 0, 'Bug 14337 - Verify the borrower cannot renew with a hold on the record if AllowRenewalIfOtherItemsAvailable and onshelfholds are disabled' );
1991
1992     t::lib::Mocks::mock_preference( 'AllowRenewalIfOtherItemsAvailable', 1 );
1993     ( $renewokay, $error ) = CanBookBeRenewed( $borrower1, $issue );
1994     is( $renewokay, 0, 'Bug 14337 - Verify the borrower cannot renew with a hold on the record if AllowRenewalIfOtherItemsAvailable is enabled and onshelfholds is disabled' );
1995
1996     Koha::CirculationRules->set_rules(
1997         {
1998             categorycode => undef,
1999             itemtype     => $item_1->effective_itemtype,
2000             branchcode   => undef,
2001             rules        => {
2002                 onshelfholds => 1,
2003             }
2004         }
2005     );
2006     t::lib::Mocks::mock_preference( 'AllowRenewalIfOtherItemsAvailable', 0 );
2007     ( $renewokay, $error ) = CanBookBeRenewed( $borrower1, $issue );
2008     is( $renewokay, 0, 'Bug 14337 - Verify the borrower cannot renew with a hold on the record if AllowRenewalIfOtherItemsAvailable is disabled and onshelfhold is enabled' );
2009
2010     t::lib::Mocks::mock_preference( 'AllowRenewalIfOtherItemsAvailable', 1 );
2011     ( $renewokay, $error ) = CanBookBeRenewed( $borrower1, $issue );
2012     is( $renewokay, 1, 'Bug 14337 - Verify the borrower can renew with a hold on the record if AllowRenewalIfOtherItemsAvailable and onshelfhold are enabled' );
2013
2014     AddReserve(
2015         {
2016             branchcode     => $library2->{branchcode},
2017             borrowernumber => $borrowernumber3,
2018             biblionumber   => $biblio->biblionumber,
2019             priority       => 1,
2020         }
2021     );
2022
2023     ( $renewokay, $error ) = CanBookBeRenewed( $borrower1, $issue );
2024     is( $renewokay, 0, 'Verify the borrower cannot renew with 2 holds on the record if AllowRenewalIfOtherItemsAvailable and onshelfhold are enabled and one other item on record' );
2025
2026     my $item_3= $builder->build_sample_item(
2027         {
2028             biblionumber     => $biblio->biblionumber,
2029             library          => $library2->{branchcode},
2030             itype            => $item_1->effective_itemtype,
2031         }
2032     );
2033
2034     ( $renewokay, $error ) = CanBookBeRenewed( $borrower1, $issue );
2035     is( $renewokay, 1, 'Verify the borrower cannot renew with 2 holds on the record if AllowRenewalIfOtherItemsAvailable and onshelfhold are enabled and two other items on record' );
2036
2037     Koha::CirculationRules->set_rules(
2038         {
2039             categorycode => $patron_category_2->{categorycode},
2040             itemtype     => $item_1->effective_itemtype,
2041             branchcode   => undef,
2042             rules        => {
2043                 reservesallowed => 0,
2044             }
2045         }
2046     );
2047
2048     ( $renewokay, $error ) = CanBookBeRenewed( $borrower1, $issue );
2049     is( $renewokay, 0, 'Verify the borrower cannot renew with 2 holds on the record, but only one of those holds can be filled when AllowRenewalIfOtherItemsAvailable and onshelfhold are enabled and two other items on record' );
2050
2051     Koha::CirculationRules->set_rules(
2052         {
2053             categorycode => $patron_category_2->{categorycode},
2054             itemtype     => $item_1->effective_itemtype,
2055             branchcode   => undef,
2056             rules        => {
2057                 reservesallowed => 25,
2058             }
2059         }
2060     );
2061
2062     # Setting item not checked out to be not for loan but holdable
2063     $item_2->notforloan(-1)->store;
2064
2065     ( $renewokay, $error ) = CanBookBeRenewed( $borrower1, $issue );
2066     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' );
2067
2068     my $mock_circ = Test::MockModule->new("C4::Circulation");
2069     $mock_circ->mock( CanItemBeReserved => sub {
2070         warn "Checked";
2071         return { status => 'no' }
2072     } );
2073
2074     $item_2->notforloan(0)->store;
2075     $item_3->delete();
2076     # Two items total, one item available, one issued, two holds on record
2077
2078     warnings_are{
2079        ( $renewokay, $error ) = CanBookBeRenewed( $borrower1, $issue );
2080     } [], "CanItemBeReserved not called when there are more possible holds than available items";
2081     is( $renewokay, 0, 'Borrower cannot renew when there are more holds than available items' );
2082
2083     $item_3 = $builder->build_sample_item(
2084         {
2085             biblionumber     => $biblio->biblionumber,
2086             library          => $library2->{branchcode},
2087             itype            => $item_1->effective_itemtype,
2088         }
2089     );
2090
2091     Koha::CirculationRules->set_rules(
2092         {
2093             categorycode => undef,
2094             itemtype     => $item_1->effective_itemtype,
2095             branchcode   => undef,
2096             rules        => {
2097                 reservesallowed => 0,
2098             }
2099         }
2100     );
2101
2102     warnings_are{
2103        ( $renewokay, $error ) = CanBookBeRenewed( $borrower1, $issue );
2104     } ["Checked","Checked"], "CanItemBeReserved only called once per available item if it returns a negative result for all items for a borrower";
2105     is( $renewokay, 0, 'Borrower cannot renew when there are more holds than available items' );
2106
2107 };
2108
2109 {
2110     # Don't allow renewing onsite checkout
2111     my $branch   = $library->{branchcode};
2112
2113     #Create another record
2114     my $biblio = $builder->build_sample_biblio();
2115
2116     my $item = $builder->build_sample_item(
2117         {
2118             biblionumber     => $biblio->biblionumber,
2119             library          => $branch,
2120             itype            => $itemtype,
2121         }
2122     );
2123
2124     my $borrower = Koha::Patron->new({
2125         firstname =>  'fn',
2126         surname => 'dn',
2127         categorycode => $patron_category->{categorycode},
2128         branchcode => $branch,
2129     })->store;
2130
2131     my $issue = AddIssue( $borrower, $item->barcode, undef, undef, undef, undef, { onsite_checkout => 1 } );
2132     my ( $renewed, $error ) = CanBookBeRenewed( $borrower, $issue );
2133     is( $renewed, 0, 'CanBookBeRenewed should not allow to renew on-site checkout' );
2134     is( $error, 'onsite_checkout', 'A correct error code should be returned by CanBookBeRenewed for on-site checkout' );
2135 }
2136
2137 {
2138     my $library = $builder->build({ source => 'Branch' });
2139
2140     my $biblio = $builder->build_sample_biblio();
2141
2142     my $item = $builder->build_sample_item(
2143         {
2144             biblionumber     => $biblio->biblionumber,
2145             library          => $library->{branchcode},
2146             itype            => $itemtype,
2147         }
2148     );
2149     my $patron = $builder->build_object( { class => 'Koha::Patrons',  value => { branchcode => $library->{branchcode}, categorycode => $patron_category->{categorycode} } } );
2150
2151     my $issue = AddIssue( $patron, $item->barcode );
2152     UpdateFine(
2153         {
2154             issue_id       => $issue->id,
2155             itemnumber     => $item->itemnumber,
2156             borrowernumber => $patron->borrowernumber,
2157             amount         => 1,
2158             type           => q{}
2159         }
2160     );
2161     UpdateFine(
2162         {
2163             issue_id       => $issue->id,
2164             itemnumber     => $item->itemnumber,
2165             borrowernumber => $patron->borrowernumber,
2166             amount         => 2,
2167             type           => q{}
2168         }
2169     );
2170     is( Koha::Account::Lines->search({ issue_id => $issue->id })->count, 1, 'UpdateFine should not create a new accountline when updating an existing fine');
2171 }
2172
2173 subtest 'CanBookBeIssued & AllowReturnToBranch' => sub {
2174     plan tests => 24;
2175
2176     my $homebranch    = $builder->build( { source => 'Branch' } );
2177     my $holdingbranch = $builder->build( { source => 'Branch' } );
2178     my $otherbranch   = $builder->build( { source => 'Branch' } );
2179     my $patron_1      = $builder->build_object( { class => 'Koha::Patrons', value => { categorycode => $patron_category->{categorycode} } } );
2180     my $patron_2      = $builder->build_object( { class => 'Koha::Patrons', value => { categorycode => $patron_category->{categorycode} } } );
2181
2182     my $item = $builder->build_sample_item(
2183         {
2184             homebranch    => $homebranch->{branchcode},
2185             holdingbranch => $holdingbranch->{branchcode},
2186         }
2187     );
2188     Koha::CirculationRules->set_rules(
2189         {
2190             categorycode => undef,
2191             itemtype     => $item->effective_itemtype,
2192             branchcode   => undef,
2193             rules        => {
2194                 reservesallowed => 25,
2195                 issuelength     => 14,
2196                 lengthunit      => 'days',
2197                 renewalsallowed => 1,
2198                 renewalperiod   => 7,
2199                 norenewalbefore => undef,
2200                 auto_renew      => 0,
2201                 fine            => .10,
2202                 chargeperiod    => 1,
2203                 maxissueqty     => 20
2204             }
2205         }
2206     );
2207
2208     set_userenv($holdingbranch);
2209
2210     my $issue = AddIssue( $patron_1, $item->barcode );
2211     is( ref($issue), 'Koha::Checkout', 'AddIssue should return a Koha::Checkout object' );
2212
2213     my ( $error, $question, $alerts );
2214
2215     # AllowReturnToBranch == anywhere
2216     t::lib::Mocks::mock_preference( 'AllowReturnToBranch', 'anywhere' );
2217     ## Test that unknown barcodes don't generate internal server errors
2218     set_userenv($homebranch);
2219     ( $error, $question, $alerts ) = CanBookBeIssued( $patron_2, 'KohaIsAwesome' );
2220     ok( $error->{UNKNOWN_BARCODE}, '"KohaIsAwesome" is not a valid barcode as expected.' );
2221     ## Can be issued from homebranch
2222     set_userenv($homebranch);
2223     ( $error, $question, $alerts ) = CanBookBeIssued( $patron_2, $item->barcode );
2224     is( keys(%$error) + keys(%$alerts), 0, 'There should not be any errors or alerts (impossible)' . str($error, $question, $alerts) );
2225     is( exists $question->{ISSUED_TO_ANOTHER}, 1, 'ISSUED_TO_ANOTHER must be set' );
2226     ## Can be issued from holdingbranch
2227     set_userenv($holdingbranch);
2228     ( $error, $question, $alerts ) = CanBookBeIssued( $patron_2, $item->barcode );
2229     is( keys(%$error) + keys(%$alerts), 0, 'There should not be any errors or alerts (impossible)' . str($error, $question, $alerts) );
2230     is( exists $question->{ISSUED_TO_ANOTHER}, 1, 'ISSUED_TO_ANOTHER must be set' );
2231     ## Can be issued from another branch
2232     ( $error, $question, $alerts ) = CanBookBeIssued( $patron_2, $item->barcode );
2233     is( keys(%$error) + keys(%$alerts), 0, 'There should not be any errors or alerts (impossible)' . str($error, $question, $alerts) );
2234     is( exists $question->{ISSUED_TO_ANOTHER}, 1, 'ISSUED_TO_ANOTHER must be set' );
2235
2236     # AllowReturnToBranch == holdingbranch
2237     t::lib::Mocks::mock_preference( 'AllowReturnToBranch', 'holdingbranch' );
2238     ## Cannot be issued from homebranch
2239     set_userenv($homebranch);
2240     ( $error, $question, $alerts ) = CanBookBeIssued( $patron_2, $item->barcode );
2241     is( keys(%$question) + keys(%$alerts), 0, 'There should not be any errors or alerts (impossible)' . str($error, $question, $alerts) );
2242     is( exists $error->{RETURN_IMPOSSIBLE}, 1, 'RETURN_IMPOSSIBLE must be set' );
2243     is( $error->{branch_to_return},         $holdingbranch->{branchcode}, 'branch_to_return matched holdingbranch' );
2244     ## Can be issued from holdinbranch
2245     set_userenv($holdingbranch);
2246     ( $error, $question, $alerts ) = CanBookBeIssued( $patron_2, $item->barcode );
2247     is( keys(%$error) + keys(%$alerts), 0, 'There should not be any errors or alerts (impossible)' . str($error, $question, $alerts) );
2248     is( exists $question->{ISSUED_TO_ANOTHER}, 1, 'ISSUED_TO_ANOTHER must be set' );
2249     ## Cannot be issued from another branch
2250     set_userenv($otherbranch);
2251     ( $error, $question, $alerts ) = CanBookBeIssued( $patron_2, $item->barcode );
2252     is( keys(%$question) + keys(%$alerts), 0, 'There should not be any errors or alerts (impossible)' . str($error, $question, $alerts) );
2253     is( exists $error->{RETURN_IMPOSSIBLE}, 1, 'RETURN_IMPOSSIBLE must be set' );
2254     is( $error->{branch_to_return},         $holdingbranch->{branchcode}, 'branch_to_return matches holdingbranch' );
2255
2256     # AllowReturnToBranch == homebranch
2257     t::lib::Mocks::mock_preference( 'AllowReturnToBranch', 'homebranch' );
2258     ## Can be issued from holdinbranch
2259     set_userenv($homebranch);
2260     ( $error, $question, $alerts ) = CanBookBeIssued( $patron_2, $item->barcode );
2261     is( keys(%$error) + keys(%$alerts), 0, 'There should not be any errors or alerts (impossible)' . str($error, $question, $alerts) );
2262     is( exists $question->{ISSUED_TO_ANOTHER}, 1, 'ISSUED_TO_ANOTHER must be set' );
2263     ## Cannot be issued from holdinbranch
2264     set_userenv($holdingbranch);
2265     ( $error, $question, $alerts ) = CanBookBeIssued( $patron_2, $item->barcode );
2266     is( keys(%$question) + keys(%$alerts), 0, 'There should not be any errors or alerts (impossible)' . str($error, $question, $alerts) );
2267     is( exists $error->{RETURN_IMPOSSIBLE}, 1, 'RETURN_IMPOSSIBLE must be set' );
2268     is( $error->{branch_to_return},         $homebranch->{branchcode}, 'branch_to_return matches homebranch' );
2269     ## Cannot be issued from holdinbranch
2270     set_userenv($otherbranch);
2271     ( $error, $question, $alerts ) = CanBookBeIssued( $patron_2, $item->barcode );
2272     is( keys(%$question) + keys(%$alerts), 0, 'There should not be any errors or alerts (impossible)' . str($error, $question, $alerts) );
2273     is( exists $error->{RETURN_IMPOSSIBLE}, 1, 'RETURN_IMPOSSIBLE must be set' );
2274     is( $error->{branch_to_return},         $homebranch->{branchcode}, 'branch_to_return matches homebranch' );
2275
2276     # TODO t::lib::Mocks::mock_preference('AllowReturnToBranch', 'homeorholdingbranch');
2277 };
2278
2279 subtest 'AddIssue & AllowReturnToBranch' => sub {
2280     plan tests => 9;
2281
2282     my $homebranch    = $builder->build( { source => 'Branch' } );
2283     my $holdingbranch = $builder->build( { source => 'Branch' } );
2284     my $otherbranch   = $builder->build( { source => 'Branch' } );
2285
2286     my $patron_1  = $builder->build_object({
2287         class => 'Koha::Patrons',
2288         value => { categorycode => $patron_category->{categorycode} }
2289     });
2290
2291     my $patron_2  = $builder->build_object({
2292         class => 'Koha::Patrons',
2293         value => { categorycode => $patron_category->{categorycode} }
2294     });
2295
2296
2297     my $item = $builder->build_sample_item(
2298         {
2299             homebranch    => $homebranch->{branchcode},
2300             holdingbranch => $holdingbranch->{branchcode},
2301         }
2302     );
2303
2304     set_userenv($holdingbranch);
2305
2306     my $ref_issue = 'Koha::Checkout';
2307     my $issue = AddIssue( $patron_1, $item->barcode );
2308
2309     my ( $error, $question, $alerts );
2310
2311     # AllowReturnToBranch == homebranch
2312     t::lib::Mocks::mock_preference( 'AllowReturnToBranch', 'anywhere' );
2313     ## Can be issued from homebranch
2314     set_userenv($homebranch);
2315     is ( ref( AddIssue( $patron_2, $item->barcode ) ), $ref_issue, 'AllowReturnToBranch - anywhere | Can be issued from homebranch');
2316     set_userenv($holdingbranch); AddIssue( $patron_1, $item->barcode ); # Reinsert the original issue
2317     ## Can be issued from holdinbranch
2318     set_userenv($holdingbranch);
2319     is ( ref( AddIssue( $patron_2, $item->barcode ) ), $ref_issue, 'AllowReturnToBranch - anywhere | Can be issued from holdingbranch');
2320     set_userenv($holdingbranch); AddIssue( $patron_1, $item->barcode ); # Reinsert the original issue
2321     ## Can be issued from another branch
2322     set_userenv($otherbranch);
2323     is ( ref( AddIssue( $patron_2, $item->barcode ) ), $ref_issue, 'AllowReturnToBranch - anywhere | Can be issued from otherbranch');
2324     set_userenv($holdingbranch); AddIssue( $patron_1, $item->barcode ); # Reinsert the original issue
2325
2326     # AllowReturnToBranch == holdinbranch
2327     t::lib::Mocks::mock_preference( 'AllowReturnToBranch', 'holdingbranch' );
2328     ## Cannot be issued from homebranch
2329     set_userenv($homebranch);
2330     is ( ref( AddIssue( $patron_2, $item->barcode ) ), '', 'AllowReturnToBranch - holdingbranch | Cannot be issued from homebranch');
2331     ## Can be issued from holdingbranch
2332     set_userenv($holdingbranch);
2333     is ( ref( AddIssue( $patron_2, $item->barcode ) ), $ref_issue, 'AllowReturnToBranch - holdingbranch | Can be issued from holdingbranch');
2334     set_userenv($holdingbranch); AddIssue( $patron_1, $item->barcode ); # Reinsert the original issue
2335     ## Cannot be issued from another branch
2336     set_userenv($otherbranch);
2337     is ( ref( AddIssue( $patron_2, $item->barcode ) ), '', 'AllowReturnToBranch - holdingbranch | Cannot be issued from otherbranch');
2338
2339     # AllowReturnToBranch == homebranch
2340     t::lib::Mocks::mock_preference( 'AllowReturnToBranch', 'homebranch' );
2341     ## Can be issued from homebranch
2342     set_userenv($homebranch);
2343     is ( ref( AddIssue( $patron_2, $item->barcode ) ), $ref_issue, 'AllowReturnToBranch - homebranch | Can be issued from homebranch' );
2344     AddIssue( $patron_1, $item->barcode ); # Re-issue to patron 1
2345     ## Cannot be issued from holdinbranch
2346     set_userenv($holdingbranch);
2347     is ( ref( AddIssue( $patron_2, $item->barcode ) ), '', 'AllowReturnToBranch - homebranch | Cannot be issued from holdingbranch' );
2348     ## Cannot be issued from another branch
2349     set_userenv($otherbranch);
2350     is ( ref( AddIssue( $patron_2, $item->barcode ) ), '', 'AllowReturnToBranch - homebranch | Cannot be issued from otherbranch' );
2351     # TODO t::lib::Mocks::mock_preference('AllowReturnToBranch', 'homeorholdingbranch');
2352 };
2353
2354 subtest 'AddIssue | recalls' => sub {
2355     plan tests => 3;
2356
2357     t::lib::Mocks::mock_preference("UseRecalls", 1);
2358     t::lib::Mocks::mock_preference("item-level_itypes", 1);
2359     my $patron1 = $builder->build_object({ class => 'Koha::Patrons' });
2360     my $patron2 = $builder->build_object({ class => 'Koha::Patrons' });
2361     my $item = $builder->build_sample_item;
2362     Koha::CirculationRules->set_rules({
2363         branchcode => undef,
2364         itemtype => undef,
2365         categorycode => undef,
2366         rules => {
2367             recalls_allowed => 10,
2368         },
2369     });
2370
2371     # checking out item that they have recalled
2372     my $recall1 = Koha::Recall->new(
2373         {   patron_id         => $patron1->borrowernumber,
2374             biblio_id         => $item->biblionumber,
2375             item_id           => $item->itemnumber,
2376             item_level        => 1,
2377             pickup_library_id => $patron1->branchcode,
2378         }
2379     )->store;
2380     AddIssue( $patron1, $item->barcode, undef, undef, undef, undef, { recall_id => $recall1->id } );
2381     $recall1 = Koha::Recalls->find( $recall1->id );
2382     is( $recall1->fulfilled, 1, 'Recall was fulfilled when patron checked out item' );
2383     AddReturn( $item->barcode, $item->homebranch );
2384
2385     # this item is has a recall request. cancel recall
2386     my $recall2 = Koha::Recall->new(
2387         {   patron_id         => $patron2->borrowernumber,
2388             biblio_id         => $item->biblionumber,
2389             item_id           => $item->itemnumber,
2390             item_level        => 1,
2391             pickup_library_id => $patron2->branchcode,
2392         }
2393     )->store;
2394     AddIssue( $patron1, $item->barcode, undef, undef, undef, undef, { recall_id => $recall2->id, cancel_recall => 'cancel' } );
2395     $recall2 = Koha::Recalls->find( $recall2->id );
2396     is( $recall2->cancelled, 1, 'Recall was cancelled when patron checked out item' );
2397     AddReturn( $item->barcode, $item->homebranch );
2398
2399     # this item is waiting to fulfill a recall. revert recall
2400     my $recall3 = Koha::Recall->new(
2401         {   patron_id         => $patron2->borrowernumber,
2402             biblio_id         => $item->biblionumber,
2403             item_id           => $item->itemnumber,
2404             item_level        => 1,
2405             pickup_library_id => $patron2->branchcode,
2406         }
2407     )->store;
2408     $recall3->set_waiting;
2409     AddIssue( $patron1, $item->barcode, undef, undef, undef, undef, { recall_id => $recall3->id, cancel_recall => 'revert' } );
2410     $recall3 = Koha::Recalls->find( $recall3->id );
2411     is( $recall3->requested, 1, 'Recall was reverted from waiting when patron checked out item' );
2412     AddReturn( $item->barcode, $item->homebranch );
2413 };
2414
2415 subtest 'AddIssue & illrequests.due_date' => sub {
2416     plan tests => 2;
2417
2418     t::lib::Mocks::mock_preference( 'ILLModule', 1 );
2419     my $library = $builder->build( { source => 'Branch' } );
2420     my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
2421     my $item = $builder->build_sample_item();
2422
2423     set_userenv($library);
2424
2425     my $custom_date_due = '9999-12-18 12:34:56';
2426     my $expected_date_due = '9999-12-18 23:59:00';
2427     my $illrequest = Koha::Illrequest->new({
2428         borrowernumber => $patron->borrowernumber,
2429         biblio_id => $item->biblionumber,
2430         branchcode => $library->{'branchcode'},
2431         due_date => $custom_date_due,
2432     })->store;
2433
2434     my $issue = AddIssue( $patron, $item->barcode );
2435     is( $issue->date_due, $expected_date_due, 'Custom illrequest date due has been set for this issue');
2436
2437     $patron = $builder->build_object( { class => 'Koha::Patrons' } );
2438     $item = $builder->build_sample_item();
2439     $custom_date_due = '9999-12-19';
2440     $expected_date_due = '9999-12-19 23:59:00';
2441     $illrequest = Koha::Illrequest->new({
2442         borrowernumber => $patron->borrowernumber,
2443         biblio_id => $item->biblionumber,
2444         branchcode => $library->{'branchcode'},
2445         due_date => $custom_date_due,
2446     })->store;
2447
2448     $issue = AddIssue( $patron, $item->barcode );
2449     is( $issue->date_due, $expected_date_due, 'Custom illrequest date due has been set for this issue');
2450 };
2451
2452 subtest 'CanBookBeIssued + Koha::Patron->is_debarred|has_overdues' => sub {
2453     plan tests => 8;
2454
2455     my $library = $builder->build( { source => 'Branch' } );
2456     my $patron  = $builder->build_object( { class => 'Koha::Patrons', value => { categorycode => $patron_category->{categorycode} } } );
2457     my $item_1 = $builder->build_sample_item(
2458         {
2459             library => $library->{branchcode},
2460         }
2461     );
2462     my $item_2 = $builder->build_sample_item(
2463         {
2464             library => $library->{branchcode},
2465         }
2466     );
2467     Koha::CirculationRules->set_rules(
2468         {
2469             categorycode => undef,
2470             itemtype     => undef,
2471             branchcode   => $library->{branchcode},
2472             rules        => {
2473                 reservesallowed => 25,
2474                 issuelength     => 14,
2475                 lengthunit      => 'days',
2476                 renewalsallowed => 1,
2477                 renewalperiod   => 7,
2478                 norenewalbefore => undef,
2479                 auto_renew      => 0,
2480                 fine            => .10,
2481                 chargeperiod    => 1,
2482                 maxissueqty     => 20
2483             }
2484         }
2485     );
2486
2487     my ( $error, $question, $alerts );
2488
2489     # Patron cannot issue item_1, they have overdues
2490     my $yesterday = DateTime->today( time_zone => C4::Context->tz() )->add( days => -1 );
2491     my $issue = AddIssue( $patron, $item_1->barcode, $yesterday );    # Add an overdue
2492
2493     t::lib::Mocks::mock_preference( 'OverduesBlockCirc', 'confirmation' );
2494     ( $error, $question, $alerts ) = CanBookBeIssued( $patron, $item_2->barcode );
2495     is( keys(%$error) + keys(%$alerts),  0, 'No key for error and alert' . str($error, $question, $alerts) );
2496     is( $question->{USERBLOCKEDOVERDUE}, 1, 'OverduesBlockCirc=confirmation, USERBLOCKEDOVERDUE should be set for question' );
2497
2498     t::lib::Mocks::mock_preference( 'OverduesBlockCirc', 'block' );
2499     ( $error, $question, $alerts ) = CanBookBeIssued( $patron, $item_2->barcode );
2500     is( keys(%$question) + keys(%$alerts),  0, 'No key for question and alert ' . str($error, $question, $alerts) );
2501     is( $error->{USERBLOCKEDOVERDUE},      1, 'OverduesBlockCirc=block, USERBLOCKEDOVERDUE should be set for error' );
2502
2503     # Patron cannot issue item_1, they are debarred
2504     my $tomorrow = DateTime->today( time_zone => C4::Context->tz() )->add( days => 1 );
2505     Koha::Patron::Debarments::AddDebarment( { borrowernumber => $patron->borrowernumber, expiration => $tomorrow } );
2506     ( $error, $question, $alerts ) = CanBookBeIssued( $patron, $item_2->barcode );
2507     is( keys(%$question) + keys(%$alerts),  0, 'No key for question and alert ' . str($error, $question, $alerts) );
2508     is( $error->{USERBLOCKEDWITHENDDATE}, output_pref( { dt => $tomorrow, dateformat => 'sql', dateonly => 1 } ), 'USERBLOCKEDWITHENDDATE should be tomorrow' );
2509
2510     Koha::Patron::Debarments::AddDebarment( { borrowernumber => $patron->borrowernumber } );
2511     ( $error, $question, $alerts ) = CanBookBeIssued( $patron, $item_2->barcode );
2512     is( keys(%$question) + keys(%$alerts),  0, 'No key for question and alert ' . str($error, $question, $alerts) );
2513     is( $error->{USERBLOCKEDNOENDDATE},    '9999-12-31', 'USERBLOCKEDNOENDDATE should be 9999-12-31 for unlimited debarments' );
2514 };
2515
2516 subtest 'CanBookBeIssued + Statistic patrons "X"' => sub {
2517     plan tests => 13;
2518
2519     my $library           = $builder->build_object( { class => 'Koha::Libraries' } );
2520     my $patron_category_x = $builder->build_object(
2521         {
2522             class => 'Koha::Patron::Categories',
2523             value => { category_type => 'X' }
2524         }
2525     );
2526     my $patron = $builder->build_object(
2527         {
2528             class => 'Koha::Patrons',
2529             value => {
2530                 categorycode  => $patron_category_x->categorycode,
2531                 gonenoaddress => undef,
2532                 lost          => undef,
2533                 debarred      => undef,
2534                 borrowernotes => ""
2535             }
2536         }
2537     );
2538     my $item_1 = $builder->build_sample_item(
2539         {
2540             library => $library->{branchcode},
2541         }
2542     );
2543
2544     my ( $error, $question, $alerts ) = CanBookBeIssued( $patron, $item_1->barcode );
2545     is(
2546         $error->{STATS}, 1,
2547         '"Error" flag "STATS" must be set if CanBookBeIssued is called with a statistic patron (category_type=X)'
2548     );
2549
2550     my $stat = Koha::Statistics->search( { itemnumber => $item_1->itemnumber } )->next;
2551     is( $stat->branch,         C4::Context->userenv->{'branch'}, 'Recorded a branch' );
2552     is( $stat->type,           'localuse',                       'Recorded type as localuse' );
2553     is( $stat->itemnumber,     $item_1->itemnumber,              'Recorded an itemnumber' );
2554     is( $stat->itemtype,       $item_1->effective_itemtype,      'Recorded an itemtype' );
2555     is( $stat->borrowernumber, $patron->borrowernumber,          'Recorded a borrower number' );
2556     is( $stat->ccode,          $item_1->ccode,                   'Recorded a collection code' );
2557     is( $stat->categorycode,   $patron->categorycode,            'Recorded a categorycode' );
2558     is( $stat->location,       $item_1->location,                'Recorded a location' );
2559
2560     t::lib::Mocks::mock_userenv( { branchcode => $library->branchcode } );
2561     my $patron_2 = $builder->build_object(
2562         { class => 'Koha::Patrons', value => { categorycode => $patron_category->{categorycode} } } );
2563     my $item_2 = $builder->build_sample_item( { library => $library->branchcode } );
2564     my $issue  = AddIssue( $patron_2, $item_2->barcode );
2565     $item_2->discard_changes;
2566     ok( $item_2->onloan, "Item is checked out" );
2567
2568     my $item_3 = $builder->build_sample_item( { library => $library->branchcode } );
2569     CanBookBeIssued( $patron, $item_3->barcode );
2570     $item_3->discard_changes;
2571     is(
2572         Koha::Statistics->search( { itemnumber => $item_3->itemnumber } )->count, 1,
2573         'Single entry recorded in the stats table'
2574     );
2575
2576     my $item_4 = $builder->build_sample_item( { library => $library->branchcode } );
2577     AddIssue( $patron_2, $item_4->barcode );
2578     $item_4->discard_changes;
2579     is(
2580         Koha::Statistics->search( { itemnumber => $item_4->itemnumber } )->count, 1,
2581         'Issue should be recorded in statistics table for item 4.'
2582     );
2583     CanBookBeIssued( $patron, $item_4->barcode );
2584     $item_4->discard_changes;
2585     is(
2586         Koha::Statistics->search( { itemnumber => $item_4->itemnumber } )->count, 2,
2587         'Issue, return, and localuse should be recorded in statistics table for item 4.'
2588     );
2589
2590     # TODO There are other tests to provide here
2591 };
2592
2593 subtest 'MultipleReserves' => sub {
2594     plan tests => 3;
2595
2596     my $biblio = $builder->build_sample_biblio();
2597
2598     my $branch = $library2->{branchcode};
2599
2600     my $item_1 = $builder->build_sample_item(
2601         {
2602             biblionumber     => $biblio->biblionumber,
2603             library          => $branch,
2604             replacementprice => 12.00,
2605             itype            => $itemtype,
2606         }
2607     );
2608
2609     my $item_2 = $builder->build_sample_item(
2610         {
2611             biblionumber     => $biblio->biblionumber,
2612             library          => $branch,
2613             replacementprice => 12.00,
2614             itype            => $itemtype,
2615         }
2616     );
2617
2618     my $bibitems       = '';
2619     my $priority       = '1';
2620     my $resdate        = undef;
2621     my $expdate        = undef;
2622     my $notes          = '';
2623     my $checkitem      = undef;
2624     my $found          = undef;
2625
2626     my %renewing_borrower_data = (
2627         firstname =>  'John',
2628         surname => 'Renewal',
2629         categorycode => $patron_category->{categorycode},
2630         branchcode => $branch,
2631     );
2632     my $patron = Koha::Patron->new(\%renewing_borrower_data)->store;
2633     my $issue = AddIssue( $patron, $item_1->barcode);
2634     my $datedue = dt_from_string( $issue->date_due() );
2635     is (defined $issue->date_due(), 1, "item 1 checked out");
2636     my $borrowing_borrowernumber = Koha::Checkouts->find({ itemnumber => $item_1->itemnumber })->borrowernumber;
2637
2638     my %reserving_borrower_data1 = (
2639         firstname =>  'Katrin',
2640         surname => 'Reservation',
2641         categorycode => $patron_category->{categorycode},
2642         branchcode => $branch,
2643     );
2644     my $reserving_borrowernumber1 = Koha::Patron->new(\%reserving_borrower_data1)->store->borrowernumber;
2645     AddReserve(
2646         {
2647             branchcode       => $branch,
2648             borrowernumber   => $reserving_borrowernumber1,
2649             biblionumber     => $biblio->biblionumber,
2650             priority         => $priority,
2651             reservation_date => $resdate,
2652             expiration_date  => $expdate,
2653             notes            => $notes,
2654             itemnumber       => $checkitem,
2655             found            => $found,
2656         }
2657     );
2658
2659     my %reserving_borrower_data2 = (
2660         firstname =>  'Kirk',
2661         surname => 'Reservation',
2662         categorycode => $patron_category->{categorycode},
2663         branchcode => $branch,
2664     );
2665     my $reserving_borrowernumber2 = Koha::Patron->new(\%reserving_borrower_data2)->store->borrowernumber;
2666     AddReserve(
2667         {
2668             branchcode       => $branch,
2669             borrowernumber   => $reserving_borrowernumber2,
2670             biblionumber     => $biblio->biblionumber,
2671             priority         => $priority,
2672             reservation_date => $resdate,
2673             expiration_date  => $expdate,
2674             notes            => $notes,
2675             itemnumber       => $checkitem,
2676             found            => $found,
2677         }
2678     );
2679
2680     {
2681         my ( $renewokay, $error ) = CanBookBeRenewed($patron, $issue, 1);
2682         is($renewokay, 0, 'Bug 17941 - should cover the case where 2 books are both reserved, so failing');
2683     }
2684
2685     my $item_3 = $builder->build_sample_item(
2686         {
2687             biblionumber     => $biblio->biblionumber,
2688             library          => $branch,
2689             replacementprice => 12.00,
2690             itype            => $itemtype,
2691         }
2692     );
2693
2694     {
2695         my ( $renewokay, $error ) = CanBookBeRenewed($patron, $issue, 1);
2696         is($renewokay, 1, 'Bug 17941 - should cover the case where 2 books are reserved, but a third one is available');
2697     }
2698 };
2699
2700 subtest 'CanBookBeIssued + AllowMultipleIssuesOnABiblio' => sub {
2701     plan tests => 5;
2702
2703     my $library = $builder->build( { source => 'Branch' } );
2704     my $patron  = $builder->build_object( { class => 'Koha::Patrons', value => { categorycode => $patron_category->{categorycode} } } );
2705
2706     my $biblionumber = $builder->build_sample_biblio(
2707         {
2708             branchcode => $library->{branchcode},
2709         }
2710     )->biblionumber;
2711     my $item_1 = $builder->build_sample_item(
2712         {
2713             biblionumber => $biblionumber,
2714             library      => $library->{branchcode},
2715         }
2716     );
2717
2718     my $item_2 = $builder->build_sample_item(
2719         {
2720             biblionumber => $biblionumber,
2721             library      => $library->{branchcode},
2722         }
2723     );
2724
2725     Koha::CirculationRules->set_rules(
2726         {
2727             categorycode => undef,
2728             itemtype     => undef,
2729             branchcode   => $library->{branchcode},
2730             rules        => {
2731                 reservesallowed => 25,
2732                 issuelength     => 14,
2733                 lengthunit      => 'days',
2734                 renewalsallowed => 1,
2735                 renewalperiod   => 7,
2736                 norenewalbefore => undef,
2737                 auto_renew      => 0,
2738                 fine            => .10,
2739                 chargeperiod    => 1,
2740                 maxissueqty     => 20
2741             }
2742         }
2743     );
2744
2745     my ( $error, $question, $alerts );
2746     my $issue = AddIssue( $patron, $item_1->barcode, dt_from_string->add( days => 1 ) );
2747
2748     t::lib::Mocks::mock_preference('AllowMultipleIssuesOnABiblio', 0);
2749     ( $error, $question, $alerts ) = CanBookBeIssued( $patron, $item_2->barcode );
2750     cmp_deeply(
2751         { error => $error, alerts => $alerts },
2752         { error => {}, alerts => {} },
2753         'No error or alert should be raised'
2754     );
2755     is( $question->{BIBLIO_ALREADY_ISSUED}, 1, 'BIBLIO_ALREADY_ISSUED question flag should be set if AllowMultipleIssuesOnABiblio=0 and issue already exists' );
2756
2757     t::lib::Mocks::mock_preference('AllowMultipleIssuesOnABiblio', 1);
2758     ( $error, $question, $alerts ) = CanBookBeIssued( $patron, $item_2->barcode );
2759     cmp_deeply(
2760         { error => $error, question => $question, alerts => $alerts },
2761         { error => {}, question => {}, alerts => {} },
2762         'No BIBLIO_ALREADY_ISSUED flag should be set if AllowMultipleIssuesOnABiblio=1'
2763     );
2764
2765     # Add a subscription
2766     Koha::Subscription->new({ biblionumber => $biblionumber })->store;
2767
2768     t::lib::Mocks::mock_preference('AllowMultipleIssuesOnABiblio', 0);
2769     ( $error, $question, $alerts ) = CanBookBeIssued( $patron, $item_2->barcode );
2770     cmp_deeply(
2771         { error => $error, question => $question, alerts => $alerts },
2772         { error => {}, question => {}, alerts => {} },
2773         'No BIBLIO_ALREADY_ISSUED flag should be set if it is a subscription'
2774     );
2775
2776     t::lib::Mocks::mock_preference('AllowMultipleIssuesOnABiblio', 1);
2777     ( $error, $question, $alerts ) = CanBookBeIssued( $patron, $item_2->barcode );
2778     cmp_deeply(
2779         { error => $error, question => $question, alerts => $alerts },
2780         { error => {}, question => {}, alerts => {} },
2781         'No BIBLIO_ALREADY_ISSUED flag should be set if it is a subscription'
2782     );
2783 };
2784
2785 subtest 'AddReturn + CumulativeRestrictionPeriods' => sub {
2786     plan tests => 8;
2787
2788     my $library = $builder->build( { source => 'Branch' } );
2789     my $patron  = $builder->build_object( { class => 'Koha::Patrons', value => { categorycode => $patron_category->{categorycode} } } );
2790
2791     # Add 2 items
2792     my $biblionumber = $builder->build_sample_biblio(
2793         {
2794             branchcode => $library->{branchcode},
2795         }
2796     )->biblionumber;
2797     my $item_1 = $builder->build_sample_item(
2798         {
2799             biblionumber => $biblionumber,
2800             library      => $library->{branchcode},
2801         }
2802     );
2803     my $item_2 = $builder->build_sample_item(
2804         {
2805             biblionumber => $biblionumber,
2806             library      => $library->{branchcode},
2807         }
2808     );
2809
2810     # And the circulation rule
2811     Koha::CirculationRules->search->delete;
2812     Koha::CirculationRules->set_rules(
2813         {
2814             categorycode => undef,
2815             itemtype     => undef,
2816             branchcode   => undef,
2817             rules        => {
2818                 issuelength => 1,
2819                 firstremind => 1,        # 1 day of grace
2820                 finedays    => 2,        # 2 days of fine per day of overdue
2821                 lengthunit  => 'days',
2822             }
2823         }
2824     );
2825
2826     # Patron cannot issue item_1, they have overdues
2827     my $now = dt_from_string;
2828     my $five_days_ago = $now->clone->subtract( days => 5 );
2829     my $ten_days_ago  = $now->clone->subtract( days => 10 );
2830     AddIssue( $patron, $item_1->barcode, $five_days_ago );    # Add an overdue
2831     AddIssue( $patron, $item_2->barcode, $ten_days_ago )
2832       ;    # Add another overdue
2833
2834     t::lib::Mocks::mock_preference( 'CumulativeRestrictionPeriods', '0' );
2835     AddReturn( $item_1->barcode, $library->{branchcode}, undef, $now );
2836     my $suspensions = $patron->restrictions->search( { type => 'SUSPENSION' } );
2837     is( $suspensions->count, 1, "Suspension added" );
2838     my $THE_suspension = $suspensions->next;
2839
2840     # FIXME Is it right? I'd have expected 5 * 2 - 1 instead
2841     # Same for the others
2842     my $expected_expiration = output_pref(
2843         {
2844             dt         => $now->clone->add( days => ( 5 - 1 ) * 2 ),
2845             dateformat => 'sql',
2846             dateonly   => 1
2847         }
2848     );
2849     is( $THE_suspension->expiration, $expected_expiration, "Suspesion expiration set" );
2850
2851     AddReturn( $item_2->barcode, $library->{branchcode}, undef, $now );
2852     $suspensions = $patron->restrictions->search( { type => 'SUSPENSION' } );
2853     is( $suspensions->count, 1, "Only one suspension" );
2854     $THE_suspension = $suspensions->next;
2855
2856     $expected_expiration = output_pref(
2857         {
2858             dt         => $now->clone->add( days => ( 10 - 1 ) * 2 ),
2859             dateformat => 'sql',
2860             dateonly   => 1
2861         }
2862     );
2863     is( $THE_suspension->expiration, $expected_expiration, "Suspension expiration date updated" );
2864
2865     Koha::Patron::Debarments::DelUniqueDebarment(
2866         { borrowernumber => $patron->borrowernumber, type => 'SUSPENSION' } );
2867
2868     t::lib::Mocks::mock_preference( 'CumulativeRestrictionPeriods', '1' );
2869     AddIssue( $patron, $item_1->barcode, $five_days_ago );    # Add an overdue
2870     AddIssue( $patron, $item_2->barcode, $ten_days_ago )
2871       ;    # Add another overdue
2872     AddReturn( $item_1->barcode, $library->{branchcode}, undef, $now );
2873     $suspensions = $patron->restrictions->search( { type => 'SUSPENSION' } );
2874     is( $suspensions->count, 1, "Only one suspension" );
2875     $THE_suspension = $suspensions->next;
2876
2877     $expected_expiration = output_pref(
2878         {
2879             dt         => $now->clone->add( days => ( 5 - 1 ) * 2 ),
2880             dateformat => 'sql',
2881             dateonly   => 1
2882         }
2883     );
2884     is( $THE_suspension->expiration, $expected_expiration, "Suspension expiration date updated" );
2885
2886     AddReturn( $item_2->barcode, $library->{branchcode}, undef, $now );
2887     $suspensions = $patron->restrictions->search( { type => 'SUSPENSION' } );
2888     is( $suspensions->count, 1, "Only one suspension" );
2889     $THE_suspension = $suspensions->next;
2890
2891     $expected_expiration = output_pref(
2892         {
2893             dt => $now->clone->add( days => ( 5 - 1 ) * 2 + ( 10 - 1 ) * 2 ),
2894             dateformat => 'sql',
2895             dateonly   => 1
2896         }
2897     );
2898     is( $THE_suspension->expiration, $expected_expiration, "Suspension expiration date updated" );
2899 };
2900
2901 subtest 'AddReturn + suspension_chargeperiod' => sub {
2902     plan tests => 29;
2903
2904     my $library = $builder->build( { source => 'Branch' } );
2905     my $patron  = $builder->build_object( { class => 'Koha::Patrons', value => { categorycode => $patron_category->{categorycode} } } );
2906
2907     my $biblionumber = $builder->build_sample_biblio(
2908         {
2909             branchcode => $library->{branchcode},
2910         }
2911     )->biblionumber;
2912     my $item_1 = $builder->build_sample_item(
2913         {
2914             biblionumber => $biblionumber,
2915             library      => $library->{branchcode},
2916         }
2917     );
2918
2919     # And the issuing rule
2920     Koha::CirculationRules->search->delete;
2921     Koha::CirculationRules->set_rules(
2922         {
2923             categorycode => '*',
2924             itemtype     => '*',
2925             branchcode   => '*',
2926             rules        => {
2927                 issuelength => 1,
2928                 firstremind => 0,    # 0 day of grace
2929                 finedays    => 2,    # 2 days of fine per day of overdue
2930                 suspension_chargeperiod => 1,
2931                 lengthunit              => 'days',
2932             }
2933         }
2934     );
2935
2936     my $now = dt_from_string;
2937     my $five_days_ago = $now->clone->subtract( days => 5 );
2938     # We want to charge 2 days every day, without grace
2939     # With 5 days of overdue: 5 * Z
2940     my $expected_expiration = $now->clone->add( days => ( 5 * 2 ) / 1 );
2941     test_debarment_on_checkout(
2942         {
2943             item            => $item_1,
2944             library         => $library,
2945             patron          => $patron,
2946             due_date        => $five_days_ago,
2947             expiration_date => $expected_expiration,
2948         }
2949     );
2950
2951     # Same with undef firstremind
2952     Koha::CirculationRules->search->delete;
2953     Koha::CirculationRules->set_rules(
2954         {
2955             categorycode => '*',
2956             itemtype     => '*',
2957             branchcode   => '*',
2958             rules        => {
2959                 issuelength => 1,
2960                 firstremind => undef,    # 0 day of grace
2961                 finedays    => 2,    # 2 days of fine per day of overdue
2962                 suspension_chargeperiod => 1,
2963                 lengthunit              => 'days',
2964             }
2965         }
2966     );
2967     {
2968     my $now = dt_from_string;
2969     my $five_days_ago = $now->clone->subtract( days => 5 );
2970     # We want to charge 2 days every day, without grace
2971     # With 5 days of overdue: 5 * Z
2972     my $expected_expiration = $now->clone->add( days => ( 5 * 2 ) / 1 );
2973     test_debarment_on_checkout(
2974         {
2975             item            => $item_1,
2976             library         => $library,
2977             patron          => $patron,
2978             due_date        => $five_days_ago,
2979             expiration_date => $expected_expiration,
2980         }
2981     );
2982     }
2983     # We want to charge 2 days every 2 days, without grace
2984     # With 5 days of overdue: (5 * 2) / 2
2985     Koha::CirculationRules->set_rule(
2986         {
2987             categorycode => undef,
2988             branchcode   => undef,
2989             itemtype     => undef,
2990             rule_name    => 'suspension_chargeperiod',
2991             rule_value   => '2',
2992         }
2993     );
2994
2995     $expected_expiration = $now->clone->add( days => floor( 5 * 2 ) / 2 );
2996     test_debarment_on_checkout(
2997         {
2998             item            => $item_1,
2999             library         => $library,
3000             patron          => $patron,
3001             due_date        => $five_days_ago,
3002             expiration_date => $expected_expiration,
3003         }
3004     );
3005
3006     # We want to charge 2 days every 3 days, with 1 day of grace
3007     # With 5 days of overdue: ((5-1) / 3 ) * 2
3008     Koha::CirculationRules->set_rules(
3009         {
3010             categorycode => undef,
3011             branchcode   => undef,
3012             itemtype     => undef,
3013             rules        => {
3014                 suspension_chargeperiod => 3,
3015                 firstremind             => 1,
3016             }
3017         }
3018     );
3019     $expected_expiration = $now->clone->add( days => floor( ( ( 5 - 1 ) / 3 ) * 2 ) );
3020     test_debarment_on_checkout(
3021         {
3022             item            => $item_1,
3023             library         => $library,
3024             patron          => $patron,
3025             due_date        => $five_days_ago,
3026             expiration_date => $expected_expiration,
3027         }
3028     );
3029
3030     # Use finesCalendar to know if holiday must be skipped to calculate the due date
3031     # We want to charge 2 days every days, with 0 day of grace (to not burn brains)
3032     Koha::CirculationRules->set_rules(
3033         {
3034             categorycode => undef,
3035             branchcode   => undef,
3036             itemtype     => undef,
3037             rules        => {
3038                 finedays                => 2,
3039                 suspension_chargeperiod => 1,
3040                 firstremind             => 0,
3041             }
3042         }
3043     );
3044     t::lib::Mocks::mock_preference('finesCalendar', 'noFinesWhenClosed');
3045     t::lib::Mocks::mock_preference('SuspensionsCalendar', 'noSuspensionsWhenClosed');
3046
3047     # Adding a holiday 2 days ago
3048     my $calendar = C4::Calendar->new(branchcode => $library->{branchcode});
3049     my $two_days_ago = $now->clone->subtract( days => 2 );
3050     $calendar->insert_single_holiday(
3051         day             => $two_days_ago->day,
3052         month           => $two_days_ago->month,
3053         year            => $two_days_ago->year,
3054         title           => 'holidayTest-2d',
3055         description     => 'holidayDesc 2 days ago'
3056     );
3057     # With 5 days of overdue, only 4 (x finedays=2) days must charged (one was an holiday)
3058     $expected_expiration = $now->clone->add( days => floor( ( ( 5 - 0 - 1 ) / 1 ) * 2 ) );
3059     test_debarment_on_checkout(
3060         {
3061             item            => $item_1,
3062             library         => $library,
3063             patron          => $patron,
3064             due_date        => $five_days_ago,
3065             expiration_date => $expected_expiration,
3066         }
3067     );
3068
3069     # Adding a holiday 2 days ahead, with finesCalendar=noFinesWhenClosed it should be skipped
3070     my $two_days_ahead = $now->clone->add( days => 2 );
3071     $calendar->insert_single_holiday(
3072         day             => $two_days_ahead->day,
3073         month           => $two_days_ahead->month,
3074         year            => $two_days_ahead->year,
3075         title           => 'holidayTest+2d',
3076         description     => 'holidayDesc 2 days ahead'
3077     );
3078
3079     # Same as above, but we should skip D+2
3080     $expected_expiration = $now->clone->add( days => floor( ( ( 5 - 0 - 1 ) / 1 ) * 2 ) + 1 );
3081     test_debarment_on_checkout(
3082         {
3083             item            => $item_1,
3084             library         => $library,
3085             patron          => $patron,
3086             due_date        => $five_days_ago,
3087             expiration_date => $expected_expiration,
3088         }
3089     );
3090
3091     # Adding another holiday, day of expiration date
3092     my $expected_expiration_dt = dt_from_string($expected_expiration);
3093     $calendar->insert_single_holiday(
3094         day             => $expected_expiration_dt->day,
3095         month           => $expected_expiration_dt->month,
3096         year            => $expected_expiration_dt->year,
3097         title           => 'holidayTest_exp',
3098         description     => 'holidayDesc on expiration date'
3099     );
3100     # Expiration date will be the day after
3101     test_debarment_on_checkout(
3102         {
3103             item            => $item_1,
3104             library         => $library,
3105             patron          => $patron,
3106             due_date        => $five_days_ago,
3107             expiration_date => $expected_expiration_dt->clone->add( days => 1 ),
3108         }
3109     );
3110
3111     test_debarment_on_checkout(
3112         {
3113             item            => $item_1,
3114             library         => $library,
3115             patron          => $patron,
3116             return_date     => $now->clone->add(days => 5),
3117             expiration_date => $now->clone->add(days => 5 + (5 * 2 - 1) ),
3118         }
3119     );
3120
3121     test_debarment_on_checkout(
3122         {
3123             item            => $item_1,
3124             library         => $library,
3125             patron          => $patron,
3126             due_date        => $now->clone->add(days => 1),
3127             return_date     => $now->clone->add(days => 5),
3128             expiration_date => $now->clone->add(days => 5 + (4 * 2 - 1) ),
3129         }
3130     );
3131
3132     Koha::CirculationRules->search->delete;
3133     Koha::CirculationRules->set_rules(
3134         {
3135             categorycode => undef,
3136             itemtype     => undef,
3137             branchcode   => undef,
3138             rules => {
3139                 finedays   => 0,
3140                 lengthunit => 'days',
3141               }
3142         }
3143     );
3144
3145     Koha::Patron::Debarments::AddDebarment(
3146         {
3147             borrowernumber => $patron->borrowernumber,
3148             expiration     => '9999-12-31',
3149             type           => 'MANUAL',
3150         }
3151     );
3152
3153     AddIssue( $patron, $item_1->barcode, $now->clone->subtract( days => 1 ) );
3154     my ( undef, $message ) = AddReturn( $item_1->barcode, $library->{branchcode}, undef, $now );
3155     is( $message->{WasReturned} && exists $message->{ForeverDebarred}, 1, 'Forever debarred message for Addreturn when overdue');
3156
3157     Koha::Patron::Debarments::DelUniqueDebarment(
3158         {
3159             borrowernumber => $patron->borrowernumber,
3160             type           => 'MANUAL',
3161         }
3162     );
3163     Koha::Patron::Debarments::AddDebarment(
3164         {
3165             borrowernumber => $patron->borrowernumber,
3166             expiration     => $now->clone->add( days => 10 ),
3167             type           => 'MANUAL',
3168         }
3169     );
3170
3171     AddIssue( $patron, $item_1->barcode, $now->clone->subtract( days => 1 ) );
3172     (undef, $message) = AddReturn( $item_1->barcode, $library->{branchcode}, undef, $now );
3173     is( $message->{WasReturned} && exists $message->{PrevDebarred}, 1, 'Previously debarred message for Addreturn when overdue');
3174 };
3175
3176 subtest 'CanBookBeIssued + AutoReturnCheckedOutItems' => sub {
3177     plan tests => 2;
3178
3179     my $library = $builder->build_object( { class => 'Koha::Libraries' } );
3180     my $patron1 = $builder->build_object(
3181         {
3182             class => 'Koha::Patrons',
3183             value => {
3184                 branchcode   => $library->branchcode,
3185                 categorycode => $patron_category->{categorycode}
3186             }
3187         }
3188     );
3189     my $patron2 = $builder->build_object(
3190         {
3191             class => 'Koha::Patrons',
3192             value => {
3193                 branchcode   => $library->branchcode,
3194                 categorycode => $patron_category->{categorycode}
3195             }
3196         }
3197     );
3198
3199     t::lib::Mocks::mock_userenv({ branchcode => $library->branchcode });
3200
3201     my $item = $builder->build_sample_item(
3202         {
3203             library      => $library->branchcode,
3204         }
3205     );
3206
3207     my ( $error, $question, $alerts );
3208     my $issue = AddIssue( $patron1, $item->barcode );
3209
3210     t::lib::Mocks::mock_preference('AutoReturnCheckedOutItems', 0);
3211     ( $error, $question, $alerts ) = CanBookBeIssued( $patron2, $item->barcode );
3212     is( $question->{ISSUED_TO_ANOTHER}, 1, 'ISSUED_TO_ANOTHER question flag should be set if AutoReturnCheckedOutItems is disabled and item is checked out to another' );
3213
3214     t::lib::Mocks::mock_preference('AutoReturnCheckedOutItems', 1);
3215     ( $error, $question, $alerts ) = CanBookBeIssued( $patron2, $item->barcode );
3216     is( $alerts->{RETURNED_FROM_ANOTHER}->{patron}->borrowernumber, $patron1->borrowernumber, 'RETURNED_FROM_ANOTHER alert flag should be set if AutoReturnCheckedOutItems is enabled and item is checked out to another' );
3217
3218     t::lib::Mocks::mock_preference('AutoReturnCheckedOutItems', 0);
3219 };
3220
3221
3222 subtest 'AddReturn | is_overdue' => sub {
3223     plan tests => 9;
3224
3225     t::lib::Mocks::mock_preference('MarkLostItemsAsReturned', 'batchmod|moredetail|cronjob|additem|pendingreserves|onpayment');
3226     t::lib::Mocks::mock_preference('CalculateFinesOnReturn', 1);
3227     t::lib::Mocks::mock_preference('finesMode', 'production');
3228     t::lib::Mocks::mock_preference('MaxFine', '100');
3229
3230     my $library = $builder->build( { source => 'Branch' } );
3231     my $patron  = $builder->build_object(
3232         {
3233             class => 'Koha::Patrons',
3234             value => { categorycode => $patron_category->{categorycode} }
3235         }
3236     );
3237     my $manager = $builder->build_object( { class => "Koha::Patrons" } );
3238     t::lib::Mocks::mock_userenv({ patron => $manager, branchcode => $manager->branchcode });
3239
3240     my $item = $builder->build_sample_item(
3241         {
3242             library      => $library->{branchcode},
3243             replacementprice => 7
3244         }
3245     );
3246
3247     Koha::CirculationRules->search->delete;
3248     Koha::CirculationRules->set_rules(
3249         {
3250             categorycode => undef,
3251             itemtype     => undef,
3252             branchcode   => undef,
3253             rules        => {
3254                 issuelength  => 6,
3255                 lengthunit   => 'days',
3256                 fine         => 1,        # Charge 1 every day of overdue
3257                 chargeperiod => 1,
3258             }
3259         }
3260     );
3261
3262     my $now   = dt_from_string;
3263     my $one_day_ago   = $now->clone->subtract( days => 1 );
3264     my $two_days_ago  = $now->clone->subtract( days => 2 );
3265     my $five_days_ago = $now->clone->subtract( days => 5 );
3266     my $ten_days_ago  = $now->clone->subtract( days => 10 );
3267
3268     # No return date specified, today will be used => 10 days overdue charged
3269     AddIssue( $patron, $item->barcode, $ten_days_ago ); # date due was 10d ago
3270     AddReturn( $item->barcode, $library->{branchcode} );
3271     is( int($patron->account->balance()), 10, 'Patron should have a charge of 10 (10 days x 1)' );
3272     Koha::Account::Lines->search({ borrowernumber => $patron->borrowernumber })->delete;
3273
3274     # specify return date 5 days before => no overdue charged
3275     AddIssue( $patron, $item->barcode, $five_days_ago ); # date due was 5d ago
3276     AddReturn( $item->barcode, $library->{branchcode}, undef, $ten_days_ago );
3277     is( int($patron->account->balance()), 0, 'AddReturn: pass return_date => no overdue' );
3278     Koha::Account::Lines->search({ borrowernumber => $patron->borrowernumber })->delete;
3279
3280     # specify return date 5 days later => 5 days overdue charged
3281     AddIssue( $patron, $item->barcode, $ten_days_ago ); # date due was 10d ago
3282     AddReturn( $item->barcode, $library->{branchcode}, undef, $five_days_ago );
3283     is( int($patron->account->balance()), 5, 'AddReturn: pass return_date => overdue' );
3284     Koha::Account::Lines->search({ borrowernumber => $patron->borrowernumber })->delete;
3285
3286     # specify return date 5 days later, specify exemptfine => no overdue charge
3287     AddIssue( $patron, $item->barcode, $ten_days_ago ); # date due was 10d ago
3288     AddReturn( $item->barcode, $library->{branchcode}, 1, $five_days_ago );
3289     is( int($patron->account->balance()), 0, 'AddReturn: pass return_date => no overdue' );
3290     Koha::Account::Lines->search({ borrowernumber => $patron->borrowernumber })->delete;
3291
3292     subtest 'bug 22877 | Lost item return' => sub {
3293
3294         plan tests => 3;
3295
3296         my $issue = AddIssue( $patron, $item->barcode, $ten_days_ago );    # date due was 10d ago
3297
3298         # Fake fines cronjob on this checkout
3299         my ($fine) =
3300           CalcFine( $item, $patron->categorycode, $library->{branchcode},
3301             $ten_days_ago, $now );
3302         UpdateFine(
3303             {
3304                 issue_id       => $issue->issue_id,
3305                 itemnumber     => $item->itemnumber,
3306                 borrowernumber => $patron->borrowernumber,
3307                 amount         => $fine,
3308                 due            => output_pref($ten_days_ago)
3309             }
3310         );
3311         is( int( $patron->account->balance() ),
3312             10, "Overdue fine of 10 days overdue" );
3313
3314         # Fake longoverdue with charge and not marking returned
3315         LostItem( $item->itemnumber, 'cronjob', 0 );
3316         is( int( $patron->account->balance() ),
3317             17, "Lost fine of 7 plus 10 days overdue" );
3318
3319         # Now we return it today
3320         AddReturn( $item->barcode, $library->{branchcode} );
3321         is( int( $patron->account->balance() ),
3322             17, "Should have a single 10 days overdue fine and lost charge" );
3323
3324         # Cleanup
3325         Koha::Account::Lines->search({ borrowernumber => $patron->borrowernumber })->delete;
3326     };
3327
3328     subtest 'bug 8338 | backdated return resulting in zero amount fine' => sub {
3329
3330         plan tests => 17;
3331
3332         t::lib::Mocks::mock_preference('CalculateFinesOnBackdate', 1);
3333
3334         my $issue = AddIssue( $patron, $item->barcode, $one_day_ago );    # date due was 1d ago
3335
3336         # Fake fines cronjob on this checkout
3337         my ($fine) =
3338           CalcFine( $item, $patron->categorycode, $library->{branchcode},
3339             $one_day_ago, $now );
3340         UpdateFine(
3341             {
3342                 issue_id       => $issue->issue_id,
3343                 itemnumber     => $item->itemnumber,
3344                 borrowernumber => $patron->borrowernumber,
3345                 amount         => $fine,
3346                 due            => output_pref($one_day_ago)
3347             }
3348         );
3349         is( int( $patron->account->balance() ),
3350             1, "Overdue fine of 1 day overdue" );
3351
3352         # Backdated return (dropbox mode example - charge should be removed)
3353         AddReturn( $item->barcode, $library->{branchcode}, 1, $one_day_ago );
3354         is( int( $patron->account->balance() ),
3355             0, "Overdue fine should be annulled" );
3356         my $lines = Koha::Account::Lines->search({ borrowernumber => $patron->borrowernumber });
3357         is( $lines->count, 0, "Overdue fine accountline has been removed");
3358
3359         $issue = AddIssue( $patron, $item->barcode, $two_days_ago );    # date due was 2d ago
3360
3361         # Fake fines cronjob on this checkout
3362         ($fine) =
3363           CalcFine( $item, $patron->categorycode, $library->{branchcode},
3364             $two_days_ago, $now );
3365         UpdateFine(
3366             {
3367                 issue_id       => $issue->issue_id,
3368                 itemnumber     => $item->itemnumber,
3369                 borrowernumber => $patron->borrowernumber,
3370                 amount         => $fine,
3371                 due            => output_pref($one_day_ago)
3372             }
3373         );
3374         is( int( $patron->account->balance() ),
3375             2, "Overdue fine of 2 days overdue" );
3376
3377         # Payment made against fine
3378         $lines = Koha::Account::Lines->search({ borrowernumber => $patron->borrowernumber });
3379         my $debit = $lines->next;
3380         my $credit = $patron->account->add_credit(
3381             {
3382                 amount    => 2,
3383                 type      => 'PAYMENT',
3384                 interface => 'test',
3385             }
3386         );
3387         $credit->apply( { debits => [$debit] } );
3388
3389         is( int( $patron->account->balance() ),
3390             0, "Overdue fine should be paid off" );
3391         $lines = Koha::Account::Lines->search({ borrowernumber => $patron->borrowernumber });
3392         is ( $lines->count, 2, "Overdue (debit) and Payment (credit) present");
3393         my $line = $lines->next;
3394         is( $line->amount+0, 2, "Overdue fine amount remains as 2 days");
3395         is( $line->amountoutstanding+0, 0, "Overdue fine amountoutstanding reduced to 0");
3396
3397         # Backdated return (dropbox mode example - charge should be removed)
3398         AddReturn( $item->barcode, $library->{branchcode}, undef, $one_day_ago );
3399         is( int( $patron->account->balance() ),
3400             -1, "Refund credit has been applied" );
3401         $lines = Koha::Account::Lines->search({ borrowernumber => $patron->borrowernumber }, { order_by => { '-asc' => 'accountlines_id' }});
3402         is( $lines->count, 3, "Overdue (debit), Payment (credit) and Refund (credit) are all present");
3403
3404         $line = $lines->next;
3405         is($line->amount+0,1, "Overdue fine amount has been reduced to 1");
3406         is($line->amountoutstanding+0,0, "Overdue fine amount outstanding remains at 0");
3407         is($line->status,'RETURNED', "Overdue fine is fixed");
3408         $line = $lines->next;
3409         is($line->amount+0,-2, "Original payment amount remains as 2");
3410         is($line->amountoutstanding+0,0, "Original payment remains applied");
3411         $line = $lines->next;
3412         is($line->amount+0,-1, "Refund amount correctly set to 1");
3413         is($line->amountoutstanding+0,-1, "Refund amount outstanding unspent");
3414
3415         # Cleanup
3416         Koha::Account::Lines->search({ borrowernumber => $patron->borrowernumber })->delete;
3417     };
3418
3419     subtest 'bug 25417 | backdated return + exemptfine' => sub {
3420
3421         plan tests => 2;
3422
3423         t::lib::Mocks::mock_preference('CalculateFinesOnBackdate', 1);
3424
3425         my $issue = AddIssue( $patron, $item->barcode, $one_day_ago );    # date due was 1d ago
3426
3427         # Fake fines cronjob on this checkout
3428         my ($fine) =
3429           CalcFine( $item, $patron->categorycode, $library->{branchcode},
3430             $one_day_ago, $now );
3431         UpdateFine(
3432             {
3433                 issue_id       => $issue->issue_id,
3434                 itemnumber     => $item->itemnumber,
3435                 borrowernumber => $patron->borrowernumber,
3436                 amount         => $fine,
3437                 due            => output_pref($one_day_ago)
3438             }
3439         );
3440         is( int( $patron->account->balance() ),
3441             1, "Overdue fine of 1 day overdue" );
3442
3443         # Backdated return (dropbox mode example - charge should no longer exist)
3444         AddReturn( $item->barcode, $library->{branchcode}, 1, $one_day_ago );
3445         is( int( $patron->account->balance() ),
3446             0, "Overdue fine should be annulled" );
3447
3448         # Cleanup
3449         Koha::Account::Lines->search({ borrowernumber => $patron->borrowernumber })->delete;
3450     };
3451
3452     subtest 'bug 24075 | backdated return with return datetime matching due datetime' => sub {
3453         plan tests => 7;
3454
3455         t::lib::Mocks::mock_preference( 'CalculateFinesOnBackdate', 1 );
3456
3457         my $due_date = dt_from_string;
3458         my $issue = AddIssue( $patron, $item->barcode, $due_date );
3459
3460         # Add fine
3461         UpdateFine(
3462             {
3463                 issue_id       => $issue->issue_id,
3464                 itemnumber     => $item->itemnumber,
3465                 borrowernumber => $patron->borrowernumber,
3466                 amount         => 0.25,
3467                 due            => output_pref($due_date)
3468             }
3469         );
3470         is( $patron->account->balance(),
3471             0.25, 'Overdue fine of $0.25 recorded' );
3472
3473         # Backdate return to exact due date and time
3474         my ( undef, $message ) =
3475           AddReturn( $item->barcode, $library->{branchcode},
3476             undef, $due_date );
3477
3478         my $accountline =
3479           Koha::Account::Lines->find( { issue_id => $issue->id } );
3480         ok( !$accountline, 'accountline removed as expected' );
3481
3482         # Re-issue
3483         $issue = AddIssue( $patron, $item->barcode, $due_date );
3484
3485         # Add fine
3486         UpdateFine(
3487             {
3488                 issue_id       => $issue->issue_id,
3489                 itemnumber     => $item->itemnumber,
3490                 borrowernumber => $patron->borrowernumber,
3491                 amount         => .25,
3492                 due            => output_pref($due_date)
3493             }
3494         );
3495         is( $patron->account->balance(),
3496             0.25, 'Overdue fine of $0.25 recorded' );
3497
3498         # Partial pay accruing fine
3499         my $lines = Koha::Account::Lines->search(
3500             {
3501                 borrowernumber => $patron->borrowernumber,
3502                 issue_id       => $issue->id
3503             }
3504         );
3505         my $debit  = $lines->next;
3506         my $credit = $patron->account->add_credit(
3507             {
3508                 amount    => .20,
3509                 type      => 'PAYMENT',
3510                 interface => 'test',
3511             }
3512         );
3513         $credit->apply( { debits => [$debit] } );
3514
3515         is( $patron->account->balance(), .05, 'Overdue fine reduced to $0.05' );
3516
3517         # Backdate return to exact due date and time
3518         ( undef, $message ) =
3519           AddReturn( $item->barcode, $library->{branchcode},
3520             undef, $due_date );
3521
3522         $lines = Koha::Account::Lines->search(
3523             {
3524                 borrowernumber => $patron->borrowernumber,
3525                 issue_id       => $issue->id
3526             }
3527         );
3528         $accountline = $lines->next;
3529         is( $accountline->amountoutstanding + 0,
3530             0, 'Partially paid fee amount outstanding was reduced to 0' );
3531         is( $accountline->amount + 0,
3532             0, 'Partially paid fee amount was reduced to 0' );
3533         is( $patron->account->balance(), -0.20, 'Patron refund recorded' );
3534
3535         # Cleanup
3536         Koha::Account::Lines->search(
3537             { borrowernumber => $patron->borrowernumber } )->delete;
3538     };
3539
3540     subtest 'enh 23091 | Lost item return policies' => sub {
3541         plan tests => 5;
3542
3543         my $manager = $builder->build_object({ class => "Koha::Patrons" });
3544
3545         my $branchcode_false =
3546           $builder->build( { source => 'Branch' } )->{branchcode};
3547         my $specific_rule_false = $builder->build(
3548             {
3549                 source => 'CirculationRule',
3550                 value  => {
3551                     branchcode   => $branchcode_false,
3552                     categorycode => undef,
3553                     itemtype     => undef,
3554                     rule_name    => 'lostreturn',
3555                     rule_value   => 0
3556                 }
3557             }
3558         );
3559         my $branchcode_refund =
3560           $builder->build( { source => 'Branch' } )->{branchcode};
3561         my $specific_rule_refund = $builder->build(
3562             {
3563                 source => 'CirculationRule',
3564                 value  => {
3565                     branchcode   => $branchcode_refund,
3566                     categorycode => undef,
3567                     itemtype     => undef,
3568                     rule_name    => 'lostreturn',
3569                     rule_value   => 'refund'
3570                 }
3571             }
3572         );
3573         my $branchcode_restore =
3574           $builder->build( { source => 'Branch' } )->{branchcode};
3575         my $specific_rule_restore = $builder->build(
3576             {
3577                 source => 'CirculationRule',
3578                 value  => {
3579                     branchcode   => $branchcode_restore,
3580                     categorycode => undef,
3581                     itemtype     => undef,
3582                     rule_name    => 'lostreturn',
3583                     rule_value   => 'restore'
3584                 }
3585             }
3586         );
3587         my $branchcode_charge =
3588           $builder->build( { source => 'Branch' } )->{branchcode};
3589         my $specific_rule_charge = $builder->build(
3590             {
3591                 source => 'CirculationRule',
3592                 value  => {
3593                     branchcode   => $branchcode_charge,
3594                     categorycode => undef,
3595                     itemtype     => undef,
3596                     rule_name    => 'lostreturn',
3597                     rule_value   => 'charge'
3598                 }
3599             }
3600         );
3601
3602         my $branchcode_refund_unpaid =
3603         $builder->build( { source => 'Branch' } )->{branchcode};
3604         my $specific_rule_refund_unpaid = $builder->build(
3605             {
3606                 source => 'CirculationRule',
3607                 value  => {
3608                     branchcode   => $branchcode_refund_unpaid,
3609                     categorycode => undef,
3610                     itemtype     => undef,
3611                     rule_name    => 'lostreturn',
3612                     rule_value   => 'refund_unpaid'
3613                 }
3614             }
3615         );
3616
3617         my $replacement_amount = 99.00;
3618         t::lib::Mocks::mock_preference( 'AllowReturnToBranch', 'anywhere' );
3619         t::lib::Mocks::mock_preference( 'WhenLostChargeReplacementFee', 1 );
3620         t::lib::Mocks::mock_preference( 'WhenLostForgiveFine',          0 );
3621         t::lib::Mocks::mock_preference( 'BlockReturnOfLostItems',       0 );
3622         t::lib::Mocks::mock_preference( 'RefundLostOnReturnControl',
3623             'CheckinLibrary' );
3624         t::lib::Mocks::mock_preference( 'NoRefundOnLostReturnedItemsAge',
3625             undef );
3626
3627         subtest 'lostreturn | refund_unpaid' => sub {
3628             plan tests => 21;
3629
3630             t::lib::Mocks::mock_userenv({ patron => $manager, branchcode => $branchcode_refund_unpaid });
3631
3632             my $item = $builder->build_sample_item(
3633                 {
3634                     replacementprice => $replacement_amount
3635                 }
3636             );
3637
3638             # Issue the item
3639             my $issue = C4::Circulation::AddIssue( $patron, $item->barcode );
3640
3641             # Mark item as lost
3642             $item->itemlost(3)->store;
3643             C4::Circulation::LostItem( $item->itemnumber, 1 );
3644
3645             my $lost_fee_lines = Koha::Account::Lines->search(
3646                 {
3647                     borrowernumber  => $patron->id,
3648                     itemnumber      => $item->itemnumber,
3649                     debit_type_code => 'LOST'
3650                 }
3651             );
3652             is( $lost_fee_lines->count, 1, 'Lost item fee produced' );
3653             my $lost_fee_line = $lost_fee_lines->next;
3654             is( int($lost_fee_line->amount),
3655                 $replacement_amount, 'The right LOST amount is generated' );
3656             is( int($lost_fee_line->amountoutstanding),
3657                 $replacement_amount,
3658                 'The right LOST amountoutstanding is generated' );
3659             is( $lost_fee_line->status, undef, 'The LOST status was not set' );
3660
3661             is(
3662                 int($patron->account->balance),
3663                 $replacement_amount ,
3664                 "Account balance equals the replacement amount after being charged lost fee when no payments has been made"
3665             );
3666
3667             # Return lost item without any payments having been made
3668             my ( $returned, $message ) = AddReturn( $item->barcode, $branchcode_refund_unpaid );
3669
3670             $lost_fee_line->discard_changes;
3671
3672             is( int($lost_fee_line->amount), $replacement_amount, 'The LOST amount is left intact' );
3673             is( int($lost_fee_line->amountoutstanding) , 0, 'The LOST amountoutstanding is zero' );
3674             is( $lost_fee_line->status, 'FOUND', 'The FOUND status was set' );
3675             is(
3676                 int($patron->account->balance),
3677                 0,
3678                 'Account balance should be zero after returning item with lost fee when no payments has been made'
3679             );
3680
3681             # Create a second item
3682             $item = $builder->build_sample_item(
3683                 {
3684                     replacementprice => $replacement_amount
3685                 }
3686             );
3687
3688             # Issue the item
3689             $issue = C4::Circulation::AddIssue( $patron, $item->barcode );
3690
3691             # Mark item as lost
3692             $item->itemlost(3)->store;
3693             C4::Circulation::LostItem( $item->itemnumber, 1 );
3694
3695             $lost_fee_lines = Koha::Account::Lines->search(
3696                 {
3697                     borrowernumber  => $patron->id,
3698                     itemnumber      => $item->itemnumber,
3699                     debit_type_code => 'LOST'
3700                 }
3701             );
3702             is( $lost_fee_lines->count, 1, 'Lost item fee produced' );
3703             $lost_fee_line = $lost_fee_lines->next;
3704
3705             # Make partial payment
3706             $patron->account->payin_amount({
3707                 type => 'PAYMENT',
3708                 interface => 'intranet',
3709                 payment_type => 'CASH',
3710                 user_id => $patron->borrowernumber,
3711                 amount => 39.00,
3712                 debits => [$lost_fee_line]
3713             });
3714
3715             $lost_fee_line->discard_changes;
3716
3717             is( int($lost_fee_line->amountoutstanding),
3718                 60,
3719                 'The LOST amountoutstanding is the expected amount after partial payment of lost fee'
3720             );
3721
3722             is(
3723                 int($patron->account->balance),
3724                 60,
3725                 'Account balance is the expected amount after partial payment of lost fee'
3726             );
3727
3728              # Return lost item with partial payment having been made
3729             ( $returned, $message ) = AddReturn( $item->barcode, $branchcode_refund_unpaid );
3730
3731             $lost_fee_line->discard_changes;
3732
3733             is( int($lost_fee_line->amountoutstanding) , 0, 'The LOST amountoutstanding is zero after returning lost item with partial payment' );
3734             is( $lost_fee_line->status, 'FOUND', 'The FOUND status was set for lost item with partial payment' );
3735             is(
3736                 int($patron->account->balance),
3737                 0,
3738                 'Account balance should be zero after returning item with lost fee when partial payment has been made'
3739             );
3740
3741             # Create a third item
3742             $item = $builder->build_sample_item(
3743                 {
3744                     replacementprice => $replacement_amount
3745                 }
3746             );
3747
3748             # Issue the item
3749             $issue = C4::Circulation::AddIssue( $patron, $item->barcode );
3750
3751             # Mark item as lost
3752             $item->itemlost(3)->store;
3753             C4::Circulation::LostItem( $item->itemnumber, 1 );
3754
3755             $lost_fee_lines = Koha::Account::Lines->search(
3756                 {
3757                     borrowernumber  => $patron->id,
3758                     itemnumber      => $item->itemnumber,
3759                     debit_type_code => 'LOST'
3760                 }
3761             );
3762             is( $lost_fee_lines->count, 1, 'Lost item fee produced' );
3763             $lost_fee_line = $lost_fee_lines->next;
3764
3765             # Make full payment
3766             $patron->account->payin_amount({
3767                 type => 'PAYMENT',
3768                 interface => 'intranet',
3769                 payment_type => 'CASH',
3770                 user_id => $patron->borrowernumber,
3771                 amount => $replacement_amount,
3772                 debits => [$lost_fee_line]
3773             });
3774
3775             $lost_fee_line->discard_changes;
3776
3777             is( int($lost_fee_line->amountoutstanding),
3778                 0,
3779                 'The LOST amountoutstanding is the expected amount after partial payment of lost fee'
3780             );
3781
3782             is(
3783                 int($patron->account->balance),
3784                 0,
3785                 'Account balance is the expected amount after partial payment of lost fee'
3786             );
3787
3788              # Return lost item with partial payment having been made
3789             ( $returned, $message ) = AddReturn( $item->barcode, $branchcode_refund_unpaid );
3790
3791             $lost_fee_line->discard_changes;
3792
3793             is( int($lost_fee_line->amountoutstanding) , 0, 'The LOST amountoutstanding is zero after returning lost item with full payment' );
3794             is( $lost_fee_line->status, 'FOUND', 'The FOUND status was set for lost item with partial payment' );
3795             is(
3796                 int($patron->account->balance),
3797                 0,
3798                 'Account balance should be zero after returning item with lost fee when full payment has been made'
3799             );
3800         };
3801
3802         subtest 'lostreturn | false' => sub {
3803             plan tests => 12;
3804
3805             t::lib::Mocks::mock_userenv({ patron => $manager, branchcode => $branchcode_false });
3806
3807             my $item = $builder->build_sample_item(
3808                 {
3809                     replacementprice => $replacement_amount
3810                 }
3811             );
3812
3813             # Issue the item
3814             my $issue = C4::Circulation::AddIssue( $patron, $item->barcode, $ten_days_ago );
3815
3816             # Fake fines cronjob on this checkout
3817             my ($fine) =
3818               CalcFine( $item, $patron->categorycode, $library->{branchcode},
3819                 $ten_days_ago, $now );
3820             UpdateFine(
3821                 {
3822                     issue_id       => $issue->issue_id,
3823                     itemnumber     => $item->itemnumber,
3824                     borrowernumber => $patron->borrowernumber,
3825                     amount         => $fine,
3826                     due            => output_pref($ten_days_ago)
3827                 }
3828             );
3829             my $overdue_fees = Koha::Account::Lines->search(
3830                 {
3831                     borrowernumber  => $patron->id,
3832                     itemnumber      => $item->itemnumber,
3833                     debit_type_code => 'OVERDUE'
3834                 }
3835             );
3836             is( $overdue_fees->count, 1, 'Overdue item fee produced' );
3837             my $overdue_fee = $overdue_fees->next;
3838             is( $overdue_fee->amount + 0,
3839                 10, 'The right OVERDUE amount is generated' );
3840             is( $overdue_fee->amountoutstanding + 0,
3841                 10,
3842                 'The right OVERDUE amountoutstanding is generated' );
3843
3844             # Simulate item marked as lost
3845             $item->itemlost(3)->store;
3846             C4::Circulation::LostItem( $item->itemnumber, 1 );
3847
3848             my $lost_fee_lines = Koha::Account::Lines->search(
3849                 {
3850                     borrowernumber  => $patron->id,
3851                     itemnumber      => $item->itemnumber,
3852                     debit_type_code => 'LOST'
3853                 }
3854             );
3855             is( $lost_fee_lines->count, 1, 'Lost item fee produced' );
3856             my $lost_fee_line = $lost_fee_lines->next;
3857             is( $lost_fee_line->amount + 0,
3858                 $replacement_amount, 'The right LOST amount is generated' );
3859             is( $lost_fee_line->amountoutstanding + 0,
3860                 $replacement_amount,
3861                 'The right LOST amountoutstanding is generated' );
3862             is( $lost_fee_line->status, undef, 'The LOST status was not set' );
3863
3864             # Return lost item
3865             my ( $returned, $message ) =
3866               AddReturn( $item->barcode, $branchcode_false, undef, $five_days_ago );
3867
3868             $overdue_fee->discard_changes;
3869             is( $overdue_fee->amount + 0,
3870                 10, 'The OVERDUE amount is left intact' );
3871             is( $overdue_fee->amountoutstanding + 0,
3872                 10,
3873                 'The OVERDUE amountoutstanding is left intact' );
3874
3875             $lost_fee_line->discard_changes;
3876             is( $lost_fee_line->amount + 0,
3877                 $replacement_amount, 'The LOST amount is left intact' );
3878             is( $lost_fee_line->amountoutstanding + 0,
3879                 $replacement_amount,
3880                 'The LOST amountoutstanding is left intact' );
3881             # FIXME: Should we set the LOST fee status to 'FOUND' regardless of whether we're refunding or not?
3882             is( $lost_fee_line->status, undef, 'The LOST status was not set' );
3883         };
3884
3885         subtest 'lostreturn | refund' => sub {
3886             plan tests => 12;
3887
3888             t::lib::Mocks::mock_userenv({ patron => $manager, branchcode => $branchcode_refund });
3889
3890             my $item = $builder->build_sample_item(
3891                 {
3892                     replacementprice => $replacement_amount
3893                 }
3894             );
3895
3896             # Issue the item
3897             my $issue = C4::Circulation::AddIssue( $patron, $item->barcode, $ten_days_ago );
3898
3899             # Fake fines cronjob on this checkout
3900             my ($fine) =
3901               CalcFine( $item, $patron->categorycode, $library->{branchcode},
3902                 $ten_days_ago, $now );
3903             UpdateFine(
3904                 {
3905                     issue_id       => $issue->issue_id,
3906                     itemnumber     => $item->itemnumber,
3907                     borrowernumber => $patron->borrowernumber,
3908                     amount         => $fine,
3909                     due            => output_pref($ten_days_ago)
3910                 }
3911             );
3912             my $overdue_fees = Koha::Account::Lines->search(
3913                 {
3914                     borrowernumber  => $patron->id,
3915                     itemnumber      => $item->itemnumber,
3916                     debit_type_code => 'OVERDUE'
3917                 }
3918             );
3919             is( $overdue_fees->count, 1, 'Overdue item fee produced' );
3920             my $overdue_fee = $overdue_fees->next;
3921             is( $overdue_fee->amount + 0,
3922                 10, 'The right OVERDUE amount is generated' );
3923             is( $overdue_fee->amountoutstanding + 0,
3924                 10,
3925                 'The right OVERDUE amountoutstanding is generated' );
3926
3927             # Simulate item marked as lost
3928             $item->itemlost(3)->store;
3929             C4::Circulation::LostItem( $item->itemnumber, 1 );
3930
3931             my $lost_fee_lines = Koha::Account::Lines->search(
3932                 {
3933                     borrowernumber  => $patron->id,
3934                     itemnumber      => $item->itemnumber,
3935                     debit_type_code => 'LOST'
3936                 }
3937             );
3938             is( $lost_fee_lines->count, 1, 'Lost item fee produced' );
3939             my $lost_fee_line = $lost_fee_lines->next;
3940             is( $lost_fee_line->amount + 0,
3941                 $replacement_amount, 'The right LOST amount is generated' );
3942             is( $lost_fee_line->amountoutstanding + 0,
3943                 $replacement_amount,
3944                 'The right LOST amountoutstanding is generated' );
3945             is( $lost_fee_line->status, undef, 'The LOST status was not set' );
3946
3947             # Return the lost item
3948             my ( undef, $message ) =
3949               AddReturn( $item->barcode, $branchcode_refund, undef, $five_days_ago );
3950
3951             $overdue_fee->discard_changes;
3952             is( $overdue_fee->amount + 0,
3953                 10, 'The OVERDUE amount is left intact' );
3954             is( $overdue_fee->amountoutstanding + 0,
3955                 10,
3956                 'The OVERDUE amountoutstanding is left intact' );
3957
3958             $lost_fee_line->discard_changes;
3959             is( $lost_fee_line->amount + 0,
3960                 $replacement_amount, 'The LOST amount is left intact' );
3961             is( $lost_fee_line->amountoutstanding + 0,
3962                 0,
3963                 'The LOST amountoutstanding is refunded' );
3964             is( $lost_fee_line->status, 'FOUND', 'The LOST status was set to FOUND' );
3965         };
3966
3967         subtest 'lostreturn | restore' => sub {
3968             plan tests => 13;
3969
3970             t::lib::Mocks::mock_userenv({ patron => $manager, branchcode => $branchcode_restore });
3971
3972             my $item = $builder->build_sample_item(
3973                 {
3974                     replacementprice => $replacement_amount
3975                 }
3976             );
3977
3978             # Issue the item
3979             my $issue = C4::Circulation::AddIssue( $patron, $item->barcode , $ten_days_ago);
3980
3981             # Fake fines cronjob on this checkout
3982             my ($fine) =
3983               CalcFine( $item, $patron->categorycode, $library->{branchcode},
3984                 $ten_days_ago, $now );
3985             UpdateFine(
3986                 {
3987                     issue_id       => $issue->issue_id,
3988                     itemnumber     => $item->itemnumber,
3989                     borrowernumber => $patron->borrowernumber,
3990                     amount         => $fine,
3991                     due            => output_pref($ten_days_ago)
3992                 }
3993             );
3994             my $overdue_fees = Koha::Account::Lines->search(
3995                 {
3996                     borrowernumber  => $patron->id,
3997                     itemnumber      => $item->itemnumber,
3998                     debit_type_code => 'OVERDUE'
3999                 }
4000             );
4001             is( $overdue_fees->count, 1, 'Overdue item fee produced' );
4002             my $overdue_fee = $overdue_fees->next;
4003             is( $overdue_fee->amount + 0,
4004                 10, 'The right OVERDUE amount is generated' );
4005             is( $overdue_fee->amountoutstanding + 0,
4006                 10,
4007                 'The right OVERDUE amountoutstanding is generated' );
4008
4009             # Simulate item marked as lost
4010             $item->itemlost(3)->store;
4011             C4::Circulation::LostItem( $item->itemnumber, 1 );
4012
4013             my $lost_fee_lines = Koha::Account::Lines->search(
4014                 {
4015                     borrowernumber  => $patron->id,
4016                     itemnumber      => $item->itemnumber,
4017                     debit_type_code => 'LOST'
4018                 }
4019             );
4020             is( $lost_fee_lines->count, 1, 'Lost item fee produced' );
4021             my $lost_fee_line = $lost_fee_lines->next;
4022             is( $lost_fee_line->amount + 0,
4023                 $replacement_amount, 'The right LOST amount is generated' );
4024             is( $lost_fee_line->amountoutstanding + 0,
4025                 $replacement_amount,
4026                 'The right LOST amountoutstanding is generated' );
4027             is( $lost_fee_line->status, undef, 'The LOST status was not set' );
4028
4029             # Simulate refunding overdue fees upon marking item as lost
4030             my $overdue_forgive = $patron->account->add_credit(
4031                 {
4032                     amount     => 10.00,
4033                     user_id    => $manager->borrowernumber,
4034                     library_id => $branchcode_restore,
4035                     interface  => 'test',
4036                     type       => 'FORGIVEN',
4037                     item_id    => $item->itemnumber
4038                 }
4039             );
4040             $overdue_forgive->apply( { debits => [$overdue_fee] } );
4041             $overdue_fee->discard_changes;
4042             is($overdue_fee->amountoutstanding + 0, 0, 'Overdue fee forgiven');
4043
4044             # Do nothing
4045             my ( undef, $message ) =
4046               AddReturn( $item->barcode, $branchcode_restore, undef, $five_days_ago );
4047
4048             $overdue_fee->discard_changes;
4049             is( $overdue_fee->amount + 0,
4050                 10, 'The OVERDUE amount is left intact' );
4051             is( $overdue_fee->amountoutstanding + 0,
4052                 10,
4053                 'The OVERDUE amountoutstanding is restored' );
4054
4055             $lost_fee_line->discard_changes;
4056             is( $lost_fee_line->amount + 0,
4057                 $replacement_amount, 'The LOST amount is left intact' );
4058             is( $lost_fee_line->amountoutstanding + 0,
4059                 0,
4060                 'The LOST amountoutstanding is refunded' );
4061             is( $lost_fee_line->status, 'FOUND', 'The LOST status was set to FOUND' );
4062         };
4063
4064         subtest 'lostreturn | charge' => sub {
4065             plan tests => 16;
4066
4067             t::lib::Mocks::mock_userenv({ patron => $manager, branchcode => $branchcode_charge });
4068
4069             my $item = $builder->build_sample_item(
4070                 {
4071                     replacementprice => $replacement_amount
4072                 }
4073             );
4074
4075             # Issue the item
4076             my $issue = C4::Circulation::AddIssue( $patron, $item->barcode, $ten_days_ago );
4077
4078             # Fake fines cronjob on this checkout
4079             my ($fine) =
4080               CalcFine( $item, $patron->categorycode, $library->{branchcode},
4081                 $ten_days_ago, $now );
4082             UpdateFine(
4083                 {
4084                     issue_id       => $issue->issue_id,
4085                     itemnumber     => $item->itemnumber,
4086                     borrowernumber => $patron->borrowernumber,
4087                     amount         => $fine,
4088                     due            => output_pref($ten_days_ago)
4089                 }
4090             );
4091             my $overdue_fees = Koha::Account::Lines->search(
4092                 {
4093                     borrowernumber  => $patron->id,
4094                     itemnumber      => $item->itemnumber,
4095                     debit_type_code => 'OVERDUE'
4096                 }
4097             );
4098             is( $overdue_fees->count, 1, 'Overdue item fee produced' );
4099             my $overdue_fee = $overdue_fees->next;
4100             is( $overdue_fee->amount + 0,
4101                 10, 'The right OVERDUE amount is generated' );
4102             is( $overdue_fee->amountoutstanding + 0,
4103                 10,
4104                 'The right OVERDUE amountoutstanding is generated' );
4105
4106             # Simulate item marked as lost
4107             $item->itemlost(3)->store;
4108             C4::Circulation::LostItem( $item->itemnumber, 1 );
4109
4110             my $lost_fee_lines = Koha::Account::Lines->search(
4111                 {
4112                     borrowernumber  => $patron->id,
4113                     itemnumber      => $item->itemnumber,
4114                     debit_type_code => 'LOST'
4115                 }
4116             );
4117             is( $lost_fee_lines->count, 1, 'Lost item fee produced' );
4118             my $lost_fee_line = $lost_fee_lines->next;
4119             is( $lost_fee_line->amount + 0,
4120                 $replacement_amount, 'The right LOST amount is generated' );
4121             is( $lost_fee_line->amountoutstanding + 0,
4122                 $replacement_amount,
4123                 'The right LOST amountoutstanding is generated' );
4124             is( $lost_fee_line->status, undef, 'The LOST status was not set' );
4125
4126             # Simulate refunding overdue fees upon marking item as lost
4127             my $overdue_forgive = $patron->account->add_credit(
4128                 {
4129                     amount     => 10.00,
4130                     user_id    => $manager->borrowernumber,
4131                     library_id => $branchcode_charge,
4132                     interface  => 'test',
4133                     type       => 'FORGIVEN',
4134                     item_id    => $item->itemnumber
4135                 }
4136             );
4137             $overdue_forgive->apply( { debits => [$overdue_fee] } );
4138             $overdue_fee->discard_changes;
4139             is($overdue_fee->amountoutstanding + 0, 0, 'Overdue fee forgiven');
4140
4141             # Do nothing
4142             my ( undef, $message ) =
4143               AddReturn( $item->barcode, $branchcode_charge, undef, $five_days_ago );
4144
4145             $lost_fee_line->discard_changes;
4146             is( $lost_fee_line->amount + 0,
4147                 $replacement_amount, 'The LOST amount is left intact' );
4148             is( $lost_fee_line->amountoutstanding + 0,
4149                 0,
4150                 'The LOST amountoutstanding is refunded' );
4151             is( $lost_fee_line->status, 'FOUND', 'The LOST status was set to FOUND' );
4152
4153             $overdue_fees = Koha::Account::Lines->search(
4154                 {
4155                     borrowernumber  => $patron->id,
4156                     itemnumber      => $item->itemnumber,
4157                     debit_type_code => 'OVERDUE'
4158                 },
4159                 {
4160                     order_by => { '-asc' => 'accountlines_id'}
4161                 }
4162             );
4163             is( $overdue_fees->count, 2, 'A second OVERDUE fee has been added' );
4164             $overdue_fee = $overdue_fees->next;
4165             is( $overdue_fee->amount + 0,
4166                 10, 'The original OVERDUE amount is left intact' );
4167             is( $overdue_fee->amountoutstanding + 0,
4168                 0,
4169                 'The original OVERDUE amountoutstanding is left as forgiven' );
4170             $overdue_fee = $overdue_fees->next;
4171             is( $overdue_fee->amount + 0,
4172                 5, 'The new OVERDUE amount is correct for the backdated return' );
4173             is( $overdue_fee->amountoutstanding + 0,
4174                 5,
4175                 'The new OVERDUE amountoutstanding is correct for the backdated return' );
4176         };
4177     };
4178 };
4179
4180 subtest '_FixOverduesOnReturn' => sub {
4181     plan tests => 14;
4182
4183     my $manager = $builder->build_object({ class => "Koha::Patrons" });
4184     t::lib::Mocks::mock_userenv({ patron => $manager, branchcode => $manager->branchcode });
4185
4186     my $biblio = $builder->build_sample_biblio({ author => 'Hall, Kylie' });
4187
4188     my $branchcode  = $library2->{branchcode};
4189
4190     my $item = $builder->build_sample_item(
4191         {
4192             biblionumber     => $biblio->biblionumber,
4193             library          => $branchcode,
4194             replacementprice => 99.00,
4195             itype            => $itemtype,
4196         }
4197     );
4198
4199     my $patron = $builder->build( { source => 'Borrower' } );
4200
4201     ## Start with basic call, should just close out the open fine
4202     my $accountline = Koha::Account::Line->new(
4203         {
4204             borrowernumber => $patron->{borrowernumber},
4205             debit_type_code    => 'OVERDUE',
4206             status         => 'UNRETURNED',
4207             itemnumber     => $item->itemnumber,
4208             amount => 99.00,
4209             amountoutstanding => 99.00,
4210             interface => 'test',
4211         }
4212     )->store();
4213
4214     C4::Circulation::_FixOverduesOnReturn( $patron->{borrowernumber}, $item->itemnumber, undef, 'RETURNED' );
4215
4216     $accountline->_result()->discard_changes();
4217
4218     is( $accountline->amountoutstanding+0, 99, 'Fine has the same amount outstanding as previously' );
4219     isnt( $accountline->status, 'UNRETURNED', 'Open fine ( account type OVERDUE ) has been closed out ( status not UNRETURNED )');
4220     is( $accountline->status, 'RETURNED', 'Passed status has been used to set as RETURNED )');
4221
4222     ## Run again, with exemptfine enabled
4223     $accountline->set(
4224         {
4225             debit_type_code    => 'OVERDUE',
4226             status         => 'UNRETURNED',
4227             amountoutstanding => 99.00,
4228         }
4229     )->store();
4230
4231     C4::Circulation::_FixOverduesOnReturn( $patron->{borrowernumber}, $item->itemnumber, 1, 'RETURNED' );
4232
4233     $accountline->_result()->discard_changes();
4234     my $offset = Koha::Account::Offsets->search({ debit_id => $accountline->id, type => 'APPLY' })->next();
4235
4236     is( $accountline->amountoutstanding + 0, 0, 'Fine amountoutstanding has been reduced to 0' );
4237     isnt( $accountline->status, 'UNRETURNED', 'Open fine ( account type OVERDUE ) has been closed out ( status not UNRETURNED )');
4238     is( $accountline->status, 'RETURNED', 'Open fine ( account type OVERDUE ) has been set to returned ( status RETURNED )');
4239     is( ref $offset, "Koha::Account::Offset", "Found matching offset for fine reduction via forgiveness" );
4240     is( $offset->amount + 0, -99, "Amount of offset is correct" );
4241     my $credit = $offset->credit;
4242     is( ref $credit, "Koha::Account::Line", "Found matching credit for fine forgiveness" );
4243     is( $credit->amount + 0, -99, "Credit amount is set correctly" );
4244     is( $credit->amountoutstanding + 0, 0, "Credit amountoutstanding is correctly set to 0" );
4245
4246     # Bug 25417 - Only forgive fines where there is an amount outstanding to forgive
4247     $accountline->set(
4248         {
4249             debit_type_code    => 'OVERDUE',
4250             status         => 'UNRETURNED',
4251             amountoutstanding => 0.00,
4252         }
4253     )->store();
4254     $offset->delete;
4255
4256     C4::Circulation::_FixOverduesOnReturn( $patron->{borrowernumber}, $item->itemnumber, 1, 'RETURNED' );
4257
4258     $accountline->_result()->discard_changes();
4259     $offset = Koha::Account::Offsets->search({ debit_id => $accountline->id, type => 'CREATE' })->next();
4260     is( $offset, undef, "No offset created when trying to forgive fine with no outstanding balance" );
4261     isnt( $accountline->status, 'UNRETURNED', 'Open fine ( account type OVERDUE ) has been closed out ( status not UNRETURNED )');
4262     is( $accountline->status, 'RETURNED', 'Passed status has been used to set as RETURNED )');
4263 };
4264
4265 subtest 'Set waiting flag' => sub {
4266     plan tests => 11;
4267
4268     my $library_1 = $builder->build( { source => 'Branch' } );
4269     my $patron_1  = $builder->build( { source => 'Borrower', value => { branchcode => $library_1->{branchcode}, categorycode => $patron_category->{categorycode} } } );
4270     my $library_2 = $builder->build( { source => 'Branch' } );
4271     my $patron_2  = $builder->build( { source => 'Borrower', value => { branchcode => $library_2->{branchcode}, categorycode => $patron_category->{categorycode} } } );
4272
4273     my $item = $builder->build_sample_item(
4274         {
4275             library      => $library_1->{branchcode},
4276         }
4277     );
4278
4279     set_userenv( $library_2 );
4280     my $reserve_id = AddReserve(
4281         {
4282             branchcode     => $library_2->{branchcode},
4283             borrowernumber => $patron_2->{borrowernumber},
4284             biblionumber   => $item->biblionumber,
4285             priority       => 1,
4286             itemnumber     => $item->itemnumber,
4287         }
4288     );
4289
4290     set_userenv( $library_1 );
4291     my $do_transfer = 1;
4292     my ( $res, $rr ) = AddReturn( $item->barcode, $library_1->{branchcode} );
4293     ModReserveAffect( $item->itemnumber, undef, $do_transfer, $reserve_id );
4294     my $hold = Koha::Holds->find( $reserve_id );
4295     is( $hold->found, 'T', 'Hold is in transit' );
4296
4297     my ( $status ) = CheckReserves($item);
4298     is( $status, 'Transferred', 'Hold is not waiting yet');
4299
4300     set_userenv( $library_2 );
4301     $do_transfer = 0;
4302     AddReturn( $item->barcode, $library_2->{branchcode} );
4303     ModReserveAffect( $item->itemnumber, undef, $do_transfer, $reserve_id );
4304     $hold = Koha::Holds->find( $reserve_id );
4305     is( $hold->found, 'W', 'Hold is waiting' );
4306     ( $status ) = CheckReserves($item);
4307     is( $status, 'Waiting', 'Now the hold is waiting');
4308
4309     #Bug 21944 - Waiting transfer checked in at branch other than pickup location
4310     set_userenv( $library_1 );
4311     (undef, my $messages, undef, undef ) = AddReturn ( $item->barcode, $library_1->{branchcode} );
4312     $hold = Koha::Holds->find( $reserve_id );
4313     is( $hold->found, undef, 'Hold is no longer marked waiting' );
4314     is( $hold->priority, 1,  "Hold is now priority one again");
4315     is( $hold->waitingdate, undef, "Hold no longer has a waiting date");
4316     is( $hold->itemnumber, $item->itemnumber, "Hold has retained its' itemnumber");
4317     is( $messages->{ResFound}->{ResFound}, "Reserved", "Hold is still returned");
4318     is( $messages->{ResFound}->{found}, undef, "Hold is no longer marked found in return message");
4319     is( $messages->{ResFound}->{priority}, 1, "Hold is priority 1 in return message");
4320 };
4321
4322 subtest 'Cancel transfers on lost items' => sub {
4323     plan tests => 6;
4324
4325     my $library_to = $builder->build_object( { class => 'Koha::Libraries' } );
4326     my $item   = $builder->build_sample_item();
4327     my $holdingbranch = $item->holdingbranch;
4328     # Historic transfer (datearrived is defined)
4329     my $old_transfer = $builder->build_object(
4330         {
4331             class => 'Koha::Item::Transfers',
4332             value => {
4333                 itemnumber    => $item->itemnumber,
4334                 frombranch    => $holdingbranch,
4335                 tobranch      => $library_to->branchcode,
4336                 reason        => 'Manual',
4337                 datesent      => \'NOW()',
4338                 datearrived   => \'NOW()',
4339                 datecancelled => undef,
4340                 daterequested => \'NOW()'
4341             }
4342         }
4343     );
4344     # Queued transfer (datesent is undefined)
4345     my $transfer_1 = $builder->build_object(
4346         {
4347             class => 'Koha::Item::Transfers',
4348             value => {
4349                 itemnumber    => $item->itemnumber,
4350                 frombranch    => $holdingbranch,
4351                 tobranch      => $library_to->branchcode,
4352                 reason        => 'Manual',
4353                 datesent      => undef,
4354                 datearrived   => undef,
4355                 datecancelled => undef,
4356                 daterequested => \'NOW()'
4357             }
4358         }
4359     );
4360     # In transit transfer (datesent is defined, datearrived and datecancelled are both undefined)
4361     my $transfer_2 = $builder->build_object(
4362         {
4363             class => 'Koha::Item::Transfers',
4364             value => {
4365                 itemnumber    => $item->itemnumber,
4366                 frombranch    => $holdingbranch,
4367                 tobranch      => $library_to->branchcode,
4368                 reason        => 'Manual',
4369                 datesent      => \'NOW()',
4370                 datearrived   => undef,
4371                 datecancelled => undef,
4372                 daterequested => \'NOW()'
4373             }
4374         }
4375     );
4376
4377     # Simulate item being marked as lost
4378     $item->itemlost(1)->store;
4379     LostItem( $item->itemnumber, 'test', 1 );
4380
4381     $transfer_1->discard_changes;
4382     isnt($transfer_1->datecancelled, undef, "Queud transfer was cancelled upon item lost");
4383     is($transfer_1->cancellation_reason, 'ItemLost', "Cancellation reason was set to 'ItemLost'");
4384     $transfer_2->discard_changes;
4385     isnt($transfer_2->datecancelled, undef, "Active transfer was cancelled upon item lost");
4386     is($transfer_2->cancellation_reason, 'ItemLost', "Cancellation reason was set to 'ItemLost'");
4387     $old_transfer->discard_changes;
4388     is($old_transfer->datecancelled, undef, "Old transfers are unaffected");
4389     $item->discard_changes;
4390     is($item->holdingbranch, $holdingbranch, "Items holding branch remains unchanged");
4391 };
4392
4393 subtest 'CanBookBeIssued | is_overdue' => sub {
4394     plan tests => 3;
4395
4396     # Set a simple circ policy
4397     Koha::CirculationRules->set_rules(
4398         {
4399             categorycode => undef,
4400             branchcode   => undef,
4401             itemtype     => undef,
4402             rules        => {
4403                 maxissueqty     => 1,
4404                 reservesallowed => 25,
4405                 issuelength     => 14,
4406                 lengthunit      => 'days',
4407                 renewalsallowed => 1,
4408                 renewalperiod   => 7,
4409                 norenewalbefore => undef,
4410                 auto_renew      => 0,
4411                 fine            => .10,
4412                 chargeperiod    => 1,
4413             }
4414         }
4415     );
4416
4417     my $now   = dt_from_string()->truncate( to => 'day' );
4418     my $five_days_go = $now->clone->add( days => 5 );
4419     my $ten_days_go  = $now->clone->add( days => 10);
4420     my $library = $builder->build( { source => 'Branch' } );
4421     my $patron  = $builder->build_object( { class => 'Koha::Patrons', value => { categorycode => $patron_category->{categorycode} } } );
4422
4423     my $item = $builder->build_sample_item(
4424         {
4425             library      => $library->{branchcode},
4426         }
4427     );
4428
4429     my $issue = AddIssue( $patron, $item->barcode, $five_days_go ); # date due was 10d ago
4430     my $actualissue = Koha::Checkouts->find( { itemnumber => $item->itemnumber } );
4431     is( output_pref({ str => $actualissue->date_due, dateonly => 1}), output_pref({ str => $five_days_go, dateonly => 1}), "First issue works");
4432     my ($issuingimpossible, $needsconfirmation) = CanBookBeIssued($patron, $item->barcode, $ten_days_go, undef, undef, undef);
4433     is( $needsconfirmation->{RENEW_ISSUE}, 1, "This is a renewal");
4434     is( $needsconfirmation->{TOO_MANY}, undef, "Not too many, is a renewal");
4435 };
4436
4437 subtest 'ItemsDeniedRenewal rules are checked' => sub {
4438     plan tests => 4;
4439
4440     my $idr_lib = $builder->build_object({ class => 'Koha::Libraries'});
4441     Koha::CirculationRules->set_rules(
4442         {
4443             categorycode => '*',
4444             itemtype     => '*',
4445             branchcode   => $idr_lib->branchcode,
4446             rules        => {
4447                 reservesallowed => 25,
4448                 issuelength     => 14,
4449                 lengthunit      => 'days',
4450                 renewalsallowed => 10,
4451                 renewalperiod   => 7,
4452                 norenewalbefore => undef,
4453                 auto_renew      => 0,
4454                 fine            => .10,
4455                 chargeperiod    => 1,
4456             }
4457         }
4458     );
4459
4460     my $allow_book = $builder->build_sample_item({
4461         homebranch => $idr_lib->branchcode,
4462         holdingbranch => $idr_lib->branchcode,
4463     });
4464
4465     my $idr_borrower = $builder->build_object({ class => 'Koha::Patrons', value=> {
4466         branchcode => $idr_lib->branchcode,
4467         }
4468     });
4469     my $future = dt_from_string->add( days => 1 );
4470     my $issue = $builder->build_object(
4471         {
4472             class => 'Koha::Checkouts',
4473             value => {
4474                 returndate      => undef,
4475                 renewals_count  => 0,
4476                 auto_renew      => 0,
4477                 borrowernumber  => $idr_borrower->borrowernumber,
4478                 itemnumber      => $allow_book->itemnumber,
4479                 onsite_checkout => 0,
4480                 date_due        => $future,
4481             }
4482         }
4483     );
4484
4485     my $mock_item_class = Test::MockModule->new("Koha::Item");
4486     $mock_item_class->mock( 'is_denied_renewal', sub { return 1; } );
4487
4488     my ( $mayrenew, $error ) = CanBookBeRenewed( $idr_borrower, $issue );
4489     is( $mayrenew, 0, 'Renewal blocked when $item->is_denied_renewal returns true' );
4490     is( $error, 'item_denied_renewal', 'Renewal blocked when $item->is_denied_renewal returns true' );
4491
4492     $mock_item_class->unmock( 'is_denied_renewal' );
4493     $mock_item_class->mock( 'is_denied_renewal', sub { return 0; } );
4494
4495     ( $mayrenew, $error ) = CanBookBeRenewed( $idr_borrower, $issue );
4496     is( $mayrenew, 1, 'Renewal allowed when $item->is_denied_renewal returns false' );
4497     is( $error, undef, 'Renewal allowed when $item->is_denied_renewal returns false' );
4498
4499     $mock_item_class->unmock( 'is_denied_renewal' );
4500 };
4501
4502 subtest 'CanBookBeIssued | item-level_itypes=biblio' => sub {
4503     plan tests => 2;
4504
4505     t::lib::Mocks::mock_preference('item-level_itypes', 0); # biblio
4506     my $library = $builder->build( { source => 'Branch' } );
4507     my $patron  = $builder->build_object( { class => 'Koha::Patrons', value => { categorycode => $patron_category->{categorycode} } } )->store;
4508
4509     my $item = $builder->build_sample_item(
4510         {
4511             library      => $library->{branchcode},
4512         }
4513     );
4514
4515     my ( $issuingimpossible, $needsconfirmation ) = CanBookBeIssued( $patron, $item->barcode, undef, undef, undef, undef );
4516     is_deeply( $needsconfirmation, {}, 'Item can be issued to this patron' );
4517     is_deeply( $issuingimpossible, {}, 'Item can be issued to this patron' );
4518 };
4519
4520 subtest 'CanBookBeIssued | notforloan' => sub {
4521     plan tests => 2;
4522
4523     t::lib::Mocks::mock_preference('AllowNotForLoanOverride', 0);
4524
4525     my $library = $builder->build( { source => 'Branch' } );
4526     my $patron  = $builder->build_object( { class => 'Koha::Patrons', value => { categorycode => $patron_category->{categorycode} } } )->store;
4527
4528     my $itemtype = $builder->build(
4529         {
4530             source => 'Itemtype',
4531             value  => { notforloan => undef, }
4532         }
4533     );
4534     my $item = $builder->build_sample_item(
4535         {
4536             library  => $library->{branchcode},
4537             itype    => $itemtype->{itemtype},
4538         }
4539     );
4540     $item->biblioitem->itemtype($itemtype->{itemtype})->store;
4541
4542     my ( $issuingimpossible, $needsconfirmation );
4543
4544
4545     subtest 'item-level_itypes = 1' => sub {
4546         plan tests => 6;
4547
4548         t::lib::Mocks::mock_preference('item-level_itypes', 1); # item
4549         # Is for loan at item type and item level
4550         ( $issuingimpossible, $needsconfirmation ) = CanBookBeIssued( $patron, $item->barcode, undef, undef, undef, undef );
4551         is_deeply( $needsconfirmation, {}, 'Item can be issued to this patron' );
4552         is_deeply( $issuingimpossible, {}, 'Item can be issued to this patron' );
4553
4554         # not for loan at item type level
4555         Koha::ItemTypes->find( $itemtype->{itemtype} )->notforloan(1)->store;
4556         ( $issuingimpossible, $needsconfirmation ) = CanBookBeIssued( $patron, $item->barcode, undef, undef, undef, undef );
4557         is_deeply( $needsconfirmation, {}, 'No confirmation needed, AllowNotForLoanOverride=0' );
4558         is_deeply(
4559             $issuingimpossible,
4560             { NOT_FOR_LOAN => 1, itemtype_notforloan => $itemtype->{itemtype} },
4561             'Item can not be issued, not for loan at item type level'
4562         );
4563
4564         # not for loan at item level
4565         Koha::ItemTypes->find( $itemtype->{itemtype} )->notforloan(undef)->store;
4566         $item->notforloan( 1 )->store;
4567         ( $issuingimpossible, $needsconfirmation ) = CanBookBeIssued( $patron, $item->barcode, undef, undef, undef, undef );
4568         is_deeply( $needsconfirmation, {}, 'No confirmation needed, AllowNotForLoanOverride=0' );
4569         is_deeply(
4570             $issuingimpossible,
4571             { NOT_FOR_LOAN => 1, item_notforloan => 1 },
4572             'Item can not be issued, not for loan at item type level'
4573         );
4574     };
4575
4576     subtest 'item-level_itypes = 0' => sub {
4577         plan tests => 6;
4578
4579         t::lib::Mocks::mock_preference('item-level_itypes', 0); # biblio
4580
4581         # We set another itemtype for biblioitem
4582         my $itemtype = $builder->build(
4583             {
4584                 source => 'Itemtype',
4585                 value  => { notforloan => undef, }
4586             }
4587         );
4588
4589         # for loan at item type and item level
4590         $item->notforloan(0)->store;
4591         $item->biblioitem->itemtype($itemtype->{itemtype})->store;
4592         ( $issuingimpossible, $needsconfirmation ) = CanBookBeIssued( $patron, $item->barcode, undef, undef, undef, undef );
4593         is_deeply( $needsconfirmation, {}, 'Item can be issued to this patron' );
4594         is_deeply( $issuingimpossible, {}, 'Item can be issued to this patron' );
4595
4596         # not for loan at item type level
4597         Koha::ItemTypes->find( $itemtype->{itemtype} )->notforloan(1)->store;
4598         ( $issuingimpossible, $needsconfirmation ) = CanBookBeIssued( $patron, $item->barcode, undef, undef, undef, undef );
4599         is_deeply( $needsconfirmation, {}, 'No confirmation needed, AllowNotForLoanOverride=0' );
4600         is_deeply(
4601             $issuingimpossible,
4602             { NOT_FOR_LOAN => 1, itemtype_notforloan => $itemtype->{itemtype} },
4603             'Item can not be issued, not for loan at item type level'
4604         );
4605
4606         # not for loan at item level
4607         Koha::ItemTypes->find( $itemtype->{itemtype} )->notforloan(undef)->store;
4608         $item->notforloan( 1 )->store;
4609         ( $issuingimpossible, $needsconfirmation ) = CanBookBeIssued( $patron, $item->barcode, undef, undef, undef, undef );
4610         is_deeply( $needsconfirmation, {}, 'No confirmation needed, AllowNotForLoanOverride=0' );
4611         is_deeply(
4612             $issuingimpossible,
4613             { NOT_FOR_LOAN => 1, item_notforloan => 1 },
4614             'Item can not be issued, not for loan at item type level'
4615         );
4616     };
4617
4618     # TODO test with AllowNotForLoanOverride = 1
4619 };
4620
4621 subtest 'CanBookBeIssued | recalls' => sub {
4622     plan tests => 3;
4623
4624     t::lib::Mocks::mock_preference("UseRecalls", 1);
4625     t::lib::Mocks::mock_preference("item-level_itypes", 1);
4626     my $patron1 = $builder->build_object({ class => 'Koha::Patrons' });
4627     my $patron2 = $builder->build_object({ class => 'Koha::Patrons' });
4628     my $item = $builder->build_sample_item;
4629     Koha::CirculationRules->set_rules({
4630         branchcode => undef,
4631         itemtype => undef,
4632         categorycode => undef,
4633         rules => {
4634             recalls_allowed => 10,
4635         },
4636     });
4637
4638     # item-level recall
4639     my $recall = Koha::Recall->new(
4640         {   patron_id         => $patron1->borrowernumber,
4641             biblio_id         => $item->biblionumber,
4642             item_id           => $item->itemnumber,
4643             item_level        => 1,
4644             pickup_library_id => $patron1->branchcode,
4645         }
4646     )->store;
4647
4648     my ( $issuingimpossible, $needsconfirmation ) = CanBookBeIssued( $patron2, $item->barcode, undef, undef, undef, undef );
4649     is( $needsconfirmation->{RECALLED}->id, $recall->id, "Another patron has placed an item-level recall on this item" );
4650
4651     $recall->set_cancelled;
4652
4653     # biblio-level recall
4654     $recall = Koha::Recall->new(
4655         {   patron_id         => $patron1->borrowernumber,
4656             biblio_id         => $item->biblionumber,
4657             item_id           => undef,
4658             item_level        => 0,
4659             pickup_library_id => $patron1->branchcode,
4660         }
4661     )->store;
4662
4663     ( $issuingimpossible, $needsconfirmation ) = CanBookBeIssued( $patron2, $item->barcode, undef, undef, undef, undef );
4664     is( $needsconfirmation->{RECALLED}->id, $recall->id, "Another patron has placed a biblio-level recall and this item is eligible to fill it" );
4665
4666     $recall->set_cancelled;
4667
4668     # biblio-level recall
4669     $recall = Koha::Recall->new(
4670         {   patron_id         => $patron1->borrowernumber,
4671             biblio_id         => $item->biblionumber,
4672             item_id           => undef,
4673             item_level        => 0,
4674             pickup_library_id => $patron1->branchcode,
4675         }
4676     )->store;
4677     $recall->set_waiting( { item => $item, expirationdate => dt_from_string() } );
4678
4679     my ( undef, undef, undef, $messages ) = CanBookBeIssued( $patron1, $item->barcode, undef, undef, undef, undef );
4680     is( $messages->{RECALLED}, $recall->id, "This book can be issued by this patron and they have placed a recall" );
4681
4682     $recall->set_cancelled;
4683 };
4684
4685 subtest 'CanBookBeIssued | bookings' => sub {
4686     plan tests => 4;
4687
4688     my $schema = Koha::Database->schema;
4689     $schema->storage->txn_begin;
4690
4691     my $patron1 = $builder->build_object( { class => 'Koha::Patrons' } );
4692     my $patron2 = $builder->build_object( { class => 'Koha::Patrons' } );
4693     my $item    = $builder->build_sample_item( { bookable => 1 } );
4694
4695     # item-level booking
4696     my $booking = Koha::Booking->new(
4697         {
4698             patron_id  => $patron1->borrowernumber,
4699             item_id    => $item->itemnumber,
4700             biblio_id  => $item->biblio->biblionumber,
4701             start_date => dt_from_string()->subtract( days => 1 ),
4702             end_date   => dt_from_string()->add( days => 6 ),
4703         }
4704     )->store();
4705
4706     # Booking encompasses proposed checkout
4707     my ( $issuingimpossible, $needsconfirmation, $alerts, $messages ) = CanBookBeIssued(
4708         $patron2, $item->barcode,
4709         dt_from_string()->add( days => 5 ),
4710         undef, undef, undef
4711     );
4712     is(
4713         $issuingimpossible->{BOOKED_TO_ANOTHER}->booking_id,
4714         $booking->booking_id,
4715         "Another patron has booked this item with a start date before the proposed due date"
4716     );
4717
4718     ( $issuingimpossible, $needsconfirmation, $alerts, $messages ) = CanBookBeIssued(
4719         $patron1, $item->barcode,
4720         dt_from_string()->add( days => 5 ),
4721         undef, undef, undef
4722     );
4723     is(
4724         $alerts->{BOOKED}->booking_id,
4725         $booking->booking_id, "Booked to this user"
4726     );
4727
4728     # Booking start will clash before issue due
4729     $booking->start_date( dt_from_string()->add( days => 3 ) )->store();
4730
4731     ( $issuingimpossible, $needsconfirmation, $alerts, $messages ) = CanBookBeIssued(
4732         $patron2, $item->barcode,
4733         dt_from_string()->add( days => 5 ),
4734         undef, undef, undef
4735     );
4736     is(
4737         $needsconfirmation->{BOOKED_TO_ANOTHER}->booking_id,
4738         $booking->booking_id,
4739         "Another patron has booked this item for a period starting before the proposed due date"
4740     );
4741
4742     ( $issuingimpossible, $needsconfirmation, $alerts, $messages ) = CanBookBeIssued(
4743         $patron1, $item->barcode,
4744         dt_from_string()->add( days => 5 ),
4745         undef, undef, undef
4746     );
4747     is(
4748         $needsconfirmation->{BOOKED_EARLY}->booking_id,
4749         $booking->booking_id,
4750         "Booked to this user, but they're collecting early"
4751     );
4752
4753     $schema->storage->txn_rollback;
4754 };
4755
4756 subtest 'AddReturn should clear items.onloan for unissued items' => sub {
4757     plan tests => 1;
4758
4759     t::lib::Mocks::mock_preference( "AllowReturnToBranch", 'anywhere' );
4760     my $item = $builder->build_sample_item(
4761         {
4762             onloan => '2018-01-01',
4763         }
4764     );
4765
4766     AddReturn( $item->barcode, $item->homebranch );
4767     $item->discard_changes; # refresh
4768     is( $item->onloan, undef, 'AddReturn did clear items.onloan' );
4769 };
4770
4771 subtest 'AddReturn | recalls' => sub {
4772     plan tests => 3;
4773
4774     t::lib::Mocks::mock_preference("UseRecalls", 1);
4775     t::lib::Mocks::mock_preference("item-level_itypes", 1);
4776     my $patron1 = $builder->build_object({ class => 'Koha::Patrons' });
4777     my $patron2 = $builder->build_object({ class => 'Koha::Patrons' });
4778     my $item1 = $builder->build_sample_item;
4779     Koha::CirculationRules->set_rules({
4780         branchcode => undef,
4781         itemtype => undef,
4782         categorycode => undef,
4783         rules => {
4784             recalls_allowed => 10,
4785         },
4786     });
4787
4788     # this item can fill a recall with pickup at this branch
4789     AddIssue( $patron1, $item1->barcode );
4790     my $recall1 = Koha::Recall->new(
4791         {   patron_id         => $patron2->borrowernumber,
4792             biblio_id         => $item1->biblionumber,
4793             item_id           => $item1->itemnumber,
4794             item_level        => 1,
4795             pickup_library_id => $item1->homebranch,
4796         }
4797     )->store;
4798     my ( $doreturn, $messages, $iteminfo, $borrowerinfo ) = AddReturn( $item1->barcode, $item1->homebranch );
4799     is( $messages->{RecallFound}->id, $recall1->id, "Recall found" );
4800     $recall1->set_cancelled;
4801
4802     # this item can fill a recall but needs transfer
4803     AddIssue( $patron1, $item1->barcode );
4804     $recall1 = Koha::Recall->new(
4805         {   patron_id         => $patron2->borrowernumber,
4806             biblio_id         => $item1->biblionumber,
4807             item_id           => $item1->itemnumber,
4808             item_level        => 1,
4809             pickup_library_id => $patron2->branchcode,
4810         }
4811     )->store;
4812     ( $doreturn, $messages, $iteminfo, $borrowerinfo ) = AddReturn( $item1->barcode, $item1->homebranch );
4813     is( $messages->{RecallNeedsTransfer}, $item1->homebranch, "Recall requiring transfer found" );
4814     $recall1->set_cancelled;
4815
4816     # this item is already in transit, do not ask to transfer
4817     AddIssue( $patron1, $item1->barcode );
4818     $recall1 = Koha::Recall->new(
4819         {   patron_id         => $patron2->borrowernumber,
4820             biblio_id         => $item1->biblionumber,
4821             item_id           => $item1->itemnumber,
4822             item_level        => 1,
4823             pickup_library_id => $patron2->branchcode,
4824         }
4825     )->store;
4826     $recall1->start_transfer;
4827     ( $doreturn, $messages, $iteminfo, $borrowerinfo ) = AddReturn( $item1->barcode, $patron2->branchcode );
4828     is( $messages->{TransferredRecall}->id, $recall1->id, "In transit recall found" );
4829     $recall1->set_cancelled;
4830 };
4831
4832 subtest 'AddReturn | bundles' => sub {
4833     plan tests => 1;
4834
4835     my $schema = Koha::Database->schema;
4836     $schema->storage->txn_begin;
4837
4838     my $patron1 = $builder->build_object({ class => 'Koha::Patrons' });
4839     my $host_item1 = $builder->build_sample_item;
4840     my $bundle_item1 = $builder->build_sample_item;
4841     $schema->resultset('ItemBundle')
4842       ->create(
4843         { host => $host_item1->itemnumber, item => $bundle_item1->itemnumber } );
4844
4845     my ( $doreturn, $messages, $iteminfo, $borrowerinfo ) = AddReturn( $bundle_item1->barcode, $bundle_item1->homebranch );
4846     is($messages->{InBundle}->id, $host_item1->id, 'AddReturn returns InBundle host item when item is part of a bundle');
4847
4848     $schema->storage->txn_rollback;
4849 };
4850
4851 subtest 'AddRenewal and AddIssuingCharge tests' => sub {
4852
4853     plan tests => 13;
4854
4855
4856     t::lib::Mocks::mock_preference('item-level_itypes', 1);
4857
4858     my $issuing_charges = 15;
4859     my $title   = 'A title';
4860     my $author  = 'Author, An';
4861     my $barcode = 'WHATARETHEODDS';
4862
4863     my $circ = Test::MockModule->new('C4::Circulation');
4864     $circ->mock(
4865         'GetIssuingCharges',
4866         sub {
4867             return $issuing_charges;
4868         }
4869     );
4870
4871     my $library  = $builder->build_object({ class => 'Koha::Libraries' });
4872     my $itemtype = $builder->build_object({ class => 'Koha::ItemTypes', value => { rentalcharge_daily => 0.00 }});
4873     my $patron   = $builder->build_object({
4874         class => 'Koha::Patrons',
4875         value => { branchcode => $library->id }
4876     });
4877
4878     my $biblio = $builder->build_sample_biblio({ title=> $title, author => $author });
4879     my $item_id = Koha::Item->new(
4880         {
4881             biblionumber     => $biblio->biblionumber,
4882             homebranch       => $library->id,
4883             holdingbranch    => $library->id,
4884             barcode          => $barcode,
4885             replacementprice => 23.00,
4886             itype            => $itemtype->id
4887         },
4888     )->store->itemnumber;
4889     my $item = Koha::Items->find( $item_id );
4890
4891     my $context = Test::MockModule->new('C4::Context');
4892     $context->mock( userenv => { branch => $library->id } );
4893
4894     # Check the item out
4895     AddIssue( $patron, $item->barcode );
4896
4897     throws_ok {
4898         AddRenewal(
4899             {
4900                 borrowernumber  => $patron->borrowernumber,
4901                 itemnumber      => $item->itemnumber,
4902                 branch          => $library->id,
4903                 lastreneweddate => { break => "the_renewal" }
4904             }
4905         );
4906     } 'Koha::Exceptions::Checkout::FailedRenewal', 'Exception is thrown when renewal update to issues fails';
4907
4908     t::lib::Mocks::mock_preference( 'RenewalLog', 0 );
4909     my $date = output_pref( { dt => dt_from_string(), dateonly => 1, dateformat => 'iso' } );
4910     my %params_renewal = (
4911         timestamp => { -like => $date . "%" },
4912         module => "CIRCULATION",
4913         action => "RENEWAL",
4914     );
4915     my $old_log_size = Koha::ActionLogs->count( \%params_renewal );;
4916     AddRenewal(
4917         {
4918             borrowernumber => $patron->id,
4919             itemnumber     => $item->id,
4920             branch         => $library->id
4921         }
4922     );
4923     my $new_log_size = Koha::ActionLogs->count( \%params_renewal );
4924     is( $new_log_size, $old_log_size, 'renew log not added because of the syspref RenewalLog' );
4925
4926     my $checkouts = $patron->checkouts;
4927     # The following will fail if run on 00:00:00
4928     unlike ( $checkouts->next->lastreneweddate, qr/00:00:00/, 'AddRenewal should set the renewal date with the time part');
4929
4930     my $lines = Koha::Account::Lines->search({
4931         borrowernumber => $patron->id,
4932         itemnumber     => $item->id
4933     });
4934
4935     is( $lines->count, 2 );
4936
4937     my $line = $lines->next;
4938     is( $line->debit_type_code, 'RENT',       'The issue of item with issuing charge generates an accountline of the correct type' );
4939     is( $line->branchcode,  $library->id, 'AddIssuingCharge correctly sets branchcode' );
4940     is( $line->description, '',     'AddIssue does not set a hardcoded description for the accountline' );
4941
4942     $line = $lines->next;
4943     is( $line->debit_type_code, 'RENT_RENEW', 'The renewal of item with issuing charge generates an accountline of the correct type' );
4944     is( $line->branchcode,  $library->id, 'AddRenewal correctly sets branchcode' );
4945     is( $line->description, '', 'AddRenewal does not set a hardcoded description for the accountline' );
4946
4947     t::lib::Mocks::mock_preference( 'RenewalLog', 1 );
4948
4949     $context = Test::MockModule->new('C4::Context');
4950     $context->mock( userenv => { branch => undef, interface => 'CRON'} ); #Test statistical logging of renewal via cron (atuo_renew)
4951
4952     my $now = dt_from_string;
4953     $date = output_pref( { dt => $now, dateonly => 1, dateformat => 'iso' } );
4954     $old_log_size = Koha::ActionLogs->count( \%params_renewal );
4955     my $sth = $dbh->prepare("SELECT COUNT(*) FROM statistics WHERE itemnumber = ? AND branch = ?");
4956     $sth->execute($item->id, $library->id);
4957     my ($old_stats_size) = $sth->fetchrow_array;
4958     AddRenewal(
4959         {
4960             borrowernumber => $patron->id,
4961             itemnumber     => $item->id,
4962             branch         => $library->id
4963         }
4964     );
4965     $new_log_size = Koha::ActionLogs->count( \%params_renewal );
4966     $sth->execute($item->id, $library->id);
4967     my ($new_stats_size) = $sth->fetchrow_array;
4968     is( $new_log_size, $old_log_size + 1, 'renew log successfully added' );
4969     is( $new_stats_size, $old_stats_size + 1, 'renew statistic successfully added with passed branch' );
4970
4971     AddReturn( $item->id, $library->id, undef, $date );
4972     AddIssue( $patron, $item->barcode, $now );
4973     AddRenewal(
4974         {
4975             borrowernumber => $patron->id,
4976             itemnumber     => $item->id,
4977             branch         => $library->id,
4978             skipfinecalc   => 1
4979         }
4980     );
4981     my $lines_skipped = Koha::Account::Lines->search({
4982         borrowernumber => $patron->id,
4983         itemnumber     => $item->id
4984     });
4985     is( $lines_skipped->count, 5, 'Passing skipfinecalc causes fine calculation on renewal to be skipped' );
4986
4987 };
4988
4989 subtest 'AddRenewal() adds to renewals' => sub {
4990     plan tests => 5;
4991
4992     my $library  = $builder->build_object({ class => 'Koha::Libraries' });
4993     my $patron   = $builder->build_object({
4994         class => 'Koha::Patrons',
4995         value => { branchcode => $library->id }
4996     });
4997
4998     my $item = $builder->build_sample_item();
4999
5000     set_userenv( $library->unblessed );
5001
5002     # Check the item out
5003     my $issue = AddIssue( $patron, $item->barcode );
5004     is(ref($issue), 'Koha::Checkout', 'Issue added');
5005
5006     # Renew item
5007     my $duedate = AddRenewal(
5008         {
5009             borrowernumber => $patron->id,
5010             itemnumber     => $item->id,
5011             branch         => $library->id,
5012             automatic      => 1
5013         }
5014     );
5015
5016     ok( $duedate, "Renewal added" );
5017
5018     my $renewals = Koha::Checkouts::Renewals->search({ checkout_id => $issue->issue_id });
5019     is($renewals->count, 1, 'One renewal added');
5020     my $THE_renewal = $renewals->next;
5021     is( $THE_renewal->renewer_id, C4::Context->userenv->{'number'}, 'Renewer recorded from context' );
5022     is( $THE_renewal->renewal_type, 'Automatic', 'AddRenewal "automatic" parameter sets renewal type to "Automatic"');
5023 };
5024
5025 subtest 'ProcessOfflinePayment() tests' => sub {
5026
5027     plan tests => 4;
5028
5029
5030     my $amount = 123;
5031
5032     my $patron  = $builder->build_object({ class => 'Koha::Patrons' });
5033     my $library = $builder->build_object({ class => 'Koha::Libraries' });
5034     my $result  = C4::Circulation::ProcessOfflinePayment({ cardnumber => $patron->cardnumber, amount => $amount, branchcode => $library->id });
5035
5036     is( $result, 'Success.', 'The right string is returned' );
5037
5038     my $lines = $patron->account->lines;
5039     is( $lines->count, 1, 'line created correctly');
5040
5041     my $line = $lines->next;
5042     is( $line->amount+0, $amount * -1, 'amount picked from params' );
5043     is( $line->branchcode, $library->id, 'branchcode set correctly' );
5044
5045 };
5046
5047 subtest 'Incremented fee tests' => sub {
5048     plan tests => 19;
5049
5050     my $dt = dt_from_string();
5051     Time::Fake->offset( $dt->epoch );
5052
5053     t::lib::Mocks::mock_preference( 'item-level_itypes', 1 );
5054
5055     my $library = $builder->build_object( { class => 'Koha::Libraries' } )->store;
5056
5057     $module->mock( 'userenv', sub { { branch => $library->id } } );
5058
5059     my $patron = $builder->build_object(
5060         {
5061             class => 'Koha::Patrons',
5062             value => { categorycode => $patron_category->{categorycode} }
5063         }
5064     )->store;
5065
5066     my $itemtype = $builder->build_object(
5067         {
5068             class => 'Koha::ItemTypes',
5069             value => {
5070                 notforloan                   => undef,
5071                 rentalcharge                 => 0,
5072                 rentalcharge_daily           => 1,
5073                 rentalcharge_daily_calendar  => 0
5074             }
5075         }
5076     )->store;
5077
5078     my $item = $builder->build_sample_item(
5079         {
5080             library  => $library->id,
5081             itype    => $itemtype->id,
5082         }
5083     );
5084
5085     is( $itemtype->rentalcharge_daily + 0,1, 'Daily rental charge stored and retreived correctly' );
5086     is( $item->effective_itemtype, $itemtype->id, "Itemtype set correctly for item" );
5087
5088     my $now         = dt_from_string;
5089     my $dt_from     = $now->clone;
5090     my $dt_to       = $now->clone->add( days => 7 );
5091     my $dt_to_renew = $now->clone->add( days => 13 );
5092
5093     # Daily Tests
5094     my $issue =
5095       AddIssue( $patron, $item->barcode, $dt_to, undef, $dt_from );
5096     my $accountline = Koha::Account::Lines->find( { itemnumber => $item->id } );
5097     is(
5098         $accountline->amount + 0,
5099         7,
5100         "Daily rental charge calculated correctly with rentalcharge_daily_calendar = 0"
5101     );
5102     $accountline->delete();
5103     AddRenewal(
5104         {
5105             borrowernumber  => $patron->id,
5106             itemnumber      => $item->id,
5107             branch          => $library->id,
5108             datedue         => $dt_to_renew,
5109             lastreneweddate => $dt_to
5110         }
5111     );
5112     $accountline = Koha::Account::Lines->find( { itemnumber => $item->id } );
5113     is(
5114         $accountline->amount + 0,
5115         6,
5116         "Daily rental charge calculated correctly with rentalcharge_daily_calendar = 0, for renewal"
5117     );
5118     $accountline->delete();
5119     $issue->delete();
5120
5121     t::lib::Mocks::mock_preference( 'finesCalendar', 'noFinesWhenClosed' );
5122     $itemtype->rentalcharge_daily_calendar(1)->store();
5123     $issue =
5124       AddIssue( $patron, $item->barcode, $dt_to, undef, $dt_from );
5125     $accountline = Koha::Account::Lines->find( { itemnumber => $item->id } );
5126     is(
5127         $accountline->amount + 0,
5128         7,
5129         "Daily rental charge calculated correctly with rentalcharge_daily_calendar = 1"
5130     );
5131     $accountline->delete();
5132     AddRenewal(
5133         {
5134             borrowernumber  => $patron->id,
5135             itemnumber      => $item->id,
5136             branch          => $library->id,
5137             datedue         => $dt_to_renew,
5138             lastreneweddate => $dt_to
5139         }
5140     );
5141     $accountline = Koha::Account::Lines->find( { itemnumber => $item->id } );
5142     is(
5143         $accountline->amount + 0,
5144         6,
5145         "Daily rental charge calculated correctly with rentalcharge_daily_calendar = 1, for renewal"
5146     );
5147     $accountline->delete();
5148     $issue->delete();
5149
5150     my $calendar = C4::Calendar->new( branchcode => $library->id );
5151     # DateTime 1..7 (Mon..Sun), C4::Calender 0..6 (Sun..Sat)
5152     my $closed_day =
5153         ( $dt_from->day_of_week == 6 ) ? 0
5154       : ( $dt_from->day_of_week == 7 ) ? 1
5155       :                                  $dt_from->day_of_week + 1;
5156     my $closed_day_name = $dt_from->clone->add(days => 1)->day_name;
5157     $calendar->insert_week_day_holiday(
5158         weekday     => $closed_day,
5159         title       => 'Test holiday',
5160         description => 'Test holiday'
5161     );
5162     $issue =
5163       AddIssue( $patron, $item->barcode, $dt_to, undef, $dt_from );
5164     $accountline = Koha::Account::Lines->find( { itemnumber => $item->id } );
5165     is(
5166         $accountline->amount + 0,
5167         6,
5168         "Daily rental charge calculated correctly with rentalcharge_daily_calendar = 1 and closed $closed_day_name"
5169     );
5170     $accountline->delete();
5171     AddRenewal(
5172         {
5173             borrowernumber  => $patron->id,
5174             itemnumber      => $item->id,
5175             branch          => $library->id,
5176             datedue         => $dt_to_renew,
5177             lastreneweddate => $dt_to
5178         }
5179     );
5180     $accountline = Koha::Account::Lines->find( { itemnumber => $item->id } );
5181     is(
5182         $accountline->amount + 0,
5183         5,
5184         "Daily rental charge calculated correctly with rentalcharge_daily_calendar = 1 and closed $closed_day_name, for renewal"
5185     );
5186     $accountline->delete();
5187     $issue->delete();
5188
5189     $itemtype->rentalcharge(2)->store;
5190     is( $itemtype->rentalcharge + 0, 2, 'Rental charge updated and retreived correctly' );
5191     $issue =
5192       AddIssue( $patron, $item->barcode, $dt_to, undef, $dt_from );
5193     my $accountlines =
5194       Koha::Account::Lines->search( { itemnumber => $item->id } );
5195     is( $accountlines->count, '2', "Fixed charge and accrued charge recorded distinctly" );
5196     $accountlines->delete();
5197     AddRenewal(
5198         {
5199             borrowernumber  => $patron->id,
5200             itemnumber      => $item->id,
5201             branch          => $library->id,
5202             datedue         => $dt_to_renew,
5203             lastreneweddate => $dt_to
5204         }
5205     );
5206     $accountlines = Koha::Account::Lines->search( { itemnumber => $item->id } );
5207     is( $accountlines->count, '2', "Fixed charge and accrued charge recorded distinctly, for renewal" );
5208     $accountlines->delete();
5209     $issue->delete();
5210     $itemtype->rentalcharge(0)->store;
5211     is( $itemtype->rentalcharge + 0, 0, 'Rental charge reset and retreived correctly' );
5212
5213     # Hourly
5214     Koha::CirculationRules->set_rule(
5215         {
5216             categorycode => $patron->categorycode,
5217             itemtype     => $itemtype->id,
5218             branchcode   => $library->id,
5219             rule_name    => 'lengthunit',
5220             rule_value   => 'hours',
5221         }
5222     );
5223
5224     $itemtype->rentalcharge_hourly('0.25')->store();
5225     is( $itemtype->rentalcharge_hourly, '0.25', 'Hourly rental charge stored and retreived correctly' );
5226
5227     $dt_to       = $now->clone->add( hours => 168 );
5228     $dt_to_renew = $now->clone->add( hours => 312 );
5229
5230     $itemtype->rentalcharge_hourly_calendar(0)->store();
5231     $issue =
5232       AddIssue( $patron, $item->barcode, $dt_to, undef, $dt_from );
5233     $accountline = Koha::Account::Lines->find( { itemnumber => $item->id } );
5234     is(
5235         $accountline->amount + 0,
5236         42,
5237         "Hourly rental charge calculated correctly with rentalcharge_hourly_calendar = 0 (168h * 0.25u)"
5238     );
5239     $accountline->delete();
5240     AddRenewal(
5241         {
5242             borrowernumber  => $patron->id,
5243             itemnumber      => $item->id,
5244             branch          => $library->id,
5245             datedue         => $dt_to_renew,
5246             lastreneweddate => $dt_to
5247         }
5248     );
5249     $accountline = Koha::Account::Lines->find( { itemnumber => $item->id } );
5250     is(
5251         $accountline->amount + 0,
5252         36,
5253         "Hourly rental charge calculated correctly with rentalcharge_hourly_calendar = 0, for renewal (312h - 168h * 0.25u)"
5254     );
5255     $accountline->delete();
5256     $issue->delete();
5257
5258     $itemtype->rentalcharge_hourly_calendar(1)->store();
5259     $issue =
5260       AddIssue( $patron, $item->barcode, $dt_to, undef, $dt_from );
5261     $accountline = Koha::Account::Lines->find( { itemnumber => $item->id } );
5262     is(
5263         $accountline->amount + 0,
5264         36,
5265         "Hourly rental charge calculated correctly with rentalcharge_hourly_calendar = 1 and closed $closed_day_name (168h - 24h * 0.25u)"
5266     );
5267     $accountline->delete();
5268     AddRenewal(
5269         {
5270             borrowernumber  => $patron->id,
5271             itemnumber      => $item->id,
5272             branch          => $library->id,
5273             datedue         => $dt_to_renew,
5274             lastreneweddate => $dt_to
5275         }
5276     );
5277     $accountline = Koha::Account::Lines->find( { itemnumber => $item->id } );
5278     is(
5279         $accountline->amount + 0,
5280         30,
5281         "Hourly rental charge calculated correctly with rentalcharge_hourly_calendar = 1 and closed $closed_day_name, for renewal (312h - 168h - 24h * 0.25u"
5282     );
5283     $accountline->delete();
5284     $issue->delete();
5285
5286     $calendar->delete_holiday( weekday => $closed_day );
5287     $issue =
5288       AddIssue( $patron, $item->barcode, $dt_to, undef, $dt_from );
5289     $accountline = Koha::Account::Lines->find( { itemnumber => $item->id } );
5290     is(
5291         $accountline->amount + 0,
5292         42,
5293         "Hourly rental charge calculated correctly with rentalcharge_hourly_calendar = 1 (168h - 0h * 0.25u"
5294     );
5295     $accountline->delete();
5296     AddRenewal(
5297         {
5298             borrowernumber  => $patron->id,
5299             itemnumber      => $item->id,
5300             branch          => $library->id,
5301             datedue         => $dt_to_renew,
5302             lastreneweddate => $dt_to
5303         }
5304     );
5305     $accountline = Koha::Account::Lines->find( { itemnumber => $item->id } );
5306     is(
5307         $accountline->amount + 0,
5308         36,
5309         "Hourly rental charge calculated correctly with rentalcharge_hourly_calendar = 1, for renewal (312h - 168h - 0h * 0.25u)"
5310     );
5311     $accountline->delete();
5312     $issue->delete();
5313     Time::Fake->reset;
5314 };
5315
5316 subtest 'CanBookBeIssued & RentalFeesCheckoutConfirmation' => sub {
5317     plan tests => 2;
5318
5319     t::lib::Mocks::mock_preference('RentalFeesCheckoutConfirmation', 1);
5320     t::lib::Mocks::mock_preference('item-level_itypes', 1);
5321
5322     my $library =
5323       $builder->build_object( { class => 'Koha::Libraries' } )->store;
5324     my $patron = $builder->build_object(
5325         {
5326             class => 'Koha::Patrons',
5327             value => { categorycode => $patron_category->{categorycode} }
5328         }
5329     )->store;
5330
5331     my $itemtype = $builder->build_object(
5332         {
5333             class => 'Koha::ItemTypes',
5334             value => {
5335                 notforloan             => 0,
5336                 rentalcharge           => 0,
5337                 rentalcharge_daily => 0
5338             }
5339         }
5340     );
5341
5342     my $item = $builder->build_sample_item(
5343         {
5344             library    => $library->id,
5345             notforloan => 0,
5346             itemlost   => 0,
5347             withdrawn  => 0,
5348             itype      => $itemtype->id,
5349         }
5350     )->store;
5351
5352     my ( $issuingimpossible, $needsconfirmation );
5353     my $dt_from = dt_from_string();
5354     my $dt_due = $dt_from->clone->add( days => 3 );
5355
5356     $itemtype->rentalcharge(1)->store;
5357     ( $issuingimpossible, $needsconfirmation ) = CanBookBeIssued( $patron, $item->barcode, $dt_due, undef, undef, undef );
5358     is_deeply( $needsconfirmation, { RENTALCHARGE => '1.00' }, 'Item needs rentalcharge confirmation to be issued' );
5359     $itemtype->rentalcharge('0')->store;
5360     $itemtype->rentalcharge_daily(1)->store;
5361     ( $issuingimpossible, $needsconfirmation ) = CanBookBeIssued( $patron, $item->barcode, $dt_due, undef, undef, undef );
5362     is_deeply( $needsconfirmation, { RENTALCHARGE => '3' }, 'Item needs rentalcharge confirmation to be issued, increment' );
5363     $itemtype->rentalcharge_daily('0')->store;
5364 };
5365
5366 subtest 'CanBookBeIssued & CircConfirmItemParts' => sub {
5367     plan tests => 1;
5368
5369     t::lib::Mocks::mock_preference('CircConfirmItemParts', 1);
5370
5371     my $patron = $builder->build_object(
5372         {
5373             class => 'Koha::Patrons',
5374             value => { categorycode => $patron_category->{categorycode} }
5375         }
5376     )->store;
5377
5378     my $item = $builder->build_sample_item(
5379         {
5380             materials => 'includes DVD',
5381         }
5382     )->store;
5383
5384     my $dt_due = dt_from_string->add( days => 3 );
5385
5386     my ( $issuingimpossible, $needsconfirmation ) = CanBookBeIssued( $patron, $item->barcode, $dt_due, undef, undef, undef );
5387     is_deeply( $needsconfirmation, { ADDITIONAL_MATERIALS => 'includes DVD' }, 'Item needs confirmation of additional parts' );
5388 };
5389
5390 subtest 'Do not return on renewal (LOST charge)' => sub {
5391     plan tests => 1;
5392
5393     t::lib::Mocks::mock_preference('MarkLostItemsAsReturned', 'onpayment');
5394     my $library = $builder->build_object( { class => "Koha::Libraries" } );
5395     my $manager = $builder->build_object( { class => "Koha::Patrons" } );
5396     t::lib::Mocks::mock_userenv({ patron => $manager,branchcode => $manager->branchcode });
5397
5398     my $biblio = $builder->build_sample_biblio;
5399
5400     my $item = $builder->build_sample_item(
5401         {
5402             biblionumber     => $biblio->biblionumber,
5403             library          => $library->branchcode,
5404             replacementprice => 99.00,
5405             itype            => $itemtype,
5406         }
5407     );
5408
5409     my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
5410     AddIssue( $patron, $item->barcode );
5411
5412     my $accountline = Koha::Account::Line->new(
5413         {
5414             borrowernumber    => $patron->borrowernumber,
5415             debit_type_code   => 'LOST',
5416             status            => undef,
5417             itemnumber        => $item->itemnumber,
5418             amount            => 12,
5419             amountoutstanding => 12,
5420             interface         => 'something',
5421         }
5422     )->store();
5423
5424     # AddRenewal doesn't call _FixAccountForLostAndFound
5425     AddIssue( $patron, $item->barcode );
5426
5427     is( $patron->checkouts->count, 1,
5428         'Renewal should not return the item even if a LOST payment has been made earlier'
5429     );
5430 };
5431
5432 subtest 'Filling a hold should cancel existing transfer' => sub {
5433     plan tests => 4;
5434
5435     t::lib::Mocks::mock_preference('AutomaticItemReturn', 1);
5436
5437     my $libraryA = $builder->build_object( { class => 'Koha::Libraries' } );
5438     my $libraryB = $builder->build_object( { class => 'Koha::Libraries' } );
5439     my $patron = $builder->build_object(
5440         {
5441             class => 'Koha::Patrons',
5442             value => {
5443                 categorycode => $patron_category->{categorycode},
5444                 branchcode => $libraryA->branchcode,
5445             }
5446         }
5447     )->store;
5448
5449     my $item = $builder->build_sample_item({
5450         homebranch => $libraryB->branchcode,
5451     });
5452
5453     my ( undef, $message ) = AddReturn( $item->barcode, $libraryA->branchcode, undef, undef );
5454     is( Koha::Item::Transfers->search({ itemnumber => $item->itemnumber, datearrived => undef })->count, 1, "We generate a transfer on checkin");
5455     AddReserve({
5456         branchcode     => $libraryA->branchcode,
5457         borrowernumber => $patron->borrowernumber,
5458         biblionumber   => $item->biblionumber,
5459         itemnumber     => $item->itemnumber
5460     });
5461     my $reserves = Koha::Holds->search({ itemnumber => $item->itemnumber });
5462     is( $reserves->count, 1, "Reserve is placed");
5463     ( undef, $message ) = AddReturn( $item->barcode, $libraryA->branchcode, undef, undef );
5464     my $reserve = $reserves->next;
5465     ModReserveAffect( $item->itemnumber, $patron->borrowernumber, 0, $reserve->reserve_id );
5466     $reserve->discard_changes;
5467     ok( $reserve->found eq 'W', "Reserve is marked waiting" );
5468     is( Koha::Item::Transfers->search({ itemnumber => $item->itemnumber, datearrived => undef })->count, 0, "No outstanding transfers when hold is waiting");
5469 };
5470
5471 subtest 'Tests for NoRefundOnLostReturnedItemsAge with AddReturn' => sub {
5472
5473     plan tests => 4;
5474
5475     t::lib::Mocks::mock_preference('BlockReturnOfLostItems', 0);
5476     my $library = $builder->build_object( { class => 'Koha::Libraries' } );
5477     my $patron  = $builder->build_object(
5478         {
5479             class => 'Koha::Patrons',
5480             value => { categorycode => $patron_category->{categorycode} }
5481         }
5482     );
5483
5484     my $biblionumber = $builder->build_sample_biblio(
5485         {
5486             branchcode => $library->branchcode,
5487         }
5488     )->biblionumber;
5489
5490     # And the circulation rule
5491     Koha::CirculationRules->search->delete;
5492     Koha::CirculationRules->set_rules(
5493         {
5494             categorycode => undef,
5495             itemtype     => undef,
5496             branchcode   => undef,
5497             rules        => {
5498                 issuelength => 14,
5499                 lengthunit  => 'days',
5500             }
5501         }
5502     );
5503     $builder->build(
5504         {
5505             source => 'CirculationRule',
5506             value  => {
5507                 branchcode   => undef,
5508                 categorycode => undef,
5509                 itemtype     => undef,
5510                 rule_name    => 'lostreturn',
5511                 rule_value   => 'refund'
5512             }
5513         }
5514     );
5515
5516     subtest 'NoRefundOnLostReturnedItemsAge = undef' => sub {
5517         plan tests => 3;
5518
5519         t::lib::Mocks::mock_preference( 'WhenLostChargeReplacementFee',   1 );
5520         t::lib::Mocks::mock_preference( 'NoRefundOnLostReturnedItemsAge', undef );
5521
5522         my $lost_on = dt_from_string->subtract( days => 7 )->date;
5523
5524         my $item = $builder->build_sample_item(
5525             {
5526                 biblionumber     => $biblionumber,
5527                 library          => $library->branchcode,
5528                 replacementprice => '42',
5529             }
5530         );
5531         my $issue = AddIssue( $patron, $item->barcode );
5532         LostItem( $item->itemnumber, 'cli', 0 );
5533         $item->_result->itemlost(1);
5534         $item->_result->itemlost_on( $lost_on );
5535         $item->_result->update();
5536
5537         my $a = Koha::Account::Lines->search(
5538             {
5539                 itemnumber     => $item->id,
5540                 borrowernumber => $patron->borrowernumber
5541             }
5542         )->next;
5543         ok( $a, "Found accountline for lost fee" );
5544         is( $a->amountoutstanding + 0, 42, "Lost fee charged correctly" );
5545         my ( $doreturn, $messages ) = AddReturn( $item->barcode, $library->branchcode, undef, dt_from_string );
5546         $a = $a->get_from_storage;
5547         is( $a->amountoutstanding + 0, 0, "Lost fee was refunded" );
5548         $a->delete;
5549     };
5550
5551     subtest 'NoRefundOnLostReturnedItemsAge > length of days item has been lost' => sub {
5552         plan tests => 3;
5553
5554         t::lib::Mocks::mock_preference( 'WhenLostChargeReplacementFee',   1 );
5555         t::lib::Mocks::mock_preference( 'NoRefundOnLostReturnedItemsAge', 7 );
5556
5557         my $lost_on = dt_from_string->subtract( days => 6 )->date;
5558
5559         my $item = $builder->build_sample_item(
5560             {
5561                 biblionumber     => $biblionumber,
5562                 library          => $library->branchcode,
5563                 replacementprice => '42',
5564             }
5565         );
5566         my $issue = AddIssue( $patron, $item->barcode );
5567         LostItem( $item->itemnumber, 'cli', 0 );
5568         $item->_result->itemlost(1);
5569         $item->_result->itemlost_on( $lost_on );
5570         $item->_result->update();
5571
5572         my $a = Koha::Account::Lines->search(
5573             {
5574                 itemnumber     => $item->id,
5575                 borrowernumber => $patron->borrowernumber
5576             }
5577         )->next;
5578         ok( $a, "Found accountline for lost fee" );
5579         is( $a->amountoutstanding + 0, 42, "Lost fee charged correctly" );
5580         my ( $doreturn, $messages ) = AddReturn( $item->barcode, $library->branchcode, undef, dt_from_string );
5581         $a = $a->get_from_storage;
5582         is( $a->amountoutstanding + 0, 0, "Lost fee was refunded" );
5583         $a->delete;
5584     };
5585
5586     subtest 'NoRefundOnLostReturnedItemsAge = length of days item has been lost' => sub {
5587         plan tests => 3;
5588
5589         t::lib::Mocks::mock_preference( 'WhenLostChargeReplacementFee',   1 );
5590         t::lib::Mocks::mock_preference( 'NoRefundOnLostReturnedItemsAge', 7 );
5591
5592         my $lost_on = dt_from_string->subtract( days => 7 )->date;
5593
5594         my $item = $builder->build_sample_item(
5595             {
5596                 biblionumber     => $biblionumber,
5597                 library          => $library->branchcode,
5598                 replacementprice => '42',
5599             }
5600         );
5601         my $issue = AddIssue( $patron, $item->barcode );
5602         LostItem( $item->itemnumber, 'cli', 0 );
5603         $item->_result->itemlost(1);
5604         $item->_result->itemlost_on( $lost_on );
5605         $item->_result->update();
5606
5607         my $a = Koha::Account::Lines->search(
5608             {
5609                 itemnumber     => $item->id,
5610                 borrowernumber => $patron->borrowernumber
5611             }
5612         )->next;
5613         ok( $a, "Found accountline for lost fee" );
5614         is( $a->amountoutstanding + 0, 42, "Lost fee charged correctly" );
5615         my ( $doreturn, $messages ) = AddReturn( $item->barcode, $library->branchcode, undef, dt_from_string );
5616         $a = $a->get_from_storage;
5617         is( $a->amountoutstanding + 0, 42, "Lost fee was not refunded" );
5618         $a->delete;
5619     };
5620
5621     subtest 'NoRefundOnLostReturnedItemsAge < length of days item has been lost' => sub {
5622         plan tests => 3;
5623
5624         t::lib::Mocks::mock_preference( 'WhenLostChargeReplacementFee',   1 );
5625         t::lib::Mocks::mock_preference( 'NoRefundOnLostReturnedItemsAge', 7 );
5626
5627         my $lost_on = dt_from_string->subtract( days => 8 )->date;
5628
5629         my $item = $builder->build_sample_item(
5630             {
5631                 biblionumber     => $biblionumber,
5632                 library          => $library->branchcode,
5633                 replacementprice => '42',
5634             }
5635         );
5636         my $issue = AddIssue( $patron, $item->barcode );
5637         LostItem( $item->itemnumber, 'cli', 0 );
5638         $item->_result->itemlost(1);
5639         $item->_result->itemlost_on( $lost_on );
5640         $item->_result->update();
5641
5642         my $a = Koha::Account::Lines->search(
5643             {
5644                 itemnumber     => $item->id,
5645                 borrowernumber => $patron->borrowernumber
5646             }
5647         );
5648         $a = $a->next;
5649         ok( $a, "Found accountline for lost fee" );
5650         is( $a->amountoutstanding + 0, 42, "Lost fee charged correctly" );
5651         my ( $doreturn, $messages ) = AddReturn( $item->barcode, $library->branchcode, undef, dt_from_string );
5652         $a = $a->get_from_storage;
5653         is( $a->amountoutstanding + 0, 42, "Lost fee was not refunded" );
5654         $a->delete;
5655     };
5656 };
5657
5658 subtest 'Tests for NoRefundOnLostReturnedItemsAge with AddIssue' => sub {
5659
5660     plan tests => 4;
5661
5662     t::lib::Mocks::mock_preference('BlockReturnOfLostItems', 0);
5663     my $library = $builder->build_object( { class => 'Koha::Libraries' } );
5664     my $patron  = $builder->build_object(
5665         {
5666             class => 'Koha::Patrons',
5667             value => { categorycode => $patron_category->{categorycode} }
5668         }
5669     );
5670     my $patron2  = $builder->build_object(
5671         {
5672             class => 'Koha::Patrons',
5673             value => { categorycode => $patron_category->{categorycode} }
5674         }
5675     );
5676
5677     my $biblionumber = $builder->build_sample_biblio(
5678         {
5679             branchcode => $library->branchcode,
5680         }
5681     )->biblionumber;
5682
5683     # And the circulation rule
5684     Koha::CirculationRules->search->delete;
5685     Koha::CirculationRules->set_rules(
5686         {
5687             categorycode => undef,
5688             itemtype     => undef,
5689             branchcode   => undef,
5690             rules        => {
5691                 issuelength => 14,
5692                 lengthunit  => 'days',
5693             }
5694         }
5695     );
5696     $builder->build(
5697         {
5698             source => 'CirculationRule',
5699             value  => {
5700                 branchcode   => undef,
5701                 categorycode => undef,
5702                 itemtype     => undef,
5703                 rule_name    => 'lostreturn',
5704                 rule_value   => 'refund'
5705             }
5706         }
5707     );
5708
5709     subtest 'NoRefundOnLostReturnedItemsAge = undef' => sub {
5710         plan tests => 3;
5711
5712         t::lib::Mocks::mock_preference( 'WhenLostChargeReplacementFee',   1 );
5713         t::lib::Mocks::mock_preference( 'NoRefundOnLostReturnedItemsAge', undef );
5714
5715         my $lost_on = dt_from_string->subtract( days => 7 )->date;
5716
5717         my $item = $builder->build_sample_item(
5718             {
5719                 biblionumber     => $biblionumber,
5720                 library          => $library->branchcode,
5721                 replacementprice => '42',
5722             }
5723         );
5724         my $issue = AddIssue( $patron, $item->barcode );
5725         LostItem( $item->itemnumber, 'cli', 0 );
5726         $item->_result->itemlost(1);
5727         $item->_result->itemlost_on( $lost_on );
5728         $item->_result->update();
5729
5730         my $a = Koha::Account::Lines->search(
5731             {
5732                 itemnumber     => $item->id,
5733                 borrowernumber => $patron->borrowernumber
5734             }
5735         )->next;
5736         ok( $a, "Found accountline for lost fee" );
5737         is( $a->amountoutstanding + 0, 42, "Lost fee charged correctly" );
5738         $issue = AddIssue( $patron2, $item->barcode );
5739         $a = $a->get_from_storage;
5740         is( $a->amountoutstanding + 0, 0, "Lost fee was refunded" );
5741         $a->delete;
5742         $issue->delete;
5743     };
5744
5745     subtest 'NoRefundOnLostReturnedItemsAge > length of days item has been lost' => sub {
5746         plan tests => 3;
5747
5748         t::lib::Mocks::mock_preference( 'WhenLostChargeReplacementFee',   1 );
5749         t::lib::Mocks::mock_preference( 'NoRefundOnLostReturnedItemsAge', 7 );
5750
5751         my $lost_on = dt_from_string->subtract( days => 6 )->date;
5752
5753         my $item = $builder->build_sample_item(
5754             {
5755                 biblionumber     => $biblionumber,
5756                 library          => $library->branchcode,
5757                 replacementprice => '42',
5758             }
5759         );
5760         my $issue = AddIssue( $patron, $item->barcode );
5761         LostItem( $item->itemnumber, 'cli', 0 );
5762         $item->_result->itemlost(1);
5763         $item->_result->itemlost_on( $lost_on );
5764         $item->_result->update();
5765
5766         my $a = Koha::Account::Lines->search(
5767             {
5768                 itemnumber     => $item->id,
5769                 borrowernumber => $patron->borrowernumber
5770             }
5771         )->next;
5772         ok( $a, "Found accountline for lost fee" );
5773         is( $a->amountoutstanding + 0, 42, "Lost fee charged correctly" );
5774         $issue = AddIssue( $patron2, $item->barcode );
5775         $a = $a->get_from_storage;
5776         is( $a->amountoutstanding + 0, 0, "Lost fee was refunded" );
5777         $a->delete;
5778     };
5779
5780     subtest 'NoRefundOnLostReturnedItemsAge = length of days item has been lost' => sub {
5781         plan tests => 3;
5782
5783         t::lib::Mocks::mock_preference( 'WhenLostChargeReplacementFee',   1 );
5784         t::lib::Mocks::mock_preference( 'NoRefundOnLostReturnedItemsAge', 7 );
5785
5786         my $lost_on = dt_from_string->subtract( days => 7 )->date;
5787
5788         my $item = $builder->build_sample_item(
5789             {
5790                 biblionumber     => $biblionumber,
5791                 library          => $library->branchcode,
5792                 replacementprice => '42',
5793             }
5794         );
5795         my $issue = AddIssue( $patron, $item->barcode );
5796         LostItem( $item->itemnumber, 'cli', 0 );
5797         $item->_result->itemlost(1);
5798         $item->_result->itemlost_on( $lost_on );
5799         $item->_result->update();
5800
5801         my $a = Koha::Account::Lines->search(
5802             {
5803                 itemnumber     => $item->id,
5804                 borrowernumber => $patron->borrowernumber
5805             }
5806         )->next;
5807         ok( $a, "Found accountline for lost fee" );
5808         is( $a->amountoutstanding + 0, 42, "Lost fee charged correctly" );
5809         $issue = AddIssue( $patron2, $item->barcode );
5810         $a = $a->get_from_storage;
5811         is( $a->amountoutstanding + 0, 42, "Lost fee was not refunded" );
5812         $a->delete;
5813     };
5814
5815     subtest 'NoRefundOnLostReturnedItemsAge < length of days item has been lost' => sub {
5816         plan tests => 3;
5817
5818         t::lib::Mocks::mock_preference( 'WhenLostChargeReplacementFee',   1 );
5819         t::lib::Mocks::mock_preference( 'NoRefundOnLostReturnedItemsAge', 7 );
5820
5821         my $lost_on = dt_from_string->subtract( days => 8 )->date;
5822
5823         my $item = $builder->build_sample_item(
5824             {
5825                 biblionumber     => $biblionumber,
5826                 library          => $library->branchcode,
5827                 replacementprice => '42',
5828             }
5829         );
5830         my $issue = AddIssue( $patron, $item->barcode );
5831         LostItem( $item->itemnumber, 'cli', 0 );
5832         $item->_result->itemlost(1);
5833         $item->_result->itemlost_on( $lost_on );
5834         $item->_result->update();
5835
5836         my $a = Koha::Account::Lines->search(
5837             {
5838                 itemnumber     => $item->id,
5839                 borrowernumber => $patron->borrowernumber
5840             }
5841         );
5842         $a = $a->next;
5843         ok( $a, "Found accountline for lost fee" );
5844         is( $a->amountoutstanding + 0, 42, "Lost fee charged correctly" );
5845         $issue = AddIssue( $patron2, $item->barcode );
5846         $a = $a->get_from_storage;
5847         is( $a->amountoutstanding + 0, 42, "Lost fee was not refunded" );
5848         $a->delete;
5849     };
5850 };
5851
5852 subtest 'transferbook tests' => sub {
5853     plan tests => 9;
5854
5855     throws_ok
5856     { C4::Circulation::transferbook({}); }
5857     'Koha::Exceptions::MissingParameter',
5858     'Koha::Patron->store raises an exception on missing params';
5859
5860     throws_ok
5861     { C4::Circulation::transferbook({to_branch=>'anything'}); }
5862     'Koha::Exceptions::MissingParameter',
5863     'Koha::Patron->store raises an exception on missing params';
5864
5865     throws_ok
5866     { C4::Circulation::transferbook({from_branch=>'anything'}); }
5867     'Koha::Exceptions::MissingParameter',
5868     'Koha::Patron->store raises an exception on missing params';
5869
5870     my ($doreturn,$messages) = C4::Circulation::transferbook({to_branch=>'there',from_branch=>'here'});
5871     is( $doreturn, 0, "No return without barcode");
5872     ok( exists $messages->{BadBarcode}, "We get a BadBarcode message if no barcode passed");
5873     is( $messages->{BadBarcode}, undef, "No barcode passed means undef BadBarcode" );
5874
5875     ($doreturn,$messages) = C4::Circulation::transferbook({to_branch=>'there',from_branch=>'here',barcode=>'BadBarcode'});
5876     is( $doreturn, 0, "No return without barcode");
5877     ok( exists $messages->{BadBarcode}, "We get a BadBarcode message if no barcode passed");
5878     is( $messages->{BadBarcode}, 'BadBarcode', "No barcode passed means undef BadBarcode" );
5879
5880 };
5881
5882 subtest 'Checkout should correctly terminate a transfer' => sub {
5883     plan tests => 7;
5884
5885     my $library_1 = $builder->build_object( { class => 'Koha::Libraries' } );
5886     my $patron_1 = $builder->build_object(
5887         {
5888             class => 'Koha::Patrons',
5889             value => { branchcode => $library_1->branchcode }
5890         }
5891     );
5892     my $library_2 = $builder->build_object( { class => 'Koha::Libraries' } );
5893     my $patron_2 = $builder->build_object(
5894         {
5895             class => 'Koha::Patrons',
5896             value => { branchcode => $library_2->branchcode }
5897         }
5898     );
5899
5900     my $item = $builder->build_sample_item(
5901         {
5902             library => $library_1->branchcode,
5903         }
5904     );
5905
5906     t::lib::Mocks::mock_userenv( { branchcode => $library_1->branchcode } );
5907     my $reserve_id = AddReserve(
5908         {
5909             branchcode     => $library_2->branchcode,
5910             borrowernumber => $patron_2->borrowernumber,
5911             biblionumber   => $item->biblionumber,
5912             itemnumber     => $item->itemnumber,
5913             priority       => 1,
5914         }
5915     );
5916
5917     my $do_transfer = 1;
5918     ModItemTransfer( $item->itemnumber, $library_1->branchcode,
5919         $library_2->branchcode, 'Reserve' );
5920     ModReserveAffect( $item->itemnumber, undef, $do_transfer, $reserve_id );
5921     my $hold = Koha::Holds->find($reserve_id);
5922     is( $hold->found, 'T', 'Hold is in transit' );
5923     my $transfer = $item->get_transfer;
5924     is( $transfer->frombranch, $library_1->branchcode );
5925     is( $transfer->tobranch,   $library_2->branchcode );
5926     is( $transfer->reason,     'Reserve' );
5927
5928     t::lib::Mocks::mock_userenv( { branchcode => $library_2->branchcode } );
5929     AddIssue( $patron_1, $item->barcode );
5930     $transfer = $transfer->get_from_storage;
5931     isnt( $transfer->datearrived, undef );
5932     $hold = $hold->get_from_storage;
5933     is( $hold->found, undef, 'Hold is not waiting or transit' );
5934     is( $hold->priority, 1, );
5935 };
5936
5937 subtest 'AddIssue records staff who checked out item if appropriate' => sub  {
5938     plan tests => 2;
5939
5940     $module->mock( 'userenv', sub { { branch => $library->{id} } } );
5941
5942     my $library = $builder->build_object( { class => 'Koha::Libraries' } );
5943     my $patron = $builder->build_object(
5944         {
5945             class => 'Koha::Patrons',
5946             value => { categorycode => $patron_category->{categorycode} }
5947         }
5948     );
5949     my $issuer = $builder->build_object(
5950         {
5951             class => 'Koha::Patrons',
5952             value => { categorycode => $patron_category->{categorycode} }
5953         }
5954     );
5955     my $item_1 = $builder->build_sample_item(
5956         {
5957             library  => $library->{branchcode}
5958         }
5959     );
5960
5961     my $item_2 = $builder->build_sample_item(
5962         {
5963             library  => $library->branchcode
5964         }
5965     );
5966
5967     $module->mock( 'userenv', sub { { branch => $library->id, number => $issuer->borrowernumber } } );
5968
5969     my $dt_from = dt_from_string();
5970     my $dt_to   = dt_from_string()->add( days => 7 );
5971
5972     my $issue_1 = AddIssue( $patron, $item_1->barcode, $dt_to, undef, $dt_from );
5973
5974     is( $issue_1->issuer, undef, "Staff who checked out the item not recorded when RecordStaffUserOnCheckout turned off" );
5975
5976     t::lib::Mocks::mock_preference('RecordStaffUserOnCheckout', 1);
5977
5978     my $issue_2 =
5979       AddIssue( $patron, $item_2->barcode, $dt_to, undef, $dt_from );
5980
5981     is( $issue_2->issuer->borrowernumber, $issuer->borrowernumber, "Staff who checked out the item recorded when RecordStaffUserOnCheckout turned on" );
5982 };
5983
5984 subtest "Item's onloan value should be set if checked out item is checked out to a different patron" => sub {
5985     plan tests => 2;
5986
5987     my $library_1 = $builder->build_object( { class => 'Koha::Libraries' } );
5988     my $patron_1 = $builder->build_object(
5989         {
5990             class => 'Koha::Patrons',
5991             value => { branchcode => $library_1->branchcode }
5992         }
5993     );
5994     my $patron_2 = $builder->build_object(
5995         {
5996             class => 'Koha::Patrons',
5997             value => { branchcode => $library_1->branchcode }
5998         }
5999     );
6000
6001     my $item = $builder->build_sample_item(
6002         {
6003             library => $library_1->branchcode,
6004         }
6005     );
6006
6007     AddIssue( $patron_1, $item->barcode );
6008     ok( $item->get_from_storage->onloan, "Item's onloan column is set after initial checkout" );
6009     AddIssue( $patron_2, $item->barcode );
6010     ok( $item->get_from_storage->onloan, "Item's onloan column is set after second checkout" );
6011 };
6012
6013 subtest "updateWrongTransfer tests" => sub {
6014     plan tests => 5;
6015
6016     my $library1 = $builder->build_object( { class => 'Koha::Libraries' } );
6017     my $library2 = $builder->build_object( { class => 'Koha::Libraries' } );
6018     my $library3 = $builder->build_object( { class => 'Koha::Libraries' } );
6019     my $item     = $builder->build_sample_item(
6020         {
6021             homebranch    => $library1->branchcode,
6022             holdingbranch => $library2->branchcode,
6023             datelastseen  => undef
6024         }
6025     );
6026
6027     my $transfer = $builder->build_object(
6028         {
6029             class => 'Koha::Item::Transfers',
6030             value => {
6031                 itemnumber    => $item->itemnumber,
6032                 frombranch    => $library2->branchcode,
6033                 tobranch      => $library1->branchcode,
6034                 daterequested => dt_from_string,
6035                 datesent      => dt_from_string,
6036                 datecancelled => undef,
6037                 datearrived   => undef,
6038                 reason        => 'Manual'
6039             }
6040         }
6041     );
6042     is( ref($transfer), 'Koha::Item::Transfer', 'Mock transfer added' );
6043
6044     my $new_transfer = C4::Circulation::updateWrongTransfer($item->itemnumber, $library1->branchcode);
6045     is(ref($new_transfer), 'Koha::Item::Transfer', "updateWrongTransfer returns a 'Koha::Item::Transfer' object");
6046     ok( !$new_transfer->in_transit, "New transfer is NOT created as in transit (or cancelled)");
6047
6048     my $original_transfer = $transfer->get_from_storage;
6049     ok( defined($original_transfer->datecancelled), "Original transfer was cancelled");
6050     is( $original_transfer->cancellation_reason, 'WrongTransfer', "Original transfer cancellation reason is 'WrongTransfer'");
6051 };
6052
6053 subtest "SendCirculationAlert" => sub {
6054     plan tests => 3;
6055
6056     # When you would unsuspectingly call this unit test (with perl, not prove), you will be bitten by LOCK.
6057     # LOCK will commit changes and ruin your data
6058     # In order to prevent that, we will add KOHA_TESTING to $ENV; see further Circulation.pm
6059     $ENV{KOHA_TESTING} = 1;
6060
6061     # Setup branch, borrowr, and notice
6062     my $library = $builder->build_object({ class => 'Koha::Libraries' });
6063     set_userenv( $library->unblessed);
6064     my $patron = $builder->build_object({ class => 'Koha::Patrons' });
6065     C4::Members::Messaging::SetMessagingPreference({
6066         borrowernumber => $patron->id,
6067         message_transport_types => ['sms'],
6068         message_attribute_id => 5
6069     });
6070     my $item = $builder->build_sample_item();
6071     my $checkin_notice = $builder->build_object({
6072         class => 'Koha::Notice::Templates',
6073         value =>{
6074             module => 'circulation',
6075             code => 'CHECKIN',
6076             branchcode => $library->branchcode,
6077             name => 'Test Checkin',
6078             is_html => 0,
6079             content => "Checkins:\n----\n[% biblio.title %]-[% old_checkout.issue_id %]\n----Thank you.",
6080             message_transport_type => 'sms',
6081             lang => 'default'
6082         }
6083     })->store;
6084
6085     # Checkout an item, mark it returned, generate a notice
6086     my $issue_1 = AddIssue( $patron, $item->barcode);
6087     MarkIssueReturned( $patron->borrowernumber, $item->itemnumber, undef, 0, { skip_record_index => 1} );
6088     C4::Circulation::SendCirculationAlert({
6089         type => 'CHECKIN',
6090         item => $item->unblessed,
6091         borrower => $patron->unblessed,
6092         branch => $library->branchcode,
6093         issue => $issue_1
6094     });
6095     my $notice = Koha::Notice::Messages->find({ borrowernumber => $patron->id, letter_code => 'CHECKIN' });
6096     is($notice->content,"Checkins:\n".$item->biblio->title."-".$issue_1->id."\nThank you.", 'Letter generated with expected output on first checkin' );
6097     is($notice->to_address, $patron->smsalertnumber, "Letter has the correct to_address set to smsalertnumber for SMS type notices");
6098
6099     # Checkout an item, mark it returned, generate a notice
6100     my $issue_2 = AddIssue( $patron, $item->barcode);
6101     MarkIssueReturned( $patron->borrowernumber, $item->itemnumber, undef, 0, { skip_record_index => 1} );
6102     C4::Circulation::SendCirculationAlert({
6103         type => 'CHECKIN',
6104         item => $item->unblessed,
6105         borrower => $patron->unblessed,
6106         branch => $library->branchcode,
6107         issue => $issue_2
6108     });
6109     $notice->discard_changes();
6110     is($notice->content,"Checkins:\n".$item->biblio->title."-".$issue_1->id."\n".$item->biblio->title."-".$issue_2->id."\nThank you.", 'Letter appended with expected output on second checkin' );
6111
6112 };
6113
6114 subtest "GetSoonestRenewDate tests" => sub {
6115     plan tests => 6;
6116     Koha::CirculationRules->set_rule(
6117         {
6118             categorycode => undef,
6119             branchcode   => undef,
6120             itemtype     => undef,
6121             rule_name    => 'norenewalbefore',
6122             rule_value   => '7',
6123         }
6124     );
6125     my $patron = $builder->build_object(
6126         {
6127             class => 'Koha::Patrons',
6128             value => {
6129                 autorenew_checkouts => 1,
6130             }
6131         }
6132     );
6133     my $item = $builder->build_sample_item();
6134     my $issue = AddIssue( $patron, $item->barcode);
6135     my $datedue = dt_from_string( $issue->date_due() );
6136
6137     # Bug 14395
6138     # Test 'exact time' setting for syspref NoRenewalBeforePrecision
6139     t::lib::Mocks::mock_preference( 'NoRenewalBeforePrecision', 'exact_time' );
6140     is(
6141         GetSoonestRenewDate( $patron, $issue ),
6142         $datedue->clone->add( days => -7 ),
6143         'Bug 14395: Renewals permitted 7 days before due date, as expected'
6144     );
6145
6146     # Bug 14395
6147     # Test 'date' setting for syspref NoRenewalBeforePrecision
6148     t::lib::Mocks::mock_preference( 'NoRenewalBeforePrecision', 'date' );
6149     is(
6150         GetSoonestRenewDate( $patron, $issue ),
6151         $datedue->clone->add( days => -7 )->truncate( to => 'day' ),
6152         'Bug 14395: Renewals permitted 7 days before due date, as expected'
6153     );
6154
6155
6156     Koha::CirculationRules->set_rule(
6157         {
6158             categorycode => undef,
6159             branchcode   => undef,
6160             itemtype     => undef,
6161             rule_name    => 'norenewalbefore',
6162             rule_value   => undef,
6163         }
6164     );
6165
6166     is(
6167         GetSoonestRenewDate( $patron, $issue ),
6168         dt_from_string,
6169         'Checkouts without auto-renewal can be renewed immediately if no norenewalbefore'
6170     );
6171
6172     t::lib::Mocks::mock_preference( 'NoRenewalBeforePrecision', 'date' );
6173     $issue->auto_renew(1)->store;
6174     is(
6175         GetSoonestRenewDate( $patron, $issue, 1 ),
6176         $datedue->clone->truncate( to => 'day' ),
6177         'Checkouts with auto-renewal can be renewed earliest on due date if noautorenewalbefore'
6178     );
6179     t::lib::Mocks::mock_preference( 'NoRenewalBeforePrecision', 'exact' );
6180     is(
6181         GetSoonestRenewDate( $patron, $issue, 1 ),
6182         $datedue,
6183         'Checkouts with auto-renewal can be renewed earliest on due date if noautorenewalbefore'
6184     );
6185
6186     t::lib::Mocks::mock_preference( 'NoRenewalBeforePrecision', 'date' );
6187     Koha::CirculationRules->set_rule(
6188         {
6189             categorycode => undef,
6190             branchcode   => undef,
6191             itemtype     => undef,
6192             rule_name    => 'norenewalbefore',
6193             rule_value   => 1,
6194         }
6195     );
6196     $issue->date_due(dt_from_string)->store;
6197     is(
6198         GetSoonestRenewDate( $patron, $issue ),
6199         dt_from_string->subtract( days => 1 )->truncate( to => 'day' ),
6200         'Checkouts with auto-renewal can be renewed 1 day before due date if no renewalbefore = 1 and precision = "date"'
6201     );
6202
6203 };
6204
6205 subtest "CanBookBeIssued + needsconfirmation message" => sub {
6206     plan tests => 4;
6207
6208     my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
6209     my $library = $builder->build_object( { class => 'Koha::Libraries' } );
6210     my $biblio = $builder->build_object({ class => 'Koha::Biblios' });
6211     my $biblioitem = $builder->build_object({ class => 'Koha::Biblioitems', value => { biblionumber => $biblio->biblionumber }});
6212     my $item = $builder->build_object({ class => 'Koha::Items' , value => { itype => $itemtype, biblionumber => $biblio->biblionumber }});
6213
6214     my $hold = $builder->build_object({ class => 'Koha::Holds', value => {
6215         biblionumber => $item->biblionumber,
6216         branchcode => $library->branchcode,
6217         itemnumber => undef,
6218         itemtype => undef,
6219         priority => 1,
6220         found => undef,
6221         suspend => 0,
6222         item_group_id => undef
6223     }});
6224
6225     my ( $error, $needsconfirmation, $alerts, $messages );
6226
6227     ( $error, $needsconfirmation, $alerts, $messages ) = CanBookBeIssued( $patron, $item->barcode );
6228     is($needsconfirmation->{resbranchcode}, $hold->branchcode, "Branchcodes match when hold exists.");
6229
6230     $hold->priority(0)->store();
6231
6232     $hold->found("W")->store();
6233     ( $error, $needsconfirmation, $alerts, $messages ) = CanBookBeIssued( $patron, $item->barcode );
6234     is($needsconfirmation->{resbranchcode}, $hold->branchcode, "Branchcodes match when hold is waiting.");
6235
6236     $hold->found("T")->store();
6237     ( $error, $needsconfirmation, $alerts, $messages ) = CanBookBeIssued( $patron, $item->barcode );
6238     is($needsconfirmation->{resbranchcode}, $hold->branchcode, "Branchcodes match when hold is being transferred.");
6239
6240     $hold->found("P")->store();
6241     ( $error, $needsconfirmation, $alerts, $messages ) = CanBookBeIssued( $patron, $item->barcode );
6242     is($needsconfirmation->{resbranchcode}, $hold->branchcode, "Branchcodes match when hold is being processed.");
6243 };
6244
6245 subtest 'Tests for BlockReturnOfWithdrawnItems' => sub {
6246
6247     plan tests => 1;
6248
6249     t::lib::Mocks::mock_preference('BlockReturnOfWithdrawnItems', 1);
6250     t::lib::Mocks::mock_preference('RecordLocalUseOnReturn', 0);
6251     my $item = $builder->build_sample_item();
6252     $item->withdrawn(1)->itemlost(1)->store;
6253     my @return = AddReturn( $item->barcode, $item->homebranch, 0, undef );
6254     is_deeply(
6255         \@return,
6256         [ 0, { NotIssued => $item->barcode, withdrawn => 1 }, undef, {} ], "Item returned as withdrawn, no other messages");
6257 };
6258
6259 subtest 'Tests for transfer not in transit' => sub {
6260
6261     plan tests => 2;
6262
6263
6264     # These tests are to ensure a 'pending' transfer, generated by
6265     # stock rotation, will be advanced when checked in
6266
6267     my $item = $builder->build_sample_item();
6268     my $transfer = $builder->build_object({ class => 'Koha::Item::Transfers', value => {
6269         itemnumber => $item->id,
6270         reason => 'StockrotationRepatriation',
6271         datesent => undef,
6272         frombranch => $item->homebranch,
6273     }});
6274     my @return = AddReturn( $item->barcode, $item->homebranch, 0, undef );
6275     is_deeply(
6276         \@return,
6277         [ 0, { WasTransfered => $transfer->tobranch, TransferTrigger => 'StockrotationRepatriation', NotIssued => $item->barcode }, undef, {} ], "Item is reported to have been transferred");
6278
6279     $transfer->discard_changes;
6280     ok( $transfer->datesent, 'The datesent field is populated, i.e. transfer is initiated');
6281
6282 };
6283
6284 subtest 'Tests for RecordLocalUseOnReturn' => sub {
6285
6286     plan tests => 2;
6287
6288     t::lib::Mocks::mock_preference('RecordLocalUseOnReturn', 0);
6289     my $item = $builder->build_sample_item();
6290     $item->withdrawn(1)->itemlost(1)->store;
6291     my @return = AddReturn( $item->barcode, $item->homebranch, 0, undef );
6292     is_deeply(
6293         \@return,
6294         [ 0, { NotIssued => $item->barcode, withdrawn => 1  }, undef, {} ], "RecordLocalUSeOnReturn is off, no local use recorded");
6295
6296     t::lib::Mocks::mock_preference('RecordLocalUseOnReturn', 1);
6297     my @return2 = AddReturn( $item->barcode, $item->homebranch, 0, undef );
6298     is_deeply(
6299         \@return2,
6300         [ 0, { NotIssued => $item->barcode, withdrawn => 1, LocalUse => 1  }, undef, {} ], "Local use is recorded");
6301 };
6302
6303 subtest 'Test CanBookBeIssued param ignore_reserves (Bug 35322)' => sub {
6304     plan tests => 4;
6305
6306     my $homebranch    = $builder->build( { source => 'Branch' } );
6307     my $holdingbranch = $builder->build( { source => 'Branch' } );
6308     my $patron_1      = $builder->build_object(
6309         { class => 'Koha::Patrons', value => { categorycode => $patron_category->{categorycode} } } );
6310     my $patron_2 = $builder->build_object(
6311         { class => 'Koha::Patrons', value => { categorycode => $patron_category->{categorycode} } } );
6312
6313     my $item = $builder->build_sample_item(
6314         {
6315             homebranch    => $homebranch->{branchcode},
6316             holdingbranch => $holdingbranch->{branchcode},
6317         }
6318     );
6319
6320     Koha::CirculationRules->search()->delete();
6321     Koha::CirculationRules->set_rules(
6322         {
6323             categorycode => undef,
6324             itemtype     => undef,
6325             branchcode   => undef,
6326             rules        => {
6327                 reservesallowed => 25,
6328                 issuelength     => 14,
6329                 lengthunit      => 'days',
6330                 renewalsallowed => 1,
6331                 renewalperiod   => 7,
6332                 chargeperiod    => 1,
6333                 maxissueqty     => 20,
6334             }
6335         }
6336     );
6337
6338     my $reserve_id = AddReserve(
6339         {
6340             branchcode       => $homebranch,
6341             borrowernumber   => $patron_1->id,
6342             biblionumber     => $item->biblionumber,
6343             priority         => 1,
6344             reservation_date => dt_from_string,
6345             expiration_date  => dt_from_string,
6346             itemnumber       => $item->id,
6347             found            => 'W',
6348         }
6349     );
6350
6351     set_userenv($holdingbranch);
6352
6353     my ( $error, $question, $alerts ) = CanBookBeIssued( $patron_2, $item->barcode, undef, undef, 0 );
6354     is(
6355         keys(%$error) + keys(%$alerts), 0,
6356         'There should not be any errors or alerts (impossible)' . str( $error, $question, $alerts )
6357     );
6358     is( exists $question->{RESERVE_WAITING}, 1, 'RESERVE_WAITING is set' );
6359
6360     ( $error, $question, $alerts ) = CanBookBeIssued( $patron_2, $item->barcode, undef, undef, 1 );
6361     is(
6362         keys(%$error) + keys(%$alerts), 0,
6363         'There should not be any errors or alerts (impossible)' . str( $error, $question, $alerts )
6364     );
6365     isnt( exists $question->{RESERVE_WAITING}, 1, 'RESERVE_WAITING is not set' );
6366
6367 };
6368
6369
6370 $schema->storage->txn_rollback;
6371 C4::Context->clear_syspref_cache();
6372 $branches = Koha::Libraries->search();
6373 for my $branch ( $branches->next ) {
6374     my $key = $branch->branchcode . "_holidays";
6375     $cache->clear_from_cache($key);
6376 }