| Summary: | Error 500 when attempting to pay overdue fines for returned items, with phantom credits accumulating on patron accounts | ||
|---|---|---|---|
| Product: | Koha | Reporter: | amanda <amanda> |
| Component: | Fines and fees | Assignee: | Bugs List <koha-bugs> |
| Status: | NEW --- | QA Contact: | Testopia <testopia> |
| Severity: | major | ||
| Priority: | P5 - low | CC: | dcook, didier.gautheron, fridolin.somers, jonathan.druart, katrin.fischer, laurent.ducos, lisette, lucas, m.de.rooy, martin.renvoize, nick, phil, tomascohen |
| Version: | 25.05 | ||
| Hardware: | All | ||
| OS: | Linux | ||
| GIT URL: | Initiative type: | --- | |
| Sponsorship status: | --- | Comma delimited list of Sponsors: | |
| Crowdfunding goal: | 0 | Crowdfunding committed: | 0 |
| Crowdfunding contact: | Patch complexity: | --- | |
| Documentation contact: | Documentation submission: | ||
| Text to go in the release notes: | Version(s) released in: | ||
| Circulation function: | |||
I think this probably doesn't need to be in Koha security, but could be public in the Koha product. Can you share which Koha version you are using please? Yes. Its 25.05.05. And I agree with you, its koha not security |
When a librarian attempts to pay an overdue fine for a returned item via the staff interface (Circulation > Accounting > Make a payment), the system throws an HTTP 500 error instead of processing the payment. Each failed payment attempt creates a PAYMENT credit entry in accountlines with a negative amountoutstanding (e.g. -1.50), without applying it to the debit. Over time, these phantom credits accumulate, displaying an unrealistic credit balance on the patron's account (e.g. 20.00, 50.00...) while the original fine remains outstanding. --- **Steps to reproduce:** 1. Create an overdue fine for a patron (item must be returned — status RETURNED in accountlines) 2. Go to the patron's account in the staff interface 3. Click "Make a payment", select the fine, click "Confirm" 4. Observe HTTP 500 error 5. Repeat step 3 — each attempt adds a new phantom PAYMENT credit --- **Expected behavior:** The fine should be marked as paid (amountoutstanding = 0, status = PAID) and a PAYMENT credit record should be created with amountoutstanding = 0. **Actual behavior:** HTTP 500 error is thrown. The fine remains pending. A PAYMENT credit is created with amountoutstanding < 0 (phantom credit). --- **Root cause:** When an item is returned, the loan record is moved from `issues` to `old_issues`. The corresponding fine in `accountlines` retains `old_issue_id` but has `issue_id = NULL`. During payment processing, `Koha/Account/Line.pm` (line ~692) calls: ``` $debit->item->itemlost(...) ``` This call chain goes through `C4/Circulation.pm` line 3176 (`CanBookBeRenewed`), which calls `$issue->item`. Since `issue_id` is NULL, `$issue` is undefined, causing the fatal error: ``` Can't call method "item" on an undefined value at /usr/share/koha/lib/C4/Circulation.pm line 3176 ``` The foreign key constraint on `accountlines.issue_id` only accepts IDs from the `issues` table (active loans), so it is not possible to populate `issue_id` with values from `old_issues` as a workaround. --- **Plack error log:** ``` {UNKNOWN}: Can't call method "item" on an undefined value at /usr/share/koha/lib/C4/Circulation.pm line 3176. at /usr/share/koha/lib/Koha/Account/Line.pm line 692 ``` --- **Workaround (database-level):** ```sql -- Zero out phantom credits UPDATE accountlines SET amountoutstanding = 0 WHERE borrowernumber = <borrowernumber> AND credit_type_code = 'PAYMENT' AND amountoutstanding < 0; -- Mark affected fines as paid UPDATE accountlines SET amountoutstanding = 0, status = 'PAID' WHERE borrowernumber = <borrowernumber> AND debit_type_code = 'OVERDUE' AND amountoutstanding > 0 AND issue_id IS NULL AND old_issue_id IS NOT NULL; ``` --- **Affected patrons query:** ```sql SELECT DISTINCT a.borrowernumber, p.surname, p.firstname, COUNT(*) AS pending_fines, SUM(a.amountoutstanding) AS total_outstanding FROM accountlines a JOIN borrowers p ON a.borrowernumber = p.borrowernumber WHERE a.debit_type_code = 'OVERDUE' AND a.amountoutstanding > 0 AND a.issue_id IS NULL AND a.old_issue_id IS NOT NULL GROUP BY a.borrowernumber, p.surname, p.firstname ORDER BY total_outstanding DESC; ``` --- **Severity:** Major — prevents librarians from processing fine payments for any returned item. **Component:** Circulation / Accounting