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