commit e926f0e5ae10a6b320f7296bf07c1b2d5b86435a Author: Matt Blenkinsop Date: Mon Oct 26 16:32:24 2026 +0000 monthly modal wip commit 7a83ec040d5b2f4c56735978d2363c2d3338d758 Author: Matt Blenkinsop Date: Fri Oct 23 11:43:13 2026 +0100 Bug 42088: Unit tests for the counter log summary Test plan: prove -l t/db_dependent/Koha/ERM/EUsage/CounterLogSummary.t \ t/db_dependent/api/v1/erm_counter_log_summaries.t \ t/db_dependent/api/v1/erm_usage_data_providers.t yarn cypress run --spec t/cypress/integration/ERM/DataProviders_spec.ts Assisted-by: Fable 5.1 (Anthropic) commit cd5b8d7d8f24498749ff065dc7fe0f1904dee346 Author: Matt Blenkinsop Date: Fri Oct 23 11:43:07 2026 +0100 Bug 42088: Remove the per-provider date aggregates from the providers API The earliest_* and latest_* fields were only used by the old summary page. Computing them ran twelve aggregate queries on erm_usage_mus for every provider in the list. last_run stays, the providers list shows it. Assisted-by: Fable 5.1 (Anthropic) commit c7b96e2cfa98fe212c6f15eae3c37fb77e42e490 Author: Matt Blenkinsop Date: Fri Oct 23 11:43:00 2026 +0100 Bug 42088: Rebuild the data providers summary from the counter logs The summary page showed the earliest and latest usage month per report family, which cost twelve aggregate queries per provider plus the content of every counter file embedded in the response. It now shows one row per harvest year, provider and report type with a status badge for each month, read from the counter log summary API in one request. The provider name opens the provider page. The first three columns can be sorted and filtered; the default order is year descending, then provider and report type. The status labels and badge classes move from the import logs tab to a shared module so both tables render the same badges. Assisted-by: Fable 5.1 (Anthropic) commit 9776978c1bf58d7cb1dc451607523d08abf73e50 Author: Matt Blenkinsop Date: Fri Oct 23 11:42:50 2026 +0100 Bug 42088: Add a counter log summary API GET /erm/counter_logs/summary lists the erm_counter_log_summary view through the read-only Koha::ERM::EUsage::CounterLogSummary class, with the usual q, _order_by and paging parameters. Searching and ordering on provider_name needs no embed because the view carries the name. Assisted-by: Fable 5.1 (Anthropic) commit 8c4bd2a0b3fd0ee1f6526d5f9ddf40f31895e106 Author: Matt Blenkinsop Date: Fri Oct 23 11:42:49 2026 +0100 Bug 42088: DBIC files for erm_counter_log_summary Generated with the view loaded into the dbic database. The primary key and the relationship to the usage data provider are added by hand below the marker, as for erm_usage_yus. Assisted-by: Fable 5.1 (Anthropic) commit ea623df070fd004fb2c5012df83eda7e72f892c1 Author: Matt Blenkinsop Date: Fri Oct 23 11:42:46 2026 +0100 Bug 42088: Add a period index and the erm_counter_log_summary view The data providers summary page needs one row per usage data provider, report type and harvest year, with the status of the latest counter log for each month. This patch adds the database side of that: - An index on erm_counter_logs (usage_data_provider_id, report_type, report_year, report_month), the natural key of a harvested month. It serves the view below, the Import logs tab and the retry lookup. - A view erm_counter_log_summary, in erm_views.sql next to erm_usage_yus. It keeps the newest log of each month (by importdate, then id) with a window function and pivots the twelve statuses into month_1 to month_12. Logs without a single-month period (multi-month harvests, uploads covering several months) are not included. MIN(erm_counter_log_id) gives the row a stable primary key for DBIC. The derived table is materialised on every query, so the view scans the single-month logs each time. With the index this stays in milliseconds for tens of thousands of logs. Assisted-by: Fable 5.1 (Anthropic) commit f23cf5a87c2e6251f9732d0eba788635c5930086 Author: Matt Blenkinsop Date: Wed Oct 21 15:03:01 2026 +0100 Bug 42088: Unit tests Test plan: prove -l t/db_dependent/Koha/ERM/EUsage/CounterLog.t \ t/db_dependent/Koha/ERM/EUsage/UsageDataProvider.t \ t/db_dependent/Koha/ERM/EUsage/COUNTER_5/CounterFile.t \ t/db_dependent/Koha/BackgroundJobs/ErmSushiHarvester.t \ t/db_dependent/api/v1/erm_counter_logs.t yarn cypress run --spec t/cypress/integration/ERM/DataProviders_spec.ts Assisted-by: Fable 5.1 (Anthropic) commit cd74b78bcc88d3ac0a50fed1a60a7d3818531773 Author: Matt Blenkinsop Date: Wed Oct 21 15:02:59 2026 +0100 Bug 42088: Show harvest status in the import logs tab The Import logs tab of a usage data provider now shows the report type, the period, a status badge with a select filter, the exception details and a link to the background job. Download and delete are only offered for rows that have a counter file. The empty state counts counter logs instead of counter files, so failed harvests are listed too. Test plan: 1. Run a harvest for a provider over two months 2. Open the provider, Import logs tab 3. Check one row per report type and month, with status, period and a job link 4. Filter the status column on "Retry needed" 5. Upload a COUNTER file and check its row Assisted-by: Fable 5.1 (Anthropic) commit d9194c5517d9162f1624606bc80e11d25ee1be92 Author: Matt Blenkinsop Date: Wed Oct 21 15:02:59 2026 +0100 Bug 42088: Expose status and the background job on the counter logs API The counter log definition gains status, and the list endpoint can embed background_job. Filtering on status already works through the q parameter, for example q={"status":"retry"}. Assisted-by: Fable 5.1 (Anthropic) commit e7a094c8441106d7e62f820e4e70109c6ec0c165 Author: Matt Blenkinsop Date: Wed Oct 21 15:02:46 2026 +0100 Bug 42088: Record the outcome of every SUSHI harvest in the counter logs Until now a counter log row was only written after a COUNTER file was stored, and logdetails was always empty. A harvest that failed left no record, and any exception in the SUSHI response blocked the import, including warnings such as 3040 Partial Data Returned. This patch writes one counter log row for every harvest attempt and for every file upload: - harvest_sushi collects the exceptions of the SUSHI response (top level exception, Exceptions array, Report_Header.Exceptions, vendor error bodies) and classifies the outcome as success, no_usage, no_data, retry or error. Code 3030 No Usage Available without items gives no_usage: the month is complete, the usage is zero. Code 3032, HTTP 204 and an empty report give no_data. Retry codes are 1000, 1010, 1011, 1020, 3031 and 3040, plus HTTP 202, 429 and 5xx. A response that is not JSON is an error. - Reports with items are stored even when they carry a warning exception; the log is flagged retry for 3040 and 3031. - logdetails holds a JSON document with the HTTP status, the exceptions, an optional free text message and the COUNTER release. The request URL is never stored or logged, it holds the API key. The HTTP failure is logged through Koha::Logger instead of warn. - CounterFile::store accepts the log fields of a harvest. For an uploaded file it reads the report type, the period and the Exceptions row from the file header. - CounterLog gains sushi_code_status, decoded_logdetails and a background_job accessor. - The background job passes its id to the harvest and reports the harvest status, shown on the job detail page. Assisted-by: Fable 5.1 (Anthropic) commit b1c213c70def1dfd64cdbd2ef62cdf212c618703 Author: Matt Blenkinsop Date: Wed Oct 21 14:31:52 2026 +0100 Bug 42088: Tolerate COUNTER header exceptions without Data The CSV header builders for COUNTER 5 and 5.1 concatenated Code, Message and Data of every Report_Header exception. An exception without Data (or without Message) raised an "uninitialized value" warning. Reports that carry a warning exception such as 3040 Partial Data Returned are now stored, so the header must handle these fields when they are absent. Assisted-by: Fable 5.1 (Anthropic) commit e2a365565e378ee40929b5404c1ed6b9fc371c88 Author: Matt Blenkinsop Date: Wed Oct 21 14:31:51 2026 +0100 Bug 42088: DBIC schema changes Assisted-by: Fable 5.1 (Anthropic) commit 998f43626713037b7538d4743a234974eeff2007 Author: Matt Blenkinsop Date: Wed Oct 21 14:22:53 2026 +0100 Bug 42088: Add a status column to erm_counter_logs Assisted-by: Sonnet 5 (Anthropic) commit 591f8be731f63dda3ab8537e0851f010b1bd372c Author: Matt Blenkinsop Date: Tue Oct 20 15:08:06 2026 +0100 Bug 42088: DBIC files commit 93e93efbdc7bdc8e7261f01dcb1f10074f005b7f Author: Matt Blenkinsop Date: Tue Oct 20 15:07:06 2026 +0100 Bug 42088: Add a link between counter logs and background jobs This patch adds a FK link between erm_counter_logs and background_jobs as well as two new columns for holding metadata about the report harvested Assisted-by: Sonnet 5 (Anthropic) commit 8dbea383d6de1fe168a3de9947b74c5ddb0fe26e Author: Matt Blenkinsop Date: Fri Oct 16 14:19:52 2026 +0100 Bug 41294: Update unit tests Assisted-by: Sonnet 5 (Anthropic) commit 72ac1ec2e5433767ce234247486eccec637caada Author: Matt Blenkinsop Date: Fri Oct 16 14:17:51 2026 +0100 Bug 41294: Update the job report for the background job and cronjob Assisted-by: Sonnet 5 (Anthropic) commit 6c9bdd3b3a5d04afb485f35e9432b1746be04083 Author: Matt Blenkinsop Date: Fri Oct 16 14:16:25 2026 +0100 Bug 41294: Update the job queue messages in the UI Assisted-by: Sonnet 5 (Anthropic) commit 54633cf4278a8bf7872364fc53ceea08562745c4 Author: Matt Blenkinsop Date: Fri Oct 16 14:14:56 2026 +0100 Bug 41294: Split harvest period into monthly jobs Assisted-by: Sonnet 5 (Anthropic) commit e81fc531dc231639f1944b631f990b6cd64ff414 Author: Matt Blenkinsop Date: Fri Oct 16 11:51:00 2026 +0100 Bug 42217: Update unit tests Assisted-by: Sonnet 5 (Anthropic) commit fb53874ce754d5aeffdd395e19b6f64c76281533 Author: Matt Blenkinsop Date: Fri Oct 16 11:50:25 2026 +0100 Bug 42217: Prevent re-harvested data from duplicating usage Assisted-by: Sonnet 5 (Anthropic) commit 5df307f0ac85fedbfef2e6249b6ea671e35c43ee Author: Matt Blenkinsop Date: Fri Oct 16 11:08:29 2026 +0100 Bug 42218: Update unit tests Assisted-by: Sonnet 5 (Anthropic) commit ac4bc9e6575e157d4ad0aeaf58ddf0f6408ddb7b Author: Matt Blenkinsop Date: Fri Oct 16 11:08:07 2026 +0100 Bug 42218: Update test builder to recognise SQL views Assisted-by: Sonnet 5 (Anthropic) commit 4028f5352680cdf43f68c48034a08fa4af3cacfd Author: Matt Blenkinsop Date: Fri Oct 16 11:06:33 2026 +0100 Bug 42218: Remove erm_usage_yus references from the code Assisted-by: Sonnet 5 (Anthropic) commit 3ba10d6da4ab791fd14243f58b5c2d964030d8ee Author: Matt Blenkinsop Date: Fri Oct 16 11:05:05 2026 +0100 Bug 42218: DBIC files Assisted-by: Sonnet 5 (Anthropic) commit 1a12c52884fb458a78ba7bdf640cb3efc19e1f21 Author: Matt Blenkinsop Date: Fri Oct 16 11:02:39 2026 +0100 Bug 42218: Remove erm_usage_yus and replace with an SQL view Assisted-by: Sonnet 5 (Anthropic) commit 9699df82669d693da842d57aad173614e9f837a8 Author: Matt Blenkinsop Date: Wed Oct 14 15:59:15 2026 +0100 Bug 42218: Create a shared UsageObject class All of the usage object classes have identical methods that should be combined into one base class Assisted-by: Sonnet 5 (Anthropic) 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