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

(-)a/C4/Reserves.pm (-3 / +37 lines)
Lines 971-976 sub CancelExpiredReserves { Link Here
971
        if ( defined($hold->found) && $hold->found eq 'W' ) {
971
        if ( defined($hold->found) && $hold->found eq 'W' ) {
972
            $cancel_params->{charge_cancel_fee} = 1;
972
            $cancel_params->{charge_cancel_fee} = 1;
973
        }
973
        }
974
        $cancel_params->{autofill} = C4::Context->preference('ExpireReservesAutoFill');
974
        $hold->cancel( $cancel_params );
975
        $hold->cancel( $cancel_params );
975
    }
976
    }
976
}
977
}
Lines 1137-1143 sub ModReserveStatus { Link Here
1137
1138
1138
=head2 ModReserveAffect
1139
=head2 ModReserveAffect
1139
1140
1140
  &ModReserveAffect($itemnumber,$borrowernumber,$diffBranchSend,$reserve_id, $desk_id);
1141
  &ModReserveAffect($itemnumber,$borrowernumber,$diffBranchSend,$reserve_id, $desk_id, $notify_library);
1141
1142
1142
This function affect an item and a status for a given reserve, either fetched directly
1143
This function affect an item and a status for a given reserve, either fetched directly
1143
by record_id, or by borrowernumber and itemnumber or biblionumber. If only biblionumber
1144
by record_id, or by borrowernumber and itemnumber or biblionumber. If only biblionumber
Lines 1153-1159 This function also removes any entry of the hold in holds queue table. Link Here
1153
=cut
1154
=cut
1154
1155
1155
sub ModReserveAffect {
1156
sub ModReserveAffect {
1156
    my ( $itemnumber, $borrowernumber, $transferToDo, $reserve_id, $desk_id ) = @_;
1157
    my ( $itemnumber, $borrowernumber, $transferToDo, $reserve_id, $desk_id, $notify_library ) = @_;
1157
    my $dbh = C4::Context->dbh;
1158
    my $dbh = C4::Context->dbh;
1158
1159
1159
    # we want to attach $itemnumber to $borrowernumber, find the biblionumber
1160
    # we want to attach $itemnumber to $borrowernumber, find the biblionumber
Lines 1187-1193 sub ModReserveAffect { Link Here
1187
        $hold->set_processing();
1188
        $hold->set_processing();
1188
    } else {
1189
    } else {
1189
        $hold->set_waiting($desk_id);
1190
        $hold->set_waiting($desk_id);
1190
        _koha_notify_reserve( $hold->reserve_id ) unless $already_on_shelf;
1191
        _koha_notify_reserve( $hold->reserve_id, $notify_library ) unless $already_on_shelf;
1191
        # Complete transfer if one exists
1192
        # Complete transfer if one exists
1192
        my $transfer = $hold->item->get_transfer;
1193
        my $transfer = $hold->item->get_transfer;
1193
        $transfer->receive if $transfer;
1194
        $transfer->receive if $transfer;
Lines 1812-1817 The following tables are availalbe witin the notice: Link Here
1812
1813
1813
sub _koha_notify_reserve {
1814
sub _koha_notify_reserve {
1814
    my $reserve_id = shift;
1815
    my $reserve_id = shift;
1816
    my $notify_library = shift;
1817
1815
    my $hold = Koha::Holds->find($reserve_id);
1818
    my $hold = Koha::Holds->find($reserve_id);
1816
    my $borrowernumber = $hold->borrowernumber;
1819
    my $borrowernumber = $hold->borrowernumber;
1817
1820
Lines 1879-1884 sub _koha_notify_reserve { Link Here
1879
        &$send_notification('print', 'HOLD');
1882
        &$send_notification('print', 'HOLD');
1880
    }
1883
    }
1881
1884
1885
    if ($notify_library) {
1886
        my $letter = C4::Letters::GetPreparedLetter(
1887
            module      => 'reserves',
1888
            letter_code => 'HOLD_CHANGED',
1889
            branchcode  => $hold->branchcode,
1890
            substitute  => { today => output_pref( dt_from_string ) },
1891
            tables      => {
1892
                'branches'    => $library,
1893
                'borrowers'   => $patron->unblessed,
1894
                'biblio'      => $hold->biblionumber,
1895
                'biblioitems' => $hold->biblionumber,
1896
                'reserves'    => $hold->unblessed,
1897
                'items'       => $hold->itemnumber,
1898
            },
1899
        );
1900
1901
        my $email =
1902
             C4::Context->preference('ExpireReservesAutoFillEmail')
1903
          || $library->{branchemail}
1904
          || C4::Context->preference('KohaAdminEmailAddress');
1905
1906
        C4::Letters::EnqueueLetter(
1907
            {
1908
                letter                 => $letter,
1909
                borrowernumber         => $borrowernumber,
1910
                message_transport_type => 'email',
1911
                from_address           => $email,
1912
                to_address             => $email,
1913
            }
1914
        );
1915
    }
1882
}
1916
}
1883
1917
1884
=head2 _ShiftPriorityByDateAndPriority
1918
=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 532-537 Cancel a hold: Link Here
532
533
533
sub cancel {
534
sub cancel {
534
    my ( $self, $params ) = @_;
535
    my ( $self, $params ) = @_;
536
<<<<<<< HEAD
537
=======
538
539
    my $autofill_next = $params->{autofill} && $self->itemnumber && $self->found && $self->found eq 'W';
540
541
>>>>>>> Bug 14364: Allow automatically canceled expired waiting holds to fill the next hold
535
    $self->_result->result_source->schema->txn_do(
542
    $self->_result->result_source->schema->txn_do(
536
        sub {
543
        sub {
537
            my $patron = $self->patron;
544
            my $patron = $self->patron;
Lines 584-590 sub cancel { Link Here
584
                if $patron->privacy == 2;
591
                if $patron->privacy == 2;
585
592
586
            $self->SUPER::delete(); # Do not add a DELETE log
593
            $self->SUPER::delete(); # Do not add a DELETE log
587
588
            # now fix the priority on the others....
594
            # now fix the priority on the others....
589
            C4::Reserves::_FixPriority({ biblionumber => $self->biblionumber });
595
            C4::Reserves::_FixPriority({ biblionumber => $self->biblionumber });
590
596
Lines 609-614 sub cancel { Link Here
609
                if C4::Context->preference('HoldsLog');
615
                if C4::Context->preference('HoldsLog');
610
        }
616
        }
611
    );
617
    );
618
619
    if ($autofill_next) {
620
        my ( undef, $next_hold ) = C4::Reserves::CheckReserves( $self->itemnumber );
621
        if ($next_hold) {
622
            my $is_transfer = $self->branchcode ne $next_hold->{branchcode};
623
624
            C4::Reserves::ModReserveAffect( $self->itemnumber, $self->borrowernumber, $is_transfer, $next_hold->{reserve_id}, $self->desk_id, $autofill_next );
625
            C4::Reserves::ModItemTransfer( $self->itemnumber, $self->branchcode, $next_hold->{branchcode}, "Reserve" ) if $is_transfer;
626
        }
627
    }
628
612
    return $self;
629
    return $self;
613
}
630
}
614
631
(-)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 503-505 INSERT IGNORE INTO letter (module, code, name, title, content, message_transport Link Here
503
    [% END %]
503
    [% END %]
504
[% END %]
504
[% END %]
505
", 'email');
505
", 'email');
506
INSERT INTO letter(module,code,branchcode,name,is_html,title,content,message_transport_type)
507
    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>>).
508
509
Please update the hold information for this item.
510
511
Title: <<biblio.title>>
512
Author: <<biblio.author>>
513
Copy: <<items.copynumber>>
514
Pickup location: <<branches.branchname>>', 'email');
(-)a/installer/data/mysql/mandatory/sysprefs.sql (+2 lines)
Lines 203-208 INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` Link Here
203
('EnhancedMessagingPreferences','1','','If ON, allows patrons to select to receive additional messages about items due or nearly due.','YesNo'),
203
('EnhancedMessagingPreferences','1','','If ON, allows patrons to select to receive additional messages about items due or nearly due.','YesNo'),
204
('EnhancedMessagingPreferencesOPAC', '1', NULL, 'If ON, show patrons messaging setting on the OPAC.', 'YesNo'),
204
('EnhancedMessagingPreferencesOPAC', '1', NULL, 'If ON, show patrons messaging setting on the OPAC.', 'YesNo'),
205
('expandedSearchOption','0',NULL,'If ON, set advanced search to be expanded by default','YesNo'),
205
('expandedSearchOption','0',NULL,'If ON, set advanced search to be expanded by default','YesNo'),
206
('ExpireReservesAutoFill','0',NULL,'Automatically fill the next hold with a automatically canceled expired waiting hold.','YesNo'),
207
('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'),
206
('ExpireReservesMaxPickUpDelay','0','','Enabling this allows holds to expire automatically if they have not been picked by within the time period specified in ReservesMaxPickUpDelay','YesNo'),
208
('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
('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'),
209
('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
('ExpireReservesOnHolidays', '1', NULL, 'If false, reserves at a library will not be canceled on days the library is not open.', 'YesNo'),
210
('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