Bug 23355: Link cash_register_actions to cash_registers by foreign key
[koha.git] / t / db_dependent / Koha / Account / Lines.t
blob667071f0a2ed45e7edb2931c907ce7d6697f84c1
1 #!/usr/bin/perl
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>
20 use Modern::Perl;
22 use Test::More tests => 15;
23 use Test::Exception;
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;
31 use t::lib::Mocks;
32 use t::lib::TestBuilder;
34 my $schema = Koha::Database->new->schema;
35 my $builder = t::lib::TestBuilder->new;
37 subtest 'patron() tests' => sub {
39 plan tests => 3;
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",
50 status => "RETURNED",
51 amount => 10,
52 interface => 'commandline',
53 })->store;
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 {
68 plan tests => 3;
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',
82 itype => 'BK',
83 })->store;
85 my $line = Koha::Account::Line->new(
87 borrowernumber => $patron->{borrowernumber},
88 itemnumber => $item->itemnumber,
89 debit_type_code => "OVERDUE",
90 status => "RETURNED",
91 amount => 10,
92 interface => 'commandline',
93 })->store;
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 {
107 plan tests => 5;
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",
120 amount => 10,
121 amountoutstanding => 10,
122 interface => 'commandline',
124 )->store;
126 my $debit_2 = Koha::Account::Line->new(
127 { borrowernumber => $patron->id,
128 debit_type_code => "OVERDUE",
129 status => "RETURNED",
130 amount => 10,
131 amountoutstanding => 10,
132 interface => 'commandline',
134 )->store;
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",
143 amount => -10,
144 amountoutstanding => -10,
145 interface => 'commandline',
147 )->store;
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",
156 amount => -10,
157 amountoutstanding => -10,
158 interface => 'commandline',
160 )->store;
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",
169 amount => -100,
170 amountoutstanding => -100,
171 interface => 'commandline',
173 )->store;
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 {
183 plan tests => 5;
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",
196 amount => 10,
197 amountoutstanding => 10,
198 interface => 'commandline',
200 )->store;
202 my $debit_2 = Koha::Account::Line->new(
203 { borrowernumber => $patron->id,
204 debit_type_code => "OVERDUE",
205 status => "RETURNED",
206 amount => 10,
207 amountoutstanding => 10,
208 interface => 'commandline',
210 )->store;
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",
219 amount => -10,
220 amountoutstanding => -10,
221 interface => 'commandline',
223 )->store;
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",
232 amount => -10,
233 amountoutstanding => -10,
234 interface => 'commandline',
236 )->store;
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",
245 amount => -100,
246 amountoutstanding => -100,
247 interface => 'commandline',
249 )->store;
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 {
259 plan tests => 5;
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",
272 amount => 10,
273 amountoutstanding => 10,
274 interface => 'commandline',
276 )->store;
278 my $debit_2 = Koha::Account::Line->new(
279 { borrowernumber => $patron->id,
280 debit_type_code => "OVERDUE",
281 status => "RETURNED",
282 amount => 10,
283 amountoutstanding => 10,
284 interface => 'commandline',
286 )->store;
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",
295 amount => -10,
296 amountoutstanding => -10,
297 interface => 'commandline',
299 )->store;
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",
308 amount => -10,
309 amountoutstanding => -10,
310 interface => 'commandline',
312 )->store;
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",
321 amount => -100,
322 amountoutstanding => -100,
323 interface => 'commandline',
325 )->store;
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 {
335 plan tests => 5;
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",
348 amount => 10,
349 amountoutstanding => 0,
350 interface => 'commandline',
352 )->store;
354 my $debit_2 = Koha::Account::Line->new(
355 { borrowernumber => $patron->id,
356 debit_type_code => "OVERDUE",
357 status => "RETURNED",
358 amount => 10,
359 amountoutstanding => 0,
360 interface => 'commandline',
362 )->store;
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",
371 amount => -10,
372 amountoutstanding => 0,
373 interface => 'commandline',
375 )->store;
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",
384 amount => -10,
385 amountoutstanding => 0,
386 interface => 'commandline',
388 )->store;
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",
397 amount => -100,
398 amountoutstanding => 0,
399 interface => 'commandline',
401 )->store;
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 {
411 plan tests => 4;
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",
428 amount => 10,
429 interface => 'commandline',
430 })->store;
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 {
440 plan tests => 24;
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",
453 amount => 10,
454 amountoutstanding => 10,
455 interface => 'commandline',
457 )->store;
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',
467 )->store;
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' );
478 # re-read debit info
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 });
503 throws_ok
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 });
509 throws_ok
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' });
516 throws_ok
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",
526 amount => 100,
527 amountoutstanding => 100,
528 interface => 'commandline',
530 )->store;
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';
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 {
556 plan tests => 4;
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",
578 amount => 10,
579 interface => 'commandline',
580 register_id => $register->id
581 })->store;
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");
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");
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");
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");
600 $schema->storage->txn_rollback;
603 subtest 'adjust() tests' => sub {
605 plan tests => 29;
607 $schema->storage->txn_begin;
609 # count logs before any actions
610 my $action_logs = $schema->resultset('ActionLog')->search()->count;
612 # Disable logs
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",
622 amount => 10,
623 amountoutstanding => 10,
624 interface => 'commandline',
626 )->store;
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'
636 )->store;
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' );
670 # Enable logs
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 {
718 plan tests => 6;
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({
731 amount => 10,
732 interface => 'commandline',
733 item_id => $item->itemnumber,
734 issue_id => $checkout->issue_id,
735 type => 'OVERDUE',
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 {
757 plan tests => 10;
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({
765 amount => 8,
766 interface => 'commandline',
767 type => 'ACCOUNT',
769 my $debit2 = $account->add_debit({
770 amount => 12,
771 interface => 'commandline',
772 type => 'ACCOUNT',
774 my $credit1 = $account->add_credit({
775 amount => 5,
776 interface => 'commandline',
777 type => 'CREDIT',
779 my $credit2 = $account->add_credit({
780 amount => 10,
781 interface => 'commandline',
782 type => 'CREDIT',
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 {
817 plan tests => 16;
819 $schema->storage->txn_begin;
821 # Create a borrower
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',
827 surname => 'Hall',
828 firstname => 'Daria',
829 } );
830 $borrower->categorycode( $categorycode );
831 $borrower->branchcode( $branchcode );
832 $borrower->store;
834 my $account = Koha::Account->new({ patron_id => $borrower->id });
836 my $line1 = Koha::Account::Line->new(
838 borrowernumber => $borrower->borrowernumber,
839 amount => 10,
840 amountoutstanding => 10,
841 interface => 'commandline',
842 debit_type_code => 'OVERDUE'
844 )->store();
845 my $line2 = Koha::Account::Line->new(
847 borrowernumber => $borrower->borrowernumber,
848 amount => 20,
849 amountoutstanding => 20,
850 interface => 'commandline',
851 debit_type_code => 'OVERDUE'
853 )->store();
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],
862 amount => 30,
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 {
905 plan tests => 18;
907 $schema->storage->txn_begin;
909 # Create a borrower
910 my $categorycode =
911 $builder->build( { source => 'Category' } )->{categorycode};
912 my $branchcode = $builder->build( { source => 'Branch' } )->{branchcode};
914 my $borrower = Koha::Patron->new(
916 cardnumber => 'dariahall',
917 surname => 'Hall',
918 firstname => 'Daria',
921 $borrower->categorycode($categorycode);
922 $borrower->branchcode($branchcode);
923 $borrower->store;
925 my $staff = Koha::Patron->new(
927 cardnumber => 'bobby',
928 surname => 'Bloggs',
929 firstname => 'Bobby',
932 $staff->categorycode($categorycode);
933 $staff->branchcode($branchcode);
934 $staff->store;
936 my $account = Koha::Account->new( { patron_id => $borrower->id } );
938 my $debit1 = Koha::Account::Line->new(
940 borrowernumber => $borrower->borrowernumber,
941 amount => 10,
942 amountoutstanding => 10,
943 interface => 'commandline',
944 debit_type_code => 'OVERDUE'
946 )->store();
947 my $credit1 = Koha::Account::Line->new(
949 borrowernumber => $borrower->borrowernumber,
950 amount => -20,
951 amountoutstanding => -20,
952 interface => 'commandline',
953 credit_type_code => 'CREDIT'
955 )->store();
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' );
963 my $pay_params = {
964 interface => 'intranet',
965 staff_id => $staff->borrowernumber,
966 branch => $branchcode,
967 payout_type => 'CASH',
968 amount => 20
971 throws_ok { $debit1->payout($pay_params); }
972 'Koha::Exceptions::Account::IsNotCredit',
973 '->payout() can only be used with credits';
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);
983 'Koha::Exceptions::MissingParameter',
984 "->payout() requires the `$required` parameter is passed";
987 throws_ok {
988 $credit1->payout(
990 interface => 'intranet',
991 staff_id => $staff->borrowernumber,
992 branch => $branchcode,
993 payout_type => 'CASH',
994 amount => 25
998 'Koha::Exceptions::ParameterTooHigh',
999 '->payout() cannot pay out more than the amountoutstanding';
1001 t::lib::Mocks::mock_preference( 'UseCashRegisters', 1 );
1002 throws_ok {
1003 $credit1->payout(
1005 interface => 'intranet',
1006 staff_id => $staff->borrowernumber,
1007 branch => $branchcode,
1008 payout_type => 'CASH',
1009 amount => 10
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',
1023 amount => 10
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 {
1043 plan tests => 27;
1045 $schema->storage->txn_begin;
1047 # Create a borrower
1048 my $categorycode =
1049 $builder->build( { source => 'Category' } )->{categorycode};
1050 my $branchcode = $builder->build( { source => 'Branch' } )->{branchcode};
1052 my $borrower = Koha::Patron->new(
1054 cardnumber => 'dariahall',
1055 surname => 'Hall',
1056 firstname => 'Daria',
1059 $borrower->categorycode($categorycode);
1060 $borrower->branchcode($branchcode);
1061 $borrower->store;
1063 my $staff = Koha::Patron->new(
1065 cardnumber => 'bobby',
1066 surname => 'Bloggs',
1067 firstname => 'Bobby',
1070 $staff->categorycode($categorycode);
1071 $staff->branchcode($branchcode);
1072 $staff->store;
1074 my $account = Koha::Account->new( { patron_id => $borrower->id } );
1076 my $debit1 = Koha::Account::Line->new(
1078 borrowernumber => $borrower->borrowernumber,
1079 amount => 20,
1080 amountoutstanding => 20,
1081 interface => 'commandline',
1082 debit_type_code => 'LOST'
1084 )->store();
1085 my $credit1 = Koha::Account::Line->new(
1087 borrowernumber => $borrower->borrowernumber,
1088 amount => -20,
1089 amountoutstanding => -20,
1090 interface => 'commandline',
1091 credit_type_code => 'CREDIT'
1093 )->store();
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',
1104 amount => 5,
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} );
1117 throws_ok {
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} );
1129 throws_ok {
1130 $debit1->reduce($params);
1132 'Koha::Exceptions::MissingParameter',
1133 "->reduce() requires the `$d` parameter is passed when interface is intranet";
1136 throws_ok {
1137 $debit1->reduce(
1139 interface => 'intranet',
1140 staff_id => $staff->borrowernumber,
1141 branch => $branchcode,
1142 reduction_type => 'REFUND',
1143 amount => 25
1147 'Koha::Exceptions::ParameterTooHigh',
1148 '->reduce() cannot reduce more than original amount';
1150 # Partial Reduction
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;
1193 throws_ok {
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',
1206 amount => 5
1209 throws_ok {
1210 $payout->reduce($reduce_params);
1212 'Koha::Exceptions::Account::IsNotDebit',
1213 '->reduce() cannot be used on a payout debit';
1215 $schema->storage->txn_rollback;