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

(-)a/Koha/Exceptions.pm (+5 lines)
Lines 15-20 use Exception::Class ( Link Here
15
        isa => 'Koha::Exception',
15
        isa => 'Koha::Exception',
16
        description => 'Same object already exists',
16
        description => 'Same object already exists',
17
    },
17
    },
18
    'Koha::Exceptions::InvalidStatus' => {
19
        isa         => 'Koha::Exception',
20
        description => 'The current status is not valid in context',
21
        fields      => ['invalid_status'],
22
    },
18
    'Koha::Exceptions::ObjectNotFound' => {
23
    'Koha::Exceptions::ObjectNotFound' => {
19
        isa => 'Koha::Exception',
24
        isa => 'Koha::Exception',
20
        description => 'The required object doesn\'t exist',
25
        description => 'The required object doesn\'t exist',
(-)a/Koha/Hold.pm (+116 lines)
Lines 30-35 use Koha::AuthorisedValues; Link Here
30
use Koha::DateUtils qw( dt_from_string );
30
use Koha::DateUtils qw( dt_from_string );
31
use Koha::Patrons;
31
use Koha::Patrons;
32
use Koha::Biblios;
32
use Koha::Biblios;
33
use Koha::Hold::CancellationRequests;
33
use Koha::Items;
34
use Koha::Items;
34
use Koha::Libraries;
35
use Koha::Libraries;
35
use Koha::Old::Holds;
36
use Koha::Old::Holds;
Lines 38-43 use Koha::Plugins; Link Here
38
39
39
use Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue;
40
use Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue;
40
41
42
use Koha::Exceptions;
41
use Koha::Exceptions::Hold;
43
use Koha::Exceptions::Hold;
42
44
43
use base qw(Koha::Object);
45
use base qw(Koha::Object);
Lines 416-421 sub is_cancelable_from_opac { Link Here
416
    return 0; # if ->is_in_transit or if ->is_waiting or ->is_in_processing
418
    return 0; # if ->is_in_transit or if ->is_waiting or ->is_in_processing
417
}
419
}
418
420
421
=head3 cancellation_requestable_from_opac
422
423
    if ( $hold->cancellation_requestable_from_opac ) { ... }
424
425
Returns a I<boolean> representing if a cancellation request can be placed on the hold
426
from the OPAC. It targets holds that cannot be cancelled from the OPAC (see the
427
B<is_cancelable_from_opac> method above), but for which circulation rules allow
428
requesting cancellation.
429
430
Throws a B<Koha::Exceptions::InvalidStatus> exception with the following I<invalid_status>
431
values:
432
433
=over 4
434
435
=item B<'hold_not_waiting'>: the hold is expected to be waiting and it is not.
436
437
=item B<'no_item_linked'>: the waiting hold doesn't have an item properly linked.
438
439
=back
440
441
=cut
442
443
sub cancellation_requestable_from_opac {
444
    my ( $self ) = @_;
445
446
    Koha::Exceptions::InvalidStatus->throw( invalid_status => 'hold_not_waiting' )
447
      unless $self->is_waiting;
448
449
    my $item = $self->item;
450
451
    Koha::Exceptions::InvalidStatus->throw( invalid_status => 'no_item_linked' )
452
      unless $item;
453
454
    my $patron = $self->patron;
455
456
    my $controlbranch = $patron->branchcode;
457
458
    if ( C4::Context->preference('ReservesControlBranch') eq 'ItemHomeLibrary' ) {
459
        $controlbranch = $item->homebranch;
460
    }
461
462
    return Koha::CirculationRules->get_effective_rule(
463
        {
464
            categorycode => $patron->categorycode,
465
            itemtype     => $item->itype,
466
            branchcode   => $controlbranch,
467
            rule_name    => 'waiting_hold_cancellation',
468
        }
469
    )->rule_value ? 1 : 0;
470
}
471
472
=head3 owner_cancellation_request
473
474
    if ( $hold->owner_cancellation_request ) { ... }
475
476
Returns a I<Koha::Hold::CancellationRequest> if the owner has placed one.
477
478
=cut
479
480
sub owner_cancellation_request {
481
    my ($self) = @_;
482
483
    my ($owner_request) = $self->cancellation_requests->search(
484
        { requester_id => $self->borrowernumber } )->as_list;
485
486
    return $owner_request;
487
}
488
419
=head3 is_at_destination
489
=head3 is_at_destination
420
490
421
Returns true if hold is waiting
491
Returns true if hold is waiting
Lines 525-530 sub is_suspended { Link Here
525
    return $self->suspend();
595
    return $self->suspend();
526
}
596
}
527
597
598
=head3 add_cancellation_request
599
600
    my $cancellation_request = $hold->add_cancellation_request(
601
        {
602
          [ requester_id  => $patron->id,
603
            creation_date => $creation_date,
604
            source        => 'owner', ]
605
        }
606
    );
607
608
Adds a cancellation request to the hold. Returns the generated
609
I<Koha::Hold::CancellationRequest> object.
610
611
=cut
612
613
sub add_cancellation_request {
614
    my ( $self, $params ) = @_;
615
616
    my $request = Koha::Hold::CancellationRequest->new(
617
        {   hold_id      => $self->id,
618
            requester_id => $params->{requester_id},
619
            ( $params->{creation_date} ? ( creation_date => $params->{creation_date} ) : () ),
620
            source => $params->{source},
621
        }
622
    )->store;
623
624
    $request->discard_changes;
625
626
    return $request;
627
}
628
629
=head3 cancellation_requests
630
631
    my $cancellation_requests = $hold->cancellation_requests;
632
633
Returns related a I<Koha::Hold::CancellationRequests> resultset.
634
635
=cut
636
637
sub cancellation_requests {
638
    my ($self) = @_;
639
640
    return Koha::Hold::CancellationRequests->search( { hold_id => $self->id } );
641
}
528
642
529
=head3 cancel
643
=head3 cancel
530
644
Lines 584-589 sub cancel { Link Here
584
                }
698
                }
585
            }
699
            }
586
700
701
            $self->cancellation_requests->filter_by_current->accept;
702
587
            my $old_me = $self->_move_to_old;
703
            my $old_me = $self->_move_to_old;
588
704
589
            Koha::Plugins->call(
705
            Koha::Plugins->call(
(-)a/t/db_dependent/Koha/Hold.t (-3 / +235 lines)
Lines 19-25 Link Here
19
19
20
use Modern::Perl;
20
use Modern::Perl;
21
21
22
use Test::More tests => 6;
22
use Test::More tests => 8;
23
23
24
use Test::Exception;
24
use Test::Exception;
25
use Test::MockModule;
25
use Test::MockModule;
Lines 495-501 subtest 'is_pickup_location_valid() tests' => sub { Link Here
495
495
496
subtest 'cancel() tests' => sub {
496
subtest 'cancel() tests' => sub {
497
497
498
    plan tests => 6;
498
    plan tests => 7;
499
499
500
    $schema->storage->txn_begin;
500
    $schema->storage->txn_begin;
501
501
Lines 614-619 subtest 'cancel() tests' => sub { Link Here
614
        )->cancel({ skip_holds_queue => 0 });
614
        )->cancel({ skip_holds_queue => 0 });
615
    };
615
    };
616
616
617
    subtest 'cancellation_requests handling' => sub {
618
619
        plan tests => 2;
620
621
        my $item = $builder->build_sample_item;
622
        my $hold = $builder->build_object(
623
            {
624
                class => 'Koha::Holds',
625
                value => {
626
                    biblionumber => $item->biblionumber,
627
                    itemnumber   => $item->itemnumber,
628
                    found        => 'W',
629
                }
630
            }
631
        );
632
633
        my $request = $hold->add_cancellation_request(
634
            {
635
                requester_id  => $hold->borrowernumber,
636
            }
637
        );
638
639
        # limit noise
640
        t::lib::Mocks::mock_preference( 'ExpireReservesMaxPickUpDelayCharge', undef );
641
        t::lib::Mocks::mock_preference( 'RealTimeHoldsQueue', 0 );
642
643
        is( $request->status, 'requested' );
644
645
        $hold->cancel;
646
647
        $request->discard_changes;
648
649
        is( $request->status, 'accepted', 'When holds are cancellaed, cancellation requests are implicitly accepted' );
650
    };
651
617
    $schema->storage->txn_rollback;
652
    $schema->storage->txn_rollback;
618
};
653
};
619
654
Lines 656-658 subtest 'suspend_hold() and resume() tests' => sub { Link Here
656
691
657
    $schema->storage->txn_rollback;
692
    $schema->storage->txn_rollback;
658
};
693
};
659
- 
694
695
subtest 'cancellation_requests() and add_cancellation_request() tests' => sub {
696
697
    plan tests => 8;
698
699
    $schema->storage->txn_begin;
700
701
    t::lib::Mocks::mock_preference( 'RealTimeHoldsQueue', 0 );
702
703
    my $hold = $builder->build_object( { class => 'Koha::Holds', } );
704
705
    is( $hold->cancellation_requests->count, 0 );
706
707
    # Add two cancellation requests
708
    my $request_1 = $hold->add_cancellation_request;
709
710
    is( $request_1->requester_id, undef );
711
    is( $request_1->status, 'requested', 'Default value set' );
712
    isnt( $request_1->creation_date, undef );
713
714
    my $requester     = $builder->build_object( { class => 'Koha::Patrons' } );
715
    my $creation_date = '2021-06-25 14:05:35';
716
717
    my $request_2 = $hold->add_cancellation_request(
718
        {
719
            requester_id  => $requester->id,
720
            creation_date => $creation_date,
721
        }
722
    );
723
724
    is( $request_2->requester_id, $requester->id, 'Passed requester_id set' );
725
    is( $request_2->creation_date, $creation_date, 'Passed creation_date set' );
726
727
    is( $hold->cancellation_requests->count, 2 );
728
729
    subtest 'owner_cancellation_request() tests' => sub {
730
731
        plan tests => 3;
732
733
        my $owner_request = $hold->owner_cancellation_request;
734
735
        ok( !$owner_request );
736
737
        $hold->add_cancellation_request({ requester_id => $hold->borrowernumber });
738
739
        $owner_request = $hold->owner_cancellation_request;
740
741
        is( ref($owner_request), 'Koha::Hold::CancellationRequest' );
742
        is( $owner_request->requester_id, $hold->borrowernumber );
743
    };
744
745
    $schema->storage->txn_rollback;
746
};
747
748
subtest 'cancellation_requestable_from_opac() tests' => sub {
749
750
    plan tests => 5;
751
752
    $schema->storage->txn_begin;
753
754
    my $category =
755
      $builder->build_object( { class => 'Koha::Patron::Categories' } );
756
    my $item_home_library =
757
      $builder->build_object( { class => 'Koha::Libraries' } );
758
    my $patron_home_library =
759
      $builder->build_object( { class => 'Koha::Libraries' } );
760
761
    my $item =
762
      $builder->build_sample_item( { library => $item_home_library->id } );
763
    my $patron = $builder->build_object(
764
        {
765
            class => 'Koha::Patrons',
766
            value => { branchcode => $patron_home_library->id }
767
        }
768
    );
769
770
    subtest 'Exception cases' => sub {
771
772
        plan tests => 4;
773
774
        my $hold = $builder->build_object(
775
            {
776
                class => 'Koha::Holds',
777
                value => {
778
                    itemnumber     => undef,
779
                    found          => undef,
780
                    borrowernumber => $patron->id
781
                }
782
            }
783
        );
784
785
        throws_ok { $hold->cancellation_requestable_from_opac; }
786
        'Koha::Exceptions::InvalidStatus',
787
          'Exception thrown because hold is not waiting';
788
789
        is( $@->invalid_status, 'hold_not_waiting' );
790
791
        $hold = $builder->build_object(
792
            {
793
                class => 'Koha::Holds',
794
                value => {
795
                    itemnumber     => undef,
796
                    found          => 'W',
797
                    borrowernumber => $patron->id
798
                }
799
            }
800
        );
801
802
        throws_ok { $hold->cancellation_requestable_from_opac; }
803
        'Koha::Exceptions::InvalidStatus',
804
          'Exception thrown because waiting hold has no item linked';
805
806
        is( $@->invalid_status, 'no_item_linked' );
807
    };
808
809
    # set default rule to enabled
810
    Koha::CirculationRules->set_rule(
811
        {
812
            categorycode => '*',
813
            itemtype     => '*',
814
            branchcode   => '*',
815
            rule_name    => 'waiting_hold_cancellation',
816
            rule_value   => 1,
817
        }
818
    );
819
820
    my $hold = $builder->build_object(
821
        {
822
            class => 'Koha::Holds',
823
            value => {
824
                itemnumber     => $item->id,
825
                found          => 'W',
826
                borrowernumber => $patron->id
827
            }
828
        }
829
    );
830
831
    t::lib::Mocks::mock_preference( 'ReservesControlBranch',
832
        'ItemHomeLibrary' );
833
834
    Koha::CirculationRules->set_rule(
835
        {
836
            categorycode => $patron->categorycode,
837
            itemtype     => $item->itype,
838
            branchcode   => $item->homebranch,
839
            rule_name    => 'waiting_hold_cancellation',
840
            rule_value   => 0,
841
        }
842
    );
843
844
    ok( !$hold->cancellation_requestable_from_opac );
845
846
    Koha::CirculationRules->set_rule(
847
        {
848
            categorycode => $patron->categorycode,
849
            itemtype     => $item->itype,
850
            branchcode   => $item->homebranch,
851
            rule_name    => 'waiting_hold_cancellation',
852
            rule_value   => 1,
853
        }
854
    );
855
856
    ok(
857
        $hold->cancellation_requestable_from_opac,
858
        'Make sure it is picking the right circulation rule'
859
    );
860
861
    t::lib::Mocks::mock_preference( 'ReservesControlBranch', 'PatronLibrary' );
862
863
    Koha::CirculationRules->set_rule(
864
        {
865
            categorycode => $patron->categorycode,
866
            itemtype     => $item->itype,
867
            branchcode   => $patron->branchcode,
868
            rule_name    => 'waiting_hold_cancellation',
869
            rule_value   => 0,
870
        }
871
    );
872
873
    ok( !$hold->cancellation_requestable_from_opac );
874
875
    Koha::CirculationRules->set_rule(
876
        {
877
            categorycode => $patron->categorycode,
878
            itemtype     => $item->itype,
879
            branchcode   => $patron->branchcode,
880
            rule_name    => 'waiting_hold_cancellation',
881
            rule_value   => 1,
882
        }
883
    );
884
885
    ok(
886
        $hold->cancellation_requestable_from_opac,
887
        'Make sure it is picking the right circulation rule'
888
    );
889
890
    $schema->storage->txn_rollback;
891
};

Return to bug 22456