d1663d98a3b0466e7d258e6c55b79bade601eb73
[srvgit] / t / db_dependent / Circulation / TooMany.t
1 #!/usr/bin/perl
2
3 # This file is part of Koha.
4 #
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
8 # version.
9 #
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.
13 #
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>.
16
17 use Modern::Perl;
18 use Test::More tests => 10;
19 use C4::Context;
20
21 use C4::Members;
22 use C4::Items;
23 use C4::Biblio;
24 use C4::Circulation;
25 use C4::Context;
26
27 use Koha::DateUtils qw( dt_from_string );
28 use Koha::Database;
29 use Koha::CirculationRules;
30
31 use t::lib::TestBuilder;
32 use t::lib::Mocks;
33
34 my $schema = Koha::Database->new->schema;
35 $schema->storage->txn_begin;
36
37 our $dbh = C4::Context->dbh;
38
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();
47
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
50
51 my $branch = $builder->build({
52     source => 'Branch',
53 });
54
55 my $category = $builder->build({
56     source => 'Category',
57 });
58
59 my $patron = $builder->build({
60     source => 'Borrower',
61     value => {
62         categorycode => $category->{categorycode},
63         branchcode => $branch->{branchcode},
64     },
65 });
66
67 my $biblio = $builder->build_sample_biblio({ branchcode => $branch->{branchcode} });
68 my $item = $builder->build_sample_item({
69     biblionumber => $biblio->biblionumber,
70     homebranch => $branch->{branchcode},
71     holdingbranch => $branch->{branchcode},
72 });
73
74 my $patron_object = Koha::Patrons->find( $patron->{borrowernumber} );
75 t::lib::Mocks::mock_userenv( { patron => $patron_object });
76
77 # TooMany return ($current_loan_count, $max_loans_allowed) or undef
78 # CO = Checkout
79 # OSCO: On-site checkout
80
81 subtest 'no rules exist' => sub {
82     plan tests => 2;
83     is_deeply(
84         C4::Circulation::TooMany( $patron, $item ),
85         { reason => 'NO_RULE_DEFINED', max_allowed => 0 },
86         'CO should not be allowed, in any cases'
87     );
88     is_deeply(
89         C4::Circulation::TooMany( $patron, $item, { onsite_checkout => 1 } ),
90         { reason => 'NO_RULE_DEFINED', max_allowed => 0 },
91         'OSCO should not be allowed, in any cases'
92     );
93 };
94
95 subtest '1 Issuingrule exist 0 0: no issue allowed' => sub {
96     plan tests => 4;
97     Koha::CirculationRules->set_rules(
98         {
99             branchcode   => $branch->{branchcode},
100             categorycode => $category->{categorycode},
101             itemtype     => undef,
102             rules        => {
103                 maxissueqty       => 0,
104                 maxonsiteissueqty => 0,
105             }
106         },
107     );
108     t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 0);
109     is_deeply(
110         C4::Circulation::TooMany( $patron, $item ),
111         {
112             reason => 'TOO_MANY_CHECKOUTS',
113             count => 0,
114             max_allowed => 0,
115         },
116         'CO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
117     );
118     is_deeply(
119         C4::Circulation::TooMany( $patron, $item, { onsite_checkout => 1 } ),
120         {
121             reason => 'TOO_MANY_ONSITE_CHECKOUTS',
122             count => 0,
123             max_allowed => 0,
124         },
125         'OSCO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
126     );
127
128     t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 1);
129     is_deeply(
130         C4::Circulation::TooMany( $patron, $item ),
131         {
132             reason => 'TOO_MANY_CHECKOUTS',
133             count => 0,
134             max_allowed => 0,
135         },
136         'CO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
137     );
138     is_deeply(
139         C4::Circulation::TooMany( $patron, $item, { onsite_checkout => 1 } ),
140         {
141             reason => 'TOO_MANY_ONSITE_CHECKOUTS',
142             count => 0,
143             max_allowed => 0,
144         },
145         'OSCO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
146     );
147
148     teardown();
149 };
150
151 subtest '1 Issuingrule exist with onsiteissueqty=unlimited' => sub {
152     plan tests => 4;
153
154     Koha::CirculationRules->set_rules(
155         {
156             branchcode   => $branch->{branchcode},
157             categorycode => $category->{categorycode},
158             itemtype     => undef,
159             rules        => {
160                 maxissueqty       => 1,
161                 maxonsiteissueqty => undef,
162             }
163         },
164     );
165
166     my $issue = C4::Circulation::AddIssue( $patron, $item->barcode, dt_from_string() );
167     t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 0);
168     is_deeply(
169         C4::Circulation::TooMany( $patron, $item ),
170         {
171             reason => 'TOO_MANY_CHECKOUTS',
172             count => 1,
173             max_allowed => 1,
174         },
175         'CO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
176     );
177     is(
178         C4::Circulation::TooMany( $patron, $item, { onsite_checkout => 1 } ),
179         undef,
180         'OSCO should be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
181     );
182
183     t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 1);
184     is_deeply(
185         C4::Circulation::TooMany( $patron, $item ),
186         {
187             reason => 'TOO_MANY_CHECKOUTS',
188             count => 1,
189             max_allowed => 1,
190         },
191         'CO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
192     );
193     is_deeply(
194         C4::Circulation::TooMany( $patron, $item, { onsite_checkout => 1 } ),
195         {
196             reason => 'TOO_MANY_CHECKOUTS',
197             count => 1,
198             max_allowed => 1,
199         },
200         'OSCO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
201     );
202
203     teardown();
204 };
205
206
207 subtest '1 Issuingrule exist 1 1: issue is allowed' => sub {
208     plan tests => 4;
209     Koha::CirculationRules->set_rules(
210         {
211             branchcode   => $branch->{branchcode},
212             categorycode => $category->{categorycode},
213             itemtype     => undef,
214             rules        => {
215                 maxissueqty       => 1,
216                 maxonsiteissueqty => 1,
217             }
218         }
219     );
220     t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 0);
221     is(
222         C4::Circulation::TooMany( $patron, $item ),
223         undef,
224         'CO should be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
225     );
226     is(
227         C4::Circulation::TooMany( $patron, $item, { onsite_checkout => 1 } ),
228         undef,
229         'OSCO should be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
230     );
231
232     t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 1);
233     is(
234         C4::Circulation::TooMany( $patron, $item ),
235         undef,
236         'CO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
237     );
238     is(
239         C4::Circulation::TooMany( $patron, $item, { onsite_checkout => 1 } ),
240         undef,
241         'OSCO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
242     );
243
244     teardown();
245 };
246
247 subtest '1 Issuingrule exist: 1 CO allowed, 1 OSCO allowed. Do a CO' => sub {
248     plan tests => 5;
249     Koha::CirculationRules->set_rules(
250         {
251             branchcode   => $branch->{branchcode},
252             categorycode => $category->{categorycode},
253             itemtype     => undef,
254             rules        => {
255                 maxissueqty       => 1,
256                 maxonsiteissueqty => 1,
257             }
258         }
259     );
260
261     my $issue = C4::Circulation::AddIssue( $patron, $item->barcode, dt_from_string() );
262     like( $issue->issue_id, qr|^\d+$|, 'The issue should have been inserted' );
263
264     t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 0);
265     is_deeply(
266         C4::Circulation::TooMany( $patron, $item ),
267         {
268             reason => 'TOO_MANY_CHECKOUTS',
269             count => 1,
270             max_allowed => 1,
271         },
272         'CO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
273     );
274     is(
275         C4::Circulation::TooMany( $patron, $item, { onsite_checkout => 1 } ),
276         undef,
277         'OSCO should be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
278     );
279
280     t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 1);
281     is_deeply(
282         C4::Circulation::TooMany( $patron, $item ),
283         {
284             reason => 'TOO_MANY_CHECKOUTS',
285             count => 1,
286             max_allowed => 1,
287         },
288         'CO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
289     );
290     is_deeply(
291         C4::Circulation::TooMany( $patron, $item, { onsite_checkout => 1 } ),
292         {
293             reason => 'TOO_MANY_CHECKOUTS',
294             count => 1,
295             max_allowed => 1,
296         },
297         'OSCO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
298     );
299
300     teardown();
301 };
302
303 subtest '1 Issuingrule exist: 1 CO allowed, 1 OSCO allowed, Do a OSCO' => sub {
304     plan tests => 5;
305     Koha::CirculationRules->set_rules(
306         {
307             branchcode   => $branch->{branchcode},
308             categorycode => $category->{categorycode},
309             itemtype     => undef,
310             rules        => {
311                 maxissueqty       => 1,
312                 maxonsiteissueqty => 1,
313             }
314         }
315     );
316
317     my $issue = C4::Circulation::AddIssue( $patron, $item->barcode, dt_from_string(), undef, undef, undef, { onsite_checkout => 1 } );
318     like( $issue->issue_id, qr|^\d+$|, 'The issue should have been inserted' );
319
320     t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 0);
321     is(
322         C4::Circulation::TooMany( $patron, $item ),
323         undef,
324         'CO should be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
325     );
326     is_deeply(
327         C4::Circulation::TooMany( $patron, $item, { onsite_checkout => 1 } ),
328         {
329             reason => 'TOO_MANY_ONSITE_CHECKOUTS',
330             count => 1,
331             max_allowed => 1,
332         },
333         'OSCO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
334     );
335
336     t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 1);
337     is_deeply(
338         C4::Circulation::TooMany( $patron, $item ),
339         {
340             reason => 'TOO_MANY_CHECKOUTS',
341             count => 1,
342             max_allowed => 1,
343         },
344         'CO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
345     );
346     is_deeply(
347         C4::Circulation::TooMany( $patron, $item, { onsite_checkout => 1 } ),
348         {
349             reason => 'TOO_MANY_ONSITE_CHECKOUTS',
350             count => 1,
351             max_allowed => 1,
352         },
353         'OSCO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
354     );
355
356     teardown();
357 };
358
359 subtest '1 BranchBorrowerCircRule exist: 1 CO allowed, 1 OSCO allowed' => sub {
360     # Note: the same test coul be done for
361     # DefaultBorrowerCircRule, DefaultBranchCircRule, DefaultBranchItemRule ans DefaultCircRule.pm
362
363     plan tests => 10;
364     Koha::CirculationRules->set_rules(
365         {
366             branchcode   => $branch->{branchcode},
367             categorycode => $category->{categorycode},
368             itemtype     => undef,
369             rules        => {
370                 maxissueqty       => 1,
371                 maxonsiteissueqty => 1,
372             }
373         }
374     );
375
376     my $issue = C4::Circulation::AddIssue( $patron, $item->barcode, dt_from_string(), undef, undef, undef );
377     like( $issue->issue_id, qr|^\d+$|, 'The issue should have been inserted' );
378
379     t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 0);
380     is_deeply(
381         C4::Circulation::TooMany( $patron, $item ),
382         {
383             reason => 'TOO_MANY_CHECKOUTS',
384             count => 1,
385             max_allowed => 1,
386         },
387         'CO should be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
388     );
389     is(
390         C4::Circulation::TooMany( $patron, $item, { onsite_checkout => 1 } ),
391         undef,
392         'OSCO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
393     );
394
395     t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 1);
396     is_deeply(
397         C4::Circulation::TooMany( $patron, $item ),
398         {
399             reason => 'TOO_MANY_CHECKOUTS',
400             count => 1,
401             max_allowed => 1,
402         },
403         'CO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
404     );
405     is_deeply(
406         C4::Circulation::TooMany( $patron, $item, { onsite_checkout => 1 } ),
407         {
408             reason => 'TOO_MANY_CHECKOUTS',
409             count => 1,
410             max_allowed => 1,
411         },
412         'OSCO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
413     );
414
415     teardown();
416     Koha::CirculationRules->set_rules(
417         {
418             branchcode   => $branch->{branchcode},
419             categorycode => $category->{categorycode},
420             itemtype     => undef,
421             rules        => {
422                 maxissueqty       => 1,
423                 maxonsiteissueqty => 1,
424             }
425         }
426     );
427
428     $issue = C4::Circulation::AddIssue( $patron, $item->barcode, dt_from_string(), undef, undef, undef, { onsite_checkout => 1 } );
429     like( $issue->issue_id, qr|^\d+$|, 'The issue should have been inserted' );
430
431     t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 0);
432     is(
433         C4::Circulation::TooMany( $patron, $item ),
434         undef,
435         'CO should be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
436     );
437     is_deeply(
438         C4::Circulation::TooMany( $patron, $item, { onsite_checkout => 1 } ),
439         {
440             reason => 'TOO_MANY_ONSITE_CHECKOUTS',
441             count => 1,
442             max_allowed => 1,
443         },
444         'OSCO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 0'
445     );
446
447     t::lib::Mocks::mock_preference('ConsiderOnSiteCheckoutsAsNormalCheckouts', 1);
448     is_deeply(
449         C4::Circulation::TooMany( $patron, $item ),
450         {
451             reason => 'TOO_MANY_CHECKOUTS',
452             count => 1,
453             max_allowed => 1,
454         },
455         'CO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
456     );
457     is_deeply(
458         C4::Circulation::TooMany( $patron, $item, { onsite_checkout => 1 } ),
459         {
460             reason => 'TOO_MANY_ONSITE_CHECKOUTS',
461             count => 1,
462             max_allowed => 1,
463         },
464         'OSCO should not be allowed if ConsiderOnSiteCheckoutsAsNormalCheckouts == 1'
465     );
466
467     teardown();
468 };
469
470 subtest 'General vs specific rules limit quantity correctly' => sub {
471     plan tests => 10;
472
473     t::lib::Mocks::mock_preference('CircControl', 'ItemHomeLibrary');
474     my $branch   = $builder->build({source => 'Branch',});
475     my $category = $builder->build({source => 'Category',});
476     my $itemtype = $builder->build({
477         source => 'Itemtype',
478         value => {
479             rentalcharge => 0,
480             rentalcharge_daily => 0,
481             rentalcharge_hourly => 0,
482             notforloan => 0,
483         }
484     });
485     my $patron = $builder->build({
486         source => 'Borrower',
487         value => {
488             categorycode => $category->{categorycode},
489             branchcode => $branch->{branchcode},
490         }
491     });
492
493     # Set up an issuing rule
494     Koha::CirculationRules->set_rules(
495         {
496             categorycode => '*',
497             itemtype     => $itemtype->{itemtype},
498             branchcode   => '*',
499             rules        => {
500                 issuelength => 1,
501                 firstremind => 1,        # 1 day of grace
502                 finedays    => 2,        # 2 days of fine per day of overdue
503                 lengthunit  => 'days',
504             }
505         }
506     );
507
508     # Set default maximum issue quantity limits for branch
509     Koha::CirculationRules->set_rules(
510         {
511             branchcode   => $branch->{branchcode},
512             categorycode => '*',
513             rules        => {
514                 patron_maxissueqty       => 1,
515                 patron_maxonsiteissueqty => 1,
516             }
517         }
518     );
519
520     # Set an All->All for an itemtype
521     Koha::CirculationRules->set_rules(
522         {
523             branchcode   => '*',
524             categorycode => '*',
525             itemtype     => $itemtype->{itemtype},
526             rules        => {
527                 maxissueqty       => 1,
528                 maxonsiteissueqty => 1,
529             }
530         }
531     );
532
533     # Create an item
534     my $issue_item = $builder->build_sample_item({
535         itype => $itemtype->{itemtype}
536     });
537     my $branch_item = $builder->build_sample_item({
538         itype => $itemtype->{itemtype},
539         homebranch => $branch->{branchcode},
540         holdingbranch => $branch->{branchcode}
541     });
542
543
544     t::lib::Mocks::mock_userenv({ branchcode => $branch->{branchcode} });
545     my $issue = C4::Circulation::AddIssue( $patron, $issue_item->barcode, dt_from_string() );
546     # We checkout one item
547     is_deeply(
548         C4::Circulation::TooMany( $patron, $branch_item ),
549         {
550             reason => 'TOO_MANY_CHECKOUTS',
551             count => 1,
552             max_allowed => 1,
553         },
554         'We are only allowed one, and we have one (itemtype on item)'
555     );
556
557     # Check itemtype on biblio level
558     t::lib::Mocks::mock_preference('item-level_itypes', 0);
559     $issue_item->biblio->biblioitem->itemtype($itemtype->{itemtype})->store;
560     $branch_item->biblio->biblioitem->itemtype($itemtype->{itemtype})->store;
561     # We checkout one item
562     is_deeply(
563         C4::Circulation::TooMany( $patron, $branch_item ),
564         {
565             reason => 'TOO_MANY_CHECKOUTS',
566             count => 1,
567             max_allowed => 1,
568         },
569         'We are only allowed one, and we have one (itemtype on biblioitem)'
570     );
571     t::lib::Mocks::mock_preference('item-level_itypes', 1);
572
573     # Set a branch specific rule
574     Koha::CirculationRules->set_rules(
575         {
576             branchcode   => $branch->{branchcode},
577             categorycode => $category->{categorycode},
578             itemtype     => $itemtype->{itemtype},
579             rules        => {
580                 maxissueqty       => 1,
581                 maxonsiteissueqty => 1,
582             }
583         }
584     );
585
586     is(
587         C4::Circulation::TooMany( $patron, $branch_item ),
588         undef,
589         'We are allowed one from the branch specifically now'
590     );
591
592     # If circcontrol is PatronLibrary we count all the patron's loan, regardless of branch
593     t::lib::Mocks::mock_preference('CircControl', 'PatronLibrary');
594     is_deeply(
595         C4::Circulation::TooMany( $patron, $branch_item ),
596         {
597             reason => 'TOO_MANY_CHECKOUTS',
598             count => 1,
599             max_allowed => 1,
600         },
601         'We are allowed one from the branch specifically, but have one'
602     );
603     t::lib::Mocks::mock_preference('CircControl', 'ItemHomeLibrary');
604
605     $issue = C4::Circulation::AddIssue( $patron, $branch_item->barcode, dt_from_string() );
606     # We issue that one
607     # And make another
608     my $branch_item_2 = $builder->build_sample_item({
609         itype => $itemtype->{itemtype},
610         homebranch => $branch->{branchcode},
611         holdingbranch => $branch->{branchcode}
612     });
613     is_deeply(
614         C4::Circulation::TooMany( $patron, $branch_item_2 ),
615         {
616             reason => 'TOO_MANY_CHECKOUTS',
617             count => 1,
618             max_allowed => 1,
619         },
620         'We are only allowed one from that branch, and have one'
621     );
622
623     # Now we make anothe from a different branch
624     my $item_2 = $builder->build_sample_item({
625         itype => $itemtype->{itemtype},
626     });
627     is_deeply(
628         C4::Circulation::TooMany( $patron, $item_2 ),
629         {
630             reason => 'TOO_MANY_CHECKOUTS',
631             count => 2,
632             max_allowed => 1,
633         },
634         'We are only allowed one for general rule, and have two'
635     );
636     t::lib::Mocks::mock_preference('CircControl', 'PatronLibrary');
637     is_deeply(
638         C4::Circulation::TooMany( $patron, $item_2 ),
639         {
640             reason => 'TOO_MANY_CHECKOUTS',
641             count => 2,
642             max_allowed => 1,
643         },
644         'We are only allowed one for general rule, and have two'
645     );
646
647     t::lib::Mocks::mock_preference('CircControl', 'PickupLibrary');
648     is_deeply(
649         C4::Circulation::TooMany( $patron, $item_2 ),
650         {
651             reason => 'TOO_MANY_CHECKOUTS',
652             count => 2,
653             max_allowed => 1,
654         },
655         'We are only allowed one for general rule, and have checked out two at this branch'
656     );
657
658     my $branch2   = $builder->build({source => 'Branch',});
659     t::lib::Mocks::mock_userenv({ branchcode => $branch2->{branchcode} });
660     is_deeply(
661         C4::Circulation::TooMany( $patron, $item_2 ),
662         {
663             reason => 'TOO_MANY_CHECKOUTS',
664             count => 2,
665             max_allowed => 1,
666         },
667         'We are only allowed one for general rule, and have two total (no rule for specific branch)'
668     );
669     # Set a branch specific rule for new branch
670     Koha::CirculationRules->set_rules(
671         {
672             branchcode   => $branch2->{branchcode},
673             categorycode => $category->{categorycode},
674             itemtype     => $itemtype->{itemtype},
675             rules        => {
676                 maxissueqty       => 1,
677                 maxonsiteissueqty => 1,
678             }
679         }
680     );
681
682     is(
683         C4::Circulation::TooMany( $patron, $branch_item ),
684         undef,
685         'We are allowed one from the branch specifically now'
686     );
687 };
688
689 subtest 'empty string means unlimited' => sub {
690     plan tests => 2;
691
692     Koha::CirculationRules->set_rules(
693         {
694             branchcode   => '*',
695             categorycode => '*',
696             itemtype     => '*',
697             rules        => {
698                 maxissueqty       => '',
699                 maxonsiteissueqty => '',
700             }
701         },
702     );
703     is(
704         C4::Circulation::TooMany( $patron, $item ),
705         undef,
706         'maxissueqty="" should mean unlimited'
707     );
708
709     is(
710         C4::Circulation::TooMany( $patron, $item, { onsite_checkout => 1 } ),
711         undef,
712         'maxonsiteissueqty="" should mean unlimited'
713       );
714 };
715
716 subtest 'itemtype group tests' => sub {
717     plan tests => 13;
718
719     t::lib::Mocks::mock_preference( 'CircControl', 'ItemHomeLibrary' );
720     Koha::CirculationRules->set_rules(
721         {
722             branchcode   => '*',
723             categorycode => '*',
724             itemtype     => '*',
725             rules        => {
726                 maxissueqty       => '',
727                 maxonsiteissueqty => '',
728                 issuelength       => 1,
729                 firstremind       => 1,      # 1 day of grace
730                 finedays          => 2,      # 2 days of fine per day of overdue
731                 lengthunit        => 'days',
732             }
733         },
734     );
735
736     my $parent_itype = $builder->build(
737         {
738             source => 'Itemtype',
739             value  => {
740                 parent_type         => undef,
741                 rentalcharge        => undef,
742                 rentalcharge_daily  => undef,
743                 rentalcharge_hourly => undef,
744                 notforloan          => 0,
745             }
746         }
747     );
748     my $child_itype_1 = $builder->build(
749         {
750             source => 'Itemtype',
751             value  => {
752                 parent_type         => $parent_itype->{itemtype},
753                 rentalcharge        => 0,
754                 rentalcharge_daily  => 0,
755                 rentalcharge_hourly => 0,
756                 notforloan          => 0,
757             }
758         }
759     );
760     my $child_itype_2 = $builder->build(
761         {
762             source => 'Itemtype',
763             value  => {
764                 parent_type         => $parent_itype->{itemtype},
765                 rentalcharge        => 0,
766                 rentalcharge_daily  => 0,
767                 rentalcharge_hourly => 0,
768                 notforloan          => 0,
769             }
770         }
771     );
772
773     my $branch   = $builder->build( { source => 'Branch', } );
774     my $category = $builder->build( { source => 'Category', } );
775     my $patron   = $builder->build(
776         {
777             source => 'Borrower',
778             value  => {
779                 categorycode => $category->{categorycode},
780                 branchcode   => $branch->{branchcode},
781             },
782         }
783     );
784     my $item = $builder->build_sample_item(
785         {
786             homebranch    => $branch->{branchcode},
787             holdingbranch => $branch->{branchcode},
788             itype         => $child_itype_1->{itemtype}
789         }
790     );
791
792     my $all_iq_rule = $builder->build(
793         {
794             source => 'CirculationRule',
795             value  => {
796                 branchcode   => $branch->{branchcode},
797                 categorycode => $category->{categorycode},
798                 itemtype     => undef,
799                 rule_name    => 'maxissueqty',
800                 rule_value   => 1
801             }
802         }
803     );
804     is( C4::Circulation::TooMany( $patron, $item ),
805         undef, 'Checkout allowed, using all rule of 1' );
806
807     #Checkout an item
808     my $issue =
809       C4::Circulation::AddIssue( $patron, $item->barcode, dt_from_string() );
810     like( $issue->issue_id, qr|^\d+$|, 'The issue should have been inserted' );
811
812     #Patron has 1 checkout of child itype1
813
814     my $parent_iq_rule = $builder->build(
815         {
816             source => 'CirculationRule',
817             value  => {
818                 branchcode   => $branch->{branchcode},
819                 categorycode => $category->{categorycode},
820                 itemtype     => $parent_itype->{itemtype},
821                 rule_name    => 'maxissueqty',
822                 rule_value   => 2
823             }
824         }
825     );
826
827     is( C4::Circulation::TooMany( $patron, $item ),
828         undef, 'Checkout allowed, using parent type rule of 2' );
829
830     my $child1_iq_rule = $builder->build_object(
831         {
832             class => 'Koha::CirculationRules',
833             value => {
834                 branchcode   => $branch->{branchcode},
835                 categorycode => $category->{categorycode},
836                 itemtype     => $child_itype_1->{itemtype},
837                 rule_name    => 'maxissueqty',
838                 rule_value   => 1
839             }
840         }
841     );
842
843     is_deeply(
844         C4::Circulation::TooMany( $patron, $item ),
845         {
846             reason      => 'TOO_MANY_CHECKOUTS',
847             count       => 1,
848             max_allowed => 1,
849         },
850         'Checkout not allowed, using specific type rule of 1'
851     );
852
853     my $item_1 = $builder->build_sample_item(
854         {
855             homebranch    => $branch->{branchcode},
856             holdingbranch => $branch->{branchcode},
857             itype         => $child_itype_2->{itemtype}
858         }
859     );
860
861     my $child2_iq_rule = $builder->build(
862         {
863             source => 'CirculationRule',
864             value  => {
865                 branchcode   => $branch->{branchcode},
866                 categorycode => $category->{categorycode},
867                 itemtype     => $child_itype_2->{itemtype},
868                 rule_name    => 'maxissueqty',
869                 rule_value   => 3
870             }
871         }
872     );
873
874     is( C4::Circulation::TooMany( $patron, $item_1 ),
875         undef, 'Checkout allowed' );
876
877     #checkout an item
878     $issue =
879       C4::Circulation::AddIssue( $patron, $item_1->barcode, dt_from_string() );
880     like( $issue->issue_id, qr|^\d+$|, 'the issue should have been inserted' );
881
882     #patron has 1 checkout of childitype1 and 1 checkout of childitype2
883
884     is_deeply(
885         C4::Circulation::TooMany( $patron, $item ),
886         {
887             reason      => 'TOO_MANY_CHECKOUTS',
888             count       => 2,
889             max_allowed => 2,
890         },
891 'Checkout not allowed, using parent type rule of 2, checkout of sibling itemtype counted'
892     );
893
894     my $parent_item = $builder->build_sample_item(
895         {
896             homebranch    => $branch->{branchcode},
897             holdingbranch => $branch->{branchcode},
898             itype         => $parent_itype->{itemtype}
899         }
900     );
901
902     is_deeply(
903         C4::Circulation::TooMany( $patron, $parent_item ),
904         {
905             reason      => 'TOO_MANY_CHECKOUTS',
906             count       => 2,
907             max_allowed => 2,
908         },
909 'Checkout not allowed, using parent type rule of 2, checkout of child itemtypes counted'
910     );
911
912     #increase parent type to greater than specific
913     my $circ_rule_object =
914       Koha::CirculationRules->find( $parent_iq_rule->{id} );
915     $circ_rule_object->rule_value(4)->store();
916
917     is( C4::Circulation::TooMany( $patron, $item_1 ),
918         undef, 'Checkout allowed, using specific type rule of 3' );
919
920     my $item_2 = $builder->build_sample_item(
921         {
922             homebranch    => $branch->{branchcode},
923             holdingbranch => $branch->{branchcode},
924             itype         => $child_itype_2->{itemtype}
925         }
926     );
927
928     #checkout an item
929     $issue =
930       C4::Circulation::AddIssue( $patron, $item_2->barcode, dt_from_string(),
931         undef, undef, undef );
932     like( $issue->issue_id, qr|^\d+$|, 'the issue should have been inserted' );
933
934     #patron has 1 checkout of childitype1 and 2 of childitype2
935
936     is(
937         C4::Circulation::TooMany( $patron, $item_2 ),
938         undef,
939 'Checkout allowed, using specific type rule of 3, checkout of sibling itemtype not counted'
940     );
941
942     $child1_iq_rule->rule_value(2)->store(); #Allow 2 checkouts for child type 1
943
944     my $item_3 = $builder->build_sample_item(
945         {
946             homebranch    => $branch->{branchcode},
947             holdingbranch => $branch->{branchcode},
948             itype         => $child_itype_1->{itemtype}
949         }
950     );
951     my $item_4 = $builder->build_sample_item(
952         {
953             homebranch    => $branch->{branchcode},
954             holdingbranch => $branch->{branchcode},
955             itype         => $child_itype_2->{itemtype}
956         }
957     );
958
959     #checkout an item
960     $issue =
961       C4::Circulation::AddIssue( $patron, $item_4->barcode, dt_from_string(),
962         undef, undef, undef );
963     like( $issue->issue_id, qr|^\d+$|, 'the issue should have been inserted' );
964
965     #patron has 1 checkout of childitype 1 and 3 of childitype2
966
967     is_deeply(
968         C4::Circulation::TooMany( $patron, $item_3 ),
969         {
970             reason      => 'TOO_MANY_CHECKOUTS',
971             max_allowed => 4,
972             count       => 4,
973         },
974 'Checkout not allowed, using specific type rule of 2, checkout of sibling itemtype not counted, but parent rule (4) prevents another'
975     );
976
977     teardown();
978 };
979
980 $schema->storage->txn_rollback;
981
982 sub teardown {
983     $dbh->do(q|DELETE FROM issues|);
984     $dbh->do(q|DELETE FROM circulation_rules|);
985 }
986