View | Details | Raw Unified | Return to bug 40445
Collapse All | Expand All

(-)a/Koha/Cash/Register.pm (+10 lines)
Lines 395-400 sub add_cashup { Link Here
395
    # Calculate difference (actual - expected)
395
    # Calculate difference (actual - expected)
396
    my $difference = $amount - $expected_amount;
396
    my $difference = $amount - $expected_amount;
397
397
398
    # Validate reconciliation note requirement if there's a discrepancy
399
    if ( $difference != 0 ) {
400
        my $note_required = C4::Context->preference('CashupReconciliationNoteRequired') // 0;
401
402
        if ( $note_required && !defined $reconciliation_note ) {
403
            Koha::Exceptions::MissingParameter->throw(
404
                error => "Reconciliation note is required when cashup amount differs from expected amount" );
405
        }
406
    }
407
398
    # Use database transaction to ensure consistency
408
    # Use database transaction to ensure consistency
399
    my $schema = $self->_result->result_source->schema;
409
    my $schema = $self->_result->result_source->schema;
400
    my $cashup;
410
    my $cashup;
(-)a/installer/data/mysql/atomicupdate/bug_40445_reconciliation_note_required.pl (+31 lines)
Line 0 Link Here
1
use Modern::Perl;
2
use Koha::Installer::Output qw(say_warning say_success say_info);
3
4
return {
5
    bug_number  => "40445",
6
    description => "Add system preference for required cashup reconciliation notes",
7
    up          => sub {
8
        my ($args) = @_;
9
        my ( $dbh, $out ) = @$args{qw(dbh out)};
10
11
        # Add CashupReconciliationNoteRequired preference
12
        $dbh->do(
13
            q{
14
            INSERT IGNORE INTO systempreferences (variable, value, options, explanation, type)
15
            VALUES (
16
                'CashupReconciliationNoteRequired',
17
                '0',
18
                '',
19
                'Require a reconciliation note when completing cashup with discrepancies between expected and actual amounts',
20
                'YesNo'
21
            )
22
        }
23
        );
24
25
        say_success( $out, "Added CashupReconciliationNoteRequired system preference" );
26
        say_info(
27
            $out,
28
            "CashupReconciliationNoteRequired: Controls whether reconciliation notes are required during cashup with discrepancies"
29
        );
30
    },
31
};
(-)a/installer/data/mysql/mandatory/sysprefs.sql (+1 lines)
Lines 150-155 INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` Link Here
150
('casLogout','0','','Does a logout from Koha should also log the user out of CAS?','YesNo'),
150
('casLogout','0','','Does a logout from Koha should also log the user out of CAS?','YesNo'),
151
('casServerUrl','https://localhost:8443/cas','','URL of the cas server','Free'),
151
('casServerUrl','https://localhost:8443/cas','','URL of the cas server','Free'),
152
('casServerVersion','2', '2|3','Version of the CAS server Koha will connect to.','Choice'),
152
('casServerVersion','2', '2|3','Version of the CAS server Koha will connect to.','Choice'),
153
('CashupReconciliationNoteRequired', '0', NULL, 'Require a reconciliation note when completing cashup with discrepancies between expected and actual amounts', 'YesNo'),
153
('CatalogConcerns', '0', NULL, 'Allow users to report catalog concerns', 'YesNo'),
154
('CatalogConcerns', '0', NULL, 'Allow users to report catalog concerns', 'YesNo'),
154
('CatalogerEmails', '', '', 'Notify these catalogers by email when a catalog concern is submitted', 'free'),
155
('CatalogerEmails', '', '', 'Notify these catalogers by email when a catalog concern is submitted', 'free'),
155
('CatalogModuleRelink','0',NULL,'If OFF the linker will never replace the authids that are set in the cataloging module.','YesNo'),
156
('CatalogModuleRelink','0',NULL,'If OFF the linker will never replace the authids that are set in the cataloging module.','YesNo'),
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/accounting.pref (+6 lines)
Lines 57-59 Accounting: Link Here
57
                branchyyyymmincr: 'Automatically generate credit numbers in the form <branchcode>yyyymm0001'
57
                branchyyyymmincr: 'Automatically generate credit numbers in the form <branchcode>yyyymm0001'
58
                incremental: 'Automatically generate credit numbers in the form 1, 2, 3'
58
                incremental: 'Automatically generate credit numbers in the form 1, 2, 3'
59
            - Automatic generation also has to be enabled for each credit type (<a href="/cgi-bin/koha/admin/credit_types.pl">Configure credit types</a>).
59
            - Automatic generation also has to be enabled for each credit type (<a href="/cgi-bin/koha/admin/credit_types.pl">Configure credit types</a>).
60
        -
61
            - pref: CashupReconciliationNoteRequired
62
              choices:
63
                1: "Require"
64
                0: "Don't require"
65
            - a reconciliation note when completing cashup with discrepancies between expected and actual amounts.
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/pos/register.tt (-1 / +18 lines)
Lines 80-85 Link Here
80
            <div id="error_message" class="alert alert-warning"> The cashup amount must be a positive number greater than zero. </div>
80
            <div id="error_message" class="alert alert-warning"> The cashup amount must be a positive number greater than zero. </div>
81
        [% END %]
81
        [% END %]
82
82
83
        [% IF ( error_reconciliation_note_required ) %]
84
            <div id="error_message" class="alert alert-warning"> Reconciliation note is required when cashup amount differs from expected amount. </div>
85
        [% END %]
86
83
        [% IF ( error_cashup_complete ) %]
87
        [% IF ( error_cashup_complete ) %]
84
            <div id="error_message" class="alert alert-warning">
88
            <div id="error_message" class="alert alert-warning">
85
                Failed to complete cashup. Please try again.
89
                Failed to complete cashup. Please try again.
Lines 465-472 Link Here
465
                                <span id="reconciliation_text"></span>
469
                                <span id="reconciliation_text"></span>
466
                            </li>
470
                            </li>
467
                            <li id="reconciliation_note_field" style="display: none;">
471
                            <li id="reconciliation_note_field" style="display: none;">
468
                                <label for="reconciliation_note">Note (optional):</label>
472
                                <label for="reconciliation_note" id="reconciliation_note_label">Note (optional):</label>
469
                                <textarea id="reconciliation_note" name="reconciliation_note" rows="3" cols="40" maxlength="1000" placeholder="Enter a note explaining the surplus or deficit..."></textarea>
473
                                <textarea id="reconciliation_note" name="reconciliation_note" rows="3" cols="40" maxlength="1000" placeholder="Enter a note explaining the surplus or deficit..."></textarea>
474
                                [% IF reconciliation_note_required %]
475
                                    <span class="required">Required</span>
476
                                [% END %]
470
                                <div class="hint">Maximum 1000 characters</div>
477
                                <div class="hint">Maximum 1000 characters</div>
471
                            </li>
478
                            </li>
472
                        </ol>
479
                        </ol>
Lines 743-751 Link Here
743
                // Show/hide note field based on whether there's a discrepancy
750
                // Show/hide note field based on whether there's a discrepancy
744
                if (hasDiscrepancy) {
751
                if (hasDiscrepancy) {
745
                    $("#reconciliation_note_field").show();
752
                    $("#reconciliation_note_field").show();
753
                    // Update required attribute and label based on system preference
754
                    [% IF reconciliation_note_required %]
755
                        $("#reconciliation_note").attr('required', 'required');
756
                        $("#reconciliation_note_label").addClass("required");
757
                    [% ELSE %]
758
                        $("#reconciliation_note").removeAttr('required');
759
                        $("#reconciliation_note_label").removeClass("required");
760
                    [% END %]
746
                } else {
761
                } else {
747
                    $("#reconciliation_note_field").hide();
762
                    $("#reconciliation_note_field").hide();
748
                    $("#reconciliation_note").val(''); // Clear note when balanced
763
                    $("#reconciliation_note").val(''); // Clear note when balanced
764
                    $("#reconciliation_note").removeAttr('required');
765
                    $("#reconciliation_note_label").removeClass("required");
749
                }
766
                }
750
            } else {
767
            } else {
751
                $("#reconciliation_display").hide();
768
                $("#reconciliation_display").hide();
(-)a/pos/register.pl (-4 / +11 lines)
Lines 68-76 if ( !$registers->count ) { Link Here
68
    my $cashup_in_progress = $cash_register->cashup_in_progress();
68
    my $cashup_in_progress = $cash_register->cashup_in_progress();
69
69
70
    $template->param(
70
    $template->param(
71
        register           => $cash_register,
71
        register                     => $cash_register,
72
        accountlines       => $accountlines,
72
        accountlines                 => $accountlines,
73
        cashup_in_progress => $cashup_in_progress,
73
        cashup_in_progress           => $cashup_in_progress,
74
        reconciliation_note_required => C4::Context->preference('CashupReconciliationNoteRequired'),
74
    );
75
    );
75
76
76
    my $transactions_range_from = $input->param('trange_f');
77
    my $transactions_range_from = $input->param('trange_f');
Lines 160-166 if ( !$registers->count ) { Link Here
160
                    } elsif ( $@->isa('Koha::Exceptions::Object::DuplicateID') ) {
161
                    } elsif ( $@->isa('Koha::Exceptions::Object::DuplicateID') ) {
161
                        $template->param( error_cashup_already_completed => 1 );
162
                        $template->param( error_cashup_already_completed => 1 );
162
                    } elsif ( $@->isa('Koha::Exceptions::MissingParameter') ) {
163
                    } elsif ( $@->isa('Koha::Exceptions::MissingParameter') ) {
163
                        $template->param( error_cashup_missing_param => 1, error_message => $@ );
164
165
                        # Check if this is a reconciliation note error specifically
166
                        if ( $@->error =~ /Reconciliation note is required/ ) {
167
                            $template->param( error_reconciliation_note_required => 1 );
168
                        } else {
169
                            $template->param( error_cashup_missing_param => 1, error_message => $@ );
170
                        }
164
                    } elsif ( $@->isa('Koha::Exceptions::Account::AmountNotPositive') ) {
171
                    } elsif ( $@->isa('Koha::Exceptions::Account::AmountNotPositive') ) {
165
                        $template->param( error_cashup_amount_invalid => 1 );
172
                        $template->param( error_cashup_amount_invalid => 1 );
166
                    } else {
173
                    } else {
(-)a/t/db_dependent/Koha/Cash/Register.t (-2 / +126 lines)
Lines 20-26 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 => 10;
23
use Test::More tests => 11;
24
24
25
use Test::Exception;
25
use Test::Exception;
26
26
Lines 29-34 use Koha::Account; Link Here
29
use Koha::Account::CreditTypes;
29
use Koha::Account::CreditTypes;
30
use Koha::Account::DebitTypes;
30
use Koha::Account::DebitTypes;
31
31
32
use t::lib::Mocks;
32
use t::lib::TestBuilder;
33
use t::lib::TestBuilder;
33
34
34
my $builder = t::lib::TestBuilder->new;
35
my $builder = t::lib::TestBuilder->new;
Lines 1580-1582 subtest 'add_cashup' => sub { Link Here
1580
1581
1581
    $schema->storage->txn_rollback;
1582
    $schema->storage->txn_rollback;
1582
};
1583
};
1583
- 
1584
1585
subtest 'required_reconciliation_note' => sub {
1586
    plan tests => 4;
1587
1588
    $schema->storage->txn_begin;
1589
1590
    my $register = $builder->build_object( { class => 'Koha::Cash::Registers' } );
1591
    my $manager  = $builder->build_object( { class => 'Koha::Patrons' } );
1592
    my $patron   = $builder->build_object( { class => 'Koha::Patrons' } );
1593
    my $account  = $patron->account;
1594
1595
    # Create a cash transaction
1596
    my $fine = $account->add_debit(
1597
        {
1598
            amount    => '10.00',
1599
            type      => 'OVERDUE',
1600
            interface => 'cron'
1601
        }
1602
    );
1603
1604
    my $payment = $account->pay(
1605
        {
1606
            cash_register => $register->id,
1607
            amount        => '10.00',
1608
            credit_type   => 'PAYMENT',
1609
            payment_type  => 'CASH',
1610
            lines         => [$fine]
1611
        }
1612
    );
1613
1614
    # Enable the required note preference
1615
    t::lib::Mocks::mock_preference( 'CashupReconciliationNoteRequired', 1 );
1616
1617
    # Test 1: Missing note with discrepancy throws exception
1618
    throws_ok {
1619
        $register->add_cashup(
1620
            {
1621
                manager_id => $manager->id,
1622
                amount     => '15.00'         # Creates discrepancy
1623
            }
1624
        );
1625
    }
1626
    'Koha::Exceptions::MissingParameter',
1627
        'Missing reconciliation note with discrepancy throws MissingParameter exception when preference enabled';
1628
1629
    # Test 2: Note provided with discrepancy succeeds
1630
    my $cashup1;
1631
    lives_ok {
1632
        $cashup1 = $register->add_cashup(
1633
            {
1634
                manager_id          => $manager->id,
1635
                amount              => '15.00',
1636
                reconciliation_note => 'Found extra money'
1637
            }
1638
        );
1639
    }
1640
    'Cashup with note and discrepancy succeeds when preference enabled';
1641
1642
    # Test 3: No note with no discrepancy succeeds (note only required for discrepancies)
1643
    my $register2 = $builder->build_object( { class => 'Koha::Cash::Registers' } );
1644
    my $fine2     = $account->add_debit(
1645
        {
1646
            amount    => '20.00',
1647
            type      => 'OVERDUE',
1648
            interface => 'cron'
1649
        }
1650
    );
1651
1652
    my $payment2 = $account->pay(
1653
        {
1654
            cash_register => $register2->id,
1655
            amount        => '20.00',
1656
            credit_type   => 'PAYMENT',
1657
            payment_type  => 'CASH',
1658
            lines         => [$fine2]
1659
        }
1660
    );
1661
1662
    my $cashup2;
1663
    lives_ok {
1664
        $cashup2 = $register2->add_cashup(
1665
            {
1666
                manager_id => $manager->id,
1667
                amount     => '20.00'         # Exact amount, no discrepancy
1668
            }
1669
        );
1670
    }
1671
    'Cashup without note succeeds when there is no discrepancy';
1672
1673
    # Test 4: Preference disabled allows missing note even with discrepancy
1674
    t::lib::Mocks::mock_preference( 'CashupReconciliationNoteRequired', 0 );
1675
1676
    my $register3 = $builder->build_object( { class => 'Koha::Cash::Registers' } );
1677
    my $fine3     = $account->add_debit(
1678
        {
1679
            amount    => '10.00',
1680
            type      => 'OVERDUE',
1681
            interface => 'cron'
1682
        }
1683
    );
1684
1685
    my $payment3 = $account->pay(
1686
        {
1687
            cash_register => $register3->id,
1688
            amount        => '10.00',
1689
            credit_type   => 'PAYMENT',
1690
            payment_type  => 'CASH',
1691
            lines         => [$fine3]
1692
        }
1693
    );
1694
1695
    my $cashup3;
1696
    lives_ok {
1697
        $cashup3 = $register3->add_cashup(
1698
            {
1699
                manager_id => $manager->id,
1700
                amount     => '15.00'         # Creates discrepancy
1701
            }
1702
        );
1703
    }
1704
    'Missing note with discrepancy succeeds when preference disabled';
1705
1706
    $schema->storage->txn_rollback;
1707
};

Return to bug 40445