From 3ed69bb1fe016b61612824f15d289c1771ab72b9 Mon Sep 17 00:00:00 2001 From: Martin Renvoize Date: Mon, 12 Jan 2026 15:15:12 +0000 Subject: [PATCH] Bug 40445: (follow-up) Fix two-stage cashup completion not saving MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When attempting to complete a two-stage cashup, the submission was failing silently and not recording the cashup completion. This was caused by two bugs: 1. Backend bug: pos/registers.pl was ignoring the user-entered amount and reconciliation note from the form submission. Instead of reading the 'amount' parameter from the form, it was recalculating the expected amount, which meant reconciliation (surplus/deficit) was never performed. 2. Frontend bug: The JavaScript modal handler was crashing when trying to parse the expected amount because jQuery's .data() method returns a number (not a string) when the value looks numeric. The code was calling .replace() on this number, which caused a TypeError. This crash prevented the registerid from being set in the hidden form field, causing the form submission to be ignored by the backend. Test plan: 1. Create some cash transactions on a register 2. Click "Record cashup" → "Start cashup" 3. Click "Complete cashup" and enter an amount different from expected 4. Add a reconciliation note 5. Click "Complete cashup" 6. Verify the cashup completes successfully 7. Verify a CASHUP_SURPLUS or CASHUP_DEFICIT accountline was created 8. Verify the reconciliation note was saved Signed-off-by: Jackie Usher --- .../prog/js/modals/cashup_modals.js | 6 +++- pos/registers.pl | 33 +++++++++++++------ 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/koha-tmpl/intranet-tmpl/prog/js/modals/cashup_modals.js b/koha-tmpl/intranet-tmpl/prog/js/modals/cashup_modals.js index 853c0319de8..34c82d3a85d 100644 --- a/koha-tmpl/intranet-tmpl/prog/js/modals/cashup_modals.js +++ b/koha-tmpl/intranet-tmpl/prog/js/modals/cashup_modals.js @@ -243,7 +243,11 @@ function initConfirmCashupModal(modalSelector, options) { modal.find(".register-id-field").val(rid); // Parse expected amount to check if negative - var expectedAmount = expected.replace(/[^0-9.-]/g, ""); + // Convert to string first in case jQuery's .data() parsed it as a number + var expectedAmount = String(expected || "").replace( + /[^0-9.-]/g, + "" + ); var isNegative = parseFloat(expectedAmount) < 0; // Update labels based on sign diff --git a/pos/registers.pl b/pos/registers.pl index 251363e01df..8e4543772c7 100755 --- a/pos/registers.pl +++ b/pos/registers.pl @@ -166,19 +166,32 @@ if ( $op eq 'cud-cashup_start' ) { next unless $register; eval { - # Quick cashup: calculate expected amount from outstanding accountlines - my $expected_amount = - $register->outstanding_accountlines->total( { payment_type => [ 'CASH', 'SIP00' ] } ) * -1; + # Get the amount from the request parameter + # For quick cashup, this will be the expected amount set by JavaScript + # For two-stage cashup completion, this will be the user-entered actual amount + my $amount = $input->param('amount'); + + # If no amount provided, calculate expected amount (backwards compatibility) + unless ( defined $amount && $amount ne '' ) { + $amount = + $register->outstanding_accountlines->total( { payment_type => [ 'CASH', 'SIP00' ] } ) * -1; + } - # Quick cashup assumes actual amount equals expected (no reconciliation needed) - $register->add_cashup( - { - manager_id => $logged_in_user->id, - amount => $expected_amount, + # Get optional reconciliation note + my $reconciliation_note = $input->param('reconciliation_note'); - # No reconciliation_note = quick cashup assumes correct amounts - } + # Complete the cashup + my %cashup_params = ( + manager_id => $logged_in_user->id, + amount => $amount, ); + + # Add reconciliation note if provided + if ( defined $reconciliation_note && $reconciliation_note ne '' ) { + $cashup_params{reconciliation_note} = $reconciliation_note; + } + + $register->add_cashup( \%cashup_params ); $success_count++; }; if ($@) { -- 2.53.0