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

(-)a/Koha/REST/V1/CashRegisters/Cashups.pm (+16 lines)
Lines 63-70 sub get { Link Here
63
    my $c = shift->openapi->valid_input or return;
63
    my $c = shift->openapi->valid_input or return;
64
64
65
    return try {
65
    return try {
66
67
        # Try to find as a completed cashup first
66
        my $cashup = Koha::Cash::Register::Cashups->find( $c->param('cashup_id') );
68
        my $cashup = Koha::Cash::Register::Cashups->find( $c->param('cashup_id') );
67
69
70
        # If not found, try as a CASHUP_START action (for preview)
71
        unless ($cashup) {
72
            require Koha::Cash::Register::Actions;
73
            my $action = Koha::Cash::Register::Actions->find( $c->param('cashup_id') );
74
75
            # Only allow CASHUP_START actions for preview
76
            if ( $action && $action->code eq 'CASHUP_START' ) {
77
78
                # Wrap as Cashup object for summary generation
79
                require Koha::Cash::Register::Cashup;
80
                $cashup = Koha::Cash::Register::Cashup->_new_from_dbic( $action->_result );
81
            }
82
        }
83
68
        return $c->render_resource_not_found("Cashup")
84
        return $c->render_resource_not_found("Cashup")
69
            unless $cashup;
85
            unless $cashup;
70
86
(-)a/installer/data/mysql/atomicupdate/bug_40445.pl (+40 lines)
Lines 33-37 return { Link Here
33
            $out,
33
            $out,
34
            "Staff can now record actual cash amounts during cashup with automatic surplus/deficit tracking"
34
            "Staff can now record actual cash amounts during cashup with automatic surplus/deficit tracking"
35
        );
35
        );
36
37
        # Add CashupReconciliationNoteRequired preference
38
        $dbh->do(
39
            q{
40
            INSERT IGNORE INTO systempreferences (variable, value, options, explanation, type)
41
            VALUES (
42
                'CashupReconciliationNoteRequired',
43
                '0',
44
                '',
45
                'Require a reconciliation note when completing cashup with discrepancies between expected and actual amounts',
46
                'YesNo'
47
            )
48
        }
49
        );
50
51
        say_success( $out, "Added CashupReconciliationNoteRequired system preference" );
52
        say_info(
53
            $out,
54
            "CashupReconciliationNoteRequired: Controls whether reconciliation notes are required during cashup with discrepancies"
55
        );
56
57
        # Add CashupReconciliationNoteAuthorisedValue preference
58
        $dbh->do(
59
            q{
60
            INSERT IGNORE INTO systempreferences (variable, value, options, explanation, type)
61
            VALUES (
62
                'CashupReconciliationNoteAuthorisedValue',
63
                '',
64
                '',
65
                'Authorized value category to use for cashup reconciliation notes (leave empty for free text)',
66
                'Free'
67
            )
68
        }
69
        );
70
71
        say_success( $out, "Added CashupReconciliationNoteAuthorisedValue system preference" );
72
        say_info(
73
            $out,
74
            "CashupReconciliationNoteAuthorisedValue: Optionally restrict reconciliation notes to authorized values"
75
        );
36
    },
76
    },
37
};
77
};
(-)a/installer/data/mysql/mandatory/sysprefs.sql (+2 lines)
Lines 149-154 INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` Link Here
149
('CardnumberLength', '', NULL, 'Set a length for card numbers with a maximum of 32 characters.', 'Free'),
149
('CardnumberLength', '', NULL, 'Set a length for card numbers with a maximum of 32 characters.', 'Free'),
150
('CardnumberLog','1',NULL,'If ON, log edit actions on patron cardnumbers','YesNo'),
150
('CardnumberLog','1',NULL,'If ON, log edit actions on patron cardnumbers','YesNo'),
151
('casAuthentication','0',NULL,'Enable or disable CAS authentication','YesNo'),
151
('casAuthentication','0',NULL,'Enable or disable CAS authentication','YesNo'),
152
('CashupReconciliationNoteAuthorisedValue', '', NULL, 'Authorized value category to use for cashup reconciliation notes (leave empty for free text)', 'Free'),
153
('CashupReconciliationNoteRequired', '0', NULL, 'Require a reconciliation note when completing cashup with discrepancies between expected and actual amounts', 'YesNo'),
152
('casLogout','0',NULL,'Does a logout from Koha should also log the user out of CAS?','YesNo'),
154
('casLogout','0',NULL,'Does a logout from Koha should also log the user out of CAS?','YesNo'),
153
('casServerUrl','https://localhost:8443/cas',NULL,'URL of the cas server','Free'),
155
('casServerUrl','https://localhost:8443/cas',NULL,'URL of the cas server','Free'),
154
('casServerVersion','2', '2|3','Version of the CAS server Koha will connect to.','Choice'),
156
('casServerVersion','2', '2|3','Version of the CAS server Koha will connect to.','Choice'),
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/accounting.pref (+10 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.
66
        -
67
            - "Use authorized value category "
68
            - pref: CashupReconciliationNoteAuthorisedValue
69
            - " for reconciliation notes during cashup. Leave empty to allow free text entry."
(-)a/koha-tmpl/intranet-tmpl/prog/js/cashup_modal.js (-8 / +97 lines)
Lines 3-9 $(document).ready(function () { Link Here
3
        var button = $(e.relatedTarget);
3
        var button = $(e.relatedTarget);
4
        var cashup = button.data("cashup");
4
        var cashup = button.data("cashup");
5
        var description = button.data("register");
5
        var description = button.data("register");
6
        var inProgress = button.data("in-progress") || false;
6
        var summary_modal = $(this);
7
        var summary_modal = $(this);
8
9
        // Update title based on whether this is a preview
10
        if (inProgress) {
11
            summary_modal
12
                .find("#cashupSummaryLabel")
13
                .text(__("Cashup summary preview"));
14
        } else {
15
            summary_modal
16
                .find("#cashupSummaryLabel")
17
                .text(__("Cashup summary"));
18
        }
19
7
        summary_modal.find("#register_description").text(description);
20
        summary_modal.find("#register_description").text(description);
8
        $.ajax({
21
        $.ajax({
9
            url: "/api/v1/cashups/" + cashup,
22
            url: "/api/v1/cashups/" + cashup,
Lines 17-22 $(document).ready(function () { Link Here
17
                let to_date = $datetime(data.summary.to_date);
30
                let to_date = $datetime(data.summary.to_date);
18
                summary_modal.find("#to_date").text(to_date);
31
                summary_modal.find("#to_date").text(to_date);
19
32
33
                // Add preview notice if this is an in-progress cashup
34
                if (inProgress) {
35
                    var previewNotice = summary_modal.find(".preview-notice");
36
                    if (previewNotice.length === 0) {
37
                        summary_modal
38
                            .find(".modal-body > ul")
39
                            .before(
40
                                '<div class="alert alert-info preview-notice">' +
41
                                    '<i class="fa-solid fa-info-circle"></i> ' +
42
                                    "<strong>" +
43
                                    __("Preview:") +
44
                                    "</strong> " +
45
                                    __(
46
                                        "This summary shows the expected cashup amounts. A reconciliation record may be added when you complete the cashup."
47
                                    ) +
48
                                    "</div>"
49
                            );
50
                    }
51
                } else {
52
                    summary_modal.find(".preview-notice").remove();
53
                }
54
20
                // Check for reconciliation (surplus or deficit) from dedicated fields
55
                // Check for reconciliation (surplus or deficit) from dedicated fields
21
                var surplus = data.summary.surplus_total;
56
                var surplus = data.summary.surplus_total;
22
                var deficit = data.summary.deficit_total;
57
                var deficit = data.summary.deficit_total;
Lines 69-77 $(document).ready(function () { Link Here
69
                var tfoot = summary_modal.find("tfoot");
104
                var tfoot = summary_modal.find("tfoot");
70
                tfoot.empty();
105
                tfoot.empty();
71
106
107
                // Determine if this is a negative cashup (float deficit scenario)
108
                var isNegativeCashup = data.summary.total < 0;
109
110
                // Add informational notice for negative cashups
111
                if (isNegativeCashup) {
112
                    var noticeText = __(
113
                        "This cashup shows a negative amount because refunds exceeded collections during this session. " +
114
                            "The register float was topped up to restore the expected balance."
115
                    );
116
                    tbody.prepend(
117
                        "<tr class='reconciliation-info'><td colspan='2'>" +
118
                            "<i class='fa-solid fa-info-circle'></i> " +
119
                            "<strong>" +
120
                            __("Float deficit:") +
121
                            "</strong> " +
122
                            noticeText +
123
                            "</td></tr>"
124
                    );
125
                }
126
72
                // 1. Total (sum of all transactions)
127
                // 1. Total (sum of all transactions)
128
                var totalLabel = isNegativeCashup
129
                    ? __("Total float deficit")
130
                    : __("Total");
131
73
                tfoot.append(
132
                tfoot.append(
74
                    "<tr class='total-row'><td><strong>Total</strong></td><td><strong>" +
133
                    "<tr class='total-row'><td><strong>" +
134
                        totalLabel +
135
                        "</strong></td><td><strong>" +
75
                        data.summary.total.format_price() +
136
                        data.summary.total.format_price() +
76
                        "</strong></td></tr>"
137
                        "</strong></td></tr>"
77
                );
138
                );
Lines 93-100 $(document).ready(function () { Link Here
93
                    }
154
                    }
94
                }
155
                }
95
                if (cashCollected !== null) {
156
                if (cashCollected !== null) {
157
                    var cashLabel =
158
                        cashCollected < 0
159
                            ? __("Cash added to register")
160
                            : __("Cash collected");
161
96
                    tfoot.append(
162
                    tfoot.append(
97
                        "<tr><td><strong>Cash collected</strong></td><td><strong>" +
163
                        "<tr><td><strong>" +
164
                            cashLabel +
165
                            "</strong></td><td><strong>" +
98
                            cashCollected.format_price() +
166
                            cashCollected.format_price() +
99
                            "</strong></td></tr>"
167
                            "</strong></td></tr>"
100
                    );
168
                    );
Lines 107-116 $(document).ready(function () { Link Here
107
                        type.payment_type !== "Cash" &&
175
                        type.payment_type !== "Cash" &&
108
                        type.payment_type !== "CASH"
176
                        type.payment_type !== "CASH"
109
                    ) {
177
                    ) {
178
                        var paymentTypeLabel =
179
                            type.total < 0
180
                                ? __x("{payment_type} to add", {
181
                                      payment_type: escape_str(
182
                                          type.payment_type
183
                                      ),
184
                                  })
185
                                : __x("{payment_type} collected", {
186
                                      payment_type: escape_str(
187
                                          type.payment_type
188
                                      ),
189
                                  });
190
110
                        tfoot.append(
191
                        tfoot.append(
111
                            "<tr><td><strong>" +
192
                            "<tr><td><strong>" +
112
                                escape_str(type.payment_type) +
193
                                paymentTypeLabel +
113
                                " collected" +
114
                                "</strong></td><td><strong>" +
194
                                "</strong></td><td><strong>" +
115
                                type.total.format_price() +
195
                                type.total.format_price() +
116
                                "</strong></td></tr>"
196
                                "</strong></td></tr>"
Lines 133-146 $(document).ready(function () { Link Here
133
                    if (surplus) {
213
                    if (surplus) {
134
                        reconciliationClass =
214
                        reconciliationClass =
135
                            "reconciliation-result text-warning";
215
                            "reconciliation-result text-warning";
136
                        reconciliationLabel = "Cashup surplus";
216
                        reconciliationLabel = __("Cashup surplus");
137
                        reconciliationAmount =
217
                        reconciliationAmount =
138
                            "+" + Math.abs(surplus).format_price();
218
                            "+" + Math.abs(surplus).format_price();
139
                        reconciliationNote = data.summary.surplus_note;
219
                        reconciliationNote = data.summary.surplus_note;
140
                    } else if (deficit) {
220
                    } else if (deficit) {
141
                        reconciliationClass =
221
                        reconciliationClass =
142
                            "reconciliation-result text-danger";
222
                            "reconciliation-result text-danger";
143
                        reconciliationLabel = "Cashup deficit";
223
                        reconciliationLabel = __("Cashup deficit");
144
                        reconciliationAmount =
224
                        reconciliationAmount =
145
                            "-" + Math.abs(deficit).format_price();
225
                            "-" + Math.abs(deficit).format_price();
146
                        reconciliationNote = data.summary.deficit_note;
226
                        reconciliationNote = data.summary.deficit_note;
Lines 158-170 $(document).ready(function () { Link Here
158
238
159
                    // Add note if present
239
                    // Add note if present
160
                    if (reconciliationNote) {
240
                    if (reconciliationNote) {
241
                        // Check if note is an authorized value code and use description if available
242
                        var noteDisplay = reconciliationNote;
243
                        if (
244
                            typeof reconciliation_note_avs !== "undefined" &&
245
                            reconciliation_note_avs[reconciliationNote]
246
                        ) {
247
                            noteDisplay =
248
                                reconciliation_note_avs[reconciliationNote];
249
                        }
250
161
                        tfoot.append(
251
                        tfoot.append(
162
                            "<tr class='" +
252
                            "<tr class='" +
163
                                reconciliationClass +
253
                                reconciliationClass +
164
                                "'><td colspan='2'><em>" +
254
                                "'><td colspan='2'><em>" +
165
                                __("Note:") +
255
                                __("Note:") +
166
                                " " +
256
                                " " +
167
                                escape_str(reconciliationNote) +
257
                                escape_str(noteDisplay) +
168
                                "</em></td></tr>"
258
                                "</em></td></tr>"
169
                        );
259
                        );
170
                    }
260
                    }
171
- 

Return to bug 40445