commit 83056319a1b6522c1849771af5140470ade4a4ed Author: Hammat Wele Date: Tue Oct 6 15:23:06 2026 +0000 Bug 41419 - Error javascript undefined is not valid JSON when showing the order edit modal in orderreceive.tt To reproduce 1. Go to Acquisitions → New vendor, enter a name, check Vendor is Active, and click Save. 2. Click the name of the vendor you just created in the table. 3. Click New → Basket, enter a basket name, and click Save. 4. Click Add to basket → From a new (empty) record. Enter a title, add an item, and click Save. 5. Click Close basket. 6. Click Receive shipments, enter a number in the Vendor invoice field, and click Next. 7. Click Receive for the created line. 8. Open the browser console. 9. In the modal, tick the Receive? checkbox for all the lines, then click Confirm 10. Go to the previous page → The console shows the error: undefined is not valid JSON at JSON.parse was shown 11. Apply the patch 12. Go to Acquisitions → Search for the vandor created on step 2 13. Repeat step 3, 4, 5, 6, 7, 8, 9, 10 → There is no js error commit 4a34be3fcbc32bd4c511780c5b436c21a128f2e1 Author: Lisette Scheer Date: Wed Sep 9 16:51:20 2026 +0000 Bug 43378: Add check for overwriting $highest when a group hold matches To test, before applying patches: 1. Create a library group 2. Add 2 libraries to it 3. Make 2 patrons at the same library in the group 4. Create an item at the other group branch 5. Place 2 bib level holds for the patrons from step 3 6. Check in the item 7. Observe the hold tries to fill the last hold. Don't confirm the hold 8. Apply the patches 9. Repeat step 6 10. Observe the hold now tries to fill the first hold. 11. prove t/db_dependent/Holds/LocalHoldsPriority.t All tests should pass! Signed-off-by: Stephanie Baker Signed-off-by: Andrew Fuerste Henry Signed-off-by: Jacob O'Mara Signed-off-by: Pedro Amorim commit 8aed3ecb45395e8748a043e74788721396e5a1b7 Author: Martin Renvoize Date: Wed Sep 23 12:06:45 2026 +0100 Bug 43604: Fix Koha::Item->safe_to_delete crash when userenv has no matching patron Koha::Item->safe_to_delete assumed C4::Context->userenv->{number} always corresponds to a real Koha::Patron. misc/commit_file.pl sets a dummy CLI userenv with borrowernumber 0, so running --revert on a batch calls safe_delete/safe_to_delete on each item and dies: Can't call method "can_edit_items_from" on an undefined value at Koha/Item.pm line 374. Guard against Koha::Patrons->find returning undef, matching the pattern already used elsewhere (e.g. Koha::UI::Table::Builder::Items->build_table). Test plan: 1. Run misc/commit_file.pl --batch-number NNN --revert on a committed import batch -- it dies as above. 2. Apply this patch. 3. prove t/db_dependent/Koha/Item.t -- all tests pass. 4. Repeat step 1 -- the batch reverts successfully. Co-Authored-By: Claude Sonnet 5 Signed-off-by: David Nind Signed-off-by: Andrew Fuerste Henry Signed-off-by: Pedro Amorim commit a8aa8da20b4d31e198e6051681f6a26734a5469f Author: Fridolin Somers Date: Tue Sep 29 12:15:08 2026 +0200 Bug 43595: Missing space between title and author in items search Since tidy the items search is missing space between title and author. Many places use : [% title | html %] [% IF author %]by [% END %][% biblio.author | html %] Lets do the same. Test plan : 1) Use a UNIMARC catalog 2) Without patch 3) Go to items search and perform a search 4) You see missing space : titleby author 5) Apply patch 6) Go to items search and perform a search 7) You see correct display : title by author Signed-off-by: David Liddle Signed-off-by: Andrew Fuerste Henry Signed-off-by: Pedro Amorim commit df242a1b963039a221fe9d8ac3fa408501f5f971 Author: Lucas Gass Date: Wed Sep 9 19:45:12 2026 +0000 Bug 43314: Add hint about cronjob to the AnonymizeLastBorrower system preference To test: 1. APPLY PATCH and restart_all 2. Look at the AnonymizeLastBorrower system pref. 3. The hint should read: NOTE: This system preference requires the misc/cronjobs/anonymize_last_borrowers.pl cronjob. Ask your system administrator to schedule it. 4. Confirm that makes sense. Signed-off-by: David Nind Signed-off-by: Andrew Fuerste Henry Signed-off-by: Pedro Amorim commit 350fae1f3f48e56cc3e3b5367c6e0e17c89f54fd Author: Pedro Amorim Date: Fri Oct 16 11:09:43 2026 +0000 Bug 42503: DBRev 26.06.00.036 Signed-off-by: Pedro Amorim commit d831f76a7a199874167847c2791992dbe7fdb1a0 Author: Pedro Amorim Date: Fri Oct 16 10:59:30 2026 +0000 Bug 42503: (RM follow-up) Make tmp_holdsqueue upgrade match kohastructure The atomic update added the new tmp_holdsqueue columns without the COMMENTs from kohastructure.sql, and added itype_description after local_holdgroup_match, which pushed item_type_id to the end of the table. Upgraded and fresh installs ended up with different tables. This patch adds the COMMENTs and adds itype_description after item_type_id, so both match. Co-Authored-By: Claude Opus 5.5 Signed-off-by: Pedro Amorim commit 71fe969991e9077ba7a68a48e3e3598ab1d74e35 Author: Pedro Amorim Date: Fri Oct 16 09:56:12 2026 +0000 Bug 42503: (RM follow-up) Fix patron column when HidePatronName is enabled With HidePatronName enabled the API returns the patron with only patron_id, cardnumber and category, so $patron_to_html finds no name and renders "A patron from another library" for everyone. This patch: - renders the cardnumber, linked to the patron's holds, when the patron is accessible (patron_id present) but has no name - keeps library_id and the library embed in the HidePatronName redaction, so restricted staff get "A patron from " as before the conversion - updates the HidePatronName API tests to expect library_id and the library embed Test plan: Setup: 1) Apply all patches for this bug except this one, then: updatedatabase && yarn build && restart_all 2) Populate the queue: perl t/lib/sample_holds_queue.pl A) The regression (as koha, superlibrarian): 1) In 'Administration' > 'System preferences', set 'HidePatronName' to 'Don't show' and save. 2) Open http://localhost:8081/cgi-bin/koha/circ/holds_queue.pl?run_report=1 3) Notice every 'Patron' cell says "A patron from another library", with no link. 4) Apply this patch, run restart_all and reload the page. 5) The 'Patron' column now shows the patron's cardnumber as a link, phone and email still hidden. 6) Click a cardnumber, it should land on http://localhost:8081/cgi-bin/koha/circ/circulation.pl?borrowernumber=X#reserves B) No regressions (with this patch applied): 1) Set 'HidePatronName' to 'Show'. Reload the queue, patrons show as "Surname, Preferredname (cardnumber)" plus phone/email. 2) Create a staff user at Centerville with only 'catalogue' and 'circulate_remaining_permissions' (no superlibrarian, no 'view_borrower_infos_from_any_libraries'), set a username/password. 3) Make sure the queue has a hold for a Centerville patron and one for a patron from another library (edit a patron's home library if needed). 4) Log in as that user and open the queue. 5) Centerville patron: "Surname, Preferredname (cardnumber)". Other library patron: "A patron from ". 6) As koha, set 'HidePatronName' to 'Don't show', log back in as the restricted user and reload. 7) Centerville patron: cardnumber link only. Other library patron: "A patron from " (before this patch: "A patron from another library"). prove t/db_dependent/api/v1/holds_queue.t Co-Authored-By: Claude Opus 5.5 Signed-off-by: Pedro Amorim commit 34f16549f097207b0aa537ba45c14bd51d49792f Author: Tomás Cohen Arazi Date: Thu Oct 15 10:33:55 2026 -0300 Bug 42503: (follow-up) Make local_holdgroup_match exclusivity test deterministic The "local_holdgroup_match uses exclusivity controls, not LocalHoldsPriority controls" subtest built its patron with TestBuilder's default (random) category. When that category happened to have exclude_from_local_holds_priority = 1, the LocalHoldsPriority phase in C4::HoldsQueue::MapItemsToHoldRequests was skipped for the patron: next if $patron->category->exclude_from_local_holds_priority; With LHP skipped, item targeting fell through to the generic allocation path, which iterates branches/items in Perl hash order and is therefore not deterministic. In that case the queue could target item_b instead of the expected item_c, failing the assertion roughly 25-30% of the time. Build the patron with a category that explicitly has exclude_from_local_holds_priority = 0 so the LocalHoldsPriority path the subtest is meant to exercise always runs. Note: this only fixes the test. The underlying generic-allocation ordering in MapItemsToHoldRequests is itself non-deterministic (it iterates keys %items_by_branch and does not honor StaticHoldsQueueWeight, as the existing FIXME in that sub notes). That is a pre-existing issue with a wider blast radius and should be addressed separately. Test plan: 1. Run the test in a loop, e.g.: for i in $(seq 1 50); do prove t/db_dependent/HoldsQueue.t || break; done 2. Without this patch it fails intermittently (~25-30% of runs) on "LHP correctly targeted the lib_c item". 3. With this patch it passes consistently (50/50). Signed-off-by: Pedro Amorim commit 14eb206dbcd39849c0b95e58a789f6f9719aaef1 Author: Tomás Cohen Arazi Date: Wed Oct 14 16:16:29 2026 -0300 Bug 42503: Show translated item type in the holds queue The holds queue "Item type" column displayed a single materialized, default-language description. This makes it show the translated item type description according to the staff interface language. To do this the item type *code* is now materialized on tmp_holdsqueue (new `itype` column, FK to itemtypes), and the translated description is resolved through the standard `+strings` API embed convention: - tmp_holdsqueue gains an `itype` column (varchar(10)) with a foreign key to itemtypes(itemtype) (ON DELETE SET NULL / ON UPDATE CASCADE), added via kohastructure.sql and an idempotent atomic update. - Koha::Hold::HoldsQueueItem gains a strings_map() implementing the `+strings` convention for `itype` (translated_description), and maps itype => item_type_id in to_api_mapping. - The holds_queue_item API definition exposes item_type_id and _strings. - C4::HoldsQueue populates `itype` when building the queue and filters on the joined item.itype/item.ccode/item.location columns. - The holds queue template renders the column from _strings.item_type_id.str, falling back to the materialized itype_description. Sorting/filtering is on the item type code; sorting by translated description is left as a follow-up. Test plan: 1. Apply the atomic update (updatedatabase.pl). 2. Translate an item type description (Administration > Item types > "Translate into other languages") into a non-default language. 3. Rebuild the holds queue (build_holds_queue.pl) with items of that type. 4. View the holds queue in the staff interface in that language. => The Item type column shows the translated description. 5. prove t/db_dependent/HoldsQueue.t t/db_dependent/api/v1/holds_queue.t Signed-off-by: Pedro Amorim commit 28d06dc704df8a98bbfa9ea7b682e018aff66caf Author: Tomás Cohen Arazi Date: Fri Oct 9 22:26:21 2026 +0200 Bug 42503: (follow-up) Use $patron_to_html for the holds queue patron column The holds queue patron column formatted the name by hand, which caused two regressions compared to the original view_holdsqueue.tt (which used patron-title.inc): - the preferred name and card number were ignored: the column showed 'Surname, Firstname' instead of 'Surname, Preferredname (cardnumber)' - patrons the logged-in staff member may not see (no 'view_borrower_infos_from_any_libraries' for the patron's library) showed an empty cell instead of 'A patron from ' Both are display-only problems. The API already does the right thing: identifying fields are redacted by Koha::Patron->is_accessible during to_api serialisation, while library_id/library are preserved. Rather than reimplement the formatting rules, this patch reuses the shared window.$patron_to_html helper (koha-tmpl/.../js/patron-format.js, already loaded globally via js_includes.inc and used by holds.js, ill-list-table.js, etc.). It handles the preferred name, middle/other names, the card number, the 'A patron from ' fallback for redacted patrons, and the circulation link, all consistently with the rest of the staff interface. Changes: - koha-tmpl/.../circ/holds_queue.tt: - render the patron column with $patron_to_html(row.patron, { invert_name, display_cardnumber, url: 'circulation_reserves' }) - keep the 'patron.library' embed (used by the helper's fallback) - t/db_dependent/api/v1/holds_queue.t: add a 'patron visibility across libraries' subtest verifying identifying fields are redacted for a restricted cross-library staff user while library_id is preserved, and that a superlibrarian still sees the full patron Test plan: 1. Apply all patches for this bug 2. Run the API tests: $ ktd --shell k$ prove t/db_dependent/api/v1/holds_queue.t => SUCCESS: all tests pass, including the new cross-library subtest 3. Edit a patron in the queue: set 'Preferred name' different from 'First name' 4. Reload Circulation > Holds queue => FAIL (before this patch): patron shows 'Surname, Firstname', no card number 5. Create a staff user at library A with only 'catalogue' and 'circulate_remaining_permissions' (no 'view_borrower_infos_from_any_libraries'); make sure the queue has a hold for a patron from library B 6. Log in as that staff user and open the holds queue => FAIL (before this patch): the patron cell is empty 7. Apply this patch and rebuild assets: k$ yarn build 8. Repeat steps 4 and 6 => SUCCESS: the patron shows 'Surname, Preferredname (cardnumber)', and the cross-library patron shows 'A patron from ' 9. Sign off :-D Assisted-by: Sonnet 4.6 (Anthropic) Signed-off-by: Pedro Amorim commit 48bd07327c401869656900543d79cd223a5821bf Author: Tomás Cohen Arazi Date: Fri Oct 9 21:28:47 2026 +0200 Bug 42503: (follow-up) Restore publication details fields in holds queue The API-driven holds queue 'Publication details' column regressed when the page was converted to use DataTables. The original view_holdsqueue.tt rendered edition statement, publisher, publication year (falling back to the copyright date), pages, material size and ISBN. The new render function only emitted edition statement, publisher and publication year. This patch restores the missing fields in the column render function. All the required data is already available on the embedded biblio object, so no API or embed change is needed. Changes: - koha-tmpl/.../circ/holds_queue.tt: rebuild the publication details string to include: - copyright_date fallback when publication_year is empty - pages (prefixed with ': ') - material_size - isbn (as 'ISBN: ...') - No functional change to the API or the embed set Test plan: 1. Apply all patches for this bug 2. Make sure a biblio in the queue has pages, size, ISBN and a copyright date but no publication year 3. Populate the holds queue: $ ktd --shell k$ perl t/lib/sample_holds_queue.pl 4. Navigate to Circulation > Holds queue and look at the 'Publication details' column => FAIL: Only edition, publisher and year are shown. Pages, size, ISBN and the copyright date fallback are missing 5. Apply this patch 6. Rebuild assets and reload the page: k$ yarn build 7. Repeat step 4 => SUCCESS: Pages, size, ISBN and the copyright date fallback are shown again, matching the previous behaviour 8. Sign off :-D Assisted-by: Sonnet 4.6 (Anthropic) Signed-off-by: Pedro Amorim commit dcc7139d74afee3fd4b78f06d0495e8d4831dfbe Author: Jan Kissig Date: Fri Jul 31 09:33:13 2026 +0000 Bug 42503: (follow-up) Fix broken links in circ-nav.inc The page view_holdsqueue got renamed to holds_queue. This patch changes last remaining occurences of view_holdsqueue.pl to holds_queue.pl in 'circ-nav.inc'. Signed-off-by: Paul Derscheid Signed-off-by: Pedro Amorim commit a7b6b330c646d60b27e4496ad37d8fdb1cd46aba Author: Tomás Cohen Arazi Date: Wed Jul 29 17:15:59 2026 -0300 Bug 42503: (follow-up) Drop is_superlibrarian from HidePatronName check When HidePatronName is enabled, the patron embed should be redacted for all users regardless of superlibrarian status. The API contract is consistent: the syspref controls visibility universally. Adds test coverage for HidePatronName behavior including verification that superlibrarians are also subject to the redaction. Signed-off-by: Paul Derscheid Signed-off-by: Pedro Amorim commit 9f1f96edc22f38e85ee2d0bb5902a322e45b5826 Author: Tomás Cohen Arazi Date: Sat Jun 13 10:07:28 2026 -0300 Bug 42503: (follow-up) Rename view_holdsqueue to holds_queue Renames circ/view_holdsqueue.{pl,tt} to circ/holds_queue.{pl,tt} for consistency with other circ pages. Updates all references: circulation-home, Koha::Manual, columns_settings.yml, and the template's internal links. Includes a DB migration to rename saved column preferences from view_holdsqueue to holds_queue. Signed-off-by: David Nind Signed-off-by: Paul Derscheid Signed-off-by: Pedro Amorim commit c980c3bbf5875daef5b5ba84a1becf451ac2fd6a Author: Tomás Cohen Arazi Date: Sun May 10 17:13:31 2026 -0300 Bug 42503: Convert holds queue page to API-driven The holds queue page (circ/view_holdsqueue.pl) loaded all queue items at once server-side via GetHoldsQueueItems(), which is slow for large systems. This patch converts it to use the new /api/v1/holds/queue endpoint with DataTables for paginated, server-side rendering. Changes: - circ/view_holdsqueue.pl: stripped to auth + template params only. Data loading removed entirely. - view_holdsqueue.tt: replaced server-rendered FOREACH loop with a kohaTable instance calling /api/v1/holds/queue with dynamic filters via additional_filters (ILL table pattern). The sidebar form redraws the table without page reload on subsequent filter changes. - Embeds used: biblio, item+strings, patron, patron.notice_email_address - Removed publication details column (available via biblio embed if needed in the future) and patron category column (simplification, can be re-added as patron+strings embed) Test plan: 1. Apply patches (including the [DO NOT PUSH] sample data commit) 2. Populate the holds queue: $ ktd --shell k$ cd /kohadevbox/koha && perl t/lib/sample_holds_queue.pl => SUCCESS: 40 items populated 3. Navigate to Circulation > Holds queue 4. Submit the filter form (select a library or leave as All) => SUCCESS: Table loads data via API with pagination 5. Verify columns: title, author, current library, home library, collection, location, item type, call number, copy number, enumeration, barcode, patron (name+phone+email), send to, date, notes 6. Change library filter in sidebar, click Submit => SUCCESS: Table redraws without page reload 7. Click Clear button => SUCCESS: All filters reset, full results shown 8. Verify sorting works on columns 9. Sign off :-D Assisted-by: Sonnet 4.6 (Anthropic) Signed-off-by: David Nind Signed-off-by: Paul Derscheid Signed-off-by: Pedro Amorim commit cc26ca38f2cb6ffd2f508b1b0c3a3c262a74c1d6 Author: Tomás Cohen Arazi Date: Fri Jun 12 16:22:31 2026 -0300 Bug 42503: Add holds queue REST API endpoint Adds GET /api/v1/holds/queue backed by Koha::Hold::HoldsQueueItems (the tmp_holdsqueue table populated by the holds queue cron job). Changes: - Koha/REST/V1/Holds/Queue.pm: controller with list method - Koha/Hold/HoldsQueueItem.pm: Koha::Object class with relationship accessors (patron, biblio, item), strings_map, and to_api_mapping - Koha/Hold/HoldsQueueItems.pm: Koha::Objects class with api_query_fixer for biblioitem field resolution - Koha/Schema/Result/TmpHoldsqueue.pm: adds 'patron' relationship alias so patron.* fields are embeddable and sortable - api/v1/swagger/paths/holds_queue.yaml: path spec - api/v1/swagger/definitions/holds_queue_item.yaml: definition Supports filtering by holding_library_id, pickup_library_id, biblio_id, item_id, patron_id, item_level. Embeds: +strings, biblio, item, item+strings, patron, patron+strings, patron.notice_email_address. Sorting on embedded fields (patron.surname, patron.category_id, biblio.publisher) works correctly. Respects HidePatronName system preference. Test plan: 1. Apply all patches for this bug 2. Run: $ ktd --shell k$ yarn build ; koha-plack --restart kohadev k$ prove t/db_dependent/api/v1/holds_queue.t => SUCCESS: Tests pass! 3. Populate the holds queue: k$ perl misc/cronjobs/holds/build_holdsqueue.pl 4. Verify the endpoint: GET /api/v1/holds/queue x-koha-embed: biblio,item+strings,patron+strings => SUCCESS: Returns holds queue items with embedded data 5. Sign off :-D Signed-off-by: Tomas Cohen Arazi Signed-off-by: David Nind Signed-off-by: Paul Derscheid Signed-off-by: Pedro Amorim commit 9050394d9a5f777f40026a7d88ab253ce02817b7 Author: Tomás Cohen Arazi Date: Fri Jul 31 16:31:43 2026 -0300 Bug 42503: DBIC schema changes Signed-off-by: Tomás Cohen Arazi Signed-off-by: Paul Derscheid Signed-off-by: Pedro Amorim commit ecaf9b85873feb7ebd65cebafce6fd319a96b765 Author: Tomás Cohen Arazi Date: Fri Jun 12 15:59:36 2026 -0300 Bug 42503: Populate materialized descriptions during queue build The tmp_holdsqueue table already materializes patron (surname, firstname, phone, cardnumber) and biblio (title) fields to avoid joins at read time. This extends the pattern to item type, collection code, and shelving location descriptions. C4::HoldsQueue::AddToHoldsQueue now resolves and stores: - itype_description: from effective item type (Koha::ItemType->description) - ccode_description: from authorised values (CCODE category) - location_description: from authorised values (LOC category) This enables server-side sorting by human-readable descriptions rather than internal codes. Test plan: $ ktd --shell k$ prove t/db_dependent/HoldsQueue/materialized_descriptions.t => SUCCESS: All tests pass Signed-off-by: David Nind Signed-off-by: Paul Derscheid Signed-off-by: Pedro Amorim commit 2d0c6f227e6144590d468fb8c4dc1afad1fdaa97 Author: Tomás Cohen Arazi Date: Wed Jul 29 19:26:14 2026 +0000 Bug 42503: DBIC schema RM amended: Removed [DO NOT PUSH] from commit message. Pushing as is. Signed-off-by: Tomas Cohen Arazi Signed-off-by: Paul Derscheid Signed-off-by: Pedro Amorim commit 46ad63f376fb679d918dcf95c81eee76069bd740 Author: Tomás Cohen Arazi Date: Fri Jun 12 15:59:28 2026 -0300 Bug 42503: Add materialized description columns to tmp_holdsqueue Add itype_description, ccode_description, and location_description columns to the tmp_holdsqueue table. These store the human-readable descriptions at queue-build time, enabling server-side sorting by description rather than code. Signed-off-by: David Nind Signed-off-by: Paul Derscheid Signed-off-by: Pedro Amorim commit f7f4ff75e20a289333713a1e2b4fc3f73a3e0a73 Author: Martin Renvoize Date: Wed Oct 14 08:09:53 2026 +0100 Bug 31925: (follow-up) Ignore false positive in codespell The 008 fixed-field value used in Biblio.t contains 'acn', which codespell flags as a misspelling of 'can' or 'acne'. Mark the line so codespell ignores it. Signed-off-by: Pedro Amorim commit f0dbdc012d1576f569319153625716cb3c3e0836 Author: Tomás Cohen Arazi Date: Wed Aug 5 12:42:05 2026 -0300 Bug 43234: (follow-up) Refactor cleanup_database.pl to use filter_by_purgeable This patch refactors the ILL request purging in cleanup_database.pl to use the new Koha::ILL::Requests->filter_by_purgeable method instead of the raw SQL PurgeIllRequests subroutine. Changes: - Replace PurgeIllRequests() call with filter_by_purgeable + count/delete - Remove the PurgeIllRequests subroutine (raw SQL) - Add use Koha::ILL::Requests import The behavior is identical: dry-run reports count, --confirm deletes. Test plan: 1. Apply patch 2. Run: $ ktd --shell k$ perl misc/cronjobs/cleanup_database.pl --illrequests-days 30 \ --illrequests-status COMP -v => SUCCESS: Reports count of requests that would be removed 3. Run with --confirm: k$ perl misc/cronjobs/cleanup_database.pl --illrequests-days 30 \ --illrequests-status COMP --confirm -v => SUCCESS: Requests are deleted, count reported 4. Run unit tests: k$ prove t/db_dependent/Koha/ILL/Requests.t => SUCCESS: Tests pass 5. Sign off :-D Signed-off-by: David Nind Signed-off-by: Lisette Scheer Signed-off-by: Pedro Amorim commit b7a08c46c119a9e177e53443723f9bd4f7f682bb Author: Tomás Cohen Arazi Date: Wed Aug 5 12:39:31 2026 -0300 Bug 43234: Add Koha::ILL::Requests->filter_by_purgeable This patch adds a filter_by_purgeable() method to Koha::ILL::Requests that returns a filtered resultset of ILL requests eligible for purging based on status and age. The method accepts: - days: number of days since last update - statuses: arrayref of status codes to match The caller can use ->count for reporting or ->delete to purge, following the standard Koha::Objects pattern. Test plan: 1. Apply patch 2. Run: $ ktd --shell k$ prove t/db_dependent/Koha/ILL/Requests.t => SUCCESS: Tests pass 3. Tests cover: - Single status filter (COMP older than 30 days) - Multiple statuses (COMP + KILL) - Recent requests excluded by age threshold - Throws MissingParameter without days - Throws MissingParameter without statuses - Throws MissingParameter with empty statuses array 4. Sign off :-D Signed-off-by: David Nind Signed-off-by: Lisette Scheer Signed-off-by: Pedro Amorim commit c316606d579034e9fae3c438341057e3a776debd Author: Paul Derscheid Date: Fri Oct 9 12:35:31 2026 +0200 Bug 43523: (QA follow-up) Document exclude_nonzero_balance Assisted-by: Opus 5.5 (Anthropic) Signed-off-by: Paul Derscheid Signed-off-by: Pedro Amorim commit 239b0b161cadedf7405b44f32a715517f7ca571d Author: Paul Derscheid Date: Fri Oct 9 12:31:24 2026 +0200 Bug 43523: (QA follow-up) Throttle Finish button - Use throttled-button.inc for the step 2 Finish button - Keep it disabled for 10 minutes: the deletion can run for minutes and a second submit races the first (Can't call method "delete" on an undefined value at tools/cleanborrowers.pl) Test plan: 1. Make sure there are many deletable patrons (thousands) 2. Tools > Batch patron deletion and anonymization, tick "Verify you want to delete patrons", set "who have not borrowed since" to today, click Next 3. Choose "Move these patrons to the trash", click Finish 4. The button shows a spinner and stays disabled until the result page loads 5. Repeat with "Permanently delete these patrons" and cancel the confirmation dialog: the button stays enabled Assisted-by: Opus 5.5 (Anthropic) Signed-off-by: Paul Derscheid Signed-off-by: Pedro Amorim commit 6158017096a185df1bcc61b1be9a11d355781c8f Author: Lucas Gass Date: Fri Sep 11 16:07:36 2026 +0000 Bug 43523: Add a new test Signed-off-by: Michelle Spinney Signed-off-by: Paul Derscheid Signed-off-by: Pedro Amorim commit 7d5462cb61ddce20d3695b4015f9de1f6756eb37 Author: Lucas Gass Date: Fri Sep 11 13:56:36 2026 +0000 Bug 43523: Replace _skip_borrowers_with_nonzero_balance with SQL To test: 0. Add many borrowers to your system, I was testing with 10,000 each time 1. First without the patch, go to Tools -> Batch patron deletion and anonymization 2. Check 'Verify you want to delete patrons' and choose paramaters that will return most, or all, of the 10k borrowers. I used "who have not borrowed since: 1900/01/01" 3. Click Next >> and confirm. 4. The script should now take significant time to load. For me, with 10k borrowers, it was 3minutes 45 seconds 5. APPLY PATCH, restart_all 6. Load 10,000 new borrowers since the old ones are now deleted. 7. Try the steps again. 8. Load times should be better. For me, it now loads in under 1 second. 9. prove -v t/db_dependent/Members.t NOTE: This doesn't effect the actual deletion which still takes quite a long time. I have filed Bug 43525. as the deletion should be moved to a background job. Signed-off-by: Michelle Spinney Signed-off-by: Paul Derscheid Signed-off-by: Pedro Amorim commit 28ed446a383f915f17401588dfe21b738967d4b1 Author: Kyle M Hall Date: Tue Oct 6 12:26:42 2026 -0400 Bug 43460: (QA follow-up) Resolve the swagger spec once when converting XML payloads parse_xml resolved the whole spec on every call. The definitions are now cached per process. Signed-off-by: Kyle M Hall Signed-off-by: Mike Grgurev Signed-off-by: Pedro Amorim commit 3c76df5f7a9cab611fdfbd7f74e12dae07e6fb57 Author: Kyle M Hall Date: Tue Oct 6 10:47:01 2026 -0400 Bug 43460: (QA follow-up) Leave a response body that isn't JSON alone The response hook decoded every application/xml body as JSON and died on anything else. A body that doesn't decode as JSON is now left alone, so plugins that build their own XML response no longer get a 500. Signed-off-by: Kyle M Hall Signed-off-by: Mike Grgurev Signed-off-by: Pedro Amorim commit e622432130ba36503deb9eafad317f5f663f1884 Author: Kyle M Hall Date: Tue Oct 6 10:46:54 2026 -0400 Bug 43460: (QA follow-up) Add tests for XML response bodies A route that renders its own XML the way TwiML is rendered gets a 500 from the response hook until the next patch. The xml_response fixture route shows it. Signed-off-by: Kyle M Hall Signed-off-by: Mike Grgurev Signed-off-by: Pedro Amorim commit a6726ebddfa430e984f0d22df17a470c723b9bc4 Author: Kyle M Hall Date: Thu Sep 17 09:25:10 2026 -0400 Bug 43460: (QA follow-up) Remove validate_json_payload * Removes validate_json_payload, it validated against the whole swagger document so it accepted every payload, its 400 was unreachable from inside Try::Tiny, and it re-bundled the spec on every call * Narrows the try to the XML parse, a body that isn't XML is still left for valid_input to reject while a failure in parse_xml is no longer hidden Signed-off-by: Kyle M Hall Signed-off-by: Mike Grgurev Signed-off-by: Pedro Amorim commit 1178a6b514fa3f40ba901c809c14c21b8edb1f40 Author: Kyle M Hall Date: Thu Sep 17 09:23:44 2026 -0400 Bug 43460: (QA follow-up) Only convert the XML payload for the endpoint action around_action also fires for the Auth#under bridge, so the conversion ran twice and the second pass always died parsing the now-JSON body. The $last guard limits it to the endpoint action. Signed-off-by: Kyle M Hall Signed-off-by: Mike Grgurev Signed-off-by: Pedro Amorim commit 80e4667087e68c02395cfb556e7ae06d651bdb63 Author: Kyle M Hall Date: Thu Sep 17 09:28:42 2026 -0400 Bug 43460: (QA follow-up) Add unit tests * Covers the XML payload conversion hook * Adds the echo_authorized fixture route, the last case needs a plugin route that requires a permission and fails until the next patch Signed-off-by: Kyle M Hall Signed-off-by: Mike Grgurev Signed-off-by: Pedro Amorim commit 654dab7d1e92895758e20e4154f9f2682eda1907 Author: Kyle M Hall Date: Thu Sep 10 12:41:28 2026 -0400 Bug 43460: (QA follow-up) Document x-koha-raw-payload in PluginRoutes POD The flag only existed in the POD of a private sub in Koha::REST::V1, which is not where a plugin author looks. Signed-off-by: Kyle M Hall Signed-off-by: Mike Grgurev Signed-off-by: Pedro Amorim commit 74c024b434754fb67790e6a6baabcc8b9cd1904c Author: Kyle M Hall Date: Thu Sep 10 12:41:19 2026 -0400 Bug 43460: (QA follow-up) Tidy the x-koha-raw-payload tests and fixture * Declares the raw body as a string and calls valid_input in the fixture controllers, an object body returns a 400 for raw XML * Drops the /public/ prefix from the fixture routes, the namespace is the segment after /api/v1/ so it had no effect * Installs only the Test plugin in V1.t, which removes the copied BadAPIRoute warnings and the logger mock * Asserts the response content type, the response hook only skips application/xml Signed-off-by: Kyle M Hall Signed-off-by: Mike Grgurev Signed-off-by: Pedro Amorim commit 07e121f4483fd4317cbc3c5246813574f565cf30 Author: Kyle M Hall Date: Thu Sep 10 12:41:16 2026 -0400 Bug 43460: (QA follow-up) Remove dead openapi.op_spec fallback * Removes the 'openapi.op_spec' fallback from the new helper and from Koha::REST::V1::Auth, it only existed in Mojolicious::Plugin::OpenAPI before 1.17 and Koha requires 5.09 * Replaces "XML<->JSON" in the helper POD, Pod renders it as "XM-JSON" Signed-off-by: Kyle M Hall Signed-off-by: Mike Grgurev Signed-off-by: Pedro Amorim commit 2099efee3e2586bb8b15c9fe63be53ba3f205677 Author: Pedro Amorim Date: Fri Sep 4 10:03:29 2026 +0000 Bug 43460: Add tests for x-koha-raw-payload Adds echo_raw and echo_converted routes to the Test plugin fixture, one setting x-koha-raw-payload and one without it, both echoing the request body. Posts a real NCIP-style XML message with attributes through Koha::REST::V1 via Test::Mojo: the flagged route returns the exact bytes sent, while the default route shows the controller only sees the body after attributes were silently dropped by the XML<->JSON conversion. prove t/db_dependent/Koha/REST/V1.t Co-Authored-By: Claude Sonnet 5 Signed-off-by: Kyle M Hall Signed-off-by: Mike Grgurev Signed-off-by: Pedro Amorim commit f5997a11fd86eabbbf52202eebc1552a2b7987ef Author: Pedro Amorim Date: Fri Sep 4 10:02:08 2026 +0000 Bug 43460: Let API routes skip the global XML<->JSON conversion hooks Koha::REST::V1's hooks convert every route's XML body to JSON and back, breaking plugin routes with native XML protocols like NCIP. A route can now set x-koha-raw-payload in its OpenAPI spec to opt out of both conversions. Co-Authored-By: Claude Sonnet 5 Signed-off-by: Kyle M Hall Signed-off-by: Mike Grgurev Signed-off-by: Pedro Amorim commit d866ecb0f9df9db1fcb71ff37800c0af8f0dc53e Author: Brendan Lawlor Date: Fri Oct 9 18:03:06 2026 +0000 Bug 39419: (QA follow-up) Set patron_expiration_date for Club and ILS-DI holds AddReserve's expiration_date parameter used to set patron_expiration_date. It now sets expirationdate, and patron_expiration_date has its own parameter. Club holds and the ILS-DI HoldTitle and HoldItem services still passed the requester's date only as expiration_date, so patron_expiration_date was no longer set for these holds. set_waiting and revert_found both rely on it. This patch passes the date to both parameters, like the OPAC and staff interface do. Test plan: 1. In KTD, enable the ILS-DI system preference and set ILS-DI:AuthorizedIPs to 0.0.0.0/0 2. Place a hold through ILS-DI: curl -s 'http://localhost:8080/cgi-bin/koha/ilsdi.pl?service=HoldTitle&patron_id=42&bib_id=1&request_location=127.0.0.1&expiry_date=2099-01-01' 3. Check the new hold: SELECT reserve_id, expirationdate, patron_expiration_date FROM reserves ORDER BY reserve_id DESC LIMIT 1 4. Note expirationdate is 2099-01-01 but patron_expiration_date is NULL 5. Cancel that hold 6. Apply patch and restart_all 7. Repeat steps 2 and 3 8. Note both expirationdate and patron_expiration_date are 2099-01-01 9. Run: prove t/db_dependent/ILSDI_Services.t t/db_dependent/Koha/Club/Hold.t t/db_dependent/api/v1/clubs_holds.t Sponsored-by: CLAMS Co-Authored-By: Claude Opus 5.5 Signed-off-by: Brendan Lawlor Signed-off-by: Pedro Amorim commit 0fd2552504b8d6914b7963e02e6baaf0300d19ed Author: Brendan Lawlor Date: Fri Oct 9 16:53:28 2026 +0000 Bug 39419: (QA follow-up) Update tests for change to AddReserve The subtest 'set_waiting+patron_expiration_date' passed expiration_date and expected patron_expiration_date to be set. It is now empty, so tests fail. This patch changes expiration_date to patron_expiration_date in both AddReserve calls in that subtest. expirationdate is still filled in, because Koha::Hold::store copies patron_expiration_date into it when it is empty. To test: prove t/db_dependent/Koha/Holds.t Sponsored-by: CLAMS Co-Authored-By: Claude Opus 5.5 Signed-off-by: Brendan Lawlor Signed-off-by: Pedro Amorim commit 95c5313196202f0a6d123cafbef80c917e246d16 Author: Brendan Lawlor Date: Thu Sep 24 20:43:15 2026 +0000 Bug 39419: (QA follow-up) Allow clearing patron_expiration_date via PATCH The Holds API edit method used '//' to fall back to the existing patron_expiration_date, so an explicit null in the request body was ignored and the date could not be cleared. This patch uses 'exists', consistent with expiration_date. It also fixes a stray comma and semi colons in the AddReserve POD and removes an unused patron_expiration_date variable in ModReserve. Test plan: 1. In KTD, create a hold: curl -s -u koha:koha -X POST http://localhost:8081/api/v1/holds \ -H "Content-Type: application/json" \ -d '{"biblio_id": 1, "patron_id": 20, "pickup_library_id": "CPL", "expiration_date": "2099-01-01", "patron_expiration_date": "2099-01-01"}' 2. Using the hold_id from the response, clear both dates: curl -s -u koha:koha -X PATCH http://localhost:8081/api/v1/holds/ \ -H "Content-Type: application/json" \ -d '{"expiration_date": null, "patron_expiration_date": null}' 3. Note patron_expiration_date is still 2099-01-01 4. Apply patch and restart_all 5. Repeat steps 1 and 2 6. Note both expiration_date and patron_expiration_date are now null 7. Run: prove t/db_dependent/api/v1/holds.t t/db_dependent/Reserves.t Sponsored-by: CLAMS Co-Authored-By: Claude Opus 5.5 Signed-off-by: Brendan Lawlor Signed-off-by: Pedro Amorim commit d29aacb90822aef601272a02a5709c520a5a6b03 Author: Jonathan Druart Date: Thu Sep 17 16:26:52 2026 +0200 Bug 39419: (QA follow-up) Tidy and number of tests Signed-off-by: Jonathan Druart Signed-off-by: Pedro Amorim commit 97cd99d70159b2777eae52f09d5d909332d9c536 Author: Brendan Lawlor Date: Wed Apr 15 17:35:15 2026 +0000 Bug 39419: (follow-up) Set both expiration dates when placing holds in opac and staff interface This patch sets both the expirationdate and patron_expiration date when placing holds in the opac and staff interface as is consistent with current behavior. Test plan: 1. Place some holds: a) in the staff interface with setting a 'Hold expires on date' b) in the opac with setting a 'Hold not needed after' 2. Run this query in ktd --dbshell select borrowernumber, expirationdate, patron_expiration_date from reserves; 3. Note both expirationdate and patron_expiration_date are set in the holds Note: Editing the date in the Expiration column on the records' Holds tab only updates the expirationdate, which is consistent with current behavior. Sponsored-by: CLAMS Signed-off-by: Andrew Fuerste Henry Signed-off-by: Jonathan Druart Signed-off-by: Pedro Amorim commit d95cf37ef36b767974d60e81627a991d0152d2d0 Author: Brendan Lawlor Date: Thu Sep 4 17:58:31 2025 +0000 Bug 39419: (follow-up) Unit tests perl t/db_dependent/Reserves.t perl t/db_dependent/api/v1/holds.t Signed-off-by: Andrew Fuerste Henry Signed-off-by: Jonathan Druart Signed-off-by: Pedro Amorim commit 582554bbd6108e4ba13a6e09a7eda44a64f99a5e Author: Brendan Lawlor Date: Tue Aug 19 19:16:00 2025 +0000 Bug 39419: Enable Holds API to set expiration_date and patron_expiration_date independently This patch adds patron_expiration_date as a new parameter to the Holds API for the add and edit methods. To test: 1. Apply patch and restart_all 2. Make some requests to add holds like POST /api/v1/holds example body: { "biblio_id": "22", "patron_id": "42", "pickup_library_id": "FFL", "expiration_date": "2099-12-31", "patron_expiration_date": "3099-01-01" } 3. Verify that you can set the expiration_date and patron_expiration_date indepdently 4. Confirm that patron_expiration_date is a new field in the API response 5. Make note of the hold_id in the response for the next step 6. Make some requests to edit the hold like PATCH /api/v1/holds/{hold_id} example body: { "patron_expiration_date": "2999-09-19", "expiration_date": "2031-09-19" } 7. Verify that you can set the expiration_date and patron_expiration_date indepdently 8. Make sure making and modifying holds isn't broken in the staff intterface or opac Sponsored-by: CLAMS Signed-off-by: Andrew Fuerste Henry Signed-off-by: Jonathan Druart Signed-off-by: Pedro Amorim commit 1328267f64c78382d5048b66ecdba8905ed73f8d Author: Pedro Amorim Date: Tue Oct 13 14:48:19 2026 +0000 Bug 40478: DBIC Signed-off-by: Pedro Amorim commit 5aaa1200e0aeb5ab485173f16882e76e455dbe9b Author: Pedro Amorim Date: Tue Oct 13 14:40:21 2026 +0000 Bug 40478: DBRev 26.06.00.035 Signed-off-by: Pedro Amorim commit 2b617819d33a9166b914e149f04c2afcbacc3e1b Author: Lucas Gass Date: Tue Oct 13 14:19:02 2026 +0000 Bug 40478: (follow-up) Add support for EAN13 Signed-off-by: Pedro Amorim commit 0156fa8ffefee0e3123f8d174afd1397a7799abb Author: Lucas Gass Date: Thu Oct 1 14:18:49 2026 +0000 Bug 40478: (follow-up) Adjust kohastructure.sql Signed-off-by: Pedro Amorim