3 # This file is part of Koha.
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.
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.
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>.
21 use Test
::More tests
=> 51;
24 use Test
::Deep
qw( cmp_deeply );
30 use POSIX
qw( floor );
32 use t
::lib
::TestBuilder
;
41 use C4
::Overdues
qw(UpdateFine CalcFine);
45 use Koha
::Item
::Transfers
;
49 use Koha
::CirculationRules
;
50 use Koha
::Subscriptions
;
51 use Koha
::Account
::Lines
;
52 use Koha
::Account
::Offsets
;
57 t
::lib
::Mocks
::mock_userenv
({ branchcode
=> $library->{branchcode
} });
61 my ( $error, $question, $alert ) = @_;
63 $s = %$error ?
' (error: ' . join( ' ', keys %$error ) . ')' : '';
64 $s .= %$question ?
' (question: ' . join( ' ', keys %$question ) . ')' : '';
65 $s .= %$alert ?
' (alert: ' . join( ' ', keys %$alert ) . ')' : '';
69 sub test_debarment_on_checkout
{
71 my $item = $params->{item
};
72 my $library = $params->{library
};
73 my $patron = $params->{patron
};
74 my $due_date = $params->{due_date
} || dt_from_string
;
75 my $return_date = $params->{return_date
} || dt_from_string
;
76 my $expected_expiration_date = $params->{expiration_date
};
78 $expected_expiration_date = output_pref
(
80 dt
=> $expected_expiration_date,
86 my $line_number = $caller[2];
87 AddIssue
( $patron, $item->barcode, $due_date );
89 my ( undef, $message ) = AddReturn
( $item->barcode, $library->{branchcode
}, undef, $return_date );
90 is
( $message->{WasReturned
} && exists $message->{Debarred
}, 1, 'AddReturn must have debarred the patron' )
91 or diag
('AddReturn returned message ' . Dumper
$message );
92 my $debarments = Koha
::Patron
::Debarments
::GetDebarments
(
93 { borrowernumber
=> $patron->{borrowernumber
}, type
=> 'SUSPENSION' } );
94 is
( scalar(@
$debarments), 1, 'Test at line ' . $line_number );
96 is
( $debarments->[0]->{expiration
},
97 $expected_expiration_date, 'Test at line ' . $line_number );
98 Koha
::Patron
::Debarments
::DelUniqueDebarment
(
99 { borrowernumber
=> $patron->{borrowernumber
}, type
=> 'SUSPENSION' } );
102 my $schema = Koha
::Database
->schema;
103 $schema->storage->txn_begin;
104 my $builder = t
::lib
::TestBuilder
->new;
105 my $dbh = C4
::Context
->dbh;
107 # Prevent random failures by mocking ->now
108 my $now_value = dt_from_string
;
109 my $mocked_datetime = Test
::MockModule
->new('DateTime');
110 $mocked_datetime->mock( 'now', sub { return $now_value->clone; } );
112 my $cache = Koha
::Caches
->get_instance();
113 $dbh->do(q
|DELETE FROM special_holidays
|);
114 $dbh->do(q
|DELETE FROM repeatable_holidays
|);
115 my $branches = Koha
::Libraries
->search();
116 for my $branch ( $branches->next ) {
117 my $key = $branch->branchcode . "_holidays";
118 $cache->clear_from_cache($key);
121 # Start with a clean slate
122 $dbh->do('DELETE FROM issues');
123 $dbh->do('DELETE FROM borrowers');
125 # Disable recording of the staff who checked out an item until we're ready for it
126 t
::lib
::Mocks
::mock_preference
('RecordStaffUserOnCheckout', 0);
128 my $module = Test
::MockModule
->new('C4::Context');
130 my $library = $builder->build({
133 my $library2 = $builder->build({
136 my $itemtype = $builder->build(
138 source
=> 'Itemtype',
142 rentalcharge_daily
=> 0,
143 defaultreplacecost
=> undef,
148 my $patron_category = $builder->build(
150 source
=> 'Category',
152 category_type
=> 'P',
154 BlockExpiredPatronOpacActions
=> -1, # Pick the pref value
159 my $CircControl = C4
::Context
->preference('CircControl');
160 my $HomeOrHoldingBranch = C4
::Context
->preference('HomeOrHoldingBranch');
163 homebranch
=> $library2->{branchcode
},
164 holdingbranch
=> $library2->{branchcode
}
168 branchcode
=> $library2->{branchcode
}
171 t
::lib
::Mocks
::mock_preference
('AutoReturnCheckedOutItems', 0);
173 # No userenv, PickupLibrary
174 t
::lib
::Mocks
::mock_preference
('IndependentBranches', '0');
175 t
::lib
::Mocks
::mock_preference
('CircControl', 'PickupLibrary');
177 C4
::Context
->preference('CircControl'),
179 'CircControl changed to PickupLibrary'
182 C4
::Circulation
::_GetCircControlBranch
($item, $borrower),
183 $item->{$HomeOrHoldingBranch},
184 '_GetCircControlBranch returned item branch (no userenv defined)'
187 # No userenv, PatronLibrary
188 t
::lib
::Mocks
::mock_preference
('CircControl', 'PatronLibrary');
190 C4
::Context
->preference('CircControl'),
192 'CircControl changed to PatronLibrary'
195 C4
::Circulation
::_GetCircControlBranch
($item, $borrower),
196 $borrower->{branchcode
},
197 '_GetCircControlBranch returned borrower branch'
200 # No userenv, ItemHomeLibrary
201 t
::lib
::Mocks
::mock_preference
('CircControl', 'ItemHomeLibrary');
203 C4
::Context
->preference('CircControl'),
205 'CircControl changed to ItemHomeLibrary'
208 $item->{$HomeOrHoldingBranch},
209 C4
::Circulation
::_GetCircControlBranch
($item, $borrower),
210 '_GetCircControlBranch returned item branch'
214 t
::lib
::Mocks
::mock_userenv
({ branchcode
=> $library2->{branchcode
} });
215 is
(C4
::Context
->userenv->{branch
}, $library2->{branchcode
}, 'userenv set');
217 # Userenv set, PickupLibrary
218 t
::lib
::Mocks
::mock_preference
('CircControl', 'PickupLibrary');
220 C4
::Context
->preference('CircControl'),
222 'CircControl changed to PickupLibrary'
225 C4
::Circulation
::_GetCircControlBranch
($item, $borrower),
226 $library2->{branchcode
},
227 '_GetCircControlBranch returned current branch'
230 # Userenv set, PatronLibrary
231 t
::lib
::Mocks
::mock_preference
('CircControl', 'PatronLibrary');
233 C4
::Context
->preference('CircControl'),
235 'CircControl changed to PatronLibrary'
238 C4
::Circulation
::_GetCircControlBranch
($item, $borrower),
239 $borrower->{branchcode
},
240 '_GetCircControlBranch returned borrower branch'
243 # Userenv set, ItemHomeLibrary
244 t
::lib
::Mocks
::mock_preference
('CircControl', 'ItemHomeLibrary');
246 C4
::Context
->preference('CircControl'),
248 'CircControl changed to ItemHomeLibrary'
251 C4
::Circulation
::_GetCircControlBranch
($item, $borrower),
252 $item->{$HomeOrHoldingBranch},
253 '_GetCircControlBranch returned item branch'
256 # Reset initial configuration
257 t
::lib
::Mocks
::mock_preference
('CircControl', $CircControl);
259 C4
::Context
->preference('CircControl'),
261 'CircControl reset to its initial value'
264 # Set a simple circ policy
265 $dbh->do('DELETE FROM circulation_rules');
266 Koha
::CirculationRules
->set_rules(
268 categorycode
=> undef,
272 reservesallowed
=> 25,
274 lengthunit
=> 'days',
275 renewalsallowed
=> 1,
277 norenewalbefore
=> undef,
285 my ( $reused_itemnumber_1, $reused_itemnumber_2 );
286 subtest
"CanBookBeRenewed tests" => sub {
289 C4
::Context
->set_preference('ItemsDeniedRenewal','');
290 # Generate test biblio
291 my $biblio = $builder->build_sample_biblio();
293 my $branch = $library2->{branchcode
};
295 my $item_1 = $builder->build_sample_item(
297 biblionumber
=> $biblio->biblionumber,
299 replacementprice
=> 12.00,
303 $reused_itemnumber_1 = $item_1->itemnumber;
305 my $item_2 = $builder->build_sample_item(
307 biblionumber
=> $biblio->biblionumber,
309 replacementprice
=> 23.00,
313 $reused_itemnumber_2 = $item_2->itemnumber;
315 my $item_3 = $builder->build_sample_item(
317 biblionumber
=> $biblio->biblionumber,
319 replacementprice
=> 23.00,
325 my %renewing_borrower_data = (
327 surname
=> 'Renewal',
328 categorycode
=> $patron_category->{categorycode
},
329 branchcode
=> $branch,
332 my %reserving_borrower_data = (
333 firstname
=> 'Katrin',
334 surname
=> 'Reservation',
335 categorycode
=> $patron_category->{categorycode
},
336 branchcode
=> $branch,
339 my %hold_waiting_borrower_data = (
341 surname
=> 'Reservation',
342 categorycode
=> $patron_category->{categorycode
},
343 branchcode
=> $branch,
346 my %restricted_borrower_data = (
347 firstname
=> 'Alice',
348 surname
=> 'Reservation',
349 categorycode
=> $patron_category->{categorycode
},
350 debarred
=> '3228-01-01',
351 branchcode
=> $branch,
354 my %expired_borrower_data = (
357 categorycode
=> $patron_category->{categorycode
},
358 branchcode
=> $branch,
359 dateexpiry
=> dt_from_string
->subtract( months
=> 1 ),
362 my $renewing_borrowernumber = Koha
::Patron
->new(\
%renewing_borrower_data)->store->borrowernumber;
363 my $reserving_borrowernumber = Koha
::Patron
->new(\
%reserving_borrower_data)->store->borrowernumber;
364 my $hold_waiting_borrowernumber = Koha
::Patron
->new(\
%hold_waiting_borrower_data)->store->borrowernumber;
365 my $restricted_borrowernumber = Koha
::Patron
->new(\
%restricted_borrower_data)->store->borrowernumber;
366 my $expired_borrowernumber = Koha
::Patron
->new(\
%expired_borrower_data)->store->borrowernumber;
368 my $renewing_borrower_obj = Koha
::Patrons
->find( $renewing_borrowernumber );
369 my $renewing_borrower = $renewing_borrower_obj->unblessed;
370 my $restricted_borrower = Koha
::Patrons
->find( $restricted_borrowernumber )->unblessed;
371 my $expired_borrower = Koha
::Patrons
->find( $expired_borrowernumber )->unblessed;
378 my $checkitem = undef;
381 my $issue = AddIssue
( $renewing_borrower, $item_1->barcode);
382 my $datedue = dt_from_string
( $issue->date_due() );
383 is
(defined $issue->date_due(), 1, "Item 1 checked out, due date: " . $issue->date_due() );
385 my $issue2 = AddIssue
( $renewing_borrower, $item_2->barcode);
386 $datedue = dt_from_string
( $issue->date_due() );
387 is
(defined $issue2, 1, "Item 2 checked out, due date: " . $issue2->date_due());
390 my $borrowing_borrowernumber = Koha
::Checkouts
->find( { itemnumber
=> $item_1->itemnumber } )->borrowernumber;
391 is
($borrowing_borrowernumber, $renewing_borrowernumber, "Item checked out to $renewing_borrower->{firstname} $renewing_borrower->{surname}");
393 my ( $renewokay, $error ) = CanBookBeRenewed
($renewing_borrowernumber, $item_1->itemnumber, 1);
394 is
( $renewokay, 1, 'Can renew, no holds for this title or item');
397 # Biblio-level hold, renewal test
400 branchcode
=> $branch,
401 borrowernumber
=> $reserving_borrowernumber,
402 biblionumber
=> $biblio->biblionumber,
403 priority
=> $priority,
404 reservation_date
=> $resdate,
405 expiration_date
=> $expdate,
407 itemnumber
=> $checkitem,
412 # Testing of feature to allow the renewal of reserved items if other items on the record can fill all needed holds
413 Koha
::CirculationRules
->set_rule(
415 categorycode
=> undef,
418 rule_name
=> 'onshelfholds',
422 t
::lib
::Mocks
::mock_preference
('AllowRenewalIfOtherItemsAvailable', 1 );
423 ( $renewokay, $error ) = CanBookBeRenewed
($renewing_borrowernumber, $item_1->itemnumber);
424 is
( $renewokay, 1, 'Bug 11634 - Allow renewal of item with unfilled holds if other available items can fill those holds');
425 ( $renewokay, $error ) = CanBookBeRenewed
($renewing_borrowernumber, $item_2->itemnumber);
426 is
( $renewokay, 1, 'Bug 11634 - Allow renewal of item with unfilled holds if other available items can fill those holds');
428 # Now let's add an item level hold, we should no longer be able to renew the item
429 my $hold = Koha
::Database
->new()->schema()->resultset('Reserve')->create(
431 borrowernumber
=> $hold_waiting_borrowernumber,
432 biblionumber
=> $biblio->biblionumber,
433 itemnumber
=> $item_1->itemnumber,
434 branchcode
=> $branch,
438 ( $renewokay, $error ) = CanBookBeRenewed
($renewing_borrowernumber, $item_1->itemnumber);
439 is
( $renewokay, 0, 'Bug 13919 - Renewal possible with item level hold on item');
442 # 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
443 # be able to renew these items
444 $hold = Koha
::Database
->new()->schema()->resultset('Reserve')->create(
446 borrowernumber
=> $hold_waiting_borrowernumber,
447 biblionumber
=> $biblio->biblionumber,
448 itemnumber
=> $item_3->itemnumber,
449 branchcode
=> $branch,
454 ( $renewokay, $error ) = CanBookBeRenewed
($renewing_borrowernumber, $item_1->itemnumber);
455 is
( $renewokay, 0, 'Bug 11634 - Allow renewal of item with unfilled holds if other available items can fill those holds');
456 ( $renewokay, $error ) = CanBookBeRenewed
($renewing_borrowernumber, $item_2->itemnumber);
457 is
( $renewokay, 0, 'Bug 11634 - Allow renewal of item with unfilled holds if other available items can fill those holds');
458 t
::lib
::Mocks
::mock_preference
('AllowRenewalIfOtherItemsAvailable', 0 );
460 ( $renewokay, $error ) = CanBookBeRenewed
($renewing_borrowernumber, $item_1->itemnumber);
461 is
( $renewokay, 0, '(Bug 10663) Cannot renew, reserved');
462 is
( $error, 'on_reserve', '(Bug 10663) Cannot renew, reserved (returned error is on_reserve)');
464 ( $renewokay, $error ) = CanBookBeRenewed
($renewing_borrowernumber, $item_2->itemnumber);
465 is
( $renewokay, 0, '(Bug 10663) Cannot renew, reserved');
466 is
( $error, 'on_reserve', '(Bug 10663) Cannot renew, reserved (returned error is on_reserve)');
468 my $reserveid = Koha
::Holds
->search({ biblionumber
=> $biblio->biblionumber, borrowernumber
=> $reserving_borrowernumber })->next->reserve_id;
469 my $reserving_borrower = Koha
::Patrons
->find( $reserving_borrowernumber )->unblessed;
470 AddIssue
($reserving_borrower, $item_3->barcode);
471 my $reserve = $dbh->selectrow_hashref(
472 'SELECT * FROM old_reserves WHERE reserve_id = ?',
476 is
($reserve->{found
}, 'F', 'hold marked completed when checking out item that fills it');
478 # Item-level hold, renewal test
481 branchcode
=> $branch,
482 borrowernumber
=> $reserving_borrowernumber,
483 biblionumber
=> $biblio->biblionumber,
484 priority
=> $priority,
485 reservation_date
=> $resdate,
486 expiration_date
=> $expdate,
488 itemnumber
=> $item_1->itemnumber,
493 ( $renewokay, $error ) = CanBookBeRenewed
($renewing_borrowernumber, $item_1->itemnumber, 1);
494 is
( $renewokay, 0, '(Bug 10663) Cannot renew, item reserved');
495 is
( $error, 'on_reserve', '(Bug 10663) Cannot renew, item reserved (returned error is on_reserve)');
497 ( $renewokay, $error ) = CanBookBeRenewed
($renewing_borrowernumber, $item_2->itemnumber, 1);
498 is
( $renewokay, 1, 'Can renew item 2, item-level hold is on item 1');
500 # Items can't fill hold for reasons
501 $item_1->notforloan(1)->store;
502 ( $renewokay, $error ) = CanBookBeRenewed
($renewing_borrowernumber, $item_1->itemnumber, 1);
503 is
( $renewokay, 1, 'Can renew, item is marked not for loan, hold does not block');
504 $item_1->set({notforloan
=> 0, itype
=> $itemtype })->store;
506 # FIXME: Add more for itemtype not for loan etc.
508 # Restricted users cannot renew when RestrictionBlockRenewing is enabled
509 my $item_5 = $builder->build_sample_item(
511 biblionumber
=> $biblio->biblionumber,
513 replacementprice
=> 23.00,
517 my $datedue5 = AddIssue
($restricted_borrower, $item_5->barcode);
518 is
(defined $datedue5, 1, "Item with date due checked out, due date: $datedue5");
520 t
::lib
::Mocks
::mock_preference
('RestrictionBlockRenewing','1');
521 ( $renewokay, $error ) = CanBookBeRenewed
($renewing_borrowernumber, $item_2->itemnumber);
522 is
( $renewokay, 1, '(Bug 8236), Can renew, user is not restricted');
523 ( $renewokay, $error ) = CanBookBeRenewed
($restricted_borrowernumber, $item_5->itemnumber);
524 is
( $renewokay, 0, '(Bug 8236), Cannot renew, user is restricted');
526 # Users cannot renew an overdue item
527 my $item_6 = $builder->build_sample_item(
529 biblionumber
=> $biblio->biblionumber,
531 replacementprice
=> 23.00,
536 my $item_7 = $builder->build_sample_item(
538 biblionumber
=> $biblio->biblionumber,
540 replacementprice
=> 23.00,
545 my $datedue6 = AddIssue
( $renewing_borrower, $item_6->barcode);
546 is
(defined $datedue6, 1, "Item 2 checked out, due date: ".$datedue6->date_due);
548 my $now = dt_from_string
();
549 my $five_weeks = DateTime
::Duration
->new(weeks
=> 5);
550 my $five_weeks_ago = $now - $five_weeks;
551 t
::lib
::Mocks
::mock_preference
('finesMode', 'production');
553 my $passeddatedue1 = AddIssue
($renewing_borrower, $item_7->barcode, $five_weeks_ago);
554 is
(defined $passeddatedue1, 1, "Item with passed date due checked out, due date: " . $passeddatedue1->date_due);
556 my ( $fine ) = CalcFine
( $item_7->unblessed, $renewing_borrower->{categorycode
}, $branch, $five_weeks_ago, $now );
557 C4
::Overdues
::UpdateFine
(
559 issue_id
=> $passeddatedue1->id(),
560 itemnumber
=> $item_7->itemnumber,
561 borrowernumber
=> $renewing_borrower->{borrowernumber
},
563 due
=> Koha
::DateUtils
::output_pref
($five_weeks_ago)
567 t
::lib
::Mocks
::mock_preference
('RenewalLog', 0);
568 my $date = output_pref
( { dt
=> dt_from_string
(), dateonly
=> 1, dateformat
=> 'iso' } );
569 my %params_renewal = (
570 timestamp
=> { -like
=> $date . "%" },
571 module
=> "CIRCULATION",
575 timestamp
=> { -like
=> $date . "%" },
576 module
=> "CIRCULATION",
579 my $old_log_size = Koha
::ActionLogs
->count( \
%params_renewal );
580 my $dt = dt_from_string
();
581 Time
::Fake
->offset( $dt->epoch );
582 my $datedue1 = AddRenewal
( $renewing_borrower->{borrowernumber
}, $item_7->itemnumber, $branch );
583 my $new_log_size = Koha
::ActionLogs
->count( \
%params_renewal );
584 is
($new_log_size, $old_log_size, 'renew log not added because of the syspref RenewalLog');
585 isnt
(DateTime
->compare($datedue1, $dt), 0, "AddRenewal returned a good duedate");
588 t
::lib
::Mocks
::mock_preference
('RenewalLog', 1);
589 $date = output_pref
( { dt
=> dt_from_string
(), dateonly
=> 1, dateformat
=> 'iso' } );
590 $old_log_size = Koha
::ActionLogs
->count( \
%params_renewal );
591 AddRenewal
( $renewing_borrower->{borrowernumber
}, $item_7->itemnumber, $branch );
592 $new_log_size = Koha
::ActionLogs
->count( \
%params_renewal );
593 is
($new_log_size, $old_log_size + 1, 'renew log successfully added');
595 my $fines = Koha
::Account
::Lines
->search( { borrowernumber
=> $renewing_borrower->{borrowernumber
}, itemnumber
=> $item_7->itemnumber } );
596 is
( $fines->count, 2, 'AddRenewal left both fines' );
597 isnt
( $fines->next->status, 'UNRETURNED', 'Fine on renewed item is closed out properly' );
598 isnt
( $fines->next->status, 'UNRETURNED', 'Fine on renewed item is closed out properly' );
602 my $old_issue_log_size = Koha
::ActionLogs
->count( \
%params_issue );
603 my $old_renew_log_size = Koha
::ActionLogs
->count( \
%params_renewal );
604 AddIssue
( $renewing_borrower,$item_7->barcode,Koha
::DateUtils
::output_pref
({str
=>$datedue6->date_due, dateformat
=>'iso'}),0,$date, 0, undef );
605 $new_log_size = Koha
::ActionLogs
->count( \
%params_renewal );
606 is
($new_log_size, $old_renew_log_size + 1, 'renew log successfully added when renewed via issuing');
607 $new_log_size = Koha
::ActionLogs
->count( \
%params_issue );
608 is
($new_log_size, $old_issue_log_size, 'renew not logged as issue when renewed via issuing');
610 $fines = Koha
::Account
::Lines
->search( { borrowernumber
=> $renewing_borrower->{borrowernumber
}, itemnumber
=> $item_7->itemnumber } );
613 t
::lib
::Mocks
::mock_preference
('OverduesBlockRenewing','blockitem');
614 ( $renewokay, $error ) = CanBookBeRenewed
($renewing_borrowernumber, $item_6->itemnumber);
615 is
( $renewokay, 1, '(Bug 8236), Can renew, this item is not overdue');
616 ( $renewokay, $error ) = CanBookBeRenewed
($renewing_borrowernumber, $item_7->itemnumber);
617 is
( $renewokay, 0, '(Bug 8236), Cannot renew, this item is overdue');
620 $hold = Koha
::Holds
->search({ biblionumber
=> $biblio->biblionumber, borrowernumber
=> $reserving_borrowernumber })->next;
624 # Test automatic renewal before value for "norenewalbefore" in policy is set
625 # In this case automatic renewal is not permitted prior to due date
626 my $item_4 = $builder->build_sample_item(
628 biblionumber
=> $biblio->biblionumber,
630 replacementprice
=> 16.00,
635 $issue = AddIssue
( $renewing_borrower, $item_4->barcode, undef, undef, undef, undef, { auto_renew
=> 1 } );
636 ( $renewokay, $error ) =
637 CanBookBeRenewed
( $renewing_borrowernumber, $item_4->itemnumber );
638 is
( $renewokay, 0, 'Bug 14101: Cannot renew, renewal is automatic and premature' );
639 is
( $error, 'auto_too_soon',
640 'Bug 14101: Cannot renew, renewal is automatic and premature, "No renewal before" = undef (returned code is auto_too_soon)' );
643 branchcode
=> $branch,
644 borrowernumber
=> $reserving_borrowernumber,
645 biblionumber
=> $biblio->biblionumber,
646 itemnumber
=> $bibitems,
647 priority
=> $priority,
648 reservation_date
=> $resdate,
649 expiration_date
=> $expdate,
652 itemnumber
=> $item_4->itemnumber,
656 ( $renewokay, $error ) = CanBookBeRenewed
( $renewing_borrowernumber, $item_4->itemnumber );
657 is
( $renewokay, 0, 'Still should not be able to renew' );
658 is
( $error, 'on_reserve', 'returned code is on_reserve, reserve checked when not checking for cron' );
659 ( $renewokay, $error ) = CanBookBeRenewed
( $renewing_borrowernumber, $item_4->itemnumber, undef, 1 );
660 is
( $renewokay, 0, 'Still should not be able to renew' );
661 is
( $error, 'auto_too_soon', 'returned code is auto_too_soon, reserve not checked when checking for cron' );
662 ( $renewokay, $error ) = CanBookBeRenewed
( $renewing_borrowernumber, $item_4->itemnumber, 1 );
663 is
( $renewokay, 0, 'Still should not be able to renew' );
664 is
( $error, 'on_reserve', 'returned code is on_reserve, auto_too_soon limit is overridden' );
665 ( $renewokay, $error ) = CanBookBeRenewed
( $renewing_borrowernumber, $item_4->itemnumber, 1, 1 );
666 is
( $renewokay, 0, 'Still should not be able to renew' );
667 is
( $error, 'on_reserve', 'returned code is on_reserve, auto_too_soon limit is overridden' );
668 $dbh->do('UPDATE circulation_rules SET rule_value = 0 where rule_name = "norenewalbefore"');
669 ( $renewokay, $error ) = CanBookBeRenewed
( $renewing_borrowernumber, $item_4->itemnumber, 1 );
670 is
( $renewokay, 0, 'Still should not be able to renew' );
671 is
( $error, 'on_reserve', 'returned code is on_reserve, auto_renew only happens if not on reserve' );
672 ModReserveCancelAll
($item_4->itemnumber, $reserving_borrowernumber);
676 $renewing_borrower_obj->autorenew_checkouts(0)->store;
677 ( $renewokay, $error ) = CanBookBeRenewed
( $renewing_borrowernumber, $item_4->itemnumber );
678 is
( $renewokay, 1, 'No renewal before is undef, but patron opted out of auto_renewal' );
679 $renewing_borrower_obj->autorenew_checkouts(1)->store;
683 # Test premature manual renewal
684 Koha
::CirculationRules
->set_rule(
686 categorycode
=> undef,
689 rule_name
=> 'norenewalbefore',
694 ( $renewokay, $error ) = CanBookBeRenewed
($renewing_borrowernumber, $item_1->itemnumber);
695 is
( $renewokay, 0, 'Bug 7413: Cannot renew, renewal is premature');
696 is
( $error, 'too_soon', 'Bug 7413: Cannot renew, renewal is premature (returned code is too_soon)');
699 # Test 'exact time' setting for syspref NoRenewalBeforePrecision
700 t
::lib
::Mocks
::mock_preference
( 'NoRenewalBeforePrecision', 'exact_time' );
702 GetSoonestRenewDate
( $renewing_borrowernumber, $item_1->itemnumber ),
703 $datedue->clone->add( days
=> -7 ),
704 'Bug 14395: Renewals permitted 7 days before due date, as expected'
708 # Test 'date' setting for syspref NoRenewalBeforePrecision
709 t
::lib
::Mocks
::mock_preference
( 'NoRenewalBeforePrecision', 'date' );
711 GetSoonestRenewDate
( $renewing_borrowernumber, $item_1->itemnumber ),
712 $datedue->clone->add( days
=> -7 )->truncate( to
=> 'day' ),
713 'Bug 14395: Renewals permitted 7 days before due date, as expected'
717 # Test premature automatic renewal
718 ( $renewokay, $error ) =
719 CanBookBeRenewed
( $renewing_borrowernumber, $item_4->itemnumber );
720 is
( $renewokay, 0, 'Bug 14101: Cannot renew, renewal is automatic and premature' );
721 is
( $error, 'auto_too_soon',
722 'Bug 14101: Cannot renew, renewal is automatic and premature (returned code is auto_too_soon)'
725 $renewing_borrower_obj->autorenew_checkouts(0)->store;
726 ( $renewokay, $error ) = CanBookBeRenewed
( $renewing_borrowernumber, $item_4->itemnumber );
727 is
( $renewokay, 0, 'No renewal before is 7, patron opted out of auto_renewal still cannot renew early' );
728 is
( $error, 'too_soon', 'Error is too_soon, no auto' );
729 $renewing_borrower_obj->autorenew_checkouts(1)->store;
731 # Change policy so that loans can only be renewed exactly on due date (0 days prior to due date)
732 # and test automatic renewal again
733 $dbh->do(q{UPDATE circulation_rules SET rule_value = '0' WHERE rule_name = 'norenewalbefore'});
734 ( $renewokay, $error ) =
735 CanBookBeRenewed
( $renewing_borrowernumber, $item_4->itemnumber );
736 is
( $renewokay, 0, 'Bug 14101: Cannot renew, renewal is automatic and premature' );
737 is
( $error, 'auto_too_soon',
738 'Bug 14101: Cannot renew, renewal is automatic and premature, "No renewal before" = 0 (returned code is auto_too_soon)'
741 $renewing_borrower_obj->autorenew_checkouts(0)->store;
742 ( $renewokay, $error ) = CanBookBeRenewed
( $renewing_borrowernumber, $item_4->itemnumber );
743 is
( $renewokay, 0, 'No renewal before is 0, patron opted out of auto_renewal still cannot renew early' );
744 is
( $error, 'too_soon', 'Error is too_soon, no auto' );
745 $renewing_borrower_obj->autorenew_checkouts(1)->store;
747 # Change policy so that loans can be renewed 99 days prior to the due date
748 # and test automatic renewal again
749 $dbh->do(q{UPDATE circulation_rules SET rule_value = '99' WHERE rule_name = 'norenewalbefore'});
750 ( $renewokay, $error ) =
751 CanBookBeRenewed
( $renewing_borrowernumber, $item_4->itemnumber );
752 is
( $renewokay, 0, 'Bug 14101: Cannot renew, renewal is automatic' );
753 is
( $error, 'auto_renew',
754 'Bug 14101: Cannot renew, renewal is automatic (returned code is auto_renew)'
757 $renewing_borrower_obj->autorenew_checkouts(0)->store;
758 ( $renewokay, $error ) = CanBookBeRenewed
( $renewing_borrowernumber, $item_4->itemnumber );
759 is
( $renewokay, 1, 'No renewal before is 99, patron opted out of auto_renewal so can renew' );
760 $renewing_borrower_obj->autorenew_checkouts(1)->store;
762 subtest
"too_late_renewal / no_auto_renewal_after" => sub {
764 my $item_to_auto_renew = $builder->build_sample_item(
766 biblionumber
=> $biblio->biblionumber,
771 my $ten_days_before = dt_from_string
->add( days
=> -10 );
772 my $ten_days_ahead = dt_from_string
->add( days
=> 10 );
773 AddIssue
( $renewing_borrower, $item_to_auto_renew->barcode, $ten_days_ahead, undef, $ten_days_before, undef, { auto_renew
=> 1 } );
775 Koha
::CirculationRules
->set_rules(
777 categorycode
=> undef,
781 norenewalbefore
=> '7',
782 no_auto_renewal_after
=> '9',
786 ( $renewokay, $error ) =
787 CanBookBeRenewed
( $renewing_borrowernumber, $item_to_auto_renew->itemnumber );
788 is
( $renewokay, 0, 'Do not renew, renewal is automatic' );
789 is
( $error, 'auto_too_late', 'Cannot renew, too late(returned code is auto_too_late)' );
791 Koha
::CirculationRules
->set_rules(
793 categorycode
=> undef,
797 norenewalbefore
=> '7',
798 no_auto_renewal_after
=> '10',
802 ( $renewokay, $error ) =
803 CanBookBeRenewed
( $renewing_borrowernumber, $item_to_auto_renew->itemnumber );
804 is
( $renewokay, 0, 'Do not renew, renewal is automatic' );
805 is
( $error, 'auto_too_late', 'Cannot auto renew, too late - no_auto_renewal_after is inclusive(returned code is auto_too_late)' );
807 Koha
::CirculationRules
->set_rules(
809 categorycode
=> undef,
813 norenewalbefore
=> '7',
814 no_auto_renewal_after
=> '11',
818 ( $renewokay, $error ) =
819 CanBookBeRenewed
( $renewing_borrowernumber, $item_to_auto_renew->itemnumber );
820 is
( $renewokay, 0, 'Do not renew, renewal is automatic' );
821 is
( $error, 'auto_too_soon', 'Cannot auto renew, too soon - no_auto_renewal_after is defined(returned code is auto_too_soon)' );
823 Koha
::CirculationRules
->set_rules(
825 categorycode
=> undef,
829 norenewalbefore
=> '10',
830 no_auto_renewal_after
=> '11',
834 ( $renewokay, $error ) =
835 CanBookBeRenewed
( $renewing_borrowernumber, $item_to_auto_renew->itemnumber );
836 is
( $renewokay, 0, 'Do not renew, renewal is automatic' );
837 is
( $error, 'auto_renew', 'Cannot renew, renew is automatic' );
839 Koha
::CirculationRules
->set_rules(
841 categorycode
=> undef,
845 norenewalbefore
=> '10',
846 no_auto_renewal_after
=> undef,
847 no_auto_renewal_after_hard_limit
=> dt_from_string
->add( days
=> -1 ),
851 ( $renewokay, $error ) =
852 CanBookBeRenewed
( $renewing_borrowernumber, $item_to_auto_renew->itemnumber );
853 is
( $renewokay, 0, 'Do not renew, renewal is automatic' );
854 is
( $error, 'auto_too_late', 'Cannot renew, too late(returned code is auto_too_late)' );
856 Koha
::CirculationRules
->set_rules(
858 categorycode
=> undef,
862 norenewalbefore
=> '7',
863 no_auto_renewal_after
=> '15',
864 no_auto_renewal_after_hard_limit
=> dt_from_string
->add( days
=> -1 ),
868 ( $renewokay, $error ) =
869 CanBookBeRenewed
( $renewing_borrowernumber, $item_to_auto_renew->itemnumber );
870 is
( $renewokay, 0, 'Do not renew, renewal is automatic' );
871 is
( $error, 'auto_too_late', 'Cannot renew, too late(returned code is auto_too_late)' );
873 Koha
::CirculationRules
->set_rules(
875 categorycode
=> undef,
879 norenewalbefore
=> '10',
880 no_auto_renewal_after
=> undef,
881 no_auto_renewal_after_hard_limit
=> dt_from_string
->add( days
=> 1 ),
885 ( $renewokay, $error ) =
886 CanBookBeRenewed
( $renewing_borrowernumber, $item_to_auto_renew->itemnumber );
887 is
( $renewokay, 0, 'Do not renew, renewal is automatic' );
888 is
( $error, 'auto_renew', 'Cannot renew, renew is automatic' );
891 subtest
"auto_too_much_oweing | OPACFineNoRenewalsBlockAutoRenew & OPACFineNoRenewalsIncludeCredit" => sub {
893 my $item_to_auto_renew = $builder->build_sample_item(
895 biblionumber
=> $biblio->biblionumber,
900 my $ten_days_before = dt_from_string
->add( days
=> -10 );
901 my $ten_days_ahead = dt_from_string
->add( days
=> 10 );
902 AddIssue
( $renewing_borrower, $item_to_auto_renew->barcode, $ten_days_ahead, undef, $ten_days_before, undef, { auto_renew
=> 1 } );
904 Koha
::CirculationRules
->set_rules(
906 categorycode
=> undef,
910 norenewalbefore
=> '10',
911 no_auto_renewal_after
=> '11',
915 C4
::Context
->set_preference('OPACFineNoRenewalsBlockAutoRenew','1');
916 C4
::Context
->set_preference('OPACFineNoRenewals','10');
917 C4
::Context
->set_preference('OPACFineNoRenewalsIncludeCredit','1');
918 my $fines_amount = 5;
919 my $account = Koha
::Account
->new({patron_id
=> $renewing_borrowernumber});
922 amount
=> $fines_amount,
925 item_id
=> $item_to_auto_renew->itemnumber,
926 description
=> "Some fines"
928 )->status('RETURNED')->store;
929 ( $renewokay, $error ) =
930 CanBookBeRenewed
( $renewing_borrowernumber, $item_to_auto_renew->itemnumber );
931 is
( $renewokay, 0, 'Do not renew, renewal is automatic' );
932 is
( $error, 'auto_renew', 'Can auto renew, OPACFineNoRenewals=10, patron has 5' );
936 amount
=> $fines_amount,
939 item_id
=> $item_to_auto_renew->itemnumber,
940 description
=> "Some fines"
942 )->status('RETURNED')->store;
943 ( $renewokay, $error ) =
944 CanBookBeRenewed
( $renewing_borrowernumber, $item_to_auto_renew->itemnumber );
945 is
( $renewokay, 0, 'Do not renew, renewal is automatic' );
946 is
( $error, 'auto_renew', 'Can auto renew, OPACFineNoRenewals=10, patron has 10' );
950 amount
=> $fines_amount,
953 item_id
=> $item_to_auto_renew->itemnumber,
954 description
=> "Some fines"
956 )->status('RETURNED')->store;
957 ( $renewokay, $error ) =
958 CanBookBeRenewed
( $renewing_borrowernumber, $item_to_auto_renew->itemnumber );
959 is
( $renewokay, 0, 'Do not renew, renewal is automatic' );
960 is
( $error, 'auto_too_much_oweing', 'Cannot auto renew, OPACFineNoRenewals=10, patron has 15' );
962 $account->add_credit(
964 amount
=> $fines_amount,
967 description
=> "Some payment"
970 ( $renewokay, $error ) =
971 CanBookBeRenewed
( $renewing_borrowernumber, $item_to_auto_renew->itemnumber );
972 is
( $renewokay, 0, 'Do not renew, renewal is automatic' );
973 is
( $error, 'auto_renew', 'Can auto renew, OPACFineNoRenewals=10, OPACFineNoRenewalsIncludeCredit=1, patron has 15 debt, 5 credit' );
975 C4
::Context
->set_preference('OPACFineNoRenewalsIncludeCredit','0');
976 ( $renewokay, $error ) =
977 CanBookBeRenewed
( $renewing_borrowernumber, $item_to_auto_renew->itemnumber );
978 is
( $renewokay, 0, 'Do not renew, renewal is automatic' );
979 is
( $error, 'auto_too_much_oweing', 'Cannot auto renew, OPACFineNoRenewals=10, OPACFineNoRenewalsIncludeCredit=1, patron has 15 debt, 5 credit' );
981 $dbh->do('DELETE FROM accountlines WHERE borrowernumber=?', undef, $renewing_borrowernumber);
982 C4
::Context
->set_preference('OPACFineNoRenewalsIncludeCredit','1');
985 subtest
"auto_account_expired | BlockExpiredPatronOpacActions" => sub {
987 my $item_to_auto_renew = $builder->build_sample_item(
989 biblionumber
=> $biblio->biblionumber,
994 Koha
::CirculationRules
->set_rules(
996 categorycode
=> undef,
1000 norenewalbefore
=> 10,
1001 no_auto_renewal_after
=> 11,
1006 my $ten_days_before = dt_from_string
->add( days
=> -10 );
1007 my $ten_days_ahead = dt_from_string
->add( days
=> 10 );
1009 # Patron is expired and BlockExpiredPatronOpacActions=0
1010 # => auto renew is allowed
1011 t
::lib
::Mocks
::mock_preference
('BlockExpiredPatronOpacActions', 0);
1012 my $patron = $expired_borrower;
1013 my $checkout = AddIssue
( $patron, $item_to_auto_renew->barcode, $ten_days_ahead, undef, $ten_days_before, undef, { auto_renew
=> 1 } );
1014 ( $renewokay, $error ) =
1015 CanBookBeRenewed
( $patron->{borrowernumber
}, $item_to_auto_renew->itemnumber );
1016 is
( $renewokay, 0, 'Do not renew, renewal is automatic' );
1017 is
( $error, 'auto_renew', 'Can auto renew, patron is expired but BlockExpiredPatronOpacActions=0' );
1018 Koha
::Checkouts
->find( $checkout->issue_id )->delete;
1021 # Patron is expired and BlockExpiredPatronOpacActions=1
1022 # => auto renew is not allowed
1023 t
::lib
::Mocks
::mock_preference
('BlockExpiredPatronOpacActions', 1);
1024 $patron = $expired_borrower;
1025 $checkout = AddIssue
( $patron, $item_to_auto_renew->barcode, $ten_days_ahead, undef, $ten_days_before, undef, { auto_renew
=> 1 } );
1026 ( $renewokay, $error ) =
1027 CanBookBeRenewed
( $patron->{borrowernumber
}, $item_to_auto_renew->itemnumber );
1028 is
( $renewokay, 0, 'Do not renew, renewal is automatic' );
1029 is
( $error, 'auto_account_expired', 'Can not auto renew, lockExpiredPatronOpacActions=1 and patron is expired' );
1030 Koha
::Checkouts
->find( $checkout->issue_id )->delete;
1033 # Patron is not expired and BlockExpiredPatronOpacActions=1
1034 # => auto renew is allowed
1035 t
::lib
::Mocks
::mock_preference
('BlockExpiredPatronOpacActions', 1);
1036 $patron = $renewing_borrower;
1037 $checkout = AddIssue
( $patron, $item_to_auto_renew->barcode, $ten_days_ahead, undef, $ten_days_before, undef, { auto_renew
=> 1 } );
1038 ( $renewokay, $error ) =
1039 CanBookBeRenewed
( $patron->{borrowernumber
}, $item_to_auto_renew->itemnumber );
1040 is
( $renewokay, 0, 'Do not renew, renewal is automatic' );
1041 is
( $error, 'auto_renew', 'Can auto renew, BlockExpiredPatronOpacActions=1 but patron is not expired' );
1042 Koha
::Checkouts
->find( $checkout->issue_id )->delete;
1045 subtest
"GetLatestAutoRenewDate" => sub {
1047 my $item_to_auto_renew = $builder->build_sample_item(
1049 biblionumber
=> $biblio->biblionumber,
1054 my $ten_days_before = dt_from_string
->add( days
=> -10 );
1055 my $ten_days_ahead = dt_from_string
->add( days
=> 10 );
1056 AddIssue
( $renewing_borrower, $item_to_auto_renew->barcode, $ten_days_ahead, undef, $ten_days_before, undef, { auto_renew
=> 1 } );
1057 Koha
::CirculationRules
->set_rules(
1059 categorycode
=> undef,
1060 branchcode
=> undef,
1063 norenewalbefore
=> '7',
1064 no_auto_renewal_after
=> '',
1065 no_auto_renewal_after_hard_limit
=> undef,
1069 my $latest_auto_renew_date = GetLatestAutoRenewDate
( $renewing_borrowernumber, $item_to_auto_renew->itemnumber );
1070 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' );
1071 my $five_days_before = dt_from_string
->add( days
=> -5 );
1072 Koha
::CirculationRules
->set_rules(
1074 categorycode
=> undef,
1075 branchcode
=> undef,
1078 norenewalbefore
=> '10',
1079 no_auto_renewal_after
=> '5',
1080 no_auto_renewal_after_hard_limit
=> undef,
1084 $latest_auto_renew_date = GetLatestAutoRenewDate
( $renewing_borrowernumber, $item_to_auto_renew->itemnumber );
1085 is
( $latest_auto_renew_date->truncate( to
=> 'minute' ),
1086 $five_days_before->truncate( to
=> 'minute' ),
1087 'GetLatestAutoRenewDate should return -5 days if no_auto_renewal_after = 5 and date_due is 10 days before'
1089 my $five_days_ahead = dt_from_string
->add( days
=> 5 );
1090 $dbh->do(q{UPDATE circulation_rules SET rule_value = '10' WHERE rule_name = 'norenewalbefore'});
1091 $dbh->do(q{UPDATE circulation_rules SET rule_value = '15' WHERE rule_name = 'no_auto_renewal_after'});
1092 $dbh->do(q{UPDATE circulation_rules SET rule_value = NULL WHERE rule_name = 'no_auto_renewal_after_hard_limit'});
1093 Koha
::CirculationRules
->set_rules(
1095 categorycode
=> undef,
1096 branchcode
=> undef,
1099 norenewalbefore
=> '10',
1100 no_auto_renewal_after
=> '15',
1101 no_auto_renewal_after_hard_limit
=> undef,
1105 $latest_auto_renew_date = GetLatestAutoRenewDate
( $renewing_borrowernumber, $item_to_auto_renew->itemnumber );
1106 is
( $latest_auto_renew_date->truncate( to
=> 'minute' ),
1107 $five_days_ahead->truncate( to
=> 'minute' ),
1108 'GetLatestAutoRenewDate should return +5 days if no_auto_renewal_after = 15 and date_due is 10 days before'
1110 my $two_days_ahead = dt_from_string
->add( days
=> 2 );
1111 Koha
::CirculationRules
->set_rules(
1113 categorycode
=> undef,
1114 branchcode
=> undef,
1117 norenewalbefore
=> '10',
1118 no_auto_renewal_after
=> '',
1119 no_auto_renewal_after_hard_limit
=> dt_from_string
->add( days
=> 2 ),
1123 $latest_auto_renew_date = GetLatestAutoRenewDate
( $renewing_borrowernumber, $item_to_auto_renew->itemnumber );
1124 is
( $latest_auto_renew_date->truncate( to
=> 'day' ),
1125 $two_days_ahead->truncate( to
=> 'day' ),
1126 'GetLatestAutoRenewDate should return +2 days if no_auto_renewal_after_hard_limit is defined and not no_auto_renewal_after'
1128 Koha
::CirculationRules
->set_rules(
1130 categorycode
=> undef,
1131 branchcode
=> undef,
1134 norenewalbefore
=> '10',
1135 no_auto_renewal_after
=> '15',
1136 no_auto_renewal_after_hard_limit
=> dt_from_string
->add( days
=> 2 ),
1140 $latest_auto_renew_date = GetLatestAutoRenewDate
( $renewing_borrowernumber, $item_to_auto_renew->itemnumber );
1141 is
( $latest_auto_renew_date->truncate( to
=> 'day' ),
1142 $two_days_ahead->truncate( to
=> 'day' ),
1143 'GetLatestAutoRenewDate should return +2 days if no_auto_renewal_after_hard_limit is < no_auto_renewal_after'
1149 # set policy to forbid renewals
1150 Koha
::CirculationRules
->set_rules(
1152 categorycode
=> undef,
1153 branchcode
=> undef,
1156 norenewalbefore
=> undef,
1157 renewalsallowed
=> 0,
1162 ( $renewokay, $error ) = CanBookBeRenewed
($renewing_borrowernumber, $item_1->itemnumber);
1163 is
( $renewokay, 0, 'Cannot renew, 0 renewals allowed');
1164 is
( $error, 'too_many', 'Cannot renew, 0 renewals allowed (returned code is too_many)');
1166 # Too many unseen renewals
1167 Koha
::CirculationRules
->set_rules(
1169 categorycode
=> undef,
1170 branchcode
=> undef,
1173 unseen_renewals_allowed
=> 2,
1174 renewalsallowed
=> 10,
1178 t
::lib
::Mocks
::mock_preference
('UnseenRenewals', 1);
1179 $dbh->do('UPDATE issues SET unseen_renewals = 2 where borrowernumber = ? AND itemnumber = ?', undef, ($renewing_borrowernumber, $item_1->itemnumber));
1180 ( $renewokay, $error ) = CanBookBeRenewed
($renewing_borrowernumber, $item_1->itemnumber);
1181 is
( $renewokay, 0, 'Cannot renew, 0 unseen renewals allowed');
1182 is
( $error, 'too_unseen', 'Cannot renew, returned code is too_unseen');
1183 Koha
::CirculationRules
->set_rules(
1185 categorycode
=> undef,
1186 branchcode
=> undef,
1189 norenewalbefore
=> undef,
1190 renewalsallowed
=> 0,
1194 t
::lib
::Mocks
::mock_preference
('UnseenRenewals', 0);
1196 # Test WhenLostForgiveFine and WhenLostChargeReplacementFee
1197 t
::lib
::Mocks
::mock_preference
('WhenLostForgiveFine','1');
1198 t
::lib
::Mocks
::mock_preference
('WhenLostChargeReplacementFee','1');
1200 C4
::Overdues
::UpdateFine
(
1202 issue_id
=> $issue->id(),
1203 itemnumber
=> $item_1->itemnumber,
1204 borrowernumber
=> $renewing_borrower->{borrowernumber
},
1207 due
=> Koha
::DateUtils
::output_pref
($datedue)
1211 my $line = Koha
::Account
::Lines
->search({ borrowernumber
=> $renewing_borrower->{borrowernumber
} })->next();
1212 is
( $line->debit_type_code, 'OVERDUE', 'Account line type is OVERDUE' );
1213 is
( $line->status, 'UNRETURNED', 'Account line status is UNRETURNED' );
1214 is
( $line->amountoutstanding+0, 15, 'Account line amount outstanding is 15.00' );
1215 is
( $line->amount+0, 15, 'Account line amount is 15.00' );
1216 is
( $line->issue_id, $issue->id, 'Account line issue id matches' );
1218 my $offset = Koha
::Account
::Offsets
->search({ debit_id
=> $line->id })->next();
1219 is
( $offset->type, 'OVERDUE', 'Account offset type is Fine' );
1220 is
( $offset->amount+0, 15, 'Account offset amount is 15.00' );
1222 t
::lib
::Mocks
::mock_preference
('WhenLostForgiveFine','0');
1223 t
::lib
::Mocks
::mock_preference
('WhenLostChargeReplacementFee','0');
1225 LostItem
( $item_1->itemnumber, 'test', 1 );
1227 $line = Koha
::Account
::Lines
->find($line->id);
1228 is
( $line->debit_type_code, 'OVERDUE', 'Account type remains as OVERDUE' );
1229 isnt
( $line->status, 'UNRETURNED', 'Account status correctly changed from UNRETURNED to RETURNED' );
1231 my $item = Koha
::Items
->find($item_1->itemnumber);
1232 ok
( !$item->onloan(), "Lost item marked as returned has false onloan value" );
1233 my $checkout = Koha
::Checkouts
->find({ itemnumber
=> $item_1->itemnumber });
1234 is
( $checkout, undef, 'LostItem called with forced return has checked in the item' );
1236 my $total_due = $dbh->selectrow_array(
1237 'SELECT SUM( amountoutstanding ) FROM accountlines WHERE borrowernumber = ?',
1238 undef, $renewing_borrower->{borrowernumber
}
1241 is
( $total_due+0, 15, 'Borrower only charged replacement fee with both WhenLostForgiveFine and WhenLostChargeReplacementFee enabled' );
1243 C4
::Context
->dbh->do("DELETE FROM accountlines");
1245 C4
::Overdues
::UpdateFine
(
1247 issue_id
=> $issue2->id(),
1248 itemnumber
=> $item_2->itemnumber,
1249 borrowernumber
=> $renewing_borrower->{borrowernumber
},
1252 due
=> Koha
::DateUtils
::output_pref
($datedue)
1256 LostItem
( $item_2->itemnumber, 'test', 0 );
1258 my $item2 = Koha
::Items
->find($item_2->itemnumber);
1259 ok
( $item2->onloan(), "Lost item *not* marked as returned has true onloan value" );
1260 ok
( Koha
::Checkouts
->find({ itemnumber
=> $item_2->itemnumber }), 'LostItem called without forced return has checked in the item' );
1262 $total_due = $dbh->selectrow_array(
1263 'SELECT SUM( amountoutstanding ) FROM accountlines WHERE borrowernumber = ?',
1264 undef, $renewing_borrower->{borrowernumber
}
1267 ok
( $total_due == 15, 'Borrower only charged fine with both WhenLostForgiveFine and WhenLostChargeReplacementFee disabled' );
1269 my $future = dt_from_string
();
1270 $future->add( days
=> 7 );
1271 my $units = C4
::Overdues
::get_chargeable_units
('days', $future, $now, $library2->{branchcode
});
1272 ok
( $units == 0, '_get_chargeable_units returns 0 for items not past due date (Bug 12596)' );
1274 # Users cannot renew any item if there is an overdue item
1275 t
::lib
::Mocks
::mock_preference
('OverduesBlockRenewing','block');
1276 ( $renewokay, $error ) = CanBookBeRenewed
($renewing_borrowernumber, $item_6->itemnumber);
1277 is
( $renewokay, 0, '(Bug 8236), Cannot renew, one of the items is overdue');
1278 ( $renewokay, $error ) = CanBookBeRenewed
($renewing_borrowernumber, $item_7->itemnumber);
1279 is
( $renewokay, 0, '(Bug 8236), Cannot renew, one of the items is overdue');
1281 my $manager = $builder->build_object({ class => "Koha::Patrons" });
1282 t
::lib
::Mocks
::mock_userenv
({ patron
=> $manager,branchcode
=> $manager->branchcode });
1283 t
::lib
::Mocks
::mock_preference
('WhenLostChargeReplacementFee','1');
1284 $checkout = Koha
::Checkouts
->find( { itemnumber
=> $item_3->itemnumber } );
1285 LostItem
( $item_3->itemnumber, 'test', 0 );
1286 my $accountline = Koha
::Account
::Lines
->find( { itemnumber
=> $item_3->itemnumber } );
1287 is
( $accountline->issue_id, $checkout->id, "Issue id added for lost replacement fee charge" );
1289 $accountline->description,
1290 sprintf( "%s %s %s",
1291 $item_3->biblio->title || '',
1292 $item_3->barcode || '',
1293 $item_3->itemcallnumber || '' ),
1294 "Account line description must not contain 'Lost Items ', but be title, barcode, itemcallnumber"
1298 subtest
"GetUpcomingDueIssues" => sub {
1301 my $branch = $library2->{branchcode
};
1303 #Create another record
1304 my $biblio2 = $builder->build_sample_biblio();
1307 my $item_1 = Koha
::Items
->find($reused_itemnumber_1);
1308 my $item_2 = Koha
::Items
->find($reused_itemnumber_2);
1309 my $item_3 = $builder->build_sample_item(
1311 biblionumber
=> $biblio2->biblionumber,
1319 my %a_borrower_data = (
1320 firstname
=> 'Fridolyn',
1321 surname
=> 'SOMERS',
1322 categorycode
=> $patron_category->{categorycode
},
1323 branchcode
=> $branch,
1326 my $a_borrower_borrowernumber = Koha
::Patron
->new(\
%a_borrower_data)->store->borrowernumber;
1327 my $a_borrower = Koha
::Patrons
->find( $a_borrower_borrowernumber )->unblessed;
1329 my $yesterday = DateTime
->today(time_zone
=> C4
::Context
->tz())->add( days
=> -1 );
1330 my $two_days_ahead = DateTime
->today(time_zone
=> C4
::Context
->tz())->add( days
=> 2 );
1331 my $today = DateTime
->today(time_zone
=> C4
::Context
->tz());
1333 my $issue = AddIssue
( $a_borrower, $item_1->barcode, $yesterday );
1334 my $datedue = dt_from_string
( $issue->date_due() );
1335 my $issue2 = AddIssue
( $a_borrower, $item_2->barcode, $two_days_ahead );
1336 my $datedue2 = dt_from_string
( $issue->date_due() );
1340 # GetUpcomingDueIssues tests
1342 $upcoming_dues = C4
::Circulation
::GetUpcomingDueIssues
( { days_in_advance
=> $i } );
1343 is
( scalar( @
$upcoming_dues ), 0, "No items due in less than one day ($i days in advance)" );
1346 #days_in_advance needs to be inclusive, so 1 matches items due tomorrow, 0 items due today etc.
1347 $upcoming_dues = C4
::Circulation
::GetUpcomingDueIssues
( { days_in_advance
=> 2 } );
1348 is
( scalar ( @
$upcoming_dues), 1, "Only one item due in 2 days or less" );
1351 $upcoming_dues = C4
::Circulation
::GetUpcomingDueIssues
( { days_in_advance
=> $i } );
1352 is
( scalar( @
$upcoming_dues ), 1,
1353 "Bug 9362: Only one item due in more than 2 days ($i days in advance)" );
1356 # Bug 11218 - Due notices not generated - GetUpcomingDueIssues needs to select due today items as well
1358 my $issue3 = AddIssue
( $a_borrower, $item_3->barcode, $today );
1360 $upcoming_dues = C4
::Circulation
::GetUpcomingDueIssues
( { days_in_advance
=> -1 } );
1361 is
( scalar ( @
$upcoming_dues), 0, "Overdues can not be selected" );
1363 $upcoming_dues = C4
::Circulation
::GetUpcomingDueIssues
( { days_in_advance
=> 0 } );
1364 is
( scalar ( @
$upcoming_dues), 1, "1 item is due today" );
1366 $upcoming_dues = C4
::Circulation
::GetUpcomingDueIssues
( { days_in_advance
=> 1 } );
1367 is
( scalar ( @
$upcoming_dues), 1, "1 item is due today, none tomorrow" );
1369 $upcoming_dues = C4
::Circulation
::GetUpcomingDueIssues
( { days_in_advance
=> 2 } );
1370 is
( scalar ( @
$upcoming_dues), 2, "2 items are due withing 2 days" );
1372 $upcoming_dues = C4
::Circulation
::GetUpcomingDueIssues
( { days_in_advance
=> 3 } );
1373 is
( scalar ( @
$upcoming_dues), 2, "2 items are due withing 2 days" );
1375 $upcoming_dues = C4
::Circulation
::GetUpcomingDueIssues
();
1376 is
( scalar ( @
$upcoming_dues), 2, "days_in_advance is 7 in GetUpcomingDueIssues if not provided" );
1380 subtest
"Bug 13841 - Do not create new 0 amount fines" => sub {
1381 my $branch = $library2->{branchcode
};
1383 my $biblio = $builder->build_sample_biblio();
1386 my $item = $builder->build_sample_item(
1388 biblionumber
=> $biblio->biblionumber,
1395 my %a_borrower_data = (
1396 firstname
=> 'Kyle',
1398 categorycode
=> $patron_category->{categorycode
},
1399 branchcode
=> $branch,
1402 my $borrowernumber = Koha
::Patron
->new(\
%a_borrower_data)->store->borrowernumber;
1404 my $borrower = Koha
::Patrons
->find( $borrowernumber )->unblessed;
1405 my $issue = AddIssue
( $borrower, $item->barcode );
1408 issue_id
=> $issue->id(),
1409 itemnumber
=> $item->itemnumber,
1410 borrowernumber
=> $borrowernumber,
1416 my $hr = $dbh->selectrow_hashref(q{SELECT COUNT(*) AS count FROM accountlines WHERE borrowernumber = ? AND itemnumber = ?}, undef, $borrowernumber, $item->itemnumber );
1417 my $count = $hr->{count
};
1419 is
( $count, 0, "Calling UpdateFine on non-existant fine with an amount of 0 does not result in an empty fine" );
1422 subtest
"AllowRenewalIfOtherItemsAvailable tests" => sub {
1423 $dbh->do('DELETE FROM issues');
1424 $dbh->do('DELETE FROM items');
1425 $dbh->do('DELETE FROM circulation_rules');
1426 Koha
::CirculationRules
->set_rules(
1428 categorycode
=> undef,
1430 branchcode
=> undef,
1432 reservesallowed
=> 25,
1434 lengthunit
=> 'days',
1435 renewalsallowed
=> 1,
1437 norenewalbefore
=> undef,
1445 my $biblio = $builder->build_sample_biblio();
1447 my $item_1 = $builder->build_sample_item(
1449 biblionumber
=> $biblio->biblionumber,
1450 library
=> $library2->{branchcode
},
1455 my $item_2= $builder->build_sample_item(
1457 biblionumber
=> $biblio->biblionumber,
1458 library
=> $library2->{branchcode
},
1463 my $borrowernumber1 = Koha
::Patron
->new({
1464 firstname
=> 'Kyle',
1466 categorycode
=> $patron_category->{categorycode
},
1467 branchcode
=> $library2->{branchcode
},
1468 })->store->borrowernumber;
1469 my $borrowernumber2 = Koha
::Patron
->new({
1470 firstname
=> 'Chelsea',
1472 categorycode
=> $patron_category->{categorycode
},
1473 branchcode
=> $library2->{branchcode
},
1474 })->store->borrowernumber;
1476 my $borrower1 = Koha
::Patrons
->find( $borrowernumber1 )->unblessed;
1477 my $borrower2 = Koha
::Patrons
->find( $borrowernumber2 )->unblessed;
1479 my $issue = AddIssue
( $borrower1, $item_1->barcode );
1481 my ( $renewokay, $error ) = CanBookBeRenewed
( $borrowernumber1, $item_1->itemnumber );
1482 is
( $renewokay, 1, 'Bug 14337 - Verify the borrower can renew with no hold on the record' );
1486 branchcode
=> $library2->{branchcode
},
1487 borrowernumber
=> $borrowernumber2,
1488 biblionumber
=> $biblio->biblionumber,
1493 Koha
::CirculationRules
->set_rules(
1495 categorycode
=> undef,
1497 branchcode
=> undef,
1503 t
::lib
::Mocks
::mock_preference
( 'AllowRenewalIfOtherItemsAvailable', 0 );
1504 ( $renewokay, $error ) = CanBookBeRenewed
( $borrowernumber1, $item_1->itemnumber );
1505 is
( $renewokay, 0, 'Bug 14337 - Verify the borrower cannot renew with a hold on the record if AllowRenewalIfOtherItemsAvailable and onshelfholds are disabled' );
1507 Koha
::CirculationRules
->set_rules(
1509 categorycode
=> undef,
1511 branchcode
=> undef,
1517 t
::lib
::Mocks
::mock_preference
( 'AllowRenewalIfOtherItemsAvailable', 1 );
1518 ( $renewokay, $error ) = CanBookBeRenewed
( $borrowernumber1, $item_1->itemnumber );
1519 is
( $renewokay, 0, 'Bug 14337 - Verify the borrower cannot renew with a hold on the record if AllowRenewalIfOtherItemsAvailable is enabled and onshelfholds is disabled' );
1521 Koha
::CirculationRules
->set_rules(
1523 categorycode
=> undef,
1525 branchcode
=> undef,
1531 t
::lib
::Mocks
::mock_preference
( 'AllowRenewalIfOtherItemsAvailable', 0 );
1532 ( $renewokay, $error ) = CanBookBeRenewed
( $borrowernumber1, $item_1->itemnumber );
1533 is
( $renewokay, 0, 'Bug 14337 - Verify the borrower cannot renew with a hold on the record if AllowRenewalIfOtherItemsAvailable is disabled and onshelfhold is enabled' );
1535 Koha
::CirculationRules
->set_rules(
1537 categorycode
=> undef,
1539 branchcode
=> undef,
1545 t
::lib
::Mocks
::mock_preference
( 'AllowRenewalIfOtherItemsAvailable', 1 );
1546 ( $renewokay, $error ) = CanBookBeRenewed
( $borrowernumber1, $item_1->itemnumber );
1547 is
( $renewokay, 1, 'Bug 14337 - Verify the borrower can renew with a hold on the record if AllowRenewalIfOtherItemsAvailable and onshelfhold are enabled' );
1549 # Setting item not checked out to be not for loan but holdable
1550 $item_2->notforloan(-1)->store;
1552 ( $renewokay, $error ) = CanBookBeRenewed
( $borrowernumber1, $item_1->itemnumber );
1553 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' );
1557 # Don't allow renewing onsite checkout
1558 my $branch = $library->{branchcode
};
1560 #Create another record
1561 my $biblio = $builder->build_sample_biblio();
1563 my $item = $builder->build_sample_item(
1565 biblionumber
=> $biblio->biblionumber,
1571 my $borrowernumber = Koha
::Patron
->new({
1574 categorycode
=> $patron_category->{categorycode
},
1575 branchcode
=> $branch,
1576 })->store->borrowernumber;
1578 my $borrower = Koha
::Patrons
->find( $borrowernumber )->unblessed;
1580 my $issue = AddIssue
( $borrower, $item->barcode, undef, undef, undef, undef, { onsite_checkout
=> 1 } );
1581 my ( $renewed, $error ) = CanBookBeRenewed
( $borrowernumber, $item->itemnumber );
1582 is
( $renewed, 0, 'CanBookBeRenewed should not allow to renew on-site checkout' );
1583 is
( $error, 'onsite_checkout', 'A correct error code should be returned by CanBookBeRenewed for on-site checkout' );
1587 my $library = $builder->build({ source
=> 'Branch' });
1589 my $biblio = $builder->build_sample_biblio();
1591 my $item = $builder->build_sample_item(
1593 biblionumber
=> $biblio->biblionumber,
1594 library
=> $library->{branchcode
},
1599 my $patron = $builder->build({ source
=> 'Borrower', value
=> { branchcode
=> $library->{branchcode
}, categorycode
=> $patron_category->{categorycode
} } } );
1601 my $issue = AddIssue
( $patron, $item->barcode );
1604 issue_id
=> $issue->id(),
1605 itemnumber
=> $item->itemnumber,
1606 borrowernumber
=> $patron->{borrowernumber
},
1613 issue_id
=> $issue->id(),
1614 itemnumber
=> $item->itemnumber,
1615 borrowernumber
=> $patron->{borrowernumber
},
1620 is
( Koha
::Account
::Lines
->search({ issue_id
=> $issue->id })->count, 1, 'UpdateFine should not create a new accountline when updating an existing fine');
1623 subtest
'CanBookBeIssued & AllowReturnToBranch' => sub {
1626 my $homebranch = $builder->build( { source
=> 'Branch' } );
1627 my $holdingbranch = $builder->build( { source
=> 'Branch' } );
1628 my $otherbranch = $builder->build( { source
=> 'Branch' } );
1629 my $patron_1 = $builder->build_object( { class => 'Koha::Patrons', value
=> { categorycode
=> $patron_category->{categorycode
} } } );
1630 my $patron_2 = $builder->build_object( { class => 'Koha::Patrons', value
=> { categorycode
=> $patron_category->{categorycode
} } } );
1632 my $item = $builder->build_sample_item(
1634 homebranch
=> $homebranch->{branchcode
},
1635 holdingbranch
=> $holdingbranch->{branchcode
},
1639 set_userenv
($holdingbranch);
1641 my $issue = AddIssue
( $patron_1->unblessed, $item->barcode );
1642 is
( ref($issue), 'Koha::Checkout', 'AddIssue should return a Koha::Checkout object' );
1644 my ( $error, $question, $alerts );
1646 # AllowReturnToBranch == anywhere
1647 t
::lib
::Mocks
::mock_preference
( 'AllowReturnToBranch', 'anywhere' );
1648 ## Test that unknown barcodes don't generate internal server errors
1649 set_userenv
($homebranch);
1650 ( $error, $question, $alerts ) = CanBookBeIssued
( $patron_2, 'KohaIsAwesome' );
1651 ok
( $error->{UNKNOWN_BARCODE
}, '"KohaIsAwesome" is not a valid barcode as expected.' );
1652 ## Can be issued from homebranch
1653 set_userenv
($homebranch);
1654 ( $error, $question, $alerts ) = CanBookBeIssued
( $patron_2, $item->barcode );
1655 is
( keys(%$error) + keys(%$alerts), 0, 'There should not be any errors or alerts (impossible)' . str
($error, $question, $alerts) );
1656 is
( exists $question->{ISSUED_TO_ANOTHER
}, 1, 'ISSUED_TO_ANOTHER must be set' );
1657 ## Can be issued from holdingbranch
1658 set_userenv
($holdingbranch);
1659 ( $error, $question, $alerts ) = CanBookBeIssued
( $patron_2, $item->barcode );
1660 is
( keys(%$error) + keys(%$alerts), 0, 'There should not be any errors or alerts (impossible)' . str
($error, $question, $alerts) );
1661 is
( exists $question->{ISSUED_TO_ANOTHER
}, 1, 'ISSUED_TO_ANOTHER must be set' );
1662 ## Can be issued from another branch
1663 ( $error, $question, $alerts ) = CanBookBeIssued
( $patron_2, $item->barcode );
1664 is
( keys(%$error) + keys(%$alerts), 0, 'There should not be any errors or alerts (impossible)' . str
($error, $question, $alerts) );
1665 is
( exists $question->{ISSUED_TO_ANOTHER
}, 1, 'ISSUED_TO_ANOTHER must be set' );
1667 # AllowReturnToBranch == holdingbranch
1668 t
::lib
::Mocks
::mock_preference
( 'AllowReturnToBranch', 'holdingbranch' );
1669 ## Cannot be issued from homebranch
1670 set_userenv
($homebranch);
1671 ( $error, $question, $alerts ) = CanBookBeIssued
( $patron_2, $item->barcode );
1672 is
( keys(%$question) + keys(%$alerts), 0, 'There should not be any errors or alerts (impossible)' . str
($error, $question, $alerts) );
1673 is
( exists $error->{RETURN_IMPOSSIBLE
}, 1, 'RETURN_IMPOSSIBLE must be set' );
1674 is
( $error->{branch_to_return
}, $holdingbranch->{branchcode
}, 'branch_to_return matched holdingbranch' );
1675 ## Can be issued from holdinbranch
1676 set_userenv
($holdingbranch);
1677 ( $error, $question, $alerts ) = CanBookBeIssued
( $patron_2, $item->barcode );
1678 is
( keys(%$error) + keys(%$alerts), 0, 'There should not be any errors or alerts (impossible)' . str
($error, $question, $alerts) );
1679 is
( exists $question->{ISSUED_TO_ANOTHER
}, 1, 'ISSUED_TO_ANOTHER must be set' );
1680 ## Cannot be issued from another branch
1681 set_userenv
($otherbranch);
1682 ( $error, $question, $alerts ) = CanBookBeIssued
( $patron_2, $item->barcode );
1683 is
( keys(%$question) + keys(%$alerts), 0, 'There should not be any errors or alerts (impossible)' . str
($error, $question, $alerts) );
1684 is
( exists $error->{RETURN_IMPOSSIBLE
}, 1, 'RETURN_IMPOSSIBLE must be set' );
1685 is
( $error->{branch_to_return
}, $holdingbranch->{branchcode
}, 'branch_to_return matches holdingbranch' );
1687 # AllowReturnToBranch == homebranch
1688 t
::lib
::Mocks
::mock_preference
( 'AllowReturnToBranch', 'homebranch' );
1689 ## Can be issued from holdinbranch
1690 set_userenv
($homebranch);
1691 ( $error, $question, $alerts ) = CanBookBeIssued
( $patron_2, $item->barcode );
1692 is
( keys(%$error) + keys(%$alerts), 0, 'There should not be any errors or alerts (impossible)' . str
($error, $question, $alerts) );
1693 is
( exists $question->{ISSUED_TO_ANOTHER
}, 1, 'ISSUED_TO_ANOTHER must be set' );
1694 ## Cannot be issued from holdinbranch
1695 set_userenv
($holdingbranch);
1696 ( $error, $question, $alerts ) = CanBookBeIssued
( $patron_2, $item->barcode );
1697 is
( keys(%$question) + keys(%$alerts), 0, 'There should not be any errors or alerts (impossible)' . str
($error, $question, $alerts) );
1698 is
( exists $error->{RETURN_IMPOSSIBLE
}, 1, 'RETURN_IMPOSSIBLE must be set' );
1699 is
( $error->{branch_to_return
}, $homebranch->{branchcode
}, 'branch_to_return matches homebranch' );
1700 ## Cannot be issued from holdinbranch
1701 set_userenv
($otherbranch);
1702 ( $error, $question, $alerts ) = CanBookBeIssued
( $patron_2, $item->barcode );
1703 is
( keys(%$question) + keys(%$alerts), 0, 'There should not be any errors or alerts (impossible)' . str
($error, $question, $alerts) );
1704 is
( exists $error->{RETURN_IMPOSSIBLE
}, 1, 'RETURN_IMPOSSIBLE must be set' );
1705 is
( $error->{branch_to_return
}, $homebranch->{branchcode
}, 'branch_to_return matches homebranch' );
1707 # TODO t::lib::Mocks::mock_preference('AllowReturnToBranch', 'homeorholdingbranch');
1710 subtest
'AddIssue & AllowReturnToBranch' => sub {
1713 my $homebranch = $builder->build( { source
=> 'Branch' } );
1714 my $holdingbranch = $builder->build( { source
=> 'Branch' } );
1715 my $otherbranch = $builder->build( { source
=> 'Branch' } );
1716 my $patron_1 = $builder->build( { source
=> 'Borrower', value
=> { categorycode
=> $patron_category->{categorycode
} } } );
1717 my $patron_2 = $builder->build( { source
=> 'Borrower', value
=> { categorycode
=> $patron_category->{categorycode
} } } );
1719 my $item = $builder->build_sample_item(
1721 homebranch
=> $homebranch->{branchcode
},
1722 holdingbranch
=> $holdingbranch->{branchcode
},
1726 set_userenv
($holdingbranch);
1728 my $ref_issue = 'Koha::Checkout';
1729 my $issue = AddIssue
( $patron_1, $item->barcode );
1731 my ( $error, $question, $alerts );
1733 # AllowReturnToBranch == homebranch
1734 t
::lib
::Mocks
::mock_preference
( 'AllowReturnToBranch', 'anywhere' );
1735 ## Can be issued from homebranch
1736 set_userenv
($homebranch);
1737 is
( ref( AddIssue
( $patron_2, $item->barcode ) ), $ref_issue, 'AllowReturnToBranch - anywhere | Can be issued from homebranch');
1738 set_userenv
($holdingbranch); AddIssue
( $patron_1, $item->barcode ); # Reinsert the original issue
1739 ## Can be issued from holdinbranch
1740 set_userenv
($holdingbranch);
1741 is
( ref( AddIssue
( $patron_2, $item->barcode ) ), $ref_issue, 'AllowReturnToBranch - anywhere | Can be issued from holdingbranch');
1742 set_userenv
($holdingbranch); AddIssue
( $patron_1, $item->barcode ); # Reinsert the original issue
1743 ## Can be issued from another branch
1744 set_userenv
($otherbranch);
1745 is
( ref( AddIssue
( $patron_2, $item->barcode ) ), $ref_issue, 'AllowReturnToBranch - anywhere | Can be issued from otherbranch');
1746 set_userenv
($holdingbranch); AddIssue
( $patron_1, $item->barcode ); # Reinsert the original issue
1748 # AllowReturnToBranch == holdinbranch
1749 t
::lib
::Mocks
::mock_preference
( 'AllowReturnToBranch', 'holdingbranch' );
1750 ## Cannot be issued from homebranch
1751 set_userenv
($homebranch);
1752 is
( ref( AddIssue
( $patron_2, $item->barcode ) ), '', 'AllowReturnToBranch - holdingbranch | Cannot be issued from homebranch');
1753 ## Can be issued from holdingbranch
1754 set_userenv
($holdingbranch);
1755 is
( ref( AddIssue
( $patron_2, $item->barcode ) ), $ref_issue, 'AllowReturnToBranch - holdingbranch | Can be issued from holdingbranch');
1756 set_userenv
($holdingbranch); AddIssue
( $patron_1, $item->barcode ); # Reinsert the original issue
1757 ## Cannot be issued from another branch
1758 set_userenv
($otherbranch);
1759 is
( ref( AddIssue
( $patron_2, $item->barcode ) ), '', 'AllowReturnToBranch - holdingbranch | Cannot be issued from otherbranch');
1761 # AllowReturnToBranch == homebranch
1762 t
::lib
::Mocks
::mock_preference
( 'AllowReturnToBranch', 'homebranch' );
1763 ## Can be issued from homebranch
1764 set_userenv
($homebranch);
1765 is
( ref( AddIssue
( $patron_2, $item->barcode ) ), $ref_issue, 'AllowReturnToBranch - homebranch | Can be issued from homebranch' );
1766 set_userenv
($holdingbranch); AddIssue
( $patron_1, $item->barcode ); # Reinsert the original issue
1767 ## Cannot be issued from holdinbranch
1768 set_userenv
($holdingbranch);
1769 is
( ref( AddIssue
( $patron_2, $item->barcode ) ), '', 'AllowReturnToBranch - homebranch | Cannot be issued from holdingbranch' );
1770 ## Cannot be issued from another branch
1771 set_userenv
($otherbranch);
1772 is
( ref( AddIssue
( $patron_2, $item->barcode ) ), '', 'AllowReturnToBranch - homebranch | Cannot be issued from otherbranch' );
1773 # TODO t::lib::Mocks::mock_preference('AllowReturnToBranch', 'homeorholdingbranch');
1776 subtest
'CanBookBeIssued + Koha::Patron->is_debarred|has_overdues' => sub {
1779 my $library = $builder->build( { source
=> 'Branch' } );
1780 my $patron = $builder->build_object( { class => 'Koha::Patrons', value
=> { categorycode
=> $patron_category->{categorycode
} } } );
1781 my $item_1 = $builder->build_sample_item(
1783 library
=> $library->{branchcode
},
1786 my $item_2 = $builder->build_sample_item(
1788 library
=> $library->{branchcode
},
1792 my ( $error, $question, $alerts );
1794 # Patron cannot issue item_1, they have overdues
1795 my $yesterday = DateTime
->today( time_zone
=> C4
::Context
->tz() )->add( days
=> -1 );
1796 my $issue = AddIssue
( $patron->unblessed, $item_1->barcode, $yesterday ); # Add an overdue
1798 t
::lib
::Mocks
::mock_preference
( 'OverduesBlockCirc', 'confirmation' );
1799 ( $error, $question, $alerts ) = CanBookBeIssued
( $patron, $item_2->barcode );
1800 is
( keys(%$error) + keys(%$alerts), 0, 'No key for error and alert' . str
($error, $question, $alerts) );
1801 is
( $question->{USERBLOCKEDOVERDUE
}, 1, 'OverduesBlockCirc=confirmation, USERBLOCKEDOVERDUE should be set for question' );
1803 t
::lib
::Mocks
::mock_preference
( 'OverduesBlockCirc', 'block' );
1804 ( $error, $question, $alerts ) = CanBookBeIssued
( $patron, $item_2->barcode );
1805 is
( keys(%$question) + keys(%$alerts), 0, 'No key for question and alert ' . str
($error, $question, $alerts) );
1806 is
( $error->{USERBLOCKEDOVERDUE
}, 1, 'OverduesBlockCirc=block, USERBLOCKEDOVERDUE should be set for error' );
1808 # Patron cannot issue item_1, they are debarred
1809 my $tomorrow = DateTime
->today( time_zone
=> C4
::Context
->tz() )->add( days
=> 1 );
1810 Koha
::Patron
::Debarments
::AddDebarment
( { borrowernumber
=> $patron->borrowernumber, expiration
=> $tomorrow } );
1811 ( $error, $question, $alerts ) = CanBookBeIssued
( $patron, $item_2->barcode );
1812 is
( keys(%$question) + keys(%$alerts), 0, 'No key for question and alert ' . str
($error, $question, $alerts) );
1813 is
( $error->{USERBLOCKEDWITHENDDATE
}, output_pref
( { dt
=> $tomorrow, dateformat
=> 'sql', dateonly
=> 1 } ), 'USERBLOCKEDWITHENDDATE should be tomorrow' );
1815 Koha
::Patron
::Debarments
::AddDebarment
( { borrowernumber
=> $patron->borrowernumber } );
1816 ( $error, $question, $alerts ) = CanBookBeIssued
( $patron, $item_2->barcode );
1817 is
( keys(%$question) + keys(%$alerts), 0, 'No key for question and alert ' . str
($error, $question, $alerts) );
1818 is
( $error->{USERBLOCKEDNOENDDATE
}, '9999-12-31', 'USERBLOCKEDNOENDDATE should be 9999-12-31 for unlimited debarments' );
1821 subtest
'CanBookBeIssued + Statistic patrons "X"' => sub {
1824 my $library = $builder->build_object( { class => 'Koha::Libraries' } );
1825 my $patron_category_x = $builder->build_object(
1827 class => 'Koha::Patron::Categories',
1828 value
=> { category_type
=> 'X' }
1831 my $patron = $builder->build_object(
1833 class => 'Koha::Patrons',
1835 categorycode
=> $patron_category_x->categorycode,
1836 gonenoaddress
=> undef,
1843 my $item_1 = $builder->build_sample_item(
1845 library
=> $library->{branchcode
},
1849 my ( $error, $question, $alerts ) = CanBookBeIssued
( $patron, $item_1->barcode );
1850 is
( $error->{STATS
}, 1, '"Error" flag "STATS" must be set if CanBookBeIssued is called with a statistic patron (category_type=X)' );
1852 # TODO There are other tests to provide here
1855 subtest
'MultipleReserves' => sub {
1858 my $biblio = $builder->build_sample_biblio();
1860 my $branch = $library2->{branchcode
};
1862 my $item_1 = $builder->build_sample_item(
1864 biblionumber
=> $biblio->biblionumber,
1866 replacementprice
=> 12.00,
1871 my $item_2 = $builder->build_sample_item(
1873 biblionumber
=> $biblio->biblionumber,
1875 replacementprice
=> 12.00,
1882 my $resdate = undef;
1883 my $expdate = undef;
1885 my $checkitem = undef;
1888 my %renewing_borrower_data = (
1889 firstname
=> 'John',
1890 surname
=> 'Renewal',
1891 categorycode
=> $patron_category->{categorycode
},
1892 branchcode
=> $branch,
1894 my $renewing_borrowernumber = Koha
::Patron
->new(\
%renewing_borrower_data)->store->borrowernumber;
1895 my $renewing_borrower = Koha
::Patrons
->find( $renewing_borrowernumber )->unblessed;
1896 my $issue = AddIssue
( $renewing_borrower, $item_1->barcode);
1897 my $datedue = dt_from_string
( $issue->date_due() );
1898 is
(defined $issue->date_due(), 1, "item 1 checked out");
1899 my $borrowing_borrowernumber = Koha
::Checkouts
->find({ itemnumber
=> $item_1->itemnumber })->borrowernumber;
1901 my %reserving_borrower_data1 = (
1902 firstname
=> 'Katrin',
1903 surname
=> 'Reservation',
1904 categorycode
=> $patron_category->{categorycode
},
1905 branchcode
=> $branch,
1907 my $reserving_borrowernumber1 = Koha
::Patron
->new(\
%reserving_borrower_data1)->store->borrowernumber;
1910 branchcode
=> $branch,
1911 borrowernumber
=> $reserving_borrowernumber1,
1912 biblionumber
=> $biblio->biblionumber,
1913 priority
=> $priority,
1914 reservation_date
=> $resdate,
1915 expiration_date
=> $expdate,
1917 itemnumber
=> $checkitem,
1922 my %reserving_borrower_data2 = (
1923 firstname
=> 'Kirk',
1924 surname
=> 'Reservation',
1925 categorycode
=> $patron_category->{categorycode
},
1926 branchcode
=> $branch,
1928 my $reserving_borrowernumber2 = Koha
::Patron
->new(\
%reserving_borrower_data2)->store->borrowernumber;
1931 branchcode
=> $branch,
1932 borrowernumber
=> $reserving_borrowernumber2,
1933 biblionumber
=> $biblio->biblionumber,
1934 priority
=> $priority,
1935 reservation_date
=> $resdate,
1936 expiration_date
=> $expdate,
1938 itemnumber
=> $checkitem,
1944 my ( $renewokay, $error ) = CanBookBeRenewed
($renewing_borrowernumber, $item_1->itemnumber, 1);
1945 is
($renewokay, 0, 'Bug 17941 - should cover the case where 2 books are both reserved, so failing');
1948 my $item_3 = $builder->build_sample_item(
1950 biblionumber
=> $biblio->biblionumber,
1952 replacementprice
=> 12.00,
1958 my ( $renewokay, $error ) = CanBookBeRenewed
($renewing_borrowernumber, $item_1->itemnumber, 1);
1959 is
($renewokay, 1, 'Bug 17941 - should cover the case where 2 books are reserved, but a third one is available');
1963 subtest
'CanBookBeIssued + AllowMultipleIssuesOnABiblio' => sub {
1966 my $library = $builder->build( { source
=> 'Branch' } );
1967 my $patron = $builder->build_object( { class => 'Koha::Patrons', value
=> { categorycode
=> $patron_category->{categorycode
} } } );
1969 my $biblionumber = $builder->build_sample_biblio(
1971 branchcode
=> $library->{branchcode
},
1974 my $item_1 = $builder->build_sample_item(
1976 biblionumber
=> $biblionumber,
1977 library
=> $library->{branchcode
},
1981 my $item_2 = $builder->build_sample_item(
1983 biblionumber
=> $biblionumber,
1984 library
=> $library->{branchcode
},
1988 my ( $error, $question, $alerts );
1989 my $issue = AddIssue
( $patron->unblessed, $item_1->barcode, dt_from_string
->add( days
=> 1 ) );
1991 t
::lib
::Mocks
::mock_preference
('AllowMultipleIssuesOnABiblio', 0);
1992 ( $error, $question, $alerts ) = CanBookBeIssued
( $patron, $item_2->barcode );
1994 { error
=> $error, alerts
=> $alerts },
1995 { error
=> {}, alerts
=> {} },
1996 'No error or alert should be raised'
1998 is
( $question->{BIBLIO_ALREADY_ISSUED
}, 1, 'BIBLIO_ALREADY_ISSUED question flag should be set if AllowMultipleIssuesOnABiblio=0 and issue already exists' );
2000 t
::lib
::Mocks
::mock_preference
('AllowMultipleIssuesOnABiblio', 1);
2001 ( $error, $question, $alerts ) = CanBookBeIssued
( $patron, $item_2->barcode );
2003 { error
=> $error, question
=> $question, alerts
=> $alerts },
2004 { error
=> {}, question
=> {}, alerts
=> {} },
2005 'No BIBLIO_ALREADY_ISSUED flag should be set if AllowMultipleIssuesOnABiblio=1'
2008 # Add a subscription
2009 Koha
::Subscription
->new({ biblionumber
=> $biblionumber })->store;
2011 t
::lib
::Mocks
::mock_preference
('AllowMultipleIssuesOnABiblio', 0);
2012 ( $error, $question, $alerts ) = CanBookBeIssued
( $patron, $item_2->barcode );
2014 { error
=> $error, question
=> $question, alerts
=> $alerts },
2015 { error
=> {}, question
=> {}, alerts
=> {} },
2016 'No BIBLIO_ALREADY_ISSUED flag should be set if it is a subscription'
2019 t
::lib
::Mocks
::mock_preference
('AllowMultipleIssuesOnABiblio', 1);
2020 ( $error, $question, $alerts ) = CanBookBeIssued
( $patron, $item_2->barcode );
2022 { error
=> $error, question
=> $question, alerts
=> $alerts },
2023 { error
=> {}, question
=> {}, alerts
=> {} },
2024 'No BIBLIO_ALREADY_ISSUED flag should be set if it is a subscription'
2028 subtest
'AddReturn + CumulativeRestrictionPeriods' => sub {
2031 my $library = $builder->build( { source
=> 'Branch' } );
2032 my $patron = $builder->build( { source
=> 'Borrower', value
=> { categorycode
=> $patron_category->{categorycode
} } } );
2035 my $biblionumber = $builder->build_sample_biblio(
2037 branchcode
=> $library->{branchcode
},
2040 my $item_1 = $builder->build_sample_item(
2042 biblionumber
=> $biblionumber,
2043 library
=> $library->{branchcode
},
2046 my $item_2 = $builder->build_sample_item(
2048 biblionumber
=> $biblionumber,
2049 library
=> $library->{branchcode
},
2053 # And the circulation rule
2054 Koha
::CirculationRules
->search->delete;
2055 Koha
::CirculationRules
->set_rules(
2057 categorycode
=> undef,
2059 branchcode
=> undef,
2062 firstremind
=> 1, # 1 day of grace
2063 finedays
=> 2, # 2 days of fine per day of overdue
2064 lengthunit
=> 'days',
2069 # Patron cannot issue item_1, they have overdues
2070 my $now = dt_from_string
;
2071 my $five_days_ago = $now->clone->subtract( days
=> 5 );
2072 my $ten_days_ago = $now->clone->subtract( days
=> 10 );
2073 AddIssue
( $patron, $item_1->barcode, $five_days_ago ); # Add an overdue
2074 AddIssue
( $patron, $item_2->barcode, $ten_days_ago )
2075 ; # Add another overdue
2077 t
::lib
::Mocks
::mock_preference
( 'CumulativeRestrictionPeriods', '0' );
2078 AddReturn
( $item_1->barcode, $library->{branchcode
}, undef, $now );
2079 my $debarments = Koha
::Patron
::Debarments
::GetDebarments
(
2080 { borrowernumber
=> $patron->{borrowernumber
}, type
=> 'SUSPENSION' } );
2081 is
( scalar(@
$debarments), 1 );
2083 # FIXME Is it right? I'd have expected 5 * 2 - 1 instead
2084 # Same for the others
2085 my $expected_expiration = output_pref
(
2087 dt
=> $now->clone->add( days
=> ( 5 - 1 ) * 2 ),
2088 dateformat
=> 'sql',
2092 is
( $debarments->[0]->{expiration
}, $expected_expiration );
2094 AddReturn
( $item_2->barcode, $library->{branchcode
}, undef, $now );
2095 $debarments = Koha
::Patron
::Debarments
::GetDebarments
(
2096 { borrowernumber
=> $patron->{borrowernumber
}, type
=> 'SUSPENSION' } );
2097 is
( scalar(@
$debarments), 1 );
2098 $expected_expiration = output_pref
(
2100 dt
=> $now->clone->add( days
=> ( 10 - 1 ) * 2 ),
2101 dateformat
=> 'sql',
2105 is
( $debarments->[0]->{expiration
}, $expected_expiration );
2107 Koha
::Patron
::Debarments
::DelUniqueDebarment
(
2108 { borrowernumber
=> $patron->{borrowernumber
}, type
=> 'SUSPENSION' } );
2110 t
::lib
::Mocks
::mock_preference
( 'CumulativeRestrictionPeriods', '1' );
2111 AddIssue
( $patron, $item_1->barcode, $five_days_ago ); # Add an overdue
2112 AddIssue
( $patron, $item_2->barcode, $ten_days_ago )
2113 ; # Add another overdue
2114 AddReturn
( $item_1->barcode, $library->{branchcode
}, undef, $now );
2115 $debarments = Koha
::Patron
::Debarments
::GetDebarments
(
2116 { borrowernumber
=> $patron->{borrowernumber
}, type
=> 'SUSPENSION' } );
2117 is
( scalar(@
$debarments), 1 );
2118 $expected_expiration = output_pref
(
2120 dt
=> $now->clone->add( days
=> ( 5 - 1 ) * 2 ),
2121 dateformat
=> 'sql',
2125 is
( $debarments->[0]->{expiration
}, $expected_expiration );
2127 AddReturn
( $item_2->barcode, $library->{branchcode
}, undef, $now );
2128 $debarments = Koha
::Patron
::Debarments
::GetDebarments
(
2129 { borrowernumber
=> $patron->{borrowernumber
}, type
=> 'SUSPENSION' } );
2130 is
( scalar(@
$debarments), 1 );
2131 $expected_expiration = output_pref
(
2133 dt
=> $now->clone->add( days
=> ( 5 - 1 ) * 2 + ( 10 - 1 ) * 2 ),
2134 dateformat
=> 'sql',
2138 is
( $debarments->[0]->{expiration
}, $expected_expiration );
2141 subtest
'AddReturn + suspension_chargeperiod' => sub {
2144 my $library = $builder->build( { source
=> 'Branch' } );
2145 my $patron = $builder->build( { source
=> 'Borrower', value
=> { categorycode
=> $patron_category->{categorycode
} } } );
2147 my $biblionumber = $builder->build_sample_biblio(
2149 branchcode
=> $library->{branchcode
},
2152 my $item_1 = $builder->build_sample_item(
2154 biblionumber
=> $biblionumber,
2155 library
=> $library->{branchcode
},
2159 # And the issuing rule
2160 Koha
::CirculationRules
->search->delete;
2161 Koha
::CirculationRules
->set_rules(
2163 categorycode
=> '*',
2168 firstremind
=> 0, # 0 day of grace
2169 finedays
=> 2, # 2 days of fine per day of overdue
2170 suspension_chargeperiod
=> 1,
2171 lengthunit
=> 'days',
2176 my $now = dt_from_string
;
2177 my $five_days_ago = $now->clone->subtract( days
=> 5 );
2178 # We want to charge 2 days every day, without grace
2179 # With 5 days of overdue: 5 * Z
2180 my $expected_expiration = $now->clone->add( days
=> ( 5 * 2 ) / 1 );
2181 test_debarment_on_checkout
(
2184 library
=> $library,
2186 due_date
=> $five_days_ago,
2187 expiration_date
=> $expected_expiration,
2191 # Same with undef firstremind
2192 Koha
::CirculationRules
->search->delete;
2193 Koha
::CirculationRules
->set_rules(
2195 categorycode
=> '*',
2200 firstremind
=> undef, # 0 day of grace
2201 finedays
=> 2, # 2 days of fine per day of overdue
2202 suspension_chargeperiod
=> 1,
2203 lengthunit
=> 'days',
2208 my $now = dt_from_string
;
2209 my $five_days_ago = $now->clone->subtract( days
=> 5 );
2210 # We want to charge 2 days every day, without grace
2211 # With 5 days of overdue: 5 * Z
2212 my $expected_expiration = $now->clone->add( days
=> ( 5 * 2 ) / 1 );
2213 test_debarment_on_checkout
(
2216 library
=> $library,
2218 due_date
=> $five_days_ago,
2219 expiration_date
=> $expected_expiration,
2223 # We want to charge 2 days every 2 days, without grace
2224 # With 5 days of overdue: (5 * 2) / 2
2225 Koha
::CirculationRules
->set_rule(
2227 categorycode
=> undef,
2228 branchcode
=> undef,
2230 rule_name
=> 'suspension_chargeperiod',
2235 $expected_expiration = $now->clone->add( days
=> floor
( 5 * 2 ) / 2 );
2236 test_debarment_on_checkout
(
2239 library
=> $library,
2241 due_date
=> $five_days_ago,
2242 expiration_date
=> $expected_expiration,
2246 # We want to charge 2 days every 3 days, with 1 day of grace
2247 # With 5 days of overdue: ((5-1) / 3 ) * 2
2248 Koha
::CirculationRules
->set_rules(
2250 categorycode
=> undef,
2251 branchcode
=> undef,
2254 suspension_chargeperiod
=> 3,
2259 $expected_expiration = $now->clone->add( days
=> floor
( ( ( 5 - 1 ) / 3 ) * 2 ) );
2260 test_debarment_on_checkout
(
2263 library
=> $library,
2265 due_date
=> $five_days_ago,
2266 expiration_date
=> $expected_expiration,
2270 # Use finesCalendar to know if holiday must be skipped to calculate the due date
2271 # We want to charge 2 days every days, with 0 day of grace (to not burn brains)
2272 Koha
::CirculationRules
->set_rules(
2274 categorycode
=> undef,
2275 branchcode
=> undef,
2279 suspension_chargeperiod
=> 1,
2284 t
::lib
::Mocks
::mock_preference
('finesCalendar', 'noFinesWhenClosed');
2285 t
::lib
::Mocks
::mock_preference
('SuspensionsCalendar', 'noSuspensionsWhenClosed');
2287 # Adding a holiday 2 days ago
2288 my $calendar = C4
::Calendar
->new(branchcode
=> $library->{branchcode
});
2289 my $two_days_ago = $now->clone->subtract( days
=> 2 );
2290 $calendar->insert_single_holiday(
2291 day
=> $two_days_ago->day,
2292 month
=> $two_days_ago->month,
2293 year
=> $two_days_ago->year,
2294 title
=> 'holidayTest-2d',
2295 description
=> 'holidayDesc 2 days ago'
2297 # With 5 days of overdue, only 4 (x finedays=2) days must charged (one was an holiday)
2298 $expected_expiration = $now->clone->add( days
=> floor
( ( ( 5 - 0 - 1 ) / 1 ) * 2 ) );
2299 test_debarment_on_checkout
(
2302 library
=> $library,
2304 due_date
=> $five_days_ago,
2305 expiration_date
=> $expected_expiration,
2309 # Adding a holiday 2 days ahead, with finesCalendar=noFinesWhenClosed it should be skipped
2310 my $two_days_ahead = $now->clone->add( days
=> 2 );
2311 $calendar->insert_single_holiday(
2312 day
=> $two_days_ahead->day,
2313 month
=> $two_days_ahead->month,
2314 year
=> $two_days_ahead->year,
2315 title
=> 'holidayTest+2d',
2316 description
=> 'holidayDesc 2 days ahead'
2319 # Same as above, but we should skip D+2
2320 $expected_expiration = $now->clone->add( days
=> floor
( ( ( 5 - 0 - 1 ) / 1 ) * 2 ) + 1 );
2321 test_debarment_on_checkout
(
2324 library
=> $library,
2326 due_date
=> $five_days_ago,
2327 expiration_date
=> $expected_expiration,
2331 # Adding another holiday, day of expiration date
2332 my $expected_expiration_dt = dt_from_string
($expected_expiration);
2333 $calendar->insert_single_holiday(
2334 day
=> $expected_expiration_dt->day,
2335 month
=> $expected_expiration_dt->month,
2336 year
=> $expected_expiration_dt->year,
2337 title
=> 'holidayTest_exp',
2338 description
=> 'holidayDesc on expiration date'
2340 # Expiration date will be the day after
2341 test_debarment_on_checkout
(
2344 library
=> $library,
2346 due_date
=> $five_days_ago,
2347 expiration_date
=> $expected_expiration_dt->clone->add( days
=> 1 ),
2351 test_debarment_on_checkout
(
2354 library
=> $library,
2356 return_date
=> $now->clone->add(days
=> 5),
2357 expiration_date
=> $now->clone->add(days
=> 5 + (5 * 2 - 1) ),
2361 test_debarment_on_checkout
(
2364 library
=> $library,
2366 due_date
=> $now->clone->add(days
=> 1),
2367 return_date
=> $now->clone->add(days
=> 5),
2368 expiration_date
=> $now->clone->add(days
=> 5 + (4 * 2 - 1) ),
2374 subtest
'CanBookBeIssued + AutoReturnCheckedOutItems' => sub {
2377 my $library = $builder->build_object( { class => 'Koha::Libraries' } );
2378 my $patron1 = $builder->build_object(
2380 class => 'Koha::Patrons',
2382 library
=> $library->branchcode,
2383 categorycode
=> $patron_category->{categorycode
}
2387 my $patron2 = $builder->build_object(
2389 class => 'Koha::Patrons',
2391 library
=> $library->branchcode,
2392 categorycode
=> $patron_category->{categorycode
}
2397 t
::lib
::Mocks
::mock_userenv
({ branchcode
=> $library->branchcode });
2399 my $item = $builder->build_sample_item(
2401 library
=> $library->branchcode,
2405 my ( $error, $question, $alerts );
2406 my $issue = AddIssue
( $patron1->unblessed, $item->barcode );
2408 t
::lib
::Mocks
::mock_preference
('AutoReturnCheckedOutItems', 0);
2409 ( $error, $question, $alerts ) = CanBookBeIssued
( $patron2, $item->barcode );
2410 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' );
2412 t
::lib
::Mocks
::mock_preference
('AutoReturnCheckedOutItems', 1);
2413 ( $error, $question, $alerts ) = CanBookBeIssued
( $patron2, $item->barcode );
2414 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' );
2416 t
::lib
::Mocks
::mock_preference
('AutoReturnCheckedOutItems', 0);
2420 subtest
'AddReturn | is_overdue' => sub {
2423 t
::lib
::Mocks
::mock_preference
('MarkLostItemsAsReturned', 'batchmod|moredetail|cronjob|additem|pendingreserves|onpayment');
2424 t
::lib
::Mocks
::mock_preference
('CalculateFinesOnReturn', 1);
2425 t
::lib
::Mocks
::mock_preference
('finesMode', 'production');
2426 t
::lib
::Mocks
::mock_preference
('MaxFine', '100');
2428 my $library = $builder->build( { source
=> 'Branch' } );
2429 my $patron = $builder->build( { source
=> 'Borrower', value
=> { categorycode
=> $patron_category->{categorycode
} } } );
2430 my $manager = $builder->build_object({ class => "Koha::Patrons" });
2431 t
::lib
::Mocks
::mock_userenv
({ patron
=> $manager, branchcode
=> $manager->branchcode });
2433 my $item = $builder->build_sample_item(
2435 library
=> $library->{branchcode
},
2436 replacementprice
=> 7
2440 Koha
::CirculationRules
->search->delete;
2441 Koha
::CirculationRules
->set_rules(
2443 categorycode
=> undef,
2445 branchcode
=> undef,
2448 lengthunit
=> 'days',
2449 fine
=> 1, # Charge 1 every day of overdue
2455 my $now = dt_from_string
;
2456 my $one_day_ago = $now->clone->subtract( days
=> 1 );
2457 my $two_days_ago = $now->clone->subtract( days
=> 2 );
2458 my $five_days_ago = $now->clone->subtract( days
=> 5 );
2459 my $ten_days_ago = $now->clone->subtract( days
=> 10 );
2460 $patron = Koha
::Patrons
->find( $patron->{borrowernumber
} );
2462 # No return date specified, today will be used => 10 days overdue charged
2463 AddIssue
( $patron->unblessed, $item->barcode, $ten_days_ago ); # date due was 10d ago
2464 AddReturn
( $item->barcode, $library->{branchcode
} );
2465 is
( int($patron->account->balance()), 10, 'Patron should have a charge of 10 (10 days x 1)' );
2466 Koha
::Account
::Lines
->search({ borrowernumber
=> $patron->borrowernumber })->delete;
2468 # specify return date 5 days before => no overdue charged
2469 AddIssue
( $patron->unblessed, $item->barcode, $five_days_ago ); # date due was 5d ago
2470 AddReturn
( $item->barcode, $library->{branchcode
}, undef, $ten_days_ago );
2471 is
( int($patron->account->balance()), 0, 'AddReturn: pass return_date => no overdue' );
2472 Koha
::Account
::Lines
->search({ borrowernumber
=> $patron->borrowernumber })->delete;
2474 # specify return date 5 days later => 5 days overdue charged
2475 AddIssue
( $patron->unblessed, $item->barcode, $ten_days_ago ); # date due was 10d ago
2476 AddReturn
( $item->barcode, $library->{branchcode
}, undef, $five_days_ago );
2477 is
( int($patron->account->balance()), 5, 'AddReturn: pass return_date => overdue' );
2478 Koha
::Account
::Lines
->search({ borrowernumber
=> $patron->borrowernumber })->delete;
2480 # specify return date 5 days later, specify exemptfine => no overdue charge
2481 AddIssue
( $patron->unblessed, $item->barcode, $ten_days_ago ); # date due was 10d ago
2482 AddReturn
( $item->barcode, $library->{branchcode
}, 1, $five_days_ago );
2483 is
( int($patron->account->balance()), 0, 'AddReturn: pass return_date => no overdue' );
2484 Koha
::Account
::Lines
->search({ borrowernumber
=> $patron->borrowernumber })->delete;
2486 subtest
'bug 22877 | Lost item return' => sub {
2490 my $issue = AddIssue
( $patron->unblessed, $item->barcode, $ten_days_ago ); # date due was 10d ago
2492 # Fake fines cronjob on this checkout
2494 CalcFine
( $item, $patron->categorycode, $library->{branchcode
},
2495 $ten_days_ago, $now );
2498 issue_id
=> $issue->issue_id,
2499 itemnumber
=> $item->itemnumber,
2500 borrowernumber
=> $patron->borrowernumber,
2502 due
=> output_pref
($ten_days_ago)
2505 is
( int( $patron->account->balance() ),
2506 10, "Overdue fine of 10 days overdue" );
2508 # Fake longoverdue with charge and not marking returned
2509 LostItem
( $item->itemnumber, 'cronjob', 0 );
2510 is
( int( $patron->account->balance() ),
2511 17, "Lost fine of 7 plus 10 days overdue" );
2513 # Now we return it today
2514 AddReturn
( $item->barcode, $library->{branchcode
} );
2515 is
( int( $patron->account->balance() ),
2516 17, "Should have a single 10 days overdue fine and lost charge" );
2519 Koha
::Account
::Lines
->search({ borrowernumber
=> $patron->borrowernumber })->delete;
2522 subtest
'bug 8338 | backdated return resulting in zero amount fine' => sub {
2526 t
::lib
::Mocks
::mock_preference
('CalculateFinesOnBackdate', 1);
2528 my $issue = AddIssue
( $patron->unblessed, $item->barcode, $one_day_ago ); # date due was 1d ago
2530 # Fake fines cronjob on this checkout
2532 CalcFine
( $item, $patron->categorycode, $library->{branchcode
},
2533 $one_day_ago, $now );
2536 issue_id
=> $issue->issue_id,
2537 itemnumber
=> $item->itemnumber,
2538 borrowernumber
=> $patron->borrowernumber,
2540 due
=> output_pref
($one_day_ago)
2543 is
( int( $patron->account->balance() ),
2544 1, "Overdue fine of 1 day overdue" );
2546 # Backdated return (dropbox mode example - charge should be removed)
2547 AddReturn
( $item->barcode, $library->{branchcode
}, 1, $one_day_ago );
2548 is
( int( $patron->account->balance() ),
2549 0, "Overdue fine should be annulled" );
2550 my $lines = Koha
::Account
::Lines
->search({ borrowernumber
=> $patron->borrowernumber });
2551 is
( $lines->count, 0, "Overdue fine accountline has been removed");
2553 $issue = AddIssue
( $patron->unblessed, $item->barcode, $two_days_ago ); # date due was 2d ago
2555 # Fake fines cronjob on this checkout
2557 CalcFine
( $item, $patron->categorycode, $library->{branchcode
},
2558 $two_days_ago, $now );
2561 issue_id
=> $issue->issue_id,
2562 itemnumber
=> $item->itemnumber,
2563 borrowernumber
=> $patron->borrowernumber,
2565 due
=> output_pref
($one_day_ago)
2568 is
( int( $patron->account->balance() ),
2569 2, "Overdue fine of 2 days overdue" );
2571 # Payment made against fine
2572 $lines = Koha
::Account
::Lines
->search({ borrowernumber
=> $patron->borrowernumber });
2573 my $debit = $lines->next;
2574 my $credit = $patron->account->add_credit(
2578 interface
=> 'test',
2582 { debits
=> [ $debit ], offset_type
=> 'Payment' } );
2584 is
( int( $patron->account->balance() ),
2585 0, "Overdue fine should be paid off" );
2586 $lines = Koha
::Account
::Lines
->search({ borrowernumber
=> $patron->borrowernumber });
2587 is
( $lines->count, 2, "Overdue (debit) and Payment (credit) present");
2588 my $line = $lines->next;
2589 is
( $line->amount+0, 2, "Overdue fine amount remains as 2 days");
2590 is
( $line->amountoutstanding+0, 0, "Overdue fine amountoutstanding reduced to 0");
2592 # Backdated return (dropbox mode example - charge should be removed)
2593 AddReturn
( $item->barcode, $library->{branchcode
}, undef, $one_day_ago );
2594 is
( int( $patron->account->balance() ),
2595 -1, "Refund credit has been applied" );
2596 $lines = Koha
::Account
::Lines
->search({ borrowernumber
=> $patron->borrowernumber }, { order_by
=> { '-asc' => 'accountlines_id' }});
2597 is
( $lines->count, 3, "Overdue (debit), Payment (credit) and Refund (credit) are all present");
2599 $line = $lines->next;
2600 is
($line->amount+0,1, "Overdue fine amount has been reduced to 1");
2601 is
($line->amountoutstanding+0,0, "Overdue fine amount outstanding remains at 0");
2602 is
($line->status,'RETURNED', "Overdue fine is fixed");
2603 $line = $lines->next;
2604 is
($line->amount+0,-2, "Original payment amount remains as 2");
2605 is
($line->amountoutstanding+0,0, "Original payment remains applied");
2606 $line = $lines->next;
2607 is
($line->amount+0,-1, "Refund amount correctly set to 1");
2608 is
($line->amountoutstanding+0,-1, "Refund amount outstanding unspent");
2611 Koha
::Account
::Lines
->search({ borrowernumber
=> $patron->borrowernumber })->delete;
2614 subtest
'bug 25417 | backdated return + exemptfine' => sub {
2618 t
::lib
::Mocks
::mock_preference
('CalculateFinesOnBackdate', 1);
2620 my $issue = AddIssue
( $patron->unblessed, $item->barcode, $one_day_ago ); # date due was 1d ago
2622 # Fake fines cronjob on this checkout
2624 CalcFine
( $item, $patron->categorycode, $library->{branchcode
},
2625 $one_day_ago, $now );
2628 issue_id
=> $issue->issue_id,
2629 itemnumber
=> $item->itemnumber,
2630 borrowernumber
=> $patron->borrowernumber,
2632 due
=> output_pref
($one_day_ago)
2635 is
( int( $patron->account->balance() ),
2636 1, "Overdue fine of 1 day overdue" );
2638 # Backdated return (dropbox mode example - charge should no longer exist)
2639 AddReturn
( $item->barcode, $library->{branchcode
}, 1, $one_day_ago );
2640 is
( int( $patron->account->balance() ),
2641 0, "Overdue fine should be annulled" );
2644 Koha
::Account
::Lines
->search({ borrowernumber
=> $patron->borrowernumber })->delete;
2647 subtest
'bug 24075 | backdated return with return datetime matching due datetime' => sub {
2650 t
::lib
::Mocks
::mock_preference
( 'CalculateFinesOnBackdate', 1 );
2652 my $due_date = dt_from_string
;
2653 my $issue = AddIssue
( $patron->unblessed, $item->barcode, $due_date );
2658 issue_id
=> $issue->issue_id,
2659 itemnumber
=> $item->itemnumber,
2660 borrowernumber
=> $patron->borrowernumber,
2662 due
=> output_pref
($due_date)
2665 is
( $patron->account->balance(),
2666 0.25, 'Overdue fine of $0.25 recorded' );
2668 # Backdate return to exact due date and time
2669 my ( undef, $message ) =
2670 AddReturn
( $item->barcode, $library->{branchcode
},
2674 Koha
::Account
::Lines
->find( { issue_id
=> $issue->id } );
2675 ok
( !$accountline, 'accountline removed as expected' );
2678 $issue = AddIssue
( $patron->unblessed, $item->barcode, $due_date );
2683 issue_id
=> $issue->issue_id,
2684 itemnumber
=> $item->itemnumber,
2685 borrowernumber
=> $patron->borrowernumber,
2687 due
=> output_pref
($due_date)
2690 is
( $patron->account->balance(),
2691 0.25, 'Overdue fine of $0.25 recorded' );
2693 # Partial pay accruing fine
2694 my $lines = Koha
::Account
::Lines
->search(
2696 borrowernumber
=> $patron->borrowernumber,
2697 issue_id
=> $issue->id
2700 my $debit = $lines->next;
2701 my $credit = $patron->account->add_credit(
2705 interface
=> 'test',
2708 $credit->apply( { debits
=> [$debit], offset_type
=> 'Payment' } );
2710 is
( $patron->account->balance(), .05, 'Overdue fine reduced to $0.05' );
2712 # Backdate return to exact due date and time
2713 ( undef, $message ) =
2714 AddReturn
( $item->barcode, $library->{branchcode
},
2717 $lines = Koha
::Account
::Lines
->search(
2719 borrowernumber
=> $patron->borrowernumber,
2720 issue_id
=> $issue->id
2723 $accountline = $lines->next;
2724 is
( $accountline->amountoutstanding + 0,
2725 0, 'Partially paid fee amount outstanding was reduced to 0' );
2726 is
( $accountline->amount + 0,
2727 0, 'Partially paid fee amount was reduced to 0' );
2728 is
( $patron->account->balance(), -0.20, 'Patron refund recorded' );
2731 Koha
::Account
::Lines
->search(
2732 { borrowernumber
=> $patron->borrowernumber } )->delete;
2735 subtest
'enh 23091 | Lost item return policies' => sub {
2738 my $manager = $builder->build_object({ class => "Koha::Patrons" });
2740 my $branchcode_false =
2741 $builder->build( { source
=> 'Branch' } )->{branchcode
};
2742 my $specific_rule_false = $builder->build(
2744 source
=> 'CirculationRule',
2746 branchcode
=> $branchcode_false,
2747 categorycode
=> undef,
2749 rule_name
=> 'lostreturn',
2754 my $branchcode_refund =
2755 $builder->build( { source
=> 'Branch' } )->{branchcode
};
2756 my $specific_rule_refund = $builder->build(
2758 source
=> 'CirculationRule',
2760 branchcode
=> $branchcode_refund,
2761 categorycode
=> undef,
2763 rule_name
=> 'lostreturn',
2764 rule_value
=> 'refund'
2768 my $branchcode_restore =
2769 $builder->build( { source
=> 'Branch' } )->{branchcode
};
2770 my $specific_rule_restore = $builder->build(
2772 source
=> 'CirculationRule',
2774 branchcode
=> $branchcode_restore,
2775 categorycode
=> undef,
2777 rule_name
=> 'lostreturn',
2778 rule_value
=> 'restore'
2782 my $branchcode_charge =
2783 $builder->build( { source
=> 'Branch' } )->{branchcode
};
2784 my $specific_rule_charge = $builder->build(
2786 source
=> 'CirculationRule',
2788 branchcode
=> $branchcode_charge,
2789 categorycode
=> undef,
2791 rule_name
=> 'lostreturn',
2792 rule_value
=> 'charge'
2797 my $replacement_amount = 99.00;
2798 t
::lib
::Mocks
::mock_preference
( 'AllowReturnToBranch', 'anywhere' );
2799 t
::lib
::Mocks
::mock_preference
( 'WhenLostChargeReplacementFee', 1 );
2800 t
::lib
::Mocks
::mock_preference
( 'WhenLostForgiveFine', 0 );
2801 t
::lib
::Mocks
::mock_preference
( 'BlockReturnOfLostItems', 0 );
2802 t
::lib
::Mocks
::mock_preference
( 'RefundLostOnReturnControl',
2804 t
::lib
::Mocks
::mock_preference
( 'NoRefundOnLostReturnedItemsAge',
2807 subtest
'lostreturn | false' => sub {
2810 t
::lib
::Mocks
::mock_userenv
({ patron
=> $manager, branchcode
=> $branchcode_false });
2812 my $item = $builder->build_sample_item(
2814 replacementprice
=> $replacement_amount
2819 my $issue = C4
::Circulation
::AddIssue
( $patron->unblessed, $item->barcode, $ten_days_ago );
2821 # Fake fines cronjob on this checkout
2823 CalcFine
( $item, $patron->categorycode, $library->{branchcode
},
2824 $ten_days_ago, $now );
2827 issue_id
=> $issue->issue_id,
2828 itemnumber
=> $item->itemnumber,
2829 borrowernumber
=> $patron->borrowernumber,
2831 due
=> output_pref
($ten_days_ago)
2834 my $overdue_fees = Koha
::Account
::Lines
->search(
2836 borrowernumber
=> $patron->id,
2837 itemnumber
=> $item->itemnumber,
2838 debit_type_code
=> 'OVERDUE'
2841 is
( $overdue_fees->count, 1, 'Overdue item fee produced' );
2842 my $overdue_fee = $overdue_fees->next;
2843 is
( $overdue_fee->amount + 0,
2844 10, 'The right OVERDUE amount is generated' );
2845 is
( $overdue_fee->amountoutstanding + 0,
2847 'The right OVERDUE amountoutstanding is generated' );
2849 # Simulate item marked as lost
2850 $item->itemlost(3)->store;
2851 C4
::Circulation
::LostItem
( $item->itemnumber, 1 );
2853 my $lost_fee_lines = Koha
::Account
::Lines
->search(
2855 borrowernumber
=> $patron->id,
2856 itemnumber
=> $item->itemnumber,
2857 debit_type_code
=> 'LOST'
2860 is
( $lost_fee_lines->count, 1, 'Lost item fee produced' );
2861 my $lost_fee_line = $lost_fee_lines->next;
2862 is
( $lost_fee_line->amount + 0,
2863 $replacement_amount, 'The right LOST amount is generated' );
2864 is
( $lost_fee_line->amountoutstanding + 0,
2865 $replacement_amount,
2866 'The right LOST amountoutstanding is generated' );
2867 is
( $lost_fee_line->status, undef, 'The LOST status was not set' );
2870 my ( $returned, $message ) =
2871 AddReturn
( $item->barcode, $branchcode_false, undef, $five_days_ago );
2873 $overdue_fee->discard_changes;
2874 is
( $overdue_fee->amount + 0,
2875 10, 'The OVERDUE amount is left intact' );
2876 is
( $overdue_fee->amountoutstanding + 0,
2878 'The OVERDUE amountoutstanding is left intact' );
2880 $lost_fee_line->discard_changes;
2881 is
( $lost_fee_line->amount + 0,
2882 $replacement_amount, 'The LOST amount is left intact' );
2883 is
( $lost_fee_line->amountoutstanding + 0,
2884 $replacement_amount,
2885 'The LOST amountoutstanding is left intact' );
2886 # FIXME: Should we set the LOST fee status to 'FOUND' regardless of whether we're refunding or not?
2887 is
( $lost_fee_line->status, undef, 'The LOST status was not set' );
2890 subtest
'lostreturn | refund' => sub {
2893 t
::lib
::Mocks
::mock_userenv
({ patron
=> $manager, branchcode
=> $branchcode_refund });
2895 my $item = $builder->build_sample_item(
2897 replacementprice
=> $replacement_amount
2902 my $issue = C4
::Circulation
::AddIssue
( $patron->unblessed, $item->barcode, $ten_days_ago );
2904 # Fake fines cronjob on this checkout
2906 CalcFine
( $item, $patron->categorycode, $library->{branchcode
},
2907 $ten_days_ago, $now );
2910 issue_id
=> $issue->issue_id,
2911 itemnumber
=> $item->itemnumber,
2912 borrowernumber
=> $patron->borrowernumber,
2914 due
=> output_pref
($ten_days_ago)
2917 my $overdue_fees = Koha
::Account
::Lines
->search(
2919 borrowernumber
=> $patron->id,
2920 itemnumber
=> $item->itemnumber,
2921 debit_type_code
=> 'OVERDUE'
2924 is
( $overdue_fees->count, 1, 'Overdue item fee produced' );
2925 my $overdue_fee = $overdue_fees->next;
2926 is
( $overdue_fee->amount + 0,
2927 10, 'The right OVERDUE amount is generated' );
2928 is
( $overdue_fee->amountoutstanding + 0,
2930 'The right OVERDUE amountoutstanding is generated' );
2932 # Simulate item marked as lost
2933 $item->itemlost(3)->store;
2934 C4
::Circulation
::LostItem
( $item->itemnumber, 1 );
2936 my $lost_fee_lines = Koha
::Account
::Lines
->search(
2938 borrowernumber
=> $patron->id,
2939 itemnumber
=> $item->itemnumber,
2940 debit_type_code
=> 'LOST'
2943 is
( $lost_fee_lines->count, 1, 'Lost item fee produced' );
2944 my $lost_fee_line = $lost_fee_lines->next;
2945 is
( $lost_fee_line->amount + 0,
2946 $replacement_amount, 'The right LOST amount is generated' );
2947 is
( $lost_fee_line->amountoutstanding + 0,
2948 $replacement_amount,
2949 'The right LOST amountoutstanding is generated' );
2950 is
( $lost_fee_line->status, undef, 'The LOST status was not set' );
2952 # Return the lost item
2953 my ( undef, $message ) =
2954 AddReturn
( $item->barcode, $branchcode_refund, undef, $five_days_ago );
2956 $overdue_fee->discard_changes;
2957 is
( $overdue_fee->amount + 0,
2958 10, 'The OVERDUE amount is left intact' );
2959 is
( $overdue_fee->amountoutstanding + 0,
2961 'The OVERDUE amountoutstanding is left intact' );
2963 $lost_fee_line->discard_changes;
2964 is
( $lost_fee_line->amount + 0,
2965 $replacement_amount, 'The LOST amount is left intact' );
2966 is
( $lost_fee_line->amountoutstanding + 0,
2968 'The LOST amountoutstanding is refunded' );
2969 is
( $lost_fee_line->status, 'FOUND', 'The LOST status was set to FOUND' );
2972 subtest
'lostreturn | restore' => sub {
2975 t
::lib
::Mocks
::mock_userenv
({ patron
=> $manager, branchcode
=> $branchcode_restore });
2977 my $item = $builder->build_sample_item(
2979 replacementprice
=> $replacement_amount
2984 my $issue = C4
::Circulation
::AddIssue
( $patron->unblessed, $item->barcode , $ten_days_ago);
2986 # Fake fines cronjob on this checkout
2988 CalcFine
( $item, $patron->categorycode, $library->{branchcode
},
2989 $ten_days_ago, $now );
2992 issue_id
=> $issue->issue_id,
2993 itemnumber
=> $item->itemnumber,
2994 borrowernumber
=> $patron->borrowernumber,
2996 due
=> output_pref
($ten_days_ago)
2999 my $overdue_fees = Koha
::Account
::Lines
->search(
3001 borrowernumber
=> $patron->id,
3002 itemnumber
=> $item->itemnumber,
3003 debit_type_code
=> 'OVERDUE'
3006 is
( $overdue_fees->count, 1, 'Overdue item fee produced' );
3007 my $overdue_fee = $overdue_fees->next;
3008 is
( $overdue_fee->amount + 0,
3009 10, 'The right OVERDUE amount is generated' );
3010 is
( $overdue_fee->amountoutstanding + 0,
3012 'The right OVERDUE amountoutstanding is generated' );
3014 # Simulate item marked as lost
3015 $item->itemlost(3)->store;
3016 C4
::Circulation
::LostItem
( $item->itemnumber, 1 );
3018 my $lost_fee_lines = Koha
::Account
::Lines
->search(
3020 borrowernumber
=> $patron->id,
3021 itemnumber
=> $item->itemnumber,
3022 debit_type_code
=> 'LOST'
3025 is
( $lost_fee_lines->count, 1, 'Lost item fee produced' );
3026 my $lost_fee_line = $lost_fee_lines->next;
3027 is
( $lost_fee_line->amount + 0,
3028 $replacement_amount, 'The right LOST amount is generated' );
3029 is
( $lost_fee_line->amountoutstanding + 0,
3030 $replacement_amount,
3031 'The right LOST amountoutstanding is generated' );
3032 is
( $lost_fee_line->status, undef, 'The LOST status was not set' );
3034 # Simulate refunding overdue fees upon marking item as lost
3035 my $overdue_forgive = $patron->account->add_credit(
3038 user_id
=> $manager->borrowernumber,
3039 library_id
=> $branchcode_restore,
3040 interface
=> 'test',
3042 item_id
=> $item->itemnumber
3045 $overdue_forgive->apply(
3046 { debits
=> [$overdue_fee], offset_type
=> 'Forgiven' } );
3047 $overdue_fee->discard_changes;
3048 is
($overdue_fee->amountoutstanding + 0, 0, 'Overdue fee forgiven');
3051 my ( undef, $message ) =
3052 AddReturn
( $item->barcode, $branchcode_restore, undef, $five_days_ago );
3054 $overdue_fee->discard_changes;
3055 is
( $overdue_fee->amount + 0,
3056 10, 'The OVERDUE amount is left intact' );
3057 is
( $overdue_fee->amountoutstanding + 0,
3059 'The OVERDUE amountoutstanding is restored' );
3061 $lost_fee_line->discard_changes;
3062 is
( $lost_fee_line->amount + 0,
3063 $replacement_amount, 'The LOST amount is left intact' );
3064 is
( $lost_fee_line->amountoutstanding + 0,
3066 'The LOST amountoutstanding is refunded' );
3067 is
( $lost_fee_line->status, 'FOUND', 'The LOST status was set to FOUND' );
3070 subtest
'lostreturn | charge' => sub {
3073 t
::lib
::Mocks
::mock_userenv
({ patron
=> $manager, branchcode
=> $branchcode_charge });
3075 my $item = $builder->build_sample_item(
3077 replacementprice
=> $replacement_amount
3082 my $issue = C4
::Circulation
::AddIssue
( $patron->unblessed, $item->barcode, $ten_days_ago );
3084 # Fake fines cronjob on this checkout
3086 CalcFine
( $item, $patron->categorycode, $library->{branchcode
},
3087 $ten_days_ago, $now );
3090 issue_id
=> $issue->issue_id,
3091 itemnumber
=> $item->itemnumber,
3092 borrowernumber
=> $patron->borrowernumber,
3094 due
=> output_pref
($ten_days_ago)
3097 my $overdue_fees = Koha
::Account
::Lines
->search(
3099 borrowernumber
=> $patron->id,
3100 itemnumber
=> $item->itemnumber,
3101 debit_type_code
=> 'OVERDUE'
3104 is
( $overdue_fees->count, 1, 'Overdue item fee produced' );
3105 my $overdue_fee = $overdue_fees->next;
3106 is
( $overdue_fee->amount + 0,
3107 10, 'The right OVERDUE amount is generated' );
3108 is
( $overdue_fee->amountoutstanding + 0,
3110 'The right OVERDUE amountoutstanding is generated' );
3112 # Simulate item marked as lost
3113 $item->itemlost(3)->store;
3114 C4
::Circulation
::LostItem
( $item->itemnumber, 1 );
3116 my $lost_fee_lines = Koha
::Account
::Lines
->search(
3118 borrowernumber
=> $patron->id,
3119 itemnumber
=> $item->itemnumber,
3120 debit_type_code
=> 'LOST'
3123 is
( $lost_fee_lines->count, 1, 'Lost item fee produced' );
3124 my $lost_fee_line = $lost_fee_lines->next;
3125 is
( $lost_fee_line->amount + 0,
3126 $replacement_amount, 'The right LOST amount is generated' );
3127 is
( $lost_fee_line->amountoutstanding + 0,
3128 $replacement_amount,
3129 'The right LOST amountoutstanding is generated' );
3130 is
( $lost_fee_line->status, undef, 'The LOST status was not set' );
3132 # Simulate refunding overdue fees upon marking item as lost
3133 my $overdue_forgive = $patron->account->add_credit(
3136 user_id
=> $manager->borrowernumber,
3137 library_id
=> $branchcode_charge,
3138 interface
=> 'test',
3140 item_id
=> $item->itemnumber
3143 $overdue_forgive->apply(
3144 { debits
=> [$overdue_fee], offset_type
=> 'Forgiven' } );
3145 $overdue_fee->discard_changes;
3146 is
($overdue_fee->amountoutstanding + 0, 0, 'Overdue fee forgiven');
3149 my ( undef, $message ) =
3150 AddReturn
( $item->barcode, $branchcode_charge, undef, $five_days_ago );
3152 $lost_fee_line->discard_changes;
3153 is
( $lost_fee_line->amount + 0,
3154 $replacement_amount, 'The LOST amount is left intact' );
3155 is
( $lost_fee_line->amountoutstanding + 0,
3157 'The LOST amountoutstanding is refunded' );
3158 is
( $lost_fee_line->status, 'FOUND', 'The LOST status was set to FOUND' );
3160 $overdue_fees = Koha
::Account
::Lines
->search(
3162 borrowernumber
=> $patron->id,
3163 itemnumber
=> $item->itemnumber,
3164 debit_type_code
=> 'OVERDUE'
3167 order_by
=> { '-asc' => 'accountlines_id'}
3170 is
( $overdue_fees->count, 2, 'A second OVERDUE fee has been added' );
3171 $overdue_fee = $overdue_fees->next;
3172 is
( $overdue_fee->amount + 0,
3173 10, 'The original OVERDUE amount is left intact' );
3174 is
( $overdue_fee->amountoutstanding + 0,
3176 'The original OVERDUE amountoutstanding is left as forgiven' );
3177 $overdue_fee = $overdue_fees->next;
3178 is
( $overdue_fee->amount + 0,
3179 5, 'The new OVERDUE amount is correct for the backdated return' );
3180 is
( $overdue_fee->amountoutstanding + 0,
3182 'The new OVERDUE amountoutstanding is correct for the backdated return' );
3187 subtest
'_FixOverduesOnReturn' => sub {
3190 my $manager = $builder->build_object({ class => "Koha::Patrons" });
3191 t
::lib
::Mocks
::mock_userenv
({ patron
=> $manager, branchcode
=> $manager->branchcode });
3193 my $biblio = $builder->build_sample_biblio({ author
=> 'Hall, Kylie' });
3195 my $branchcode = $library2->{branchcode
};
3197 my $item = $builder->build_sample_item(
3199 biblionumber
=> $biblio->biblionumber,
3200 library
=> $branchcode,
3201 replacementprice
=> 99.00,
3206 my $patron = $builder->build( { source
=> 'Borrower' } );
3208 ## Start with basic call, should just close out the open fine
3209 my $accountline = Koha
::Account
::Line
->new(
3211 borrowernumber
=> $patron->{borrowernumber
},
3212 debit_type_code
=> 'OVERDUE',
3213 status
=> 'UNRETURNED',
3214 itemnumber
=> $item->itemnumber,
3216 amountoutstanding
=> 99.00,
3217 interface
=> 'test',
3221 C4
::Circulation
::_FixOverduesOnReturn
( $patron->{borrowernumber
}, $item->itemnumber, undef, 'RETURNED' );
3223 $accountline->_result()->discard_changes();
3225 is
( $accountline->amountoutstanding+0, 99, 'Fine has the same amount outstanding as previously' );
3226 isnt
( $accountline->status, 'UNRETURNED', 'Open fine ( account type OVERDUE ) has been closed out ( status not UNRETURNED )');
3227 is
( $accountline->status, 'RETURNED', 'Passed status has been used to set as RETURNED )');
3229 ## Run again, with exemptfine enabled
3232 debit_type_code
=> 'OVERDUE',
3233 status
=> 'UNRETURNED',
3234 amountoutstanding
=> 99.00,
3238 C4
::Circulation
::_FixOverduesOnReturn
( $patron->{borrowernumber
}, $item->itemnumber, 1, 'RETURNED' );
3240 $accountline->_result()->discard_changes();
3241 my $offset = Koha
::Account
::Offsets
->search({ debit_id
=> $accountline->id, type
=> 'Forgiven' })->next();
3243 is
( $accountline->amountoutstanding + 0, 0, 'Fine amountoutstanding has been reduced to 0' );
3244 isnt
( $accountline->status, 'UNRETURNED', 'Open fine ( account type OVERDUE ) has been closed out ( status not UNRETURNED )');
3245 is
( $accountline->status, 'RETURNED', 'Open fine ( account type OVERDUE ) has been set to returned ( status RETURNED )');
3246 is
( ref $offset, "Koha::Account::Offset", "Found matching offset for fine reduction via forgiveness" );
3247 is
( $offset->amount + 0, -99, "Amount of offset is correct" );
3248 my $credit = $offset->credit;
3249 is
( ref $credit, "Koha::Account::Line", "Found matching credit for fine forgiveness" );
3250 is
( $credit->amount + 0, -99, "Credit amount is set correctly" );
3251 is
( $credit->amountoutstanding + 0, 0, "Credit amountoutstanding is correctly set to 0" );
3253 # Bug 25417 - Only forgive fines where there is an amount outstanding to forgive
3256 debit_type_code
=> 'OVERDUE',
3257 status
=> 'UNRETURNED',
3258 amountoutstanding
=> 0.00,
3263 C4
::Circulation
::_FixOverduesOnReturn
( $patron->{borrowernumber
}, $item->itemnumber, 1, 'RETURNED' );
3265 $accountline->_result()->discard_changes();
3266 $offset = Koha
::Account
::Offsets
->search({ debit_id
=> $accountline->id, type
=> 'Forgiven' })->next();
3267 is
( $offset, undef, "No offset created when trying to forgive fine with no outstanding balance" );
3268 isnt
( $accountline->status, 'UNRETURNED', 'Open fine ( account type OVERDUE ) has been closed out ( status not UNRETURNED )');
3269 is
( $accountline->status, 'RETURNED', 'Passed status has been used to set as RETURNED )');
3272 subtest
'Set waiting flag' => sub {
3275 my $library_1 = $builder->build( { source
=> 'Branch' } );
3276 my $patron_1 = $builder->build( { source
=> 'Borrower', value
=> { branchcode
=> $library_1->{branchcode
}, categorycode
=> $patron_category->{categorycode
} } } );
3277 my $library_2 = $builder->build( { source
=> 'Branch' } );
3278 my $patron_2 = $builder->build( { source
=> 'Borrower', value
=> { branchcode
=> $library_2->{branchcode
}, categorycode
=> $patron_category->{categorycode
} } } );
3280 my $item = $builder->build_sample_item(
3282 library
=> $library_1->{branchcode
},
3286 set_userenv
( $library_2 );
3287 my $reserve_id = AddReserve
(
3289 branchcode
=> $library_2->{branchcode
},
3290 borrowernumber
=> $patron_2->{borrowernumber
},
3291 biblionumber
=> $item->biblionumber,
3293 itemnumber
=> $item->itemnumber,
3297 set_userenv
( $library_1 );
3298 my $do_transfer = 1;
3299 my ( $res, $rr ) = AddReturn
( $item->barcode, $library_1->{branchcode
} );
3300 ModReserveAffect
( $item->itemnumber, undef, $do_transfer, $reserve_id );
3301 my $hold = Koha
::Holds
->find( $reserve_id );
3302 is
( $hold->found, 'T', 'Hold is in transit' );
3304 my ( $status ) = CheckReserves
($item->itemnumber);
3305 is
( $status, 'Reserved', 'Hold is not waiting yet');
3307 set_userenv
( $library_2 );
3309 AddReturn
( $item->barcode, $library_2->{branchcode
} );
3310 ModReserveAffect
( $item->itemnumber, undef, $do_transfer, $reserve_id );
3311 $hold = Koha
::Holds
->find( $reserve_id );
3312 is
( $hold->found, 'W', 'Hold is waiting' );
3313 ( $status ) = CheckReserves
($item->itemnumber);
3314 is
( $status, 'Waiting', 'Now the hold is waiting');
3316 #Bug 21944 - Waiting transfer checked in at branch other than pickup location
3317 set_userenv
( $library_1 );
3318 (undef, my $messages, undef, undef ) = AddReturn
( $item->barcode, $library_1->{branchcode
} );
3319 $hold = Koha
::Holds
->find( $reserve_id );
3320 is
( $hold->found, undef, 'Hold is no longer marked waiting' );
3321 is
( $hold->priority, 1, "Hold is now priority one again");
3322 is
( $hold->waitingdate, undef, "Hold no longer has a waiting date");
3323 is
( $hold->itemnumber, $item->itemnumber, "Hold has retained its' itemnumber");
3324 is
( $messages->{ResFound
}->{ResFound
}, "Reserved", "Hold is still returned");
3325 is
( $messages->{ResFound
}->{found
}, undef, "Hold is no longer marked found in return message");
3326 is
( $messages->{ResFound
}->{priority
}, 1, "Hold is priority 1 in return message");
3329 subtest
'Cancel transfers on lost items' => sub {
3331 my $library_1 = $builder->build( { source
=> 'Branch' } );
3332 my $patron_1 = $builder->build( { source
=> 'Borrower', value
=> { branchcode
=> $library_1->{branchcode
}, categorycode
=> $patron_category->{categorycode
} } } );
3333 my $library_2 = $builder->build( { source
=> 'Branch' } );
3334 my $patron_2 = $builder->build( { source
=> 'Borrower', value
=> { branchcode
=> $library_2->{branchcode
}, categorycode
=> $patron_category->{categorycode
} } } );
3335 my $biblio = $builder->build_sample_biblio({branchcode
=> $library->{branchcode
}});
3336 my $item = $builder->build_sample_item({
3337 biblionumber
=> $biblio->biblionumber,
3338 library
=> $library_1->{branchcode
},
3341 set_userenv
( $library_2 );
3342 my $reserve_id = AddReserve
(
3344 branchcode
=> $library_2->{branchcode
},
3345 borrowernumber
=> $patron_2->{borrowernumber
},
3346 biblionumber
=> $item->biblionumber,
3348 itemnumber
=> $item->itemnumber,
3352 #Return book and add transfer
3353 set_userenv
( $library_1 );
3354 my $do_transfer = 1;
3355 my ( $res, $rr ) = AddReturn
( $item->barcode, $library_1->{branchcode
} );
3356 ModReserveAffect
( $item->itemnumber, undef, $do_transfer, $reserve_id );
3357 C4
::Circulation
::transferbook
({
3358 from_branch
=> $library_1->{branchcode
},
3359 to_branch
=> $library_2->{branchcode
},
3360 barcode
=> $item->barcode,
3362 my $hold = Koha
::Holds
->find( $reserve_id );
3363 is
( $hold->found, 'T', 'Hold is in transit' );
3365 #Check transfer exists and the items holding branch is the transfer destination branch before marking it as lost
3366 my ($datesent,$frombranch,$tobranch) = GetTransfers
($item->itemnumber);
3367 is
( $frombranch, $library_1->{branchcode
}, 'The transfer is generated from the correct library');
3368 is
( $tobranch, $library_2->{branchcode
}, 'The transfer is generated to the correct library');
3369 my $itemcheck = Koha
::Items
->find($item->itemnumber);
3370 is
( $itemcheck->holdingbranch, $library_1->{branchcode
}, 'Items holding branch is the transfers origination branch before it is marked as lost' );
3372 #Simulate item being marked as lost and confirm the transfer is deleted and the items holding branch is the transfers source branch
3373 $item->itemlost(1)->store;
3374 LostItem
( $item->itemnumber, 'test', 1 );
3375 ($datesent,$frombranch,$tobranch) = GetTransfers
($item->itemnumber);
3376 is
( $tobranch, undef, 'The transfer on the lost item has been deleted as the LostItemCancelOutstandingTransfer is enabled');
3377 $itemcheck = Koha
::Items
->find($item->itemnumber);
3378 is
( $itemcheck->holdingbranch, $library_1->{branchcode
}, 'Lost item with cancelled hold has holding branch equallying the transfers source branch' );
3382 subtest
'CanBookBeIssued | is_overdue' => sub {
3385 # Set a simple circ policy
3386 Koha
::CirculationRules
->set_rules(
3388 categorycode
=> undef,
3389 branchcode
=> undef,
3393 reservesallowed
=> 25,
3395 lengthunit
=> 'days',
3396 renewalsallowed
=> 1,
3398 norenewalbefore
=> undef,
3406 my $now = dt_from_string
;
3407 my $five_days_go = output_pref
({ dt
=> $now->clone->add( days
=> 5 ), dateonly
=> 1});
3408 my $ten_days_go = output_pref
({ dt
=> $now->clone->add( days
=> 10), dateonly
=> 1 });
3409 my $library = $builder->build( { source
=> 'Branch' } );
3410 my $patron = $builder->build_object( { class => 'Koha::Patrons', value
=> { categorycode
=> $patron_category->{categorycode
} } } );
3412 my $item = $builder->build_sample_item(
3414 library
=> $library->{branchcode
},
3418 my $issue = AddIssue
( $patron->unblessed, $item->barcode, $five_days_go ); # date due was 10d ago
3419 my $actualissue = Koha
::Checkouts
->find( { itemnumber
=> $item->itemnumber } );
3420 is
( output_pref
({ str
=> $actualissue->date_due, dateonly
=> 1}), $five_days_go, "First issue works");
3421 my ($issuingimpossible, $needsconfirmation) = CanBookBeIssued
($patron,$item->barcode,$ten_days_go, undef, undef, undef);
3422 is
( $needsconfirmation->{RENEW_ISSUE
}, 1, "This is a renewal");
3423 is
( $needsconfirmation->{TOO_MANY
}, undef, "Not too many, is a renewal");
3426 subtest
'ItemsDeniedRenewal preference' => sub {
3429 C4
::Context
->set_preference('ItemsDeniedRenewal','');
3431 my $idr_lib = $builder->build_object({ class => 'Koha::Libraries'});
3432 Koha
::CirculationRules
->set_rules(
3434 categorycode
=> '*',
3436 branchcode
=> $idr_lib->branchcode,
3438 reservesallowed
=> 25,
3440 lengthunit
=> 'days',
3441 renewalsallowed
=> 10,
3443 norenewalbefore
=> undef,
3451 my $deny_book = $builder->build_object({ class => 'Koha::Items', value
=> {
3452 homebranch
=> $idr_lib->branchcode,
3456 itemcallnumber
=> undef,
3460 my $allow_book = $builder->build_object({ class => 'Koha::Items', value
=> {
3461 homebranch
=> $idr_lib->branchcode,
3464 location
=> 'NOPROC'
3468 my $idr_borrower = $builder->build_object({ class => 'Koha::Patrons', value
=> {
3469 branchcode
=> $idr_lib->branchcode,
3472 my $future = dt_from_string
->add( days
=> 1 );
3473 my $deny_issue = $builder->build_object({ class => 'Koha::Checkouts', value
=> {
3474 returndate
=> undef,
3477 borrowernumber
=> $idr_borrower->borrowernumber,
3478 itemnumber
=> $deny_book->itemnumber,
3479 onsite_checkout
=> 0,
3480 date_due
=> $future,
3483 my $allow_issue = $builder->build_object({ class => 'Koha::Checkouts', value
=> {
3484 returndate
=> undef,
3487 borrowernumber
=> $idr_borrower->borrowernumber,
3488 itemnumber
=> $allow_book->itemnumber,
3489 onsite_checkout
=> 0,
3490 date_due
=> $future,
3496 my ( $idr_mayrenew, $idr_error ) =
3497 CanBookBeRenewed
( $idr_borrower->borrowernumber, $deny_issue->itemnumber );
3498 is
( $idr_mayrenew, 1, 'Renewal allowed when no rules' );
3499 is
( $idr_error, undef, 'Renewal allowed when no rules' );
3501 $idr_rules="withdrawn: [1]";
3503 C4
::Context
->set_preference('ItemsDeniedRenewal',$idr_rules);
3504 ( $idr_mayrenew, $idr_error ) =
3505 CanBookBeRenewed
( $idr_borrower->borrowernumber, $deny_issue->itemnumber );
3506 is
( $idr_mayrenew, 0, 'Renewal blocked when 1 rules (withdrawn)' );
3507 is
( $idr_error, 'item_denied_renewal', 'Renewal blocked when 1 rule (withdrawn)' );
3508 ( $idr_mayrenew, $idr_error ) =
3509 CanBookBeRenewed
( $idr_borrower->borrowernumber, $allow_issue->itemnumber );
3510 is
( $idr_mayrenew, 1, 'Renewal allowed when 1 rules not matched (withdrawn)' );
3511 is
( $idr_error, undef, 'Renewal allowed when 1 rules not matched (withdrawn)' );
3513 $idr_rules="withdrawn: [1]\nitype: [HIDE,INVISIBLE]";
3515 C4
::Context
->set_preference('ItemsDeniedRenewal',$idr_rules);
3516 ( $idr_mayrenew, $idr_error ) =
3517 CanBookBeRenewed
( $idr_borrower->borrowernumber, $deny_issue->itemnumber );
3518 is
( $idr_mayrenew, 0, 'Renewal blocked when 2 rules matched (withdrawn, itype)' );
3519 is
( $idr_error, 'item_denied_renewal', 'Renewal blocked when 2 rules matched (withdrawn,itype)' );
3520 ( $idr_mayrenew, $idr_error ) =
3521 CanBookBeRenewed
( $idr_borrower->borrowernumber, $allow_issue->itemnumber );
3522 is
( $idr_mayrenew, 1, 'Renewal allowed when 2 rules not matched (withdrawn, itype)' );
3523 is
( $idr_error, undef, 'Renewal allowed when 2 rules not matched (withdrawn, itype)' );
3525 $idr_rules="withdrawn: [1]\nitype: [HIDE,INVISIBLE]\nlocation: [PROC]";
3527 C4
::Context
->set_preference('ItemsDeniedRenewal',$idr_rules);
3528 ( $idr_mayrenew, $idr_error ) =
3529 CanBookBeRenewed
( $idr_borrower->borrowernumber, $deny_issue->itemnumber );
3530 is
( $idr_mayrenew, 0, 'Renewal blocked when 3 rules matched (withdrawn, itype, location)' );
3531 is
( $idr_error, 'item_denied_renewal', 'Renewal blocked when 3 rules matched (withdrawn,itype, location)' );
3532 ( $idr_mayrenew, $idr_error ) =
3533 CanBookBeRenewed
( $idr_borrower->borrowernumber, $allow_issue->itemnumber );
3534 is
( $idr_mayrenew, 1, 'Renewal allowed when 3 rules not matched (withdrawn, itype, location)' );
3535 is
( $idr_error, undef, 'Renewal allowed when 3 rules not matched (withdrawn, itype, location)' );
3537 $idr_rules="itemcallnumber: [NULL]";
3538 C4
::Context
->set_preference('ItemsDeniedRenewal',$idr_rules);
3539 ( $idr_mayrenew, $idr_error ) =
3540 CanBookBeRenewed
( $idr_borrower->borrowernumber, $deny_issue->itemnumber );
3541 is
( $idr_mayrenew, 0, 'Renewal blocked for undef when NULL in pref' );
3542 $idr_rules="itemcallnumber: ['']";
3543 C4
::Context
->set_preference('ItemsDeniedRenewal',$idr_rules);
3544 ( $idr_mayrenew, $idr_error ) =
3545 CanBookBeRenewed
( $idr_borrower->borrowernumber, $deny_issue->itemnumber );
3546 is
( $idr_mayrenew, 1, 'Renewal not blocked for undef when "" in pref' );
3548 $idr_rules="itemnotes: [NULL]";
3549 C4
::Context
->set_preference('ItemsDeniedRenewal',$idr_rules);
3550 ( $idr_mayrenew, $idr_error ) =
3551 CanBookBeRenewed
( $idr_borrower->borrowernumber, $deny_issue->itemnumber );
3552 is
( $idr_mayrenew, 1, 'Renewal not blocked for "" when NULL in pref' );
3553 $idr_rules="itemnotes: ['']";
3554 C4
::Context
->set_preference('ItemsDeniedRenewal',$idr_rules);
3555 ( $idr_mayrenew, $idr_error ) =
3556 CanBookBeRenewed
( $idr_borrower->borrowernumber, $deny_issue->itemnumber );
3557 is
( $idr_mayrenew, 0, 'Renewal blocked for empty string when "" in pref' );
3560 subtest
'CanBookBeIssued | item-level_itypes=biblio' => sub {
3563 t
::lib
::Mocks
::mock_preference
('item-level_itypes', 0); # biblio
3564 my $library = $builder->build( { source
=> 'Branch' } );
3565 my $patron = $builder->build_object( { class => 'Koha::Patrons', value
=> { categorycode
=> $patron_category->{categorycode
} } } )->store;
3567 my $item = $builder->build_sample_item(
3569 library
=> $library->{branchcode
},
3573 my ( $issuingimpossible, $needsconfirmation ) = CanBookBeIssued
( $patron, $item->barcode, undef, undef, undef, undef );
3574 is_deeply
( $needsconfirmation, {}, 'Item can be issued to this patron' );
3575 is_deeply
( $issuingimpossible, {}, 'Item can be issued to this patron' );
3578 subtest
'CanBookBeIssued | notforloan' => sub {
3581 t
::lib
::Mocks
::mock_preference
('AllowNotForLoanOverride', 0);
3583 my $library = $builder->build( { source
=> 'Branch' } );
3584 my $patron = $builder->build_object( { class => 'Koha::Patrons', value
=> { categorycode
=> $patron_category->{categorycode
} } } )->store;
3586 my $itemtype = $builder->build(
3588 source
=> 'Itemtype',
3589 value
=> { notforloan
=> undef, }
3592 my $item = $builder->build_sample_item(
3594 library
=> $library->{branchcode
},
3595 itype
=> $itemtype->{itemtype
},
3598 $item->biblioitem->itemtype($itemtype->{itemtype
})->store;
3600 my ( $issuingimpossible, $needsconfirmation );
3603 subtest
'item-level_itypes = 1' => sub {
3606 t
::lib
::Mocks
::mock_preference
('item-level_itypes', 1); # item
3607 # Is for loan at item type and item level
3608 ( $issuingimpossible, $needsconfirmation ) = CanBookBeIssued
( $patron, $item->barcode, undef, undef, undef, undef );
3609 is_deeply
( $needsconfirmation, {}, 'Item can be issued to this patron' );
3610 is_deeply
( $issuingimpossible, {}, 'Item can be issued to this patron' );
3612 # not for loan at item type level
3613 Koha
::ItemTypes
->find( $itemtype->{itemtype
} )->notforloan(1)->store;
3614 ( $issuingimpossible, $needsconfirmation ) = CanBookBeIssued
( $patron, $item->barcode, undef, undef, undef, undef );
3615 is_deeply
( $needsconfirmation, {}, 'No confirmation needed, AllowNotForLoanOverride=0' );
3618 { NOT_FOR_LOAN
=> 1, itemtype_notforloan
=> $itemtype->{itemtype
} },
3619 'Item can not be issued, not for loan at item type level'
3622 # not for loan at item level
3623 Koha
::ItemTypes
->find( $itemtype->{itemtype
} )->notforloan(undef)->store;
3624 $item->notforloan( 1 )->store;
3625 ( $issuingimpossible, $needsconfirmation ) = CanBookBeIssued
( $patron, $item->barcode, undef, undef, undef, undef );
3626 is_deeply
( $needsconfirmation, {}, 'No confirmation needed, AllowNotForLoanOverride=0' );
3629 { NOT_FOR_LOAN
=> 1, item_notforloan
=> 1 },
3630 'Item can not be issued, not for loan at item type level'
3634 subtest
'item-level_itypes = 0' => sub {
3637 t
::lib
::Mocks
::mock_preference
('item-level_itypes', 0); # biblio
3639 # We set another itemtype for biblioitem
3640 my $itemtype = $builder->build(
3642 source
=> 'Itemtype',
3643 value
=> { notforloan
=> undef, }
3647 # for loan at item type and item level
3648 $item->notforloan(0)->store;
3649 $item->biblioitem->itemtype($itemtype->{itemtype
})->store;
3650 ( $issuingimpossible, $needsconfirmation ) = CanBookBeIssued
( $patron, $item->barcode, undef, undef, undef, undef );
3651 is_deeply
( $needsconfirmation, {}, 'Item can be issued to this patron' );
3652 is_deeply
( $issuingimpossible, {}, 'Item can be issued to this patron' );
3654 # not for loan at item type level
3655 Koha
::ItemTypes
->find( $itemtype->{itemtype
} )->notforloan(1)->store;
3656 ( $issuingimpossible, $needsconfirmation ) = CanBookBeIssued
( $patron, $item->barcode, undef, undef, undef, undef );
3657 is_deeply
( $needsconfirmation, {}, 'No confirmation needed, AllowNotForLoanOverride=0' );
3660 { NOT_FOR_LOAN
=> 1, itemtype_notforloan
=> $itemtype->{itemtype
} },
3661 'Item can not be issued, not for loan at item type level'
3664 # not for loan at item level
3665 Koha
::ItemTypes
->find( $itemtype->{itemtype
} )->notforloan(undef)->store;
3666 $item->notforloan( 1 )->store;
3667 ( $issuingimpossible, $needsconfirmation ) = CanBookBeIssued
( $patron, $item->barcode, undef, undef, undef, undef );
3668 is_deeply
( $needsconfirmation, {}, 'No confirmation needed, AllowNotForLoanOverride=0' );
3671 { NOT_FOR_LOAN
=> 1, item_notforloan
=> 1 },
3672 'Item can not be issued, not for loan at item type level'
3676 # TODO test with AllowNotForLoanOverride = 1
3679 subtest
'AddReturn should clear items.onloan for unissued items' => sub {
3682 t
::lib
::Mocks
::mock_preference
( "AllowReturnToBranch", 'anywhere' );
3683 my $item = $builder->build_sample_item(
3685 onloan
=> '2018-01-01',
3689 AddReturn
( $item->barcode, $item->homebranch );
3690 $item->discard_changes; # refresh
3691 is
( $item->onloan, undef, 'AddReturn did clear items.onloan' );
3695 subtest
'AddRenewal and AddIssuingCharge tests' => sub {
3700 t
::lib
::Mocks
::mock_preference
('item-level_itypes', 1);
3702 my $issuing_charges = 15;
3703 my $title = 'A title';
3704 my $author = 'Author, An';
3705 my $barcode = 'WHATARETHEODDS';
3707 my $circ = Test
::MockModule
->new('C4::Circulation');
3709 'GetIssuingCharges',
3711 return $issuing_charges;
3715 my $library = $builder->build_object({ class => 'Koha::Libraries' });
3716 my $itemtype = $builder->build_object({ class => 'Koha::ItemTypes', value
=> { rentalcharge_daily
=> 0.00 }});
3717 my $patron = $builder->build_object({
3718 class => 'Koha::Patrons',
3719 value
=> { branchcode
=> $library->id }
3722 my $biblio = $builder->build_sample_biblio({ title
=> $title, author
=> $author });
3723 my $item_id = Koha
::Item
->new(
3725 biblionumber
=> $biblio->biblionumber,
3726 homebranch
=> $library->id,
3727 holdingbranch
=> $library->id,
3728 barcode
=> $barcode,
3729 replacementprice
=> 23.00,
3730 itype
=> $itemtype->id
3732 )->store->itemnumber;
3733 my $item = Koha
::Items
->find( $item_id );
3735 my $context = Test
::MockModule
->new('C4::Context');
3736 $context->mock( userenv
=> { branch
=> $library->id } );
3738 # Check the item out
3739 AddIssue
( $patron->unblessed, $item->barcode );
3740 t
::lib
::Mocks
::mock_preference
( 'RenewalLog', 0 );
3741 my $date = output_pref
( { dt
=> dt_from_string
(), dateonly
=> 1, dateformat
=> 'iso' } );
3742 my %params_renewal = (
3743 timestamp
=> { -like
=> $date . "%" },
3744 module
=> "CIRCULATION",
3745 action
=> "RENEWAL",
3747 my $old_log_size = Koha
::ActionLogs
->count( \
%params_renewal );;
3748 AddRenewal
( $patron->id, $item->id, $library->id );
3749 my $new_log_size = Koha
::ActionLogs
->count( \
%params_renewal );
3750 is
( $new_log_size, $old_log_size, 'renew log not added because of the syspref RenewalLog' );
3752 my $checkouts = $patron->checkouts;
3753 # The following will fail if run on 00:00:00
3754 unlike
( $checkouts->next->lastreneweddate, qr/00:00:00/, 'AddRenewal should set the renewal date with the time part');
3756 my $lines = Koha
::Account
::Lines
->search({
3757 borrowernumber
=> $patron->id,
3758 itemnumber
=> $item->id
3761 is
( $lines->count, 2 );
3763 my $line = $lines->next;
3764 is
( $line->debit_type_code, 'RENT', 'The issue of item with issuing charge generates an accountline of the correct type' );
3765 is
( $line->branchcode, $library->id, 'AddIssuingCharge correctly sets branchcode' );
3766 is
( $line->description, '', 'AddIssue does not set a hardcoded description for the accountline' );
3768 $line = $lines->next;
3769 is
( $line->debit_type_code, 'RENT_RENEW', 'The renewal of item with issuing charge generates an accountline of the correct type' );
3770 is
( $line->branchcode, $library->id, 'AddRenewal correctly sets branchcode' );
3771 is
( $line->description, '', 'AddRenewal does not set a hardcoded description for the accountline' );
3773 t
::lib
::Mocks
::mock_preference
( 'RenewalLog', 1 );
3775 $context = Test
::MockModule
->new('C4::Context');
3776 $context->mock( userenv
=> { branch
=> undef, interface
=> 'CRON'} ); #Test statistical logging of renewal via cron (atuo_renew)
3778 my $now = dt_from_string
;
3779 $date = output_pref
( { dt
=> $now, dateonly
=> 1, dateformat
=> 'iso' } );
3780 $old_log_size = Koha
::ActionLogs
->count( \
%params_renewal );
3781 my $sth = $dbh->prepare("SELECT COUNT(*) FROM statistics WHERE itemnumber = ? AND branch = ?");
3782 $sth->execute($item->id, $library->id);
3783 my ($old_stats_size) = $sth->fetchrow_array;
3784 AddRenewal
( $patron->id, $item->id, $library->id );
3785 $new_log_size = Koha
::ActionLogs
->count( \
%params_renewal );
3786 $sth->execute($item->id, $library->id);
3787 my ($new_stats_size) = $sth->fetchrow_array;
3788 is
( $new_log_size, $old_log_size + 1, 'renew log successfully added' );
3789 is
( $new_stats_size, $old_stats_size + 1, 'renew statistic successfully added with passed branch' );
3791 AddReturn
( $item->id, $library->id, undef, $date );
3792 AddIssue
( $patron->unblessed, $item->barcode, $now );
3793 AddRenewal
( $patron->id, $item->id, $library->id, undef, undef, 1 );
3794 my $lines_skipped = Koha
::Account
::Lines
->search({
3795 borrowernumber
=> $patron->id,
3796 itemnumber
=> $item->id
3798 is
( $lines_skipped->count, 5, 'Passing skipfinecalc causes fine calculation on renewal to be skipped' );
3802 subtest
'ProcessOfflinePayment() tests' => sub {
3809 my $patron = $builder->build_object({ class => 'Koha::Patrons' });
3810 my $library = $builder->build_object({ class => 'Koha::Libraries' });
3811 my $result = C4
::Circulation
::ProcessOfflinePayment
({ cardnumber
=> $patron->cardnumber, amount
=> $amount, branchcode
=> $library->id });
3813 is
( $result, 'Success.', 'The right string is returned' );
3815 my $lines = $patron->account->lines;
3816 is
( $lines->count, 1, 'line created correctly');
3818 my $line = $lines->next;
3819 is
( $line->amount+0, $amount * -1, 'amount picked from params' );
3820 is
( $line->branchcode, $library->id, 'branchcode set correctly' );
3824 subtest
'Incremented fee tests' => sub {
3827 my $dt = dt_from_string
();
3828 Time
::Fake
->offset( $dt->epoch );
3830 t
::lib
::Mocks
::mock_preference
( 'item-level_itypes', 1 );
3833 $builder->build_object( { class => 'Koha::Libraries' } )->store;
3835 $module->mock( 'userenv', sub { { branch
=> $library->id } } );
3837 my $patron = $builder->build_object(
3839 class => 'Koha::Patrons',
3840 value
=> { categorycode
=> $patron_category->{categorycode
} }
3844 my $itemtype = $builder->build_object(
3846 class => 'Koha::ItemTypes',
3848 notforloan
=> undef,
3850 rentalcharge_daily
=> 1,
3851 rentalcharge_daily_calendar
=> 0
3856 my $item = $builder->build_sample_item(
3858 library
=> $library->{branchcode
},
3859 itype
=> $itemtype->id,
3863 is
( $itemtype->rentalcharge_daily+0,
3864 1, 'Daily rental charge stored and retreived correctly' );
3865 is
( $item->effective_itemtype, $itemtype->id,
3866 "Itemtype set correctly for item" );
3868 my $now = dt_from_string
;
3869 my $dt_from = $now->clone;
3870 my $dt_to = $now->clone->add( days
=> 7 );
3871 my $dt_to_renew = $now->clone->add( days
=> 13 );
3875 AddIssue
( $patron->unblessed, $item->barcode, $dt_to, undef, $dt_from );
3876 my $accountline = Koha
::Account
::Lines
->find( { itemnumber
=> $item->id } );
3877 is
( $accountline->amount+0, 7,
3878 "Daily rental charge calculated correctly with rentalcharge_daily_calendar = 0"
3880 $accountline->delete();
3881 AddRenewal
( $patron->id, $item->id, $library->id, $dt_to_renew, $dt_to );
3882 $accountline = Koha
::Account
::Lines
->find( { itemnumber
=> $item->id } );
3883 is
( $accountline->amount+0, 6,
3884 "Daily rental charge calculated correctly with rentalcharge_daily_calendar = 0, for renewal"
3886 $accountline->delete();
3889 t
::lib
::Mocks
::mock_preference
( 'finesCalendar', 'noFinesWhenClosed' );
3890 $itemtype->rentalcharge_daily_calendar(1)->store();
3892 AddIssue
( $patron->unblessed, $item->barcode, $dt_to, undef, $dt_from );
3893 $accountline = Koha
::Account
::Lines
->find( { itemnumber
=> $item->id } );
3894 is
( $accountline->amount+0, 7,
3895 "Daily rental charge calculated correctly with rentalcharge_daily_calendar = 1"
3897 $accountline->delete();
3898 AddRenewal
( $patron->id, $item->id, $library->id, $dt_to_renew, $dt_to );
3899 $accountline = Koha
::Account
::Lines
->find( { itemnumber
=> $item->id } );
3900 is
( $accountline->amount+0, 6,
3901 "Daily rental charge calculated correctly with rentalcharge_daily_calendar = 1, for renewal"
3903 $accountline->delete();
3906 my $calendar = C4
::Calendar
->new( branchcode
=> $library->id );
3907 # DateTime 1..7 (Mon..Sun), C4::Calender 0..6 (Sun..Sat)
3909 ( $dt_from->day_of_week == 6 ) ?
0
3910 : ( $dt_from->day_of_week == 7 ) ?
1
3911 : $dt_from->day_of_week + 1;
3912 my $closed_day_name = $dt_from->clone->add(days
=> 1)->day_name;
3913 $calendar->insert_week_day_holiday(
3914 weekday
=> $closed_day,
3915 title
=> 'Test holiday',
3916 description
=> 'Test holiday'
3919 AddIssue
( $patron->unblessed, $item->barcode, $dt_to, undef, $dt_from );
3920 $accountline = Koha
::Account
::Lines
->find( { itemnumber
=> $item->id } );
3921 is
( $accountline->amount+0, 6,
3922 "Daily rental charge calculated correctly with rentalcharge_daily_calendar = 1 and closed $closed_day_name"
3924 $accountline->delete();
3925 AddRenewal
( $patron->id, $item->id, $library->id, $dt_to_renew, $dt_to );
3926 $accountline = Koha
::Account
::Lines
->find( { itemnumber
=> $item->id } );
3927 is
( $accountline->amount+0, 5,
3928 "Daily rental charge calculated correctly with rentalcharge_daily_calendar = 1 and closed $closed_day_name, for renewal"
3930 $accountline->delete();
3933 $itemtype->rentalcharge(2)->store;
3934 is
( $itemtype->rentalcharge+0, 2,
3935 'Rental charge updated and retreived correctly' );
3937 AddIssue
( $patron->unblessed, $item->barcode, $dt_to, undef, $dt_from );
3939 Koha
::Account
::Lines
->search( { itemnumber
=> $item->id } );
3940 is
( $accountlines->count, '2',
3941 "Fixed charge and accrued charge recorded distinctly" );
3942 $accountlines->delete();
3943 AddRenewal
( $patron->id, $item->id, $library->id, $dt_to_renew, $dt_to );
3944 $accountlines = Koha
::Account
::Lines
->search( { itemnumber
=> $item->id } );
3945 is
( $accountlines->count, '2',
3946 "Fixed charge and accrued charge recorded distinctly, for renewal" );
3947 $accountlines->delete();
3949 $itemtype->rentalcharge(0)->store;
3950 is
( $itemtype->rentalcharge+0, 0,
3951 'Rental charge reset and retreived correctly' );
3954 Koha
::CirculationRules
->set_rule(
3956 categorycode
=> $patron->categorycode,
3957 itemtype
=> $itemtype->id,
3958 branchcode
=> $library->id,
3959 rule_name
=> 'lengthunit',
3960 rule_value
=> 'hours',
3964 $itemtype->rentalcharge_hourly('0.25')->store();
3965 is
( $itemtype->rentalcharge_hourly,
3966 '0.25', 'Hourly rental charge stored and retreived correctly' );
3968 $dt_to = $now->clone->add( hours
=> 168 );
3969 $dt_to_renew = $now->clone->add( hours
=> 312 );
3971 $itemtype->rentalcharge_hourly_calendar(0)->store();
3973 AddIssue
( $patron->unblessed, $item->barcode, $dt_to, undef, $dt_from );
3974 $accountline = Koha
::Account
::Lines
->find( { itemnumber
=> $item->id } );
3975 is
( $accountline->amount + 0, 42,
3976 "Hourly rental charge calculated correctly with rentalcharge_hourly_calendar = 0 (168h * 0.25u)" );
3977 $accountline->delete();
3978 AddRenewal
( $patron->id, $item->id, $library->id, $dt_to_renew, $dt_to );
3979 $accountline = Koha
::Account
::Lines
->find( { itemnumber
=> $item->id } );
3980 is
( $accountline->amount + 0, 36,
3981 "Hourly rental charge calculated correctly with rentalcharge_hourly_calendar = 0, for renewal (312h - 168h * 0.25u)" );
3982 $accountline->delete();
3985 $itemtype->rentalcharge_hourly_calendar(1)->store();
3987 AddIssue
( $patron->unblessed, $item->barcode, $dt_to, undef, $dt_from );
3988 $accountline = Koha
::Account
::Lines
->find( { itemnumber
=> $item->id } );
3989 is
( $accountline->amount + 0, 36,
3990 "Hourly rental charge calculated correctly with rentalcharge_hourly_calendar = 1 and closed $closed_day_name (168h - 24h * 0.25u)" );
3991 $accountline->delete();
3992 AddRenewal
( $patron->id, $item->id, $library->id, $dt_to_renew, $dt_to );
3993 $accountline = Koha
::Account
::Lines
->find( { itemnumber
=> $item->id } );
3994 is
( $accountline->amount + 0, 30,
3995 "Hourly rental charge calculated correctly with rentalcharge_hourly_calendar = 1 and closed $closed_day_name, for renewal (312h - 168h - 24h * 0.25u" );
3996 $accountline->delete();
3999 $calendar->delete_holiday( weekday
=> $closed_day );
4001 AddIssue
( $patron->unblessed, $item->barcode, $dt_to, undef, $dt_from );
4002 $accountline = Koha
::Account
::Lines
->find( { itemnumber
=> $item->id } );
4003 is
( $accountline->amount + 0, 42,
4004 "Hourly rental charge calculated correctly with rentalcharge_hourly_calendar = 1 (168h - 0h * 0.25u" );
4005 $accountline->delete();
4006 AddRenewal
( $patron->id, $item->id, $library->id, $dt_to_renew, $dt_to );
4007 $accountline = Koha
::Account
::Lines
->find( { itemnumber
=> $item->id } );
4008 is
( $accountline->amount + 0, 36,
4009 "Hourly rental charge calculated correctly with rentalcharge_hourly_calendar = 1, for renewal (312h - 168h - 0h * 0.25u)" );
4010 $accountline->delete();
4015 subtest
'CanBookBeIssued & RentalFeesCheckoutConfirmation' => sub {
4018 t
::lib
::Mocks
::mock_preference
('RentalFeesCheckoutConfirmation', 1);
4019 t
::lib
::Mocks
::mock_preference
('item-level_itypes', 1);
4022 $builder->build_object( { class => 'Koha::Libraries' } )->store;
4023 my $patron = $builder->build_object(
4025 class => 'Koha::Patrons',
4026 value
=> { categorycode
=> $patron_category->{categorycode
} }
4030 my $itemtype = $builder->build_object(
4032 class => 'Koha::ItemTypes',
4036 rentalcharge_daily
=> 0
4041 my $item = $builder->build_sample_item(
4043 library
=> $library->id,
4047 itype
=> $itemtype->id,
4051 my ( $issuingimpossible, $needsconfirmation );
4052 my $dt_from = dt_from_string
();
4053 my $dt_due = $dt_from->clone->add( days
=> 3 );
4055 $itemtype->rentalcharge(1)->store;
4056 ( $issuingimpossible, $needsconfirmation ) = CanBookBeIssued
( $patron, $item->barcode, $dt_due, undef, undef, undef );
4057 is_deeply
( $needsconfirmation, { RENTALCHARGE
=> '1.00' }, 'Item needs rentalcharge confirmation to be issued' );
4058 $itemtype->rentalcharge('0')->store;
4059 $itemtype->rentalcharge_daily(1)->store;
4060 ( $issuingimpossible, $needsconfirmation ) = CanBookBeIssued
( $patron, $item->barcode, $dt_due, undef, undef, undef );
4061 is_deeply
( $needsconfirmation, { RENTALCHARGE
=> '3' }, 'Item needs rentalcharge confirmation to be issued, increment' );
4062 $itemtype->rentalcharge_daily('0')->store;
4065 subtest
'CanBookBeIssued & CircConfirmItemParts' => sub {
4068 t
::lib
::Mocks
::mock_preference
('CircConfirmItemParts', 1);
4070 my $patron = $builder->build_object(
4072 class => 'Koha::Patrons',
4073 value
=> { categorycode
=> $patron_category->{categorycode
} }
4077 my $item = $builder->build_sample_item(
4079 materials
=> 'includes DVD',
4083 my $dt_due = dt_from_string
->add( days
=> 3 );
4085 my ( $issuingimpossible, $needsconfirmation ) = CanBookBeIssued
( $patron, $item->barcode, $dt_due, undef, undef, undef );
4086 is_deeply
( $needsconfirmation, { ADDITIONAL_MATERIALS
=> 'includes DVD' }, 'Item needs confirmation of additional parts' );
4089 subtest
'Do not return on renewal (LOST charge)' => sub {
4092 t
::lib
::Mocks
::mock_preference
('MarkLostItemsAsReturned', 'onpayment');
4093 my $library = $builder->build_object( { class => "Koha::Libraries" } );
4094 my $manager = $builder->build_object( { class => "Koha::Patrons" } );
4095 t
::lib
::Mocks
::mock_userenv
({ patron
=> $manager,branchcode
=> $manager->branchcode });
4097 my $biblio = $builder->build_sample_biblio;
4099 my $item = $builder->build_sample_item(
4101 biblionumber
=> $biblio->biblionumber,
4102 library
=> $library->branchcode,
4103 replacementprice
=> 99.00,
4108 my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
4109 AddIssue
( $patron->unblessed, $item->barcode );
4111 my $accountline = Koha
::Account
::Line
->new(
4113 borrowernumber
=> $patron->borrowernumber,
4114 debit_type_code
=> 'LOST',
4116 itemnumber
=> $item->itemnumber,
4118 amountoutstanding
=> 12,
4119 interface
=> 'something',
4123 # AddRenewal doesn't call _FixAccountForLostAndFound
4124 AddIssue
( $patron->unblessed, $item->barcode );
4126 is
( $patron->checkouts->count, 1,
4127 'Renewal should not return the item even if a LOST payment has been made earlier'
4131 subtest
'Filling a hold should cancel existing transfer' => sub {
4134 t
::lib
::Mocks
::mock_preference
('AutomaticItemReturn', 1);
4136 my $libraryA = $builder->build_object( { class => 'Koha::Libraries' } );
4137 my $libraryB = $builder->build_object( { class => 'Koha::Libraries' } );
4138 my $patron = $builder->build_object(
4140 class => 'Koha::Patrons',
4142 categorycode
=> $patron_category->{categorycode
},
4143 branchcode
=> $libraryA->branchcode,
4148 my $item = $builder->build_sample_item({
4149 homebranch
=> $libraryB->branchcode,
4152 my ( undef, $message ) = AddReturn
( $item->barcode, $libraryA->branchcode, undef, undef );
4153 is
( Koha
::Item
::Transfers
->search({ itemnumber
=> $item->itemnumber, datearrived
=> undef })->count, 1, "We generate a transfer on checkin");
4155 branchcode
=> $libraryA->branchcode,
4156 borrowernumber
=> $patron->borrowernumber,
4157 biblionumber
=> $item->biblionumber,
4158 itemnumber
=> $item->itemnumber
4160 my $reserves = Koha
::Holds
->search({ itemnumber
=> $item->itemnumber });
4161 is
( $reserves->count, 1, "Reserve is placed");
4162 ( undef, $message ) = AddReturn
( $item->barcode, $libraryA->branchcode, undef, undef );
4163 my $reserve = $reserves->next;
4164 ModReserveAffect
( $item->itemnumber, $patron->borrowernumber, 0, $reserve->reserve_id );
4165 $reserve->discard_changes;
4166 ok
( $reserve->found eq 'W', "Reserve is marked waiting" );
4167 is
( Koha
::Item
::Transfers
->search({ itemnumber
=> $item->itemnumber, datearrived
=> undef })->count, 0, "No outstanding transfers when hold is waiting");
4170 subtest
'Tests for NoRefundOnLostReturnedItemsAge with AddReturn' => sub {
4174 t
::lib
::Mocks
::mock_preference
('BlockReturnOfLostItems', 0);
4175 my $library = $builder->build_object( { class => 'Koha::Libraries' } );
4176 my $patron = $builder->build_object(
4178 class => 'Koha::Patrons',
4179 value
=> { categorycode
=> $patron_category->{categorycode
} }
4183 my $biblionumber = $builder->build_sample_biblio(
4185 branchcode
=> $library->branchcode,
4189 # And the circulation rule
4190 Koha
::CirculationRules
->search->delete;
4191 Koha
::CirculationRules
->set_rules(
4193 categorycode
=> undef,
4195 branchcode
=> undef,
4198 lengthunit
=> 'days',
4204 source
=> 'CirculationRule',
4206 branchcode
=> undef,
4207 categorycode
=> undef,
4209 rule_name
=> 'lostreturn',
4210 rule_value
=> 'refund'
4215 subtest
'NoRefundOnLostReturnedItemsAge = undef' => sub {
4218 t
::lib
::Mocks
::mock_preference
( 'WhenLostChargeReplacementFee', 1 );
4219 t
::lib
::Mocks
::mock_preference
( 'NoRefundOnLostReturnedItemsAge', undef );
4221 my $lost_on = dt_from_string
->subtract( days
=> 7 )->date;
4223 my $item = $builder->build_sample_item(
4225 biblionumber
=> $biblionumber,
4226 library
=> $library->branchcode,
4227 replacementprice
=> '42',
4230 my $issue = AddIssue
( $patron->unblessed, $item->barcode );
4231 LostItem
( $item->itemnumber, 'cli', 0 );
4232 $item->_result->itemlost(1);
4233 $item->_result->itemlost_on( $lost_on );
4234 $item->_result->update();
4236 my $a = Koha
::Account
::Lines
->search(
4238 itemnumber
=> $item->id,
4239 borrowernumber
=> $patron->borrowernumber
4242 ok
( $a, "Found accountline for lost fee" );
4243 is
( $a->amountoutstanding + 0, 42, "Lost fee charged correctly" );
4244 my ( $doreturn, $messages ) = AddReturn
( $item->barcode, $library->branchcode, undef, dt_from_string
);
4245 $a = $a->get_from_storage;
4246 is
( $a->amountoutstanding + 0, 0, "Lost fee was refunded" );
4250 subtest
'NoRefundOnLostReturnedItemsAge > length of days item has been lost' => sub {
4253 t
::lib
::Mocks
::mock_preference
( 'WhenLostChargeReplacementFee', 1 );
4254 t
::lib
::Mocks
::mock_preference
( 'NoRefundOnLostReturnedItemsAge', 7 );
4256 my $lost_on = dt_from_string
->subtract( days
=> 6 )->date;
4258 my $item = $builder->build_sample_item(
4260 biblionumber
=> $biblionumber,
4261 library
=> $library->branchcode,
4262 replacementprice
=> '42',
4265 my $issue = AddIssue
( $patron->unblessed, $item->barcode );
4266 LostItem
( $item->itemnumber, 'cli', 0 );
4267 $item->_result->itemlost(1);
4268 $item->_result->itemlost_on( $lost_on );
4269 $item->_result->update();
4271 my $a = Koha
::Account
::Lines
->search(
4273 itemnumber
=> $item->id,
4274 borrowernumber
=> $patron->borrowernumber
4277 ok
( $a, "Found accountline for lost fee" );
4278 is
( $a->amountoutstanding + 0, 42, "Lost fee charged correctly" );
4279 my ( $doreturn, $messages ) = AddReturn
( $item->barcode, $library->branchcode, undef, dt_from_string
);
4280 $a = $a->get_from_storage;
4281 is
( $a->amountoutstanding + 0, 0, "Lost fee was refunded" );
4285 subtest
'NoRefundOnLostReturnedItemsAge = length of days item has been lost' => sub {
4288 t
::lib
::Mocks
::mock_preference
( 'WhenLostChargeReplacementFee', 1 );
4289 t
::lib
::Mocks
::mock_preference
( 'NoRefundOnLostReturnedItemsAge', 7 );
4291 my $lost_on = dt_from_string
->subtract( days
=> 7 )->date;
4293 my $item = $builder->build_sample_item(
4295 biblionumber
=> $biblionumber,
4296 library
=> $library->branchcode,
4297 replacementprice
=> '42',
4300 my $issue = AddIssue
( $patron->unblessed, $item->barcode );
4301 LostItem
( $item->itemnumber, 'cli', 0 );
4302 $item->_result->itemlost(1);
4303 $item->_result->itemlost_on( $lost_on );
4304 $item->_result->update();
4306 my $a = Koha
::Account
::Lines
->search(
4308 itemnumber
=> $item->id,
4309 borrowernumber
=> $patron->borrowernumber
4312 ok
( $a, "Found accountline for lost fee" );
4313 is
( $a->amountoutstanding + 0, 42, "Lost fee charged correctly" );
4314 my ( $doreturn, $messages ) = AddReturn
( $item->barcode, $library->branchcode, undef, dt_from_string
);
4315 $a = $a->get_from_storage;
4316 is
( $a->amountoutstanding + 0, 42, "Lost fee was not refunded" );
4320 subtest
'NoRefundOnLostReturnedItemsAge < length of days item has been lost' => sub {
4323 t
::lib
::Mocks
::mock_preference
( 'WhenLostChargeReplacementFee', 1 );
4324 t
::lib
::Mocks
::mock_preference
( 'NoRefundOnLostReturnedItemsAge', 7 );
4326 my $lost_on = dt_from_string
->subtract( days
=> 8 )->date;
4328 my $item = $builder->build_sample_item(
4330 biblionumber
=> $biblionumber,
4331 library
=> $library->branchcode,
4332 replacementprice
=> '42',
4335 my $issue = AddIssue
( $patron->unblessed, $item->barcode );
4336 LostItem
( $item->itemnumber, 'cli', 0 );
4337 $item->_result->itemlost(1);
4338 $item->_result->itemlost_on( $lost_on );
4339 $item->_result->update();
4341 my $a = Koha
::Account
::Lines
->search(
4343 itemnumber
=> $item->id,
4344 borrowernumber
=> $patron->borrowernumber
4348 ok
( $a, "Found accountline for lost fee" );
4349 is
( $a->amountoutstanding + 0, 42, "Lost fee charged correctly" );
4350 my ( $doreturn, $messages ) = AddReturn
( $item->barcode, $library->branchcode, undef, dt_from_string
);
4351 $a = $a->get_from_storage;
4352 is
( $a->amountoutstanding + 0, 42, "Lost fee was not refunded" );
4357 subtest
'Tests for NoRefundOnLostReturnedItemsAge with AddIssue' => sub {
4361 t
::lib
::Mocks
::mock_preference
('BlockReturnOfLostItems', 0);
4362 my $library = $builder->build_object( { class => 'Koha::Libraries' } );
4363 my $patron = $builder->build_object(
4365 class => 'Koha::Patrons',
4366 value
=> { categorycode
=> $patron_category->{categorycode
} }
4369 my $patron2 = $builder->build_object(
4371 class => 'Koha::Patrons',
4372 value
=> { categorycode
=> $patron_category->{categorycode
} }
4376 my $biblionumber = $builder->build_sample_biblio(
4378 branchcode
=> $library->branchcode,
4382 # And the circulation rule
4383 Koha
::CirculationRules
->search->delete;
4384 Koha
::CirculationRules
->set_rules(
4386 categorycode
=> undef,
4388 branchcode
=> undef,
4391 lengthunit
=> 'days',
4397 source
=> 'CirculationRule',
4399 branchcode
=> undef,
4400 categorycode
=> undef,
4402 rule_name
=> 'lostreturn',
4403 rule_value
=> 'refund'
4408 subtest
'NoRefundOnLostReturnedItemsAge = undef' => sub {
4411 t
::lib
::Mocks
::mock_preference
( 'WhenLostChargeReplacementFee', 1 );
4412 t
::lib
::Mocks
::mock_preference
( 'NoRefundOnLostReturnedItemsAge', undef );
4414 my $lost_on = dt_from_string
->subtract( days
=> 7 )->date;
4416 my $item = $builder->build_sample_item(
4418 biblionumber
=> $biblionumber,
4419 library
=> $library->branchcode,
4420 replacementprice
=> '42',
4423 my $issue = AddIssue
( $patron->unblessed, $item->barcode );
4424 LostItem
( $item->itemnumber, 'cli', 0 );
4425 $item->_result->itemlost(1);
4426 $item->_result->itemlost_on( $lost_on );
4427 $item->_result->update();
4429 my $a = Koha
::Account
::Lines
->search(
4431 itemnumber
=> $item->id,
4432 borrowernumber
=> $patron->borrowernumber
4435 ok
( $a, "Found accountline for lost fee" );
4436 is
( $a->amountoutstanding + 0, 42, "Lost fee charged correctly" );
4437 $issue = AddIssue
( $patron2->unblessed, $item->barcode );
4438 $a = $a->get_from_storage;
4439 is
( $a->amountoutstanding + 0, 0, "Lost fee was refunded" );
4444 subtest
'NoRefundOnLostReturnedItemsAge > length of days item has been lost' => sub {
4447 t
::lib
::Mocks
::mock_preference
( 'WhenLostChargeReplacementFee', 1 );
4448 t
::lib
::Mocks
::mock_preference
( 'NoRefundOnLostReturnedItemsAge', 7 );
4450 my $lost_on = dt_from_string
->subtract( days
=> 6 )->date;
4452 my $item = $builder->build_sample_item(
4454 biblionumber
=> $biblionumber,
4455 library
=> $library->branchcode,
4456 replacementprice
=> '42',
4459 my $issue = AddIssue
( $patron->unblessed, $item->barcode );
4460 LostItem
( $item->itemnumber, 'cli', 0 );
4461 $item->_result->itemlost(1);
4462 $item->_result->itemlost_on( $lost_on );
4463 $item->_result->update();
4465 my $a = Koha
::Account
::Lines
->search(
4467 itemnumber
=> $item->id,
4468 borrowernumber
=> $patron->borrowernumber
4471 ok
( $a, "Found accountline for lost fee" );
4472 is
( $a->amountoutstanding + 0, 42, "Lost fee charged correctly" );
4473 $issue = AddIssue
( $patron2->unblessed, $item->barcode );
4474 $a = $a->get_from_storage;
4475 is
( $a->amountoutstanding + 0, 0, "Lost fee was refunded" );
4479 subtest
'NoRefundOnLostReturnedItemsAge = length of days item has been lost' => sub {
4482 t
::lib
::Mocks
::mock_preference
( 'WhenLostChargeReplacementFee', 1 );
4483 t
::lib
::Mocks
::mock_preference
( 'NoRefundOnLostReturnedItemsAge', 7 );
4485 my $lost_on = dt_from_string
->subtract( days
=> 7 )->date;
4487 my $item = $builder->build_sample_item(
4489 biblionumber
=> $biblionumber,
4490 library
=> $library->branchcode,
4491 replacementprice
=> '42',
4494 my $issue = AddIssue
( $patron->unblessed, $item->barcode );
4495 LostItem
( $item->itemnumber, 'cli', 0 );
4496 $item->_result->itemlost(1);
4497 $item->_result->itemlost_on( $lost_on );
4498 $item->_result->update();
4500 my $a = Koha
::Account
::Lines
->search(
4502 itemnumber
=> $item->id,
4503 borrowernumber
=> $patron->borrowernumber
4506 ok
( $a, "Found accountline for lost fee" );
4507 is
( $a->amountoutstanding + 0, 42, "Lost fee charged correctly" );
4508 $issue = AddIssue
( $patron2->unblessed, $item->barcode );
4509 $a = $a->get_from_storage;
4510 is
( $a->amountoutstanding + 0, 42, "Lost fee was not refunded" );
4514 subtest
'NoRefundOnLostReturnedItemsAge < length of days item has been lost' => sub {
4517 t
::lib
::Mocks
::mock_preference
( 'WhenLostChargeReplacementFee', 1 );
4518 t
::lib
::Mocks
::mock_preference
( 'NoRefundOnLostReturnedItemsAge', 7 );
4520 my $lost_on = dt_from_string
->subtract( days
=> 8 )->date;
4522 my $item = $builder->build_sample_item(
4524 biblionumber
=> $biblionumber,
4525 library
=> $library->branchcode,
4526 replacementprice
=> '42',
4529 my $issue = AddIssue
( $patron->unblessed, $item->barcode );
4530 LostItem
( $item->itemnumber, 'cli', 0 );
4531 $item->_result->itemlost(1);
4532 $item->_result->itemlost_on( $lost_on );
4533 $item->_result->update();
4535 my $a = Koha
::Account
::Lines
->search(
4537 itemnumber
=> $item->id,
4538 borrowernumber
=> $patron->borrowernumber
4542 ok
( $a, "Found accountline for lost fee" );
4543 is
( $a->amountoutstanding + 0, 42, "Lost fee charged correctly" );
4544 $issue = AddIssue
( $patron2->unblessed, $item->barcode );
4545 $a = $a->get_from_storage;
4546 is
( $a->amountoutstanding + 0, 42, "Lost fee was not refunded" );
4551 subtest
'transferbook tests' => sub {
4555 { C4
::Circulation
::transferbook
({}); }
4556 'Koha::Exceptions::MissingParameter',
4557 'Koha::Patron->store raises an exception on missing params';
4560 { C4
::Circulation
::transferbook
({to_branch
=>'anything'}); }
4561 'Koha::Exceptions::MissingParameter',
4562 'Koha::Patron->store raises an exception on missing params';
4565 { C4
::Circulation
::transferbook
({from_branch
=>'anything'}); }
4566 'Koha::Exceptions::MissingParameter',
4567 'Koha::Patron->store raises an exception on missing params';
4569 my ($doreturn,$messages) = C4
::Circulation
::transferbook
({to_branch
=>'there',from_branch
=>'here'});
4570 is
( $doreturn, 0, "No return without barcode");
4571 ok
( exists $messages->{BadBarcode
}, "We get a BadBarcode message if no barcode passed");
4572 is
( $messages->{BadBarcode
}, undef, "No barcode passed means undef BadBarcode" );
4574 ($doreturn,$messages) = C4
::Circulation
::transferbook
({to_branch
=>'there',from_branch
=>'here',barcode
=>'BadBarcode'});
4575 is
( $doreturn, 0, "No return without barcode");
4576 ok
( exists $messages->{BadBarcode
}, "We get a BadBarcode message if no barcode passed");
4577 is
( $messages->{BadBarcode
}, 'BadBarcode', "No barcode passed means undef BadBarcode" );
4581 subtest
'Checkout should correctly terminate a transfer' => sub {
4584 my $library_1 = $builder->build_object( { class => 'Koha::Libraries' } );
4585 my $patron_1 = $builder->build_object(
4587 class => 'Koha::Patrons',
4588 value
=> { branchcode
=> $library_1->branchcode }
4591 my $library_2 = $builder->build_object( { class => 'Koha::Libraries' } );
4592 my $patron_2 = $builder->build_object(
4594 class => 'Koha::Patrons',
4595 value
=> { branchcode
=> $library_2->branchcode }
4599 my $item = $builder->build_sample_item(
4601 library
=> $library_1->branchcode,
4605 t
::lib
::Mocks
::mock_userenv
( { branchcode
=> $library_1->branchcode } );
4606 my $reserve_id = AddReserve
(
4608 branchcode
=> $library_2->branchcode,
4609 borrowernumber
=> $patron_2->borrowernumber,
4610 biblionumber
=> $item->biblionumber,
4611 itemnumber
=> $item->itemnumber,
4616 my $do_transfer = 1;
4617 ModItemTransfer
( $item->itemnumber, $library_1->branchcode,
4618 $library_2->branchcode );
4619 ModReserveAffect
( $item->itemnumber, undef, $do_transfer, $reserve_id );
4620 GetOtherReserves
( $item->itemnumber )
4621 ; # To put the Reason, it's what does returns.pl...
4622 my $hold = Koha
::Holds
->find($reserve_id);
4623 is
( $hold->found, 'T', 'Hold is in transit' );
4624 my $transfer = $item->get_transfer;
4625 is
( $transfer->frombranch, $library_1->branchcode );
4626 is
( $transfer->tobranch, $library_2->branchcode );
4627 is
( $transfer->reason, 'Reserve' );
4629 t
::lib
::Mocks
::mock_userenv
( { branchcode
=> $library_2->branchcode } );
4630 AddIssue
( $patron_1->unblessed, $item->barcode );
4631 $transfer = $transfer->get_from_storage;
4632 isnt
( $transfer->datearrived, undef );
4633 $hold = $hold->get_from_storage;
4634 is
( $hold->found, undef, 'Hold is waiting' );
4635 is
( $hold->priority, 1, );
4638 subtest
'AddIssue records staff who checked out item if appropriate' => sub {
4641 $module->mock( 'userenv', sub { { branch
=> $library->{id
} } } );
4643 my $library = $builder->build_object( { class => 'Koha::Libraries' } );
4644 my $patron = $builder->build_object(
4646 class => 'Koha::Patrons',
4647 value
=> { categorycode
=> $patron_category->{categorycode
} }
4650 my $issuer = $builder->build_object(
4652 class => 'Koha::Patrons',
4653 value
=> { categorycode
=> $patron_category->{categorycode
} }
4656 my $item = $builder->build_sample_item(
4658 library
=> $library->{branchcode
}
4662 $module->mock( 'userenv', sub { { branch
=> $library->id, number
=> $issuer->{borrowernumber
} } } );
4664 my $dt_from = dt_from_string
();
4665 my $dt_to = dt_from_string
()->add( days
=> 7 );
4667 my $issue = AddIssue
( $patron->unblessed, $item->barcode, $dt_to, undef, $dt_from );
4669 is
( $issue->issuer, undef, "Staff who checked out the item not recorded when RecordStaffUserOnCheckout turned off" );
4671 t
::lib
::Mocks
::mock_preference
('RecordStaffUserOnCheckout', 1);
4674 AddIssue
( $patron->unblessed, $item->barcode, $dt_to, undef, $dt_from );
4676 is
( $issue->issuer, $issuer->{borrowernumber
}, "Staff who checked out the item recorded when RecordStaffUserOnCheckout turned on" );
4679 $schema->storage->txn_rollback;
4680 C4
::Context
->clear_syspref_cache();
4681 $branches = Koha
::Libraries
->search();
4682 for my $branch ( $branches->next ) {
4683 my $key = $branch->branchcode . "_holidays";
4684 $cache->clear_from_cache($key);