3 # This file is part of Koha.
5 # Koha is free software; you can redistribute it and/or modify it under the
6 # terms of the GNU General Public License as published by the Free Software
7 # Foundation; either version 3 of the License, or (at your option) any later
10 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
11 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12 # A PARTICULAR PURPOSE. See the GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License along
15 # with Koha; if not, see <http://www.gnu.org/licenses>.
18 use Test::More tests => 9;
27 use Koha::DateUtils qw( dt_from_string );
29 use Koha::CirculationRules;
31 use t::lib::TestBuilder;
34 my $schema = Koha::Database->new->schema;
35 $schema->storage->txn_begin;
37 our $dbh = C4::Context->dbh;
39 $dbh->do(q|DELETE FROM issues|);
40 $dbh->do(q|DELETE FROM items|);
41 $dbh->do(q|DELETE FROM borrowers|);
42 $dbh->do(q|DELETE FROM branches|);
43 $dbh->do(q|DELETE FROM categories|);
44 $dbh->do(q|DELETE FROM accountlines|);
45 $dbh->do(q|DELETE FROM itemtypes|);
46 $dbh->do(q|DELETE FROM issuingrules|);
47 Koha::CirculationRules->search()->delete();
49 my $builder = t::lib::TestBuilder->new();
50 t::lib::Mocks::mock_preference('item-level_itypes', 1); # Assuming the item type is defined at item level
52 my $branch = $builder->build({
56 my $category = $builder->build({
60 my $patron = $builder->build({
63 categorycode => $category->{categorycode},
64 branchcode => $branch->{branchcode},
68 my $biblio = $builder->build({
71 branchcode => $branch->{branchcode},
74 my $item = $builder->build({
77 biblionumber => $biblio->{biblionumber},
78 homebranch => $branch->{branchcode},
79 holdingbranch => $branch->{branchcode},
83 my $patron_object = Koha::Patrons->find( $patron->{borrowernumber} );
84 my $item_object = Koha::Items->find( $item->{itemnumber} );
85 t::lib::Mocks::mock_userenv( { patron => $patron_object });
87 # TooMany return ($current_loan_count, $max_loans_allowed) or undef
89 # OSCO: On-site checkout
91 subtest 'no rules exist' => sub {
94 C4::Circulation::TooMany( $patron, $item_object ),
95 { reason => 'NO_RULE_DEFINED', max_allowed => 0 },
96 'CO should not be allowed, in any cases'
99 C4::Circulation::TooMany( $patron, $item_object, { onsite_checkout => 1 } ),
100 { reason => 'NO_RULE_DEFINED', max_allowed => 0 },
101 'OSCO should not be allowed, in any cases'
105 subtest '1 Issuingrule exist 0 0: no issue allowed' => sub {
107 Koha::CirculationRules->set_rules(
109 branchcode => $branch->{branchcode},
110 categorycode => $category->{categorycode},
114 maxonsiteissueqty => 0,
118 t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 0);
120 C4::Circulation::TooMany( $patron, $item_object ),
122 reason => 'TOO_MANY_CHECKOUTS',
126 'CO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
129 C4::Circulation::TooMany( $patron, $item_object, { onsite_checkout => 1 } ),
131 reason => 'TOO_MANY_ONSITE_CHECKOUTS',
135 'OSCO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
138 t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 1);
140 C4::Circulation::TooMany( $patron, $item_object ),
142 reason => 'TOO_MANY_CHECKOUTS',
146 'CO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
149 C4::Circulation::TooMany( $patron, $item_object, { onsite_checkout => 1 } ),
151 reason => 'TOO_MANY_ONSITE_CHECKOUTS',
155 'OSCO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
161 subtest '1 Issuingrule exist with onsiteissueqty=unlimited' => sub {
164 Koha::CirculationRules->set_rules(
166 branchcode => $branch->{branchcode},
167 categorycode => $category->{categorycode},
171 maxonsiteissueqty => undef,
176 my $issue = C4::Circulation::AddIssue( $patron, $item->{barcode}, dt_from_string() );
177 t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 0);
179 C4::Circulation::TooMany( $patron, $item_object ),
181 reason => 'TOO_MANY_CHECKOUTS',
185 'CO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
188 C4::Circulation::TooMany( $patron, $item_object, { onsite_checkout => 1 } ),
190 'OSCO should be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
193 t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 1);
195 C4::Circulation::TooMany( $patron, $item_object ),
197 reason => 'TOO_MANY_CHECKOUTS',
201 'CO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
204 C4::Circulation::TooMany( $patron, $item_object, { onsite_checkout => 1 } ),
206 reason => 'TOO_MANY_CHECKOUTS',
210 'OSCO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
217 subtest '1 Issuingrule exist 1 1: issue is allowed' => sub {
219 Koha::CirculationRules->set_rules(
221 branchcode => $branch->{branchcode},
222 categorycode => $category->{categorycode},
226 maxonsiteissueqty => 1,
230 t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 0);
232 C4::Circulation::TooMany( $patron, $item_object ),
234 'CO should be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
237 C4::Circulation::TooMany( $patron, $item_object, { onsite_checkout => 1 } ),
239 'OSCO should be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
242 t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 1);
244 C4::Circulation::TooMany( $patron, $item_object ),
246 'CO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
249 C4::Circulation::TooMany( $patron, $item_object, { onsite_checkout => 1 } ),
251 'OSCO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
257 subtest '1 Issuingrule exist: 1 CO allowed, 1 OSCO allowed. Do a CO' => sub {
259 Koha::CirculationRules->set_rules(
261 branchcode => $branch->{branchcode},
262 categorycode => $category->{categorycode},
266 maxonsiteissueqty => 1,
271 my $issue = C4::Circulation::AddIssue( $patron, $item->{barcode}, dt_from_string() );
272 like( $issue->issue_id, qr|^\d+$|, 'The issue should have been inserted' );
274 t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 0);
276 C4::Circulation::TooMany( $patron, $item_object ),
278 reason => 'TOO_MANY_CHECKOUTS',
282 'CO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
285 C4::Circulation::TooMany( $patron, $item_object, { onsite_checkout => 1 } ),
287 'OSCO should be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
290 t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 1);
292 C4::Circulation::TooMany( $patron, $item_object ),
294 reason => 'TOO_MANY_CHECKOUTS',
298 'CO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
301 C4::Circulation::TooMany( $patron, $item_object, { onsite_checkout => 1 } ),
303 reason => 'TOO_MANY_CHECKOUTS',
307 'OSCO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
313 subtest '1 Issuingrule exist: 1 CO allowed, 1 OSCO allowed, Do a OSCO' => sub {
315 Koha::CirculationRules->set_rules(
317 branchcode => $branch->{branchcode},
318 categorycode => $category->{categorycode},
322 maxonsiteissueqty => 1,
327 my $issue = C4::Circulation::AddIssue( $patron, $item->{barcode}, dt_from_string(), undef, undef, undef, { onsite_checkout => 1 } );
328 like( $issue->issue_id, qr|^\d+$|, 'The issue should have been inserted' );
330 t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 0);
332 C4::Circulation::TooMany( $patron, $item_object ),
334 'CO should be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
337 C4::Circulation::TooMany( $patron, $item_object, { onsite_checkout => 1 } ),
339 reason => 'TOO_MANY_ONSITE_CHECKOUTS',
343 'OSCO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
346 t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 1);
348 C4::Circulation::TooMany( $patron, $item_object ),
350 reason => 'TOO_MANY_CHECKOUTS',
354 'CO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
357 C4::Circulation::TooMany( $patron, $item_object, { onsite_checkout => 1 } ),
359 reason => 'TOO_MANY_ONSITE_CHECKOUTS',
363 'OSCO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
369 subtest '1 BranchBorrowerCircRule exist: 1 CO allowed, 1 OSCO allowed' => sub {
370 # Note: the same test coul be done for
371 # DefaultBorrowerCircRule, DefaultBranchCircRule, DefaultBranchItemRule ans DefaultCircRule.pm
374 Koha::CirculationRules->set_rules(
376 branchcode => $branch->{branchcode},
377 categorycode => $category->{categorycode},
381 maxonsiteissueqty => 1,
386 my $issue = C4::Circulation::AddIssue( $patron, $item->{barcode}, dt_from_string(), undef, undef, undef );
387 like( $issue->issue_id, qr|^\d+$|, 'The issue should have been inserted' );
389 t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 0);
391 C4::Circulation::TooMany( $patron, $item_object ),
393 reason => 'TOO_MANY_CHECKOUTS',
397 'CO should be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
400 C4::Circulation::TooMany( $patron, $item_object, { onsite_checkout => 1 } ),
402 'OSCO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
405 t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 1);
407 C4::Circulation::TooMany( $patron, $item_object ),
409 reason => 'TOO_MANY_CHECKOUTS',
413 'CO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
416 C4::Circulation::TooMany( $patron, $item_object, { onsite_checkout => 1 } ),
418 reason => 'TOO_MANY_CHECKOUTS',
422 'OSCO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
427 $issue = C4::Circulation::AddIssue( $patron, $item->{barcode}, dt_from_string(), undef, undef, undef, { onsite_checkout => 1 } );
428 like( $issue->issue_id, qr|^\d+$|, 'The issue should have been inserted' );
430 t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 0);
432 C4::Circulation::TooMany( $patron, $item_object ),
434 'CO should be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
437 C4::Circulation::TooMany( $patron, $item_object, { onsite_checkout => 1 } ),
439 reason => 'TOO_MANY_ONSITE_CHECKOUTS',
443 'OSCO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
446 t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 1);
448 C4::Circulation::TooMany( $patron, $item_object ),
450 reason => 'TOO_MANY_CHECKOUTS',
454 'CO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
457 C4::Circulation::TooMany( $patron, $item_object, { onsite_checkout => 1 } ),
459 reason => 'TOO_MANY_ONSITE_CHECKOUTS',
463 'OSCO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
469 subtest 'General vs specific rules limit quantity correctly' => sub {
472 t::lib::Mocks::mock_preference('CircControl', 'ItemHomeLibrary');
473 my $branch = $builder->build({source => 'Branch',});
474 my $category = $builder->build({source => 'Category',});
475 my $itemtype = $builder->build({
476 source => 'Itemtype',
479 rentalcharge_daily => 0,
480 rentalcharge_hourly => 0,
484 my $patron = $builder->build({
485 source => 'Borrower',
487 categorycode => $category->{categorycode},
488 branchcode => $branch->{branchcode},
492 # Set up an issuing rule
493 my $rule = $builder->build({
494 source => 'Issuingrule',
497 itemtype => $itemtype->{itemtype},
500 firstremind => 1, # 1 day of grace
501 finedays => 2, # 2 days of fine per day of overdue
502 lengthunit => 'days',
506 # Set an All->All for an itemtype
507 Koha::CirculationRules->set_rules(
511 itemtype => $itemtype->{itemtype},
514 maxonsiteissueqty => 1,
520 my $issue_item = $builder->build_sample_item({
521 itype => $itemtype->{itemtype}
523 my $branch_item = $builder->build_sample_item({
524 itype => $itemtype->{itemtype},
525 homebranch => $branch->{branchcode},
526 holdingbranch => $branch->{branchcode}
530 t::lib::Mocks::mock_userenv({ branchcode => $branch->{branchcode} });
531 my $issue = C4::Circulation::AddIssue( $patron, $issue_item->barcode, dt_from_string() );
532 # We checkout one item
534 C4::Circulation::TooMany( $patron, $branch_item ),
536 reason => 'TOO_MANY_CHECKOUTS',
540 'We are only allowed one, and we have one (itemtype on item)'
543 # Check itemtype on biblio level
544 t::lib::Mocks::mock_preference('item-level_itypes', 0);
545 $issue_item->biblio->biblioitem->itemtype($itemtype->{itemtype})->store;
546 $branch_item->biblio->biblioitem->itemtype($itemtype->{itemtype})->store;
547 # We checkout one item
549 C4::Circulation::TooMany( $patron, $branch_item ),
551 reason => 'TOO_MANY_CHECKOUTS',
555 'We are only allowed one, and we have one (itemtype on biblioitem)'
557 t::lib::Mocks::mock_preference('item-level_itypes', 1);
559 # Set a branch specific rule
560 Koha::CirculationRules->set_rules(
562 branchcode => $branch->{branchcode},
563 categorycode => $category->{categorycode},
564 itemtype => $itemtype->{itemtype},
567 maxonsiteissueqty => 1,
573 C4::Circulation::TooMany( $patron, $branch_item ),
575 'We are allowed one from the branch specifically now'
578 # If circcontrol is PatronLibrary we count all the patron's loan, regardless of branch
579 t::lib::Mocks::mock_preference('CircControl', 'PatronLibrary');
581 C4::Circulation::TooMany( $patron, $branch_item ),
583 reason => 'TOO_MANY_CHECKOUTS',
587 'We are allowed one from the branch specifically, but have one'
589 t::lib::Mocks::mock_preference('CircControl', 'ItemHomeLibrary');
591 $issue = C4::Circulation::AddIssue( $patron, $branch_item->barcode, dt_from_string() );
594 my $branch_item_2 = $builder->build_sample_item({
595 itype => $itemtype->{itemtype},
596 homebranch => $branch->{branchcode},
597 holdingbranch => $branch->{branchcode}
600 C4::Circulation::TooMany( $patron, $branch_item_2 ),
602 reason => 'TOO_MANY_CHECKOUTS',
606 'We are only allowed one from that branch, and have one'
609 # Now we make anothe from a different branch
610 my $item_2 = $builder->build_sample_item({
611 itype => $itemtype->{itemtype},
614 C4::Circulation::TooMany( $patron, $item_2 ),
616 reason => 'TOO_MANY_CHECKOUTS',
620 'We are only allowed one for general rule, and have two'
622 t::lib::Mocks::mock_preference('CircControl', 'PatronLibrary');
624 C4::Circulation::TooMany( $patron, $item_2 ),
626 reason => 'TOO_MANY_CHECKOUTS',
630 'We are only allowed one for general rule, and have two'
633 t::lib::Mocks::mock_preference('CircControl', 'PickupLibrary');
635 C4::Circulation::TooMany( $patron, $item_2 ),
637 reason => 'TOO_MANY_CHECKOUTS',
641 'We are only allowed one for general rule, and have checked out two at this branch'
644 my $branch2 = $builder->build({source => 'Branch',});
645 t::lib::Mocks::mock_userenv({ branchcode => $branch2->{branchcode} });
647 C4::Circulation::TooMany( $patron, $item_2 ),
649 reason => 'TOO_MANY_CHECKOUTS',
653 'We are only allowed one for general rule, and have two total (no rule for specific branch)'
655 # Set a branch specific rule for new branch
656 Koha::CirculationRules->set_rules(
658 branchcode => $branch2->{branchcode},
659 categorycode => $category->{categorycode},
660 itemtype => $itemtype->{itemtype},
663 maxonsiteissueqty => 1,
669 C4::Circulation::TooMany( $patron, $branch_item ),
671 'We are allowed one from the branch specifically now'
675 subtest 'empty string means unlimited' => sub {
678 Koha::CirculationRules->set_rules(
685 maxonsiteissueqty => '',
690 C4::Circulation::TooMany( $patron, $item_object ),
692 'maxissueqty="" should mean unlimited'
696 C4::Circulation::TooMany( $patron, $item_object, { onsite_checkout => 1 } ),
698 'maxonsiteissueqty="" should mean unlimited'
704 $schema->storage->txn_rollback;
707 $dbh->do(q|DELETE FROM issues|);
708 $dbh->do(q|DELETE FROM issuingrules|);