View | Details | Raw Unified | Return to bug 21946
Collapse All | Expand All

(-)a/C4/Circulation.pm (-64 / +146 lines)
Lines 389-396 sub TooMany { Link Here
389
  			? $item->{'itype'}         # item-level
389
  			? $item->{'itype'}         # item-level
390
			: $item->{'itemtype'};     # biblio-level
390
			: $item->{'itemtype'};     # biblio-level
391
 
391
 
392
    my ($type_object, $parent_type, $parent_maxissueqty_rule);
393
    $type_object = Koha::ItemTypes->find( $type );
394
    $parent_type = $type_object->parent_type if $type_object;
395
    my $child_types = Koha::ItemTypes->search( parent_type => $type );
396
    # Find any children if we are a parent_type;
397
392
    # given branch, patron category, and item type, determine
398
    # given branch, patron category, and item type, determine
393
    # applicable issuing rule
399
    # applicable issuing rule
400
401
    $parent_maxissueqty_rule = Koha::CirculationRules->get_effective_rule(
402
        {
403
            categorycode => $cat_borrower,
404
            itemtype     => $parent_type,
405
            branchcode   => $branch,
406
            rule_name    => 'maxissueqty',
407
        }
408
    ) if $parent_type;
409
    # If the parent rule is for default type we discount it
410
    $parent_maxissueqty_rule = undef if $parent_maxissueqty_rule && $parent_maxissueqty_rule->itemtype eq '*';
411
394
    my $maxissueqty_rule = Koha::CirculationRules->get_effective_rule(
412
    my $maxissueqty_rule = Koha::CirculationRules->get_effective_rule(
395
        {
413
        {
396
            categorycode => $cat_borrower,
414
            categorycode => $cat_borrower,
Lines 399-404 sub TooMany { Link Here
399
            rule_name    => 'maxissueqty',
417
            rule_name    => 'maxissueqty',
400
        }
418
        }
401
    );
419
    );
420
421
402
    my $maxonsiteissueqty_rule = Koha::CirculationRules->get_effective_rule(
422
    my $maxonsiteissueqty_rule = Koha::CirculationRules->get_effective_rule(
403
        {
423
        {
404
            categorycode => $cat_borrower,
424
            categorycode => $cat_borrower,
Lines 413-421 sub TooMany { Link Here
413
    # how many loans the patron already has that meet that
433
    # how many loans the patron already has that meet that
414
    # rule
434
    # rule
415
    if (defined($maxissueqty_rule) and defined($maxissueqty_rule->rule_value)) {
435
    if (defined($maxissueqty_rule) and defined($maxissueqty_rule->rule_value)) {
436
416
        my @bind_params;
437
        my @bind_params;
417
        my $count_query = q|
438
        my $count_query = "";
418
            SELECT COUNT(*) AS total, COALESCE(SUM(onsite_checkout), 0) AS onsite_checkouts
439
440
        if (C4::Context->preference('item-level_itypes')) {
441
            $count_query .= q|SELECT COALESCE( SUM( IF(items.itype = '| .$type . q|',1,0) ), 0) as type_total, COUNT(*) AS total, COALESCE(SUM(onsite_checkout), 0) AS onsite_checkouts|;
442
        } else{
443
            $count_query .= q|SELECT COALESCE(SUM( IF(biblioitems.itype = '| .$type . q|',1,0) ), 0) as type_total, COUNT(*) AS total, COALESCE(SUM(onsite_checkout), 0) AS onsite_checkouts|;
444
        }
445
446
        $count_query .= q|
419
            FROM issues
447
            FROM issues
420
            JOIN items USING (itemnumber)
448
            JOIN items USING (itemnumber)
421
        |;
449
        |;
Lines 445-459 sub TooMany { Link Here
445
            push @bind_params, $maxissueqty_rule->categorycode;
473
            push @bind_params, $maxissueqty_rule->categorycode;
446
            push @bind_params, $cat_borrower;
474
            push @bind_params, $cat_borrower;
447
        } else {
475
        } else {
448
            # rule has specific item type, so count loans of that
476
            my @types;
449
            # specific item type
477
            if ( $parent_maxissueqty_rule ) {
478
            # if we have a parent item type then we count loans of the
479
            # specific item type or its siblings or parent
480
                push @types, $parent_type;
481
                my $children = Koha::ItemTypes->search({ parent_type => $parent_type });
482
                while ( my $child = $children->next ){
483
                    push @types, $child->itemtype;
484
                }
485
            } elsif ( $child_types ) {
486
            # If we are a parent type, we need to count all child types and our own type
487
                while ( my $child = $child_types->next ){
488
                    push @types, $child->itemtype;
489
                }
490
                push @types, $type; # And don't forget to count our own types
491
            } else { push @types, $type; } # Otherwise only count the specific itemtype
492
            my $types_param = ( '?,' ) x @types;
493
            $types_param =~ s/,$//;
450
            if (C4::Context->preference('item-level_itypes')) {
494
            if (C4::Context->preference('item-level_itypes')) {
451
                $count_query .= " WHERE items.itype = ? ";
495
                $count_query .= " WHERE items.itype IN (" . $types_param . ")";
452
            } else { 
496
            } else { 
453
                $count_query .= " JOIN  biblioitems USING (biblionumber) 
497
                $count_query .= " JOIN  biblioitems USING (biblionumber) 
454
                                  WHERE biblioitems.itemtype= ? ";
498
                                  WHERE biblioitems.itemtype IN (" . $types_param . ")";
455
            }
499
            }
456
            push @bind_params, $type;
500
            push @bind_params, @types;
457
        }
501
        }
458
502
459
        $count_query .= " AND borrowernumber = ? ";
503
        $count_query .= " AND borrowernumber = ? ";
Lines 471-508 sub TooMany { Link Here
471
            }
515
            }
472
        }
516
        }
473
517
474
        my ( $checkout_count, $onsite_checkout_count ) = $dbh->selectrow_array( $count_query, {}, @bind_params );
518
        my ( $checkout_count_type, $checkout_count, $onsite_checkout_count ) = $dbh->selectrow_array( $count_query, {}, @bind_params );
475
519
476
        my $max_checkouts_allowed = $maxissueqty_rule ? $maxissueqty_rule->rule_value : undef;
477
        my $max_onsite_checkouts_allowed = $maxonsiteissueqty_rule ? $maxonsiteissueqty_rule->rule_value : undef;
520
        my $max_onsite_checkouts_allowed = $maxonsiteissueqty_rule ? $maxonsiteissueqty_rule->rule_value : undef;
478
521
479
        if ( $onsite_checkout and defined $max_onsite_checkouts_allowed ) {
522
        # If parent rules exists
480
            if ( $onsite_checkout_count >= $max_onsite_checkouts_allowed )  {
523
        if ( defined($parent_maxissueqty_rule) and defined($parent_maxissueqty_rule->rule_value) ){
481
                return {
524
            my $max_checkouts_allowed = $parent_maxissueqty_rule->rule_value;
482
                    reason => 'TOO_MANY_ONSITE_CHECKOUTS',
525
483
                    count => $onsite_checkout_count,
526
            my $qty_over = _check_max_qty({
484
                    max_allowed => $max_onsite_checkouts_allowed,
527
                checkout_count => $checkout_count,
485
                }
528
                onsite_checkout_count => $onsite_checkout_count,
486
            }
529
                onsite_checkout => $onsite_checkout,
487
        }
530
                max_checkouts_allowed => $max_checkouts_allowed,
488
        if ( C4::Context->preference('ConsiderOnSiteCheckoutsAsNormalCheckouts') ) {
531
                max_onsite_checkouts_allowed => $max_onsite_checkouts_allowed,
489
            my $delta = $switch_onsite_checkout ? 1 : 0;
532
                switch_onsite_checkout       => $switch_onsite_checkout
490
            if ( $checkout_count >= $max_checkouts_allowed + $delta ) {
533
            });
491
                return {
534
            return $qty_over if defined $qty_over;
492
                    reason => 'TOO_MANY_CHECKOUTS',
535
493
                    count => $checkout_count,
536
494
                    max_allowed => $max_checkouts_allowed,
537
           # If the parent rule is less than or equal to the child, we only need check the parent
495
                };
538
           if( $maxissueqty_rule->rule_value < $parent_maxissueqty_rule->rule_value && defined($maxissueqty_rule->itemtype) ) {
496
            }
539
               my $max_checkouts_allowed = $maxissueqty_rule->rule_value;
497
        } elsif ( not $onsite_checkout ) {
540
               my $qty_over = _check_max_qty({
498
            if ( $checkout_count - $onsite_checkout_count >= $max_checkouts_allowed )  {
541
                   checkout_count => $checkout_count_type,
499
                return {
542
                   onsite_checkout_count => $onsite_checkout_count,
500
                    reason => 'TOO_MANY_CHECKOUTS',
543
                   onsite_checkout => $onsite_checkout,
501
                    count => $checkout_count - $onsite_checkout_count,
544
                   max_checkouts_allowed => $max_checkouts_allowed,
502
                    max_allowed => $max_checkouts_allowed,
545
                   max_onsite_checkouts_allowed => $max_onsite_checkouts_allowed,
503
                };
546
                   switch_onsite_checkout       => $switch_onsite_checkout
504
            }
547
               });
548
               return $qty_over if defined $qty_over;
549
           }
550
551
        } else {
552
            my $max_checkouts_allowed = $maxissueqty_rule->rule_value;
553
            my $qty_over = _check_max_qty({
554
                checkout_count => $checkout_count,
555
                onsite_checkout_count => $onsite_checkout_count,
556
                onsite_checkout => $onsite_checkout,
557
                max_checkouts_allowed => $max_checkouts_allowed,
558
                max_onsite_checkouts_allowed => $max_onsite_checkouts_allowed,
559
                switch_onsite_checkout       => $switch_onsite_checkout
560
            });
561
            return $qty_over if defined $qty_over;
505
        }
562
        }
563
564
506
    }
565
    }
507
566
508
    # Now count total loans against the limit for the branch
567
    # Now count total loans against the limit for the branch
Lines 528-562 sub TooMany { Link Here
528
        }
587
        }
529
        my ( $checkout_count, $onsite_checkout_count ) = $dbh->selectrow_array( $branch_count_query, {}, @bind_params );
588
        my ( $checkout_count, $onsite_checkout_count ) = $dbh->selectrow_array( $branch_count_query, {}, @bind_params );
530
        my $max_checkouts_allowed = $branch_borrower_circ_rule->{patron_maxissueqty};
589
        my $max_checkouts_allowed = $branch_borrower_circ_rule->{patron_maxissueqty};
531
        my $max_onsite_checkouts_allowed = $branch_borrower_circ_rule->{patron_maxonsiteissueqty};
590
        my $max_onsite_checkouts_allowed = $branch_borrower_circ_rule->{patron_maxonsiteissueqty} || undef;
532
591
533
        if ( $onsite_checkout and $max_onsite_checkouts_allowed ne '' ) {
592
        my $qty_over = _check_max_qty({
534
            if ( $onsite_checkout_count >= $max_onsite_checkouts_allowed )  {
593
            checkout_count => $checkout_count,
535
                return {
594
            onsite_checkout_count => $onsite_checkout_count,
536
                    reason => 'TOO_MANY_ONSITE_CHECKOUTS',
595
            onsite_checkout => $onsite_checkout,
537
                    count => $onsite_checkout_count,
596
            max_checkouts_allowed => $max_checkouts_allowed,
538
                    max_allowed => $max_onsite_checkouts_allowed,
597
            max_onsite_checkouts_allowed => $max_onsite_checkouts_allowed,
539
                }
598
            switch_onsite_checkout       => $switch_onsite_checkout
540
            }
599
        });
541
        }
600
        return $qty_over if defined $qty_over;
542
        if ( C4::Context->preference('ConsiderOnSiteCheckoutsAsNormalCheckouts') ) {
601
543
            my $delta = $switch_onsite_checkout ? 1 : 0;
544
            if ( $checkout_count >= $max_checkouts_allowed + $delta ) {
545
                return {
546
                    reason => 'TOO_MANY_CHECKOUTS',
547
                    count => $checkout_count,
548
                    max_allowed => $max_checkouts_allowed,
549
                };
550
            }
551
        } elsif ( not $onsite_checkout ) {
552
            if ( $checkout_count - $onsite_checkout_count >= $max_checkouts_allowed )  {
553
                return {
554
                    reason => 'TOO_MANY_CHECKOUTS',
555
                    count => $checkout_count - $onsite_checkout_count,
556
                    max_allowed => $max_checkouts_allowed,
557
                };
558
            }
559
        }
560
    }
602
    }
561
603
562
    if ( not defined( $maxissueqty_rule ) and not defined($branch_borrower_circ_rule->{patron_maxissueqty}) ) {
604
    if ( not defined( $maxissueqty_rule ) and not defined($branch_borrower_circ_rule->{patron_maxissueqty}) ) {
Lines 567-572 sub TooMany { Link Here
567
    return;
609
    return;
568
}
610
}
569
611
612
sub _check_max_qty {
613
    my $params = shift;
614
    my $checkout_count = $params->{checkout_count};
615
    my $onsite_checkout_count = $params->{onsite_checkout_count};
616
    my $onsite_checkout = $params->{onsite_checkout};
617
    my $max_checkouts_allowed = $params->{max_checkouts_allowed};
618
    my $max_onsite_checkouts_allowed = $params->{max_onsite_checkouts_allowed};
619
    my $switch_onsite_checkout = $params->{switch_onsite_checkout};
620
621
    if ( $onsite_checkout and defined $max_onsite_checkouts_allowed ) {
622
        if ( $onsite_checkout_count >= $max_onsite_checkouts_allowed )  {
623
            return {
624
                reason => 'TOO_MANY_ONSITE_CHECKOUTS',
625
                count => $onsite_checkout_count,
626
                max_allowed => $max_onsite_checkouts_allowed,
627
            }
628
        }
629
    }
630
    if ( C4::Context->preference('ConsiderOnSiteCheckoutsAsNormalCheckouts') ) {
631
        my $delta = $switch_onsite_checkout ? 1 : 0;
632
        if ( $checkout_count >= $max_checkouts_allowed + $delta ) {
633
            return {
634
                reason => 'TOO_MANY_CHECKOUTS',
635
                count => $checkout_count,
636
                max_allowed => $max_checkouts_allowed,
637
            };
638
        }
639
    } elsif ( not $onsite_checkout ) {
640
        if ( $checkout_count - $onsite_checkout_count >= $max_checkouts_allowed )  {
641
            return {
642
                reason => 'TOO_MANY_CHECKOUTS',
643
                count => $checkout_count - $onsite_checkout_count,
644
                max_allowed => $max_checkouts_allowed,
645
            };
646
        }
647
    }
648
649
    return;
650
}
651
570
=head2 CanBookBeIssued
652
=head2 CanBookBeIssued
571
653
572
  ( $issuingimpossible, $needsconfirmation, [ $alerts ] ) =  CanBookBeIssued( $patron,
654
  ( $issuingimpossible, $needsconfirmation, [ $alerts ] ) =  CanBookBeIssued( $patron,
(-)a/t/db_dependent/Circulation/TooMany.t (-2 / +232 lines)
Lines 15-21 Link Here
15
# with Koha; if not, see <http://www.gnu.org/licenses>.
15
# with Koha; if not, see <http://www.gnu.org/licenses>.
16
16
17
use Modern::Perl;
17
use Modern::Perl;
18
use Test::More tests => 8;
18
use Test::More tests => 9;
19
use C4::Context;
19
use C4::Context;
20
20
21
use C4::Members;
21
use C4::Members;
Lines 659-665 subtest 'General vs specific rules limit quantity correctly' => sub { Link Here
659
        'We are allowed one from the branch specifically now'
659
        'We are allowed one from the branch specifically now'
660
    );
660
    );
661
661
662
};
663
664
subtest 'itemtype group tests' => sub {
665
    plan tests => 13;
666
667
    t::lib::Mocks::mock_preference('CircControl', 'ItemHomeLibrary');
668
    my $rule = $builder->build({
669
        source => 'Issuingrule',
670
        value => {
671
            categorycode => '*',
672
            itemtype     => '*',
673
            branchcode   => '*',
674
            issuelength  => 1,
675
            firstremind  => 1,        # 1 day of grace
676
            finedays     => 2,        # 2 days of fine per day of overdue
677
            lengthunit   => 'days',
678
        }
679
    });
680
681
    my $parent_itype = $builder->build({
682
        source=>'Itemtype',
683
        value => {
684
            parent_type => undef,
685
            rentalcharge => undef,
686
            rentalcharge_daily => undef,
687
            rentalcharge_hourly => undef,
688
            notforloan => 0,
689
        }
690
    });
691
    my $child_itype_1 = $builder->build({
692
        source=>'Itemtype',
693
        value => {
694
            parent_type => $parent_itype->{itemtype},
695
            rentalcharge => 0,
696
            rentalcharge_daily => 0,
697
            rentalcharge_hourly => 0,
698
            notforloan => 0,
699
        }
700
    });
701
    my $child_itype_2 = $builder->build({
702
        source=>'Itemtype',
703
        value => {
704
            parent_type => $parent_itype->{itemtype},
705
            rentalcharge => 0,
706
            rentalcharge_daily => 0,
707
            rentalcharge_hourly => 0,
708
            notforloan => 0,
709
        }
710
    });
711
712
    my $branch = $builder->build({source => 'Branch',});
713
    my $category = $builder->build({source => 'Category',});
714
    my $patron = $builder->build({
715
        source => 'Borrower',
716
        value => {
717
            categorycode => $category->{categorycode},
718
            branchcode => $branch->{branchcode},
719
        },
720
    });
721
    my $item = $builder->build_sample_item({
722
        homebranch=>$branch->{branchcode},
723
        holdingbranch=>$branch->{branchcode},
724
        itype=>$child_itype_1->{itemtype}
725
    });
726
727
    my $all_iq_rule = $builder->build({
728
        source=>'CirculationRule',
729
        value => {
730
            branchcode   => $branch->{branchcode},
731
            categorycode => $category->{categorycode},
732
            itemtype     => undef,
733
            rule_name    => 'maxissueqty',
734
            rule_value   => 1
735
        }
736
    });
737
    is(
738
        C4::Circulation::TooMany( $patron, $item->biblionumber, $item->unblessed ),
739
        undef,
740
        'Checkout allowed, using all rule of 1'
741
    );
742
743
    #Checkout an item
744
    my $issue = C4::Circulation::AddIssue( $patron, $item->barcode, dt_from_string() );
745
    like( $issue->issue_id, qr|^\d+$|, 'The issue should have been inserted' );
746
    #Patron has 1 checkout of child itype1
747
748
    my $parent_iq_rule = $builder->build({
749
        source=>'CirculationRule',
750
        value => {
751
            branchcode   => $branch->{branchcode},
752
            categorycode => $category->{categorycode},
753
            itemtype     => $parent_itype->{itemtype},
754
            rule_name    => 'maxissueqty',
755
            rule_value   => 2
756
        }
757
    });
758
759
    is(
760
        C4::Circulation::TooMany( $patron, $item->biblionumber, $item->unblessed ),
761
        undef,
762
        'Checkout allowed, using parent type rule of 2'
763
    );
764
765
    my $child1_iq_rule = $builder->build_object({
766
        class=>'Koha::CirculationRules',
767
        value => {
768
            branchcode   => $branch->{branchcode},
769
            categorycode => $category->{categorycode},
770
            itemtype     => $child_itype_1->{itemtype},
771
            rule_name    => 'maxissueqty',
772
            rule_value   => 1
773
        }
774
    });
775
776
    is_deeply(
777
        C4::Circulation::TooMany( $patron, $item->biblionumber, $item->unblessed ),
778
        {
779
            reason => 'TOO_MANY_CHECKOUTS',
780
            count => 1,
781
            max_allowed => 1,
782
        },
783
        'Checkout not allowed, using specific type rule of 1'
784
    );
785
786
    my $item_1 = $builder->build_sample_item({
787
        homebranch=>$branch->{branchcode},
788
        holdingbranch=>$branch->{branchcode},
789
        itype=>$child_itype_2->{itemtype}
790
    });
791
792
    my $child2_iq_rule = $builder->build({
793
        source=>'CirculationRule',
794
        value => {
795
            branchcode   => $branch->{branchcode},
796
            categorycode => $category->{categorycode},
797
            itemtype     => $child_itype_2->{itemtype},
798
            rule_name    => 'maxissueqty',
799
            rule_value   => 3
800
        }
801
    });
802
803
    is(
804
        C4::Circulation::TooMany( $patron, $item_1->biblionumber, $item_1->unblessed ),
805
        undef,
806
        'Checkout allowed'
807
    );
808
809
    #checkout an item
810
    $issue = C4::Circulation::AddIssue( $patron, $item_1->barcode, dt_from_string());
811
    like( $issue->issue_id, qr|^\d+$|, 'the issue should have been inserted' );
812
    #patron has 1 checkout of childitype1 and 1 checkout of childitype2
813
814
    is_deeply(
815
        C4::Circulation::TooMany( $patron, $item->biblionumber, $item->unblessed ),
816
        {
817
            reason => 'TOO_MANY_CHECKOUTS',
818
            count => 2,
819
            max_allowed => 2,
820
        },
821
        'Checkout not allowed, using parent type rule of 2, checkout of sibling itemtype counted'
822
    );
823
824
    my $parent_item = $builder->build_sample_item({
825
        homebranch=>$branch->{branchcode},
826
        holdingbranch=>$branch->{branchcode},
827
        itype=>$parent_itype->{itemtype}
828
    });
829
830
    is_deeply(
831
        C4::Circulation::TooMany( $patron, $parent_item->biblionumber, $parent_item->unblessed ),
832
        {
833
            reason => 'TOO_MANY_CHECKOUTS',
834
            count => 2,
835
            max_allowed => 2,
836
        },
837
        'Checkout not allowed, using parent type rule of 2, checkout of child itemtypes counted'
838
    );
839
840
841
    #increase parent type to greater than specific
842
    my $circ_rule_object = Koha::CirculationRules->find( $parent_iq_rule->{id} );
843
    $circ_rule_object->rule_value(4)->store();
844
845
    is(
846
        C4::Circulation::TooMany( $patron, $item->biblionumber, $item_1->unblessed ),
847
        undef,
848
        'Checkout allowed, using specific type rule of 3'
849
    );
850
851
    my $item_2 = $builder->build_sample_item({
852
        homebranch=>$branch->{branchcode},
853
        holdingbranch=>$branch->{branchcode},
854
        itype=>$child_itype_2->{itemtype}
855
    });
856
    #checkout an item
857
    $issue = C4::Circulation::AddIssue( $patron, $item_2->barcode, dt_from_string(), undef, undef, undef );
858
    like( $issue->issue_id, qr|^\d+$|, 'the issue should have been inserted' );
859
    #patron has 1 checkoout of childitype1 and 2 of childitype2
662
860
861
    is(
862
        C4::Circulation::TooMany( $patron, $item_2->biblionumber, $item_2->unblessed ),
863
        undef,
864
        'Checkout allowed, using specific type rule of 3, checkout of sibling itemtype not counted'
865
    );
866
867
    $child1_iq_rule->rule_value(2)->store(); #Allow 2 checkouts for child type 1
868
869
    my $item_3 = $builder->build_sample_item({
870
        homebranch=>$branch->{branchcode},
871
        holdingbranch=>$branch->{branchcode},
872
        itype=>$child_itype_1->{itemtype}
873
    });
874
    my $item_4 = $builder->build_sample_item({
875
        homebranch=>$branch->{branchcode},
876
        holdingbranch=>$branch->{branchcode},
877
        itype=>$child_itype_2->{itemtype}
878
    });
879
880
    #checkout an item
881
    $issue = C4::Circulation::AddIssue( $patron, $item_4->barcode, dt_from_string(), undef, undef, undef );
882
    like( $issue->issue_id, qr|^\d+$|, 'the issue should have been inserted' );
883
    #patron has 1 checkout of childitype 1 and 3 of childitype2
884
885
    is_deeply(
886
        C4::Circulation::TooMany( $patron, $item_3->biblionumber, $item_3->unblessed ),
887
        {
888
            reason => 'TOO_MANY_CHECKOUTS',
889
            max_allowed => 4,
890
            count => 4,
891
        },
892
        'Checkout not allowed, using specific type rule of 2, checkout of sibling itemtype not counted, but parent rule (4) prevents another'
893
    );
663
894
664
};
895
};
665
896
666
- 

Return to bug 21946