Bug 41592

Summary: Add an enhanced workflow for refunds against anonymous transactions
Product: Koha Reporter: Martin Renvoize (ashimema) <martin.renvoize>
Component: Point of SaleAssignee: Martin Renvoize (ashimema) <martin.renvoize>
Status: Needs Signoff --- QA Contact: Testopia <testopia>
Severity: enhancement    
Priority: P5 - low CC: hdunne-howrie, martin.renvoize, sally.healey
Version: unspecified   
Hardware: All   
OS: All   
GIT URL: Initiative type: ---
Sponsorship status: --- Comma delimited list of Sponsors:
Crowdfunding goal: 0 Patch complexity: ---
Documentation contact: Documentation submission:
Text to go in the release notes:
Version(s) released in:
Circulation function:
Bug Depends on: 24082    
Bug Blocks: 41596    
Attachments: Bug 41592: Add API endpoints for global account line search
Bug 41592: Add cross-register refund search to Point of Sale
Bug 41592: Refactor patron account page to use shared refund modal
Bug 41592: Refactor register page to use shared refund modal

Description Martin Renvoize (ashimema) 2026-01-10 11:00:41 UTC
Add the ability to search and refund transactions from any register when using
anonymous_refund permission, allowing staff to process refunds at a different
register than where the original payment was made.

Currently, the POS register page (pos/register.pl) only displays transactions
for the current register. This creates a workflow problem when a patron:

1. Makes a payment at Register A
2. Returns to Register B with their receipt requesting a refund
3. Staff at Register B cannot find the original transaction on their register page
4. Staff must navigate to patron account page to locate and process the refund

This is particularly problematic for anonymous transactions where there may be
no patron account to search.

PROPOSED SOLUTION
-----------------
Add a new "Search transactions" or "Process refund" feature accessible from the
POS interface that allows staff with anonymous_refund permission to:

1. Search across all registers (or all registers in current branch) by:
   - Receipt ID / Credit number
   - Transaction ID / Accountline ID
   - Item barcode / Product code
   - Date range

2. Display transaction details including:
   - Original payment amount
   - Original register where payment was made
   - Payment type (CASH, card, etc.)
   - Items/services purchased
   - Whether transaction has already been refunded

3. Process refund at current register:
   - Clearly indicate: "Original payment at Register A, refund from Register B"
   - Create REFUND credit on the original transaction
   - Create PAYOUT debit at the CURRENT register (cash leaving current till)
   - Maintain proper accounting: Register A shows +£10, Register B shows -£10

BENEFITS
--------
- Improved customer service (refund at any register)
- Better support for anonymous transactions
- Maintains accurate per-register accounting
- Reduces staff confusion and navigation complexity
Comment 1 Martin Renvoize (ashimema) 2026-01-12 13:13:45 UTC
Created attachment 191252 [details] [review]
Bug 41592: Add API endpoints for global account line search

This patch adds new REST API endpoints to support cross-register refund
functionality by allowing global searches of account lines (credits and debits)
across all patrons.

Changes:
- Add /api/v1/account/credits endpoint for searching credits globally
- Add /api/v1/account/debits endpoint for searching debits globally
- Support embedding of related debits in credits endpoint
- Allow searching by credit_number or accountlines_id
- Fix Koha::Account::Line credits() and debits() methods to return
  proper Koha::Account::Credits and Koha::Account::Debits objects
- Make patron_id nullable in credit and debit API definitions to support
  anonymous transactions

The new endpoints require the 'anonymous_refund' cash_management permission
and enable staff to search for and refund transactions made at any register.

Test plan:
1. Apply patch and run: yarn build && ktd --restart_all
2. Test API endpoint: GET /api/v1/account/credits?q={"credit_number":"CR-123"}
3. Test with embed: GET /api/v1/account/credits?q={"credit_number":"CR-123"}
   with header x-koha-embed: debits
4. Verify results include credit details and embedded debit information
5. Bonus points: Run the new and updated unit tests:
   prove -v t/db_dependent/api/v1/account.t
   prove -v t/db_dependent/Koha/Account/Line.t
Comment 2 Martin Renvoize (ashimema) 2026-01-12 13:13:46 UTC
Created attachment 191253 [details] [review]
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
Comment 3 Martin Renvoize (ashimema) 2026-01-12 13:13:47 UTC
Created attachment 191254 [details] [review]
Bug 41592: Refactor patron account page to use shared refund modal

This patch refactors the patron account page (boraccount.tt) to use the
new shared refund modal component created in the previous commit, removing
duplicate code and providing a consistent refund experience across both
the Point of Sale and patron account pages.

Changes:
- Replace inline refund modal with shared refund_modal.inc component
- Update modal population JavaScript to work with new modal structure:
  * Parse amount and amountoutstanding as floats for proper formatting
  * Populate transaction details fields (description, original charge, amount paid)
  * Hide payment method and original register rows (not applicable in this context)
  * Update form field IDs to match new modal structure
- Fix focus to use #refund_amount instead of deprecated #returned field
- Update cash register validation to use #refund_registerid_select
- Add html_helpers.inc for proper formatting support

The shared modal intelligently adapts to different contexts, showing only
relevant information based on available data. For patron account refunds,
payment method and original register information are hidden since debits
can be paid by multiple credits with different payment methods.

Test plan:
1. Apply patch and rebuild: yarn build
2. Navigate to a patron's account page with paid charges
3. Click "Issue refund" on a debit that has been partially or fully paid
4. Verify refund modal displays:
   - Transaction description (properly formatted)
   - Original charge amount (properly formatted with currency)
   - Amount paid (properly formatted)
   - No payment method row (hidden in this context)
   - No original register row (hidden in this context)
5. Enter a refund amount and complete the refund
6. Verify the refund is processed successfully
7. Verify cash register validation works when UseCashRegisters is enabled
Comment 4 Martin Renvoize (ashimema) 2026-01-12 13:13:49 UTC
Created attachment 191255 [details] [review]
Bug 41592: Refactor register page to use shared refund modal

This patch refactors the register report page (register.tt) to use the
new shared refund modal component created in previous commits, removing
duplicate code and providing a consistent refund experience across both
the Point of Sale and register pages.

Changes:
- Replace inline refund modal with shared refund_modal.inc component
- Update modal population JavaScript to work with new modal structure:
  * Parse amount and amountoutstanding as floats for proper formatting
  * Populate transaction details fields (description, original charge, amount paid)
  * Hide payment method and original register rows (not applicable in this context)
  * Update form field IDs to match new modal structure
- Fix focus to use #refund_amount instead of deprecated #returned field
- Update cash register validation to use #refund_registerid_select
- Add html_helpers.inc for proper formatting support

The shared modal intelligently adapts to different contexts, showing only
relevant information based on available data. For patron account refunds,
payment method and original register information are hidden since debits
can be paid by multiple credits with different payment methods.

Test plan:
1. Apply patch and rebuild: yarn build
2. Navigate to a register page with some paid charges
3. Click "Issue refund" on a debit that has been paid
4. Verify refund modal displays:
   - Transaction description (properly formatted)
   - Original charge amount (properly formatted with currency)
   - Amount paid (properly formatted)
   - No original register row (hidden in this context)
5. Enter a refund amount and complete the refund
6. Verify the refund is processed successfully