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 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, $biblio->{biblionumber}, $item ),
94 { reason => 'NO_RULE_DEFINED', max_allowed => 0 },
95 'CO should not be allowed, in any cases'
98 C4::Circulation::TooMany( $patron, $biblio->{biblionumber}, $item, { 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, $biblio->{biblionumber}, $item ),
121 reason => 'TOO_MANY_CHECKOUTS',
125 'CO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
128 C4::Circulation::TooMany( $patron, $biblio->{biblionumber}, $item, { 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, $biblio->{biblionumber}, $item ),
141 reason => 'TOO_MANY_CHECKOUTS',
145 'CO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
148 C4::Circulation::TooMany( $patron, $biblio->{biblionumber}, $item, { 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, $biblio->{biblionumber}, $item ),
180 reason => 'TOO_MANY_CHECKOUTS',
184 'CO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
187 C4::Circulation::TooMany( $patron, $biblio->{biblionumber}, $item, { onsite_checkout => 1 } ),
189 'OSCO should be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
192 t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 1);
194 C4::Circulation::TooMany( $patron, $biblio->{biblionumber}, $item ),
196 reason => 'TOO_MANY_CHECKOUTS',
200 'CO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
203 C4::Circulation::TooMany( $patron, $biblio->{biblionumber}, $item, { 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, $biblio->{biblionumber}, $item ),
233 'CO should be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
236 C4::Circulation::TooMany( $patron, $biblio->{biblionumber}, $item, { onsite_checkout => 1 } ),
238 'OSCO should be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
241 t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 1);
243 C4::Circulation::TooMany( $patron, $biblio->{biblionumber}, $item ),
245 'CO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
248 C4::Circulation::TooMany( $patron, $biblio->{biblionumber}, $item, { 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, $biblio->{biblionumber}, $item ),
277 reason => 'TOO_MANY_CHECKOUTS',
281 'CO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
284 C4::Circulation::TooMany( $patron, $biblio->{biblionumber}, $item, { onsite_checkout => 1 } ),
286 'OSCO should be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
289 t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 1);
291 C4::Circulation::TooMany( $patron, $biblio->{biblionumber}, $item ),
293 reason => 'TOO_MANY_CHECKOUTS',
297 'CO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
300 C4::Circulation::TooMany( $patron, $biblio->{biblionumber}, $item, { 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, $biblio->{biblionumber}, $item ),
333 'CO should be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
336 C4::Circulation::TooMany( $patron, $biblio->{biblionumber}, $item, { 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, $biblio->{biblionumber}, $item ),
349 reason => 'TOO_MANY_CHECKOUTS',
353 'CO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
356 C4::Circulation::TooMany( $patron, $biblio->{biblionumber}, $item, { 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, $biblio->{biblionumber}, $item ),
392 reason => 'TOO_MANY_CHECKOUTS',
396 'CO should be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
399 C4::Circulation::TooMany( $patron, $biblio->{biblionumber}, $item, { 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, $biblio->{biblionumber}, $item ),
408 reason => 'TOO_MANY_CHECKOUTS',
412 'CO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
415 C4::Circulation::TooMany( $patron, $biblio->{biblionumber}, $item, { onsite_checkout => 1 } ),
417 reason => 'TOO_MANY_CHECKOUTS',
421 'OSCO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
426 $issue = C4::Circulation::AddIssue( $patron, $item->{barcode}, dt_from_string(), undef, undef, undef, { onsite_checkout => 1 } );
427 like( $issue->issue_id, qr|^\d+$|, 'The issue should have been inserted' );
429 t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 0);
431 C4::Circulation::TooMany( $patron, $biblio->{biblionumber}, $item ),
433 'CO should be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
436 C4::Circulation::TooMany( $patron, $biblio->{biblionumber}, $item, { onsite_checkout => 1 } ),
438 reason => 'TOO_MANY_ONSITE_CHECKOUTS',
442 'OSCO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
445 t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 1);
447 C4::Circulation::TooMany( $patron, $biblio->{biblionumber}, $item ),
449 reason => 'TOO_MANY_CHECKOUTS',
453 'CO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
456 C4::Circulation::TooMany( $patron, $biblio->{biblionumber}, $item, { onsite_checkout => 1 } ),
458 reason => 'TOO_MANY_ONSITE_CHECKOUTS',
462 'OSCO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
468 subtest 'General vs specific rules limit quantity correctly' => sub {
471 t::lib::Mocks::mock_preference('CircControl', 'ItemHomeLibrary');
472 my $branch = $builder->build({source => 'Branch',});
473 my $category = $builder->build({source => 'Category',});
474 my $itemtype = $builder->build({
475 source => 'Itemtype',
478 rentalcharge_daily => 0,
479 rentalcharge_hourly => 0,
483 my $patron = $builder->build({
484 source => 'Borrower',
486 categorycode => $category->{categorycode},
487 branchcode => $branch->{branchcode},
491 # Set up an issuing rule
492 my $rule = $builder->build({
493 source => 'Issuingrule',
496 itemtype => $itemtype->{itemtype},
499 firstremind => 1, # 1 day of grace
500 finedays => 2, # 2 days of fine per day of overdue
501 lengthunit => 'days',
505 # Set an All->All for an itemtype
506 Koha::CirculationRules->set_rules(
510 itemtype => $itemtype->{itemtype},
513 maxonsiteissueqty => 1,
519 my $issue_item = $builder->build_sample_item({
520 itype => $itemtype->{itemtype}
522 my $branch_item = $builder->build_sample_item({
523 itype => $itemtype->{itemtype},
524 homebranch => $branch->{branchcode},
525 holdingbranch => $branch->{branchcode}
529 t::lib::Mocks::mock_userenv({ branchcode => $branch->{branchcode} });
530 my $issue = C4::Circulation::AddIssue( $patron, $issue_item->barcode, dt_from_string() );
531 # We checkout one item
533 C4::Circulation::TooMany( $patron, $branch_item->biblionumber, $branch_item->unblessed ),
535 reason => 'TOO_MANY_CHECKOUTS',
539 'We are only allowed one, and we have one (itemtype on item)'
542 # Check itemtype on biblio level
543 t::lib::Mocks::mock_preference('item-level_itypes', 0);
544 $issue_item->biblio->biblioitem->itemtype($itemtype->{itemtype})->store;
545 $branch_item->biblio->biblioitem->itemtype($itemtype->{itemtype})->store;
546 # We checkout one item
548 C4::Circulation::TooMany( $patron, $branch_item->biblionumber, $branch_item->unblessed ),
550 reason => 'TOO_MANY_CHECKOUTS',
554 'We are only allowed one, and we have one (itemtype on biblioitem)'
556 t::lib::Mocks::mock_preference('item-level_itypes', 1);
558 # Set a branch specific rule
559 Koha::CirculationRules->set_rules(
561 branchcode => $branch->{branchcode},
562 categorycode => $category->{categorycode},
563 itemtype => $itemtype->{itemtype},
566 maxonsiteissueqty => 1,
572 C4::Circulation::TooMany( $patron, $branch_item->biblionumber, $branch_item->unblessed ),
574 'We are allowed one from the branch specifically now'
577 # If circcontrol is PatronLibrary we count all the patron's loan, regardless of branch
578 t::lib::Mocks::mock_preference('CircControl', 'PatronLibrary');
580 C4::Circulation::TooMany( $patron, $branch_item->biblionumber, $branch_item->unblessed ),
582 reason => 'TOO_MANY_CHECKOUTS',
586 'We are allowed one from the branch specifically, but have one'
588 t::lib::Mocks::mock_preference('CircControl', 'ItemHomeLibrary');
590 $issue = C4::Circulation::AddIssue( $patron, $branch_item->barcode, dt_from_string() );
593 my $branch_item_2 = $builder->build_sample_item({
594 itype => $itemtype->{itemtype},
595 homebranch => $branch->{branchcode},
596 holdingbranch => $branch->{branchcode}
599 C4::Circulation::TooMany( $patron, $branch_item_2->biblionumber, $branch_item_2->unblessed ),
601 reason => 'TOO_MANY_CHECKOUTS',
605 'We are only allowed one from that branch, and have one'
608 # Now we make anothe from a different branch
609 my $item_2 = $builder->build_sample_item({
610 itype => $itemtype->{itemtype},
613 C4::Circulation::TooMany( $patron, $item_2->biblionumber, $item_2->unblessed ),
615 reason => 'TOO_MANY_CHECKOUTS',
619 'We are only allowed one for general rule, and have two'
621 t::lib::Mocks::mock_preference('CircControl', 'PatronLibrary');
623 C4::Circulation::TooMany( $patron, $item_2->biblionumber, $item_2->unblessed ),
625 reason => 'TOO_MANY_CHECKOUTS',
629 'We are only allowed one for general rule, and have two'
632 t::lib::Mocks::mock_preference('CircControl', 'PickupLibrary');
634 C4::Circulation::TooMany( $patron, $item_2->biblionumber, $item_2->unblessed ),
636 reason => 'TOO_MANY_CHECKOUTS',
640 'We are only allowed one for general rule, and have checked out two at this branch'
643 my $branch2 = $builder->build({source => 'Branch',});
644 t::lib::Mocks::mock_userenv({ branchcode => $branch2->{branchcode} });
646 C4::Circulation::TooMany( $patron, $item_2->biblionumber, $item_2->unblessed ),
648 reason => 'TOO_MANY_CHECKOUTS',
652 'We are only allowed one for general rule, and have two total (no rule for specific branch)'
654 # Set a branch specific rule for new branch
655 Koha::CirculationRules->set_rules(
657 branchcode => $branch2->{branchcode},
658 categorycode => $category->{categorycode},
659 itemtype => $itemtype->{itemtype},
662 maxonsiteissueqty => 1,
668 C4::Circulation::TooMany( $patron, $branch_item->biblionumber, $branch_item->unblessed ),
670 'We are allowed one from the branch specifically now'
674 subtest 'empty string means unlimited' => sub {
677 Koha::CirculationRules->set_rules(
684 maxonsiteissueqty => '',
689 C4::Circulation::TooMany( $patron, $biblio->{biblionumber}, $item ),
691 'maxissueqty="" should mean unlimited'
695 C4::Circulation::TooMany( $patron, $biblio->{biblionumber}, $item, { onsite_checkout => 1 } ),
697 'maxonsiteissueqty="" should mean unlimited'
703 $schema->storage->txn_rollback;
706 $dbh->do(q|DELETE FROM issues|);
707 $dbh->do(q|DELETE FROM issuingrules|);