From c8c2d6861c5b23c0e3ff187516dfaaaa0090980f Mon Sep 17 00:00:00 2001 From: Martin Renvoize Date: Mon, 16 Feb 2026 07:50:41 +0000 Subject: [PATCH] Bug 40445: Add configuration options and UI enhancements This patch adds configuration options for cashup reconciliation and enhances the user interface with preview functionality and improved interaction patterns. Configuration Options: 1. CashupReconciliationNoteRequired (System Preference): - Optional validation requiring reconciliation notes when discrepancies exist - Ensures documentation of surplus/deficit causes when needed - Can be disabled for less stringent workflows - Applied at cashup completion time 2. CashupReconciliationNoteAuthorisedValue (System Preference): - Allows use of authorized value dropdown for reconciliation notes - Provides standardized note options (e.g., "Till count error", "Cash removed") - Falls back to free text textarea when not configured - Improves consistency and reporting capabilities UI Enhancements: 1. Cashup Summary Preview: - Preview cashup summary for in-progress cashups - Shows current transaction totals before completion - Clearly indicates preview vs. final summary status - Helps staff verify amounts before committing 2. Improved User Experience: - Conditional note fields based on configuration - Authorized value dropdowns when configured - Clear validation messages - Responsive UI updates Backend changes: - Enhanced Koha::REST::V1::CashRegisters::Cashups with preview support - Database atomicupdate script for system preferences - Validation logic for required notes Frontend changes: - Preview modal functionality in cashup_modal.js - Conditional rendering of note input fields - Authorized value dropdown integration - Enhanced summary display logic Test plan: 1. Apply patches and run database update 2. Configure CashupReconciliationNoteRequired: - Enable preference - Perform cashup with discrepancy without note - should fail - Add note and retry - should succeed - Disable preference - verify note optional 3. Configure CashupReconciliationNoteAuthorisedValue: - Create AV category (e.g., CASHUP_NOTE) with values - Set preference to category name - Verify dropdown appears in cashup modal - Clear preference - verify textarea shown 4. Test cashup preview: - Start cashup on register - Click "Preview cashup summary" - Verify current totals displayed - Add transaction and re-preview - verify updated - Complete cashup - verify final summary Sponsored-by: OpenFifth Signed-off-by: Jackie Usher --- Koha/REST/V1/CashRegisters/Cashups.pm | 16 +++ .../data/mysql/atomicupdate/bug_40445.pl | 40 +++++++ installer/data/mysql/mandatory/sysprefs.sql | 2 + .../modules/admin/preferences/accounting.pref | 10 ++ .../intranet-tmpl/prog/js/cashup_modal.js | 104 ++++++++++++++++-- 5 files changed, 165 insertions(+), 7 deletions(-) diff --git a/Koha/REST/V1/CashRegisters/Cashups.pm b/Koha/REST/V1/CashRegisters/Cashups.pm index 375eab431e0..ffb8e63b9f1 100644 --- a/Koha/REST/V1/CashRegisters/Cashups.pm +++ b/Koha/REST/V1/CashRegisters/Cashups.pm @@ -63,8 +63,24 @@ sub get { my $c = shift->openapi->valid_input or return; return try { + + # Try to find as a completed cashup first my $cashup = Koha::Cash::Register::Cashups->find( $c->param('cashup_id') ); + # If not found, try as a CASHUP_START action (for preview) + unless ($cashup) { + require Koha::Cash::Register::Actions; + my $action = Koha::Cash::Register::Actions->find( $c->param('cashup_id') ); + + # Only allow CASHUP_START actions for preview + if ( $action && $action->code eq 'CASHUP_START' ) { + + # Wrap as Cashup object for summary generation + require Koha::Cash::Register::Cashup; + $cashup = Koha::Cash::Register::Cashup->_new_from_dbic( $action->_result ); + } + } + return $c->render_resource_not_found("Cashup") unless $cashup; diff --git a/installer/data/mysql/atomicupdate/bug_40445.pl b/installer/data/mysql/atomicupdate/bug_40445.pl index f6226eb4512..e73db60c26f 100755 --- a/installer/data/mysql/atomicupdate/bug_40445.pl +++ b/installer/data/mysql/atomicupdate/bug_40445.pl @@ -33,5 +33,45 @@ return { $out, "Staff can now record actual cash amounts during cashup with automatic surplus/deficit tracking" ); + + # Add CashupReconciliationNoteRequired preference + $dbh->do( + q{ + INSERT IGNORE INTO systempreferences (variable, value, options, explanation, type) + VALUES ( + 'CashupReconciliationNoteRequired', + '0', + '', + 'Require a reconciliation note when completing cashup with discrepancies between expected and actual amounts', + 'YesNo' + ) + } + ); + + say_success( $out, "Added CashupReconciliationNoteRequired system preference" ); + say_info( + $out, + "CashupReconciliationNoteRequired: Controls whether reconciliation notes are required during cashup with discrepancies" + ); + + # Add CashupReconciliationNoteAuthorisedValue preference + $dbh->do( + q{ + INSERT IGNORE INTO systempreferences (variable, value, options, explanation, type) + VALUES ( + 'CashupReconciliationNoteAuthorisedValue', + '', + '', + 'Authorized value category to use for cashup reconciliation notes (leave empty for free text)', + 'Free' + ) + } + ); + + say_success( $out, "Added CashupReconciliationNoteAuthorisedValue system preference" ); + say_info( + $out, + "CashupReconciliationNoteAuthorisedValue: Optionally restrict reconciliation notes to authorized values" + ); }, }; diff --git a/installer/data/mysql/mandatory/sysprefs.sql b/installer/data/mysql/mandatory/sysprefs.sql index 585d7a5d8d8..40fd0cddcf5 100644 --- a/installer/data/mysql/mandatory/sysprefs.sql +++ b/installer/data/mysql/mandatory/sysprefs.sql @@ -149,6 +149,8 @@ INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` ('CardnumberLength', '', NULL, 'Set a length for card numbers with a maximum of 32 characters.', 'Free'), ('CardnumberLog','1',NULL,'If ON, log edit actions on patron cardnumbers','YesNo'), ('casAuthentication','0',NULL,'Enable or disable CAS authentication','YesNo'), +('CashupReconciliationNoteAuthorisedValue', '', NULL, 'Authorized value category to use for cashup reconciliation notes (leave empty for free text)', 'Free'), +('CashupReconciliationNoteRequired', '0', NULL, 'Require a reconciliation note when completing cashup with discrepancies between expected and actual amounts', 'YesNo'), ('casLogout','0',NULL,'Does a logout from Koha should also log the user out of CAS?','YesNo'), ('casServerUrl','https://localhost:8443/cas',NULL,'URL of the cas server','Free'), ('casServerVersion','2', '2|3','Version of the CAS server Koha will connect to.','Choice'), diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/accounting.pref b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/accounting.pref index 55bcd590cfb..627ca5a800f 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/accounting.pref +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/accounting.pref @@ -57,3 +57,13 @@ Accounting: branchyyyymmincr: 'Automatically generate credit numbers in the form yyyymm0001' incremental: 'Automatically generate credit numbers in the form 1, 2, 3' - Automatic generation also has to be enabled for each credit type (Configure credit types). + - + - pref: CashupReconciliationNoteRequired + choices: + 1: "Require" + 0: "Don't require" + - a reconciliation note when completing cashup with discrepancies between expected and actual amounts. + - + - "Use authorized value category " + - pref: CashupReconciliationNoteAuthorisedValue + - " for reconciliation notes during cashup. Leave empty to allow free text entry." diff --git a/koha-tmpl/intranet-tmpl/prog/js/cashup_modal.js b/koha-tmpl/intranet-tmpl/prog/js/cashup_modal.js index 306b82cb106..ba236ee67c3 100644 --- a/koha-tmpl/intranet-tmpl/prog/js/cashup_modal.js +++ b/koha-tmpl/intranet-tmpl/prog/js/cashup_modal.js @@ -3,7 +3,20 @@ $(document).ready(function () { var button = $(e.relatedTarget); var cashup = button.data("cashup"); var description = button.data("register"); + var inProgress = button.data("in-progress") || false; var summary_modal = $(this); + + // Update title based on whether this is a preview + if (inProgress) { + summary_modal + .find("#cashupSummaryLabel") + .text(__("Cashup summary preview")); + } else { + summary_modal + .find("#cashupSummaryLabel") + .text(__("Cashup summary")); + } + summary_modal.find("#register_description").text(description); $.ajax({ url: "/api/v1/cashups/" + cashup, @@ -17,6 +30,28 @@ $(document).ready(function () { let to_date = $datetime(data.summary.to_date); summary_modal.find("#to_date").text(to_date); + // Add preview notice if this is an in-progress cashup + if (inProgress) { + var previewNotice = summary_modal.find(".preview-notice"); + if (previewNotice.length === 0) { + summary_modal + .find(".modal-body > ul") + .before( + '
' + + ' ' + + "" + + __("Preview:") + + " " + + __( + "This summary shows the expected cashup amounts. A reconciliation record may be added when you complete the cashup." + ) + + "
" + ); + } + } else { + summary_modal.find(".preview-notice").remove(); + } + // Check for reconciliation (surplus or deficit) from dedicated fields var surplus = data.summary.surplus_total; var deficit = data.summary.deficit_total; @@ -69,9 +104,35 @@ $(document).ready(function () { var tfoot = summary_modal.find("tfoot"); tfoot.empty(); + // Determine if this is a negative cashup (float deficit scenario) + var isNegativeCashup = data.summary.total < 0; + + // Add informational notice for negative cashups + if (isNegativeCashup) { + var noticeText = __( + "This cashup shows a negative amount because refunds exceeded collections during this session. " + + "The register float was topped up to restore the expected balance." + ); + tbody.prepend( + "" + + " " + + "" + + __("Float deficit:") + + " " + + noticeText + + "" + ); + } + // 1. Total (sum of all transactions) + var totalLabel = isNegativeCashup + ? __("Total float deficit") + : __("Total"); + tfoot.append( - "Total" + + "" + + totalLabel + + "" + data.summary.total.format_price() + "" ); @@ -93,8 +154,15 @@ $(document).ready(function () { } } if (cashCollected !== null) { + var cashLabel = + cashCollected < 0 + ? __("Cash added to register") + : __("Cash collected"); + tfoot.append( - "Cash collected" + + "" + + cashLabel + + "" + cashCollected.format_price() + "" ); @@ -107,10 +175,22 @@ $(document).ready(function () { type.payment_type !== "Cash" && type.payment_type !== "CASH" ) { + var paymentTypeLabel = + type.total < 0 + ? __x("{payment_type} to add", { + payment_type: escape_str( + type.payment_type + ), + }) + : __x("{payment_type} collected", { + payment_type: escape_str( + type.payment_type + ), + }); + tfoot.append( "" + - escape_str(type.payment_type) + - " collected" + + paymentTypeLabel + "" + type.total.format_price() + "" @@ -133,14 +213,14 @@ $(document).ready(function () { if (surplus) { reconciliationClass = "reconciliation-result text-warning"; - reconciliationLabel = "Cashup surplus"; + reconciliationLabel = __("Cashup surplus"); reconciliationAmount = "+" + Math.abs(surplus).format_price(); reconciliationNote = data.summary.surplus_note; } else if (deficit) { reconciliationClass = "reconciliation-result text-danger"; - reconciliationLabel = "Cashup deficit"; + reconciliationLabel = __("Cashup deficit"); reconciliationAmount = "-" + Math.abs(deficit).format_price(); reconciliationNote = data.summary.deficit_note; @@ -158,13 +238,23 @@ $(document).ready(function () { // Add note if present if (reconciliationNote) { + // Check if note is an authorized value code and use description if available + var noteDisplay = reconciliationNote; + if ( + typeof reconciliation_note_avs !== "undefined" && + reconciliation_note_avs[reconciliationNote] + ) { + noteDisplay = + reconciliation_note_avs[reconciliationNote]; + } + tfoot.append( "" + __("Note:") + " " + - escape_str(reconciliationNote) + + escape_str(noteDisplay) + "" ); } -- 2.53.0