Bug 26132: Remove raw sql query
[koha-ffzg.git] / t / db_dependent / Circulation / TooMany.t
index 7ce7e8e..0f4843b 100644 (file)
 # with Koha; if not, see <http://www.gnu.org/licenses>.
 
 use Modern::Perl;
-use Test::More tests => 6;
+use Test::More tests => 10;
 use C4::Context;
 
-use C4::Biblio;
 use C4::Members;
-use C4::Circulation;
 use C4::Items;
+use C4::Biblio;
+use C4::Circulation;
 use C4::Context;
 
 use Koha::DateUtils qw( dt_from_string );
 use Koha::Database;
+use Koha::CirculationRules;
 
 use t::lib::TestBuilder;
 use t::lib::Mocks;
@@ -42,12 +43,7 @@ $dbh->do(q|DELETE FROM branches|);
 $dbh->do(q|DELETE FROM categories|);
 $dbh->do(q|DELETE FROM accountlines|);
 $dbh->do(q|DELETE FROM itemtypes|);
-$dbh->do(q|DELETE FROM branch_item_rules|);
-$dbh->do(q|DELETE FROM branch_borrower_circ_rules|);
-$dbh->do(q|DELETE FROM default_branch_circ_rules|);
-$dbh->do(q|DELETE FROM default_circ_rules|);
-$dbh->do(q|DELETE FROM default_branch_item_rules|);
-$dbh->do(q|DELETE FROM issuingrules|);
+Koha::CirculationRules->search()->delete();
 
 my $builder = t::lib::TestBuilder->new();
 t::lib::Mocks::mock_preference('item-level_itypes', 1); # Assuming the item type is defined at item level
@@ -68,23 +64,15 @@ my $patron = $builder->build({
     },
 });
 
-my $biblio = $builder->build({
-    source => 'Biblio',
-    value => {
-        branchcode => $branch->{branchcode},
-    },
-});
-my $item = $builder->build({
-    source => 'Item',
-    value => {
-        biblionumber => $biblio->{biblionumber},
-        homebranch => $branch->{branchcode},
-        holdingbranch => $branch->{branchcode},
-    },
+my $biblio = $builder->build_sample_biblio({ branchcode => $branch->{branchcode} });
+my $item = $builder->build_sample_item({
+    biblionumber => $biblio->biblionumber,
+    homebranch => $branch->{branchcode},
+    holdingbranch => $branch->{branchcode},
 });
 
-C4::Context->_new_userenv ('DUMMY_SESSION_ID');
-C4::Context->set_userenv($patron->{borrowernumber}, $patron->{userid}, 'usercnum', 'First name', 'Surname', $branch->{branchcode}, 'My Library', 0);
+my $patron_object = Koha::Patrons->find( $patron->{borrowernumber} );
+t::lib::Mocks::mock_userenv( { patron => $patron_object });
 
 # TooMany return ($current_loan_count, $max_loans_allowed) or undef
 # CO = Checkout
@@ -93,12 +81,12 @@ C4::Context->set_userenv($patron->{borrowernumber}, $patron->{userid}, 'usercnum
 subtest 'no rules exist' => sub {
     plan tests => 2;
     is_deeply(
-        C4::Circulation::TooMany( $patron, $biblio->{biblionumber}, $item ),
+        C4::Circulation::TooMany( $patron, $item ),
         { reason => 'NO_RULE_DEFINED', max_allowed => 0 },
         'CO should not be allowed, in any cases'
     );
     is_deeply(
-        C4::Circulation::TooMany( $patron, $biblio->{biblionumber}, $item, { onsite_checkout => 1 } ),
+        C4::Circulation::TooMany( $patron, $item, { onsite_checkout => 1 } ),
         { reason => 'NO_RULE_DEFINED', max_allowed => 0 },
         'OSCO should not be allowed, in any cases'
     );
@@ -106,19 +94,20 @@ subtest 'no rules exist' => sub {
 
 subtest '1 Issuingrule exist 0 0: no issue allowed' => sub {
     plan tests => 4;
-    my $issuingrule = $builder->build({
-        source => 'Issuingrule',
-        value => {
-            branchcode         => $branch->{branchcode},
-            categorycode       => $category->{categorycode},
-            itemtype           => '*',
-            maxissueqty        => 0,
-            maxonsiteissueqty  => 0,
+    Koha::CirculationRules->set_rules(
+        {
+            branchcode   => $branch->{branchcode},
+            categorycode => $category->{categorycode},
+            itemtype     => undef,
+            rules        => {
+                maxissueqty       => 0,
+                maxonsiteissueqty => 0,
+            }
         },
-    });
+    );
     t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 0);
     is_deeply(
-        C4::Circulation::TooMany( $patron, $biblio->{biblionumber}, $item ),
+        C4::Circulation::TooMany( $patron, $item ),
         {
             reason => 'TOO_MANY_CHECKOUTS',
             count => 0,
@@ -127,7 +116,7 @@ subtest '1 Issuingrule exist 0 0: no issue allowed' => sub {
         'CO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
     );
     is_deeply(
-        C4::Circulation::TooMany( $patron, $biblio->{biblionumber}, $item, { onsite_checkout => 1 } ),
+        C4::Circulation::TooMany( $patron, $item, { onsite_checkout => 1 } ),
         {
             reason => 'TOO_MANY_ONSITE_CHECKOUTS',
             count => 0,
@@ -138,7 +127,7 @@ subtest '1 Issuingrule exist 0 0: no issue allowed' => sub {
 
     t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 1);
     is_deeply(
-        C4::Circulation::TooMany( $patron, $biblio->{biblionumber}, $item ),
+        C4::Circulation::TooMany( $patron, $item ),
         {
             reason => 'TOO_MANY_CHECKOUTS',
             count => 0,
@@ -147,7 +136,7 @@ subtest '1 Issuingrule exist 0 0: no issue allowed' => sub {
         'CO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
     );
     is_deeply(
-        C4::Circulation::TooMany( $patron, $biblio->{biblionumber}, $item, { onsite_checkout => 1 } ),
+        C4::Circulation::TooMany( $patron, $item, { onsite_checkout => 1 } ),
         {
             reason => 'TOO_MANY_ONSITE_CHECKOUTS',
             count => 0,
@@ -159,38 +148,95 @@ subtest '1 Issuingrule exist 0 0: no issue allowed' => sub {
     teardown();
 };
 
-subtest '1 Issuingrule exist 1 1: issue is allowed' => sub {
+subtest '1 Issuingrule exist with onsiteissueqty=unlimited' => sub {
     plan tests => 4;
-    my $issuingrule = $builder->build({
-        source => 'Issuingrule',
-        value => {
-            branchcode         => $branch->{branchcode},
-            categorycode       => $category->{categorycode},
-            itemtype           => '*',
-            maxissueqty        => 1,
-            maxonsiteissueqty  => 1,
+
+    Koha::CirculationRules->set_rules(
+        {
+            branchcode   => $branch->{branchcode},
+            categorycode => $category->{categorycode},
+            itemtype     => undef,
+            rules        => {
+                maxissueqty       => 1,
+                maxonsiteissueqty => undef,
+            }
         },
-    });
+    );
+
+    my $issue = C4::Circulation::AddIssue( $patron, $item->barcode, dt_from_string() );
+    t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 0);
+    is_deeply(
+        C4::Circulation::TooMany( $patron, $item ),
+        {
+            reason => 'TOO_MANY_CHECKOUTS',
+            count => 1,
+            max_allowed => 1,
+        },
+        'CO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
+    );
+    is(
+        C4::Circulation::TooMany( $patron, $item, { onsite_checkout => 1 } ),
+        undef,
+        'OSCO should be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
+    );
+
+    t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 1);
+    is_deeply(
+        C4::Circulation::TooMany( $patron, $item ),
+        {
+            reason => 'TOO_MANY_CHECKOUTS',
+            count => 1,
+            max_allowed => 1,
+        },
+        'CO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
+    );
+    is_deeply(
+        C4::Circulation::TooMany( $patron, $item, { onsite_checkout => 1 } ),
+        {
+            reason => 'TOO_MANY_CHECKOUTS',
+            count => 1,
+            max_allowed => 1,
+        },
+        'OSCO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
+    );
+
+    teardown();
+};
+
+
+subtest '1 Issuingrule exist 1 1: issue is allowed' => sub {
+    plan tests => 4;
+    Koha::CirculationRules->set_rules(
+        {
+            branchcode   => $branch->{branchcode},
+            categorycode => $category->{categorycode},
+            itemtype     => undef,
+            rules        => {
+                maxissueqty       => 1,
+                maxonsiteissueqty => 1,
+            }
+        }
+    );
     t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 0);
     is(
-        C4::Circulation::TooMany( $patron, $biblio->{biblionumber}, $item ),
+        C4::Circulation::TooMany( $patron, $item ),
         undef,
         'CO should be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
     );
     is(
-        C4::Circulation::TooMany( $patron, $biblio->{biblionumber}, $item, { onsite_checkout => 1 } ),
+        C4::Circulation::TooMany( $patron, $item, { onsite_checkout => 1 } ),
         undef,
         'OSCO should be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
     );
 
     t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 1);
     is(
-        C4::Circulation::TooMany( $patron, $biblio->{biblionumber}, $item ),
+        C4::Circulation::TooMany( $patron, $item ),
         undef,
         'CO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
     );
     is(
-        C4::Circulation::TooMany( $patron, $biblio->{biblionumber}, $item, { onsite_checkout => 1 } ),
+        C4::Circulation::TooMany( $patron, $item, { onsite_checkout => 1 } ),
         undef,
         'OSCO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
     );
@@ -200,23 +246,24 @@ subtest '1 Issuingrule exist 1 1: issue is allowed' => sub {
 
 subtest '1 Issuingrule exist: 1 CO allowed, 1 OSCO allowed. Do a CO' => sub {
     plan tests => 5;
-    my $issuingrule = $builder->build({
-        source => 'Issuingrule',
-        value => {
-            branchcode         => $branch->{branchcode},
-            categorycode       => $category->{categorycode},
-            itemtype           => '*',
-            maxissueqty        => 1,
-            maxonsiteissueqty  => 1,
-        },
-    });
+    Koha::CirculationRules->set_rules(
+        {
+            branchcode   => $branch->{branchcode},
+            categorycode => $category->{categorycode},
+            itemtype     => undef,
+            rules        => {
+                maxissueqty       => 1,
+                maxonsiteissueqty => 1,
+            }
+        }
+    );
 
-    my $issue = C4::Circulation::AddIssue( $patron, $item->{barcode}, dt_from_string() );
+    my $issue = C4::Circulation::AddIssue( $patron, $item->barcode, dt_from_string() );
     like( $issue->issue_id, qr|^\d+$|, 'The issue should have been inserted' );
 
     t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 0);
     is_deeply(
-        C4::Circulation::TooMany( $patron, $biblio->{biblionumber}, $item ),
+        C4::Circulation::TooMany( $patron, $item ),
         {
             reason => 'TOO_MANY_CHECKOUTS',
             count => 1,
@@ -225,14 +272,14 @@ subtest '1 Issuingrule exist: 1 CO allowed, 1 OSCO allowed. Do a CO' => sub {
         'CO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
     );
     is(
-        C4::Circulation::TooMany( $patron, $biblio->{biblionumber}, $item, { onsite_checkout => 1 } ),
+        C4::Circulation::TooMany( $patron, $item, { onsite_checkout => 1 } ),
         undef,
         'OSCO should be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
     );
 
     t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 1);
     is_deeply(
-        C4::Circulation::TooMany( $patron, $biblio->{biblionumber}, $item ),
+        C4::Circulation::TooMany( $patron, $item ),
         {
             reason => 'TOO_MANY_CHECKOUTS',
             count => 1,
@@ -241,7 +288,7 @@ subtest '1 Issuingrule exist: 1 CO allowed, 1 OSCO allowed. Do a CO' => sub {
         'CO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
     );
     is_deeply(
-        C4::Circulation::TooMany( $patron, $biblio->{biblionumber}, $item, { onsite_checkout => 1 } ),
+        C4::Circulation::TooMany( $patron, $item, { onsite_checkout => 1 } ),
         {
             reason => 'TOO_MANY_CHECKOUTS',
             count => 1,
@@ -255,28 +302,29 @@ subtest '1 Issuingrule exist: 1 CO allowed, 1 OSCO allowed. Do a CO' => sub {
 
 subtest '1 Issuingrule exist: 1 CO allowed, 1 OSCO allowed, Do a OSCO' => sub {
     plan tests => 5;
-    my $issuingrule = $builder->build({
-        source => 'Issuingrule',
-        value => {
-            branchcode         => $branch->{branchcode},
-            categorycode       => $category->{categorycode},
-            itemtype           => '*',
-            maxissueqty        => 1,
-            maxonsiteissueqty  => 1,
-        },
-    });
+    Koha::CirculationRules->set_rules(
+        {
+            branchcode   => $branch->{branchcode},
+            categorycode => $category->{categorycode},
+            itemtype     => undef,
+            rules        => {
+                maxissueqty       => 1,
+                maxonsiteissueqty => 1,
+            }
+        }
+    );
 
-    my $issue = C4::Circulation::AddIssue( $patron, $item->{barcode}, dt_from_string(), undef, undef, undef, { onsite_checkout => 1 } );
+    my $issue = C4::Circulation::AddIssue( $patron, $item->barcode, dt_from_string(), undef, undef, undef, { onsite_checkout => 1 } );
     like( $issue->issue_id, qr|^\d+$|, 'The issue should have been inserted' );
 
     t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 0);
     is(
-        C4::Circulation::TooMany( $patron, $biblio->{biblionumber}, $item ),
+        C4::Circulation::TooMany( $patron, $item ),
         undef,
         'CO should be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
     );
     is_deeply(
-        C4::Circulation::TooMany( $patron, $biblio->{biblionumber}, $item, { onsite_checkout => 1 } ),
+        C4::Circulation::TooMany( $patron, $item, { onsite_checkout => 1 } ),
         {
             reason => 'TOO_MANY_ONSITE_CHECKOUTS',
             count => 1,
@@ -287,7 +335,7 @@ subtest '1 Issuingrule exist: 1 CO allowed, 1 OSCO allowed, Do a OSCO' => sub {
 
     t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 1);
     is_deeply(
-        C4::Circulation::TooMany( $patron, $biblio->{biblionumber}, $item ),
+        C4::Circulation::TooMany( $patron, $item ),
         {
             reason => 'TOO_MANY_CHECKOUTS',
             count => 1,
@@ -296,7 +344,7 @@ subtest '1 Issuingrule exist: 1 CO allowed, 1 OSCO allowed, Do a OSCO' => sub {
         'CO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
     );
     is_deeply(
-        C4::Circulation::TooMany( $patron, $biblio->{biblionumber}, $item, { onsite_checkout => 1 } ),
+        C4::Circulation::TooMany( $patron, $item, { onsite_checkout => 1 } ),
         {
             reason => 'TOO_MANY_ONSITE_CHECKOUTS',
             count => 1,
@@ -313,22 +361,24 @@ subtest '1 BranchBorrowerCircRule exist: 1 CO allowed, 1 OSCO allowed' => sub {
     # DefaultBorrowerCircRule, DefaultBranchCircRule, DefaultBranchItemRule ans DefaultCircRule.pm
 
     plan tests => 10;
-    my $issuingrule = $builder->build({
-        source => 'BranchBorrowerCircRule',
-        value => {
-            branchcode         => $branch->{branchcode},
-            categorycode       => $category->{categorycode},
-            maxissueqty        => 1,
-            maxonsiteissueqty  => 1,
-        },
-    });
+    Koha::CirculationRules->set_rules(
+        {
+            branchcode   => $branch->{branchcode},
+            categorycode => $category->{categorycode},
+            itemtype     => undef,
+            rules        => {
+                maxissueqty       => 1,
+                maxonsiteissueqty => 1,
+            }
+        }
+    );
 
-    my $issue = C4::Circulation::AddIssue( $patron, $item->{barcode}, dt_from_string(), undef, undef, undef );
+    my $issue = C4::Circulation::AddIssue( $patron, $item->barcode, dt_from_string(), undef, undef, undef );
     like( $issue->issue_id, qr|^\d+$|, 'The issue should have been inserted' );
 
     t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 0);
     is_deeply(
-        C4::Circulation::TooMany( $patron, $biblio->{biblionumber}, $item ),
+        C4::Circulation::TooMany( $patron, $item ),
         {
             reason => 'TOO_MANY_CHECKOUTS',
             count => 1,
@@ -337,14 +387,14 @@ subtest '1 BranchBorrowerCircRule exist: 1 CO allowed, 1 OSCO allowed' => sub {
         'CO should be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
     );
     is(
-        C4::Circulation::TooMany( $patron, $biblio->{biblionumber}, $item, { onsite_checkout => 1 } ),
+        C4::Circulation::TooMany( $patron, $item, { onsite_checkout => 1 } ),
         undef,
         'OSCO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
     );
 
     t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 1);
     is_deeply(
-        C4::Circulation::TooMany( $patron, $biblio->{biblionumber}, $item ),
+        C4::Circulation::TooMany( $patron, $item ),
         {
             reason => 'TOO_MANY_CHECKOUTS',
             count => 1,
@@ -353,7 +403,7 @@ subtest '1 BranchBorrowerCircRule exist: 1 CO allowed, 1 OSCO allowed' => sub {
         'CO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
     );
     is_deeply(
-        C4::Circulation::TooMany( $patron, $biblio->{biblionumber}, $item, { onsite_checkout => 1 } ),
+        C4::Circulation::TooMany( $patron, $item, { onsite_checkout => 1 } ),
         {
             reason => 'TOO_MANY_CHECKOUTS',
             count => 1,
@@ -363,18 +413,29 @@ subtest '1 BranchBorrowerCircRule exist: 1 CO allowed, 1 OSCO allowed' => sub {
     );
 
     teardown();
+    Koha::CirculationRules->set_rules(
+        {
+            branchcode   => $branch->{branchcode},
+            categorycode => $category->{categorycode},
+            itemtype     => undef,
+            rules        => {
+                maxissueqty       => 1,
+                maxonsiteissueqty => 1,
+            }
+        }
+    );
 
-    $issue = C4::Circulation::AddIssue( $patron, $item->{barcode}, dt_from_string(), undef, undef, undef, { onsite_checkout => 1 } );
+    $issue = C4::Circulation::AddIssue( $patron, $item->barcode, dt_from_string(), undef, undef, undef, { onsite_checkout => 1 } );
     like( $issue->issue_id, qr|^\d+$|, 'The issue should have been inserted' );
 
     t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 0);
     is(
-        C4::Circulation::TooMany( $patron, $biblio->{biblionumber}, $item ),
+        C4::Circulation::TooMany( $patron, $item ),
         undef,
         'CO should be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
     );
     is_deeply(
-        C4::Circulation::TooMany( $patron, $biblio->{biblionumber}, $item, { onsite_checkout => 1 } ),
+        C4::Circulation::TooMany( $patron, $item, { onsite_checkout => 1 } ),
         {
             reason => 'TOO_MANY_ONSITE_CHECKOUTS',
             count => 1,
@@ -385,7 +446,7 @@ subtest '1 BranchBorrowerCircRule exist: 1 CO allowed, 1 OSCO allowed' => sub {
 
     t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 1);
     is_deeply(
-        C4::Circulation::TooMany( $patron, $biblio->{biblionumber}, $item ),
+        C4::Circulation::TooMany( $patron, $item ),
         {
             reason => 'TOO_MANY_CHECKOUTS',
             count => 1,
@@ -394,7 +455,7 @@ subtest '1 BranchBorrowerCircRule exist: 1 CO allowed, 1 OSCO allowed' => sub {
         'CO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
     );
     is_deeply(
-        C4::Circulation::TooMany( $patron, $biblio->{biblionumber}, $item, { onsite_checkout => 1 } ),
+        C4::Circulation::TooMany( $patron, $item, { onsite_checkout => 1 } ),
         {
             reason => 'TOO_MANY_ONSITE_CHECKOUTS',
             count => 1,
@@ -406,10 +467,508 @@ subtest '1 BranchBorrowerCircRule exist: 1 CO allowed, 1 OSCO allowed' => sub {
     teardown();
 };
 
+subtest 'General vs specific rules limit quantity correctly' => sub {
+    plan tests => 10;
+
+    t::lib::Mocks::mock_preference('CircControl', 'ItemHomeLibrary');
+    my $branch   = $builder->build({source => 'Branch',});
+    my $category = $builder->build({source => 'Category',});
+    my $itemtype = $builder->build({
+        source => 'Itemtype',
+        value => {
+            rentalcharge => 0,
+            rentalcharge_daily => 0,
+            rentalcharge_hourly => 0,
+            notforloan => 0,
+        }
+    });
+    my $patron = $builder->build({
+        source => 'Borrower',
+        value => {
+            categorycode => $category->{categorycode},
+            branchcode => $branch->{branchcode},
+        }
+    });
+
+    # Set up an issuing rule
+    Koha::CirculationRules->set_rules(
+        {
+            categorycode => '*',
+            itemtype     => $itemtype->{itemtype},
+            branchcode   => '*',
+            rules        => {
+                issuelength => 1,
+                firstremind => 1,        # 1 day of grace
+                finedays    => 2,        # 2 days of fine per day of overdue
+                lengthunit  => 'days',
+            }
+        }
+    );
+
+    # Set an All->All for an itemtype
+    Koha::CirculationRules->set_rules(
+        {
+            branchcode   => '*',
+            categorycode => '*',
+            itemtype     => $itemtype->{itemtype},
+            rules        => {
+                maxissueqty       => 1,
+                maxonsiteissueqty => 1,
+            }
+        }
+    );
+
+    # Create an item
+    my $issue_item = $builder->build_sample_item({
+        itype => $itemtype->{itemtype}
+    });
+    my $branch_item = $builder->build_sample_item({
+        itype => $itemtype->{itemtype},
+        homebranch => $branch->{branchcode},
+        holdingbranch => $branch->{branchcode}
+    });
+
+
+    t::lib::Mocks::mock_userenv({ branchcode => $branch->{branchcode} });
+    my $issue = C4::Circulation::AddIssue( $patron, $issue_item->barcode, dt_from_string() );
+    # We checkout one item
+    is_deeply(
+        C4::Circulation::TooMany( $patron, $branch_item ),
+        {
+            reason => 'TOO_MANY_CHECKOUTS',
+            count => 1,
+            max_allowed => 1,
+        },
+        'We are only allowed one, and we have one (itemtype on item)'
+    );
+
+    # Check itemtype on biblio level
+    t::lib::Mocks::mock_preference('item-level_itypes', 0);
+    $issue_item->biblio->biblioitem->itemtype($itemtype->{itemtype})->store;
+    $branch_item->biblio->biblioitem->itemtype($itemtype->{itemtype})->store;
+    # We checkout one item
+    is_deeply(
+        C4::Circulation::TooMany( $patron, $branch_item ),
+        {
+            reason => 'TOO_MANY_CHECKOUTS',
+            count => 1,
+            max_allowed => 1,
+        },
+        'We are only allowed one, and we have one (itemtype on biblioitem)'
+    );
+    t::lib::Mocks::mock_preference('item-level_itypes', 1);
+
+    # Set a branch specific rule
+    Koha::CirculationRules->set_rules(
+        {
+            branchcode   => $branch->{branchcode},
+            categorycode => $category->{categorycode},
+            itemtype     => $itemtype->{itemtype},
+            rules        => {
+                maxissueqty       => 1,
+                maxonsiteissueqty => 1,
+            }
+        }
+    );
+
+    is(
+        C4::Circulation::TooMany( $patron, $branch_item ),
+        undef,
+        'We are allowed one from the branch specifically now'
+    );
+
+    # If circcontrol is PatronLibrary we count all the patron's loan, regardless of branch
+    t::lib::Mocks::mock_preference('CircControl', 'PatronLibrary');
+    is_deeply(
+        C4::Circulation::TooMany( $patron, $branch_item ),
+        {
+            reason => 'TOO_MANY_CHECKOUTS',
+            count => 1,
+            max_allowed => 1,
+        },
+        'We are allowed one from the branch specifically, but have one'
+    );
+    t::lib::Mocks::mock_preference('CircControl', 'ItemHomeLibrary');
+
+    $issue = C4::Circulation::AddIssue( $patron, $branch_item->barcode, dt_from_string() );
+    # We issue that one
+    # And make another
+    my $branch_item_2 = $builder->build_sample_item({
+        itype => $itemtype->{itemtype},
+        homebranch => $branch->{branchcode},
+        holdingbranch => $branch->{branchcode}
+    });
+    is_deeply(
+        C4::Circulation::TooMany( $patron, $branch_item_2 ),
+        {
+            reason => 'TOO_MANY_CHECKOUTS',
+            count => 1,
+            max_allowed => 1,
+        },
+        'We are only allowed one from that branch, and have one'
+    );
+
+    # Now we make anothe from a different branch
+    my $item_2 = $builder->build_sample_item({
+        itype => $itemtype->{itemtype},
+    });
+    is_deeply(
+        C4::Circulation::TooMany( $patron, $item_2 ),
+        {
+            reason => 'TOO_MANY_CHECKOUTS',
+            count => 2,
+            max_allowed => 1,
+        },
+        'We are only allowed one for general rule, and have two'
+    );
+    t::lib::Mocks::mock_preference('CircControl', 'PatronLibrary');
+    is_deeply(
+        C4::Circulation::TooMany( $patron, $item_2 ),
+        {
+            reason => 'TOO_MANY_CHECKOUTS',
+            count => 2,
+            max_allowed => 1,
+        },
+        'We are only allowed one for general rule, and have two'
+    );
+
+    t::lib::Mocks::mock_preference('CircControl', 'PickupLibrary');
+    is_deeply(
+        C4::Circulation::TooMany( $patron, $item_2 ),
+        {
+            reason => 'TOO_MANY_CHECKOUTS',
+            count => 2,
+            max_allowed => 1,
+        },
+        'We are only allowed one for general rule, and have checked out two at this branch'
+    );
+
+    my $branch2   = $builder->build({source => 'Branch',});
+    t::lib::Mocks::mock_userenv({ branchcode => $branch2->{branchcode} });
+    is_deeply(
+        C4::Circulation::TooMany( $patron, $item_2 ),
+        {
+            reason => 'TOO_MANY_CHECKOUTS',
+            count => 2,
+            max_allowed => 1,
+        },
+        'We are only allowed one for general rule, and have two total (no rule for specific branch)'
+    );
+    # Set a branch specific rule for new branch
+    Koha::CirculationRules->set_rules(
+        {
+            branchcode   => $branch2->{branchcode},
+            categorycode => $category->{categorycode},
+            itemtype     => $itemtype->{itemtype},
+            rules        => {
+                maxissueqty       => 1,
+                maxonsiteissueqty => 1,
+            }
+        }
+    );
+
+    is(
+        C4::Circulation::TooMany( $patron, $branch_item ),
+        undef,
+        'We are allowed one from the branch specifically now'
+    );
+};
+
+subtest 'empty string means unlimited' => sub {
+    plan tests => 2;
+
+    Koha::CirculationRules->set_rules(
+        {
+            branchcode   => '*',
+            categorycode => '*',
+            itemtype     => '*',
+            rules        => {
+                maxissueqty       => '',
+                maxonsiteissueqty => '',
+            }
+        },
+    );
+    is(
+        C4::Circulation::TooMany( $patron, $item ),
+        undef,
+        'maxissueqty="" should mean unlimited'
+    );
+
+    is(
+        C4::Circulation::TooMany( $patron, $item, { onsite_checkout => 1 } ),
+        undef,
+        'maxonsiteissueqty="" should mean unlimited'
+      );
+};
+
+subtest 'itemtype group tests' => sub {
+    plan tests => 13;
+
+    t::lib::Mocks::mock_preference( 'CircControl', 'ItemHomeLibrary' );
+    Koha::CirculationRules->set_rules(
+        {
+            branchcode   => '*',
+            categorycode => '*',
+            itemtype     => '*',
+            rules        => {
+                maxissueqty       => '',
+                maxonsiteissueqty => '',
+                issuelength       => 1,
+                firstremind       => 1,      # 1 day of grace
+                finedays          => 2,      # 2 days of fine per day of overdue
+                lengthunit        => 'days',
+            }
+        },
+    );
+
+    my $parent_itype = $builder->build(
+        {
+            source => 'Itemtype',
+            value  => {
+                parent_type         => undef,
+                rentalcharge        => undef,
+                rentalcharge_daily  => undef,
+                rentalcharge_hourly => undef,
+                notforloan          => 0,
+            }
+        }
+    );
+    my $child_itype_1 = $builder->build(
+        {
+            source => 'Itemtype',
+            value  => {
+                parent_type         => $parent_itype->{itemtype},
+                rentalcharge        => 0,
+                rentalcharge_daily  => 0,
+                rentalcharge_hourly => 0,
+                notforloan          => 0,
+            }
+        }
+    );
+    my $child_itype_2 = $builder->build(
+        {
+            source => 'Itemtype',
+            value  => {
+                parent_type         => $parent_itype->{itemtype},
+                rentalcharge        => 0,
+                rentalcharge_daily  => 0,
+                rentalcharge_hourly => 0,
+                notforloan          => 0,
+            }
+        }
+    );
+
+    my $branch   = $builder->build( { source => 'Branch', } );
+    my $category = $builder->build( { source => 'Category', } );
+    my $patron   = $builder->build(
+        {
+            source => 'Borrower',
+            value  => {
+                categorycode => $category->{categorycode},
+                branchcode   => $branch->{branchcode},
+            },
+        }
+    );
+    my $item = $builder->build_sample_item(
+        {
+            homebranch    => $branch->{branchcode},
+            holdingbranch => $branch->{branchcode},
+            itype         => $child_itype_1->{itemtype}
+        }
+    );
+
+    my $all_iq_rule = $builder->build(
+        {
+            source => 'CirculationRule',
+            value  => {
+                branchcode   => $branch->{branchcode},
+                categorycode => $category->{categorycode},
+                itemtype     => undef,
+                rule_name    => 'maxissueqty',
+                rule_value   => 1
+            }
+        }
+    );
+    is( C4::Circulation::TooMany( $patron, $item ),
+        undef, 'Checkout allowed, using all rule of 1' );
+
+    #Checkout an item
+    my $issue =
+      C4::Circulation::AddIssue( $patron, $item->barcode, dt_from_string() );
+    like( $issue->issue_id, qr|^\d+$|, 'The issue should have been inserted' );
+
+    #Patron has 1 checkout of child itype1
+
+    my $parent_iq_rule = $builder->build(
+        {
+            source => 'CirculationRule',
+            value  => {
+                branchcode   => $branch->{branchcode},
+                categorycode => $category->{categorycode},
+                itemtype     => $parent_itype->{itemtype},
+                rule_name    => 'maxissueqty',
+                rule_value   => 2
+            }
+        }
+    );
+
+    is( C4::Circulation::TooMany( $patron, $item ),
+        undef, 'Checkout allowed, using parent type rule of 2' );
+
+    my $child1_iq_rule = $builder->build_object(
+        {
+            class => 'Koha::CirculationRules',
+            value => {
+                branchcode   => $branch->{branchcode},
+                categorycode => $category->{categorycode},
+                itemtype     => $child_itype_1->{itemtype},
+                rule_name    => 'maxissueqty',
+                rule_value   => 1
+            }
+        }
+    );
+
+    is_deeply(
+        C4::Circulation::TooMany( $patron, $item ),
+        {
+            reason      => 'TOO_MANY_CHECKOUTS',
+            count       => 1,
+            max_allowed => 1,
+        },
+        'Checkout not allowed, using specific type rule of 1'
+    );
+
+    my $item_1 = $builder->build_sample_item(
+        {
+            homebranch    => $branch->{branchcode},
+            holdingbranch => $branch->{branchcode},
+            itype         => $child_itype_2->{itemtype}
+        }
+    );
+
+    my $child2_iq_rule = $builder->build(
+        {
+            source => 'CirculationRule',
+            value  => {
+                branchcode   => $branch->{branchcode},
+                categorycode => $category->{categorycode},
+                itemtype     => $child_itype_2->{itemtype},
+                rule_name    => 'maxissueqty',
+                rule_value   => 3
+            }
+        }
+    );
+
+    is( C4::Circulation::TooMany( $patron, $item_1 ),
+        undef, 'Checkout allowed' );
+
+    #checkout an item
+    $issue =
+      C4::Circulation::AddIssue( $patron, $item_1->barcode, dt_from_string() );
+    like( $issue->issue_id, qr|^\d+$|, 'the issue should have been inserted' );
+
+    #patron has 1 checkout of childitype1 and 1 checkout of childitype2
+
+    is_deeply(
+        C4::Circulation::TooMany( $patron, $item ),
+        {
+            reason      => 'TOO_MANY_CHECKOUTS',
+            count       => 2,
+            max_allowed => 2,
+        },
+'Checkout not allowed, using parent type rule of 2, checkout of sibling itemtype counted'
+    );
+
+    my $parent_item = $builder->build_sample_item(
+        {
+            homebranch    => $branch->{branchcode},
+            holdingbranch => $branch->{branchcode},
+            itype         => $parent_itype->{itemtype}
+        }
+    );
+
+    is_deeply(
+        C4::Circulation::TooMany( $patron, $parent_item ),
+        {
+            reason      => 'TOO_MANY_CHECKOUTS',
+            count       => 2,
+            max_allowed => 2,
+        },
+'Checkout not allowed, using parent type rule of 2, checkout of child itemtypes counted'
+    );
+
+    #increase parent type to greater than specific
+    my $circ_rule_object =
+      Koha::CirculationRules->find( $parent_iq_rule->{id} );
+    $circ_rule_object->rule_value(4)->store();
+
+    is( C4::Circulation::TooMany( $patron, $item_1 ),
+        undef, 'Checkout allowed, using specific type rule of 3' );
+
+    my $item_2 = $builder->build_sample_item(
+        {
+            homebranch    => $branch->{branchcode},
+            holdingbranch => $branch->{branchcode},
+            itype         => $child_itype_2->{itemtype}
+        }
+    );
+
+    #checkout an item
+    $issue =
+      C4::Circulation::AddIssue( $patron, $item_2->barcode, dt_from_string(),
+        undef, undef, undef );
+    like( $issue->issue_id, qr|^\d+$|, 'the issue should have been inserted' );
+
+    #patron has 1 checkout of childitype1 and 2 of childitype2
+
+    is(
+        C4::Circulation::TooMany( $patron, $item_2 ),
+        undef,
+'Checkout allowed, using specific type rule of 3, checkout of sibling itemtype not counted'
+    );
+
+    $child1_iq_rule->rule_value(2)->store(); #Allow 2 checkouts for child type 1
+
+    my $item_3 = $builder->build_sample_item(
+        {
+            homebranch    => $branch->{branchcode},
+            holdingbranch => $branch->{branchcode},
+            itype         => $child_itype_1->{itemtype}
+        }
+    );
+    my $item_4 = $builder->build_sample_item(
+        {
+            homebranch    => $branch->{branchcode},
+            holdingbranch => $branch->{branchcode},
+            itype         => $child_itype_2->{itemtype}
+        }
+    );
+
+    #checkout an item
+    $issue =
+      C4::Circulation::AddIssue( $patron, $item_4->barcode, dt_from_string(),
+        undef, undef, undef );
+    like( $issue->issue_id, qr|^\d+$|, 'the issue should have been inserted' );
+
+    #patron has 1 checkout of childitype 1 and 3 of childitype2
+
+    is_deeply(
+        C4::Circulation::TooMany( $patron, $item_3 ),
+        {
+            reason      => 'TOO_MANY_CHECKOUTS',
+            max_allowed => 4,
+            count       => 4,
+        },
+'Checkout not allowed, using specific type rule of 2, checkout of sibling itemtype not counted, but parent rule (4) prevents another'
+    );
+
+    teardown();
+};
+
 $schema->storage->txn_rollback;
 
 sub teardown {
     $dbh->do(q|DELETE FROM issues|);
-    $dbh->do(q|DELETE FROM issuingrules|);
+    $dbh->do(q|DELETE FROM circulation_rules|);
 }