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

(-)a/C4/Reserves.pm (-3 / +37 lines)
Lines 959-964 sub CancelExpiredReserves { Link Here
959
        if ( defined($hold->found) && $hold->found eq 'W' ) {
959
        if ( defined($hold->found) && $hold->found eq 'W' ) {
960
            $cancel_params->{charge_cancel_fee} = 1;
960
            $cancel_params->{charge_cancel_fee} = 1;
961
        }
961
        }
962
        $cancel_params->{autofill} = C4::Context->preference('ExpireReservesAutoFill');
962
        $hold->cancel( $cancel_params );
963
        $hold->cancel( $cancel_params );
963
    }
964
    }
964
}
965
}
Lines 1160-1166 sub ModReserveStatus { Link Here
1160
1161
1161
=head2 ModReserveAffect
1162
=head2 ModReserveAffect
1162
1163
1163
  &ModReserveAffect($itemnumber,$borrowernumber,$diffBranchSend,$reserve_id, $desk_id);
1164
  &ModReserveAffect($itemnumber,$borrowernumber,$diffBranchSend,$reserve_id, $desk_id, $notify_library);
1164
1165
1165
This function affect an item and a status for a given reserve, either fetched directly
1166
This function affect an item and a status for a given reserve, either fetched directly
1166
by record_id, or by borrowernumber and itemnumber or biblionumber. If only biblionumber
1167
by record_id, or by borrowernumber and itemnumber or biblionumber. If only biblionumber
Lines 1176-1182 This function also removes any entry of the hold in holds queue table. Link Here
1176
=cut
1177
=cut
1177
1178
1178
sub ModReserveAffect {
1179
sub ModReserveAffect {
1179
    my ( $itemnumber, $borrowernumber, $transferToDo, $reserve_id, $desk_id ) = @_;
1180
    my ( $itemnumber, $borrowernumber, $transferToDo, $reserve_id, $desk_id, $notify_library ) = @_;
1180
    my $dbh = C4::Context->dbh;
1181
    my $dbh = C4::Context->dbh;
1181
1182
1182
    # we want to attach $itemnumber to $borrowernumber, find the biblionumber
1183
    # we want to attach $itemnumber to $borrowernumber, find the biblionumber
Lines 1210-1216 sub ModReserveAffect { Link Here
1210
        $hold->set_processing();
1211
        $hold->set_processing();
1211
    } else {
1212
    } else {
1212
        $hold->set_waiting($desk_id);
1213
        $hold->set_waiting($desk_id);
1213
        _koha_notify_reserve( $hold->reserve_id ) unless $already_on_shelf;
1214
        _koha_notify_reserve( $hold->reserve_id, $notify_library ) unless $already_on_shelf;
1214
        # Complete transfer if one exists
1215
        # Complete transfer if one exists
1215
        my $transfer = $hold->item->get_transfer;
1216
        my $transfer = $hold->item->get_transfer;
1216
        $transfer->receive if $transfer;
1217
        $transfer->receive if $transfer;
Lines 1834-1839 The following tables are availalbe witin the notice: Link Here
1834
1835
1835
sub _koha_notify_reserve {
1836
sub _koha_notify_reserve {
1836
    my $reserve_id = shift;
1837
    my $reserve_id = shift;
1838
    my $notify_library = shift;
1839
1837
    my $hold = Koha::Holds->find($reserve_id);
1840
    my $hold = Koha::Holds->find($reserve_id);
1838
    my $borrowernumber = $hold->borrowernumber;
1841
    my $borrowernumber = $hold->borrowernumber;
1839
1842
Lines 1901-1906 sub _koha_notify_reserve { Link Here
1901
        &$send_notification('print', 'HOLD');
1904
        &$send_notification('print', 'HOLD');
1902
    }
1905
    }
1903
1906
1907
    if ($notify_library) {
1908
        my $letter = C4::Letters::GetPreparedLetter(
1909
            module      => 'reserves',
1910
            letter_code => 'HOLD_CHANGED',
1911
            branchcode  => $hold->branchcode,
1912
            substitute  => { today => output_pref( dt_from_string ) },
1913
            tables      => {
1914
                'branches'    => $library,
1915
                'borrowers'   => $patron->unblessed,
1916
                'biblio'      => $hold->biblionumber,
1917
                'biblioitems' => $hold->biblionumber,
1918
                'reserves'    => $hold->unblessed,
1919
                'items'       => $hold->itemnumber,
1920
            },
1921
        );
1922
1923
        my $email =
1924
             C4::Context->preference('ExpireReservesAutoFillEmail')
1925
          || $library->{branchemail}
1926
          || C4::Context->preference('KohaAdminEmailAddress');
1927
1928
        C4::Letters::EnqueueLetter(
1929
            {
1930
                letter                 => $letter,
1931
                borrowernumber         => $borrowernumber,
1932
                message_transport_type => 'email',
1933
                from_address           => $email,
1934
                to_address             => $email,
1935
            }
1936
        );
1937
    }
1904
}
1938
}
1905
1939
1906
=head2 _ShiftPriorityByDateAndPriority
1940
=head2 _ShiftPriorityByDateAndPriority
(-)a/Koha/Hold.pm (-1 / +14 lines)
Lines 27-32 use List::MoreUtils qw(any); Link Here
27
use C4::Context qw(preference);
27
use C4::Context qw(preference);
28
use C4::Letters;
28
use C4::Letters;
29
use C4::Log;
29
use C4::Log;
30
use C4::Reserves;
30
31
31
use Koha::AuthorisedValues;
32
use Koha::AuthorisedValues;
32
use Koha::DateUtils qw(dt_from_string output_pref);
33
use Koha::DateUtils qw(dt_from_string output_pref);
Lines 512-517 Cancel a hold: Link Here
512
sub cancel {
513
sub cancel {
513
    my ( $self, $params ) = @_;
514
    my ( $self, $params ) = @_;
514
515
516
    my $autofill_next = $params->{autofill} && $self->itemnumber && $self->found && $self->found eq 'W';
517
515
    $self->_result->result_source->schema->txn_do(
518
    $self->_result->result_source->schema->txn_do(
516
        sub {
519
        sub {
517
            if ( $self->is_in_transit ) {
520
            if ( $self->is_in_transit ) {
Lines 557-563 sub cancel { Link Here
557
560
558
            $self->_move_to_old;
561
            $self->_move_to_old;
559
            $self->SUPER::delete(); # Do not add a DELETE log
562
            $self->SUPER::delete(); # Do not add a DELETE log
560
561
            # now fix the priority on the others....
563
            # now fix the priority on the others....
562
            C4::Reserves::_FixPriority({ biblionumber => $self->biblionumber });
564
            C4::Reserves::_FixPriority({ biblionumber => $self->biblionumber });
563
565
Lines 582-587 sub cancel { Link Here
582
                if C4::Context->preference('HoldsLog');
584
                if C4::Context->preference('HoldsLog');
583
        }
585
        }
584
    );
586
    );
587
588
    if ($autofill_next) {
589
        my ( undef, $next_hold ) = C4::Reserves::CheckReserves( $self->itemnumber );
590
        if ($next_hold) {
591
            my $is_transfer = $self->branchcode ne $next_hold->{branchcode};
592
593
            C4::Reserves::ModReserveAffect( $self->itemnumber, $self->borrowernumber, $is_transfer, $next_hold->{reserve_id}, $self->desk_id, $autofill_next );
594
            C4::Reserves::ModItemTransfer( $self->itemnumber, $self->branchcode, $next_hold->{branchcode}, "Reserve" ) if $is_transfer;
595
        }
596
    }
597
585
    return $self;
598
    return $self;
586
}
599
}
587
600
(-)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 199-204 INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` Link Here
199
('EnhancedMessagingPreferences','1','','If ON, allows patrons to select to receive additional messages about items due or nearly due.','YesNo'),
199
('EnhancedMessagingPreferences','1','','If ON, allows patrons to select to receive additional messages about items due or nearly due.','YesNo'),
200
('EnhancedMessagingPreferencesOPAC', '1', NULL, 'If ON, show patrons messaging setting on the OPAC.', 'YesNo'),
200
('EnhancedMessagingPreferencesOPAC', '1', NULL, 'If ON, show patrons messaging setting on the OPAC.', 'YesNo'),
201
('expandedSearchOption','0',NULL,'If ON, set advanced search to be expanded by default','YesNo'),
201
('expandedSearchOption','0',NULL,'If ON, set advanced search to be expanded by default','YesNo'),
202
('ExpireReservesAutoFill','0',NULL,'Automatically fill the next hold with a automatically canceled expired waiting hold.','YesNo'),
203
('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'),
202
('ExpireReservesMaxPickUpDelay','0','','Enabling this allows holds to expire automatically if they have not been picked by within the time period specified in ReservesMaxPickUpDelay','YesNo'),
204
('ExpireReservesMaxPickUpDelay','0','','Enabling this allows holds to expire automatically if they have not been picked by within the time period specified in ReservesMaxPickUpDelay','YesNo'),
203
('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'),
205
('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'),
204
('ExpireReservesOnHolidays', '1', NULL, 'If false, reserves at a library will not be canceled on days the library is not open.', 'YesNo'),
206
('ExpireReservesOnHolidays', '1', NULL, 'If false, reserves at a library will not be canceled on days the library is not open.', 'YesNo'),
(-)a/installer/data/mysql/nb-NO/1-Obligatorisk/sample_notices.sql (+9 lines)
Lines 493-495 INSERT IGNORE INTO letter (module, code, name, title, content, message_transport Link Here
493
    [% END %]
493
    [% END %]
494
[% END %]
494
[% END %]
495
", 'email');
495
", 'email');
496
INSERT INTO letter(module,code,branchcode,name,is_html,title,content,message_transport_type)
497
    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>>).
498
499
Please update the hold information for this item.
500
501
Title: <<biblio.title>>
502
Author: <<biblio.author>>
503
Copy: <<items.copynumber>>
504
Pickup location: <<branches.branchname>>', 'email');
(-)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