0f4843b82238f43ddf05a9eb184b285fd6fa96a9
[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 an All->All for an itemtype
509     Koha::CirculationRules->set_rules(
510         {
511             branchcode   => '*',
512             categorycode => '*',
513             itemtype     => $itemtype->{itemtype},
514             rules        => {
515                 maxissueqty       => 1,
516                 maxonsiteissueqty => 1,
517             }
518         }
519     );
520
521     # Create an item
522     my $issue_item = $builder->build_sample_item({
523         itype => $itemtype->{itemtype}
524     });
525     my $branch_item = $builder->build_sample_item({
526         itype => $itemtype->{itemtype},
527         homebranch => $branch->{branchcode},
528         holdingbranch => $branch->{branchcode}
529     });
530
531
532     t::lib::Mocks::mock_userenv({ branchcode => $branch->{branchcode} });
533     my $issue = C4::Circulation::AddIssue( $patron, $issue_item->barcode, dt_from_string() );
534     # We checkout one item
535     is_deeply(
536         C4::Circulation::TooMany( $patron, $branch_item ),
537         {
538             reason => 'TOO_MANY_CHECKOUTS',
539             count => 1,
540             max_allowed => 1,
541         },
542         'We are only allowed one, and we have one (itemtype on item)'
543     );
544
545     # Check itemtype on biblio level
546     t::lib::Mocks::mock_preference('item-level_itypes', 0);
547     $issue_item->biblio->biblioitem->itemtype($itemtype->{itemtype})->store;
548     $branch_item->biblio->biblioitem->itemtype($itemtype->{itemtype})->store;
549     # We checkout one item
550     is_deeply(
551         C4::Circulation::TooMany( $patron, $branch_item ),
552         {
553             reason => 'TOO_MANY_CHECKOUTS',
554             count => 1,
555             max_allowed => 1,
556         },
557         'We are only allowed one, and we have one (itemtype on biblioitem)'
558     );
559     t::lib::Mocks::mock_preference('item-level_itypes', 1);
560
561     # Set a branch specific rule
562     Koha::CirculationRules->set_rules(
563         {
564             branchcode   => $branch->{branchcode},
565             categorycode => $category->{categorycode},
566             itemtype     => $itemtype->{itemtype},
567             rules        => {
568                 maxissueqty       => 1,
569                 maxonsiteissueqty => 1,
570             }
571         }
572     );
573
574     is(
575         C4::Circulation::TooMany( $patron, $branch_item ),
576         undef,
577         'We are allowed one from the branch specifically now'
578     );
579
580     # If circcontrol is PatronLibrary we count all the patron's loan, regardless of branch
581     t::lib::Mocks::mock_preference('CircControl', 'PatronLibrary');
582     is_deeply(
583         C4::Circulation::TooMany( $patron, $branch_item ),
584         {
585             reason => 'TOO_MANY_CHECKOUTS',
586             count => 1,
587             max_allowed => 1,
588         },
589         'We are allowed one from the branch specifically, but have one'
590     );
591     t::lib::Mocks::mock_preference('CircControl', 'ItemHomeLibrary');
592
593     $issue = C4::Circulation::AddIssue( $patron, $branch_item->barcode, dt_from_string() );
594     # We issue that one
595     # And make another
596     my $branch_item_2 = $builder->build_sample_item({
597         itype => $itemtype->{itemtype},
598         homebranch => $branch->{branchcode},
599         holdingbranch => $branch->{branchcode}
600     });
601     is_deeply(
602         C4::Circulation::TooMany( $patron, $branch_item_2 ),
603         {
604             reason => 'TOO_MANY_CHECKOUTS',
605             count => 1,
606             max_allowed => 1,
607         },
608         'We are only allowed one from that branch, and have one'
609     );
610
611     # Now we make anothe from a different branch
612     my $item_2 = $builder->build_sample_item({
613         itype => $itemtype->{itemtype},
614     });
615     is_deeply(
616         C4::Circulation::TooMany( $patron, $item_2 ),
617         {
618             reason => 'TOO_MANY_CHECKOUTS',
619             count => 2,
620             max_allowed => 1,
621         },
622         'We are only allowed one for general rule, and have two'
623     );
624     t::lib::Mocks::mock_preference('CircControl', 'PatronLibrary');
625     is_deeply(
626         C4::Circulation::TooMany( $patron, $item_2 ),
627         {
628             reason => 'TOO_MANY_CHECKOUTS',
629             count => 2,
630             max_allowed => 1,
631         },
632         'We are only allowed one for general rule, and have two'
633     );
634
635     t::lib::Mocks::mock_preference('CircControl', 'PickupLibrary');
636     is_deeply(
637         C4::Circulation::TooMany( $patron, $item_2 ),
638         {
639             reason => 'TOO_MANY_CHECKOUTS',
640             count => 2,
641             max_allowed => 1,
642         },
643         'We are only allowed one for general rule, and have checked out two at this branch'
644     );
645
646     my $branch2   = $builder->build({source => 'Branch',});
647     t::lib::Mocks::mock_userenv({ branchcode => $branch2->{branchcode} });
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 two total (no rule for specific branch)'
656     );
657     # Set a branch specific rule for new branch
658     Koha::CirculationRules->set_rules(
659         {
660             branchcode   => $branch2->{branchcode},
661             categorycode => $category->{categorycode},
662             itemtype     => $itemtype->{itemtype},
663             rules        => {
664                 maxissueqty       => 1,
665                 maxonsiteissueqty => 1,
666             }
667         }
668     );
669
670     is(
671         C4::Circulation::TooMany( $patron, $branch_item ),
672         undef,
673         'We are allowed one from the branch specifically now'
674     );
675 };
676
677 subtest 'empty string means unlimited' => sub {
678     plan tests => 2;
679
680     Koha::CirculationRules->set_rules(
681         {
682             branchcode   => '*',
683             categorycode => '*',
684             itemtype     => '*',
685             rules        => {
686                 maxissueqty       => '',
687                 maxonsiteissueqty => '',
688             }
689         },
690     );
691     is(
692         C4::Circulation::TooMany( $patron, $item ),
693         undef,
694         'maxissueqty="" should mean unlimited'
695     );
696
697     is(
698         C4::Circulation::TooMany( $patron, $item, { onsite_checkout => 1 } ),
699         undef,
700         'maxonsiteissueqty="" should mean unlimited'
701       );
702 };
703
704 subtest 'itemtype group tests' => sub {
705     plan tests => 13;
706
707     t::lib::Mocks::mock_preference( 'CircControl', 'ItemHomeLibrary' );
708     Koha::CirculationRules->set_rules(
709         {
710             branchcode   => '*',
711             categorycode => '*',
712             itemtype     => '*',
713             rules        => {
714                 maxissueqty       => '',
715                 maxonsiteissueqty => '',
716                 issuelength       => 1,
717                 firstremind       => 1,      # 1 day of grace
718                 finedays          => 2,      # 2 days of fine per day of overdue
719                 lengthunit        => 'days',
720             }
721         },
722     );
723
724     my $parent_itype = $builder->build(
725         {
726             source => 'Itemtype',
727             value  => {
728                 parent_type         => undef,
729                 rentalcharge        => undef,
730                 rentalcharge_daily  => undef,
731                 rentalcharge_hourly => undef,
732                 notforloan          => 0,
733             }
734         }
735     );
736     my $child_itype_1 = $builder->build(
737         {
738             source => 'Itemtype',
739             value  => {
740                 parent_type         => $parent_itype->{itemtype},
741                 rentalcharge        => 0,
742                 rentalcharge_daily  => 0,
743                 rentalcharge_hourly => 0,
744                 notforloan          => 0,
745             }
746         }
747     );
748     my $child_itype_2 = $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
761     my $branch   = $builder->build( { source => 'Branch', } );
762     my $category = $builder->build( { source => 'Category', } );
763     my $patron   = $builder->build(
764         {
765             source => 'Borrower',
766             value  => {
767                 categorycode => $category->{categorycode},
768                 branchcode   => $branch->{branchcode},
769             },
770         }
771     );
772     my $item = $builder->build_sample_item(
773         {
774             homebranch    => $branch->{branchcode},
775             holdingbranch => $branch->{branchcode},
776             itype         => $child_itype_1->{itemtype}
777         }
778     );
779
780     my $all_iq_rule = $builder->build(
781         {
782             source => 'CirculationRule',
783             value  => {
784                 branchcode   => $branch->{branchcode},
785                 categorycode => $category->{categorycode},
786                 itemtype     => undef,
787                 rule_name    => 'maxissueqty',
788                 rule_value   => 1
789             }
790         }
791     );
792     is( C4::Circulation::TooMany( $patron, $item ),
793         undef, 'Checkout allowed, using all rule of 1' );
794
795     #Checkout an item
796     my $issue =
797       C4::Circulation::AddIssue( $patron, $item->barcode, dt_from_string() );
798     like( $issue->issue_id, qr|^\d+$|, 'The issue should have been inserted' );
799
800     #Patron has 1 checkout of child itype1
801
802     my $parent_iq_rule = $builder->build(
803         {
804             source => 'CirculationRule',
805             value  => {
806                 branchcode   => $branch->{branchcode},
807                 categorycode => $category->{categorycode},
808                 itemtype     => $parent_itype->{itemtype},
809                 rule_name    => 'maxissueqty',
810                 rule_value   => 2
811             }
812         }
813     );
814
815     is( C4::Circulation::TooMany( $patron, $item ),
816         undef, 'Checkout allowed, using parent type rule of 2' );
817
818     my $child1_iq_rule = $builder->build_object(
819         {
820             class => 'Koha::CirculationRules',
821             value => {
822                 branchcode   => $branch->{branchcode},
823                 categorycode => $category->{categorycode},
824                 itemtype     => $child_itype_1->{itemtype},
825                 rule_name    => 'maxissueqty',
826                 rule_value   => 1
827             }
828         }
829     );
830
831     is_deeply(
832         C4::Circulation::TooMany( $patron, $item ),
833         {
834             reason      => 'TOO_MANY_CHECKOUTS',
835             count       => 1,
836             max_allowed => 1,
837         },
838         'Checkout not allowed, using specific type rule of 1'
839     );
840
841     my $item_1 = $builder->build_sample_item(
842         {
843             homebranch    => $branch->{branchcode},
844             holdingbranch => $branch->{branchcode},
845             itype         => $child_itype_2->{itemtype}
846         }
847     );
848
849     my $child2_iq_rule = $builder->build(
850         {
851             source => 'CirculationRule',
852             value  => {
853                 branchcode   => $branch->{branchcode},
854                 categorycode => $category->{categorycode},
855                 itemtype     => $child_itype_2->{itemtype},
856                 rule_name    => 'maxissueqty',
857                 rule_value   => 3
858             }
859         }
860     );
861
862     is( C4::Circulation::TooMany( $patron, $item_1 ),
863         undef, 'Checkout allowed' );
864
865     #checkout an item
866     $issue =
867       C4::Circulation::AddIssue( $patron, $item_1->barcode, dt_from_string() );
868     like( $issue->issue_id, qr|^\d+$|, 'the issue should have been inserted' );
869
870     #patron has 1 checkout of childitype1 and 1 checkout of childitype2
871
872     is_deeply(
873         C4::Circulation::TooMany( $patron, $item ),
874         {
875             reason      => 'TOO_MANY_CHECKOUTS',
876             count       => 2,
877             max_allowed => 2,
878         },
879 'Checkout not allowed, using parent type rule of 2, checkout of sibling itemtype counted'
880     );
881
882     my $parent_item = $builder->build_sample_item(
883         {
884             homebranch    => $branch->{branchcode},
885             holdingbranch => $branch->{branchcode},
886             itype         => $parent_itype->{itemtype}
887         }
888     );
889
890     is_deeply(
891         C4::Circulation::TooMany( $patron, $parent_item ),
892         {
893             reason      => 'TOO_MANY_CHECKOUTS',
894             count       => 2,
895             max_allowed => 2,
896         },
897 'Checkout not allowed, using parent type rule of 2, checkout of child itemtypes counted'
898     );
899
900     #increase parent type to greater than specific
901     my $circ_rule_object =
902       Koha::CirculationRules->find( $parent_iq_rule->{id} );
903     $circ_rule_object->rule_value(4)->store();
904
905     is( C4::Circulation::TooMany( $patron, $item_1 ),
906         undef, 'Checkout allowed, using specific type rule of 3' );
907
908     my $item_2 = $builder->build_sample_item(
909         {
910             homebranch    => $branch->{branchcode},
911             holdingbranch => $branch->{branchcode},
912             itype         => $child_itype_2->{itemtype}
913         }
914     );
915
916     #checkout an item
917     $issue =
918       C4::Circulation::AddIssue( $patron, $item_2->barcode, dt_from_string(),
919         undef, undef, undef );
920     like( $issue->issue_id, qr|^\d+$|, 'the issue should have been inserted' );
921
922     #patron has 1 checkout of childitype1 and 2 of childitype2
923
924     is(
925         C4::Circulation::TooMany( $patron, $item_2 ),
926         undef,
927 'Checkout allowed, using specific type rule of 3, checkout of sibling itemtype not counted'
928     );
929
930     $child1_iq_rule->rule_value(2)->store(); #Allow 2 checkouts for child type 1
931
932     my $item_3 = $builder->build_sample_item(
933         {
934             homebranch    => $branch->{branchcode},
935             holdingbranch => $branch->{branchcode},
936             itype         => $child_itype_1->{itemtype}
937         }
938     );
939     my $item_4 = $builder->build_sample_item(
940         {
941             homebranch    => $branch->{branchcode},
942             holdingbranch => $branch->{branchcode},
943             itype         => $child_itype_2->{itemtype}
944         }
945     );
946
947     #checkout an item
948     $issue =
949       C4::Circulation::AddIssue( $patron, $item_4->barcode, dt_from_string(),
950         undef, undef, undef );
951     like( $issue->issue_id, qr|^\d+$|, 'the issue should have been inserted' );
952
953     #patron has 1 checkout of childitype 1 and 3 of childitype2
954
955     is_deeply(
956         C4::Circulation::TooMany( $patron, $item_3 ),
957         {
958             reason      => 'TOO_MANY_CHECKOUTS',
959             max_allowed => 4,
960             count       => 4,
961         },
962 'Checkout not allowed, using specific type rule of 2, checkout of sibling itemtype not counted, but parent rule (4) prevents another'
963     );
964
965     teardown();
966 };
967
968 $schema->storage->txn_rollback;
969
970 sub teardown {
971     $dbh->do(q|DELETE FROM issues|);
972     $dbh->do(q|DELETE FROM circulation_rules|);
973 }
974