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

(-)a/t/db_dependent/Koha/Account/Line.t (+913 lines)
Line 0 Link Here
1
#!/usr/bin/perl
2
3
# Copyright 2018 Koha Development team
4
#
5
# This file is part of Koha
6
#
7
# Koha is free software; you can redistribute it and/or modify it
8
# under the terms of the GNU General Public License as published by
9
# the Free Software Foundation; either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# Koha is distributed in the hope that it will be useful, but
13
# WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General Public License
18
# along with Koha; if not, see <http://www.gnu.org/licenses>
19
20
use Modern::Perl;
21
22
use Test::More tests => 8;
23
use Test::Exception;
24
25
use C4::Circulation qw/AddIssue AddReturn/;
26
use Koha::Account;
27
use Koha::Account::Lines;
28
use Koha::Account::Offsets;
29
use Koha::Items;
30
31
use t::lib::Mocks;
32
use t::lib::TestBuilder;
33
34
my $schema = Koha::Database->new->schema;
35
my $builder = t::lib::TestBuilder->new;
36
37
subtest 'patron() tests' => sub {
38
39
    plan tests => 3;
40
41
    $schema->storage->txn_begin;
42
43
    my $library = $builder->build( { source => 'Branch' } );
44
    my $patron = $builder->build( { source => 'Borrower' } );
45
46
    my $line = Koha::Account::Line->new(
47
    {
48
        borrowernumber => $patron->{borrowernumber},
49
        debit_type_code    => "OVERDUE",
50
        status         => "RETURNED",
51
        amount         => 10,
52
        interface      => 'commandline',
53
    })->store;
54
55
    my $account_line_patron = $line->patron;
56
    is( ref( $account_line_patron ), 'Koha::Patron', 'Koha::Account::Line->patron should return a Koha::Patron' );
57
    is( $line->borrowernumber, $account_line_patron->borrowernumber, 'Koha::Account::Line->patron should return the correct borrower' );
58
59
    $line->borrowernumber(undef)->store;
60
    is( $line->patron, undef, 'Koha::Account::Line->patron should return undef if no patron linked' );
61
62
    $schema->storage->txn_rollback;
63
};
64
65
subtest 'item() tests' => sub {
66
67
    plan tests => 3;
68
69
    $schema->storage->txn_begin;
70
71
    my $library = $builder->build( { source => 'Branch' } );
72
    my $biblioitem = $builder->build( { source => 'Biblioitem' } );
73
    my $patron = $builder->build( { source => 'Borrower' } );
74
    my $item = Koha::Item->new(
75
    {
76
        biblionumber     => $biblioitem->{biblionumber},
77
        biblioitemnumber => $biblioitem->{biblioitemnumber},
78
        homebranch       => $library->{branchcode},
79
        holdingbranch    => $library->{branchcode},
80
        barcode          => 'some_barcode_12',
81
        itype            => 'BK',
82
    })->store;
83
84
    my $line = Koha::Account::Line->new(
85
    {
86
        borrowernumber => $patron->{borrowernumber},
87
        itemnumber     => $item->itemnumber,
88
        debit_type_code    => "OVERDUE",
89
        status         => "RETURNED",
90
        amount         => 10,
91
        interface      => 'commandline',
92
    })->store;
93
94
    my $account_line_item = $line->item;
95
    is( ref( $account_line_item ), 'Koha::Item', 'Koha::Account::Line->item should return a Koha::Item' );
96
    is( $line->itemnumber, $account_line_item->itemnumber, 'Koha::Account::Line->item should return the correct item' );
97
98
    $line->itemnumber(undef)->store;
99
    is( $line->item, undef, 'Koha::Account::Line->item should return undef if no item linked' );
100
101
    $schema->storage->txn_rollback;
102
};
103
104
subtest 'is_credit() and is_debit() tests' => sub {
105
106
    plan tests => 4;
107
108
    $schema->storage->txn_begin;
109
110
    my $patron  = $builder->build_object({ class => 'Koha::Patrons' });
111
    my $account = $patron->account;
112
113
    my $credit = $account->add_credit({ amount => 100, user_id => $patron->id, interface => 'commandline' });
114
115
    ok( $credit->is_credit, 'is_credit detects credits' );
116
    ok( !$credit->is_debit, 'is_debit detects credits' );
117
118
    my $debit = Koha::Account::Line->new(
119
    {
120
        borrowernumber => $patron->id,
121
        debit_type_code    => "OVERDUE",
122
        status         => "RETURNED",
123
        amount         => 10,
124
        interface      => 'commandline',
125
    })->store;
126
127
    ok( !$debit->is_credit, 'is_credit detects debits' );
128
    ok( $debit->is_debit, 'is_debit detects debits');
129
130
    $schema->storage->txn_rollback;
131
};
132
133
subtest 'apply() tests' => sub {
134
135
    plan tests => 24;
136
137
    $schema->storage->txn_begin;
138
139
    my $patron  = $builder->build_object( { class => 'Koha::Patrons' } );
140
    my $account = $patron->account;
141
142
    my $credit = $account->add_credit( { amount => 100, user_id => $patron->id, interface => 'commandline' } );
143
144
    my $debit_1 = Koha::Account::Line->new(
145
        {   borrowernumber    => $patron->id,
146
            debit_type_code       => "OVERDUE",
147
            status            => "RETURNED",
148
            amount            => 10,
149
            amountoutstanding => 10,
150
            interface         => 'commandline',
151
        }
152
    )->store;
153
154
    my $debit_2 = Koha::Account::Line->new(
155
        {   borrowernumber    => $patron->id,
156
            debit_type_code       => "OVERDUE",
157
            status            => "RETURNED",
158
            amount            => 100,
159
            amountoutstanding => 100,
160
            interface         => 'commandline',
161
        }
162
    )->store;
163
164
    $credit->discard_changes;
165
    $debit_1->discard_changes;
166
167
    my $debits = Koha::Account::Lines->search({ accountlines_id => $debit_1->id });
168
    my $remaining_credit = $credit->apply( { debits => [ $debits->as_list ], offset_type => 'Manual Credit' } );
169
    is( $remaining_credit * 1, 90, 'Remaining credit is correctly calculated' );
170
    $credit->discard_changes;
171
    is( $credit->amountoutstanding * -1, $remaining_credit, 'Remaining credit correctly stored' );
172
173
    # re-read debit info
174
    $debit_1->discard_changes;
175
    is( $debit_1->amountoutstanding * 1, 0, 'Debit has been cancelled' );
176
177
    my $offsets = Koha::Account::Offsets->search( { credit_id => $credit->id, debit_id => $debit_1->id } );
178
    is( $offsets->count, 1, 'Only one offset is generated' );
179
    my $THE_offset = $offsets->next;
180
    is( $THE_offset->amount * 1, -10, 'Amount was calculated correctly (less than the available credit)' );
181
    is( $THE_offset->type, 'Manual Credit', 'Passed type stored correctly' );
182
183
    $debits = Koha::Account::Lines->search({ accountlines_id => $debit_2->id });
184
    $remaining_credit = $credit->apply( { debits => [ $debits->as_list ] } );
185
    is( $remaining_credit, 0, 'No remaining credit left' );
186
    $credit->discard_changes;
187
    is( $credit->amountoutstanding * 1, 0, 'No outstanding credit' );
188
    $debit_2->discard_changes;
189
    is( $debit_2->amountoutstanding * 1, 10, 'Outstanding amount decremented correctly' );
190
191
    $offsets = Koha::Account::Offsets->search( { credit_id => $credit->id, debit_id => $debit_2->id } );
192
    is( $offsets->count, 1, 'Only one offset is generated' );
193
    $THE_offset = $offsets->next;
194
    is( $THE_offset->amount * 1, -90, 'Amount was calculated correctly (less than the available credit)' );
195
    is( $THE_offset->type, 'Credit Applied', 'Defaults to \'Credit Applied\' offset type' );
196
197
    $debits = Koha::Account::Lines->search({ accountlines_id => $debit_1->id });
198
    throws_ok
199
        { $credit->apply({ debits => [ $debits->as_list ] }); }
200
        'Koha::Exceptions::Account::NoAvailableCredit',
201
        '->apply() can only be used with outstanding credits';
202
203
    $debits = Koha::Account::Lines->search({ accountlines_id => $credit->id });
204
    throws_ok
205
        { $debit_1->apply({ debits => [ $debits->as_list ] }); }
206
        'Koha::Exceptions::Account::IsNotCredit',
207
        '->apply() can only be used with credits';
208
209
    $debits = Koha::Account::Lines->search({ accountlines_id => $credit->id });
210
    my $credit_3 = $account->add_credit({ amount => 1, interface => 'commandline' });
211
    throws_ok
212
        { $credit_3->apply({ debits => [ $debits->as_list ] }); }
213
        'Koha::Exceptions::Account::IsNotDebit',
214
        '->apply() can only be applied to credits';
215
216
    my $credit_2 = $account->add_credit({ amount => 20, interface => 'commandline' });
217
    my $debit_3  = Koha::Account::Line->new(
218
        {   borrowernumber    => $patron->id,
219
            debit_type_code       => "OVERDUE",
220
            status            => "RETURNED",
221
            amount            => 100,
222
            amountoutstanding => 100,
223
            interface         => 'commandline',
224
        }
225
    )->store;
226
227
    $debits = Koha::Account::Lines->search({ accountlines_id => { -in => [ $debit_1->id, $debit_2->id, $debit_3->id, $credit->id ] } });
228
    throws_ok {
229
        $credit_2->apply( { debits => [ $debits->as_list ], offset_type => 'Manual Credit' } ); }
230
        'Koha::Exceptions::Account::IsNotDebit',
231
        '->apply() rolls back if any of the passed lines is not a debit';
232
233
    is( $debit_1->discard_changes->amountoutstanding * 1,   0, 'No changes to already cancelled debit' );
234
    is( $debit_2->discard_changes->amountoutstanding * 1,  10, 'Debit cancelled' );
235
    is( $debit_3->discard_changes->amountoutstanding * 1, 100, 'Outstanding amount correctly calculated' );
236
    is( $credit_2->discard_changes->amountoutstanding * -1, 20, 'No changes made' );
237
238
    $debits = Koha::Account::Lines->search({ accountlines_id => { -in => [ $debit_1->id, $debit_2->id, $debit_3->id ] } });
239
    $remaining_credit = $credit_2->apply( { debits => [ $debits->as_list ], offset_type => 'Manual Credit' } );
240
241
    is( $debit_1->discard_changes->amountoutstanding * 1,  0, 'No changes to already cancelled debit' );
242
    is( $debit_2->discard_changes->amountoutstanding * 1,  0, 'Debit cancelled' );
243
    is( $debit_3->discard_changes->amountoutstanding * 1, 90, 'Outstanding amount correctly calculated' );
244
    is( $credit_2->discard_changes->amountoutstanding * 1, 0, 'No remaining credit' );
245
246
    $schema->storage->txn_rollback;
247
};
248
249
subtest 'Keep account info when related patron, staff, item or cash_register is deleted' => sub {
250
251
    plan tests => 4;
252
253
    $schema->storage->txn_begin;
254
255
    my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
256
    my $staff = $builder->build_object( { class => 'Koha::Patrons' } );
257
    my $item = $builder->build_object({ class => 'Koha::Items' });
258
    my $issue = $builder->build_object(
259
        {
260
            class => 'Koha::Checkouts',
261
            value => { itemnumber => $item->itemnumber }
262
        }
263
    );
264
    my $register = $builder->build_object({ class => 'Koha::Cash::Registers' });
265
266
    my $line = Koha::Account::Line->new(
267
    {
268
        borrowernumber => $patron->borrowernumber,
269
        manager_id     => $staff->borrowernumber,
270
        itemnumber     => $item->itemnumber,
271
        debit_type_code    => "OVERDUE",
272
        status         => "RETURNED",
273
        amount         => 10,
274
        interface      => 'commandline',
275
        register_id    => $register->id
276
    })->store;
277
278
    $issue->delete;
279
    $item->delete;
280
    $line = $line->get_from_storage;
281
    is( $line->itemnumber, undef, "The account line should not be deleted when the related item is delete");
282
283
    $staff->delete;
284
    $line = $line->get_from_storage;
285
    is( $line->manager_id, undef, "The account line should not be deleted when the related staff is delete");
286
287
    $patron->delete;
288
    $line = $line->get_from_storage;
289
    is( $line->borrowernumber, undef, "The account line should not be deleted when the related patron is delete");
290
291
    $register->delete;
292
    $line = $line->get_from_storage;
293
    is( $line->register_id, undef, "The account line should not be deleted when the related cash register is delete");
294
295
    $schema->storage->txn_rollback;
296
};
297
298
subtest 'adjust() tests' => sub {
299
300
    plan tests => 29;
301
302
    $schema->storage->txn_begin;
303
304
    # count logs before any actions
305
    my $action_logs = $schema->resultset('ActionLog')->search()->count;
306
307
    # Disable logs
308
    t::lib::Mocks::mock_preference( 'FinesLog', 0 );
309
310
    my $patron  = $builder->build_object( { class => 'Koha::Patrons' } );
311
    my $account = $patron->account;
312
313
    my $debit_1 = Koha::Account::Line->new(
314
        {   borrowernumber    => $patron->id,
315
            debit_type_code       => "OVERDUE",
316
            status            => "RETURNED",
317
            amount            => 10,
318
            amountoutstanding => 10,
319
            interface         => 'commandline',
320
        }
321
    )->store;
322
323
    my $debit_2 = Koha::Account::Line->new(
324
        {   borrowernumber    => $patron->id,
325
            debit_type_code       => "OVERDUE",
326
            status            => "UNRETURNED",
327
            amount            => 100,
328
            amountoutstanding => 100,
329
            interface         => 'commandline'
330
        }
331
    )->store;
332
333
    my $credit = $account->add_credit( { amount => 40, user_id => $patron->id, interface => 'commandline' } );
334
335
    throws_ok { $debit_1->adjust( { amount => 50, type => 'bad', interface => 'commandline' } ) }
336
    qr/Update type not recognised/, 'Exception thrown for unrecognised type';
337
338
    throws_ok { $debit_1->adjust( { amount => 50, type => 'overdue_update', interface => 'commandline' } ) }
339
    qr/Update type not allowed on this debit_type/,
340
      'Exception thrown for type conflict';
341
342
    # Increment an unpaid fine
343
    $debit_2->adjust( { amount => 150, type => 'overdue_update', interface => 'commandline' } )->discard_changes;
344
345
    is( $debit_2->amount * 1, 150, 'Fine amount was updated in full' );
346
    is( $debit_2->amountoutstanding * 1, 150, 'Fine amountoutstanding was update in full' );
347
    isnt( $debit_2->date, undef, 'Date has been set' );
348
349
    my $offsets = Koha::Account::Offsets->search( { debit_id => $debit_2->id } );
350
    is( $offsets->count, 1, 'An offset is generated for the increment' );
351
    my $THIS_offset = $offsets->next;
352
    is( $THIS_offset->amount * 1, 50, 'Amount was calculated correctly (increment by 50)' );
353
    is( $THIS_offset->type, 'OVERDUE_INCREASE', 'Adjust type stored correctly' );
354
355
    is( $schema->resultset('ActionLog')->count(), $action_logs + 0, 'No log was added' );
356
357
    # Update fine to partially paid
358
    my $debits = Koha::Account::Lines->search({ accountlines_id => $debit_2->id });
359
    $credit->apply( { debits => [ $debits->as_list ], offset_type => 'Manual Credit' } );
360
361
    $debit_2->discard_changes;
362
    is( $debit_2->amount * 1, 150, 'Fine amount unaffected by partial payment' );
363
    is( $debit_2->amountoutstanding * 1, 110, 'Fine amountoutstanding updated by partial payment' );
364
365
    # Enable logs
366
    t::lib::Mocks::mock_preference( 'FinesLog', 1 );
367
368
    # Increment the partially paid fine
369
    $debit_2->adjust( { amount => 160, type => 'overdue_update', interface => 'commandline' } )->discard_changes;
370
371
    is( $debit_2->amount * 1, 160, 'Fine amount was updated in full' );
372
    is( $debit_2->amountoutstanding * 1, 120, 'Fine amountoutstanding was updated by difference' );
373
374
    $offsets = Koha::Account::Offsets->search( { debit_id => $debit_2->id } );
375
    is( $offsets->count, 3, 'An offset is generated for the increment' );
376
    $THIS_offset = $offsets->last;
377
    is( $THIS_offset->amount * 1, 10, 'Amount was calculated correctly (increment by 10)' );
378
    is( $THIS_offset->type, 'OVERDUE_INCREASE', 'Adjust type stored correctly' );
379
380
    is( $schema->resultset('ActionLog')->count(), $action_logs + 1, 'Log was added' );
381
382
    # Decrement the partially paid fine, less than what was paid
383
    $debit_2->adjust( { amount => 50, type => 'overdue_update', interface => 'commandline' } )->discard_changes;
384
385
    is( $debit_2->amount * 1, 50, 'Fine amount was updated in full' );
386
    is( $debit_2->amountoutstanding * 1, 10, 'Fine amountoutstanding was updated by difference' );
387
388
    $offsets = Koha::Account::Offsets->search( { debit_id => $debit_2->id } );
389
    is( $offsets->count, 4, 'An offset is generated for the decrement' );
390
    $THIS_offset = $offsets->last;
391
    is( $THIS_offset->amount * 1, -110, 'Amount was calculated correctly (decrement by 110)' );
392
    is( $THIS_offset->type, 'OVERDUE_DECREASE', 'Adjust type stored correctly' );
393
394
    # Decrement the partially paid fine, more than what was paid
395
    $debit_2->adjust( { amount => 30, type => 'overdue_update', interface => 'commandline' } )->discard_changes;
396
    is( $debit_2->amount * 1, 30, 'Fine amount was updated in full' );
397
    is( $debit_2->amountoutstanding * 1, 0, 'Fine amountoutstanding was zeroed (payment was 40)' );
398
399
    $offsets = Koha::Account::Offsets->search( { debit_id => $debit_2->id } );
400
    is( $offsets->count, 5, 'An offset is generated for the decrement' );
401
    $THIS_offset = $offsets->last;
402
    is( $THIS_offset->amount * 1, -20, 'Amount was calculated correctly (decrement by 20)' );
403
    is( $THIS_offset->type, 'OVERDUE_DECREASE', 'Adjust type stored correctly' );
404
405
    my $overpayment_refund = $account->lines->last;
406
    is( $overpayment_refund->amount * 1, -10, 'A new credit has been added' );
407
    is( $overpayment_refund->description, 'Overpayment refund', 'Credit generated with the expected description' );
408
409
    $schema->storage->txn_rollback;
410
};
411
412
subtest 'checkout() tests' => sub {
413
    plan tests => 6;
414
415
    $schema->storage->txn_begin;
416
417
    my $library = $builder->build_object( { class => 'Koha::Libraries' } );
418
    my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
419
    my $item = $builder->build_sample_item;
420
    my $account = $patron->account;
421
422
    t::lib::Mocks::mock_userenv({ branchcode => $library->branchcode });
423
    my $checkout = AddIssue( $patron->unblessed, $item->barcode );
424
425
    my $line = $account->add_debit({
426
        amount    => 10,
427
        interface => 'commandline',
428
        item_id   => $item->itemnumber,
429
        issue_id  => $checkout->issue_id,
430
        type      => 'OVERDUE',
431
    });
432
433
    my $line_checkout = $line->checkout;
434
    is( ref($line_checkout), 'Koha::Checkout', 'Result type is correct' );
435
    is( $line_checkout->issue_id, $checkout->issue_id, 'Koha::Account::Line->checkout should return the correct checkout');
436
437
    my ( $returned, undef, $old_checkout) = C4::Circulation::AddReturn( $item->barcode, $library->branchcode );
438
    is( $returned, 1, 'The item should have been returned' );
439
440
    $line = $line->get_from_storage;
441
    my $old_line_checkout = $line->checkout;
442
    is( ref($old_line_checkout), 'Koha::Old::Checkout', 'Result type is correct' );
443
    is( $old_line_checkout->issue_id, $old_checkout->issue_id, 'Koha::Account::Line->checkout should return the correct old_checkout' );
444
445
    $line->issue_id(undef)->store;
446
    is( $line->checkout, undef, 'Koha::Account::Line->checkout should return undef if no checkout linked' );
447
448
    $schema->storage->txn_rollback;
449
};
450
451
subtest 'credits() and debits() tests' => sub {
452
    plan tests => 10;
453
454
    $schema->storage->txn_begin;
455
456
    my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
457
    my $account = $patron->account;
458
459
    my $debit1 = $account->add_debit({
460
        amount    => 8,
461
        interface => 'commandline',
462
        type      => 'ACCOUNT',
463
    });
464
    my $debit2 = $account->add_debit({
465
        amount    => 12,
466
        interface => 'commandline',
467
        type      => 'ACCOUNT',
468
    });
469
    my $credit1 = $account->add_credit({
470
        amount    => 5,
471
        interface => 'commandline',
472
        type      => 'CREDIT',
473
    });
474
    my $credit2 = $account->add_credit({
475
        amount    => 10,
476
        interface => 'commandline',
477
        type      => 'CREDIT',
478
    });
479
480
    $credit1->apply({ debits => [ $debit1 ] });
481
    $credit2->apply({ debits => [ $debit1, $debit2 ] });
482
483
    my $credits = $debit1->credits;
484
    is($credits->count, 2, '2 Credits applied to debit 1');
485
    my $credit = $credits->next;
486
    is($credit->amount + 0, -5, 'Correct first credit');
487
    $credit = $credits->next;
488
    is($credit->amount + 0, -10, 'Correct second credit');
489
490
    $credits = $debit2->credits;
491
    is($credits->count, 1, '1 Credits applied to debit 2');
492
    $credit = $credits->next;
493
    is($credit->amount + 0, -10, 'Correct first credit');
494
495
    my $debits = $credit1->debits;
496
    is($debits->count, 1, 'Credit 1 applied to 1 debit');
497
    my $debit = $debits->next;
498
    is($debit->amount + 0, 8, 'Correct first debit');
499
500
    $debits = $credit2->debits;
501
    is($debits->count, 2, 'Credit 2 applied to 2 debits');
502
    $debit = $debits->next;
503
    is($debit->amount + 0, 8, 'Correct first debit');
504
    $debit = $debits->next;
505
    is($debit->amount + 0, 12, 'Correct second debit');
506
507
    $schema->storage->txn_rollback;
508
};
509
510
subtest "void() tests" => sub {
511
512
    plan tests => 16;
513
514
    $schema->storage->txn_begin;
515
516
    # Create a borrower
517
    my $categorycode = $builder->build({ source => 'Category' })->{ categorycode };
518
    my $branchcode   = $builder->build({ source => 'Branch' })->{ branchcode };
519
520
    my $borrower = Koha::Patron->new( {
521
        cardnumber => 'dariahall',
522
        surname => 'Hall',
523
        firstname => 'Daria',
524
    } );
525
    $borrower->categorycode( $categorycode );
526
    $borrower->branchcode( $branchcode );
527
    $borrower->store;
528
529
    my $account = Koha::Account->new({ patron_id => $borrower->id });
530
531
    my $line1 = Koha::Account::Line->new(
532
        {
533
            borrowernumber    => $borrower->borrowernumber,
534
            amount            => 10,
535
            amountoutstanding => 10,
536
            interface         => 'commandline',
537
            debit_type_code   => 'OVERDUE'
538
        }
539
    )->store();
540
    my $line2 = Koha::Account::Line->new(
541
        {
542
            borrowernumber    => $borrower->borrowernumber,
543
            amount            => 20,
544
            amountoutstanding => 20,
545
            interface         => 'commandline',
546
            debit_type_code   => 'OVERDUE'
547
        }
548
    )->store();
549
550
    is( $account->balance(), 30, "Account balance is 30" );
551
    is( $line1->amountoutstanding, 10, 'First fee has amount outstanding of 10' );
552
    is( $line2->amountoutstanding, 20, 'Second fee has amount outstanding of 20' );
553
554
    my $id = $account->pay(
555
        {
556
            lines  => [$line1, $line2],
557
            amount => 30,
558
        }
559
    );
560
561
    my $account_payment = Koha::Account::Lines->find( $id );
562
563
    is( $account->balance(), 0, "Account balance is 0" );
564
565
    $line1->_result->discard_changes();
566
    $line2->_result->discard_changes();
567
    is( $line1->amountoutstanding+0, 0, 'First fee has amount outstanding of 0' );
568
    is( $line2->amountoutstanding+0, 0, 'Second fee has amount outstanding of 0' );
569
570
    my $ret = $account_payment->void();
571
572
    is( ref($ret), 'Koha::Account::Line', 'Void returns the account line' );
573
    is( $account->balance(), 30, "Account balance is again 30" );
574
575
    $account_payment->_result->discard_changes();
576
    $line1->_result->discard_changes();
577
    $line2->_result->discard_changes();
578
579
    is( $account_payment->credit_type_code, 'PAYMENT', 'Voided payment credit_type_code is still PAYMENT' );
580
    is( $account_payment->status, 'VOID', 'Voided payment status is VOID' );
581
    is( $account_payment->amount+0, 0, 'Voided payment amount is 0' );
582
    is( $account_payment->amountoutstanding+0, 0, 'Voided payment amount outstanding is 0' );
583
584
    is( $line1->amountoutstanding+0, 10, 'First fee again has amount outstanding of 10' );
585
    is( $line2->amountoutstanding+0, 20, 'Second fee again has amount outstanding of 20' );
586
587
    # Accountlines that are not credits should be un-voidable
588
    my $line1_pre = $line1->unblessed();
589
    $ret = $line1->void();
590
    $line1->_result->discard_changes();
591
    my $line1_post = $line1->unblessed();
592
    is( $ret, undef, 'Attempted void on non-credit returns undef' );
593
    is_deeply( $line1_pre, $line1_post, 'Non-credit account line cannot be voided' );
594
595
    $schema->storage->txn_rollback;
596
};
597
598
subtest "payout() tests" => sub {
599
600
    plan tests => 18;
601
602
    $schema->storage->txn_begin;
603
604
    # Create a borrower
605
    my $categorycode =
606
      $builder->build( { source => 'Category' } )->{categorycode};
607
    my $branchcode = $builder->build( { source => 'Branch' } )->{branchcode};
608
609
    my $borrower = Koha::Patron->new(
610
        {
611
            cardnumber => 'dariahall',
612
            surname    => 'Hall',
613
            firstname  => 'Daria',
614
        }
615
    );
616
    $borrower->categorycode($categorycode);
617
    $borrower->branchcode($branchcode);
618
    $borrower->store;
619
620
    my $staff = Koha::Patron->new(
621
        {
622
            cardnumber => 'bobby',
623
            surname    => 'Bloggs',
624
            firstname  => 'Bobby',
625
        }
626
    );
627
    $staff->categorycode($categorycode);
628
    $staff->branchcode($branchcode);
629
    $staff->store;
630
631
    my $account = Koha::Account->new( { patron_id => $borrower->id } );
632
633
    my $debit1 = Koha::Account::Line->new(
634
        {
635
            borrowernumber    => $borrower->borrowernumber,
636
            amount            => 10,
637
            amountoutstanding => 10,
638
            interface         => 'commandline',
639
            debit_type_code   => 'OVERDUE'
640
        }
641
    )->store();
642
    my $credit1 = Koha::Account::Line->new(
643
        {
644
            borrowernumber    => $borrower->borrowernumber,
645
            amount            => -20,
646
            amountoutstanding => -20,
647
            interface         => 'commandline',
648
            credit_type_code  => 'CREDIT'
649
        }
650
    )->store();
651
652
    is( $account->balance(), -10, "Account balance is -10" );
653
    is( $debit1->amountoutstanding + 0,
654
        10, 'Overdue fee has an amount outstanding of 10' );
655
    is( $credit1->amountoutstanding + 0,
656
        -20, 'Credit has an amount outstanding of -20' );
657
658
    my $pay_params = {
659
        interface   => 'intranet',
660
        staff_id    => $staff->borrowernumber,
661
        branch      => $branchcode,
662
        payout_type => 'CASH',
663
        amount      => 20
664
    };
665
666
    throws_ok { $debit1->payout($pay_params); }
667
    'Koha::Exceptions::Account::IsNotCredit',
668
      '->payout() can only be used with credits';
669
670
    my @required =
671
      ( 'interface', 'staff_id', 'branch', 'payout_type', 'amount' );
672
    for my $required (@required) {
673
        my $params = {%$pay_params};
674
        delete( $params->{$required} );
675
        throws_ok {
676
            $credit1->payout($params);
677
        }
678
        'Koha::Exceptions::MissingParameter',
679
          "->payout() requires the `$required` parameter is passed";
680
    }
681
682
    throws_ok {
683
        $credit1->payout(
684
            {
685
                interface   => 'intranet',
686
                staff_id    => $staff->borrowernumber,
687
                branch      => $branchcode,
688
                payout_type => 'CASH',
689
                amount      => 25
690
            }
691
        );
692
    }
693
    'Koha::Exceptions::ParameterTooHigh',
694
      '->payout() cannot pay out more than the amountoutstanding';
695
696
    t::lib::Mocks::mock_preference( 'UseCashRegisters', 1 );
697
    throws_ok {
698
        $credit1->payout(
699
            {
700
                interface   => 'intranet',
701
                staff_id    => $staff->borrowernumber,
702
                branch      => $branchcode,
703
                payout_type => 'CASH',
704
                amount      => 10
705
            }
706
        );
707
    }
708
    'Koha::Exceptions::Account::RegisterRequired',
709
      '->payout() requires a cash_register if payout_type is `CASH`';
710
711
    t::lib::Mocks::mock_preference( 'UseCashRegisters', 0 );
712
    my $payout = $credit1->payout(
713
        {
714
            interface   => 'intranet',
715
            staff_id    => $staff->borrowernumber,
716
            branch      => $branchcode,
717
            payout_type => 'CASH',
718
            amount      => 10
719
        }
720
    );
721
722
    is( ref($payout), 'Koha::Account::Line',
723
        '->payout() returns a Koha::Account::Line' );
724
    is( $payout->amount() + 0,            10, "Payout amount is 10" );
725
    is( $payout->amountoutstanding() + 0, 0,  "Payout amountoutstanding is 0" );
726
    is( $account->balance() + 0,          0,  "Account balance is 0" );
727
    is( $debit1->amountoutstanding + 0,
728
        10, 'Overdue fee still has an amount outstanding of 10' );
729
    is( $credit1->amountoutstanding + 0,
730
        -10, 'Credit has an new amount outstanding of -10' );
731
    is( $credit1->status(), 'PAID', "Credit has a new status of PAID" );
732
733
    $schema->storage->txn_rollback;
734
};
735
736
subtest "reduce() tests" => sub {
737
738
    plan tests => 27;
739
740
    $schema->storage->txn_begin;
741
742
    # Create a borrower
743
    my $categorycode =
744
      $builder->build( { source => 'Category' } )->{categorycode};
745
    my $branchcode = $builder->build( { source => 'Branch' } )->{branchcode};
746
747
    my $borrower = Koha::Patron->new(
748
        {
749
            cardnumber => 'dariahall',
750
            surname    => 'Hall',
751
            firstname  => 'Daria',
752
        }
753
    );
754
    $borrower->categorycode($categorycode);
755
    $borrower->branchcode($branchcode);
756
    $borrower->store;
757
758
    my $staff = Koha::Patron->new(
759
        {
760
            cardnumber => 'bobby',
761
            surname    => 'Bloggs',
762
            firstname  => 'Bobby',
763
        }
764
    );
765
    $staff->categorycode($categorycode);
766
    $staff->branchcode($branchcode);
767
    $staff->store;
768
769
    my $account = Koha::Account->new( { patron_id => $borrower->id } );
770
771
    my $debit1 = Koha::Account::Line->new(
772
        {
773
            borrowernumber    => $borrower->borrowernumber,
774
            amount            => 20,
775
            amountoutstanding => 20,
776
            interface         => 'commandline',
777
            debit_type_code   => 'LOST'
778
        }
779
    )->store();
780
    my $credit1 = Koha::Account::Line->new(
781
        {
782
            borrowernumber    => $borrower->borrowernumber,
783
            amount            => -20,
784
            amountoutstanding => -20,
785
            interface         => 'commandline',
786
            credit_type_code  => 'CREDIT'
787
        }
788
    )->store();
789
790
    is( $account->balance(), 0, "Account balance is 0" );
791
    is( $debit1->amountoutstanding,
792
        20, 'Overdue fee has an amount outstanding of 20' );
793
    is( $credit1->amountoutstanding,
794
        -20, 'Credit has an amount outstanding of -20' );
795
796
    my $reduce_params = {
797
        interface      => 'commandline',
798
        reduction_type => 'REFUND',
799
        amount         => 5,
800
        staff_id       => $staff->borrowernumber,
801
        branch         => $branchcode
802
    };
803
804
    throws_ok { $credit1->reduce($reduce_params); }
805
    'Koha::Exceptions::Account::IsNotDebit',
806
      '->reduce() can only be used with debits';
807
808
    my @required = ( 'interface', 'reduction_type', 'amount' );
809
    for my $required (@required) {
810
        my $params = {%$reduce_params};
811
        delete( $params->{$required} );
812
        throws_ok {
813
            $debit1->reduce($params);
814
        }
815
        'Koha::Exceptions::MissingParameter',
816
          "->reduce() requires the `$required` parameter is passed";
817
    }
818
819
    $reduce_params->{interface} = 'intranet';
820
    my @dependant_required = ( 'staff_id', 'branch' );
821
    for my $d (@dependant_required) {
822
        my $params = {%$reduce_params};
823
        delete( $params->{$d} );
824
        throws_ok {
825
            $debit1->reduce($params);
826
        }
827
        'Koha::Exceptions::MissingParameter',
828
"->reduce() requires the `$d` parameter is passed when interface is intranet";
829
    }
830
831
    throws_ok {
832
        $debit1->reduce(
833
            {
834
                interface      => 'intranet',
835
                staff_id       => $staff->borrowernumber,
836
                branch         => $branchcode,
837
                reduction_type => 'REFUND',
838
                amount         => 25
839
            }
840
        );
841
    }
842
    'Koha::Exceptions::ParameterTooHigh',
843
      '->reduce() cannot reduce more than original amount';
844
845
    # Partial Reduction
846
    # (Refund 5 on debt of 20)
847
    my $reduction = $debit1->reduce($reduce_params);
848
849
    is( ref($reduction), 'Koha::Account::Line',
850
        '->reduce() returns a Koha::Account::Line' );
851
    is( $reduction->amount() * 1, -5, "Reduce amount is -5" );
852
    is( $reduction->amountoutstanding() * 1,
853
        0, "Reduce amountoutstanding is 0" );
854
    is( $debit1->amountoutstanding() * 1,
855
        15, "Debit amountoutstanding reduced by 5 to 15" );
856
    is( $account->balance() * 1, -5,        "Account balance is -5" );
857
    is( $reduction->status(),    'APPLIED', "Reduction status is 'APPLIED'" );
858
859
    my $offsets = Koha::Account::Offsets->search(
860
        { credit_id => $reduction->id, debit_id => $debit1->id } );
861
    is( $offsets->count, 1, 'Only one offset is generated' );
862
    my $THE_offset = $offsets->next;
863
    is( $THE_offset->amount * 1,
864
        -5, 'Correct amount was applied against debit' );
865
    is( $THE_offset->type, 'REFUND', "Offset type set to 'REFUND'" );
866
867
    # Zero offset created when zero outstanding
868
    # (Refund another 5 on paid debt of 20)
869
    $credit1->apply( { debits => [$debit1] } );
870
    is( $debit1->amountoutstanding + 0,
871
        0, 'Debit1 amountoutstanding reduced to 0' );
872
    $reduction = $debit1->reduce($reduce_params);
873
    is( $reduction->amount() * 1, -5, "Reduce amount is -5" );
874
    is( $reduction->amountoutstanding() * 1,
875
        -5, "Reduce amountoutstanding is -5" );
876
877
    $offsets = Koha::Account::Offsets->search(
878
        { credit_id => $reduction->id, debit_id => $debit1->id } );
879
    is( $offsets->count, 1, 'Only one new offset is generated' );
880
    $THE_offset = $offsets->next;
881
    is( $THE_offset->amount * 1,
882
        0, 'Zero offset created for already paid off debit' );
883
    is( $THE_offset->type, 'REFUND', "Offset type set to 'REFUND'" );
884
885
    # Compound reduction should not allow more than original amount
886
    # (Reduction of 5 + 5 + 20 > 20)
887
    $reduce_params->{amount} = 20;
888
    throws_ok {
889
        $debit1->reduce($reduce_params);
890
    }
891
    'Koha::Exceptions::ParameterTooHigh',
892
'->reduce cannot reduce more than the original amount (combined reductions test)';
893
894
    # Throw exception if attempting to reduce a payout
895
    my $payout = $reduction->payout(
896
        {
897
            interface   => 'intranet',
898
            staff_id    => $staff->borrowernumber,
899
            branch      => $branchcode,
900
            payout_type => 'CASH',
901
            amount      => 5
902
        }
903
    );
904
    throws_ok {
905
        $payout->reduce($reduce_params);
906
    }
907
    'Koha::Exceptions::Account::IsNotDebit',
908
      '->reduce() cannot be used on a payout debit';
909
910
    $schema->storage->txn_rollback;
911
};
912
913
1;
(-)a/t/db_dependent/Koha/Account/Lines.t (-886 / +6 lines)
Lines 19-32 Link Here
19
19
20
use Modern::Perl;
20
use Modern::Perl;
21
21
22
use Test::More tests => 15;
22
use Test::More tests => 4;
23
use Test::Exception;
23
use Test::Exception;
24
24
25
use C4::Circulation qw/AddIssue AddReturn/;
26
use Koha::Account;
25
use Koha::Account;
27
use Koha::Account::Lines;
26
use Koha::Account::Lines;
28
use Koha::Account::Offsets;
27
use Koha::Account::Offsets;
29
use Koha::Items;
30
28
31
use t::lib::Mocks;
29
use t::lib::Mocks;
32
use t::lib::TestBuilder;
30
use t::lib::TestBuilder;
Lines 34-107 use t::lib::TestBuilder; Link Here
34
my $schema = Koha::Database->new->schema;
32
my $schema = Koha::Database->new->schema;
35
my $builder = t::lib::TestBuilder->new;
33
my $builder = t::lib::TestBuilder->new;
36
34
37
subtest 'patron() tests' => sub {
38
39
    plan tests => 3;
40
41
    $schema->storage->txn_begin;
42
43
    my $library = $builder->build( { source => 'Branch' } );
44
    my $patron = $builder->build( { source => 'Borrower' } );
45
46
    my $line = Koha::Account::Line->new(
47
    {
48
        borrowernumber => $patron->{borrowernumber},
49
        debit_type_code    => "OVERDUE",
50
        status         => "RETURNED",
51
        amount         => 10,
52
        interface      => 'commandline',
53
    })->store;
54
55
    my $account_line_patron = $line->patron;
56
    is( ref( $account_line_patron ), 'Koha::Patron', 'Koha::Account::Line->patron should return a Koha::Patron' );
57
    is( $line->borrowernumber, $account_line_patron->borrowernumber, 'Koha::Account::Line->patron should return the correct borrower' );
58
59
    $line->borrowernumber(undef)->store;
60
    is( $line->patron, undef, 'Koha::Account::Line->patron should return undef if no patron linked' );
61
62
    $schema->storage->txn_rollback;
63
};
64
65
66
subtest 'item() tests' => sub {
67
68
    plan tests => 3;
69
70
    $schema->storage->txn_begin;
71
72
    my $library = $builder->build( { source => 'Branch' } );
73
    my $biblioitem = $builder->build( { source => 'Biblioitem' } );
74
    my $patron = $builder->build( { source => 'Borrower' } );
75
    my $item = Koha::Item->new(
76
    {
77
        biblionumber     => $biblioitem->{biblionumber},
78
        biblioitemnumber => $biblioitem->{biblioitemnumber},
79
        homebranch       => $library->{branchcode},
80
        holdingbranch    => $library->{branchcode},
81
        barcode          => 'some_barcode_12',
82
        itype            => 'BK',
83
    })->store;
84
85
    my $line = Koha::Account::Line->new(
86
    {
87
        borrowernumber => $patron->{borrowernumber},
88
        itemnumber     => $item->itemnumber,
89
        debit_type_code    => "OVERDUE",
90
        status         => "RETURNED",
91
        amount         => 10,
92
        interface      => 'commandline',
93
    })->store;
94
95
    my $account_line_item = $line->item;
96
    is( ref( $account_line_item ), 'Koha::Item', 'Koha::Account::Line->item should return a Koha::Item' );
97
    is( $line->itemnumber, $account_line_item->itemnumber, 'Koha::Account::Line->item should return the correct item' );
98
99
    $line->itemnumber(undef)->store;
100
    is( $line->item, undef, 'Koha::Account::Line->item should return undef if no item linked' );
101
102
    $schema->storage->txn_rollback;
103
};
104
105
subtest 'total_outstanding() tests' => sub {
35
subtest 'total_outstanding() tests' => sub {
106
36
107
    plan tests => 5;
37
    plan tests => 5;
Lines 115-121 subtest 'total_outstanding() tests' => sub { Link Here
115
45
116
    my $debit_1 = Koha::Account::Line->new(
46
    my $debit_1 = Koha::Account::Line->new(
117
        {   borrowernumber    => $patron->id,
47
        {   borrowernumber    => $patron->id,
118
            debit_type_code       => "OVERDUE",
48
            debit_type_code   => "OVERDUE",
119
            status            => "RETURNED",
49
            status            => "RETURNED",
120
            amount            => 10,
50
            amount            => 10,
121
            amountoutstanding => 10,
51
            amountoutstanding => 10,
Lines 125-131 subtest 'total_outstanding() tests' => sub { Link Here
125
55
126
    my $debit_2 = Koha::Account::Line->new(
56
    my $debit_2 = Koha::Account::Line->new(
127
        {   borrowernumber    => $patron->id,
57
        {   borrowernumber    => $patron->id,
128
            debit_type_code       => "OVERDUE",
58
            debit_type_code   => "OVERDUE",
129
            status            => "RETURNED",
59
            status            => "RETURNED",
130
            amount            => 10,
60
            amount            => 10,
131
            amountoutstanding => 10,
61
            amountoutstanding => 10,
Lines 138-144 subtest 'total_outstanding() tests' => sub { Link Here
138
68
139
    my $credit_1 = Koha::Account::Line->new(
69
    my $credit_1 = Koha::Account::Line->new(
140
        {   borrowernumber    => $patron->id,
70
        {   borrowernumber    => $patron->id,
141
            debit_type_code       => "OVERDUE",
71
            debit_type_code   => "OVERDUE",
142
            status            => "RETURNED",
72
            status            => "RETURNED",
143
            amount            => -10,
73
            amount            => -10,
144
            amountoutstanding => -10,
74
            amountoutstanding => -10,
Lines 151-157 subtest 'total_outstanding() tests' => sub { Link Here
151
81
152
    my $credit_2 = Koha::Account::Line->new(
82
    my $credit_2 = Koha::Account::Line->new(
153
        {   borrowernumber    => $patron->id,
83
        {   borrowernumber    => $patron->id,
154
            debit_type_code       => "OVERDUE",
84
            debit_type_code   => "OVERDUE",
155
            status            => "RETURNED",
85
            status            => "RETURNED",
156
            amount            => -10,
86
            amount            => -10,
157
            amountoutstanding => -10,
87
            amountoutstanding => -10,
Lines 164-170 subtest 'total_outstanding() tests' => sub { Link Here
164
94
165
    my $credit_3 = Koha::Account::Line->new(
95
    my $credit_3 = Koha::Account::Line->new(
166
        {   borrowernumber    => $patron->id,
96
        {   borrowernumber    => $patron->id,
167
            debit_type_code       => "OVERDUE",
97
            debit_type_code   => "OVERDUE",
168
            status            => "RETURNED",
98
            status            => "RETURNED",
169
            amount            => -100,
99
            amount            => -100,
170
            amountoutstanding => -100,
100
            amountoutstanding => -100,
Lines 406-1218 subtest 'debits_total() tests' => sub { Link Here
406
    $schema->storage->txn_rollback;
336
    $schema->storage->txn_rollback;
407
};
337
};
408
338
409
subtest 'is_credit() and is_debit() tests' => sub {
410
411
    plan tests => 4;
412
413
    $schema->storage->txn_begin;
414
415
    my $patron  = $builder->build_object({ class => 'Koha::Patrons' });
416
    my $account = $patron->account;
417
418
    my $credit = $account->add_credit({ amount => 100, user_id => $patron->id, interface => 'commandline' });
419
420
    ok( $credit->is_credit, 'is_credit detects credits' );
421
    ok( !$credit->is_debit, 'is_debit detects credits' );
422
423
    my $debit = Koha::Account::Line->new(
424
    {
425
        borrowernumber => $patron->id,
426
        debit_type_code    => "OVERDUE",
427
        status         => "RETURNED",
428
        amount         => 10,
429
        interface      => 'commandline',
430
    })->store;
431
432
    ok( !$debit->is_credit, 'is_credit detects debits' );
433
    ok( $debit->is_debit, 'is_debit detects debits');
434
435
    $schema->storage->txn_rollback;
436
};
437
438
subtest 'apply() tests' => sub {
439
440
    plan tests => 24;
441
442
    $schema->storage->txn_begin;
443
444
    my $patron  = $builder->build_object( { class => 'Koha::Patrons' } );
445
    my $account = $patron->account;
446
447
    my $credit = $account->add_credit( { amount => 100, user_id => $patron->id, interface => 'commandline' } );
448
449
    my $debit_1 = Koha::Account::Line->new(
450
        {   borrowernumber    => $patron->id,
451
            debit_type_code       => "OVERDUE",
452
            status            => "RETURNED",
453
            amount            => 10,
454
            amountoutstanding => 10,
455
            interface         => 'commandline',
456
        }
457
    )->store;
458
459
    my $debit_2 = Koha::Account::Line->new(
460
        {   borrowernumber    => $patron->id,
461
            debit_type_code       => "OVERDUE",
462
            status            => "RETURNED",
463
            amount            => 100,
464
            amountoutstanding => 100,
465
            interface         => 'commandline',
466
        }
467
    )->store;
468
469
    $credit->discard_changes;
470
    $debit_1->discard_changes;
471
472
    my $debits = Koha::Account::Lines->search({ accountlines_id => $debit_1->id });
473
    my $remaining_credit = $credit->apply( { debits => [ $debits->as_list ], offset_type => 'Manual Credit' } );
474
    is( $remaining_credit * 1, 90, 'Remaining credit is correctly calculated' );
475
    $credit->discard_changes;
476
    is( $credit->amountoutstanding * -1, $remaining_credit, 'Remaining credit correctly stored' );
477
478
    # re-read debit info
479
    $debit_1->discard_changes;
480
    is( $debit_1->amountoutstanding * 1, 0, 'Debit has been cancelled' );
481
482
    my $offsets = Koha::Account::Offsets->search( { credit_id => $credit->id, debit_id => $debit_1->id } );
483
    is( $offsets->count, 1, 'Only one offset is generated' );
484
    my $THE_offset = $offsets->next;
485
    is( $THE_offset->amount * 1, -10, 'Amount was calculated correctly (less than the available credit)' );
486
    is( $THE_offset->type, 'Manual Credit', 'Passed type stored correctly' );
487
488
    $debits = Koha::Account::Lines->search({ accountlines_id => $debit_2->id });
489
    $remaining_credit = $credit->apply( { debits => [ $debits->as_list ] } );
490
    is( $remaining_credit, 0, 'No remaining credit left' );
491
    $credit->discard_changes;
492
    is( $credit->amountoutstanding * 1, 0, 'No outstanding credit' );
493
    $debit_2->discard_changes;
494
    is( $debit_2->amountoutstanding * 1, 10, 'Outstanding amount decremented correctly' );
495
496
    $offsets = Koha::Account::Offsets->search( { credit_id => $credit->id, debit_id => $debit_2->id } );
497
    is( $offsets->count, 1, 'Only one offset is generated' );
498
    $THE_offset = $offsets->next;
499
    is( $THE_offset->amount * 1, -90, 'Amount was calculated correctly (less than the available credit)' );
500
    is( $THE_offset->type, 'Credit Applied', 'Defaults to \'Credit Applied\' offset type' );
501
502
    $debits = Koha::Account::Lines->search({ accountlines_id => $debit_1->id });
503
    throws_ok
504
        { $credit->apply({ debits => [ $debits->as_list ] }); }
505
        'Koha::Exceptions::Account::NoAvailableCredit',
506
        '->apply() can only be used with outstanding credits';
507
508
    $debits = Koha::Account::Lines->search({ accountlines_id => $credit->id });
509
    throws_ok
510
        { $debit_1->apply({ debits => [ $debits->as_list ] }); }
511
        'Koha::Exceptions::Account::IsNotCredit',
512
        '->apply() can only be used with credits';
513
514
    $debits = Koha::Account::Lines->search({ accountlines_id => $credit->id });
515
    my $credit_3 = $account->add_credit({ amount => 1, interface => 'commandline' });
516
    throws_ok
517
        { $credit_3->apply({ debits => [ $debits->as_list ] }); }
518
        'Koha::Exceptions::Account::IsNotDebit',
519
        '->apply() can only be applied to credits';
520
521
    my $credit_2 = $account->add_credit({ amount => 20, interface => 'commandline' });
522
    my $debit_3  = Koha::Account::Line->new(
523
        {   borrowernumber    => $patron->id,
524
            debit_type_code       => "OVERDUE",
525
            status            => "RETURNED",
526
            amount            => 100,
527
            amountoutstanding => 100,
528
            interface         => 'commandline',
529
        }
530
    )->store;
531
532
    $debits = Koha::Account::Lines->search({ accountlines_id => { -in => [ $debit_1->id, $debit_2->id, $debit_3->id, $credit->id ] } });
533
    throws_ok {
534
        $credit_2->apply( { debits => [ $debits->as_list ], offset_type => 'Manual Credit' } ); }
535
        'Koha::Exceptions::Account::IsNotDebit',
536
        '->apply() rolls back if any of the passed lines is not a debit';
537
538
    is( $debit_1->discard_changes->amountoutstanding * 1,   0, 'No changes to already cancelled debit' );
539
    is( $debit_2->discard_changes->amountoutstanding * 1,  10, 'Debit cancelled' );
540
    is( $debit_3->discard_changes->amountoutstanding * 1, 100, 'Outstanding amount correctly calculated' );
541
    is( $credit_2->discard_changes->amountoutstanding * -1, 20, 'No changes made' );
542
543
    $debits = Koha::Account::Lines->search({ accountlines_id => { -in => [ $debit_1->id, $debit_2->id, $debit_3->id ] } });
544
    $remaining_credit = $credit_2->apply( { debits => [ $debits->as_list ], offset_type => 'Manual Credit' } );
545
546
    is( $debit_1->discard_changes->amountoutstanding * 1,  0, 'No changes to already cancelled debit' );
547
    is( $debit_2->discard_changes->amountoutstanding * 1,  0, 'Debit cancelled' );
548
    is( $debit_3->discard_changes->amountoutstanding * 1, 90, 'Outstanding amount correctly calculated' );
549
    is( $credit_2->discard_changes->amountoutstanding * 1, 0, 'No remaining credit' );
550
551
    $schema->storage->txn_rollback;
552
};
553
554
subtest 'Keep account info when related patron, staff, item or cash_register is deleted' => sub {
555
556
    plan tests => 4;
557
558
    $schema->storage->txn_begin;
559
560
    my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
561
    my $staff = $builder->build_object( { class => 'Koha::Patrons' } );
562
    my $item = $builder->build_object({ class => 'Koha::Items' });
563
    my $issue = $builder->build_object(
564
        {
565
            class => 'Koha::Checkouts',
566
            value => { itemnumber => $item->itemnumber }
567
        }
568
    );
569
    my $register = $builder->build_object({ class => 'Koha::Cash::Registers' });
570
571
    my $line = Koha::Account::Line->new(
572
    {
573
        borrowernumber => $patron->borrowernumber,
574
        manager_id     => $staff->borrowernumber,
575
        itemnumber     => $item->itemnumber,
576
        debit_type_code    => "OVERDUE",
577
        status         => "RETURNED",
578
        amount         => 10,
579
        interface      => 'commandline',
580
        register_id    => $register->id
581
    })->store;
582
583
    $issue->delete;
584
    $item->delete;
585
    $line = $line->get_from_storage;
586
    is( $line->itemnumber, undef, "The account line should not be deleted when the related item is delete");
587
588
    $staff->delete;
589
    $line = $line->get_from_storage;
590
    is( $line->manager_id, undef, "The account line should not be deleted when the related staff is delete");
591
592
    $patron->delete;
593
    $line = $line->get_from_storage;
594
    is( $line->borrowernumber, undef, "The account line should not be deleted when the related patron is delete");
595
596
    $register->delete;
597
    $line = $line->get_from_storage;
598
    is( $line->register_id, undef, "The account line should not be deleted when the related cash register is delete");
599
600
    $schema->storage->txn_rollback;
601
};
602
603
subtest 'adjust() tests' => sub {
604
605
    plan tests => 29;
606
607
    $schema->storage->txn_begin;
608
609
    # count logs before any actions
610
    my $action_logs = $schema->resultset('ActionLog')->search()->count;
611
612
    # Disable logs
613
    t::lib::Mocks::mock_preference( 'FinesLog', 0 );
614
615
    my $patron  = $builder->build_object( { class => 'Koha::Patrons' } );
616
    my $account = $patron->account;
617
618
    my $debit_1 = Koha::Account::Line->new(
619
        {   borrowernumber    => $patron->id,
620
            debit_type_code       => "OVERDUE",
621
            status            => "RETURNED",
622
            amount            => 10,
623
            amountoutstanding => 10,
624
            interface         => 'commandline',
625
        }
626
    )->store;
627
628
    my $debit_2 = Koha::Account::Line->new(
629
        {   borrowernumber    => $patron->id,
630
            debit_type_code       => "OVERDUE",
631
            status            => "UNRETURNED",
632
            amount            => 100,
633
            amountoutstanding => 100,
634
            interface         => 'commandline'
635
        }
636
    )->store;
637
638
    my $credit = $account->add_credit( { amount => 40, user_id => $patron->id, interface => 'commandline' } );
639
640
    throws_ok { $debit_1->adjust( { amount => 50, type => 'bad', interface => 'commandline' } ) }
641
    qr/Update type not recognised/, 'Exception thrown for unrecognised type';
642
643
    throws_ok { $debit_1->adjust( { amount => 50, type => 'overdue_update', interface => 'commandline' } ) }
644
    qr/Update type not allowed on this debit_type/,
645
      'Exception thrown for type conflict';
646
647
    # Increment an unpaid fine
648
    $debit_2->adjust( { amount => 150, type => 'overdue_update', interface => 'commandline' } )->discard_changes;
649
650
    is( $debit_2->amount * 1, 150, 'Fine amount was updated in full' );
651
    is( $debit_2->amountoutstanding * 1, 150, 'Fine amountoutstanding was update in full' );
652
    isnt( $debit_2->date, undef, 'Date has been set' );
653
654
    my $offsets = Koha::Account::Offsets->search( { debit_id => $debit_2->id } );
655
    is( $offsets->count, 1, 'An offset is generated for the increment' );
656
    my $THIS_offset = $offsets->next;
657
    is( $THIS_offset->amount * 1, 50, 'Amount was calculated correctly (increment by 50)' );
658
    is( $THIS_offset->type, 'OVERDUE_INCREASE', 'Adjust type stored correctly' );
659
660
    is( $schema->resultset('ActionLog')->count(), $action_logs + 0, 'No log was added' );
661
662
    # Update fine to partially paid
663
    my $debits = Koha::Account::Lines->search({ accountlines_id => $debit_2->id });
664
    $credit->apply( { debits => [ $debits->as_list ], offset_type => 'Manual Credit' } );
665
666
    $debit_2->discard_changes;
667
    is( $debit_2->amount * 1, 150, 'Fine amount unaffected by partial payment' );
668
    is( $debit_2->amountoutstanding * 1, 110, 'Fine amountoutstanding updated by partial payment' );
669
670
    # Enable logs
671
    t::lib::Mocks::mock_preference( 'FinesLog', 1 );
672
673
    # Increment the partially paid fine
674
    $debit_2->adjust( { amount => 160, type => 'overdue_update', interface => 'commandline' } )->discard_changes;
675
676
    is( $debit_2->amount * 1, 160, 'Fine amount was updated in full' );
677
    is( $debit_2->amountoutstanding * 1, 120, 'Fine amountoutstanding was updated by difference' );
678
679
    $offsets = Koha::Account::Offsets->search( { debit_id => $debit_2->id } );
680
    is( $offsets->count, 3, 'An offset is generated for the increment' );
681
    $THIS_offset = $offsets->last;
682
    is( $THIS_offset->amount * 1, 10, 'Amount was calculated correctly (increment by 10)' );
683
    is( $THIS_offset->type, 'OVERDUE_INCREASE', 'Adjust type stored correctly' );
684
685
    is( $schema->resultset('ActionLog')->count(), $action_logs + 1, 'Log was added' );
686
687
    # Decrement the partially paid fine, less than what was paid
688
    $debit_2->adjust( { amount => 50, type => 'overdue_update', interface => 'commandline' } )->discard_changes;
689
690
    is( $debit_2->amount * 1, 50, 'Fine amount was updated in full' );
691
    is( $debit_2->amountoutstanding * 1, 10, 'Fine amountoutstanding was updated by difference' );
692
693
    $offsets = Koha::Account::Offsets->search( { debit_id => $debit_2->id } );
694
    is( $offsets->count, 4, 'An offset is generated for the decrement' );
695
    $THIS_offset = $offsets->last;
696
    is( $THIS_offset->amount * 1, -110, 'Amount was calculated correctly (decrement by 110)' );
697
    is( $THIS_offset->type, 'OVERDUE_DECREASE', 'Adjust type stored correctly' );
698
699
    # Decrement the partially paid fine, more than what was paid
700
    $debit_2->adjust( { amount => 30, type => 'overdue_update', interface => 'commandline' } )->discard_changes;
701
    is( $debit_2->amount * 1, 30, 'Fine amount was updated in full' );
702
    is( $debit_2->amountoutstanding * 1, 0, 'Fine amountoutstanding was zeroed (payment was 40)' );
703
704
    $offsets = Koha::Account::Offsets->search( { debit_id => $debit_2->id } );
705
    is( $offsets->count, 5, 'An offset is generated for the decrement' );
706
    $THIS_offset = $offsets->last;
707
    is( $THIS_offset->amount * 1, -20, 'Amount was calculated correctly (decrement by 20)' );
708
    is( $THIS_offset->type, 'OVERDUE_DECREASE', 'Adjust type stored correctly' );
709
710
    my $overpayment_refund = $account->lines->last;
711
    is( $overpayment_refund->amount * 1, -10, 'A new credit has been added' );
712
    is( $overpayment_refund->description, 'Overpayment refund', 'Credit generated with the expected description' );
713
714
    $schema->storage->txn_rollback;
715
};
716
717
subtest 'checkout() tests' => sub {
718
    plan tests => 6;
719
720
    $schema->storage->txn_begin;
721
722
    my $library = $builder->build_object( { class => 'Koha::Libraries' } );
723
    my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
724
    my $item = $builder->build_sample_item;
725
    my $account = $patron->account;
726
727
    t::lib::Mocks::mock_userenv({ branchcode => $library->branchcode });
728
    my $checkout = AddIssue( $patron->unblessed, $item->barcode );
729
730
    my $line = $account->add_debit({
731
        amount    => 10,
732
        interface => 'commandline',
733
        item_id   => $item->itemnumber,
734
        issue_id  => $checkout->issue_id,
735
        type      => 'OVERDUE',
736
    });
737
738
    my $line_checkout = $line->checkout;
739
    is( ref($line_checkout), 'Koha::Checkout', 'Result type is correct' );
740
    is( $line_checkout->issue_id, $checkout->issue_id, 'Koha::Account::Line->checkout should return the correct checkout');
741
742
    my ( $returned, undef, $old_checkout) = C4::Circulation::AddReturn( $item->barcode, $library->branchcode );
743
    is( $returned, 1, 'The item should have been returned' );
744
745
    $line = $line->get_from_storage;
746
    my $old_line_checkout = $line->checkout;
747
    is( ref($old_line_checkout), 'Koha::Old::Checkout', 'Result type is correct' );
748
    is( $old_line_checkout->issue_id, $old_checkout->issue_id, 'Koha::Account::Line->checkout should return the correct old_checkout' );
749
750
    $line->issue_id(undef)->store;
751
    is( $line->checkout, undef, 'Koha::Account::Line->checkout should return undef if no checkout linked' );
752
753
    $schema->storage->txn_rollback;
754
};
755
756
subtest 'credits() and debits() tests' => sub {
757
    plan tests => 10;
758
759
    $schema->storage->txn_begin;
760
761
    my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
762
    my $account = $patron->account;
763
764
    my $debit1 = $account->add_debit({
765
        amount    => 8,
766
        interface => 'commandline',
767
        type      => 'ACCOUNT',
768
    });
769
    my $debit2 = $account->add_debit({
770
        amount    => 12,
771
        interface => 'commandline',
772
        type      => 'ACCOUNT',
773
    });
774
    my $credit1 = $account->add_credit({
775
        amount    => 5,
776
        interface => 'commandline',
777
        type      => 'CREDIT',
778
    });
779
    my $credit2 = $account->add_credit({
780
        amount    => 10,
781
        interface => 'commandline',
782
        type      => 'CREDIT',
783
    });
784
785
    $credit1->apply({ debits => [ $debit1 ] });
786
    $credit2->apply({ debits => [ $debit1, $debit2 ] });
787
788
    my $credits = $debit1->credits;
789
    is($credits->count, 2, '2 Credits applied to debit 1');
790
    my $credit = $credits->next;
791
    is($credit->amount + 0, -5, 'Correct first credit');
792
    $credit = $credits->next;
793
    is($credit->amount + 0, -10, 'Correct second credit');
794
795
    $credits = $debit2->credits;
796
    is($credits->count, 1, '1 Credits applied to debit 2');
797
    $credit = $credits->next;
798
    is($credit->amount + 0, -10, 'Correct first credit');
799
800
    my $debits = $credit1->debits;
801
    is($debits->count, 1, 'Credit 1 applied to 1 debit');
802
    my $debit = $debits->next;
803
    is($debit->amount + 0, 8, 'Correct first debit');
804
805
    $debits = $credit2->debits;
806
    is($debits->count, 2, 'Credit 2 applied to 2 debits');
807
    $debit = $debits->next;
808
    is($debit->amount + 0, 8, 'Correct first debit');
809
    $debit = $debits->next;
810
    is($debit->amount + 0, 12, 'Correct second debit');
811
812
    $schema->storage->txn_rollback;
813
};
814
815
subtest "void() tests" => sub {
816
817
    plan tests => 16;
818
819
    $schema->storage->txn_begin;
820
821
    # Create a borrower
822
    my $categorycode = $builder->build({ source => 'Category' })->{ categorycode };
823
    my $branchcode   = $builder->build({ source => 'Branch' })->{ branchcode };
824
825
    my $borrower = Koha::Patron->new( {
826
        cardnumber => 'dariahall',
827
        surname => 'Hall',
828
        firstname => 'Daria',
829
    } );
830
    $borrower->categorycode( $categorycode );
831
    $borrower->branchcode( $branchcode );
832
    $borrower->store;
833
834
    my $account = Koha::Account->new({ patron_id => $borrower->id });
835
836
    my $line1 = Koha::Account::Line->new(
837
        {
838
            borrowernumber    => $borrower->borrowernumber,
839
            amount            => 10,
840
            amountoutstanding => 10,
841
            interface         => 'commandline',
842
            debit_type_code   => 'OVERDUE'
843
        }
844
    )->store();
845
    my $line2 = Koha::Account::Line->new(
846
        {
847
            borrowernumber    => $borrower->borrowernumber,
848
            amount            => 20,
849
            amountoutstanding => 20,
850
            interface         => 'commandline',
851
            debit_type_code   => 'OVERDUE'
852
        }
853
    )->store();
854
855
    is( $account->balance(), 30, "Account balance is 30" );
856
    is( $line1->amountoutstanding, 10, 'First fee has amount outstanding of 10' );
857
    is( $line2->amountoutstanding, 20, 'Second fee has amount outstanding of 20' );
858
859
    my $id = $account->pay(
860
        {
861
            lines  => [$line1, $line2],
862
            amount => 30,
863
        }
864
    );
865
866
    my $account_payment = Koha::Account::Lines->find( $id );
867
868
    is( $account->balance(), 0, "Account balance is 0" );
869
870
    $line1->_result->discard_changes();
871
    $line2->_result->discard_changes();
872
    is( $line1->amountoutstanding+0, 0, 'First fee has amount outstanding of 0' );
873
    is( $line2->amountoutstanding+0, 0, 'Second fee has amount outstanding of 0' );
874
875
    my $ret = $account_payment->void();
876
877
    is( ref($ret), 'Koha::Account::Line', 'Void returns the account line' );
878
    is( $account->balance(), 30, "Account balance is again 30" );
879
880
    $account_payment->_result->discard_changes();
881
    $line1->_result->discard_changes();
882
    $line2->_result->discard_changes();
883
884
    is( $account_payment->credit_type_code, 'PAYMENT', 'Voided payment credit_type_code is still PAYMENT' );
885
    is( $account_payment->status, 'VOID', 'Voided payment status is VOID' );
886
    is( $account_payment->amount+0, 0, 'Voided payment amount is 0' );
887
    is( $account_payment->amountoutstanding+0, 0, 'Voided payment amount outstanding is 0' );
888
889
    is( $line1->amountoutstanding+0, 10, 'First fee again has amount outstanding of 10' );
890
    is( $line2->amountoutstanding+0, 20, 'Second fee again has amount outstanding of 20' );
891
892
    # Accountlines that are not credits should be un-voidable
893
    my $line1_pre = $line1->unblessed();
894
    $ret = $line1->void();
895
    $line1->_result->discard_changes();
896
    my $line1_post = $line1->unblessed();
897
    is( $ret, undef, 'Attempted void on non-credit returns undef' );
898
    is_deeply( $line1_pre, $line1_post, 'Non-credit account line cannot be voided' );
899
900
    $schema->storage->txn_rollback;
901
};
902
903
subtest "payout() tests" => sub {
904
905
    plan tests => 18;
906
907
    $schema->storage->txn_begin;
908
909
    # Create a borrower
910
    my $categorycode =
911
      $builder->build( { source => 'Category' } )->{categorycode};
912
    my $branchcode = $builder->build( { source => 'Branch' } )->{branchcode};
913
914
    my $borrower = Koha::Patron->new(
915
        {
916
            cardnumber => 'dariahall',
917
            surname    => 'Hall',
918
            firstname  => 'Daria',
919
        }
920
    );
921
    $borrower->categorycode($categorycode);
922
    $borrower->branchcode($branchcode);
923
    $borrower->store;
924
925
    my $staff = Koha::Patron->new(
926
        {
927
            cardnumber => 'bobby',
928
            surname    => 'Bloggs',
929
            firstname  => 'Bobby',
930
        }
931
    );
932
    $staff->categorycode($categorycode);
933
    $staff->branchcode($branchcode);
934
    $staff->store;
935
936
    my $account = Koha::Account->new( { patron_id => $borrower->id } );
937
938
    my $debit1 = Koha::Account::Line->new(
939
        {
940
            borrowernumber    => $borrower->borrowernumber,
941
            amount            => 10,
942
            amountoutstanding => 10,
943
            interface         => 'commandline',
944
            debit_type_code   => 'OVERDUE'
945
        }
946
    )->store();
947
    my $credit1 = Koha::Account::Line->new(
948
        {
949
            borrowernumber    => $borrower->borrowernumber,
950
            amount            => -20,
951
            amountoutstanding => -20,
952
            interface         => 'commandline',
953
            credit_type_code  => 'CREDIT'
954
        }
955
    )->store();
956
957
    is( $account->balance(), -10, "Account balance is -10" );
958
    is( $debit1->amountoutstanding + 0,
959
        10, 'Overdue fee has an amount outstanding of 10' );
960
    is( $credit1->amountoutstanding + 0,
961
        -20, 'Credit has an amount outstanding of -20' );
962
963
    my $pay_params = {
964
        interface   => 'intranet',
965
        staff_id    => $staff->borrowernumber,
966
        branch      => $branchcode,
967
        payout_type => 'CASH',
968
        amount      => 20
969
    };
970
971
    throws_ok { $debit1->payout($pay_params); }
972
    'Koha::Exceptions::Account::IsNotCredit',
973
      '->payout() can only be used with credits';
974
975
    my @required =
976
      ( 'interface', 'staff_id', 'branch', 'payout_type', 'amount' );
977
    for my $required (@required) {
978
        my $params = {%$pay_params};
979
        delete( $params->{$required} );
980
        throws_ok {
981
            $credit1->payout($params);
982
        }
983
        'Koha::Exceptions::MissingParameter',
984
          "->payout() requires the `$required` parameter is passed";
985
    }
986
987
    throws_ok {
988
        $credit1->payout(
989
            {
990
                interface   => 'intranet',
991
                staff_id    => $staff->borrowernumber,
992
                branch      => $branchcode,
993
                payout_type => 'CASH',
994
                amount      => 25
995
            }
996
        );
997
    }
998
    'Koha::Exceptions::ParameterTooHigh',
999
      '->payout() cannot pay out more than the amountoutstanding';
1000
1001
    t::lib::Mocks::mock_preference( 'UseCashRegisters', 1 );
1002
    throws_ok {
1003
        $credit1->payout(
1004
            {
1005
                interface   => 'intranet',
1006
                staff_id    => $staff->borrowernumber,
1007
                branch      => $branchcode,
1008
                payout_type => 'CASH',
1009
                amount      => 10
1010
            }
1011
        );
1012
    }
1013
    'Koha::Exceptions::Account::RegisterRequired',
1014
      '->payout() requires a cash_register if payout_type is `CASH`';
1015
1016
    t::lib::Mocks::mock_preference( 'UseCashRegisters', 0 );
1017
    my $payout = $credit1->payout(
1018
        {
1019
            interface   => 'intranet',
1020
            staff_id    => $staff->borrowernumber,
1021
            branch      => $branchcode,
1022
            payout_type => 'CASH',
1023
            amount      => 10
1024
        }
1025
    );
1026
1027
    is( ref($payout), 'Koha::Account::Line',
1028
        '->payout() returns a Koha::Account::Line' );
1029
    is( $payout->amount() + 0,            10, "Payout amount is 10" );
1030
    is( $payout->amountoutstanding() + 0, 0,  "Payout amountoutstanding is 0" );
1031
    is( $account->balance() + 0,          0,  "Account balance is 0" );
1032
    is( $debit1->amountoutstanding + 0,
1033
        10, 'Overdue fee still has an amount outstanding of 10' );
1034
    is( $credit1->amountoutstanding + 0,
1035
        -10, 'Credit has an new amount outstanding of -10' );
1036
    is( $credit1->status(), 'PAID', "Credit has a new status of PAID" );
1037
1038
    $schema->storage->txn_rollback;
1039
};
1040
1041
subtest "reduce() tests" => sub {
1042
1043
    plan tests => 27;
1044
1045
    $schema->storage->txn_begin;
1046
1047
    # Create a borrower
1048
    my $categorycode =
1049
      $builder->build( { source => 'Category' } )->{categorycode};
1050
    my $branchcode = $builder->build( { source => 'Branch' } )->{branchcode};
1051
1052
    my $borrower = Koha::Patron->new(
1053
        {
1054
            cardnumber => 'dariahall',
1055
            surname    => 'Hall',
1056
            firstname  => 'Daria',
1057
        }
1058
    );
1059
    $borrower->categorycode($categorycode);
1060
    $borrower->branchcode($branchcode);
1061
    $borrower->store;
1062
1063
    my $staff = Koha::Patron->new(
1064
        {
1065
            cardnumber => 'bobby',
1066
            surname    => 'Bloggs',
1067
            firstname  => 'Bobby',
1068
        }
1069
    );
1070
    $staff->categorycode($categorycode);
1071
    $staff->branchcode($branchcode);
1072
    $staff->store;
1073
1074
    my $account = Koha::Account->new( { patron_id => $borrower->id } );
1075
1076
    my $debit1 = Koha::Account::Line->new(
1077
        {
1078
            borrowernumber    => $borrower->borrowernumber,
1079
            amount            => 20,
1080
            amountoutstanding => 20,
1081
            interface         => 'commandline',
1082
            debit_type_code   => 'LOST'
1083
        }
1084
    )->store();
1085
    my $credit1 = Koha::Account::Line->new(
1086
        {
1087
            borrowernumber    => $borrower->borrowernumber,
1088
            amount            => -20,
1089
            amountoutstanding => -20,
1090
            interface         => 'commandline',
1091
            credit_type_code  => 'CREDIT'
1092
        }
1093
    )->store();
1094
1095
    is( $account->balance(), 0, "Account balance is 0" );
1096
    is( $debit1->amountoutstanding,
1097
        20, 'Overdue fee has an amount outstanding of 20' );
1098
    is( $credit1->amountoutstanding,
1099
        -20, 'Credit has an amount outstanding of -20' );
1100
1101
    my $reduce_params = {
1102
        interface      => 'commandline',
1103
        reduction_type => 'REFUND',
1104
        amount         => 5,
1105
        staff_id       => $staff->borrowernumber,
1106
        branch         => $branchcode
1107
    };
1108
1109
    throws_ok { $credit1->reduce($reduce_params); }
1110
    'Koha::Exceptions::Account::IsNotDebit',
1111
      '->reduce() can only be used with debits';
1112
1113
    my @required = ( 'interface', 'reduction_type', 'amount' );
1114
    for my $required (@required) {
1115
        my $params = {%$reduce_params};
1116
        delete( $params->{$required} );
1117
        throws_ok {
1118
            $debit1->reduce($params);
1119
        }
1120
        'Koha::Exceptions::MissingParameter',
1121
          "->reduce() requires the `$required` parameter is passed";
1122
    }
1123
1124
    $reduce_params->{interface} = 'intranet';
1125
    my @dependant_required = ( 'staff_id', 'branch' );
1126
    for my $d (@dependant_required) {
1127
        my $params = {%$reduce_params};
1128
        delete( $params->{$d} );
1129
        throws_ok {
1130
            $debit1->reduce($params);
1131
        }
1132
        'Koha::Exceptions::MissingParameter',
1133
"->reduce() requires the `$d` parameter is passed when interface is intranet";
1134
    }
1135
1136
    throws_ok {
1137
        $debit1->reduce(
1138
            {
1139
                interface      => 'intranet',
1140
                staff_id       => $staff->borrowernumber,
1141
                branch         => $branchcode,
1142
                reduction_type => 'REFUND',
1143
                amount         => 25
1144
            }
1145
        );
1146
    }
1147
    'Koha::Exceptions::ParameterTooHigh',
1148
      '->reduce() cannot reduce more than original amount';
1149
1150
    # Partial Reduction
1151
    # (Refund 5 on debt of 20)
1152
    my $reduction = $debit1->reduce($reduce_params);
1153
1154
    is( ref($reduction), 'Koha::Account::Line',
1155
        '->reduce() returns a Koha::Account::Line' );
1156
    is( $reduction->amount() * 1, -5, "Reduce amount is -5" );
1157
    is( $reduction->amountoutstanding() * 1,
1158
        0, "Reduce amountoutstanding is 0" );
1159
    is( $debit1->amountoutstanding() * 1,
1160
        15, "Debit amountoutstanding reduced by 5 to 15" );
1161
    is( $account->balance() * 1, -5,        "Account balance is -5" );
1162
    is( $reduction->status(),    'APPLIED', "Reduction status is 'APPLIED'" );
1163
1164
    my $offsets = Koha::Account::Offsets->search(
1165
        { credit_id => $reduction->id, debit_id => $debit1->id } );
1166
    is( $offsets->count, 1, 'Only one offset is generated' );
1167
    my $THE_offset = $offsets->next;
1168
    is( $THE_offset->amount * 1,
1169
        -5, 'Correct amount was applied against debit' );
1170
    is( $THE_offset->type, 'REFUND', "Offset type set to 'REFUND'" );
1171
1172
    # Zero offset created when zero outstanding
1173
    # (Refund another 5 on paid debt of 20)
1174
    $credit1->apply( { debits => [$debit1] } );
1175
    is( $debit1->amountoutstanding + 0,
1176
        0, 'Debit1 amountoutstanding reduced to 0' );
1177
    $reduction = $debit1->reduce($reduce_params);
1178
    is( $reduction->amount() * 1, -5, "Reduce amount is -5" );
1179
    is( $reduction->amountoutstanding() * 1,
1180
        -5, "Reduce amountoutstanding is -5" );
1181
1182
    $offsets = Koha::Account::Offsets->search(
1183
        { credit_id => $reduction->id, debit_id => $debit1->id } );
1184
    is( $offsets->count, 1, 'Only one new offset is generated' );
1185
    $THE_offset = $offsets->next;
1186
    is( $THE_offset->amount * 1,
1187
        0, 'Zero offset created for already paid off debit' );
1188
    is( $THE_offset->type, 'REFUND', "Offset type set to 'REFUND'" );
1189
1190
    # Compound reduction should not allow more than original amount
1191
    # (Reduction of 5 + 5 + 20 > 20)
1192
    $reduce_params->{amount} = 20;
1193
    throws_ok {
1194
        $debit1->reduce($reduce_params);
1195
    }
1196
    'Koha::Exceptions::ParameterTooHigh',
1197
'->reduce cannot reduce more than the original amount (combined reductions test)';
1198
1199
    # Throw exception if attempting to reduce a payout
1200
    my $payout = $reduction->payout(
1201
        {
1202
            interface   => 'intranet',
1203
            staff_id    => $staff->borrowernumber,
1204
            branch      => $branchcode,
1205
            payout_type => 'CASH',
1206
            amount      => 5
1207
        }
1208
    );
1209
    throws_ok {
1210
        $payout->reduce($reduce_params);
1211
    }
1212
    'Koha::Exceptions::Account::IsNotDebit',
1213
      '->reduce() cannot be used on a payout debit';
1214
1215
    $schema->storage->txn_rollback;
1216
};
1217
1218
1;
339
1;
1219
- 

Return to bug 23355