Lines 20-30
Link Here
|
20 |
use Modern::Perl; |
20 |
use Modern::Perl; |
21 |
|
21 |
|
22 |
use Test::NoWarnings; |
22 |
use Test::NoWarnings; |
23 |
use Test::More tests => 5; |
23 |
use Test::More tests => 6; |
24 |
|
24 |
|
25 |
use Test::Exception; |
25 |
use Test::Exception; |
26 |
|
26 |
|
27 |
use Koha::Database; |
27 |
use Koha::Database; |
|
|
28 |
use Koha::Account; |
29 |
use Koha::Account::CreditTypes; |
30 |
use Koha::Account::DebitTypes; |
28 |
|
31 |
|
29 |
use t::lib::TestBuilder; |
32 |
use t::lib::TestBuilder; |
30 |
|
33 |
|
Lines 312-314
subtest 'cashup' => sub {
Link Here
|
312 |
|
315 |
|
313 |
$schema->storage->txn_rollback; |
316 |
$schema->storage->txn_rollback; |
314 |
}; |
317 |
}; |
315 |
- |
318 |
|
|
|
319 |
subtest 'cashup_reconciliation' => sub { |
320 |
plan tests => 5; |
321 |
|
322 |
$schema->storage->txn_begin; |
323 |
|
324 |
# Ensure required account types for reconciliation exist (they should already exist from mandatory data) |
325 |
use Koha::Account::CreditTypes; |
326 |
use Koha::Account::DebitTypes; |
327 |
|
328 |
my $surplus_credit_type = Koha::Account::CreditTypes->find( { code => 'CASHUP_SURPLUS' } ); |
329 |
if ( !$surplus_credit_type ) { |
330 |
$surplus_credit_type = $builder->build_object( |
331 |
{ |
332 |
class => 'Koha::Account::CreditTypes', |
333 |
value => { |
334 |
code => 'CASHUP_SURPLUS', |
335 |
description => 'Cash register surplus found during cashup', |
336 |
can_be_added_manually => 0, |
337 |
credit_number_enabled => 0, |
338 |
is_system => 1, |
339 |
archived => 0, |
340 |
} |
341 |
} |
342 |
); |
343 |
} |
344 |
|
345 |
my $deficit_debit_type = Koha::Account::DebitTypes->find( { code => 'CASHUP_DEFICIT' } ); |
346 |
if ( !$deficit_debit_type ) { |
347 |
$deficit_debit_type = $builder->build_object( |
348 |
{ |
349 |
class => 'Koha::Account::DebitTypes', |
350 |
value => { |
351 |
code => 'CASHUP_DEFICIT', |
352 |
description => 'Cash register deficit found during cashup', |
353 |
can_be_invoiced => 0, |
354 |
can_be_sold => 0, |
355 |
default_amount => undef, |
356 |
is_system => 1, |
357 |
archived => 0, |
358 |
restricts_checkouts => 0, |
359 |
} |
360 |
} |
361 |
); |
362 |
} |
363 |
|
364 |
my $register = $builder->build_object( { class => 'Koha::Cash::Registers' } ); |
365 |
my $patron = $builder->build_object( { class => 'Koha::Patrons' } ); |
366 |
|
367 |
# Create some outstanding accountlines to establish expected amount |
368 |
my $accountline1 = $builder->build_object( |
369 |
{ |
370 |
class => 'Koha::Account::Lines', |
371 |
value => { |
372 |
register_id => $register->id, |
373 |
borrowernumber => $patron->id, |
374 |
amount => -10.00, # Credit (payment) |
375 |
credit_type_code => 'PAYMENT', |
376 |
debit_type_code => undef, |
377 |
} |
378 |
} |
379 |
); |
380 |
my $accountline2 = $builder->build_object( |
381 |
{ |
382 |
class => 'Koha::Account::Lines', |
383 |
value => { |
384 |
register_id => $register->id, |
385 |
borrowernumber => $patron->id, |
386 |
amount => -5.00, # Credit (payment) |
387 |
credit_type_code => 'PAYMENT', |
388 |
debit_type_code => undef, |
389 |
} |
390 |
} |
391 |
); |
392 |
|
393 |
my $expected_amount = $register->outstanding_accountlines->total; # Should be -15.00 |
394 |
|
395 |
subtest 'balanced_cashup' => sub { |
396 |
plan tests => 3; |
397 |
|
398 |
# Test exact match - no surplus/deficit accountlines should be created |
399 |
my $amount = abs($expected_amount); # 15.00 actual matches 15.00 expected |
400 |
|
401 |
my $cashup = $register->add_cashup( |
402 |
{ |
403 |
manager_id => $patron->id, |
404 |
amount => $amount |
405 |
} |
406 |
); |
407 |
|
408 |
ok( $cashup, 'Cashup created successfully' ); |
409 |
is( sprintf( '%.0f', $cashup->amount ), sprintf( '%.0f', $amount ), 'Cashup amount matches actual amount' ); |
410 |
|
411 |
# Check no surplus/deficit accountlines were created |
412 |
my $reconciliation_lines = Koha::Account::Lines->search( |
413 |
{ |
414 |
register_id => $register->id, |
415 |
'-or' => [ |
416 |
{ credit_type_code => 'CASHUP_SURPLUS' }, |
417 |
{ debit_type_code => 'CASHUP_DEFICIT' } |
418 |
] |
419 |
} |
420 |
); |
421 |
|
422 |
is( $reconciliation_lines->count, 0, 'No reconciliation accountlines created for balanced cashup' ); |
423 |
}; |
424 |
|
425 |
subtest 'surplus_cashup' => sub { |
426 |
plan tests => 7; |
427 |
|
428 |
$schema->storage->txn_begin; |
429 |
|
430 |
my $register2 = $builder->build_object( { class => 'Koha::Cash::Registers' } ); |
431 |
my $accountline3 = $builder->build_object( |
432 |
{ |
433 |
class => 'Koha::Account::Lines', |
434 |
value => { |
435 |
register_id => $register2->id, |
436 |
borrowernumber => $patron->id, |
437 |
amount => -20.00, # Credit (payment) |
438 |
credit_type_code => 'PAYMENT', |
439 |
debit_type_code => undef, |
440 |
} |
441 |
} |
442 |
); |
443 |
|
444 |
my $expected = abs( $register2->outstanding_accountlines->total ); # 20.00 |
445 |
my $actual = 25.00; # 5.00 surplus |
446 |
my $surplus = $actual - $expected; |
447 |
|
448 |
my $cashup = $register2->add_cashup( |
449 |
{ |
450 |
manager_id => $patron->id, |
451 |
amount => $actual |
452 |
} |
453 |
); |
454 |
|
455 |
ok( $cashup, 'Surplus cashup created successfully' ); |
456 |
is( sprintf( '%.0f', $cashup->amount ), sprintf( '%.0f', $actual ), 'Cashup amount matches actual amount' ); |
457 |
|
458 |
# Check surplus accountline was created |
459 |
my $surplus_lines = Koha::Account::Lines->search( |
460 |
{ |
461 |
register_id => $register2->id, |
462 |
credit_type_code => 'CASHUP_SURPLUS' |
463 |
} |
464 |
); |
465 |
|
466 |
is( $surplus_lines->count, 1, 'One surplus accountline created' ); |
467 |
|
468 |
my $surplus_line = $surplus_lines->next; |
469 |
is( |
470 |
sprintf( '%.0f', $surplus_line->amount ), sprintf( '%.0f', -$surplus ), |
471 |
'Surplus amount is correct (negative for credit)' |
472 |
); |
473 |
|
474 |
# Note should be undef for surplus without user note |
475 |
is( $surplus_line->note, undef, 'No note for surplus without user reconciliation note' ); |
476 |
|
477 |
# Test surplus with user note |
478 |
my $register_with_note = $builder->build_object( { class => 'Koha::Cash::Registers' } ); |
479 |
my $accountline_with_note = $builder->build_object( |
480 |
{ |
481 |
class => 'Koha::Account::Lines', |
482 |
value => { |
483 |
register_id => $register_with_note->id, |
484 |
borrowernumber => $patron->id, |
485 |
amount => -10.00, |
486 |
credit_type_code => 'PAYMENT', |
487 |
debit_type_code => undef, |
488 |
} |
489 |
} |
490 |
); |
491 |
|
492 |
my $cashup_with_note = $register_with_note->add_cashup( |
493 |
{ |
494 |
manager_id => $patron->id, |
495 |
amount => 15.00, # 5.00 surplus |
496 |
reconciliation_note => 'Found extra \x{00A3}5 under the till drawer' # £5 in UTF-8 |
497 |
} |
498 |
); |
499 |
|
500 |
my $surplus_with_note = Koha::Account::Lines->search( |
501 |
{ |
502 |
register_id => $register_with_note->id, |
503 |
credit_type_code => 'CASHUP_SURPLUS' |
504 |
} |
505 |
)->next; |
506 |
|
507 |
like( |
508 |
$surplus_with_note->note, qr/Found extra .+5 under the till drawer/, |
509 |
'User note included in surplus accountline' |
510 |
); |
511 |
is( |
512 |
$surplus_with_note->note, 'Found extra \x{00A3}5 under the till drawer', |
513 |
'Only user note stored (no base reconciliation info)' |
514 |
); |
515 |
|
516 |
$schema->storage->txn_rollback; |
517 |
}; |
518 |
|
519 |
subtest 'deficit_cashup' => sub { |
520 |
plan tests => 7; |
521 |
|
522 |
$schema->storage->txn_begin; |
523 |
|
524 |
my $register3 = $builder->build_object( { class => 'Koha::Cash::Registers' } ); |
525 |
my $accountline4 = $builder->build_object( |
526 |
{ |
527 |
class => 'Koha::Account::Lines', |
528 |
value => { |
529 |
register_id => $register3->id, |
530 |
borrowernumber => $patron->id, |
531 |
amount => -30.00, # Credit (payment) |
532 |
credit_type_code => 'PAYMENT', |
533 |
debit_type_code => undef, |
534 |
} |
535 |
} |
536 |
); |
537 |
|
538 |
my $expected = abs( $register3->outstanding_accountlines->total ); # 30.00 |
539 |
my $actual = 25.00; # 5.00 deficit |
540 |
my $deficit = $expected - $actual; |
541 |
|
542 |
my $cashup = $register3->add_cashup( |
543 |
{ |
544 |
manager_id => $patron->id, |
545 |
amount => $actual |
546 |
} |
547 |
); |
548 |
|
549 |
ok( $cashup, 'Deficit cashup created successfully' ); |
550 |
is( sprintf( '%.0f', $cashup->amount ), sprintf( '%.0f', $actual ), 'Cashup amount matches actual amount' ); |
551 |
|
552 |
# Check deficit accountline was created |
553 |
my $deficit_lines = Koha::Account::Lines->search( |
554 |
{ |
555 |
register_id => $register3->id, |
556 |
debit_type_code => 'CASHUP_DEFICIT' |
557 |
} |
558 |
); |
559 |
|
560 |
is( $deficit_lines->count, 1, 'One deficit accountline created' ); |
561 |
|
562 |
my $deficit_line = $deficit_lines->next; |
563 |
is( |
564 |
sprintf( '%.0f', $deficit_line->amount ), sprintf( '%.0f', $deficit ), |
565 |
'Deficit amount is correct (positive for debit)' |
566 |
); |
567 |
|
568 |
# Note should be undef for deficit without user note |
569 |
is( $deficit_line->note, undef, 'No note for deficit without user reconciliation note' ); |
570 |
|
571 |
# Test deficit with user note |
572 |
my $register_deficit_note = $builder->build_object( { class => 'Koha::Cash::Registers' } ); |
573 |
my $accountline_deficit_note = $builder->build_object( |
574 |
{ |
575 |
class => 'Koha::Account::Lines', |
576 |
value => { |
577 |
register_id => $register_deficit_note->id, |
578 |
borrowernumber => $patron->id, |
579 |
amount => -20.00, |
580 |
credit_type_code => 'PAYMENT', |
581 |
debit_type_code => undef, |
582 |
} |
583 |
} |
584 |
); |
585 |
|
586 |
my $cashup_deficit_note = $register_deficit_note->add_cashup( |
587 |
{ |
588 |
manager_id => $patron->id, |
589 |
amount => 15.00, # 5.00 deficit |
590 |
reconciliation_note => 'Till was short, possibly due to incorrect change given' |
591 |
} |
592 |
); |
593 |
|
594 |
my $deficit_with_note = Koha::Account::Lines->search( |
595 |
{ |
596 |
register_id => $register_deficit_note->id, |
597 |
debit_type_code => 'CASHUP_DEFICIT' |
598 |
} |
599 |
)->next; |
600 |
|
601 |
like( |
602 |
$deficit_with_note->note, qr/Till was short, possibly due to incorrect change given/, |
603 |
'User note included in deficit accountline' |
604 |
); |
605 |
is( |
606 |
$deficit_with_note->note, 'Till was short, possibly due to incorrect change given', |
607 |
'Only user note stored (no base reconciliation info)' |
608 |
); |
609 |
|
610 |
$schema->storage->txn_rollback; |
611 |
}; |
612 |
|
613 |
subtest 'transaction_integrity' => sub { |
614 |
plan tests => 4; |
615 |
|
616 |
$schema->storage->txn_begin; |
617 |
|
618 |
my $register4 = $builder->build_object( { class => 'Koha::Cash::Registers' } ); |
619 |
my $accountline5 = $builder->build_object( |
620 |
{ |
621 |
class => 'Koha::Account::Lines', |
622 |
value => { |
623 |
register_id => $register4->id, |
624 |
borrowernumber => $patron->id, |
625 |
amount => -10.00, |
626 |
credit_type_code => 'PAYMENT', |
627 |
debit_type_code => undef, |
628 |
} |
629 |
} |
630 |
); |
631 |
|
632 |
my $initial_accountline_count = Koha::Account::Lines->search( { register_id => $register4->id } )->count; |
633 |
|
634 |
my $initial_action_count = $register4->cashups->count; |
635 |
|
636 |
# Test successful transaction |
637 |
my $cashup = $register4->add_cashup( |
638 |
{ |
639 |
manager_id => $patron->id, |
640 |
amount => 15.00 # Creates surplus |
641 |
} |
642 |
); |
643 |
|
644 |
# Check both cashup action and surplus accountline were created |
645 |
is( $register4->cashups->count, $initial_action_count + 1, 'Cashup action created' ); |
646 |
|
647 |
my $final_accountline_count = Koha::Account::Lines->search( { register_id => $register4->id } )->count; |
648 |
|
649 |
is( $final_accountline_count, $initial_accountline_count + 1, 'Surplus accountline created' ); |
650 |
|
651 |
# Verify the new accountline is the surplus |
652 |
my $surplus_line = Koha::Account::Lines->search( |
653 |
{ |
654 |
register_id => $register4->id, |
655 |
credit_type_code => 'CASHUP_SURPLUS' |
656 |
} |
657 |
)->next; |
658 |
|
659 |
ok( $surplus_line, 'Surplus accountline exists' ); |
660 |
is( $surplus_line->register_id, $register4->id, 'Surplus linked to correct register' ); |
661 |
|
662 |
$schema->storage->txn_rollback; |
663 |
}; |
664 |
|
665 |
subtest 'note_handling' => sub { |
666 |
plan tests => 2; |
667 |
|
668 |
$schema->storage->txn_begin; |
669 |
|
670 |
my $register_note_test = $builder->build_object( { class => 'Koha::Cash::Registers' } ); |
671 |
my $accountline_note_test = $builder->build_object( |
672 |
{ |
673 |
class => 'Koha::Account::Lines', |
674 |
value => { |
675 |
register_id => $register_note_test->id, |
676 |
borrowernumber => $patron->id, |
677 |
amount => -10.00, |
678 |
credit_type_code => 'PAYMENT', |
679 |
debit_type_code => undef, |
680 |
} |
681 |
} |
682 |
); |
683 |
|
684 |
# Test balanced cashup with note (should not create surplus/deficit) |
685 |
my $balanced_cashup = $register_note_test->add_cashup( |
686 |
{ |
687 |
manager_id => $patron->id, |
688 |
amount => 10.00, # Balanced |
689 |
reconciliation_note => 'This note should be ignored for balanced cashup' |
690 |
} |
691 |
); |
692 |
|
693 |
my $balanced_reconciliation_lines = Koha::Account::Lines->search( |
694 |
{ |
695 |
register_id => $register_note_test->id, |
696 |
'-or' => [ |
697 |
{ credit_type_code => 'CASHUP_SURPLUS' }, |
698 |
{ debit_type_code => 'CASHUP_DEFICIT' } |
699 |
] |
700 |
} |
701 |
); |
702 |
|
703 |
is( |
704 |
$balanced_reconciliation_lines->count, 0, |
705 |
'No reconciliation accountlines created for balanced cashup with note' |
706 |
); |
707 |
|
708 |
# Test empty/whitespace note handling |
709 |
my $register_empty_note = $builder->build_object( { class => 'Koha::Cash::Registers' } ); |
710 |
my $accountline_empty_note = $builder->build_object( |
711 |
{ |
712 |
class => 'Koha::Account::Lines', |
713 |
value => { |
714 |
register_id => $register_empty_note->id, |
715 |
borrowernumber => $patron->id, |
716 |
amount => -10.00, |
717 |
credit_type_code => 'PAYMENT', |
718 |
debit_type_code => undef, |
719 |
} |
720 |
} |
721 |
); |
722 |
|
723 |
my $empty_note_cashup = $register_empty_note->add_cashup( |
724 |
{ |
725 |
manager_id => $patron->id, |
726 |
amount => 12.00, # 2.00 surplus |
727 |
reconciliation_note => ' ' # Whitespace only |
728 |
} |
729 |
); |
730 |
|
731 |
my $empty_note_surplus = Koha::Account::Lines->search( |
732 |
{ |
733 |
register_id => $register_empty_note->id, |
734 |
credit_type_code => 'CASHUP_SURPLUS' |
735 |
} |
736 |
)->next; |
737 |
|
738 |
is( |
739 |
$empty_note_surplus->note, undef, |
740 |
'No note stored when user note is empty/whitespace' |
741 |
); |
742 |
|
743 |
$schema->storage->txn_rollback; |
744 |
}; |
745 |
|
746 |
$schema->storage->txn_rollback; |
747 |
}; |