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

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

Return to bug 21946