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

(-)a/C4/Reserves.pm (-3 / +37 lines)
Lines 1026-1031 sub CancelExpiredReserves { Link Here
1026
        if ( defined($hold->found) && $hold->found eq 'W' ) {
1026
        if ( defined($hold->found) && $hold->found eq 'W' ) {
1027
            $cancel_params->{charge_cancel_fee} = 1;
1027
            $cancel_params->{charge_cancel_fee} = 1;
1028
        }
1028
        }
1029
        $cancel_params->{autofill} = C4::Context->preference('ExpireReservesAutoFill');
1029
        $hold->cancel( $cancel_params );
1030
        $hold->cancel( $cancel_params );
1030
    }
1031
    }
1031
}
1032
}
Lines 1192-1198 sub ModReserveStatus { Link Here
1192
1193
1193
=head2 ModReserveAffect
1194
=head2 ModReserveAffect
1194
1195
1195
  &ModReserveAffect($itemnumber,$borrowernumber,$diffBranchSend,$reserve_id, $desk_id);
1196
  &ModReserveAffect($itemnumber,$borrowernumber,$diffBranchSend,$reserve_id, $desk_id, $notify_library);
1196
1197
1197
This function affect an item and a status for a given reserve, either fetched directly
1198
This function affect an item and a status for a given reserve, either fetched directly
1198
by record_id, or by borrowernumber and itemnumber or biblionumber. If only biblionumber
1199
by record_id, or by borrowernumber and itemnumber or biblionumber. If only biblionumber
Lines 1208-1214 This function also removes any entry of the hold in holds queue table. Link Here
1208
=cut
1209
=cut
1209
1210
1210
sub ModReserveAffect {
1211
sub ModReserveAffect {
1211
    my ( $itemnumber, $borrowernumber, $transferToDo, $reserve_id, $desk_id ) = @_;
1212
    my ( $itemnumber, $borrowernumber, $transferToDo, $reserve_id, $desk_id, $notify_library ) = @_;
1212
    my $dbh = C4::Context->dbh;
1213
    my $dbh = C4::Context->dbh;
1213
1214
1214
    # we want to attach $itemnumber to $borrowernumber, find the biblionumber
1215
    # we want to attach $itemnumber to $borrowernumber, find the biblionumber
Lines 1242-1248 sub ModReserveAffect { Link Here
1242
        $hold->set_processing();
1243
        $hold->set_processing();
1243
    } else {
1244
    } else {
1244
        $hold->set_waiting($desk_id);
1245
        $hold->set_waiting($desk_id);
1245
        _koha_notify_reserve( $hold->reserve_id ) unless $already_on_shelf;
1246
        _koha_notify_reserve( $hold->reserve_id, $notify_library ) unless $already_on_shelf;
1246
        # Complete transfer if one exists
1247
        # Complete transfer if one exists
1247
        my $transfer = $hold->item->get_transfer;
1248
        my $transfer = $hold->item->get_transfer;
1248
        $transfer->receive if $transfer;
1249
        $transfer->receive if $transfer;
Lines 1872-1877 The following tables are availalbe witin the notice: Link Here
1872
1873
1873
sub _koha_notify_reserve {
1874
sub _koha_notify_reserve {
1874
    my $reserve_id = shift;
1875
    my $reserve_id = shift;
1876
    my $notify_library = shift;
1877
1875
    my $hold = Koha::Holds->find($reserve_id);
1878
    my $hold = Koha::Holds->find($reserve_id);
1876
    my $borrowernumber = $hold->borrowernumber;
1879
    my $borrowernumber = $hold->borrowernumber;
1877
1880
Lines 1939-1944 sub _koha_notify_reserve { Link Here
1939
        &$send_notification('print', 'HOLD');
1942
        &$send_notification('print', 'HOLD');
1940
    }
1943
    }
1941
1944
1945
    if ($notify_library) {
1946
        my $letter = C4::Letters::GetPreparedLetter(
1947
            module      => 'reserves',
1948
            letter_code => 'HOLD_CHANGED',
1949
            branchcode  => $hold->branchcode,
1950
            substitute  => { today => output_pref( dt_from_string ) },
1951
            tables      => {
1952
                'branches'    => $library,
1953
                'borrowers'   => $patron->unblessed,
1954
                'biblio'      => $hold->biblionumber,
1955
                'biblioitems' => $hold->biblionumber,
1956
                'reserves'    => $hold->unblessed,
1957
                'items'       => $hold->itemnumber,
1958
            },
1959
        );
1960
1961
        my $email =
1962
             C4::Context->preference('ExpireReservesAutoFillEmail')
1963
          || $library->{branchemail}
1964
          || C4::Context->preference('KohaAdminEmailAddress');
1965
1966
        C4::Letters::EnqueueLetter(
1967
            {
1968
                letter                 => $letter,
1969
                borrowernumber         => $borrowernumber,
1970
                message_transport_type => 'email',
1971
                from_address           => $email,
1972
                to_address             => $email,
1973
            }
1974
        );
1975
    }
1942
}
1976
}
1943
1977
1944
=head2 _ShiftPriority
1978
=head2 _ShiftPriority
(-)a/Koha/Hold.pm (-2 / +19 lines)
Lines 22-30 use Modern::Perl; Link Here
22
22
23
use List::MoreUtils qw( any );
23
use List::MoreUtils qw( any );
24
24
25
use C4::Context;
25
use C4::Context qw(preference);
26
use C4::Letters qw( GetPreparedLetter EnqueueLetter );
26
use C4::Letters qw( GetPreparedLetter EnqueueLetter );
27
use C4::Log qw( logaction );
27
use C4::Log qw( logaction );
28
use C4::Reserves;
28
29
29
use Koha::AuthorisedValues;
30
use Koha::AuthorisedValues;
30
use Koha::DateUtils qw( dt_from_string );
31
use Koha::DateUtils qw( dt_from_string );
Lines 547-552 Cancel a hold: Link Here
547
548
548
sub cancel {
549
sub cancel {
549
    my ( $self, $params ) = @_;
550
    my ( $self, $params ) = @_;
551
<<<<<<< HEAD
552
=======
553
554
    my $autofill_next = $params->{autofill} && $self->itemnumber && $self->found && $self->found eq 'W';
555
556
>>>>>>> Bug 14364: Allow automatically canceled expired waiting holds to fill the next hold
550
    $self->_result->result_source->schema->txn_do(
557
    $self->_result->result_source->schema->txn_do(
551
        sub {
558
        sub {
552
            my $patron = $self->patron;
559
            my $patron = $self->patron;
Lines 599-605 sub cancel { Link Here
599
                if $patron->privacy == 2;
606
                if $patron->privacy == 2;
600
607
601
            $self->SUPER::delete(); # Do not add a DELETE log
608
            $self->SUPER::delete(); # Do not add a DELETE log
602
603
            # now fix the priority on the others....
609
            # now fix the priority on the others....
604
            C4::Reserves::_FixPriority({ biblionumber => $self->biblionumber });
610
            C4::Reserves::_FixPriority({ biblionumber => $self->biblionumber });
605
611
Lines 630-635 sub cancel { Link Here
630
            ) unless $params->{skip_holds_queue} or !C4::Context->preference('RealTimeHoldsQueue');
636
            ) unless $params->{skip_holds_queue} or !C4::Context->preference('RealTimeHoldsQueue');
631
        }
637
        }
632
    );
638
    );
639
640
    if ($autofill_next) {
641
        my ( undef, $next_hold ) = C4::Reserves::CheckReserves( $self->itemnumber );
642
        if ($next_hold) {
643
            my $is_transfer = $self->branchcode ne $next_hold->{branchcode};
644
645
            C4::Reserves::ModReserveAffect( $self->itemnumber, $self->borrowernumber, $is_transfer, $next_hold->{reserve_id}, $self->desk_id, $autofill_next );
646
            C4::Reserves::ModItemTransfer( $self->itemnumber, $self->branchcode, $next_hold->{branchcode}, "Reserve" ) if $is_transfer;
647
        }
648
    }
649
633
    return $self;
650
    return $self;
634
}
651
}
635
652
(-)a/installer/data/mysql/atomicupdate/bug_14364.perl (+22 lines)
Line 0 Link Here
1
$DBversion = 'XXX'; # will be replaced by the RM
2
if( CheckVersion( $DBversion ) ) {
3
    $dbh->do(q{
4
        INSERT IGNORE INTO systempreferences ( `variable`, `value`, `options`, `explanation`, `type` ) VALUES
5
        ('ExpireReservesAutoFill','0',NULL,'Automatically fill the next hold with a automatically canceled expired waiting hold.','YesNo'),
6
        ('ExpireReservesAutoFillEmail','', NULL,'If ExpireReservesAutoFill and an email is defined here, the email notification for the change in the hold will be sent to this address.','Free');
7
    });
8
9
    $dbh->do(q{
10
        INSERT IGNORE INTO letter(module,code,branchcode,name,is_html,title,content,message_transport_type)
11
        VALUES ( 'reserves', 'HOLD_CHANGED', '', 'Canceled Hold Available for Different Patron', '0', 'Canceled Hold Available for Different Patron', 'The patron picking up <<biblio.title>> (<<items.barcode>>) has changed to <<borrowers.firstname>> <<borrowers.surname>> (<<borrowers.cardnumber>>).
12
13
Please update the hold information for this item.
14
15
Title: <<biblio.title>>
16
Author: <<biblio.author>>
17
Copy: <<items.copynumber>>
18
Pickup location: <<branches.branchname>>', 'email');
19
    });
20
21
    NewVersion( $DBversion, 14364, "Allow automatically canceled expired waiting holds to fill the next hold");
22
}
(-)a/installer/data/mysql/mandatory/sysprefs.sql (+2 lines)
Lines 214-219 INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` Link Here
214
('EnhancedMessagingPreferences','1','','If ON, allows patrons to select to receive additional messages about items due or nearly due.','YesNo'),
214
('EnhancedMessagingPreferences','1','','If ON, allows patrons to select to receive additional messages about items due or nearly due.','YesNo'),
215
('EnhancedMessagingPreferencesOPAC', '1', NULL, 'If ON, show patrons messaging setting on the OPAC.', 'YesNo'),
215
('EnhancedMessagingPreferencesOPAC', '1', NULL, 'If ON, show patrons messaging setting on the OPAC.', 'YesNo'),
216
('expandedSearchOption','0',NULL,'If ON, set advanced search to be expanded by default','YesNo'),
216
('expandedSearchOption','0',NULL,'If ON, set advanced search to be expanded by default','YesNo'),
217
('ExpireReservesAutoFill','0',NULL,'Automatically fill the next hold with a automatically canceled expired waiting hold.','YesNo'),
218
('ExpireReservesAutoFillEmail','', NULL,'If ExpireReservesAutoFill and an email is defined here, the email notification for the change in the hold will be sent to this address.','Free'),
217
('ExpireReservesMaxPickUpDelay','0','','Enabling this allows holds to expire automatically if they have not been picked by within the time period specified in ReservesMaxPickUpDelay','YesNo'),
219
('ExpireReservesMaxPickUpDelay','0','','Enabling this allows holds to expire automatically if they have not been picked by within the time period specified in ReservesMaxPickUpDelay','YesNo'),
218
('ExpireReservesMaxPickUpDelayCharge','0',NULL,'If ExpireReservesMaxPickUpDelay is enabled, and this field has a non-zero value, than a borrower whose waiting hold has expired will be charged this amount.','free'),
220
('ExpireReservesMaxPickUpDelayCharge','0',NULL,'If ExpireReservesMaxPickUpDelay is enabled, and this field has a non-zero value, than a borrower whose waiting hold has expired will be charged this amount.','free'),
219
('ExpireReservesOnHolidays', '1', NULL, 'If false, reserves at a library will not be canceled on days the library is not open.', 'YesNo'),
221
('ExpireReservesOnHolidays', '1', NULL, 'If false, reserves at a library will not be canceled on days the library is not open.', 'YesNo'),
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref (-1 / +11 lines)
Lines 762-768 Circulation: Link Here
762
              choices:
762
              choices:
763
                  1: Allow
763
                  1: Allow
764
                  0: "Don't allow"
764
                  0: "Don't allow"
765
            - 'holds to expire automatically if they have not been picked by within the time period specified in the <a href="/cgi-bin/koha/admin/preferences.pl?op=search&searchfield=ReservesMaxPickUpDelay">ReservesMaxPickUpDelay</a> system preference.<br><strong>NOTE:</strong> This system preference requires the <code>misc/cronjobs/holds/cancel_expired_holds.pl</code> cronjob. Ask your system administrator to schedule it.'
765
            - 'holds to expire automatically if they have not been picked by within the time period specified in the <a href="/cgi-bin/koha/admin/preferences.pl?op=search&searchfield=ReservesMaxPickUpDelay">ReservesMaxPickUpDelay</a> system preference.'
766
            - '<br><strong>NOTE:</strong> This system preference requires the <code>misc/cronjobs/holds/cancel_expired_holds.pl</code> cronjob. Ask your system administrator to schedule it.<br>'
767
            - pref: ExpireReservesAutoFill
768
              choices:
769
                  1: "Do"
770
                  0: "Don't"
771
            - automatically fill the next hold using the item. If enabled, an email notification will be sent to either the email address defined in ExpireReservesAutoFillEmail, the item's holding library's email address, or the email address defined in KohaAdminEmailAddress in that order.
772
        -
773
            - Send email notification of the new hold filled with a canceled item to
774
            - pref: ExpireReservesAutoFillEmail
775
            - . If no address is defined here, the email will be sent to either the item's holding library or the email address defined in KohaAdminEmailAddress in that order.
766
        -
776
        -
767
            - If using <a href="/cgi-bin/koha/admin/preferences.pl?op=search&searchfield=ExpireReservesMaxPickUpDelay">ExpireReservesMaxPickUpDelay</a>, charge a patron who allows their waiting hold to expire a fee of
777
            - If using <a href="/cgi-bin/koha/admin/preferences.pl?op=search&searchfield=ExpireReservesMaxPickUpDelay">ExpireReservesMaxPickUpDelay</a>, charge a patron who allows their waiting hold to expire a fee of
768
            - pref: ExpireReservesMaxPickUpDelayCharge
778
            - pref: ExpireReservesMaxPickUpDelayCharge
(-)a/t/db_dependent/Holds/ExpireReservesAutoFill.t (-1 / +190 lines)
Line 0 Link Here
0
- 
1
#!/usr/bin/perl
2
3
use Modern::Perl;
4
5
use Test::More tests => 3;
6
7
use t::lib::Mocks;
8
use t::lib::TestBuilder;
9
10
use MARC::Record;
11
12
use C4::Context;
13
use C4::Biblio;
14
use C4::Items;
15
use Koha::Database;
16
use Koha::Holds;
17
18
BEGIN {
19
    use FindBin;
20
    use lib $FindBin::Bin;
21
    use_ok('C4::Reserves');
22
}
23
24
my $schema = Koha::Database->new->schema;
25
$schema->storage->txn_begin;
26
27
my $builder = t::lib::TestBuilder->new();
28
my $dbh     = C4::Context->dbh;
29
30
# Create two random branches
31
my $library_1 = $builder->build({ source => 'Branch' })->{ branchcode };
32
my $library_2 = $builder->build({ source => 'Branch' })->{ branchcode };
33
34
my $biblio = $builder->build_sample_biblio({ itemtype => 'DUMMY' });
35
my $biblionumber = $biblio->id;
36
37
# Create item instance for testing.
38
my $itemnumber = $builder->build_sample_item({ library => $library_1, biblionumber => $biblio->biblionumber })->itemnumber;
39
40
my $patron_1 = $builder->build( { source => 'Borrower' } );
41
my $patron_2 = $builder->build( { source => 'Borrower' } );
42
my $patron_3 = $builder->build( { source => 'Borrower' } );
43
44
subtest 'Test automatically canceled expired waiting holds to fill the next hold, without a transfer' => sub {
45
    plan tests => 10;
46
47
    $dbh->do('DELETE FROM reserves');
48
    $dbh->do('DELETE FROM message_queue');
49
50
    # Add a hold on the item for each of our patrons
51
    my $hold_1 = Koha::Hold->new(
52
        {
53
            priority       => 0,
54
            borrowernumber => $patron_1->{borrowernumber},
55
            branchcode     => $library_1,
56
            biblionumber   => $biblionumber,
57
            itemnumber     => $itemnumber,
58
            found          => 'W',
59
            reservedate    => '1900-01-01',
60
            waitingdate    => '1900-01-01',
61
            expirationdate => '1900-01-01',
62
            lowestPriority => 0,
63
            suspend        => 0,
64
        }
65
    )->store();
66
    my $hold_2 = Koha::Hold->new(
67
        {
68
            priority       => 1,
69
            borrowernumber => $patron_2->{borrowernumber},
70
            branchcode     => $library_1,
71
            biblionumber   => $biblionumber,
72
            itemnumber     => $itemnumber,
73
            reservedate    => '1900-01-01',
74
            expirationdate => '9999-01-01',
75
            lowestPriority => 0,
76
            suspend        => 0,
77
        }
78
    )->store();
79
    my $hold_3 = Koha::Hold->new(
80
        {
81
            priority       => 2,
82
            borrowernumber => $patron_2->{borrowernumber},
83
            branchcode     => $library_1,
84
            biblionumber   => $biblionumber,
85
            itemnumber     => $itemnumber,
86
            reservedate    => '1900-01-01',
87
            expirationdate => '9999-01-01',
88
            lowestPriority => 0,
89
            suspend        => 0,
90
        }
91
    )->store();
92
93
    # Test CancelExpiredReserves
94
    t::lib::Mocks::mock_preference( 'ExpireReservesMaxPickUpDelay', 1 );
95
    t::lib::Mocks::mock_preference( 'ReservesMaxPickUpDelay',       1 );
96
    t::lib::Mocks::mock_preference( 'ExpireReservesOnHolidays',     1 );
97
    t::lib::Mocks::mock_preference( 'ExpireReservesAutoFill',       1 );
98
    t::lib::Mocks::mock_preference( 'ExpireReservesAutoFillEmail',
99
        'kyle@example.com' );
100
101
    CancelExpiredReserves();
102
103
    my @holds = Koha::Holds->search( {}, { order_by => 'priority' } );
104
    $hold_2 = $holds[0];
105
    $hold_3 = $holds[1];
106
107
    is( @holds,            2,   'Found 2 holds' );
108
    is( $hold_2->priority, 0,   'Next hold in line now has priority of 0' );
109
    is( $hold_2->found,    'W', 'Next hold in line is now set to waiting' );
110
111
    my @messages = $schema->resultset('MessageQueue')
112
      ->search( { letter_code => 'HOLD_CHANGED' } );
113
    is( @messages, 1, 'Found 1 message in the message queue' );
114
    is( $messages[0]->to_address, 'kyle@example.com', 'Message sent to correct email address' );
115
116
    $hold_2->expirationdate('1900-01-01')->store();
117
118
    CancelExpiredReserves();
119
120
    @holds = Koha::Holds->search( {}, { order_by => 'priority' } );
121
    $hold_3 = $holds[0];
122
123
    is( @holds,            1,   'Found 1 hold' );
124
    is( $hold_3->priority, 0,   'Next hold in line now has priority of 0' );
125
    is( $hold_3->found,    'W', 'Next hold in line is now set to waiting' );
126
127
    @messages = $schema->resultset('MessageQueue')
128
      ->search( { letter_code => 'HOLD_CHANGED' } );
129
    is( @messages, 2, 'Found 2 messages in the message queue' );
130
    is( $messages[0]->to_address, 'kyle@example.com', 'Message sent to correct email address' );
131
};
132
133
subtest 'Test automatically canceled expired waiting holds to fill the next hold, with a transfer' => sub {
134
    plan tests => 5;
135
136
    $dbh->do('DELETE FROM reserves');
137
    $dbh->do('DELETE FROM message_queue');
138
139
    # Add a hold on the item for each of our patrons
140
    my $hold_1 = Koha::Hold->new(
141
        {
142
            priority       => 0,
143
            borrowernumber => $patron_1->{borrowernumber},
144
            branchcode     => $library_1,
145
            biblionumber   => $biblionumber,
146
            itemnumber     => $itemnumber,
147
            found          => 'W',
148
            reservedate    => '1900-01-01',
149
            waitingdate    => '1900-01-01',
150
            expirationdate => '1900-01-01',
151
            lowestPriority => 0,
152
            suspend        => 0,
153
        }
154
    )->store();
155
    my $hold_2 = Koha::Hold->new(
156
        {
157
            priority       => 1,
158
            borrowernumber => $patron_2->{borrowernumber},
159
            branchcode     => $library_2,
160
            biblionumber   => $biblionumber,
161
            itemnumber     => $itemnumber,
162
            reservedate    => '1900-01-01',
163
            expirationdate => '9999-01-01',
164
            lowestPriority => 0,
165
            suspend        => 0,
166
        }
167
    )->store();
168
169
    # Test CancelExpiredReserves
170
    t::lib::Mocks::mock_preference( 'ExpireReservesMaxPickUpDelay', 1 );
171
    t::lib::Mocks::mock_preference( 'ReservesMaxPickUpDelay',       1 );
172
    t::lib::Mocks::mock_preference( 'ExpireReservesOnHolidays',     1 );
173
    t::lib::Mocks::mock_preference( 'ExpireReservesAutoFill',       1 );
174
    t::lib::Mocks::mock_preference( 'ExpireReservesAutoFillEmail',
175
        'kyle@example.com' );
176
177
    CancelExpiredReserves();
178
179
    my @holds = Koha::Holds->search( {}, { order_by => 'priority' } );
180
    $hold_2 = $holds[0];
181
182
    is( @holds,            1,   'Found 1 hold' );
183
    is( $hold_2->priority, 0,   'Next hold in line now has priority of 0' );
184
    is( $hold_2->found,    'T', 'Next hold in line is now set to in transit' );
185
    is( $hold_2->branchcode, $library_2, "Next hold in line has correct branchcode" );
186
187
    my @messages = $schema->resultset('MessageQueue')
188
      ->search( { letter_code => 'HOLD_CHANGED' } );
189
    is( @messages, 0, 'No messages in the message queue when generating transfer' );
190
};

Return to bug 14364