Bug 41819: (follow-up) Reject 'AC' refunds for anonymous transactions server-side

The previous patch hides the 'Account credit' option in the refund modal
when the accountline has no borrowernumber, but that is a UI convenience
only. A crafted POST (or a stale page) could still submit
refund_type=AC against an anonymous accountline, which would create a
REFUND credit with no patron to attach it to and leave the register
unbalanced because the payout branch is skipped.

Add a server-side guard in pos/register.pl that refuses the operation
and surfaces a new error_refund_anonymous_ac flag, rendered in
pos/register.tt in the same style as the other refund errors.

Test plan:
1. Apply the patch
2. Log in as a user with cash_management/anonymous_refund permission
3. Record an anonymous sale against a register
4. Using the browser devtools (or curl), POST to pos/register.pl with
   op=cud-refund, refund_type=AC and the accountline id of the sale
5. => Confirm the request is rejected and the
   "Account credit refunds are not available for anonymous transactions"
   message is shown
6. => Confirm no REFUND accountline was created and the register
   balance is unchanged
7. Repeat the POST with refund_type=CASH (or any non-AC type) and
   confirm the refund + payout are created normally

Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
This commit is contained in:
Martin Renvoize 2026-04-22 10:21:37 +01:00 committed by Lucas Gass
parent 39eae27a42
commit 8dba205b9a
No known key found for this signature in database
GPG Key ID: 60B447398B0D36CA
2 changed files with 39 additions and 27 deletions

View File

@ -52,6 +52,10 @@
<div id="error_message" class="alert alert-warning"> You do not have permission to perform refund actions. </div>
[% END %]
[% IF ( error_refund_anonymous_ac ) %]
<div id="error_message" class="alert alert-warning"> Account credit refunds are not available for anonymous transactions. Please choose a different refund type. </div>
[% END %]
[% IF ( CAN_user_cash_management_cashup ) %]
<div id="toolbar" class="btn-toolbar">
<button id="pos_cashup" type="button" class="btn btn-default" data-bs-toggle="modal" data-bs-target="#confirmCashupModal"><i class="fa-solid fa-money-bill-1"></i> Record cashup</button>

View File

@ -126,39 +126,47 @@ if ( !$registers->count ) {
my $refund_type = $input->param('refund_type');
my $accountline = Koha::Account::Lines->find($accountline_id);
$schema->txn_do(
sub {
my $refund = $accountline->reduce(
{
reduction_type => 'REFUND',
branch => $library_id,
staff_id => $logged_in_user->id,
interface => 'intranet',
amount => $amount
}
);
# Refuse 'AC' (Account credit) refunds for anonymous accountlines:
# there is no patron account to credit, so the refund would be
# orphaned and the register would be left unbalanced.
if ( $refund_type eq 'AC' && !$accountline->borrowernumber ) {
$template->param( error_refund_anonymous_ac => 1 );
} else {
$schema->txn_do(
sub {
# Only create a payout if refund_type is not 'AC' (Account Credit)
# When 'AC' is selected, the refund stays as credit on the patron's account
unless ( $refund_type eq 'AC' ) {
my $payout = $refund->payout(
my $refund = $accountline->reduce(
{
payout_type => $refund_type,
branch => $library_id,
staff_id => $logged_in_user->id,
cash_register => $cash_register->id,
interface => 'intranet',
amount => $amount
reduction_type => 'REFUND',
branch => $library_id,
staff_id => $logged_in_user->id,
interface => 'intranet',
amount => $amount
}
);
}
}
);
# Redirect to prevent duplicate submissions (POST/REDIRECT/GET pattern)
print $input->redirect( "/cgi-bin/koha/pos/register.pl?registerid=" . $registerid );
exit;
# Only create a payout if refund_type is not 'AC' (Account Credit)
# When 'AC' is selected, the refund stays as credit on the patron's account
unless ( $refund_type eq 'AC' ) {
my $payout = $refund->payout(
{
payout_type => $refund_type,
branch => $library_id,
staff_id => $logged_in_user->id,
cash_register => $cash_register->id,
interface => 'intranet',
amount => $amount
}
);
}
}
);
# Redirect to prevent duplicate submissions (POST/REDIRECT/GET pattern)
print $input->redirect( "/cgi-bin/koha/pos/register.pl?registerid=" . $registerid );
exit;
}
} else {
$template->param( error_refund_permission => 1 );
}