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

(-)a/Koha/CirculationRules.pm (+1 lines)
Lines 91-96 our $RULE_KINDS = { Link Here
91
    },
91
    },
92
    expire_reserves_charge => {
92
    expire_reserves_charge => {
93
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
93
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
94
        can_be_blank => 0,
94
    },
95
    },
95
    chargeperiod => {
96
    chargeperiod => {
96
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
97
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
(-)a/Koha/Hold.pm (-2 / +1 lines)
Lines 617-624 sub cancel { Link Here
617
                        rule_name    => 'expire_reserves_charge',
617
                        rule_name    => 'expire_reserves_charge',
618
                    }
618
                    }
619
                );
619
                );
620
                my $rule_value = $rule && $rule->rule_value // '';
620
                $charge = $rule ? $rule->rule_value : C4::Context->preference("ExpireReservesMaxPickUpDelayCharge");
621
                $charge = $rule_value ne '' ? $rule_value : C4::Context->preference("ExpireReservesMaxPickUpDelayCharge");
622
621
623
                my $account =
622
                my $account =
624
                  Koha::Account->new( { patron_id => $self->borrowernumber } );
623
                  Koha::Account->new( { patron_id => $self->borrowernumber } );
(-)a/t/db_dependent/Holds/Holdfine.t (-258 lines)
Lines 1-258 Link Here
1
#!/usr/bin/perl
2
3
# Copyright The National Library of Finland, University of Helsinki 2020
4
# Copyright Petro Vashchuk <stalkernoid@gmail.com> 2020
5
#
6
# This file is part of Koha.
7
#
8
# Koha is free software; you can redistribute it and/or modify it under the
9
# terms of the GNU General Public License as published by the Free Software
10
# Foundation; either version 3 of the License, or (at your option) any later
11
# version.
12
#
13
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
14
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
15
# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General Public License along
18
# with Koha; if not, see <http://www.gnu.org/licenses>.
19
20
use Modern::Perl;
21
22
use C4::Context;
23
use Koha::CirculationRules;
24
25
use Test::More tests => 13;
26
27
use t::lib::TestBuilder;
28
use t::lib::Mocks;
29
use Koha::Holds;
30
31
use Koha::Account;
32
use Koha::Account::DebitTypes;
33
34
BEGIN {
35
    use_ok('C4::Reserves');
36
}
37
38
my $schema = Koha::Database->schema;
39
$schema->storage->txn_begin;
40
my $dbh = C4::Context->dbh;
41
42
my $builder = t::lib::TestBuilder->new;
43
44
my $library1 = $builder->build({
45
    source => 'Branch',
46
});
47
my $library2 = $builder->build({
48
    source => 'Branch',
49
});
50
51
my $bib_title = "Test Title";
52
53
my $borrower = $builder->build({
54
    source => 'Borrower',
55
    value => {
56
        branchcode => $library1->{branchcode},
57
    }
58
});
59
60
my $itemtype1 = $builder->build({
61
    source => 'Itemtype',
62
    value => {}
63
});
64
my $itemtype2 = $builder->build({
65
    source => 'Itemtype',
66
    value => {}
67
});
68
my $itemtype3 = $builder->build({
69
    source => 'Itemtype',
70
    value => {}
71
});
72
my $itemtype4 = $builder->build({
73
    source => 'Itemtype',
74
    value => {}
75
});
76
77
my $borrowernumber = $borrower->{borrowernumber};
78
79
my $library_A_code = $library1->{branchcode};
80
81
my $biblio = $builder->build_sample_biblio({itemtype => $itemtype1->{itemtype}});
82
my $biblionumber = $biblio->biblionumber;
83
my $item1 = $builder->build_sample_item({
84
    biblionumber => $biblionumber,
85
    itype => $itemtype1->{itemtype},
86
    homebranch => $library_A_code,
87
    holdingbranch => $library_A_code
88
});
89
my $item2 = $builder->build_sample_item({
90
    biblionumber => $biblionumber,
91
    itype => $itemtype2->{itemtype},
92
    homebranch => $library_A_code,
93
    holdingbranch => $library_A_code
94
});
95
my $item3 = $builder->build_sample_item({
96
    biblionumber => $biblionumber,
97
    itype => $itemtype3->{itemtype},
98
    homebranch => $library_A_code,
99
    holdingbranch => $library_A_code
100
});
101
102
my $library_B_code = $library2->{branchcode};
103
104
my $biblio2 = $builder->build_sample_biblio({itemtype => $itemtype4->{itemtype}});
105
my $biblionumber2 = $biblio2->biblionumber;
106
my $item4 = $builder->build_sample_item({
107
    biblionumber => $biblionumber2,
108
    itype => $itemtype4->{itemtype},
109
    homebranch => $library_B_code,
110
    holdingbranch => $library_B_code
111
});
112
113
$dbh->do("DELETE FROM circulation_rules");
114
Koha::CirculationRules->set_rules(
115
    {
116
        itemtype     => $itemtype1->{itemtype},
117
        categorycode => undef,
118
        branchcode   => undef,
119
        rules        => {
120
            expire_reserves_charge => '111'
121
        }
122
    }
123
);
124
Koha::CirculationRules->set_rules(
125
    {
126
        itemtype     => $itemtype2->{itemtype},
127
        categorycode => undef,
128
        branchcode   => undef,
129
        rules        => {
130
            expire_reserves_charge => undef
131
        }
132
    }
133
);
134
Koha::CirculationRules->set_rules(
135
    {
136
        itemtype     => undef,
137
        categorycode => undef,
138
        branchcode   => $library_B_code,
139
        rules        => {
140
            expire_reserves_charge => '444'
141
        }
142
    }
143
);
144
145
t::lib::Mocks::mock_preference('ReservesControlBranch', 'ItemHomeLibrary');
146
147
my $reserve_id;
148
my $account;
149
my $status;
150
my $start_balance;
151
152
# TEST: Hold itemtype1 item
153
$reserve_id = AddReserve(
154
    {
155
        branchcode       => $library_A_code,
156
        borrowernumber   => $borrowernumber,
157
        biblionumber     => $biblionumber,
158
        priority         => 1,
159
        itemnumber       => $item1->itemnumber,
160
    }
161
);
162
163
$account = Koha::Account->new({ patron_id => $borrowernumber });
164
165
( $status ) = CheckReserves($item1->id);
166
is( $status, 'Reserved', "Hold for the itemtype1 created" );
167
168
$start_balance = $account->balance();
169
170
Koha::Holds->find( $reserve_id )->cancel({ charge_cancel_fee => 1 });
171
172
( $status ) = CheckReserves($item1->id);
173
is( $status, '', "Hold for the itemtype1 cancelled" );
174
175
is( $account->balance() - $start_balance, 111, "Used circulation rule for itemtype1" );
176
177
# TEST: circulation rule for itemtype2 has 'expire_reserves_charge' set undef, so it should use ExpireReservesMaxPickUpDelayCharge preference
178
t::lib::Mocks::mock_preference('ExpireReservesMaxPickUpDelayCharge', 222);
179
180
$reserve_id = AddReserve(
181
    {
182
        branchcode       => $library_A_code,
183
        borrowernumber   => $borrowernumber,
184
        biblionumber     => $biblionumber,
185
        priority         => 1,
186
        itemnumber       => $item2->itemnumber,
187
    }
188
);
189
190
$account = Koha::Account->new({ patron_id => $borrowernumber });
191
192
( $status ) = CheckReserves($item2->id);
193
is( $status, 'Reserved', "Hold for the itemtype2 created" );
194
195
$start_balance = $account->balance();
196
197
Koha::Holds->find( $reserve_id )->cancel({ charge_cancel_fee => 1 });
198
199
( $status ) = CheckReserves($item2->id);
200
is( $status, '', "Hold for the itemtype2 cancelled" );
201
202
is( $account->balance() - $start_balance, 222, "Used ExpireReservesMaxPickUpDelayCharge preference as expire_reserves_charge set to undef" );
203
204
# TEST: no circulation rules for itemtype3, it should use ExpireReservesMaxPickUpDelayCharge preference
205
t::lib::Mocks::mock_preference('ExpireReservesMaxPickUpDelayCharge', 333);
206
207
$reserve_id = AddReserve(
208
    {
209
        branchcode       => $library_A_code,
210
        borrowernumber   => $borrowernumber,
211
        biblionumber     => $biblionumber,
212
        priority         => 1,
213
        itemnumber       => $item3->itemnumber,
214
    }
215
);
216
217
$account = Koha::Account->new({ patron_id => $borrowernumber });
218
219
( $status ) = CheckReserves($item3->id);
220
is( $status, 'Reserved', "Hold for the itemtype3 created" );
221
222
$start_balance = $account->balance();
223
224
Koha::Holds->find( $reserve_id )->cancel({ charge_cancel_fee => 1 });
225
226
( $status ) = CheckReserves($item3->id);
227
is( $status, '', "Hold for the itemtype3 cancelled" );
228
229
is( $account->balance() - $start_balance, 333, "Used ExpireReservesMaxPickUpDelayCharge preference as there's no circulation rules for itemtype3" );
230
231
# TEST: circulation rule for itemtype4 with library_B_code
232
t::lib::Mocks::mock_preference('ExpireReservesMaxPickUpDelayCharge', 555);
233
234
$reserve_id = AddReserve(
235
    {
236
        branchcode       => $library_B_code,
237
        borrowernumber   => $borrowernumber,
238
        biblionumber     => $biblionumber2,
239
        priority         => 1,
240
        itemnumber       => $item4->itemnumber,
241
    }
242
);
243
244
$account = Koha::Account->new({ patron_id => $borrowernumber });
245
246
( $status ) = CheckReserves($item4->id);
247
is( $status, 'Reserved', "Hold for the itemtype4 created" );
248
249
$start_balance = $account->balance();
250
251
Koha::Holds->find( $reserve_id )->cancel({ charge_cancel_fee => 1 });
252
253
( $status ) = CheckReserves($item4->id);
254
is( $status, '', "Hold for the itemtype4 cancelled" );
255
256
is( $account->balance() - $start_balance, 444, "Used circulation rule for itemtype4 with library_B_code" );
257
258
$schema->storage->txn_rollback;
(-)a/t/db_dependent/Koha/Holds.t (-29 / +243 lines)
Lines 23-29 use Test::More tests => 7; Link Here
23
use Test::Warn;
23
use Test::Warn;
24
24
25
use C4::Circulation qw( AddIssue );
25
use C4::Circulation qw( AddIssue );
26
use C4::Reserves qw( AddReserve ModReserve ModReserveCancelAll );
26
use C4::Reserves qw( AddReserve CheckReserves ModReserve ModReserveCancelAll );
27
use Koha::AuthorisedValueCategory;
27
use Koha::AuthorisedValueCategory;
28
use Koha::Database;
28
use Koha::Database;
29
use Koha::DateUtils qw( dt_from_string );
29
use Koha::DateUtils qw( dt_from_string );
Lines 132-169 subtest 'cancel' => sub { Link Here
132
    is( $third_hold->discard_changes->priority, 2, 'Third hold should now be second' );
132
    is( $third_hold->discard_changes->priority, 2, 'Third hold should now be second' );
133
133
134
    subtest 'charge_cancel_fee parameter' => sub {
134
    subtest 'charge_cancel_fee parameter' => sub {
135
        plan tests => 4;
135
        plan tests => 15;
136
        my $patron_category = $builder->build_object({ class => 'Koha::Patron::Categories', value => { reservefee => 0 } } );
136
        my $library1 = $builder->build({
137
        my $patron = $builder->build_object({ class => 'Koha::Patrons', value => { categorycode => $patron_category->categorycode } });
137
            source => 'Branch',
138
        is( $patron->account->balance, 0, 'A new patron does not have any charges' );
138
        });
139
        my $library2 = $builder->build({
140
            source => 'Branch',
141
        });
142
143
        my $bib_title = "Test Title";
144
145
        my $borrower = $builder->build({
146
            source => 'Borrower',
147
            value => {
148
                branchcode => $library1->{branchcode},
149
            }
150
        });
151
152
        my $itemtype1 = $builder->build({
153
            source => 'Itemtype',
154
            value => {}
155
        });
156
        my $itemtype2 = $builder->build({
157
            source => 'Itemtype',
158
            value => {}
159
        });
160
        my $itemtype3 = $builder->build({
161
            source => 'Itemtype',
162
            value => {}
163
        });
164
        my $itemtype4 = $builder->build({
165
            source => 'Itemtype',
166
            value => {}
167
        });
168
169
        my $borrowernumber = $borrower->{borrowernumber};
170
171
        my $library_A_code = $library1->{branchcode};
172
173
        my $biblio = $builder->build_sample_biblio({itemtype => $itemtype1->{itemtype}});
174
        my $biblionumber = $biblio->biblionumber;
175
        my $item1 = $builder->build_sample_item({
176
            biblionumber => $biblionumber,
177
            itype => $itemtype1->{itemtype},
178
            homebranch => $library_A_code,
179
            holdingbranch => $library_A_code
180
        });
181
        my $item2 = $builder->build_sample_item({
182
            biblionumber => $biblionumber,
183
            itype => $itemtype2->{itemtype},
184
            homebranch => $library_A_code,
185
            holdingbranch => $library_A_code
186
        });
187
        my $item3 = $builder->build_sample_item({
188
            biblionumber => $biblionumber,
189
            itype => $itemtype3->{itemtype},
190
            homebranch => $library_A_code,
191
            holdingbranch => $library_A_code
192
        });
193
194
        my $library_B_code = $library2->{branchcode};
195
196
        my $biblio2 = $builder->build_sample_biblio({itemtype => $itemtype4->{itemtype}});
197
        my $biblionumber2 = $biblio2->biblionumber;
198
        my $item4 = $builder->build_sample_item({
199
            biblionumber => $biblionumber2,
200
            itype => $itemtype4->{itemtype},
201
            homebranch => $library_B_code,
202
            holdingbranch => $library_B_code
203
        });
204
205
        Koha::CirculationRules->set_rules(
206
            {
207
                itemtype     => undef,
208
                categorycode => undef,
209
                branchcode   => undef,
210
                rules        => {
211
                    expire_reserves_charge => undef
212
                }
213
            }
214
        );
215
        Koha::CirculationRules->set_rules(
216
            {
217
                itemtype     => $itemtype1->{itemtype},
218
                categorycode => undef,
219
                branchcode   => undef,
220
                rules        => {
221
                    expire_reserves_charge => '111'
222
                }
223
            }
224
        );
225
        Koha::CirculationRules->set_rules(
226
            {
227
                itemtype     => $itemtype2->{itemtype},
228
                categorycode => undef,
229
                branchcode   => undef,
230
                rules        => {
231
                    expire_reserves_charge => undef
232
                }
233
            }
234
        );
235
        Koha::CirculationRules->set_rules(
236
            {
237
                itemtype     => undef,
238
                categorycode => undef,
239
                branchcode   => $library_B_code,
240
                rules        => {
241
                    expire_reserves_charge => '444'
242
                }
243
            }
244
        );
139
245
140
        my $hold_info = {
246
        t::lib::Mocks::mock_preference('ReservesControlBranch', 'ItemHomeLibrary');
141
            branchcode     => $library->branchcode,
247
142
            borrowernumber => $patron->borrowernumber,
248
        my $reserve_id;
143
            biblionumber   => $item->biblionumber,
249
        my $account;
144
            priority       => 1,
250
        my $status;
145
            title          => "title for fee",
251
        my $start_balance;
146
            itemnumber     => $item->itemnumber,
252
147
        };
253
# TEST: Hold itemtype1 item
254
        $reserve_id = AddReserve(
255
            {
256
                branchcode       => $library_A_code,
257
                borrowernumber   => $borrowernumber,
258
                biblionumber     => $biblionumber,
259
                priority         => 1,
260
                itemnumber       => $item1->itemnumber,
261
            }
262
        );
263
264
        $account = Koha::Account->new({ patron_id => $borrowernumber });
265
266
        ( $status ) = CheckReserves($item1->id);
267
        is( $status, 'Reserved', "Hold for the itemtype1 created" );
268
269
        $start_balance = $account->balance();
270
271
        Koha::Holds->find( $reserve_id )->cancel({ charge_cancel_fee => 1 });
272
273
        ( $status ) = CheckReserves($item1->id);
274
        is( $status, '', "Hold for the itemtype1 cancelled" );
275
276
        is( $account->balance() - $start_balance, 111, "Used circulation rule for itemtype1" );
277
278
# TEST: circulation rule for itemtype2 has 'expire_reserves_charge' set undef, so it should use ExpireReservesMaxPickUpDelayCharge preference
279
        t::lib::Mocks::mock_preference('ExpireReservesMaxPickUpDelayCharge', 222);
280
281
        $reserve_id = AddReserve(
282
            {
283
                branchcode       => $library_A_code,
284
                borrowernumber   => $borrowernumber,
285
                biblionumber     => $biblionumber,
286
                priority         => 1,
287
                itemnumber       => $item2->itemnumber,
288
            }
289
        );
290
291
        $account = Koha::Account->new({ patron_id => $borrowernumber });
292
293
        ( $status ) = CheckReserves($item2->id);
294
        is( $status, 'Reserved', "Hold for the itemtype2 created" );
295
296
        $start_balance = $account->balance();
297
298
        Koha::Holds->find( $reserve_id )->cancel({ charge_cancel_fee => 1 });
299
300
        ( $status ) = CheckReserves($item2->id);
301
        is( $status, '', "Hold for the itemtype2 cancelled" );
302
303
        is( $account->balance() - $start_balance, 222, "Used ExpireReservesMaxPickUpDelayCharge preference as expire_reserves_charge set to undef" );
304
305
# TEST: no circulation rules for itemtype3, it should use ExpireReservesMaxPickUpDelayCharge preference
306
        t::lib::Mocks::mock_preference('ExpireReservesMaxPickUpDelayCharge', 333);
307
308
        $reserve_id = AddReserve(
309
            {
310
                branchcode       => $library_A_code,
311
                borrowernumber   => $borrowernumber,
312
                biblionumber     => $biblionumber,
313
                priority         => 1,
314
                itemnumber       => $item3->itemnumber,
315
            }
316
        );
317
318
        $account = Koha::Account->new({ patron_id => $borrowernumber });
319
320
        ( $status ) = CheckReserves($item3->id);
321
        is( $status, 'Reserved', "Hold for the itemtype3 created" );
322
323
        $start_balance = $account->balance();
324
325
        Koha::Holds->find( $reserve_id )->cancel({ charge_cancel_fee => 1 });
326
327
        ( $status ) = CheckReserves($item3->id);
328
        is( $status, '', "Hold for the itemtype3 cancelled" );
329
330
        is( $account->balance() - $start_balance, 333, "Used ExpireReservesMaxPickUpDelayCharge preference as there's no circulation rules for itemtype3" );
331
332
# TEST: circulation rule for itemtype4 with library_B_code
333
        t::lib::Mocks::mock_preference('ExpireReservesMaxPickUpDelayCharge', 555);
334
335
        $reserve_id = AddReserve(
336
            {
337
                branchcode       => $library_B_code,
338
                borrowernumber   => $borrowernumber,
339
                biblionumber     => $biblionumber2,
340
                priority         => 1,
341
                itemnumber       => $item4->itemnumber,
342
            }
343
        );
344
345
        $account = Koha::Account->new({ patron_id => $borrowernumber });
346
347
        ( $status ) = CheckReserves($item4->id);
348
        is( $status, 'Reserved', "Hold for the itemtype4 created" );
349
350
        $start_balance = $account->balance();
351
352
        Koha::Holds->find( $reserve_id )->cancel({ charge_cancel_fee => 1 });
353
354
        ( $status ) = CheckReserves($item4->id);
355
        is( $status, '', "Hold for the itemtype4 cancelled" );
356
357
        is( $account->balance() - $start_balance, 444, "Used circulation rule for itemtype4 with library_B_code" );
358
359
        $reserve_id = AddReserve(
360
            {
361
                branchcode       => $library_B_code,
362
                borrowernumber   => $borrowernumber,
363
                biblionumber     => $biblionumber2,
364
                priority         => 1,
365
                itemnumber       => $item4->itemnumber,
366
            }
367
        );
368
369
        $account = Koha::Account->new({ patron_id => $borrowernumber });
370
371
        ( $status ) = CheckReserves($item4->id);
372
        is( $status, 'Reserved', "Hold for the itemtype4 created" );
148
373
149
        # First, test cancelling a reserve when there's no charge configured.
374
        $start_balance = $account->balance();
150
        t::lib::Mocks::mock_preference('ExpireReservesMaxPickUpDelayCharge', 0);
151
        my $reserve_id = C4::Reserves::AddReserve( $hold_info );
152
        Koha::Holds->find( $reserve_id )->cancel( { charge_cancel_fee => 1 } );
153
        is( $patron->account->balance, 0, 'ExpireReservesMaxPickUpDelayCharge=0 - The patron should not have been charged' );
154
375
155
        # Then, test cancelling a reserve when there's no charge desired.
376
        Koha::Holds->find( $reserve_id )->cancel({ charge_cancel_fee => 0 });
156
        t::lib::Mocks::mock_preference('ExpireReservesMaxPickUpDelayCharge', 42);
157
        $reserve_id = C4::Reserves::AddReserve( $hold_info );
158
        Koha::Holds->find( $reserve_id )->cancel(); # charge_cancel_fee => 0
159
        is( $patron->account->balance, 0, 'ExpireReservesMaxPickUpDelayCharge=42, but charge_cancel_fee => 0, The patron should not have been charged' );
160
377
378
        ( $status ) = CheckReserves($item4->id);
379
        is( $status, '', "Hold for the itemtype4 cancelled" );
161
380
162
        # Finally, test cancelling a reserve when there's a charge desired and configured.
381
        is( $account->balance() - $start_balance, 0, "Patron not charged when charge_cancel_fee is 0" );
163
        t::lib::Mocks::mock_preference('ExpireReservesMaxPickUpDelayCharge', 42);
164
        $reserve_id = C4::Reserves::AddReserve( $hold_info );
165
        Koha::Holds->find( $reserve_id )->cancel( { charge_cancel_fee => 1 } );
166
        is( int($patron->account->balance), 42, 'ExpireReservesMaxPickUpDelayCharge=42 and charge_cancel_fee => 1, The patron should have been charged!' );
167
    };
382
    };
168
383
169
    subtest 'waiting hold' => sub {
384
    subtest 'waiting hold' => sub {
170
- 

Return to bug 25711