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

(-)a/Koha/Biblio.pm (+107 lines)
Lines 815-820 sub to_api_mapping { Link Here
815
    };
815
    };
816
}
816
}
817
817
818
=head3 recalls
819
820
    my $recalls = $biblio->recalls;
821
822
Return all active recalls attached to this biblio
823
824
=cut
825
826
sub recalls {
827
    my ( $self ) = @_;
828
    $self->{_recalls} ||= Koha::Recalls->search({ biblionumber => $self->biblionumber, old => undef });
829
    return $self->{_recalls};
830
}
831
832
=head3 can_be_recalled
833
834
    my @items_for_recall = $biblio->can_be_recalled({ borrower => $borrower_object });
835
836
Does biblio-level checks and returns the items attached to this biblio that are available for recall
837
838
=cut
839
840
sub can_be_recalled {
841
    my ( $self, $params ) = @_;
842
843
    return 0 if !( C4::Context->preference('UseRecalls') );
844
845
    my $borrower = $params->{borrower};
846
847
    my $branchcode = C4::Context->userenv->{'branch'};
848
    if ( C4::Context->preference('CircControl') eq 'PatronLibrary' and $borrower ) {
849
        $branchcode = $borrower->branchcode;
850
    }
851
852
    # items are unavailable for recall if they are lost, withdrawn or notforloan
853
    my @items = Koha::Items->search({ biblionumber => $self->biblionumber, itemlost => 0, withdrawn => 0, notforloan => 0 });
854
855
    # if there are no available items at all, no recall can be placed
856
    return 0 if ( scalar @items == 0 );
857
858
    my @itemtypes;
859
    my @itemnumbers;
860
    foreach my $item ( @items ) {
861
        push( @itemtypes, $item->effective_itemtype );
862
        push( @itemnumbers, $item->itemnumber );
863
    }
864
865
    # Check the circulation rule for each relevant itemtype for this biblio
866
    my ( @recalls_allowed, @recalls_per_record, @on_shelf_recalls );
867
    foreach my $itemtype ( @itemtypes ) {
868
        my $rule = Koha::CirculationRules->get_effective_rules({
869
            branchcode => $branchcode,
870
            categorycode => $borrower ? $borrower->categorycode : undef,
871
            itemtype => $itemtype,
872
            rules => [
873
                'recalls_allowed',
874
                'recalls_per_record',
875
                'on_shelf_recalls',
876
            ],
877
        });
878
        push( @recalls_allowed, $rule->{recalls_allowed} ) if $rule;
879
        push( @recalls_per_record, $rule->{recalls_per_record} ) if $rule;
880
        push( @on_shelf_recalls, $rule->{on_shelf_recalls} ) if $rule;
881
    }
882
    my $recalls_allowed = max(@recalls_allowed); # take highest
883
    my $recalls_per_record = max(@recalls_per_record); # take highest
884
    my %on_shelf_recalls_count = ();
885
    foreach my $count ( @on_shelf_recalls ) {
886
        $on_shelf_recalls_count{$count}++;
887
    }
888
    my $on_shelf_recalls = (sort {$on_shelf_recalls_count{$b} <=> $on_shelf_recalls_count{$a}} @on_shelf_recalls)[0]; # take most common
889
890
    # check recalls allowed has been set and is not zero
891
    return 0 if ( $recalls_allowed == 0 || !defined($recalls_allowed) );
892
893
    if ( $borrower ) {
894
        # check borrower has not reached open recalls allowed limit
895
        return 0 if ( $borrower->recalls->count >= $recalls_allowed );
896
897
        # check borrower has not reach open recalls allowed per record limit
898
        return 0 if ( $borrower->recalls({ biblionumber => $self->biblionumber })->count >= $recalls_per_record );
899
900
        # check if any of the items under this biblio are already checked out by this borrower
901
        return 0 if ( Koha::Checkouts->search({ itemnumber => @itemnumbers, borrowernumber => $borrower->borrowernumber })->count > 0 );
902
    }
903
904
    # check item availability
905
    my $checked_out_count = 0;
906
    foreach (@items) {
907
        if ( Koha::Checkouts->search({ itemnumber => $_->itemnumber })->count > 0 ){ $checked_out_count++; }
908
    }
909
910
    # can't recall if on shelf recalls only allowed when all unavailable, but items are still available for checkout
911
    return 0 if ( $on_shelf_recalls == 2 && $checked_out_count < scalar @items );
912
913
    # check if on shelf recalls allowed when any unavailable, but no items have been checked out
914
    # can't recall if no items have been checked out
915
    return 0 if ( $on_shelf_recalls == 0 && $checked_out_count == 0 );
916
917
    # check if on shelf recalls allowed always, but no items have been checked out
918
    # can't recall if no items have been checked out
919
    return 0 if ( $on_shelf_recalls == 1 && $checked_out_count == 0 );
920
921
    # can recall
922
    return @items;
923
}
924
818
=head2 Internal methods
925
=head2 Internal methods
819
926
820
=head3 type
927
=head3 type
(-)a/Koha/Item.pm (+150 lines)
Lines 842-847 sub _after_item_action_hooks { Link Here
842
    }
842
    }
843
}
843
}
844
844
845
=head3 recall
846
847
    my $recall = $item->recall;
848
849
Return the relevant recall for this item
850
851
=cut
852
853
sub recall {
854
    my ( $self ) = @_;
855
    my $recalls = Koha::Recalls->search({ biblionumber => $self->biblionumber, old => undef }, { order_by => { -asc => 'recalldate' } });
856
    foreach my $recall (@$recall_rs) {
857
        if ( $recall->item_level_recall and $recall->itemnumber == $self->itemnumber ){
858
            return $recall;
859
        }
860
    }
861
    # no item-level recall to return, so return earliest biblio-level
862
    # FIXME: eventually this will be based on priority
863
    return $recalls->first;
864
}
865
866
=head3 can_be_recalled
867
868
    if ( $item->can_be_recalled({ borrower => $borrower_object }) ) # do recall
869
870
Does item-level checks and returns if items can be recalled by this borrower
871
872
=cut
873
874
sub can_be_recalled {
875
    my ( $self, $params ) = @_;
876
877
    return 0 if !( C4::Context->preference('UseRecalls') );
878
879
    my $borrower = $params->{borrower};
880
881
    my $branchcode = C4::Context->userenv->{'branch'};
882
    if ( $borrower ) {
883
        $branchcode = C4::Circulation::_GetCircControlBranch( $self->unblessed, $borrower->unblessed );
884
    }
885
886
    # Check the circulation rule for each relevant itemtype for this item
887
    my $rule = Koha::CirculationRules->get_effective_rules({
888
        branchcode => $branchcode,
889
        categorycode => $borrower ? $borrower->categorycode : undef,
890
        itemtype => $self->effective_itemtype,
891
        rules => [
892
            'recalls_allowed',
893
            'recalls_per_record',
894
            'on_shelf_recalls',
895
        ],
896
    });
897
898
    # check recalls allowed has been set and is not zero
899
    return 0 if ( $rule->{recalls_allowed} == 0 || !defined($rule->{recalls_allowed}) );
900
901
    if ( $borrower ) {
902
        # check borrower has not reached open recalls allowed limit
903
        return 0 if ( $borrower->recalls->count >= $rule->{recalls_allowed} );
904
905
        # check borrower has not reach open recalls allowed per record limit
906
        return 0 if ( $borrower->recalls({ biblionumber => $self->biblionumber })->count >= $rule->{recalls_per_record} );
907
    }
908
909
    # check item availability
910
    # items are unavailable for recall if they are lost, withdrawn or notforloan
911
    my @items = Koha::Items->search({ biblionumber => $self->biblionumber, itemlost => 0, withdrawn => 0, notforloan => 0 });
912
913
    # if there are no available items at all, no recall can be placed
914
    return 0 if ( scalar @items == 0 );
915
916
    my $checked_out_count = 0;
917
    foreach (@items) {
918
        if ( Koha::Checkouts->search({ itemnumber => $_->itemnumber })->count > 0 ){ $checked_out_count++; }
919
    }
920
921
    # can't recall if on shelf recalls only allowed when all unavailable, but items are still available for checkout
922
    return 0 if ( $rule->{on_shelf_recalls} == 2 && $checked_out_count < scalar @items );
923
924
    # check if on shelf recalls allowed when any unavailable, but no items have been checked out
925
    # can't recall if no items have been checked out
926
    return 0 if ( $rule->{on_shelf_recalls} == 0 && $checked_out_count == 0 );
927
928
    # check if on shelf recalls allowed always, but no items have been checked out
929
    # can't recall if no items have been checked out
930
    return 0 if ( $rule->{on_shelf_recalls} == 1 && $checked_out_count == 0 );
931
932
    # check if this patron has already recalled this item
933
    return 0 if ( Koha::Recalls->search({ itemnumber => $self->itemnumber, borrowernumber => $borrower->borrowernumber, old => undef })->count > 0 );
934
935
    # check if this patron has already checked out this item
936
    return 0 if ( Koha::Checkouts->search({ itemnumber => $self->itemnumber, borrowernumber => $borrower->borrowernumber })->count > 0 );
937
938
    # check if this patron has already reserved this item
939
    return 0 if ( Koha::Holds->search({ itemnumber => $self->itemnumber, borrowernumber => $borrower->borrowernumber })->count > 0 );
940
941
    # check if this item is not checked out - if not checked out, can't be recalled
942
    return 0 if ( !defined( $self->checkout ) );
943
944
    # check if this item is not for loan, withdrawn or lost
945
    return 0 if ( $self->notforloan != 0 );
946
    return 0 if ( $self->itemlost != 0 );
947
    return 0 if ( $self->withdrawn != 0 );
948
949
    # can recall
950
    return 1;
951
}
952
953
=head3 can_set_waiting_recall
954
955
    if ( $item->can_set_waiting_recall ) { # allocate item as waiting for recall
956
957
Checks item type and branch of circ rules to return whether this item can be used to fill a recall.
958
At this point the item has already been recalled. We are now at the checkin and set waiting stage.
959
960
=cut
961
962
sub can_set_waiting_recall {
963
    my ( $self ) = @_;
964
965
    return 0 if !( C4::Context->preference('UseRecalls') );
966
967
    my $branchcode = $self->holdingbranch;
968
    if ( C4::Context->preference('CircControl') eq 'PickupLibrary' and C4::Context->userenv and C4::Context->userenv->{'branch'} ) {
969
        $branchcode = C4::Context->userenv->{'branch'};
970
    } else {
971
        $branchcode = ( C4::Context->preference('HomeOrHoldingBranch') eq 'homebranch' ) ? $self->homebranch : $self->holdingbranch;
972
    }
973
974
    # Check the circulation rule for each relevant itemtype for this item
975
    my $rule = Koha::CirculationRules->get_effective_rules({
976
        branchcode => $branchcode,
977
        categorycode => undef,
978
        itemtype => $self->effective_itemtype,
979
        rules => [
980
            'recalls_allowed',
981
        ],
982
    });
983
984
    # check recalls allowed has been set and is not zero
985
    return 0 if ( $rule->{recalls_allowed} == 0 || !defined($rule->{recalls_allowed}) );
986
987
    # check if this item is not for loan, withdrawn or lost
988
    return 0 if ( $self->notforloan != 0 );
989
    return 0 if ( $self->itemlost != 0 );
990
    return 0 if ( $self->withdrawn != 0 );
991
992
    # can recall
993
    return 1;
994
}
845
=head3 _type
995
=head3 _type
846
996
847
=cut
997
=cut
(-)a/Koha/Patron.pm (+21 lines)
Lines 1712-1717 sub to_api_mapping { Link Here
1712
    };
1712
    };
1713
}
1713
}
1714
1714
1715
=head3 recalls
1716
1717
    my $recalls = $patron->recalls;
1718
1719
    my $recalls = $patron->recalls({ biblionumber => $biblionumber });
1720
1721
Return the patron's active recalls - total, or on a specific biblio
1722
1723
=cut
1724
1725
sub recalls {
1726
    my ( $self, $params ) = @_;
1727
1728
    my $biblionumber = $params->{biblionumber};
1729
1730
    if ( $biblionumber ) {
1731
        return Koha::Recalls->search({ borrowernumber => $self->borrowernumber, old => undef, biblionumber => $biblionumber });
1732
    }
1733
    return Koha::Recalls->search({ borrowernumber => $self->borrowernumber, old => undef });
1734
}
1735
1715
=head2 Internal methods
1736
=head2 Internal methods
1716
1737
1717
=head3 _type
1738
=head3 _type
(-)a/t/db_dependent/Koha/Biblio.t (-2 / +56 lines)
Lines 17-23 Link Here
17
17
18
use Modern::Perl;
18
use Modern::Perl;
19
19
20
use Test::More tests => 12;
20
use Test::More tests => 13;
21
21
22
use C4::Biblio;
22
use C4::Biblio;
23
use Koha::Database;
23
use Koha::Database;
Lines 569-571 subtest 'subscriptions() tests' => sub { Link Here
569
569
570
    $schema->storage->txn_rollback;
570
    $schema->storage->txn_rollback;
571
};
571
};
572
- 
572
573
subtest 'Recalls tests' => sub {
574
575
    plan tests => 2;
576
577
    $schema->storage->txn_begin;
578
    my $item = $builder->build_sample_item;
579
    my $biblio = $item->biblio;
580
    my $branchcode = $item->holdingbranch;
581
    my $patron1 = $builder->build_object({ class => 'Koha::Patrons', value => { branchcode => $branchcode } });
582
    my $patron2 = $builder->build_object({ class => 'Koha::Patrons', value => { branchcode => $branchcode } });
583
    my $patron3 = $builder->build_object({ class => 'Koha::Patrons', value => { branchcode => $branchcode } });
584
585
    my $recall1 = Koha::Recall->new({
586
        borrowernumber => $patron1->borrowernumber,
587
        recalldate => Koha::DateUtils::dt_from_string,
588
        biblionumber => $biblio->biblionumber,
589
        branchcode => $branchcode,
590
        status => 'R',
591
        itemnumber => $item->itemnumber,
592
        expirationdate => undef,
593
        item_level_recall => 1
594
    })->store;
595
    my $recall2 = Koha::Recall->new({
596
        borrowernumber => $patron2->borrowernumber,
597
        recalldate => Koha::DateUtils::dt_from_string,
598
        biblionumber => $biblio->biblionumber,
599
        branchcode => $branchcode,
600
        status => 'R',
601
        itemnumber => undef,
602
        expirationdate => undef,
603
        item_level_recall => 0
604
    })->store;
605
    my $recall3 = Koha::Recall->new({
606
        borrowernumber => $patron3->borrowernumber,
607
        recalldate => Koha::DateUtils::dt_from_string,
608
        biblionumber => $biblio->biblionumber,
609
        branchcode => $branchcode,
610
        status => 'R',
611
        itemnumber => $item->itemnumber,
612
        expirationdate => undef,
613
        item_level_recall => 1
614
    })->store;
615
616
    my $recalls_count = $biblio->recalls->count;
617
    is( $recalls_count, 3, 'Correctly get number of active recalls for biblio' );
618
619
    $recall1->set_cancelled;
620
    $recall2->set_expired({ location => 'COMMANDLINE' });
621
622
    $recalls_count = $biblio->recalls->count;
623
    is( $recalls_count, 1, 'Correctly get number of active recalls for biblio' );
624
625
    $schema->storage->txn_rollback;
626
};

Return to bug 19532