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

(-)a/C4/Reserves.pm (-4 / +40 lines)
Lines 904-909 sub CancelExpiredReserves { Link Here
904
        if ( $hold->found eq 'W' ) {
904
        if ( $hold->found eq 'W' ) {
905
            $cancel_params->{charge_cancel_fee} = 1;
905
            $cancel_params->{charge_cancel_fee} = 1;
906
        }
906
        }
907
        $cancel_params->{autofill} = C4::Context->preference('ExpireReservesAutoFill');
907
        $hold->cancel( $cancel_params );
908
        $hold->cancel( $cancel_params );
908
    }
909
    }
909
}
910
}
Lines 1104-1110 sub ModReserveStatus { Link Here
1104
1105
1105
=head2 ModReserveAffect
1106
=head2 ModReserveAffect
1106
1107
1107
  &ModReserveAffect($itemnumber,$borrowernumber,$diffBranchSend,$reserve_id);
1108
&ModReserveAffect(
1109
    $itemnumber, $borrowernumber, $diffBranchSend,
1110
    $reserve_id, $notify_library
1111
);
1108
1112
1109
This function affect an item and a status for a given reserve, either fetched directly
1113
This function affect an item and a status for a given reserve, either fetched directly
1110
by record_id, or by borrowernumber and itemnumber or biblionumber. If only biblionumber
1114
by record_id, or by borrowernumber and itemnumber or biblionumber. If only biblionumber
Lines 1118-1124 take care of the waiting status Link Here
1118
=cut
1122
=cut
1119
1123
1120
sub ModReserveAffect {
1124
sub ModReserveAffect {
1121
    my ( $itemnumber, $borrowernumber, $transferToDo, $reserve_id ) = @_;
1125
    my ( $itemnumber, $borrowernumber, $transferToDo, $reserve_id, $notify_library ) = @_;
1122
    my $dbh = C4::Context->dbh;
1126
    my $dbh = C4::Context->dbh;
1123
1127
1124
    # we want to attach $itemnumber to $borrowernumber, find the biblionumber
1128
    # we want to attach $itemnumber to $borrowernumber, find the biblionumber
Lines 1145-1152 sub ModReserveAffect { Link Here
1145
    $hold->itemnumber($itemnumber);
1149
    $hold->itemnumber($itemnumber);
1146
    $hold->set_waiting($transferToDo);
1150
    $hold->set_waiting($transferToDo);
1147
1151
1152
    _koha_notify_reserve( $hold->reserve_id, $notify_library ) unless $already_on_shelf;
1148
    if( !$transferToDo ){
1153
    if( !$transferToDo ){
1149
        _koha_notify_reserve( $hold->reserve_id ) unless $already_on_shelf;
1150
        my $transfers = Koha::Item::Transfers->search({
1154
        my $transfers = Koha::Item::Transfers->search({
1151
            itemnumber => $itemnumber,
1155
            itemnumber => $itemnumber,
1152
            datearrived => undef
1156
            datearrived => undef
Lines 1156-1162 sub ModReserveAffect { Link Here
1156
        };
1160
        };
1157
    }
1161
    }
1158
1162
1159
1160
    _FixPriority( { biblionumber => $biblionumber } );
1163
    _FixPriority( { biblionumber => $biblionumber } );
1161
    my $item = Koha::Items->find($itemnumber);
1164
    my $item = Koha::Items->find($itemnumber);
1162
    if ( $item->location && $item->location eq 'CART'
1165
    if ( $item->location && $item->location eq 'CART'
Lines 1753-1758 The following tables are availalbe witin the notice: Link Here
1753
1756
1754
sub _koha_notify_reserve {
1757
sub _koha_notify_reserve {
1755
    my $reserve_id = shift;
1758
    my $reserve_id = shift;
1759
    my $notify_library = shift;
1760
1756
    my $hold = Koha::Holds->find($reserve_id);
1761
    my $hold = Koha::Holds->find($reserve_id);
1757
    my $borrowernumber = $hold->borrowernumber;
1762
    my $borrowernumber = $hold->borrowernumber;
1758
1763
Lines 1819-1824 sub _koha_notify_reserve { Link Here
1819
        &$send_notification('print', 'HOLD');
1824
        &$send_notification('print', 'HOLD');
1820
    }
1825
    }
1821
1826
1827
    if ($notify_library) {
1828
        my $letter = C4::Letters::GetPreparedLetter(
1829
            module      => 'reserves',
1830
            letter_code => 'HOLD_CHANGED',
1831
            branchcode  => $hold->branchcode,
1832
            substitute  => { today => output_pref( dt_from_string ) },
1833
            tables      => {
1834
                'branches'    => $library,
1835
                'borrowers'   => $patron->unblessed,
1836
                'biblio'      => $hold->biblionumber,
1837
                'biblioitems' => $hold->biblionumber,
1838
                'reserves'    => $hold->unblessed,
1839
                'items'       => $hold->itemnumber,
1840
            },
1841
        );
1842
1843
        my $email =
1844
             C4::Context->preference('ExpireReservesAutoFillEmail')
1845
          || $library->{branchemail}
1846
          || C4::Context->preference('KohaAdminEmailAddress');
1847
1848
        C4::Letters::EnqueueLetter(
1849
            {
1850
                letter                 => $letter,
1851
                borrowernumber         => $borrowernumber,
1852
                message_transport_type => 'email',
1853
                from_address           => $email,
1854
                to_address             => $email,
1855
            }
1856
        );
1857
    }
1822
}
1858
}
1823
1859
1824
=head2 _ShiftPriorityByDateAndPriority
1860
=head2 _ShiftPriorityByDateAndPriority
(-)a/Koha/Hold.pm (+15 lines)
Lines 25-30 use Data::Dumper qw(Dumper); Link Here
25
25
26
use C4::Context qw(preference);
26
use C4::Context qw(preference);
27
use C4::Log;
27
use C4::Log;
28
use C4::Reserves;
28
29
29
use Koha::DateUtils qw(dt_from_string output_pref);
30
use Koha::DateUtils qw(dt_from_string output_pref);
30
use Koha::Patrons;
31
use Koha::Patrons;
Lines 352-357 Cancel a hold: Link Here
352
353
353
sub cancel {
354
sub cancel {
354
    my ( $self, $params ) = @_;
355
    my ( $self, $params ) = @_;
356
357
    my $autofill_next = $params->{autofill} && $self->itemnumber && $self->found && $self->found eq 'W';
358
355
    $self->_result->result_source->schema->txn_do(
359
    $self->_result->result_source->schema->txn_do(
356
        sub {
360
        sub {
357
            $self->cancellationdate( dt_from_string->strftime( '%Y-%m-%d %H:%M:%S' ) );
361
            $self->cancellationdate( dt_from_string->strftime( '%Y-%m-%d %H:%M:%S' ) );
Lines 383-388 sub cancel { Link Here
383
                if C4::Context->preference('HoldsLog');
387
                if C4::Context->preference('HoldsLog');
384
        }
388
        }
385
    );
389
    );
390
391
    if ($autofill_next) {
392
        my ( undef, $next_hold ) = C4::Reserves::CheckReserves( $self->itemnumber );
393
        if ($next_hold) {
394
            my $is_transfer = $self->branchcode ne $next_hold->{branchcode};
395
396
            C4::Reserves::ModReserveAffect( $self->itemnumber, $self->borrowernumber, $is_transfer, $next_hold->{reserve_id}, $autofill_next );
397
            C4::Reserves::ModItemTransfer( $self->itemnumber, $self->branchcode, $next_hold->{branchcode} ) if $is_transfer;
398
        }
399
    }
400
386
    return $self;
401
    return $self;
387
}
402
}
388
403
(-)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 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/pl-PL/mandatory/sample_notices.sql (+10 lines)
Lines 397-399 Your total unpaid fines are too high. Link Here
397
[% ELSE %]
397
[% ELSE %]
398
The following item, [% biblio.title %], has correctly been renewed and is now due on [% checkout.date_due | $KohaDates as_due_date => 1 %]
398
The following item, [% biblio.title %], has correctly been renewed and is now due on [% checkout.date_due | $KohaDates as_due_date => 1 %]
399
[% END %]", 'email');
399
[% END %]", 'email');
400
401
INSERT INTO letter(module,code,branchcode,name,is_html,title,content,message_transport_type)
402
    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>>).
403
404
Please update the hold information for this item.
405
406
Title: <<biblio.title>>
407
Author: <<biblio.author>>
408
Copy: <<items.copynumber>>
409
Pickup location: <<branches.branchname>>', 'email');
(-)a/installer/data/mysql/sysprefs.sql (+2 lines)
Lines 184-189 INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` Link Here
184
('EnhancedMessagingPreferences','1','','If ON, allows patrons to select to receive additional messages about items due or nearly due.','YesNo'),
184
('EnhancedMessagingPreferences','1','','If ON, allows patrons to select to receive additional messages about items due or nearly due.','YesNo'),
185
('EnhancedMessagingPreferencesOPAC', '1', NULL, 'If ON, show patrons messaging setting on the OPAC.', 'YesNo'),
185
('EnhancedMessagingPreferencesOPAC', '1', NULL, 'If ON, show patrons messaging setting on the OPAC.', 'YesNo'),
186
('expandedSearchOption','0',NULL,'If ON, set advanced search to be expanded by default','YesNo'),
186
('expandedSearchOption','0',NULL,'If ON, set advanced search to be expanded by default','YesNo'),
187
('ExpireReservesAutoFill','0',NULL,'Automatically fill the next hold with a automatically canceled expired waiting hold.','YesNo'),
188
('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'),
187
('ExpireReservesMaxPickUpDelay','0','','Enabling this allows holds to expire automatically if they have not been picked by within the time period specified in ReservesMaxPickUpDelay','YesNo'),
189
('ExpireReservesMaxPickUpDelay','0','','Enabling this allows holds to expire automatically if they have not been picked by within the time period specified in ReservesMaxPickUpDelay','YesNo'),
188
('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'),
190
('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'),
189
('ExpireReservesOnHolidays', '1', NULL, 'If false, reserves at a library will not be canceled on days the library is not open.', 'YesNo'),
191
('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 (+9 lines)
Lines 688-693 Circulation: Link Here
688
                  yes: Allow
688
                  yes: Allow
689
                  no: "Don't allow"
689
                  no: "Don't allow"
690
            - "holds to expire automatically if they have not been picked by within the time period specified in ReservesMaxPickUpDelay.<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."
690
            - "holds to expire automatically if they have not been picked by within the time period specified in ReservesMaxPickUpDelay.<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."
691
            - pref: ExpireReservesAutoFill
692
              choices:
693
                  yes: "Do"
694
                  no: "Don't"
695
            - 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.
696
        -
697
            - Send email notification of the new hold filled with a canceled item to
698
            - pref: ExpireReservesAutoFillEmail
699
            - . 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.
691
        -
700
        -
692
            - If using ExpireReservesMaxPickUpDelay, charge a borrower who allows their waiting hold to expire a fee of
701
            - If using ExpireReservesMaxPickUpDelay, charge a borrower who allows their waiting hold to expire a fee of
693
            - pref: ExpireReservesMaxPickUpDelayCharge
702
            - pref: ExpireReservesMaxPickUpDelayCharge
(-)a/t/db_dependent/Holds/ExpireReservesAutoFill.t (-1 / +186 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 => 8;
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
128
subtest 'Test automatically canceled expired waiting holds to fill the next hold, with a transfer' => sub {
129
    plan tests => 6;
130
131
    $dbh->do('DELETE FROM reserves');
132
    $dbh->do('DELETE FROM message_queue');
133
134
    # Add a hold on the item for each of our patrons
135
    my $hold_1 = Koha::Hold->new(
136
        {
137
            priority       => 0,
138
            borrowernumber => $patron_1->{borrowernumber},
139
            branchcode     => $library_1,
140
            biblionumber   => $biblionumber,
141
            itemnumber     => $itemnumber,
142
            found          => 'W',
143
            reservedate    => '1900-01-01',
144
            waitingdate    => '1900-01-01',
145
            expirationdate => '1900-01-01',
146
            lowestPriority => 0,
147
            suspend        => 0,
148
        }
149
    )->store();
150
    my $hold_2 = Koha::Hold->new(
151
        {
152
            priority       => 1,
153
            borrowernumber => $patron_2->{borrowernumber},
154
            branchcode     => $library_2,
155
            biblionumber   => $biblionumber,
156
            itemnumber     => $itemnumber,
157
            reservedate    => '1900-01-01',
158
            expirationdate => '9999-01-01',
159
            lowestPriority => 0,
160
            suspend        => 0,
161
        }
162
    )->store();
163
164
    # Test CancelExpiredReserves
165
    t::lib::Mocks::mock_preference( 'ExpireReservesMaxPickUpDelay', 1 );
166
    t::lib::Mocks::mock_preference( 'ReservesMaxPickUpDelay',       1 );
167
    t::lib::Mocks::mock_preference( 'ExpireReservesOnHolidays',     1 );
168
    t::lib::Mocks::mock_preference( 'ExpireReservesAutoFill',       1 );
169
    t::lib::Mocks::mock_preference( 'ExpireReservesAutoFillEmail',
170
        'kyle@example.com' );
171
172
    CancelExpiredReserves();
173
174
    my @holds = Koha::Holds->search( {}, { order_by => 'priority' } );
175
    $hold_2 = $holds[0];
176
177
    is( @holds,            1,   'Found 1 hold' );
178
    is( $hold_2->priority, 0,   'Next hold in line now has priority of 0' );
179
    is( $hold_2->found,    'T', 'Next hold in line is now set to in transit' );
180
    is( $hold_2->branchcode, $library_2, "Next hold in line has correct branchcode" );
181
182
    my @messages = $schema->resultset('MessageQueue')
183
      ->search( { letter_code => 'HOLD_CHANGED' } );
184
    is( @messages, 1, 'Found 1 message in the message queue' );
185
    is( $messages[0]->to_address, 'kyle@example.com', 'Message sent to correct email address' );
186
};

Return to bug 14364