Bugzilla – Attachment 191253 Details for
Bug 41592
Add an enhanced workflow for refunds against anonymous transactions
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 41592: Add cross-register refund search to Point of Sale
9ee282b.patch (text/plain), 27.97 KB, created by
Martin Renvoize (ashimema)
on 2026-01-12 13:13:46 UTC
(
hide
)
Description:
Bug 41592: Add cross-register refund search to Point of Sale
Filename:
MIME Type:
Creator:
Martin Renvoize (ashimema)
Created:
2026-01-12 13:13:46 UTC
Size:
27.97 KB
patch
obsolete
>From 9ee282b998c8dd9022f200ba5cc87cff0a63c502 Mon Sep 17 00:00:00 2001 >From: Martin Renvoize <martin.renvoize@openfifth.co.uk> >Date: Sun, 11 Jan 2026 17:46:31 +0000 >Subject: [PATCH] Bug 41592: Add cross-register refund search to Point of Sale > >This patch adds a "Process refund" feature to the Point of Sale page, >allowing staff to search for and refund transactions made at any register. > >Changes: >- Add toolbar with "Process refund" button to pos/pay.tt >- Add transaction search modal that searches by receipt number or transaction ID >- Implement AJAX-based search using new /api/v1/account/credits endpoint >- Display transaction details including total, date, and associated debits >- Show warning when refunding transactions from different registers >- Create reusable refund modal component (refund_modal.inc) with: > * Transaction details summary (description, original charge, amount paid) > * Payment method display (Cash/Card) > * Original register information > * Refund amount input with validation > * Transaction type selector > * Cash register selector (when applicable) > * Note field for refund reason >- Add JavaScript lookups to display human-readable descriptions instead of codes > for both debit types and payment types >- Auto-focus search input when modal opens >- Filter out PAYOUT debits and already REFUNDED items > >The refund modal is now shared and can be reused by other pages (e.g., patron >account page). It intelligently shows/hides fields based on context and available data. > >Requires the 'anonymous_refund' cash_management permission. > >Test plan: >1. Apply patch and rebuild: yarn build >2. Enable UseCashRegisters system preference >3. Create at least two cash registers for a branch >4. Make a POS sale at register A >5. Navigate to POS page >6. Click "Process refund" button >7. Search for transaction by receipt number >8. Verify transaction details are displayed correctly with proper descriptions >9. Click "Issue refund" on a debit >10. Verify refund modal shows: > - Debit description (not code) > - Original charge amount > - Amount paid > - Payment method (Cash/Card, not CASH/SIP00) > - Register warning if different >11. Complete refund and verify success >--- > .../prog/en/includes/modals/refund_modal.inc | 90 +++++++ > .../intranet-tmpl/prog/en/modules/pos/pay.tt | 236 ++++++++++++++++++ > .../prog/en/modules/pos/register.tt | 6 +- > .../intranet-tmpl/prog/js/refund_modal.js | 129 ++++++++++ > pos/register.pl | 8 +- > 5 files changed, 467 insertions(+), 2 deletions(-) > create mode 100644 koha-tmpl/intranet-tmpl/prog/en/includes/modals/refund_modal.inc > create mode 100644 koha-tmpl/intranet-tmpl/prog/js/refund_modal.js > >diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/modals/refund_modal.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/modals/refund_modal.inc >new file mode 100644 >index 00000000000..6bd4dfc8120 >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/modals/refund_modal.inc >@@ -0,0 +1,90 @@ >+<!-- Issue refund modal --> >+<div class="modal" id="issueRefundModal" tabindex="-1" role="dialog" aria-labelledby="issueRefundLabel"> >+ <form id="refund_form" action="[% refund_form_action | html %]" method="post" enctype="multipart/form-data" class="validated"> >+ [% INCLUDE 'csrf-token.inc' %] >+ <input type="hidden" name="accountline_id" value="" id="refundline" /> >+ <input type="hidden" name="op" value="cud-refund" /> >+ [% IF patron %] >+ <input type="hidden" name="borrowernumber" value="[% patron.borrowernumber | html %]" /> >+ [% END %] >+ [% IF register_id %] >+ <input type="hidden" name="registerid" value="[% register_id | html %]" id="refund_registerid" /> >+ [% ELSE %] >+ <input type="hidden" name="registerid" value="" id="refund_registerid" /> >+ [% END %] >+ <div class="modal-dialog"> >+ <div class="modal-content"> >+ <div class="modal-header"> >+ <h1 class="modal-title" id="issueRefundLabel">Issue refund</h1> >+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> >+ </div> >+ <div class="modal-body"> >+ <div id="refund_warning" class="alert alert-warning" style="display: none;"></div> >+ >+ [% IF Koha.Preference('UseCashRegisters') && ( registers.size == 0 ) %] >+ <div id="refund_error" class="alert alert-warning"> You must have at least one cash register associated with this branch before you can record cash refunds. </div> >+ [% END %] >+ >+ <!-- Debit summary --> >+ <div id="refund_debit_summary" class="alert alert-info" style="margin-bottom: 20px;"> >+ <h4>Transaction details</h4> >+ <table class="table table-sm"> >+ <tr> >+ <th>Description:</th> >+ <td id="refund_debit_description"></td> >+ </tr> >+ <tr> >+ <th>Original charge:</th> >+ <td id="refund_debit_amount"></td> >+ </tr> >+ <tr> >+ <th>Amount paid:</th> >+ <td id="refund_amount_paid"></td> >+ </tr> >+ <tr id="refund_payment_method_row" style="display: none;"> >+ <th>Payment method:</th> >+ <td id="refund_payment_method"></td> >+ </tr> >+ <tr id="refund_original_register_row" style="display: none;"> >+ <th>Original register:</th> >+ <td id="refund_original_register"></td> >+ </tr> >+ </table> >+ </div> >+ >+ <fieldset class="rows"> >+ <ol> >+ <li> >+ <label class="required" for="refund_amount">Amount to refund: </label> >+ <input type="text" inputmode="decimal" pattern="^\d+(\.\d{2})?$" id="refund_amount" name="amount" required="required" /> >+ <span class="required">Required</span> >+ </li> >+ >+ [% INCLUDE 'transaction_types.inc' type="refund" %] >+ >+ [% IF Koha.Preference('UseCashRegisters') && !register_id %] >+ <li> >+ <label for="refund_registerid_select">Cash register: </label> >+ <select name="registerid" id="refund_registerid_select"> >+ <option id="noregister" disabled selected="selected" value="">-- Select an option --</option> >+ [% PROCESS options_for_registers %] >+ </select> >+ </li> >+ [% END %] >+ >+ <li> >+ <label for="refund_note">Note: </label> >+ <textarea id="refund_note" name="refund_note" rows="5" cols="30"></textarea> >+ </li> >+ </ol> >+ </fieldset> >+ </div> >+ <div class="modal-footer"> >+ <button type="submit" class="btn btn-primary" id="refund_confirm">Confirm</button> >+ <button type="button" class="btn btn-default" data-bs-dismiss="modal">Cancel</button> >+ </div> >+ </div> >+ </div> >+ </form> >+</div> >+<!-- /#issueRefundModal --> >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/pos/pay.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/pos/pay.tt >index 4e0da24182c..a1fd4afd5b0 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/pos/pay.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/pos/pay.tt >@@ -1,5 +1,6 @@ > [% USE raw %] > [% USE AdditionalContents %] >+[% USE AuthorisedValues %] > [% USE Asset %] > [% USE Branches %] > [% USE Koha %] >@@ -7,6 +8,7 @@ > [% USE TablesSettings %] > [% USE Registers %] > [% PROCESS 'i18n.inc' %] >+[% PROCESS 'html_helpers.inc' %] > [% SET footerjs = 1 %] > [% INCLUDE 'doc-head-open.inc' %] > [% SET registers = Registers.all( filters => { current_branch => 1 } ) %] >@@ -33,6 +35,13 @@ > [% END #/ WRAPPER sub-header.inc %] > > [% WRAPPER 'main-container.inc' aside='pos-menu' %] >+ >+ [% IF ( CAN_user_cash_management_anonymous_refund ) %] >+ <div id="toolbar" class="btn-toolbar"> >+ <button id="process_refund" type="button" class="btn btn-default" data-bs-toggle="modal" data-bs-target="#refundSearchModal"><i class="fa-solid fa-undo"></i> Process refund</button> >+ </div> >+ [% END %] >+ > <h1>Point of sale</h1> > > [% IF ( registers.size == 0 ) %] >@@ -240,6 +249,57 @@ > </div> > </div> > >+<!-- Refund search modal --> >+<div class="modal" id="refundSearchModal" tabindex="-1" role="dialog" aria-labelledby="refundSearchLabel"> >+ <div class="modal-dialog modal-lg"> >+ <div class="modal-content"> >+ <div class="modal-header"> >+ <h1 class="modal-title" id="refundSearchLabel">Search transaction for refund</h1> >+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> >+ </div> >+ <div class="modal-body"> >+ <!-- Messages block for errors/warnings --> >+ <div id="refund_search_messages"></div> >+ >+ <!-- Search form --> >+ <div id="refund_search_form"> >+ <div class="input-group"> >+ <input type="text" id="receipt_id" name="receipt_id" class="form-control" placeholder="Enter receipt number or transaction ID" /> >+ <button type="button" class="btn btn-primary" id="search_transaction"><i class="fa fa-search"></i> Search</button> >+ </div> >+ </div> >+ >+ <!-- Search results --> >+ <div id="refund_search_results" style="display: none; margin-top: 20px;"> >+ <div class="page-section"> >+ <div id="transaction_info"></div> >+ <div id="transaction_debits" style="margin-top: 15px;"> >+ <h5>Items in this transaction:</h5> >+ <table id="search_results_table" class="table table-bordered table-striped"> >+ <thead> >+ <tr> >+ <th>Description</th> >+ <th>Amount</th> >+ <th>Status</th> >+ <th class="NoSort">Action</th> >+ </tr> >+ </thead> >+ <tbody></tbody> >+ </table> >+ </div> >+ </div> >+ </div> >+ </div> >+ <div class="modal-footer"> >+ <button type="button" class="btn btn-default" data-bs-dismiss="modal">Close</button> >+ </div> >+ </div> >+ </div> >+</div> >+<!-- /#refundSearchModal --> >+ >+[% INCLUDE 'modals/refund_modal.inc' refund_form_action = '/cgi-bin/koha/pos/register.pl' registers = registers %] >+ > [% IF payment_id && Koha.Preference('FinePaymentAutoPopup') %] > <!-- Automatic Print Receipt --> > <a id="printReceipt" style="display: none" href="#"></a> >@@ -247,8 +307,24 @@ > > [% MACRO jsinclude BLOCK %] > [% INCLUDE 'format_price.inc' %] >+ [% INCLUDE 'js-date-format.inc' %] > [% INCLUDE 'datatables.inc' %] >+ [% Asset.js("js/refund_modal.js") | $raw %] > <script> >+ // Create lookup objects for descriptions >+ var debitTypeDescriptions = { >+ [% FOREACH invoice_type IN invoice_types %] >+ '[% invoice_type.code | html %]': '[% invoice_type.description | html %]', >+ [% END %] >+ }; >+ >+ // Create lookup for payment type descriptions >+ var paymentTypeDescriptions = { >+ [% FOREACH pt IN AuthorisedValues.GetAuthValueDropbox('PAYMENT_TYPE') %] >+ '[% pt.authorised_value | html %]': '[% pt.lib | html %]', >+ [% END %] >+ }; >+ > function moneyFormat(textObj) { > var newValue = textObj.value; > var decAmount = ""; >@@ -527,6 +603,166 @@ > }); > $("#printReceipt").click(); > [% END %] >+ >+ // Refund search functionality >+ var current_registerid = $('select[name="registerid"]').val(); >+ >+ // Reset search modal when opened >+ $('#refundSearchModal').on('show.bs.modal', function() { >+ $('#receipt_id').val(''); >+ $('#refund_search_results').hide(); >+ $('#refund_search_messages').empty(); >+ $('#search_results_table tbody').empty(); >+ $('#transaction_info').empty(); >+ }); >+ >+ // Auto-focus search input when modal is fully shown >+ $('#refundSearchModal').on('shown.bs.modal', function() { >+ $('#receipt_id').focus(); >+ }); >+ >+ // Search transaction button click >+ $('#search_transaction').on('click', function() { >+ var receipt_id = $('#receipt_id').val().trim(); >+ >+ if (!receipt_id) { >+ showSearchMessage('Please enter a receipt number or transaction ID.', 'warning'); >+ return; >+ } >+ >+ $('#refund_search_messages').empty(); >+ $('#refund_search_results').hide(); >+ >+ // Build query - search by both credit_number and accountlines_id >+ var query = JSON.stringify({ >+ "-or": [ >+ { "credit_number": receipt_id }, >+ { "accountlines_id": receipt_id } >+ ] >+ }); >+ >+ // Call API to search for credit >+ $.ajax({ >+ url: '/api/v1/account/credits', >+ method: 'GET', >+ data: { >+ q: query, >+ }, >+ headers: { >+ 'x-koha-embed': 'debits' >+ }, >+ success: function(data) { >+ if (data && data.length > 0) { >+ displayTransactionResults(data[0]); >+ } else { >+ showSearchMessage('No transaction found with that receipt number or ID.', 'warning'); >+ } >+ }, >+ error: function(xhr) { >+ var errorMsg = 'Error searching for transaction.'; >+ if (xhr.responseJSON && xhr.responseJSON.error) { >+ errorMsg = xhr.responseJSON.error; >+ } >+ showSearchMessage(errorMsg, 'danger'); >+ } >+ }); >+ }); >+ >+ // Allow Enter key to trigger search >+ $('#receipt_id').on('keypress', function(e) { >+ if (e.which === 13) { >+ e.preventDefault(); >+ $('#search_transaction').click(); >+ } >+ }); >+ >+ function showSearchMessage(message, type) { >+ var alertClass = 'alert-' + type; >+ var iconClass = type === 'danger' ? 'fa-exclamation-circle' : 'fa-exclamation-triangle'; >+ var html = '<div class="alert ' + alertClass + '" role="alert">'; >+ html += '<i class="fa ' + iconClass + '"></i> '; >+ html += message; >+ html += '</div>'; >+ $('#refund_search_messages').html(html); >+ } >+ >+ function displayTransactionResults(credit) { >+ // Get payment type description for display >+ var paymentTypeDesc = paymentTypeDescriptions[credit.payment_type] || credit.payment_type || ''; >+ var info_html = '<h4>Transaction details</h4>'; >+ info_html += '<table class="table table-sm">'; >+ info_html += '<tr><th>Receipt #:</th><td>' + (credit.credit_number || credit.account_line_id) + '</td></tr>'; >+ info_html += '<tr><th>Date:</th><td>' + $date(credit.date) + '</td></tr>'; >+ info_html += '<tr><th>Total:</th><td>' + Math.abs(parseFloat(credit.amount)).format_price() + '</td></tr>'; >+ info_html += '<tr><th>Payment type:</th><td>' + paymentTypeDesc + '</td></tr>'; >+ info_html += '</table>'; >+ >+ // Show warning if from different register >+ if (credit.cash_register_id && credit.cash_register_id != current_registerid) { >+ showSearchMessage('Note: This transaction was made at a different cash register.', 'warning'); >+ } >+ >+ $('#transaction_info').html(info_html); >+ >+ // Display debits >+ var tbody = $('#search_results_table tbody'); >+ tbody.empty(); >+ >+ if (credit.debits && credit.debits.length > 0) { >+ credit.debits.forEach(function(debit) { >+ // Don't show PAYOUT debits or already REFUNDED debits >+ if (debit.type === 'PAYOUT' || debit.status === 'REFUNDED') { >+ return; >+ } >+ >+ // Get description from lookup, fallback to debit.description, then type code >+ var debitDesc = debitTypeDescriptions[debit.type] || debit.description || debit.type || 'Item'; >+ >+ var row = '<tr>'; >+ row += '<td>' + debitDesc + '</td>'; >+ row += '<td>' + parseFloat(debit.amount).format_price() + '</td>'; >+ row += '<td>' + (debit.status || 'Paid') + '</td>'; >+ row += '<td>'; >+ >+ if (debit.status !== 'REFUNDED') { >+ >+ row += '<button type="button" class="btn btn-sm btn-default refund_debit_btn" '; >+ row += 'data-bs-toggle="modal" '; >+ row += 'data-bs-target="#issueRefundModal" '; >+ row += 'data-accountline="' + debit.account_line_id + '" '; >+ row += 'data-amount="' + debit.amount + '" '; >+ row += 'data-amountoutstanding="' + (debit.amount_outstanding || 0) + '" '; >+ row += 'data-item="' + debitDesc + '" '; >+ row += 'data-register-id="' + (credit.cash_register_id || '') + '" '; >+ row += 'data-payment-type="' + paymentTypeDesc + '" '; >+ row += 'data-member="">'; >+ row += '<i class="fa-solid fa-money-bill-1"></i> Issue refund</button>'; >+ } else { >+ row += '<span class="badge badge-secondary">Already refunded</span>'; >+ } >+ >+ row += '</td>'; >+ row += '</tr>'; >+ >+ tbody.append(row); >+ }); >+ >+ $('#refund_search_results').show(); >+ } else { >+ showSearchMessage('No refundable items found in this transaction.', 'info'); >+ } >+ } >+ >+ // Handle "Issue refund" button click in search results >+ // Close search modal when refund button is clicked >+ $(document).on('click', '.refund_debit_btn', function() { >+ $('#refundSearchModal').modal('hide'); >+ }); >+ >+ // Initialize refund modal with current register context >+ initRefundModal({ >+ currentRegisterId: current_registerid >+ }); > }); > </script> > [% END %] >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/pos/register.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/pos/register.tt >index e01152f7515..2f459442ef0 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/pos/register.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/pos/register.tt >@@ -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_not_found ) %] >+ <div id="error_message" class="alert alert-warning"> The account line you are trying to refund was not found. </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> >@@ -397,7 +401,7 @@ > <div class="modal" id="issueRefundModal" tabindex="-1" role="dialog" aria-labelledby="issueRefundLabel"> > <form id="refund_form" method="post" enctype="multipart/form-data" class="validated"> > [% INCLUDE 'csrf-token.inc' %] >- <input type="hidden" name="accountline" value="" id="refundline" /> >+ <input type="hidden" name="accountline_id" value="" id="refundline" /> > <div class="modal-dialog"> > <div class="modal-content"> > <div class="modal-header"> >diff --git a/koha-tmpl/intranet-tmpl/prog/js/refund_modal.js b/koha-tmpl/intranet-tmpl/prog/js/refund_modal.js >new file mode 100644 >index 00000000000..a2c06755e2d >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/js/refund_modal.js >@@ -0,0 +1,129 @@ >+/** >+ * Shared refund modal functionality >+ * Handles the issue refund modal across POS and patron account pages >+ */ >+ >+(function () { >+ "use strict"; >+ >+ /** >+ * Initialize refund modal event handlers >+ * @param {Object} options - Configuration options >+ * @param {boolean} options.validateCashRegister - Whether to validate cash register selection >+ * @param {string} options.currentRegisterId - Current register ID (for POS pages) >+ */ >+ window.initRefundModal = function (options) { >+ options = options || {}; >+ var validateCashRegister = options.validateCashRegister || false; >+ var currentRegisterId = options.currentRegisterId || null; >+ >+ /** >+ * Handle modal shown event >+ * Populate modal fields with data from the trigger button >+ */ >+ $("#issueRefundModal").on("shown.bs.modal", function (e) { >+ var button = $(e.relatedTarget); >+ >+ // Get data from button attributes >+ var item = button.data("item"); >+ var accountline = button.data("accountline"); >+ var amount = parseFloat(button.data("amount")); >+ var amountoutstanding = >+ parseFloat(button.data("amountoutstanding")) || 0; >+ var paymentType = button.data("payment-type"); >+ var member = button.data("member"); >+ var registerId = button.data("register-id"); >+ >+ // Calculate amount paid >+ var paid = amount - amountoutstanding; >+ >+ // Populate transaction details section >+ $("#refund_debit_description").text(item); >+ $("#refund_debit_amount").text(amount.format_price()); >+ $("#refund_amount_paid").text(paid.format_price()); >+ >+ // Show payment method if available >+ if (paymentType) { >+ $("#refund_payment_method").text(paymentType); >+ $("#refund_payment_method_row").show(); >+ } else { >+ $("#refund_payment_method_row").hide(); >+ } >+ >+ // Show original register info if different from current (POS context) >+ if ( >+ registerId && >+ currentRegisterId && >+ registerId != currentRegisterId >+ ) { >+ $("#refund_original_register").text("Register #" + registerId); >+ $("#refund_original_register_row").show(); >+ $("#refund_warning") >+ .html( >+ "<strong>Note:</strong> Original payment was made at a different register. " + >+ "The refund will be processed from your current register." >+ ) >+ .show(); >+ } else if ($("#refund_original_register_row").length) { >+ $("#refund_original_register_row").hide(); >+ $("#refund_warning").hide(); >+ } >+ >+ // Populate form fields >+ $("#refundline").val(accountline); >+ $("#refund_amount").attr({ >+ value: paid.format_price(), >+ max: paid.format_price(), >+ min: 0, >+ }); >+ >+ // Set register ID if provided >+ if (registerId && $("#refund_registerid").length) { >+ $("#refund_registerid").val(registerId); >+ } else if (currentRegisterId && $("#refund_registerid").length) { >+ $("#refund_registerid").val(currentRegisterId); >+ } >+ >+ // Handle account credit option based on member >+ if (member === "" || member === undefined) { >+ $("#refund_type option[value='AC']").remove(); >+ } else if ($("#refund_type option[value='AC']").length == 0) { >+ $("#refund_type").prepend( >+ '<option value="AC" selected="selected">Account credit</option>' >+ ); >+ } >+ >+ // Clear note field >+ $("#refund_note").val(""); >+ >+ // Focus on amount and type fields >+ $("#refund_amount, #refund_type").focus(); >+ }); >+ >+ /** >+ * Validate cash register requirement for refunds >+ * Only applies when validateCashRegister is true >+ */ >+ if (validateCashRegister) { >+ $("#refund_form").on("submit", function (e) { >+ var refund_type = $("#refund_type").val(); >+ var register = $("#refund_registerid_select").val(); >+ >+ // Only require register for non-AC refunds with CASH or SIP00 payment types >+ if ( >+ refund_type && >+ refund_type !== "AC" && >+ (refund_type === "CASH" || refund_type.match(/^SIP\d{2}$/)) >+ ) { >+ if (!register || register === "") { >+ e.preventDefault(); >+ alert(__("Cash register is required for cash refunds")); >+ $("#refund_registerid_select").focus(); >+ return false; >+ } >+ } >+ return true; >+ }); >+ } >+ }; >+})(); >diff --git a/pos/register.pl b/pos/register.pl >index 8972b3da003..0597ef9d7f0 100755 >--- a/pos/register.pl >+++ b/pos/register.pl >@@ -121,10 +121,16 @@ if ( !$registers->count ) { > if ( $logged_in_user->has_permission( { cash_management => 'anonymous_refund' } ) ) { > my $amount = $input->param('amount'); > my $quantity = $input->param('quantity'); >- my $accountline_id = $input->param('accountline'); >+ my $accountline_id = $input->param('accountline_id'); > my $refund_type = $input->param('refund_type'); > > my $accountline = Koha::Account::Lines->find($accountline_id); >+ >+ unless ($accountline) { >+ $template->param( error_refund_not_found => 1 ); >+ output_html_with_http_headers( $input, $cookie, $template->output ); >+ exit; >+ } > $schema->txn_do( > sub { > >-- >2.52.0 >
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 41592
:
191252
| 191253 |
191254
|
191255