|
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 |
}; |