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

(-)a/C4/Reserves.pm (-3 / +37 lines)
Lines 974-979 sub CancelExpiredReserves { Link Here
974
        if ( defined($hold->found) && $hold->found eq 'W' ) {
974
        if ( defined($hold->found) && $hold->found eq 'W' ) {
975
            $cancel_params->{charge_cancel_fee} = 1;
975
            $cancel_params->{charge_cancel_fee} = 1;
976
        }
976
        }
977
        $cancel_params->{autofill} = C4::Context->preference('ExpireReservesAutoFill');
977
        $hold->cancel( $cancel_params );
978
        $hold->cancel( $cancel_params );
978
    }
979
    }
979
}
980
}
Lines 1140-1146 sub ModReserveStatus { Link Here
1140
1141
1141
=head2 ModReserveAffect
1142
=head2 ModReserveAffect
1142
1143
1143
  &ModReserveAffect($itemnumber,$borrowernumber,$diffBranchSend,$reserve_id, $desk_id);
1144
  &ModReserveAffect($itemnumber,$borrowernumber,$diffBranchSend,$reserve_id, $desk_id, $notify_library);
1144
1145
1145
This function affect an item and a status for a given reserve, either fetched directly
1146
This function affect an item and a status for a given reserve, either fetched directly
1146
by record_id, or by borrowernumber and itemnumber or biblionumber. If only biblionumber
1147
by record_id, or by borrowernumber and itemnumber or biblionumber. If only biblionumber
Lines 1156-1162 This function also removes any entry of the hold in holds queue table. Link Here
1156
=cut
1157
=cut
1157
1158
1158
sub ModReserveAffect {
1159
sub ModReserveAffect {
1159
    my ( $itemnumber, $borrowernumber, $transferToDo, $reserve_id, $desk_id ) = @_;
1160
    my ( $itemnumber, $borrowernumber, $transferToDo, $reserve_id, $desk_id, $notify_library ) = @_;
1160
    my $dbh = C4::Context->dbh;
1161
    my $dbh = C4::Context->dbh;
1161
1162
1162
    # we want to attach $itemnumber to $borrowernumber, find the biblionumber
1163
    # we want to attach $itemnumber to $borrowernumber, find the biblionumber
Lines 1190-1196 sub ModReserveAffect { Link Here
1190
        $hold->set_processing();
1191
        $hold->set_processing();
1191
    } else {
1192
    } else {
1192
        $hold->set_waiting($desk_id);
1193
        $hold->set_waiting($desk_id);
1193
        _koha_notify_reserve( $hold->reserve_id ) unless $already_on_shelf;
1194
        _koha_notify_reserve( $hold->reserve_id, $notify_library ) unless $already_on_shelf;
1194
        # Complete transfer if one exists
1195
        # Complete transfer if one exists
1195
        my $transfer = $hold->item->get_transfer;
1196
        my $transfer = $hold->item->get_transfer;
1196
        $transfer->receive if $transfer;
1197
        $transfer->receive if $transfer;
Lines 1815-1820 The following tables are availalbe witin the notice: Link Here
1815
1816
1816
sub _koha_notify_reserve {
1817
sub _koha_notify_reserve {
1817
    my $reserve_id = shift;
1818
    my $reserve_id = shift;
1819
    my $notify_library = shift;
1820
1818
    my $hold = Koha::Holds->find($reserve_id);
1821
    my $hold = Koha::Holds->find($reserve_id);
1819
    my $borrowernumber = $hold->borrowernumber;
1822
    my $borrowernumber = $hold->borrowernumber;
1820
1823
Lines 1882-1887 sub _koha_notify_reserve { Link Here
1882
        &$send_notification('print', 'HOLD');
1885
        &$send_notification('print', 'HOLD');
1883
    }
1886
    }
1884
1887
1888
    if ($notify_library) {
1889
        my $letter = C4::Letters::GetPreparedLetter(
1890
            module      => 'reserves',
1891
            letter_code => 'HOLD_CHANGED',
1892
            branchcode  => $hold->branchcode,
1893
            substitute  => { today => output_pref( dt_from_string ) },
1894
            tables      => {
1895
                'branches'    => $library,
1896
                'borrowers'   => $patron->unblessed,
1897
                'biblio'      => $hold->biblionumber,
1898
                'biblioitems' => $hold->biblionumber,
1899
                'reserves'    => $hold->unblessed,
1900
                'items'       => $hold->itemnumber,
1901
            },
1902
        );
1903
1904
        my $email =
1905
             C4::Context->preference('ExpireReservesAutoFillEmail')
1906
          || $library->{branchemail}
1907
          || C4::Context->preference('KohaAdminEmailAddress');
1908
1909
        C4::Letters::EnqueueLetter(
1910
            {
1911
                letter                 => $letter,
1912
                borrowernumber         => $borrowernumber,
1913
                message_transport_type => 'email',
1914
                from_address           => $email,
1915
                to_address             => $email,
1916
            }
1917
        );
1918
    }
1885
}
1919
}
1886
1920
1887
=head2 _ShiftPriorityByDateAndPriority
1921
=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 207-212 INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` Link Here
207
('EnhancedMessagingPreferences','1','','If ON, allows patrons to select to receive additional messages about items due or nearly due.','YesNo'),
207
('EnhancedMessagingPreferences','1','','If ON, allows patrons to select to receive additional messages about items due or nearly due.','YesNo'),
208
('EnhancedMessagingPreferencesOPAC', '1', NULL, 'If ON, show patrons messaging setting on the OPAC.', 'YesNo'),
208
('EnhancedMessagingPreferencesOPAC', '1', NULL, 'If ON, show patrons messaging setting on the OPAC.', 'YesNo'),
209
('expandedSearchOption','0',NULL,'If ON, set advanced search to be expanded by default','YesNo'),
209
('expandedSearchOption','0',NULL,'If ON, set advanced search to be expanded by default','YesNo'),
210
('ExpireReservesAutoFill','0',NULL,'Automatically fill the next hold with a automatically canceled expired waiting hold.','YesNo'),
211
('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'),
210
('ExpireReservesMaxPickUpDelay','0','','Enabling this allows holds to expire automatically if they have not been picked by within the time period specified in ReservesMaxPickUpDelay','YesNo'),
212
('ExpireReservesMaxPickUpDelay','0','','Enabling this allows holds to expire automatically if they have not been picked by within the time period specified in ReservesMaxPickUpDelay','YesNo'),
211
('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'),
213
('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'),
212
('ExpireReservesOnHolidays', '1', NULL, 'If false, reserves at a library will not be canceled on days the library is not open.', 'YesNo'),
214
('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 739-745 Circulation: Link Here
739
              choices:
739
              choices:
740
                  1: Allow
740
                  1: Allow
741
                  0: "Don't allow"
741
                  0: "Don't allow"
742
            - '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.'
742
            - '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.'
743
            - '<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>'
744
            - pref: ExpireReservesAutoFill
745
              choices:
746
                  1: "Do"
747
                  0: "Don't"
748
            - 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.
749
        -
750
            - Send email notification of the new hold filled with a canceled item to
751
            - pref: ExpireReservesAutoFillEmail
752
            - . 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.
743
        -
753
        -
744
            - 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
754
            - 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
745
            - pref: ExpireReservesMaxPickUpDelayCharge
755
            - 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