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 Koha::CirculationRules->search()->delete();
48 my $builder = t::lib::TestBuilder->new();
49 t::lib::Mocks::mock_preference('item-level_itypes', 1); # Assuming the item type is defined at item level
51 my $branch = $builder->build({
55 my $category = $builder->build({
59 my $patron = $builder->build({
62 categorycode => $category->{categorycode},
63 branchcode => $branch->{branchcode},
67 my $biblio = $builder->build({
70 branchcode => $branch->{branchcode},
73 my $item = $builder->build({
76 biblionumber => $biblio->{biblionumber},
77 homebranch => $branch->{branchcode},
78 holdingbranch => $branch->{branchcode},
82 my $patron_object = Koha::Patrons->find( $patron->{borrowernumber} );
83 my $item_object = Koha::Items->find( $item->{itemnumber} );
84 t::lib::Mocks::mock_userenv( { patron => $patron_object });
86 # TooMany return ($current_loan_count, $max_loans_allowed) or undef
88 # OSCO: On-site checkout
90 subtest 'no rules exist' => sub {
93 C4::Circulation::TooMany( $patron, $item_object ),
94 { reason => 'NO_RULE_DEFINED', max_allowed => 0 },
95 'CO should not be allowed, in any cases'
98 C4::Circulation::TooMany( $patron, $item_object, { onsite_checkout => 1 } ),
99 { reason => 'NO_RULE_DEFINED', max_allowed => 0 },
100 'OSCO should not be allowed, in any cases'
104 subtest '1 Issuingrule exist 0 0: no issue allowed' => sub {
106 Koha::CirculationRules->set_rules(
108 branchcode => $branch->{branchcode},
109 categorycode => $category->{categorycode},
113 maxonsiteissueqty => 0,
117 t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 0);
119 C4::Circulation::TooMany( $patron, $item_object ),
121 reason => 'TOO_MANY_CHECKOUTS',
125 'CO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
128 C4::Circulation::TooMany( $patron, $item_object, { onsite_checkout => 1 } ),
130 reason => 'TOO_MANY_ONSITE_CHECKOUTS',
134 'OSCO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
137 t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 1);
139 C4::Circulation::TooMany( $patron, $item_object ),
141 reason => 'TOO_MANY_CHECKOUTS',
145 'CO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
148 C4::Circulation::TooMany( $patron, $item_object, { onsite_checkout => 1 } ),
150 reason => 'TOO_MANY_ONSITE_CHECKOUTS',
154 'OSCO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
160 subtest '1 Issuingrule exist with onsiteissueqty=unlimited' => sub {
163 Koha::CirculationRules->set_rules(
165 branchcode => $branch->{branchcode},
166 categorycode => $category->{categorycode},
170 maxonsiteissueqty => undef,
175 my $issue = C4::Circulation::AddIssue( $patron, $item->{barcode}, dt_from_string() );
176 t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 0);
178 C4::Circulation::TooMany( $patron, $item_object ),
180 reason => 'TOO_MANY_CHECKOUTS',
184 'CO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
187 C4::Circulation::TooMany( $patron, $item_object, { onsite_checkout => 1 } ),
189 'OSCO should be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
192 t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 1);
194 C4::Circulation::TooMany( $patron, $item_object ),
196 reason => 'TOO_MANY_CHECKOUTS',
200 'CO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
203 C4::Circulation::TooMany( $patron, $item_object, { onsite_checkout => 1 } ),
205 reason => 'TOO_MANY_CHECKOUTS',
209 'OSCO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
216 subtest '1 Issuingrule exist 1 1: issue is allowed' => sub {
218 Koha::CirculationRules->set_rules(
220 branchcode => $branch->{branchcode},
221 categorycode => $category->{categorycode},
225 maxonsiteissueqty => 1,
229 t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 0);
231 C4::Circulation::TooMany( $patron, $item_object ),
233 'CO should be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
236 C4::Circulation::TooMany( $patron, $item_object, { onsite_checkout => 1 } ),
238 'OSCO should be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
241 t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 1);
243 C4::Circulation::TooMany( $patron, $item_object ),
245 'CO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
248 C4::Circulation::TooMany( $patron, $item_object, { onsite_checkout => 1 } ),
250 'OSCO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
256 subtest '1 Issuingrule exist: 1 CO allowed, 1 OSCO allowed. Do a CO' => sub {
258 Koha::CirculationRules->set_rules(
260 branchcode => $branch->{branchcode},
261 categorycode => $category->{categorycode},
265 maxonsiteissueqty => 1,
270 my $issue = C4::Circulation::AddIssue( $patron, $item->{barcode}, dt_from_string() );
271 like( $issue->issue_id, qr|^\d+$|, 'The issue should have been inserted' );
273 t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 0);
275 C4::Circulation::TooMany( $patron, $item_object ),
277 reason => 'TOO_MANY_CHECKOUTS',
281 'CO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
284 C4::Circulation::TooMany( $patron, $item_object, { onsite_checkout => 1 } ),
286 'OSCO should be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
289 t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 1);
291 C4::Circulation::TooMany( $patron, $item_object ),
293 reason => 'TOO_MANY_CHECKOUTS',
297 'CO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
300 C4::Circulation::TooMany( $patron, $item_object, { onsite_checkout => 1 } ),
302 reason => 'TOO_MANY_CHECKOUTS',
306 'OSCO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
312 subtest '1 Issuingrule exist: 1 CO allowed, 1 OSCO allowed, Do a OSCO' => sub {
314 Koha::CirculationRules->set_rules(
316 branchcode => $branch->{branchcode},
317 categorycode => $category->{categorycode},
321 maxonsiteissueqty => 1,
326 my $issue = C4::Circulation::AddIssue( $patron, $item->{barcode}, dt_from_string(), undef, undef, undef, { onsite_checkout => 1 } );
327 like( $issue->issue_id, qr|^\d+$|, 'The issue should have been inserted' );
329 t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 0);
331 C4::Circulation::TooMany( $patron, $item_object ),
333 'CO should be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
336 C4::Circulation::TooMany( $patron, $item_object, { onsite_checkout => 1 } ),
338 reason => 'TOO_MANY_ONSITE_CHECKOUTS',
342 'OSCO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
345 t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 1);
347 C4::Circulation::TooMany( $patron, $item_object ),
349 reason => 'TOO_MANY_CHECKOUTS',
353 'CO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
356 C4::Circulation::TooMany( $patron, $item_object, { onsite_checkout => 1 } ),
358 reason => 'TOO_MANY_ONSITE_CHECKOUTS',
362 'OSCO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
368 subtest '1 BranchBorrowerCircRule exist: 1 CO allowed, 1 OSCO allowed' => sub {
369 # Note: the same test coul be done for
370 # DefaultBorrowerCircRule, DefaultBranchCircRule, DefaultBranchItemRule ans DefaultCircRule.pm
373 Koha::CirculationRules->set_rules(
375 branchcode => $branch->{branchcode},
376 categorycode => $category->{categorycode},
380 maxonsiteissueqty => 1,
385 my $issue = C4::Circulation::AddIssue( $patron, $item->{barcode}, dt_from_string(), undef, undef, undef );
386 like( $issue->issue_id, qr|^\d+$|, 'The issue should have been inserted' );
388 t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 0);
390 C4::Circulation::TooMany( $patron, $item_object ),
392 reason => 'TOO_MANY_CHECKOUTS',
396 'CO should be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
399 C4::Circulation::TooMany( $patron, $item_object, { onsite_checkout => 1 } ),
401 'OSCO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
404 t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 1);
406 C4::Circulation::TooMany( $patron, $item_object ),
408 reason => 'TOO_MANY_CHECKOUTS',
412 'CO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
415 C4::Circulation::TooMany( $patron, $item_object, { onsite_checkout => 1 } ),
417 reason => 'TOO_MANY_CHECKOUTS',
421 'OSCO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
425 Koha::CirculationRules->set_rules(
427 branchcode => $branch->{branchcode},
428 categorycode => $category->{categorycode},
432 maxonsiteissueqty => 1,
437 $issue = C4::Circulation::AddIssue( $patron, $item->{barcode}, dt_from_string(), undef, undef, undef, { onsite_checkout => 1 } );
438 like( $issue->issue_id, qr|^\d+$|, 'The issue should have been inserted' );
440 t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 0);
442 C4::Circulation::TooMany( $patron, $item_object ),
444 'CO should be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
447 C4::Circulation::TooMany( $patron, $item_object, { onsite_checkout => 1 } ),
449 reason => 'TOO_MANY_ONSITE_CHECKOUTS',
453 'OSCO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
456 t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 1);
458 C4::Circulation::TooMany( $patron, $item_object ),
460 reason => 'TOO_MANY_CHECKOUTS',
464 'CO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
467 C4::Circulation::TooMany( $patron, $item_object, { onsite_checkout => 1 } ),
469 reason => 'TOO_MANY_ONSITE_CHECKOUTS',
473 'OSCO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
479 subtest 'General vs specific rules limit quantity correctly' => sub {
482 t::lib::Mocks::mock_preference('CircControl', 'ItemHomeLibrary');
483 my $branch = $builder->build({source => 'Branch',});
484 my $category = $builder->build({source => 'Category',});
485 my $itemtype = $builder->build({
486 source => 'Itemtype',
489 rentalcharge_daily => 0,
490 rentalcharge_hourly => 0,
494 my $patron = $builder->build({
495 source => 'Borrower',
497 categorycode => $category->{categorycode},
498 branchcode => $branch->{branchcode},
502 # Set up an issuing rule
503 Koha::CirculationRules->set_rules(
506 itemtype => $itemtype->{itemtype},
510 firstremind => 1, # 1 day of grace
511 finedays => 2, # 2 days of fine per day of overdue
512 lengthunit => 'days',
517 # Set an All->All for an itemtype
518 Koha::CirculationRules->set_rules(
522 itemtype => $itemtype->{itemtype},
525 maxonsiteissueqty => 1,
531 my $issue_item = $builder->build_sample_item({
532 itype => $itemtype->{itemtype}
534 my $branch_item = $builder->build_sample_item({
535 itype => $itemtype->{itemtype},
536 homebranch => $branch->{branchcode},
537 holdingbranch => $branch->{branchcode}
541 t::lib::Mocks::mock_userenv({ branchcode => $branch->{branchcode} });
542 my $issue = C4::Circulation::AddIssue( $patron, $issue_item->barcode, dt_from_string() );
543 # We checkout one item
545 C4::Circulation::TooMany( $patron, $branch_item ),
547 reason => 'TOO_MANY_CHECKOUTS',
551 'We are only allowed one, and we have one (itemtype on item)'
554 # Check itemtype on biblio level
555 t::lib::Mocks::mock_preference('item-level_itypes', 0);
556 $issue_item->biblio->biblioitem->itemtype($itemtype->{itemtype})->store;
557 $branch_item->biblio->biblioitem->itemtype($itemtype->{itemtype})->store;
558 # We checkout one item
560 C4::Circulation::TooMany( $patron, $branch_item ),
562 reason => 'TOO_MANY_CHECKOUTS',
566 'We are only allowed one, and we have one (itemtype on biblioitem)'
568 t::lib::Mocks::mock_preference('item-level_itypes', 1);
570 # Set a branch specific rule
571 Koha::CirculationRules->set_rules(
573 branchcode => $branch->{branchcode},
574 categorycode => $category->{categorycode},
575 itemtype => $itemtype->{itemtype},
578 maxonsiteissueqty => 1,
584 C4::Circulation::TooMany( $patron, $branch_item ),
586 'We are allowed one from the branch specifically now'
589 # If circcontrol is PatronLibrary we count all the patron's loan, regardless of branch
590 t::lib::Mocks::mock_preference('CircControl', 'PatronLibrary');
592 C4::Circulation::TooMany( $patron, $branch_item ),
594 reason => 'TOO_MANY_CHECKOUTS',
598 'We are allowed one from the branch specifically, but have one'
600 t::lib::Mocks::mock_preference('CircControl', 'ItemHomeLibrary');
602 $issue = C4::Circulation::AddIssue( $patron, $branch_item->barcode, dt_from_string() );
605 my $branch_item_2 = $builder->build_sample_item({
606 itype => $itemtype->{itemtype},
607 homebranch => $branch->{branchcode},
608 holdingbranch => $branch->{branchcode}
611 C4::Circulation::TooMany( $patron, $branch_item_2 ),
613 reason => 'TOO_MANY_CHECKOUTS',
617 'We are only allowed one from that branch, and have one'
620 # Now we make anothe from a different branch
621 my $item_2 = $builder->build_sample_item({
622 itype => $itemtype->{itemtype},
625 C4::Circulation::TooMany( $patron, $item_2 ),
627 reason => 'TOO_MANY_CHECKOUTS',
631 'We are only allowed one for general rule, and have two'
633 t::lib::Mocks::mock_preference('CircControl', 'PatronLibrary');
635 C4::Circulation::TooMany( $patron, $item_2 ),
637 reason => 'TOO_MANY_CHECKOUTS',
641 'We are only allowed one for general rule, and have two'
644 t::lib::Mocks::mock_preference('CircControl', 'PickupLibrary');
646 C4::Circulation::TooMany( $patron, $item_2 ),
648 reason => 'TOO_MANY_CHECKOUTS',
652 'We are only allowed one for general rule, and have checked out two at this branch'
655 my $branch2 = $builder->build({source => 'Branch',});
656 t::lib::Mocks::mock_userenv({ branchcode => $branch2->{branchcode} });
658 C4::Circulation::TooMany( $patron, $item_2 ),
660 reason => 'TOO_MANY_CHECKOUTS',
664 'We are only allowed one for general rule, and have two total (no rule for specific branch)'
666 # Set a branch specific rule for new branch
667 Koha::CirculationRules->set_rules(
669 branchcode => $branch2->{branchcode},
670 categorycode => $category->{categorycode},
671 itemtype => $itemtype->{itemtype},
674 maxonsiteissueqty => 1,
680 C4::Circulation::TooMany( $patron, $branch_item ),
682 'We are allowed one from the branch specifically now'
686 subtest 'empty string means unlimited' => sub {
689 Koha::CirculationRules->set_rules(
696 maxonsiteissueqty => '',
701 C4::Circulation::TooMany( $patron, $item_object ),
703 'maxissueqty="" should mean unlimited'
707 C4::Circulation::TooMany( $patron, $item_object, { onsite_checkout => 1 } ),
709 'maxonsiteissueqty="" should mean unlimited'
715 $schema->storage->txn_rollback;
718 $dbh->do(q|DELETE FROM issues|);
719 $dbh->do(q|DELETE FROM circulation_rules|);