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

(-)a/t/db_dependent/Koha/Account/Line.t (+523 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
        accounttype    => "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
        accounttype    => "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
        accounttype    => "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
            accounttype       => "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
            accounttype       => "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, 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 } );
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 }); }
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 }); }
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 }); }
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
            accounttype       => "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, 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, 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
        accounttype    => "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
            accounttype       => "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
            accounttype       => "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 accounttype/,
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, 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 "void() tests" => sub {
452
453
    plan tests => 16;
454
455
    $schema->storage->txn_begin;
456
457
    # Create a borrower
458
    my $categorycode = $builder->build({ source => 'Category' })->{ categorycode };
459
    my $branchcode   = $builder->build({ source => 'Branch' })->{ branchcode };
460
461
    my $borrower = Koha::Patron->new( {
462
        cardnumber => 'dariahall',
463
        surname => 'Hall',
464
        firstname => 'Daria',
465
    } );
466
    $borrower->categorycode( $categorycode );
467
    $borrower->branchcode( $branchcode );
468
    $borrower->store;
469
470
    my $account = Koha::Account->new({ patron_id => $borrower->id });
471
472
    my $line1 = Koha::Account::Line->new({ borrowernumber => $borrower->borrowernumber, amount => 10, amountoutstanding => 10, interface => 'commandline' })->store();
473
    my $line2 = Koha::Account::Line->new({ borrowernumber => $borrower->borrowernumber, amount => 20, amountoutstanding => 20, interface => 'commandline' })->store();
474
475
    is( $account->balance(), 30, "Account balance is 30" );
476
    is( $line1->amountoutstanding, 10, 'First fee has amount outstanding of 10' );
477
    is( $line2->amountoutstanding, 20, 'Second fee has amount outstanding of 20' );
478
479
    my $id = $account->pay(
480
        {
481
            lines  => [$line1, $line2],
482
            amount => 30,
483
        }
484
    );
485
486
    my $account_payment = Koha::Account::Lines->find( $id );
487
488
    is( $account->balance(), 0, "Account balance is 0" );
489
490
    $line1->_result->discard_changes();
491
    $line2->_result->discard_changes();
492
    is( $line1->amountoutstanding+0, 0, 'First fee has amount outstanding of 0' );
493
    is( $line2->amountoutstanding+0, 0, 'Second fee has amount outstanding of 0' );
494
495
    my $ret = $account_payment->void();
496
497
    is( ref($ret), 'Koha::Account::Line', 'Void returns the account line' );
498
    is( $account->balance(), 30, "Account balance is again 30" );
499
500
    $account_payment->_result->discard_changes();
501
    $line1->_result->discard_changes();
502
    $line2->_result->discard_changes();
503
504
    is( $account_payment->accounttype, 'Pay', 'Voided payment accounttype is still Pay' );
505
    is( $account_payment->status, 'VOID', 'Voided payment status is VOID' );
506
    is( $account_payment->amount+0, 0, 'Voided payment amount is 0' );
507
    is( $account_payment->amountoutstanding+0, 0, 'Voided payment amount outstanding is 0' );
508
509
    is( $line1->amountoutstanding+0, 10, 'First fee again has amount outstanding of 10' );
510
    is( $line2->amountoutstanding+0, 20, 'Second fee again has amount outstanding of 20' );
511
512
    # Accountlines that are not credits should be un-voidable
513
    my $line1_pre = $line1->unblessed();
514
    $ret = $line1->void();
515
    $line1->_result->discard_changes();
516
    my $line1_post = $line1->unblessed();
517
    is( $ret, undef, 'Attempted void on non-credit returns undef' );
518
    is_deeply( $line1_pre, $line1_post, 'Non-credit account line cannot be voided' );
519
520
    $schema->storage->txn_rollback;
521
};
522
523
1;
(-)a/t/db_dependent/Koha/Account/Lines.t (-490 / +1 lines)
Lines 19-32 Link Here
19
19
20
use Modern::Perl;
20
use Modern::Perl;
21
21
22
use Test::More tests => 9;
22
use Test::More tests => 1;
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
        accounttype    => "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
        accounttype    => "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 178-600 subtest 'total_outstanding() tests' => sub { Link Here
178
    $schema->storage->txn_rollback;
108
    $schema->storage->txn_rollback;
179
};
109
};
180
110
181
subtest 'is_credit() and is_debit() tests' => sub {
182
183
    plan tests => 4;
184
185
    $schema->storage->txn_begin;
186
187
    my $patron  = $builder->build_object({ class => 'Koha::Patrons' });
188
    my $account = $patron->account;
189
190
    my $credit = $account->add_credit({ amount => 100, user_id => $patron->id, interface => 'commandline' });
191
192
    ok( $credit->is_credit, 'is_credit detects credits' );
193
    ok( !$credit->is_debit, 'is_debit detects credits' );
194
195
    my $debit = Koha::Account::Line->new(
196
    {
197
        borrowernumber => $patron->id,
198
        accounttype    => "OVERDUE",
199
        status         => "RETURNED",
200
        amount         => 10,
201
        interface      => 'commandline',
202
    })->store;
203
204
    ok( !$debit->is_credit, 'is_credit detects debits' );
205
    ok( $debit->is_debit, 'is_debit detects debits');
206
207
    $schema->storage->txn_rollback;
208
};
209
210
subtest 'apply() tests' => sub {
211
212
    plan tests => 24;
213
214
    $schema->storage->txn_begin;
215
216
    my $patron  = $builder->build_object( { class => 'Koha::Patrons' } );
217
    my $account = $patron->account;
218
219
    my $credit = $account->add_credit( { amount => 100, user_id => $patron->id, interface => 'commandline' } );
220
221
    my $debit_1 = Koha::Account::Line->new(
222
        {   borrowernumber    => $patron->id,
223
            accounttype       => "OVERDUE",
224
            status            => "RETURNED",
225
            amount            => 10,
226
            amountoutstanding => 10,
227
            interface         => 'commandline',
228
        }
229
    )->store;
230
231
    my $debit_2 = Koha::Account::Line->new(
232
        {   borrowernumber    => $patron->id,
233
            accounttype       => "OVERDUE",
234
            status            => "RETURNED",
235
            amount            => 100,
236
            amountoutstanding => 100,
237
            interface         => 'commandline',
238
        }
239
    )->store;
240
241
    $credit->discard_changes;
242
    $debit_1->discard_changes;
243
244
    my $debits = Koha::Account::Lines->search({ accountlines_id => $debit_1->id });
245
    my $remaining_credit = $credit->apply( { debits => $debits, offset_type => 'Manual Credit' } );
246
    is( $remaining_credit * 1, 90, 'Remaining credit is correctly calculated' );
247
    $credit->discard_changes;
248
    is( $credit->amountoutstanding * -1, $remaining_credit, 'Remaining credit correctly stored' );
249
250
    # re-read debit info
251
    $debit_1->discard_changes;
252
    is( $debit_1->amountoutstanding * 1, 0, 'Debit has been cancelled' );
253
254
    my $offsets = Koha::Account::Offsets->search( { credit_id => $credit->id, debit_id => $debit_1->id } );
255
    is( $offsets->count, 1, 'Only one offset is generated' );
256
    my $THE_offset = $offsets->next;
257
    is( $THE_offset->amount * 1, -10, 'Amount was calculated correctly (less than the available credit)' );
258
    is( $THE_offset->type, 'Manual Credit', 'Passed type stored correctly' );
259
260
    $debits = Koha::Account::Lines->search({ accountlines_id => $debit_2->id });
261
    $remaining_credit = $credit->apply( { debits => $debits } );
262
    is( $remaining_credit, 0, 'No remaining credit left' );
263
    $credit->discard_changes;
264
    is( $credit->amountoutstanding * 1, 0, 'No outstanding credit' );
265
    $debit_2->discard_changes;
266
    is( $debit_2->amountoutstanding * 1, 10, 'Outstanding amount decremented correctly' );
267
268
    $offsets = Koha::Account::Offsets->search( { credit_id => $credit->id, debit_id => $debit_2->id } );
269
    is( $offsets->count, 1, 'Only one offset is generated' );
270
    $THE_offset = $offsets->next;
271
    is( $THE_offset->amount * 1, -90, 'Amount was calculated correctly (less than the available credit)' );
272
    is( $THE_offset->type, 'Credit Applied', 'Defaults to \'Credit Applied\' offset type' );
273
274
    $debits = Koha::Account::Lines->search({ accountlines_id => $debit_1->id });
275
    throws_ok
276
        { $credit->apply({ debits => $debits }); }
277
        'Koha::Exceptions::Account::NoAvailableCredit',
278
        '->apply() can only be used with outstanding credits';
279
280
    $debits = Koha::Account::Lines->search({ accountlines_id => $credit->id });
281
    throws_ok
282
        { $debit_1->apply({ debits => $debits }); }
283
        'Koha::Exceptions::Account::IsNotCredit',
284
        '->apply() can only be used with credits';
285
286
    $debits = Koha::Account::Lines->search({ accountlines_id => $credit->id });
287
    my $credit_3 = $account->add_credit({ amount => 1, interface => 'commandline' });
288
    throws_ok
289
        { $credit_3->apply({ debits => $debits }); }
290
        'Koha::Exceptions::Account::IsNotDebit',
291
        '->apply() can only be applied to credits';
292
293
    my $credit_2 = $account->add_credit({ amount => 20, interface => 'commandline' });
294
    my $debit_3  = Koha::Account::Line->new(
295
        {   borrowernumber    => $patron->id,
296
            accounttype       => "OVERDUE",
297
            status            => "RETURNED",
298
            amount            => 100,
299
            amountoutstanding => 100,
300
            interface         => 'commandline',
301
        }
302
    )->store;
303
304
    $debits = Koha::Account::Lines->search({ accountlines_id => { -in => [ $debit_1->id, $debit_2->id, $debit_3->id, $credit->id ] } });
305
    throws_ok {
306
        $credit_2->apply( { debits => $debits, offset_type => 'Manual Credit' } ); }
307
        'Koha::Exceptions::Account::IsNotDebit',
308
        '->apply() rolls back if any of the passed lines is not a debit';
309
310
    is( $debit_1->discard_changes->amountoutstanding * 1,   0, 'No changes to already cancelled debit' );
311
    is( $debit_2->discard_changes->amountoutstanding * 1,  10, 'Debit cancelled' );
312
    is( $debit_3->discard_changes->amountoutstanding * 1, 100, 'Outstanding amount correctly calculated' );
313
    is( $credit_2->discard_changes->amountoutstanding * -1, 20, 'No changes made' );
314
315
    $debits = Koha::Account::Lines->search({ accountlines_id => { -in => [ $debit_1->id, $debit_2->id, $debit_3->id ] } });
316
    $remaining_credit = $credit_2->apply( { debits => $debits, offset_type => 'Manual Credit' } );
317
318
    is( $debit_1->discard_changes->amountoutstanding * 1,  0, 'No changes to already cancelled debit' );
319
    is( $debit_2->discard_changes->amountoutstanding * 1,  0, 'Debit cancelled' );
320
    is( $debit_3->discard_changes->amountoutstanding * 1, 90, 'Outstanding amount correctly calculated' );
321
    is( $credit_2->discard_changes->amountoutstanding * 1, 0, 'No remaining credit' );
322
323
    $schema->storage->txn_rollback;
324
};
325
326
subtest 'Keep account info when related patron, staff, item or cash_register is deleted' => sub {
327
328
    plan tests => 4;
329
330
    $schema->storage->txn_begin;
331
332
    my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
333
    my $staff = $builder->build_object( { class => 'Koha::Patrons' } );
334
    my $item = $builder->build_object({ class => 'Koha::Items' });
335
    my $issue = $builder->build_object(
336
        {
337
            class => 'Koha::Checkouts',
338
            value => { itemnumber => $item->itemnumber }
339
        }
340
    );
341
    my $register = $builder->build_object({ class => 'Koha::Cash::Registers' });
342
343
    my $line = Koha::Account::Line->new(
344
    {
345
        borrowernumber => $patron->borrowernumber,
346
        manager_id     => $staff->borrowernumber,
347
        itemnumber     => $item->itemnumber,
348
        accounttype    => "OVERDUE",
349
        status         => "RETURNED",
350
        amount         => 10,
351
        interface      => 'commandline',
352
        register_id    => $register->id
353
    })->store;
354
355
    $issue->delete;
356
    $item->delete;
357
    $line = $line->get_from_storage;
358
    is( $line->itemnumber, undef, "The account line should not be deleted when the related item is delete");
359
360
    $staff->delete;
361
    $line = $line->get_from_storage;
362
    is( $line->manager_id, undef, "The account line should not be deleted when the related staff is delete");
363
364
    $patron->delete;
365
    $line = $line->get_from_storage;
366
    is( $line->borrowernumber, undef, "The account line should not be deleted when the related patron is delete");
367
368
    $register->delete;
369
    $line = $line->get_from_storage;
370
    is( $line->register_id, undef, "The account line should not be deleted when the related cash register is delete");
371
372
    $schema->storage->txn_rollback;
373
};
374
375
subtest 'adjust() tests' => sub {
376
377
    plan tests => 29;
378
379
    $schema->storage->txn_begin;
380
381
    # count logs before any actions
382
    my $action_logs = $schema->resultset('ActionLog')->search()->count;
383
384
    # Disable logs
385
    t::lib::Mocks::mock_preference( 'FinesLog', 0 );
386
387
    my $patron  = $builder->build_object( { class => 'Koha::Patrons' } );
388
    my $account = $patron->account;
389
390
    my $debit_1 = Koha::Account::Line->new(
391
        {   borrowernumber    => $patron->id,
392
            accounttype       => "OVERDUE",
393
            status            => "RETURNED",
394
            amount            => 10,
395
            amountoutstanding => 10,
396
            interface         => 'commandline',
397
        }
398
    )->store;
399
400
    my $debit_2 = Koha::Account::Line->new(
401
        {   borrowernumber    => $patron->id,
402
            accounttype       => "OVERDUE",
403
            status            => "UNRETURNED",
404
            amount            => 100,
405
            amountoutstanding => 100,
406
            interface         => 'commandline'
407
        }
408
    )->store;
409
410
    my $credit = $account->add_credit( { amount => 40, user_id => $patron->id, interface => 'commandline' } );
411
412
    throws_ok { $debit_1->adjust( { amount => 50, type => 'bad', interface => 'commandline' } ) }
413
    qr/Update type not recognised/, 'Exception thrown for unrecognised type';
414
415
    throws_ok { $debit_1->adjust( { amount => 50, type => 'overdue_update', interface => 'commandline' } ) }
416
    qr/Update type not allowed on this accounttype/,
417
      'Exception thrown for type conflict';
418
419
    # Increment an unpaid fine
420
    $debit_2->adjust( { amount => 150, type => 'overdue_update', interface => 'commandline' } )->discard_changes;
421
422
    is( $debit_2->amount * 1, 150, 'Fine amount was updated in full' );
423
    is( $debit_2->amountoutstanding * 1, 150, 'Fine amountoutstanding was update in full' );
424
    isnt( $debit_2->date, undef, 'Date has been set' );
425
426
    my $offsets = Koha::Account::Offsets->search( { debit_id => $debit_2->id } );
427
    is( $offsets->count, 1, 'An offset is generated for the increment' );
428
    my $THIS_offset = $offsets->next;
429
    is( $THIS_offset->amount * 1, 50, 'Amount was calculated correctly (increment by 50)' );
430
    is( $THIS_offset->type, 'OVERDUE_INCREASE', 'Adjust type stored correctly' );
431
432
    is( $schema->resultset('ActionLog')->count(), $action_logs + 0, 'No log was added' );
433
434
    # Update fine to partially paid
435
    my $debits = Koha::Account::Lines->search({ accountlines_id => $debit_2->id });
436
    $credit->apply( { debits => $debits, offset_type => 'Manual Credit' } );
437
438
    $debit_2->discard_changes;
439
    is( $debit_2->amount * 1, 150, 'Fine amount unaffected by partial payment' );
440
    is( $debit_2->amountoutstanding * 1, 110, 'Fine amountoutstanding updated by partial payment' );
441
442
    # Enable logs
443
    t::lib::Mocks::mock_preference( 'FinesLog', 1 );
444
445
    # Increment the partially paid fine
446
    $debit_2->adjust( { amount => 160, type => 'overdue_update', interface => 'commandline' } )->discard_changes;
447
448
    is( $debit_2->amount * 1, 160, 'Fine amount was updated in full' );
449
    is( $debit_2->amountoutstanding * 1, 120, 'Fine amountoutstanding was updated by difference' );
450
451
    $offsets = Koha::Account::Offsets->search( { debit_id => $debit_2->id } );
452
    is( $offsets->count, 3, 'An offset is generated for the increment' );
453
    $THIS_offset = $offsets->last;
454
    is( $THIS_offset->amount * 1, 10, 'Amount was calculated correctly (increment by 10)' );
455
    is( $THIS_offset->type, 'OVERDUE_INCREASE', 'Adjust type stored correctly' );
456
457
    is( $schema->resultset('ActionLog')->count(), $action_logs + 1, 'Log was added' );
458
459
    # Decrement the partially paid fine, less than what was paid
460
    $debit_2->adjust( { amount => 50, type => 'overdue_update', interface => 'commandline' } )->discard_changes;
461
462
    is( $debit_2->amount * 1, 50, 'Fine amount was updated in full' );
463
    is( $debit_2->amountoutstanding * 1, 10, 'Fine amountoutstanding was updated by difference' );
464
465
    $offsets = Koha::Account::Offsets->search( { debit_id => $debit_2->id } );
466
    is( $offsets->count, 4, 'An offset is generated for the decrement' );
467
    $THIS_offset = $offsets->last;
468
    is( $THIS_offset->amount * 1, -110, 'Amount was calculated correctly (decrement by 110)' );
469
    is( $THIS_offset->type, 'OVERDUE_DECREASE', 'Adjust type stored correctly' );
470
471
    # Decrement the partially paid fine, more than what was paid
472
    $debit_2->adjust( { amount => 30, type => 'overdue_update', interface => 'commandline' } )->discard_changes;
473
    is( $debit_2->amount * 1, 30, 'Fine amount was updated in full' );
474
    is( $debit_2->amountoutstanding * 1, 0, 'Fine amountoutstanding was zeroed (payment was 40)' );
475
476
    $offsets = Koha::Account::Offsets->search( { debit_id => $debit_2->id } );
477
    is( $offsets->count, 5, 'An offset is generated for the decrement' );
478
    $THIS_offset = $offsets->last;
479
    is( $THIS_offset->amount * 1, -20, 'Amount was calculated correctly (decrement by 20)' );
480
    is( $THIS_offset->type, 'OVERDUE_DECREASE', 'Adjust type stored correctly' );
481
482
    my $overpayment_refund = $account->lines->last;
483
    is( $overpayment_refund->amount * 1, -10, 'A new credit has been added' );
484
    is( $overpayment_refund->description, 'Overpayment refund', 'Credit generated with the expected description' );
485
486
    $schema->storage->txn_rollback;
487
};
488
489
subtest 'checkout() tests' => sub {
490
    plan tests => 6;
491
492
    $schema->storage->txn_begin;
493
494
    my $library = $builder->build_object( { class => 'Koha::Libraries' } );
495
    my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
496
    my $item = $builder->build_sample_item;
497
    my $account = $patron->account;
498
499
    t::lib::Mocks::mock_userenv({ branchcode => $library->branchcode });
500
    my $checkout = AddIssue( $patron->unblessed, $item->barcode );
501
502
    my $line = $account->add_debit({
503
        amount    => 10,
504
        interface => 'commandline',
505
        item_id   => $item->itemnumber,
506
        issue_id  => $checkout->issue_id,
507
        type      => 'overdue',
508
    });
509
510
    my $line_checkout = $line->checkout;
511
    is( ref($line_checkout), 'Koha::Checkout', 'Result type is correct' );
512
    is( $line_checkout->issue_id, $checkout->issue_id, 'Koha::Account::Line->checkout should return the correct checkout');
513
514
    my ( $returned, undef, $old_checkout) = C4::Circulation::AddReturn( $item->barcode, $library->branchcode );
515
    is( $returned, 1, 'The item should have been returned' );
516
517
    $line = $line->get_from_storage;
518
    my $old_line_checkout = $line->checkout;
519
    is( ref($old_line_checkout), 'Koha::Old::Checkout', 'Result type is correct' );
520
    is( $old_line_checkout->issue_id, $old_checkout->issue_id, 'Koha::Account::Line->checkout should return the correct old_checkout' );
521
522
    $line->issue_id(undef)->store;
523
    is( $line->checkout, undef, 'Koha::Account::Line->checkout should return undef if no checkout linked' );
524
525
    $schema->storage->txn_rollback;
526
};
527
528
subtest "void() tests" => sub {
529
530
    plan tests => 16;
531
532
    $schema->storage->txn_begin;
533
534
    # Create a borrower
535
    my $categorycode = $builder->build({ source => 'Category' })->{ categorycode };
536
    my $branchcode   = $builder->build({ source => 'Branch' })->{ branchcode };
537
538
    my $borrower = Koha::Patron->new( {
539
        cardnumber => 'dariahall',
540
        surname => 'Hall',
541
        firstname => 'Daria',
542
    } );
543
    $borrower->categorycode( $categorycode );
544
    $borrower->branchcode( $branchcode );
545
    $borrower->store;
546
547
    my $account = Koha::Account->new({ patron_id => $borrower->id });
548
549
    my $line1 = Koha::Account::Line->new({ borrowernumber => $borrower->borrowernumber, amount => 10, amountoutstanding => 10, interface => 'commandline' })->store();
550
    my $line2 = Koha::Account::Line->new({ borrowernumber => $borrower->borrowernumber, amount => 20, amountoutstanding => 20, interface => 'commandline' })->store();
551
552
    is( $account->balance(), 30, "Account balance is 30" );
553
    is( $line1->amountoutstanding, 10, 'First fee has amount outstanding of 10' );
554
    is( $line2->amountoutstanding, 20, 'Second fee has amount outstanding of 20' );
555
556
    my $id = $account->pay(
557
        {
558
            lines  => [$line1, $line2],
559
            amount => 30,
560
        }
561
    );
562
563
    my $account_payment = Koha::Account::Lines->find( $id );
564
565
    is( $account->balance(), 0, "Account balance is 0" );
566
567
    $line1->_result->discard_changes();
568
    $line2->_result->discard_changes();
569
    is( $line1->amountoutstanding+0, 0, 'First fee has amount outstanding of 0' );
570
    is( $line2->amountoutstanding+0, 0, 'Second fee has amount outstanding of 0' );
571
572
    my $ret = $account_payment->void();
573
574
    is( ref($ret), 'Koha::Account::Line', 'Void returns the account line' );
575
    is( $account->balance(), 30, "Account balance is again 30" );
576
577
    $account_payment->_result->discard_changes();
578
    $line1->_result->discard_changes();
579
    $line2->_result->discard_changes();
580
581
    is( $account_payment->accounttype, 'Pay', 'Voided payment accounttype is still Pay' );
582
    is( $account_payment->status, 'VOID', 'Voided payment status is VOID' );
583
    is( $account_payment->amount+0, 0, 'Voided payment amount is 0' );
584
    is( $account_payment->amountoutstanding+0, 0, 'Voided payment amount outstanding is 0' );
585
586
    is( $line1->amountoutstanding+0, 10, 'First fee again has amount outstanding of 10' );
587
    is( $line2->amountoutstanding+0, 20, 'Second fee again has amount outstanding of 20' );
588
589
    # Accountlines that are not credits should be un-voidable
590
    my $line1_pre = $line1->unblessed();
591
    $ret = $line1->void();
592
    $line1->_result->discard_changes();
593
    my $line1_post = $line1->unblessed();
594
    is( $ret, undef, 'Attempted void on non-credit returns undef' );
595
    is_deeply( $line1_pre, $line1_post, 'Non-credit account line cannot be voided' );
596
597
    $schema->storage->txn_rollback;
598
};
599
111
600
1;
112
1;
601
- 

Return to bug 23355