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