3 # Copyright 2018 Koha Development team
5 # This file is part of Koha
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.
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.
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>
22 use Test
::More tests
=> 15;
25 use C4
::Circulation qw
/AddIssue AddReturn/;
27 use Koha
::Account
::Lines
;
28 use Koha
::Account
::Offsets
;
32 use t
::lib
::TestBuilder
;
34 my $schema = Koha
::Database
->new->schema;
35 my $builder = t
::lib
::TestBuilder
->new;
37 subtest
'patron() tests' => sub {
41 $schema->storage->txn_begin;
43 my $library = $builder->build( { source
=> 'Branch' } );
44 my $patron = $builder->build( { source
=> 'Borrower' } );
46 my $line = Koha
::Account
::Line
->new(
48 borrowernumber
=> $patron->{borrowernumber
},
49 debit_type_code
=> "OVERDUE",
52 interface
=> 'commandline',
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' );
59 $line->borrowernumber(undef)->store;
60 is
( $line->patron, undef, 'Koha::Account::Line->patron should return undef if no patron linked' );
62 $schema->storage->txn_rollback;
66 subtest
'item() tests' => sub {
70 $schema->storage->txn_begin;
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(
77 biblionumber
=> $biblioitem->{biblionumber
},
78 biblioitemnumber
=> $biblioitem->{biblioitemnumber
},
79 homebranch
=> $library->{branchcode
},
80 holdingbranch
=> $library->{branchcode
},
81 barcode
=> 'some_barcode_12',
85 my $line = Koha
::Account
::Line
->new(
87 borrowernumber
=> $patron->{borrowernumber
},
88 itemnumber
=> $item->itemnumber,
89 debit_type_code
=> "OVERDUE",
92 interface
=> 'commandline',
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' );
99 $line->itemnumber(undef)->store;
100 is
( $line->item, undef, 'Koha::Account::Line->item should return undef if no item linked' );
102 $schema->storage->txn_rollback;
105 subtest
'total_outstanding() tests' => sub {
109 $schema->storage->txn_begin;
111 my $patron = $builder->build_object({ class => 'Koha::Patrons' });
113 my $lines = Koha
::Account
::Lines
->search({ borrowernumber
=> $patron->id });
114 is
( $lines->total_outstanding, 0, 'total_outstanding returns 0 if no lines (undef case)' );
116 my $debit_1 = Koha
::Account
::Line
->new(
117 { borrowernumber
=> $patron->id,
118 debit_type_code
=> "OVERDUE",
119 status
=> "RETURNED",
121 amountoutstanding
=> 10,
122 interface
=> 'commandline',
126 my $debit_2 = Koha
::Account
::Line
->new(
127 { borrowernumber
=> $patron->id,
128 debit_type_code
=> "OVERDUE",
129 status
=> "RETURNED",
131 amountoutstanding
=> 10,
132 interface
=> 'commandline',
136 $lines = Koha
::Account
::Lines
->search({ borrowernumber
=> $patron->id });
137 is
( $lines->total_outstanding, 20, 'total_outstanding sums correctly' );
139 my $credit_1 = Koha
::Account
::Line
->new(
140 { borrowernumber
=> $patron->id,
141 debit_type_code
=> "OVERDUE",
142 status
=> "RETURNED",
144 amountoutstanding
=> -10,
145 interface
=> 'commandline',
149 $lines = Koha
::Account
::Lines
->search({ borrowernumber
=> $patron->id });
150 is
( $lines->total_outstanding, 10, 'total_outstanding sums correctly' );
152 my $credit_2 = Koha
::Account
::Line
->new(
153 { borrowernumber
=> $patron->id,
154 debit_type_code
=> "OVERDUE",
155 status
=> "RETURNED",
157 amountoutstanding
=> -10,
158 interface
=> 'commandline',
162 $lines = Koha
::Account
::Lines
->search({ borrowernumber
=> $patron->id });
163 is
( $lines->total_outstanding, 0, 'total_outstanding sums correctly' );
165 my $credit_3 = Koha
::Account
::Line
->new(
166 { borrowernumber
=> $patron->id,
167 debit_type_code
=> "OVERDUE",
168 status
=> "RETURNED",
170 amountoutstanding
=> -100,
171 interface
=> 'commandline',
175 $lines = Koha
::Account
::Lines
->search({ borrowernumber
=> $patron->id });
176 is
( $lines->total_outstanding, -100, 'total_outstanding sums correctly' );
178 $schema->storage->txn_rollback;
181 subtest
'total() tests' => sub {
185 $schema->storage->txn_begin;
187 my $patron = $builder->build_object({ class => 'Koha::Patrons' });
189 my $lines = Koha
::Account
::Lines
->search({ borrowernumber
=> $patron->id });
190 is
( $lines->total, 0, 'total returns 0 if no lines (undef case)' );
192 my $debit_1 = Koha
::Account
::Line
->new(
193 { borrowernumber
=> $patron->id,
194 debit_type_code
=> "OVERDUE",
195 status
=> "RETURNED",
197 amountoutstanding
=> 10,
198 interface
=> 'commandline',
202 my $debit_2 = Koha
::Account
::Line
->new(
203 { borrowernumber
=> $patron->id,
204 debit_type_code
=> "OVERDUE",
205 status
=> "RETURNED",
207 amountoutstanding
=> 10,
208 interface
=> 'commandline',
212 $lines = Koha
::Account
::Lines
->search({ borrowernumber
=> $patron->id });
213 is
( $lines->total, 20, 'total sums correctly' );
215 my $credit_1 = Koha
::Account
::Line
->new(
216 { borrowernumber
=> $patron->id,
217 debit_type_code
=> "OVERDUE",
218 status
=> "RETURNED",
220 amountoutstanding
=> -10,
221 interface
=> 'commandline',
225 $lines = Koha
::Account
::Lines
->search({ borrowernumber
=> $patron->id });
226 is
( $lines->total, 10, 'total sums correctly' );
228 my $credit_2 = Koha
::Account
::Line
->new(
229 { borrowernumber
=> $patron->id,
230 debit_type_code
=> "OVERDUE",
231 status
=> "RETURNED",
233 amountoutstanding
=> -10,
234 interface
=> 'commandline',
238 $lines = Koha
::Account
::Lines
->search({ borrowernumber
=> $patron->id });
239 is
( $lines->total, 0, 'total sums correctly' );
241 my $credit_3 = Koha
::Account
::Line
->new(
242 { borrowernumber
=> $patron->id,
243 debit_type_code
=> "OVERDUE",
244 status
=> "RETURNED",
246 amountoutstanding
=> -100,
247 interface
=> 'commandline',
251 $lines = Koha
::Account
::Lines
->search({ borrowernumber
=> $patron->id });
252 is
( $lines->total, -100, 'total sums correctly' );
254 $schema->storage->txn_rollback;
257 subtest
'credits_total() tests' => sub {
261 $schema->storage->txn_begin;
263 my $patron = $builder->build_object({ class => 'Koha::Patrons' });
265 my $lines = Koha
::Account
::Lines
->search({ borrowernumber
=> $patron->id });
266 is
( $lines->credits_total, 0, 'credits_total returns 0 if no lines (undef case)' );
268 my $debit_1 = Koha
::Account
::Line
->new(
269 { borrowernumber
=> $patron->id,
270 debit_type_code
=> "OVERDUE",
271 status
=> "RETURNED",
273 amountoutstanding
=> 10,
274 interface
=> 'commandline',
278 my $debit_2 = Koha
::Account
::Line
->new(
279 { borrowernumber
=> $patron->id,
280 debit_type_code
=> "OVERDUE",
281 status
=> "RETURNED",
283 amountoutstanding
=> 10,
284 interface
=> 'commandline',
288 $lines = Koha
::Account
::Lines
->search({ borrowernumber
=> $patron->id });
289 is
( $lines->credits_total, 0, 'credits_total sums correctly' );
291 my $credit_1 = Koha
::Account
::Line
->new(
292 { borrowernumber
=> $patron->id,
293 debit_type_code
=> "OVERDUE",
294 status
=> "RETURNED",
296 amountoutstanding
=> -10,
297 interface
=> 'commandline',
301 $lines = Koha
::Account
::Lines
->search({ borrowernumber
=> $patron->id });
302 is
( $lines->credits_total, -10, 'credits_total sums correctly' );
304 my $credit_2 = Koha
::Account
::Line
->new(
305 { borrowernumber
=> $patron->id,
306 debit_type_code
=> "OVERDUE",
307 status
=> "RETURNED",
309 amountoutstanding
=> -10,
310 interface
=> 'commandline',
314 $lines = Koha
::Account
::Lines
->search({ borrowernumber
=> $patron->id });
315 is
( $lines->credits_total, -20, 'credits_total sums correctly' );
317 my $credit_3 = Koha
::Account
::Line
->new(
318 { borrowernumber
=> $patron->id,
319 debit_type_code
=> "OVERDUE",
320 status
=> "RETURNED",
322 amountoutstanding
=> -100,
323 interface
=> 'commandline',
327 $lines = Koha
::Account
::Lines
->search({ borrowernumber
=> $patron->id });
328 is
( $lines->credits_total, -120, 'credits_total sums correctly' );
330 $schema->storage->txn_rollback;
333 subtest
'debits_total() tests' => sub {
337 $schema->storage->txn_begin;
339 my $patron = $builder->build_object({ class => 'Koha::Patrons' });
341 my $lines = Koha
::Account
::Lines
->search({ borrowernumber
=> $patron->id });
342 is
( $lines->debits_total, 0, 'debits_total returns 0 if no lines (undef case)' );
344 my $debit_1 = Koha
::Account
::Line
->new(
345 { borrowernumber
=> $patron->id,
346 debit_type_code
=> "OVERDUE",
347 status
=> "RETURNED",
349 amountoutstanding
=> 0,
350 interface
=> 'commandline',
354 my $debit_2 = Koha
::Account
::Line
->new(
355 { borrowernumber
=> $patron->id,
356 debit_type_code
=> "OVERDUE",
357 status
=> "RETURNED",
359 amountoutstanding
=> 0,
360 interface
=> 'commandline',
364 $lines = Koha
::Account
::Lines
->search({ borrowernumber
=> $patron->id });
365 is
( $lines->debits_total, 20, 'debits_total sums correctly' );
367 my $credit_1 = Koha
::Account
::Line
->new(
368 { borrowernumber
=> $patron->id,
369 debit_type_code
=> "OVERDUE",
370 status
=> "RETURNED",
372 amountoutstanding
=> 0,
373 interface
=> 'commandline',
377 $lines = Koha
::Account
::Lines
->search({ borrowernumber
=> $patron->id });
378 is
( $lines->debits_total, 20, 'debits_total sums correctly' );
380 my $credit_2 = Koha
::Account
::Line
->new(
381 { borrowernumber
=> $patron->id,
382 debit_type_code
=> "OVERDUE",
383 status
=> "RETURNED",
385 amountoutstanding
=> 0,
386 interface
=> 'commandline',
390 $lines = Koha
::Account
::Lines
->search({ borrowernumber
=> $patron->id });
391 is
( $lines->debits_total, 20, 'debits_total sums correctly' );
393 my $credit_3 = Koha
::Account
::Line
->new(
394 { borrowernumber
=> $patron->id,
395 debit_type_code
=> "OVERDUE",
396 status
=> "RETURNED",
398 amountoutstanding
=> 0,
399 interface
=> 'commandline',
403 $lines = Koha
::Account
::Lines
->search({ borrowernumber
=> $patron->id });
404 is
( $lines->debits_total, 20, 'debits_total sums correctly' );
406 $schema->storage->txn_rollback;
409 subtest
'is_credit() and is_debit() tests' => sub {
413 $schema->storage->txn_begin;
415 my $patron = $builder->build_object({ class => 'Koha::Patrons' });
416 my $account = $patron->account;
418 my $credit = $account->add_credit({ amount
=> 100, user_id
=> $patron->id, interface
=> 'commandline' });
420 ok
( $credit->is_credit, 'is_credit detects credits' );
421 ok
( !$credit->is_debit, 'is_debit detects credits' );
423 my $debit = Koha
::Account
::Line
->new(
425 borrowernumber
=> $patron->id,
426 debit_type_code
=> "OVERDUE",
427 status
=> "RETURNED",
429 interface
=> 'commandline',
432 ok
( !$debit->is_credit, 'is_credit detects debits' );
433 ok
( $debit->is_debit, 'is_debit detects debits');
435 $schema->storage->txn_rollback;
438 subtest
'apply() tests' => sub {
442 $schema->storage->txn_begin;
444 my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
445 my $account = $patron->account;
447 my $credit = $account->add_credit( { amount
=> 100, user_id
=> $patron->id, interface
=> 'commandline' } );
449 my $debit_1 = Koha
::Account
::Line
->new(
450 { borrowernumber
=> $patron->id,
451 debit_type_code
=> "OVERDUE",
452 status
=> "RETURNED",
454 amountoutstanding
=> 10,
455 interface
=> 'commandline',
459 my $debit_2 = Koha
::Account
::Line
->new(
460 { borrowernumber
=> $patron->id,
461 debit_type_code
=> "OVERDUE",
462 status
=> "RETURNED",
464 amountoutstanding
=> 100,
465 interface
=> 'commandline',
469 $credit->discard_changes;
470 $debit_1->discard_changes;
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' );
479 $debit_1->discard_changes;
480 is
( $debit_1->amountoutstanding * 1, 0, 'Debit has been cancelled' );
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' );
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' );
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' );
502 $debits = Koha
::Account
::Lines
->search({ accountlines_id
=> $debit_1->id });
504 { $credit->apply({ debits
=> [ $debits->as_list ] }); }
505 'Koha::Exceptions::Account::NoAvailableCredit',
506 '->apply() can only be used with outstanding credits';
508 $debits = Koha
::Account
::Lines
->search({ accountlines_id
=> $credit->id });
510 { $debit_1->apply({ debits
=> [ $debits->as_list ] }); }
511 'Koha::Exceptions::Account::IsNotCredit',
512 '->apply() can only be used with credits';
514 $debits = Koha
::Account
::Lines
->search({ accountlines_id
=> $credit->id });
515 my $credit_3 = $account->add_credit({ amount
=> 1, interface
=> 'commandline' });
517 { $credit_3->apply({ debits
=> [ $debits->as_list ] }); }
518 'Koha::Exceptions::Account::IsNotDebit',
519 '->apply() can only be applied to credits';
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",
527 amountoutstanding
=> 100,
528 interface
=> 'commandline',
532 $debits = Koha
::Account
::Lines
->search({ accountlines_id
=> { -in => [ $debit_1->id, $debit_2->id, $debit_3->id, $credit->id ] } });
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';
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' );
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' } );
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' );
551 $schema->storage->txn_rollback;
554 subtest
'Keep account info when related patron, staff, item or cash_register is deleted' => sub {
558 $schema->storage->txn_begin;
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(
565 class => 'Koha::Checkouts',
566 value
=> { itemnumber
=> $item->itemnumber }
569 my $register = $builder->build_object({ class => 'Koha::Cash::Registers' });
571 my $line = Koha
::Account
::Line
->new(
573 borrowernumber
=> $patron->borrowernumber,
574 manager_id
=> $staff->borrowernumber,
575 itemnumber
=> $item->itemnumber,
576 debit_type_code
=> "OVERDUE",
577 status
=> "RETURNED",
579 interface
=> 'commandline',
580 register_id
=> $register->id
585 $line = $line->get_from_storage;
586 is
( $line->itemnumber, undef, "The account line should not be deleted when the related item is 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");
593 $line = $line->get_from_storage;
594 is
( $line->borrowernumber, undef, "The account line should not be deleted when the related patron is 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");
600 $schema->storage->txn_rollback;
603 subtest
'adjust() tests' => sub {
607 $schema->storage->txn_begin;
609 # count logs before any actions
610 my $action_logs = $schema->resultset('ActionLog')->search()->count;
613 t
::lib
::Mocks
::mock_preference
( 'FinesLog', 0 );
615 my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
616 my $account = $patron->account;
618 my $debit_1 = Koha
::Account
::Line
->new(
619 { borrowernumber
=> $patron->id,
620 debit_type_code
=> "OVERDUE",
621 status
=> "RETURNED",
623 amountoutstanding
=> 10,
624 interface
=> 'commandline',
628 my $debit_2 = Koha
::Account
::Line
->new(
629 { borrowernumber
=> $patron->id,
630 debit_type_code
=> "OVERDUE",
631 status
=> "UNRETURNED",
633 amountoutstanding
=> 100,
634 interface
=> 'commandline'
638 my $credit = $account->add_credit( { amount
=> 40, user_id
=> $patron->id, interface
=> 'commandline' } );
640 throws_ok
{ $debit_1->adjust( { amount
=> 50, type
=> 'bad', interface
=> 'commandline' } ) }
641 qr/Update type not recognised/, 'Exception thrown for unrecognised type';
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';
647 # Increment an unpaid fine
648 $debit_2->adjust( { amount
=> 150, type
=> 'overdue_update', interface
=> 'commandline' } )->discard_changes;
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' );
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' );
660 is
( $schema->resultset('ActionLog')->count(), $action_logs + 0, 'No log was added' );
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' } );
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' );
671 t
::lib
::Mocks
::mock_preference
( 'FinesLog', 1 );
673 # Increment the partially paid fine
674 $debit_2->adjust( { amount
=> 160, type
=> 'overdue_update', interface
=> 'commandline' } )->discard_changes;
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' );
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' );
685 is
( $schema->resultset('ActionLog')->count(), $action_logs + 1, 'Log was added' );
687 # Decrement the partially paid fine, less than what was paid
688 $debit_2->adjust( { amount
=> 50, type
=> 'overdue_update', interface
=> 'commandline' } )->discard_changes;
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' );
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' );
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)' );
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' );
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' );
714 $schema->storage->txn_rollback;
717 subtest
'checkout() tests' => sub {
720 $schema->storage->txn_begin;
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;
727 t
::lib
::Mocks
::mock_userenv
({ branchcode
=> $library->branchcode });
728 my $checkout = AddIssue
( $patron->unblessed, $item->barcode );
730 my $line = $account->add_debit({
732 interface
=> 'commandline',
733 item_id
=> $item->itemnumber,
734 issue_id
=> $checkout->issue_id,
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');
742 my ( $returned, undef, $old_checkout) = C4
::Circulation
::AddReturn
( $item->barcode, $library->branchcode );
743 is
( $returned, 1, 'The item should have been returned' );
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' );
750 $line->issue_id(undef)->store;
751 is
( $line->checkout, undef, 'Koha::Account::Line->checkout should return undef if no checkout linked' );
753 $schema->storage->txn_rollback;
756 subtest
'credits() and debits() tests' => sub {
759 $schema->storage->txn_begin;
761 my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
762 my $account = $patron->account;
764 my $debit1 = $account->add_debit({
766 interface
=> 'commandline',
769 my $debit2 = $account->add_debit({
771 interface
=> 'commandline',
774 my $credit1 = $account->add_credit({
776 interface
=> 'commandline',
779 my $credit2 = $account->add_credit({
781 interface
=> 'commandline',
785 $credit1->apply({ debits
=> [ $debit1 ] });
786 $credit2->apply({ debits
=> [ $debit1, $debit2 ] });
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');
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');
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');
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');
812 $schema->storage->txn_rollback;
815 subtest
"void() tests" => sub {
819 $schema->storage->txn_begin;
822 my $categorycode = $builder->build({ source
=> 'Category' })->{ categorycode
};
823 my $branchcode = $builder->build({ source
=> 'Branch' })->{ branchcode
};
825 my $borrower = Koha
::Patron
->new( {
826 cardnumber
=> 'dariahall',
828 firstname
=> 'Daria',
830 $borrower->categorycode( $categorycode );
831 $borrower->branchcode( $branchcode );
834 my $account = Koha
::Account
->new({ patron_id
=> $borrower->id });
836 my $line1 = Koha
::Account
::Line
->new(
838 borrowernumber
=> $borrower->borrowernumber,
840 amountoutstanding
=> 10,
841 interface
=> 'commandline',
842 debit_type_code
=> 'OVERDUE'
845 my $line2 = Koha
::Account
::Line
->new(
847 borrowernumber
=> $borrower->borrowernumber,
849 amountoutstanding
=> 20,
850 interface
=> 'commandline',
851 debit_type_code
=> 'OVERDUE'
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' );
859 my $id = $account->pay(
861 lines
=> [$line1, $line2],
866 my $account_payment = Koha
::Account
::Lines
->find( $id );
868 is
( $account->balance(), 0, "Account balance is 0" );
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' );
875 my $ret = $account_payment->void();
877 is
( ref($ret), 'Koha::Account::Line', 'Void returns the account line' );
878 is
( $account->balance(), 30, "Account balance is again 30" );
880 $account_payment->_result->discard_changes();
881 $line1->_result->discard_changes();
882 $line2->_result->discard_changes();
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' );
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' );
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' );
900 $schema->storage->txn_rollback;
903 subtest
"payout() tests" => sub {
907 $schema->storage->txn_begin;
911 $builder->build( { source
=> 'Category' } )->{categorycode
};
912 my $branchcode = $builder->build( { source
=> 'Branch' } )->{branchcode
};
914 my $borrower = Koha
::Patron
->new(
916 cardnumber
=> 'dariahall',
918 firstname
=> 'Daria',
921 $borrower->categorycode($categorycode);
922 $borrower->branchcode($branchcode);
925 my $staff = Koha
::Patron
->new(
927 cardnumber
=> 'bobby',
929 firstname
=> 'Bobby',
932 $staff->categorycode($categorycode);
933 $staff->branchcode($branchcode);
936 my $account = Koha
::Account
->new( { patron_id
=> $borrower->id } );
938 my $debit1 = Koha
::Account
::Line
->new(
940 borrowernumber
=> $borrower->borrowernumber,
942 amountoutstanding
=> 10,
943 interface
=> 'commandline',
944 debit_type_code
=> 'OVERDUE'
947 my $credit1 = Koha
::Account
::Line
->new(
949 borrowernumber
=> $borrower->borrowernumber,
951 amountoutstanding
=> -20,
952 interface
=> 'commandline',
953 credit_type_code
=> 'CREDIT'
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' );
964 interface
=> 'intranet',
965 staff_id
=> $staff->borrowernumber,
966 branch
=> $branchcode,
967 payout_type
=> 'CASH',
971 throws_ok
{ $debit1->payout($pay_params); }
972 'Koha::Exceptions::Account::IsNotCredit',
973 '->payout() can only be used with credits';
976 ( 'interface', 'staff_id', 'branch', 'payout_type', 'amount' );
977 for my $required (@required) {
978 my $params = {%$pay_params};
979 delete( $params->{$required} );
981 $credit1->payout($params);
983 'Koha::Exceptions::MissingParameter',
984 "->payout() requires the `$required` parameter is passed";
990 interface
=> 'intranet',
991 staff_id
=> $staff->borrowernumber,
992 branch
=> $branchcode,
993 payout_type
=> 'CASH',
998 'Koha::Exceptions::ParameterTooHigh',
999 '->payout() cannot pay out more than the amountoutstanding';
1001 t
::lib
::Mocks
::mock_preference
( 'UseCashRegisters', 1 );
1005 interface
=> 'intranet',
1006 staff_id
=> $staff->borrowernumber,
1007 branch
=> $branchcode,
1008 payout_type
=> 'CASH',
1013 'Koha::Exceptions::Account::RegisterRequired',
1014 '->payout() requires a cash_register if payout_type is `CASH`';
1016 t
::lib
::Mocks
::mock_preference
( 'UseCashRegisters', 0 );
1017 my $payout = $credit1->payout(
1019 interface
=> 'intranet',
1020 staff_id
=> $staff->borrowernumber,
1021 branch
=> $branchcode,
1022 payout_type
=> 'CASH',
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" );
1038 $schema->storage->txn_rollback;
1041 subtest
"reduce() tests" => sub {
1045 $schema->storage->txn_begin;
1049 $builder->build( { source
=> 'Category' } )->{categorycode
};
1050 my $branchcode = $builder->build( { source
=> 'Branch' } )->{branchcode
};
1052 my $borrower = Koha
::Patron
->new(
1054 cardnumber
=> 'dariahall',
1056 firstname
=> 'Daria',
1059 $borrower->categorycode($categorycode);
1060 $borrower->branchcode($branchcode);
1063 my $staff = Koha
::Patron
->new(
1065 cardnumber
=> 'bobby',
1066 surname
=> 'Bloggs',
1067 firstname
=> 'Bobby',
1070 $staff->categorycode($categorycode);
1071 $staff->branchcode($branchcode);
1074 my $account = Koha
::Account
->new( { patron_id
=> $borrower->id } );
1076 my $debit1 = Koha
::Account
::Line
->new(
1078 borrowernumber
=> $borrower->borrowernumber,
1080 amountoutstanding
=> 20,
1081 interface
=> 'commandline',
1082 debit_type_code
=> 'LOST'
1085 my $credit1 = Koha
::Account
::Line
->new(
1087 borrowernumber
=> $borrower->borrowernumber,
1089 amountoutstanding
=> -20,
1090 interface
=> 'commandline',
1091 credit_type_code
=> 'CREDIT'
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' );
1101 my $reduce_params = {
1102 interface
=> 'commandline',
1103 reduction_type
=> 'REFUND',
1105 staff_id
=> $staff->borrowernumber,
1106 branch
=> $branchcode
1109 throws_ok
{ $credit1->reduce($reduce_params); }
1110 'Koha::Exceptions::Account::IsNotDebit',
1111 '->reduce() can only be used with debits';
1113 my @required = ( 'interface', 'reduction_type', 'amount' );
1114 for my $required (@required) {
1115 my $params = {%$reduce_params};
1116 delete( $params->{$required} );
1118 $debit1->reduce($params);
1120 'Koha::Exceptions::MissingParameter',
1121 "->reduce() requires the `$required` parameter is passed";
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} );
1130 $debit1->reduce($params);
1132 'Koha::Exceptions::MissingParameter',
1133 "->reduce() requires the `$d` parameter is passed when interface is intranet";
1139 interface
=> 'intranet',
1140 staff_id
=> $staff->borrowernumber,
1141 branch
=> $branchcode,
1142 reduction_type
=> 'REFUND',
1147 'Koha::Exceptions::ParameterTooHigh',
1148 '->reduce() cannot reduce more than original amount';
1151 # (Refund 5 on debt of 20)
1152 my $reduction = $debit1->reduce($reduce_params);
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'" );
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'" );
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" );
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'" );
1190 # Compound reduction should not allow more than original amount
1191 # (Reduction of 5 + 5 + 20 > 20)
1192 $reduce_params->{amount
} = 20;
1194 $debit1->reduce($reduce_params);
1196 'Koha::Exceptions::ParameterTooHigh',
1197 '->reduce cannot reduce more than the original amount (combined reductions test)';
1199 # Throw exception if attempting to reduce a payout
1200 my $payout = $reduction->payout(
1202 interface
=> 'intranet',
1203 staff_id
=> $staff->borrowernumber,
1204 branch
=> $branchcode,
1205 payout_type
=> 'CASH',
1210 $payout->reduce($reduce_params);
1212 'Koha::Exceptions::Account::IsNotDebit',
1213 '->reduce() cannot be used on a payout debit';
1215 $schema->storage->txn_rollback;