Bug 23049: (QA follow-up) Fix for missing types in test inserts
[koha.git] / t / db_dependent / Koha / Account / Lines.t
bloba480ac656f582014affad2ee257f5e6eaea78bd9
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 => 9;
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 'is_credit() and is_debit() tests' => sub {
183 plan tests => 4;
185 $schema->storage->txn_begin;
187 my $patron = $builder->build_object({ class => 'Koha::Patrons' });
188 my $account = $patron->account;
190 my $credit = $account->add_credit({ amount => 100, user_id => $patron->id, interface => 'commandline' });
192 ok( $credit->is_credit, 'is_credit detects credits' );
193 ok( !$credit->is_debit, 'is_debit detects credits' );
195 my $debit = Koha::Account::Line->new(
197 borrowernumber => $patron->id,
198 debit_type_code => "OVERDUE",
199 status => "RETURNED",
200 amount => 10,
201 interface => 'commandline',
202 })->store;
204 ok( !$debit->is_credit, 'is_credit detects debits' );
205 ok( $debit->is_debit, 'is_debit detects debits');
207 $schema->storage->txn_rollback;
210 subtest 'apply() tests' => sub {
212 plan tests => 24;
214 $schema->storage->txn_begin;
216 my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
217 my $account = $patron->account;
219 my $credit = $account->add_credit( { amount => 100, user_id => $patron->id, interface => 'commandline' } );
221 my $debit_1 = Koha::Account::Line->new(
222 { borrowernumber => $patron->id,
223 debit_type_code => "OVERDUE",
224 status => "RETURNED",
225 amount => 10,
226 amountoutstanding => 10,
227 interface => 'commandline',
229 )->store;
231 my $debit_2 = Koha::Account::Line->new(
232 { borrowernumber => $patron->id,
233 debit_type_code => "OVERDUE",
234 status => "RETURNED",
235 amount => 100,
236 amountoutstanding => 100,
237 interface => 'commandline',
239 )->store;
241 $credit->discard_changes;
242 $debit_1->discard_changes;
244 my $debits = Koha::Account::Lines->search({ accountlines_id => $debit_1->id });
245 my $remaining_credit = $credit->apply( { debits => [ $debits->as_list ], 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' );
250 # re-read debit info
251 $debit_1->discard_changes;
252 is( $debit_1->amountoutstanding * 1, 0, 'Debit has been cancelled' );
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' );
260 $debits = Koha::Account::Lines->search({ accountlines_id => $debit_2->id });
261 $remaining_credit = $credit->apply( { debits => [ $debits->as_list ] } );
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' );
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' );
274 $debits = Koha::Account::Lines->search({ accountlines_id => $debit_1->id });
275 throws_ok
276 { $credit->apply({ debits => [ $debits->as_list ] }); }
277 'Koha::Exceptions::Account::NoAvailableCredit',
278 '->apply() can only be used with outstanding credits';
280 $debits = Koha::Account::Lines->search({ accountlines_id => $credit->id });
281 throws_ok
282 { $debit_1->apply({ debits => [ $debits->as_list ] }); }
283 'Koha::Exceptions::Account::IsNotCredit',
284 '->apply() can only be used with credits';
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->as_list ] }); }
290 'Koha::Exceptions::Account::IsNotDebit',
291 '->apply() can only be applied to credits';
293 my $credit_2 = $account->add_credit({ amount => 20, interface => 'commandline' });
294 my $debit_3 = Koha::Account::Line->new(
295 { borrowernumber => $patron->id,
296 debit_type_code => "OVERDUE",
297 status => "RETURNED",
298 amount => 100,
299 amountoutstanding => 100,
300 interface => 'commandline',
302 )->store;
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->as_list ], offset_type => 'Manual Credit' } ); }
307 'Koha::Exceptions::Account::IsNotDebit',
308 '->apply() rolls back if any of the passed lines is not a debit';
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' );
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->as_list ], offset_type => 'Manual Credit' } );
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' );
323 $schema->storage->txn_rollback;
326 subtest 'Keep account info when related patron, staff or item is deleted' => sub {
328 plan tests => 3;
330 $schema->storage->txn_begin;
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(
337 class => 'Koha::Checkouts',
338 value => { itemnumber => $item->itemnumber }
341 my $line = Koha::Account::Line->new(
343 borrowernumber => $patron->borrowernumber,
344 manager_id => $staff->borrowernumber,
345 itemnumber => $item->itemnumber,
346 debit_type_code => "OVERDUE",
347 status => "RETURNED",
348 amount => 10,
349 interface => 'commandline',
350 })->store;
352 $issue->delete;
353 $item->delete;
354 $line = $line->get_from_storage;
355 is( $line->itemnumber, undef, "The account line should not be deleted when the related item is delete");
357 $staff->delete;
358 $line = $line->get_from_storage;
359 is( $line->manager_id, undef, "The account line should not be deleted when the related staff is delete");
361 $patron->delete;
362 $line = $line->get_from_storage;
363 is( $line->borrowernumber, undef, "The account line should not be deleted when the related patron is delete");
365 $schema->storage->txn_rollback;
368 subtest 'adjust() tests' => sub {
370 plan tests => 29;
372 $schema->storage->txn_begin;
374 # count logs before any actions
375 my $action_logs = $schema->resultset('ActionLog')->search()->count;
377 # Disable logs
378 t::lib::Mocks::mock_preference( 'FinesLog', 0 );
380 my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
381 my $account = $patron->account;
383 my $debit_1 = Koha::Account::Line->new(
384 { borrowernumber => $patron->id,
385 debit_type_code => "OVERDUE",
386 status => "RETURNED",
387 amount => 10,
388 amountoutstanding => 10,
389 interface => 'commandline',
391 )->store;
393 my $debit_2 = Koha::Account::Line->new(
394 { borrowernumber => $patron->id,
395 debit_type_code => "OVERDUE",
396 status => "UNRETURNED",
397 amount => 100,
398 amountoutstanding => 100,
399 interface => 'commandline'
401 )->store;
403 my $credit = $account->add_credit( { amount => 40, user_id => $patron->id, interface => 'commandline' } );
405 throws_ok { $debit_1->adjust( { amount => 50, type => 'bad', interface => 'commandline' } ) }
406 qr/Update type not recognised/, 'Exception thrown for unrecognised type';
408 throws_ok { $debit_1->adjust( { amount => 50, type => 'overdue_update', interface => 'commandline' } ) }
409 qr/Update type not allowed on this debit_type/,
410 'Exception thrown for type conflict';
412 # Increment an unpaid fine
413 $debit_2->adjust( { amount => 150, type => 'overdue_update', interface => 'commandline' } )->discard_changes;
415 is( $debit_2->amount * 1, 150, 'Fine amount was updated in full' );
416 is( $debit_2->amountoutstanding * 1, 150, 'Fine amountoutstanding was update in full' );
417 isnt( $debit_2->date, undef, 'Date has been set' );
419 my $offsets = Koha::Account::Offsets->search( { debit_id => $debit_2->id } );
420 is( $offsets->count, 1, 'An offset is generated for the increment' );
421 my $THIS_offset = $offsets->next;
422 is( $THIS_offset->amount * 1, 50, 'Amount was calculated correctly (increment by 50)' );
423 is( $THIS_offset->type, 'OVERDUE_INCREASE', 'Adjust type stored correctly' );
425 is( $schema->resultset('ActionLog')->count(), $action_logs + 0, 'No log was added' );
427 # Update fine to partially paid
428 my $debits = Koha::Account::Lines->search({ accountlines_id => $debit_2->id });
429 $credit->apply( { debits => [ $debits->as_list ], offset_type => 'Manual Credit' } );
431 $debit_2->discard_changes;
432 is( $debit_2->amount * 1, 150, 'Fine amount unaffected by partial payment' );
433 is( $debit_2->amountoutstanding * 1, 110, 'Fine amountoutstanding updated by partial payment' );
435 # Enable logs
436 t::lib::Mocks::mock_preference( 'FinesLog', 1 );
438 # Increment the partially paid fine
439 $debit_2->adjust( { amount => 160, type => 'overdue_update', interface => 'commandline' } )->discard_changes;
441 is( $debit_2->amount * 1, 160, 'Fine amount was updated in full' );
442 is( $debit_2->amountoutstanding * 1, 120, 'Fine amountoutstanding was updated by difference' );
444 $offsets = Koha::Account::Offsets->search( { debit_id => $debit_2->id } );
445 is( $offsets->count, 3, 'An offset is generated for the increment' );
446 $THIS_offset = $offsets->last;
447 is( $THIS_offset->amount * 1, 10, 'Amount was calculated correctly (increment by 10)' );
448 is( $THIS_offset->type, 'OVERDUE_INCREASE', 'Adjust type stored correctly' );
450 is( $schema->resultset('ActionLog')->count(), $action_logs + 1, 'Log was added' );
452 # Decrement the partially paid fine, less than what was paid
453 $debit_2->adjust( { amount => 50, type => 'overdue_update', interface => 'commandline' } )->discard_changes;
455 is( $debit_2->amount * 1, 50, 'Fine amount was updated in full' );
456 is( $debit_2->amountoutstanding * 1, 10, 'Fine amountoutstanding was updated by difference' );
458 $offsets = Koha::Account::Offsets->search( { debit_id => $debit_2->id } );
459 is( $offsets->count, 4, 'An offset is generated for the decrement' );
460 $THIS_offset = $offsets->last;
461 is( $THIS_offset->amount * 1, -110, 'Amount was calculated correctly (decrement by 110)' );
462 is( $THIS_offset->type, 'OVERDUE_DECREASE', 'Adjust type stored correctly' );
464 # Decrement the partially paid fine, more than what was paid
465 $debit_2->adjust( { amount => 30, type => 'overdue_update', interface => 'commandline' } )->discard_changes;
466 is( $debit_2->amount * 1, 30, 'Fine amount was updated in full' );
467 is( $debit_2->amountoutstanding * 1, 0, 'Fine amountoutstanding was zeroed (payment was 40)' );
469 $offsets = Koha::Account::Offsets->search( { debit_id => $debit_2->id } );
470 is( $offsets->count, 5, 'An offset is generated for the decrement' );
471 $THIS_offset = $offsets->last;
472 is( $THIS_offset->amount * 1, -20, 'Amount was calculated correctly (decrement by 20)' );
473 is( $THIS_offset->type, 'OVERDUE_DECREASE', 'Adjust type stored correctly' );
475 my $overpayment_refund = $account->lines->last;
476 is( $overpayment_refund->amount * 1, -10, 'A new credit has been added' );
477 is( $overpayment_refund->description, 'Overpayment refund', 'Credit generated with the expected description' );
479 $schema->storage->txn_rollback;
482 subtest 'checkout() tests' => sub {
483 plan tests => 6;
485 $schema->storage->txn_begin;
487 my $library = $builder->build_object( { class => 'Koha::Libraries' } );
488 my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
489 my $item = $builder->build_sample_item;
490 my $account = $patron->account;
492 t::lib::Mocks::mock_userenv({ branchcode => $library->branchcode });
493 my $checkout = AddIssue( $patron->unblessed, $item->barcode );
495 my $line = $account->add_debit({
496 amount => 10,
497 interface => 'commandline',
498 item_id => $item->itemnumber,
499 issue_id => $checkout->issue_id,
500 type => 'OVERDUE',
503 my $line_checkout = $line->checkout;
504 is( ref($line_checkout), 'Koha::Checkout', 'Result type is correct' );
505 is( $line_checkout->issue_id, $checkout->issue_id, 'Koha::Account::Line->checkout should return the correct checkout');
507 my ( $returned, undef, $old_checkout) = C4::Circulation::AddReturn( $item->barcode, $library->branchcode );
508 is( $returned, 1, 'The item should have been returned' );
510 $line = $line->get_from_storage;
511 my $old_line_checkout = $line->checkout;
512 is( ref($old_line_checkout), 'Koha::Old::Checkout', 'Result type is correct' );
513 is( $old_line_checkout->issue_id, $old_checkout->issue_id, 'Koha::Account::Line->checkout should return the correct old_checkout' );
515 $line->issue_id(undef)->store;
516 is( $line->checkout, undef, 'Koha::Account::Line->checkout should return undef if no checkout linked' );
518 $schema->storage->txn_rollback;
521 subtest "void() tests" => sub {
523 plan tests => 16;
525 $schema->storage->txn_begin;
527 # Create a borrower
528 my $categorycode = $builder->build({ source => 'Category' })->{ categorycode };
529 my $branchcode = $builder->build({ source => 'Branch' })->{ branchcode };
531 my $borrower = Koha::Patron->new( {
532 cardnumber => 'dariahall',
533 surname => 'Hall',
534 firstname => 'Daria',
535 } );
536 $borrower->categorycode( $categorycode );
537 $borrower->branchcode( $branchcode );
538 $borrower->store;
540 my $account = Koha::Account->new({ patron_id => $borrower->id });
542 my $line1 = Koha::Account::Line->new(
544 borrowernumber => $borrower->borrowernumber,
545 amount => 10,
546 amountoutstanding => 10,
547 interface => 'commandline',
548 debit_type_code => 'OVERDUE'
550 )->store();
551 my $line2 = Koha::Account::Line->new(
553 borrowernumber => $borrower->borrowernumber,
554 amount => 20,
555 amountoutstanding => 20,
556 interface => 'commandline',
557 debit_type_code => 'OVERDUE'
559 )->store();
561 is( $account->balance(), 30, "Account balance is 30" );
562 is( $line1->amountoutstanding, 10, 'First fee has amount outstanding of 10' );
563 is( $line2->amountoutstanding, 20, 'Second fee has amount outstanding of 20' );
565 my $id = $account->pay(
567 lines => [$line1, $line2],
568 amount => 30,
572 my $account_payment = Koha::Account::Lines->find( $id );
574 is( $account->balance(), 0, "Account balance is 0" );
576 $line1->_result->discard_changes();
577 $line2->_result->discard_changes();
578 is( $line1->amountoutstanding+0, 0, 'First fee has amount outstanding of 0' );
579 is( $line2->amountoutstanding+0, 0, 'Second fee has amount outstanding of 0' );
581 my $ret = $account_payment->void();
583 is( ref($ret), 'Koha::Account::Line', 'Void returns the account line' );
584 is( $account->balance(), 30, "Account balance is again 30" );
586 $account_payment->_result->discard_changes();
587 $line1->_result->discard_changes();
588 $line2->_result->discard_changes();
590 is( $account_payment->accounttype, 'Pay', 'Voided payment accounttype is still Pay' );
591 is( $account_payment->status, 'VOID', 'Voided payment status is VOID' );
592 is( $account_payment->amount+0, 0, 'Voided payment amount is 0' );
593 is( $account_payment->amountoutstanding+0, 0, 'Voided payment amount outstanding is 0' );
595 is( $line1->amountoutstanding+0, 10, 'First fee again has amount outstanding of 10' );
596 is( $line2->amountoutstanding+0, 20, 'Second fee again has amount outstanding of 20' );
598 # Accountlines that are not credits should be un-voidable
599 my $line1_pre = $line1->unblessed();
600 $ret = $line1->void();
601 $line1->_result->discard_changes();
602 my $line1_post = $line1->unblessed();
603 is( $ret, undef, 'Attempted void on non-credit returns undef' );
604 is_deeply( $line1_pre, $line1_post, 'Non-credit account line cannot be voided' );
606 $schema->storage->txn_rollback;