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

(-)a/C4/Reserves.pm (-3 / +37 lines)
Lines 962-967 sub CancelExpiredReserves { Link Here
962
        if ( defined($hold->found) && $hold->found eq 'W' ) {
962
        if ( defined($hold->found) && $hold->found eq 'W' ) {
963
            $cancel_params->{charge_cancel_fee} = 1;
963
            $cancel_params->{charge_cancel_fee} = 1;
964
        }
964
        }
965
        $cancel_params->{autofill} = C4::Context->preference('ExpireReservesAutoFill');
965
        $hold->cancel( $cancel_params );
966
        $hold->cancel( $cancel_params );
966
    }
967
    }
967
}
968
}
Lines 1173-1179 sub ModReserveStatus { Link Here
1173
1174
1174
=head2 ModReserveAffect
1175
=head2 ModReserveAffect
1175
1176
1176
  &ModReserveAffect($itemnumber,$borrowernumber,$diffBranchSend,$reserve_id, $desk_id);
1177
  &ModReserveAffect($itemnumber,$borrowernumber,$diffBranchSend,$reserve_id, $desk_id, $notify_library);
1177
1178
1178
This function affect an item and a status for a given reserve, either fetched directly
1179
This function affect an item and a status for a given reserve, either fetched directly
1179
by record_id, or by borrowernumber and itemnumber or biblionumber. If only biblionumber
1180
by record_id, or by borrowernumber and itemnumber or biblionumber. If only biblionumber
Lines 1189-1195 This function also removes any entry of the hold in holds queue table. Link Here
1189
=cut
1190
=cut
1190
1191
1191
sub ModReserveAffect {
1192
sub ModReserveAffect {
1192
    my ( $itemnumber, $borrowernumber, $transferToDo, $reserve_id, $desk_id ) = @_;
1193
    my ( $itemnumber, $borrowernumber, $transferToDo, $reserve_id, $desk_id, $notify_library ) = @_;
1193
    my $dbh = C4::Context->dbh;
1194
    my $dbh = C4::Context->dbh;
1194
1195
1195
    # we want to attach $itemnumber to $borrowernumber, find the biblionumber
1196
    # we want to attach $itemnumber to $borrowernumber, find the biblionumber
Lines 1223-1229 sub ModReserveAffect { Link Here
1223
        $hold->set_processing();
1224
        $hold->set_processing();
1224
    } else {
1225
    } else {
1225
        $hold->set_waiting($desk_id);
1226
        $hold->set_waiting($desk_id);
1226
        _koha_notify_reserve( $hold->reserve_id ) unless $already_on_shelf;
1227
        _koha_notify_reserve( $hold->reserve_id, $notify_library ) unless $already_on_shelf;
1227
        # Complete transfer if one exists
1228
        # Complete transfer if one exists
1228
        my $transfer = $hold->item->get_transfer;
1229
        my $transfer = $hold->item->get_transfer;
1229
        $transfer->receive if $transfer;
1230
        $transfer->receive if $transfer;
Lines 1846-1851 The following tables are availalbe witin the notice: Link Here
1846
1847
1847
sub _koha_notify_reserve {
1848
sub _koha_notify_reserve {
1848
    my $reserve_id = shift;
1849
    my $reserve_id = shift;
1850
    my $notify_library = shift;
1851
1849
    my $hold = Koha::Holds->find($reserve_id);
1852
    my $hold = Koha::Holds->find($reserve_id);
1850
    my $borrowernumber = $hold->borrowernumber;
1853
    my $borrowernumber = $hold->borrowernumber;
1851
1854
Lines 1913-1918 sub _koha_notify_reserve { Link Here
1913
        &$send_notification('print', 'HOLD');
1916
        &$send_notification('print', 'HOLD');
1914
    }
1917
    }
1915
1918
1919
    if ($notify_library) {
1920
        my $letter = C4::Letters::GetPreparedLetter(
1921
            module      => 'reserves',
1922
            letter_code => 'HOLD_CHANGED',
1923
            branchcode  => $hold->branchcode,
1924
            substitute  => { today => output_pref( dt_from_string ) },
1925
            tables      => {
1926
                'branches'    => $library,
1927
                'borrowers'   => $patron->unblessed,
1928
                'biblio'      => $hold->biblionumber,
1929
                'biblioitems' => $hold->biblionumber,
1930
                'reserves'    => $hold->unblessed,
1931
                'items'       => $hold->itemnumber,
1932
            },
1933
        );
1934
1935
        my $email =
1936
             C4::Context->preference('ExpireReservesAutoFillEmail')
1937
          || $library->{branchemail}
1938
          || C4::Context->preference('KohaAdminEmailAddress');
1939
1940
        C4::Letters::EnqueueLetter(
1941
            {
1942
                letter                 => $letter,
1943
                borrowernumber         => $borrowernumber,
1944
                message_transport_type => 'email',
1945
                from_address           => $email,
1946
                to_address             => $email,
1947
            }
1948
        );
1949
    }
1916
}
1950
}
1917
1951
1918
=head2 _ShiftPriorityByDateAndPriority
1952
=head2 _ShiftPriorityByDateAndPriority
(-)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 509-514 Cancel a hold: Link Here
509
510
510
sub cancel {
511
sub cancel {
511
    my ( $self, $params ) = @_;
512
    my ( $self, $params ) = @_;
513
<<<<<<< HEAD
514
=======
515
516
    my $autofill_next = $params->{autofill} && $self->itemnumber && $self->found && $self->found eq 'W';
517
518
>>>>>>> Bug 14364: Allow automatically canceled expired waiting holds to fill the next hold
512
    $self->_result->result_source->schema->txn_do(
519
    $self->_result->result_source->schema->txn_do(
513
        sub {
520
        sub {
514
            $self->cancellationdate( dt_from_string->strftime( '%Y-%m-%d %H:%M:%S' ) );
521
            $self->cancellationdate( dt_from_string->strftime( '%Y-%m-%d %H:%M:%S' ) );
Lines 546-552 sub cancel { Link Here
546
553
547
            $self->_move_to_old;
554
            $self->_move_to_old;
548
            $self->SUPER::delete(); # Do not add a DELETE log
555
            $self->SUPER::delete(); # Do not add a DELETE log
549
550
            # now fix the priority on the others....
556
            # now fix the priority on the others....
551
            C4::Reserves::_FixPriority({ biblionumber => $self->biblionumber });
557
            C4::Reserves::_FixPriority({ biblionumber => $self->biblionumber });
552
558
Lines 571-576 sub cancel { Link Here
571
                if C4::Context->preference('HoldsLog');
577
                if C4::Context->preference('HoldsLog');
572
        }
578
        }
573
    );
579
    );
580
581
    if ($autofill_next) {
582
        my ( undef, $next_hold ) = C4::Reserves::CheckReserves( $self->itemnumber );
583
        if ($next_hold) {
584
            my $is_transfer = $self->branchcode ne $next_hold->{branchcode};
585
586
            C4::Reserves::ModReserveAffect( $self->itemnumber, $self->borrowernumber, $is_transfer, $next_hold->{reserve_id}, $self->desk_id, $autofill_next );
587
            C4::Reserves::ModItemTransfer( $self->itemnumber, $self->branchcode, $next_hold->{branchcode}, "Reserve" ) if $is_transfer;
588
        }
589
    }
590
574
    return $self;
591
    return $self;
575
}
592
}
576
593
(-)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/fr-FR/1-Obligatoire/sample_notices.sql (+9 lines)
Lines 475-477 INSERT IGNORE INTO letter (module, code, name, title, content, message_transport Link Here
475
    [% END %]
475
    [% END %]
476
[% END %]
476
[% END %]
477
", 'email');
477
", 'email');
478
INSERT INTO letter(module,code,branchcode,name,is_html,title,content,message_transport_type)
479
    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>>).
480
481
Please update the hold information for this item.
482
483
Title: <<biblio.title>>
484
Author: <<biblio.author>>
485
Copy: <<items.copynumber>>
486
Pickup location: <<branches.branchname>>', 'email');
(-)a/installer/data/mysql/mandatory/sysprefs.sql (+2 lines)
Lines 202-207 INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` Link Here
202
('EnhancedMessagingPreferences','1','','If ON, allows patrons to select to receive additional messages about items due or nearly due.','YesNo'),
202
('EnhancedMessagingPreferences','1','','If ON, allows patrons to select to receive additional messages about items due or nearly due.','YesNo'),
203
('EnhancedMessagingPreferencesOPAC', '1', NULL, 'If ON, show patrons messaging setting on the OPAC.', 'YesNo'),
203
('EnhancedMessagingPreferencesOPAC', '1', NULL, 'If ON, show patrons messaging setting on the OPAC.', 'YesNo'),
204
('expandedSearchOption','0',NULL,'If ON, set advanced search to be expanded by default','YesNo'),
204
('expandedSearchOption','0',NULL,'If ON, set advanced search to be expanded by default','YesNo'),
205
('ExpireReservesAutoFill','0',NULL,'Automatically fill the next hold with a automatically canceled expired waiting hold.','YesNo'),
206
('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'),
205
('ExpireReservesMaxPickUpDelay','0','','Enabling this allows holds to expire automatically if they have not been picked by within the time period specified in ReservesMaxPickUpDelay','YesNo'),
207
('ExpireReservesMaxPickUpDelay','0','','Enabling this allows holds to expire automatically if they have not been picked by within the time period specified in ReservesMaxPickUpDelay','YesNo'),
206
('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'),
208
('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'),
207
('ExpireReservesOnHolidays', '1', NULL, 'If false, reserves at a library will not be canceled on days the library is not open.', 'YesNo'),
209
('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 733-739 Circulation: Link Here
733
              choices:
733
              choices:
734
                  1: Allow
734
                  1: Allow
735
                  0: "Don't allow"
735
                  0: "Don't allow"
736
            - '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.'
736
            - '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.'
737
            - '<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>'
738
            - pref: ExpireReservesAutoFill
739
              choices:
740
                  1: "Do"
741
                  0: "Don't"
742
            - 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.
743
        -
744
            - Send email notification of the new hold filled with a canceled item to
745
            - pref: ExpireReservesAutoFillEmail
746
            - . 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.
737
        -
747
        -
738
            - 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
748
            - 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
739
            - pref: ExpireReservesMaxPickUpDelayCharge
749
            - 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