Commit Graph

61466 Commits

Author SHA1 Message Date
Martin Renvoize 4ec43ac10d
Bug 42188: (follow-up) Skip JSON payload in info column on ADD
The info column for CATALOGUING action logs is, by convention, the
record state before modification -- MODIFY writes the pre-change JSON
payload, DELETE writes the state at time of deletion, but ADD has no
pre-change state. Writing the new record state into info on ADD
duplicates the state that the subsequent MODIFY (if any) captures as
its "before" snapshot, and conflicts with that convention.

Skip the JSON payload on ADD so info degrades to the bare "biblio"
prefix. The diff column already captures the full creation (every
field "added" against an empty hashref), so no information is lost.

Test plan:
1. prove t/db_dependent/Koha/Biblio.t
2. Observe that the "CataloguingLog MARC-in-JSON diff tests" subtest
   now also asserts:
   - ADD info == "biblio"
   - MODIFY info matches /^biblio \{/
   - DELETE info matches /^biblio \{/
3. Create / modify / delete a record via the staff UI with
   CataloguingLog on, then check action_logs: the ADD row's info
   column should read just "biblio".

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-05 13:42:02 -06:00
Martin Renvoize 1846433ffb
Bug 42188: Use to_json for biblio action log info column
Replace Dumper with to_json for the biblio info column in logaction,
fixing UTF-8 encoding and extending coverage to ADD and DELETE actions
which previously logged only the literal string "biblio".

All three actions (ADD, MODIFY, DELETE) now log "biblio " followed by
a pretty-printed JSON representation of the relevant state: pre-change
for MODIFY, new state for ADD, and deleted state for DELETE.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-05 13:42:01 -06:00
Martin Renvoize 3d78340552
Bug 42188: Add/update unit tests for MARC-in-JSON action log diffs
Update the existing _unblessed_for_log() subtest to reflect the new
behaviour: MARC-derived columns (title, author, copyrightdate, etc.)
are now excluded, while frameworkcode is always included even when it
is an empty string (the Default framework).

Add a new subtest 'CataloguingLog MARC-in-JSON diff tests' covering:
- ADD:    diff _marc key is MARC-in-JSON (has leader + fields array),
          indicators and subfield values are correct, and MARC-derived
          biblio columns are absent from the diff.
- MODIFY: diff _marc key present, MARC-derived columns absent.
- DELETE: diff _marc key present with correct MARC-in-JSON structure.

Use from_json() instead of decode_json() when reading diffs back from
the database, as the DBD layer returns decoded Perl Unicode strings
which decode_json (which expects UTF-8 bytes) would reject for any
non-ASCII content.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-05 13:42:01 -06:00
Martin Renvoize 873fcbb51f
Bug 42188: Use MARC-in-JSON for biblio action log diffs
Replace the custom _marc_record_to_diffable helper with MARC::Record::MiJ's
to_mij_structure method, which produces standard MARC-in-JSON. This fixes
three problems with the old approach:

- Dollar signs used as subfield delimiters in the formatted string were
  ambiguous when subfield content itself contained " $x " sequences.
- Grouping fields by tag into a hash lost inter-field ordering, making
  lossless reconstruction of the MARC record impossible.
- Non-ASCII characters were subject to Perl string context encoding
  issues; JSON serialisation of the MiJ structure handles UTF-8 cleanly.

_unblessed_for_log in Koha::Biblio is updated to return only the three
biblio columns that are genuinely independent of the MARC record
(frameworkcode, opac_suppressed, serial). The remaining columns
(author, title, subtitle, notes, etc.) are denormalised copies of MARC
data already captured in the _marc key, so including them produced
redundant and noisy diffs.

The fmt() helper in viewlog.js is updated to render JavaScript arrays
as ordered lists rather than objects with numeric keys, improving the
display of MARC subfield arrays in the diff table.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-05 13:42:00 -06:00
David Cook b7e67b5c1d
Bug 41846: Fix routing-preview-slip.tt too
Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-05 13:42:00 -06:00
David Cook faa94eac6f
Bug 41846: Fix nl2br to happen after html escaping
This patch removes the newline to <br/> (nl2br) handling in the .pl script,
and instead moves it after the Template::Toolkit "html" filter. In this way,
we get the security of the "html" filter while also converting new lines into
safe <br/> elements. We also fix a little style problem. Hurray!

Test plan:
0. Apply the patch and koha-plack --restart kohadev
1. Ensure the RoutingSerials system preference is enabled
2. Create a "Subscription" for a known biblionumber (e.g. 29) with whatever details
3. On the subscription-detail.pl page for that subscription, click on "Create routing list"
4. Add whatever recipients you want and put the following into Notes:
Testing out notes
1-26-26 more notes
2-5-26 = more notes, 3 copies
<p>Awesome... still safe</p>

5. Click "Save"
6. Observe that your notes are output exactly the same way you put them in. The newlines
appear to be newlines to the human eye, while that pesky <p></p> markup is escaped.
7. Observe that the notes are also neatly lined up with the rest of the routing list
display for an attractive user experience
8. Celebrate again. Hurray!

Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-05 13:41:59 -06:00
Lucas Gass 610c3d0184
Bug 40658: DBRev 25.12.00.059
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-05 13:41:59 -06:00
Nick Clemens 649fbbf452
Bug 40658: Use local-number__sort and not local-number directly
This patch makes sure we inject the __sort form of local-number, rather
than the field directly - this ensurs searching works even if mappings mayu
be different and local-number itself is not numeric

To test:
1 - Edit mappings.yaml and set local-number from 'number' to 'string'
    NOTE: There are two copies of local-number - one in auth and one in biblio - the latter is the important one here
2 - perl misc/search_tools/rebuild_elasticsearch.pl -r -v
3 - Confirm in staff interface Admin -> Search Engine Configuration that local-number is now a string
4 - Search and get an error
    Error: Unable to perform your search. Please try again.
    In the logs:
[illegal_argument_exception] Text fields are not optimised for operations that require per-document field data like aggregations and sorting, so these operations are disabled by default. Please use a keyword field instead. Alternatively, set fielddata=true on [local-number] in order to load field data by uninverting the inverted index. Note that this can use significant memory.
5 - Apply patch, restart all
6 - Search again
7 - Success!

Signed-off-by: Bernard Scaife <bernard.scaife@openfifth.co.uk>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Thomas Klausner <domm@plix.at>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-05 13:41:58 -06:00
Nick Clemens 258a947b64
Bug 40658: Atomic update to ensure local-number is sortable
I don't know if this is appropriate for master as it requires Koha objects,
but this is helpful for upgrading sites to avoid any problems from old mappings
for local-number

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Thomas Klausner <domm@plix.at>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-05 13:41:58 -06:00
Simon 39c1917b21
Bug 42103: Capitalize MARC acronym in templates and DataInconsistency
This fixes the spelling of "marc" in two places:
- an error message from running the database audit
- the description for the marc_modification_templates permission

Test plan:
1. The easiest way to test this change is to check the diff for the
   patch and note that "marc" is changed to "MARC" in two places.
2. Database audit output (More > About Koha > Database audit):
   - Check the diff (otherwise would need to alter the database
     structure somehow so that the audit would generate the error
     message).
3. Check the description for the marc_modification_templates permission:
   - Go to any patron record
   - Select More > Set permissions
   - In the filter, enter: marc modification
   - Note that the description is:
       Manage marc modification templates (marc_modification_templates)
4. Apply the patch.
5. Repeat step 3 and note that the description is now:
     Manage MARC modification templates (marc_modification_templates)

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-05 13:41:48 -06:00
Andreas Roussos 6fe0b70020
Bug 41795: (follow-up) add a phrase Zebra index for 'authtype'
As per https://software.indexdata.com/zebra/doc/querymodel-rpn.html#querymodel-bib1-completeness:
"[...] Complete field (3) triggers search and scan in index type="p"."

The UNIMARC Zebra index definitions for authorities *do* include
a 'p' (short for 'phrase') type index for authtype -- however, the
MARC21 Zebra index definitions don't and this is causing these two
tests to fail:

t/db_dependent/Biblio.t
t/db_dependent/FrameworkPlugin.t

This patch fixes that.

Test plan for KTD:

1) Apply the patch, then:

   k$ sudo cp etc/zebradb/marc_defs/marc21/authorities/authority-koha-indexdefs.xml /etc/koha/zebradb/marc_defs/marc21/authorities/
   k$ sudo cp etc/zebradb/marc_defs/marc21/authorities/authority-zebra-indexdefs.xsl /etc/koha/zebradb/marc_defs/marc21/authorities/
   k$ koha-rebuild-zebra --authorities --full --force --verbose kohadev

2) Run these tests -- they should both complete without warnings/errors:

   k$ prove -v t/db_dependent/Biblio.t
   k$ prove -v t/db_dependent/FrameworkPlugin.t

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-04 11:32:18 -06:00
Nick Clemens 6a12116dd2
Bug 41343: Further improvements
Remove unused patron attribute values sent to the template

Send preferred name

Use SQL to do date comparison to see if an item is overdue

Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 11:04:37 -06:00
Nick Clemens e3862dc81b
Bug 41343: Don't fetch patron objects per overdue
We use SQL here to get all the needed info - the query says it is slow, but isn't so bad.
Worse is fetching the patron for each borrower with overdues (and more than once if they have several)

It appears we started grabbing the patron so we could use patron title, but we can just pass the info already fetched.

I additionally fetch the patron category types and update patron-title to allow for hide_patron_infos_if_needed to work
without a patron object - we can just use the branchcode as ultimately that's all we need

To test:
1 - Add many items to your catalog (can use my handy repo randitems.pl)
    https://github.com/kidclamp/handy-koha-script
2 - Add many issues (can use randissues.pl from above)
3 - I tested with about 1000
4 - Browse to Circulation->Overdues
5 - Note load time
6 - Apply patch
7 - Restart all
8 - Note load time is faster

Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 11:04:36 -06:00
Bernard Scaife 950c9f8188
Bug 41073: Fix default expiry date which is being ignored
Fixes regression in which default expiry date is ignored due to regression introduced by fix for bug 34483. If default expiry date is completed in patron import tool, the value is ignored and expiry date unaltered. This patch fixes this error.

Test plan:
1. User should exist and have expiry date in future (say 2099-12-31)
2. Save above as file
3. Tools > Import users
4. Choose file saved in 2
5. Field to use for record matching: cardnumber
6. In default values set expiration date to a date in past (say 2025-10-19) using date picker
7. Select overwrite the existing one with this
8. Leave renew existing patrons as "use the expiration date in the file if present"
9. Run
10. Observe user expiry date is still 2099-12-31
11. Apply patch / restart_all
12. Repeat 4 - 9 above
13. Observe user expiry date is now 2025-10-19

Signed-off-by: Eric Phetteplace <ephetteplace@cca.edu>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 11:04:36 -06:00
Hammat Wele a0b92c0bb6
Bug 41690: (follow-up) Add test
Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 11:04:35 -06:00
Hammat Wele 412e4e4d95
Bug 41690: Add MARC21 245 (subtitle) to Cite option
The "Cite" option in the OPAC is missing the subtitle (245) in MARC21.

To recreate:
1. Add a record or find a record with a subtitle (in KTD, biblionumber 58 has a 245 http://localhost:8081/cgi-bin/koha/catalogue/MARCdetail.pl?biblionumber=58
2. In the OPAC, go to the record
   http://localhost:8080/cgi-bin/koha/opac-detail.pl?biblionumber=58
3. Click "Cite"
   --> The subtitle is not in any of the citations
4. Apply the patch
5. Repeat step 2 and 3
    --> The subtitle is now shown

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 11:04:35 -06:00
Marcel de Rooy f5a38efff1
Bug 41962: Add comment to SearchAuthorities, change POD
Test plan:
Read the patch.
Search for both parameters to verify that they are not used.

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 11:04:34 -06:00
Marcel de Rooy db6287aa21
Bug 41795: (QA follow-up) Improve comment
Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 11:04:34 -06:00
Andreas Roussos 2b691bd4dd
Bug 41795: Fix failing test in UNIMARC instances
Previously, t/AuthoritiesMarc_MARC21.t would fail if
you tried to run it from within a UNIMARC instance.

This patch fixes that.

Test plan:

1) Inside a UNIMARC instance, run the test and
   notice the subtest failure:

   $ prove -v t/AuthoritiesMarc_MARC21.t

2) Apply this patch.

3) Re-run the test and notice that it now completes
   without any failures.

Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 11:04:33 -06:00
Andreas Roussos e43d32a123
Bug 41795: Include the completeness attribute when matching on authtype
UNIMARC instances include the following two authority types
by default (among others): Corporate Body Name (code: 'CO')
and Collective Title (code: 'CO_UNI_TI').

The problem is that when searching for Corporate Body Name
authorities (in the OPAC or in the Staff interface), the
results will include Collective Title authorities as well.
It has to do with Corporate Body Name authorities using
an authtypecode (CO) that happens to be the prefix of the
authtypecode (CO_UNI_TI) used by Collective Titles.

NOTE: Elasticsearch is not affected by this.

The fix is to additionally pass the completeness attribute
to the RPN query sent to Zebra so that it will try to match
on what is literally found in the entire field's phrase
index (i.e. the value of UNIMARC 152$b), instead of performing
a partial match.

More information can be found at the official documentation:
https://software.indexdata.com/zebra/doc/querymodel-rpn.html#querymodel-bib1-completeness

Test plan (tailored for KTD):

0) Launch a UNIMARC Koha instance using Zebra:

   ktd --marcflavour unimarc --search-engine zebra up

1) Import the Sample MARC record attached to this Bug,
   containing a single Collective Title authority
   (adding one manually would take a long time due to
   the many mandatory subfields).

2) Perform a generic authority search (without entering
   anything in the search box) for all 'Corporate Body
   Name' authorities. Note the number of results returned
   (83), and also notice that in page 4 of the results you
   get a stray 'Collective Title' authority ("Selected
   works", the one you imported in Step 1).

3) Apply this patch (and `restart_all`).

4) Repeat the same search as in Step 2. This time, the
   number of results returned should be one less than
   before (82), and you should only see 'Corporate Body
   Name' authorities in the results.

Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 11:04:33 -06:00
Fridolin Somers 3d8e069a4d
Bug 41942: Fix lang hidding in opac patron modification
Bug 34438 added lang in opac registration/modification form.
But it is linked to primary contact method hidden or not.

Looks like this is not on purpose.
This patch removes this link.

Test plan:
1) Verify system preference
   'PatronSelfModificationBorrowerUnwantedField' is empty
2) Go to OPAC user account > Personal details
3) You see 'Preferred language for notices'
4) In system preference 'PatronSelfModificationBorrowerUnwantedField'
   check 'primary_contact_method'
5) Go to OPAC user account > Personal details
   => Without patch lang is also hidden
   => With patch lang is not hidden

Signed-off-by: Owen Leonard <oleonard@myacpl.org>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 11:04:32 -06:00
Jonathan Druart d0bd802da6
Bug 42374: Restore require Koha::Plugins (instead of use)
See comment 4 for a detail explanation of the problem.

Test plan:
hit /cgi-bin/koha/installer/install.pl
login with the DB credentials
upgrade
=> Without this patch you get
```
ISBN_RANGE_MESSAGE is set to [] but that file does not exist!
Trying to use the default locations
```
=> With this patch applied: "Everything went okay. Update done."

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 11:04:32 -06:00
David Cook bf867d08c2
Bug 42374: Remove buggy ISBN_RANGE_MESSAGE env variable value
This patch removes an ISBN_RANGE_MESSAGE env variable that is
accidentally set by Business::ISBN::Data.

Test plan:
- git checkout v25.11.03-2
- reset_all
- git checkout -b 42374 origin/main
- Apply the patch
- restart_all
- Go to staff interface (e.g. http://localhost:8081/)
- Log into web installer
- Click "Update your database"
- Note it works

- Repeat the process but instead of using the web installer
use updatedatabase.pl directly (say through koha-upgrade-schema)

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 11:04:31 -06:00
Lucas Gass a601b14f26
Bug 42406: DBRev 25.12.00.058
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 11:04:31 -06:00
David Cook 8dc79ed415
Bug 42406: (QA follow-up) fix QA tool failure
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 11:04:30 -06:00
David Cook ea3b9d9c14
Bug 42406: (QA follow-up) fix little inconsistencies and errors
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 11:04:30 -06:00
Nick Clemens 3107cdc368
Bug 42406: Split delete_reports into delete_own_reports and delete_all_reports
This patch splits delete_reports into delete_own_reports and delete_all_reports

While delete_all_reports supercedes delete_own_reports it seems to make more sense than
delete_own_reports and delete_others_reports.

With delete_all_reports we can have a user who can delete all, without specific permission to delete their own
The other way we would have delete others, and you could not delete your own, which seems more odd

To test:
 1 - Have 2 users - 1 with delete_reports, 1 without
 2 - Apply patches, update database
 3 - Confirm user with permission now has 'delete_all_reports' and other user still has no reports deletion permission
 4 - Give the second user 'delete_own_reports' permission
 5 - Create 4 reports as each user
 6 - Confirm view/edit pages show the delete button for each user, and confirm they both work
 7 - You shoudl have 2 reports left for each user
 8 - Confirm each user can delete their own report from the saved SQL list:
     http://localhost:8081/cgi-bin/koha/reports/guided_reports.pl?op=list
 9 - There should be 1 report from each
10 - Give the second user 'delete_all_reports' permission in one tab and reload the saved reports in another tab
11 - Select both reports in the second tab
12 - Remove 'delete_all_reports' from the second user, leaving 'delete_own_reports' in the first tab
13 - Click 'Delete selected' in the reports tab
14 - Confirm you get 'You do not have permission to continue'
15 - Hit back, deselect other users report, confirm you can delete your own with 'Delete selected'
16 - Sign off

Signed-off-by: Judy Poyer <jpoyer@aacpl.net>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 11:04:29 -06:00
Nick Clemens cdd7438ef6
Bug 38414: (QA follow-up) Adjust templates
Now that we can explicitly list reports with any one of the permissions we need
to adjust some displays to ensure we can get to the list view.

Additionally I change 'Use saved' to 'View saved' since we may not be able to run.

I also fix the action buttons to show 'View' instead of 'Run' if user cannot execute.

I adjust reports home to check if the user can use report plugins before showing that link as well

Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 11:04:29 -06:00
David Cook 56d32231b2
Bug 38414: (follow-up) comprehensively cover ops with required permissions
Test plan:
0. Apply the patch and restart_all

1. Using a user with only "create_reports", "execute_reports", or "delete_reports",
try to perform operations not covered by that permission.
That is, if you have "create_reports", try to run, export, and delete reports.
If you have "execute_reports", try to create, modify, and delete reports
If you have "delete_reports", try to create, modify, and run reports

2. Note that a user with each of these permissions can list and view SQL.

Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 11:04:28 -06:00
Jonathan Druart e472a7607f
Bug 38414: Fix permission controller-side
This bug refactors the preview sql buttons to pass the permissions granted for each report to
the javascript that clones and updates the modal.

That allows us to check for the appropriate permissions when preparing the buttons for the modal
and to remove any that are not allowed.

This allows for different permissions per report for future devs
(see bugs 42406, 16631)

To test:
1. Create some reports
2. Create a user with only execute_reports permissions
3. Login as that user and go to Reports > use saved
4. In the actions column click the ^ and select 'Preview sql'
5. Notice delete button renders and works to delete
6. Apply patch and restart_all
7. Repeat step 4, now only the Run report button renders and works
8. Add permissions for create_reports
9. Repeat step for, now Edit, Duplicate, Schedule and run buttons render and work
10. Add permissions for delete_reports
11. Repeat step 4, confirm the delete button renders and works
12. Confirm the preview sql button works to preview different reports

We also fix some error in the script, several wrong things there:
* delete was not replaced with cud-delete
* missing op for the creation of new reports
* update and run requires two permssions

Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>

Signed-off-by: Scott Barter <sbarter@aacpl.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 11:04:28 -06:00
Brendan Lawlor 6a5b68552c
Bug 30303: (follow-up) Copy values from merge results instead of CGI
This patch makes it so the values copied come from the merge results
which fixes the case of copying empty values or - accurately.

To test:
1. Have a patron with some empty fields and some fields with '-'
2. Follow the original test plan and try to copy those fields
3. confirm empty fields and '-' now copy as expected

Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 11:04:27 -06:00
Brendan Lawlor 143b01dfa4
Bug 30303: Copy values to keeper record when merging patrons
Signed-off-by: Lari Taskula <lari.taskula@hypernova.fi>
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 11:04:27 -06:00
Brendan Lawlor a906f38203
Bug 30303: Add ui for copying values when merging patrons
When merging two patrons this patch adds a new checkbox to
'Copy values from the record to be deleted'

To test:
- Apply patches and restart_all
1. From select two patrons and click 'Merge selected patrons'
2. Select one of the records
3. Check the new box to 'Copy values from the record to be deleted'
4. A diff of the two patrons is shown with checkboxes for each field
5. Confirm checking and un-checking the boxes updates the diff as expected
6. Confirm changing the patron selection updates the diff as expected.
7. Confirm un-checking and re-checking the Copy values from the record to be deleted
   resets the check boxes for the fields to copy
8. Select some fields to copy values and click merge patrons
9. Confirm the values were copied as expected

Sponsored-by: CLAMS
Signed-off-by: Lari Taskula <lari.taskula@hypernova.fi>
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 11:04:26 -06:00
Martin Renvoize c484b66fc4
Bug 41751: Allow anonymous_refund permission to access cashups API
The cash register transaction history page (pos/register.pl) allows
access with either cashup OR anonymous_refund permission, but the
API endpoints for fetching cashups only checked for cashup permission.

This caused a 403 error when users with only anonymous_refund permission
tried to view the transaction history page, as the cashups table failed
to load.

This patch updates the API permissions for:
- GET /cash_registers/{id}/cashups
- GET /cashups/{id}

to accept either cashup or anonymous_refund permission, matching the
page access logic.

Test plan:
1. Create a staff user with only cash_management > anonymous_refund
   permission (not cashup)
2. Navigate to Point of Sale > Transaction history for any cash register
3. Verify the cashups table loads successfully (previously returned 403)
4. Run: prove t/db_dependent/api/v1/cashups.t

Signed-off-by: Jackie Usher <jackie.usher@westsussex.gov.uk>
Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 11:04:26 -06:00
Tomás Cohen Arazi 42b2758a76
Bug 42206: (follow-up) Rename to /closed_dates, improve efficiency
Rename /libraries/{id}/holidays to /libraries/{id}/closed_dates
for consistency with existing sub-resource naming (desks,
cash_registers).

Efficiency: instead of calling $calendar->is_holiday() per day
(which re-checks all three data sources each time), access the
pre-loaded data structures directly: weekly_closed_days array,
day_month_closed_days hash, and _holidays hash. The logic properly
handles exceptions (dates marked open despite a weekly/annual
closure).

Additional fixes:
- Add 365-day max range guard
- Add missing 401/403 responses in swagger spec
- Add request_id_header parameter
- Test exception handling (open on normally-closed day)
- Remove fragile 'empty defaults' assertion

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 11:04:25 -06:00
Paul Derscheid 2a3376b243
Bug 42206: Add GET /libraries/{id}/holidays endpoint
- Add list_holidays controller to Koha::REST::V1::Libraries
- Returns closed days for a library within a configurable date range
- Uses Koha::Calendar to determine holidays
- Defaults to today + 3 months if no range specified
- Add OpenAPI spec for the new endpoint
- Register route in swagger.yaml
- Add unit tests in t/db_dependent/api/v1/libraries.t

To test:
1. Apply patch
2. prove t/db_dependent/api/v1/libraries.t
3. Verify all tests pass, including the new list_holidays subtest
   which covers:
   - Holidays returned within a specified date range
   - Empty list when no holidays exist in range
   - 400 response when 'to' date precedes 'from' date
   - 404 response for non-existent library
   - Default range (no params) returns successfully

Sponsored-by: Büchereizentrale Schleswig-Holstein <https://www.bz-sh.de/>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 11:04:25 -06:00
Martin Renvoize cdf015b497
Bug 41902: Display additional biblio info on duplicate check
When a duplicate record is suspected during cataloguing, query the
AdditionalContents record_display category for the
StaffDuplicateCheckPage location. If configured, process the content
as a Template Toolkit template with the duplicate biblio object and
display the rendered result in the duplicate warning.

The TT context variable is 'biblio' (a Koha::Biblio object), so
administrators can use expressions like:
  [% biblio.author %] ([% biblio.copyrightdate %])

Content is scrubbed using the record_display scrubber for XSS safety.

Signed-off-by: Jeanne Mauriello <jeanne.mauriello@mainlib.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 11:04:24 -06:00
Martin Renvoize 470ac28564
Bug 41902: Add StaffDuplicateCheckPage location to record_display
Add a new 'StaffDuplicateCheckPage' location to the record_display
category in the Additional Contents admin UI (Tools > Additional
Contents). This allows administrators to configure custom bibliographic
information to display when a duplicate record is suspected during
cataloguing.

This uses the existing AdditionalContents infrastructure (bug 39860)
rather than a system preference, providing per-branch and multilingual
support.

Signed-off-by: Jeanne Mauriello <jeanne.mauriello@mainlib.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 11:04:24 -06:00
Martin Renvoize d095599447
Bug 42030: (follow-up) Don't diff undefined fields
This patch updates the diff for ADD/CREATE and DELETE logactions to
exclude undefined fields from the diff.

Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 11:04:23 -06:00
Martin Renvoize 1f4320be07
Bug 42030: (follow-up) Fix POD for Koha::Suggestion
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 11:04:23 -06:00
Martin Renvoize efe6f26e41
Bug 42030: Populate diff column for suggestion CREATE/MODIFY/DELETE logs
Update suggestion action logging to populate the diff column using the
correct before/after semantics:

- CREATE: empty before-state ({}), new suggestion data as after-state
- MODIFY: pre-change unblessed data as before, post-store object as after
- DELETE: log before the SQL DELETE so the object can still be fetched;
  pass the suggestion as both $infos (info column) and $original (diff
  before-state), logaction sets after-state to {}

Also add tests verifying the diff column is populated for all three actions.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 11:04:22 -06:00
Lucas Gass 7066ac0a93
Bug 23909: DBRev 25.12.00.057
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 11:04:21 -06:00
Martin Renvoize 744ac06569
Bug 23909: Preserve SCO behavior on upgrade (value 1 -> 2)
The atomicupdate converts AllowItemsOnHoldCheckoutSCO and
AllowItemsOnHoldCheckoutSIP from YesNo to Choice (0|1|2), where
value 1 now means "Allow pending holds only" and value 2 means
"Allow pending and waiting holds".

For AllowItemsOnHoldCheckoutSCO, pre-upgrade value 1 meant "allow
all on-hold checkouts (pending AND waiting)" at SCO. Post-upgrade,
sco-main.pl reads value 1 as "pending only", silently downgrading
sites that previously allowed waiting-hold checkouts. Remap SCO
value 1 -> 2 at upgrade time to preserve prior behavior.

For AllowItemsOnHoldCheckoutSIP, the SIP checkout code was already
gated on >= 1 for pending and >= 2 for waiting, so value 1 meant
"pending only" in practice even under the old YesNo type. No value
migration is needed -- leaving it at 1 preserves current behavior.

The value remap lives inside the branch that only runs when the
type is not already Choice, so re-running the atomicupdate is safe
and will not double-remap.

Test plan:
  1) On a pre-upgrade DB, set both prefs to YesNo/1:
       UPDATE systempreferences SET type='YesNo', options=NULL,
         value='1'
        WHERE variable IN ('AllowItemsOnHoldCheckoutSCO',
                           'AllowItemsOnHoldCheckoutSIP');
  2) Run installer/data/mysql/updatedatabase.pl.
  3) Verify:
       SELECT variable, type, options, value FROM systempreferences
        WHERE variable IN ('AllowItemsOnHoldCheckoutSCO',
                           'AllowItemsOnHoldCheckoutSIP');
     SCO -> type=Choice, options=0|1|2, value=2.
     SIP -> type=Choice, options=0|1|2, value=1.
  4) Re-run updatedatabase.pl; confirm both prefs report
     "already migrated, skipping" and values are unchanged.
  5) Spot-check other starting values: SCO=0 stays 0, SCO=2
     stays 2, SIP=0 stays 0.

Signed-off-by: Kristi Krueger <kkrueger@cuyahogalibrary.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 11:04:21 -06:00
Martin Renvoize f43201075a
Bug 23909: Add AllowHoldCheckoutOverride for staff circulation
Introduce a new AllowHoldCheckoutOverride system preference (default 1
to preserve existing behaviour) that lets libraries prevent staff from
overriding held-item blockers at the circulation desk.

When AllowHoldCheckoutOverride is set to 0, CanBookBeIssued still
returns the hold-related keys (RESERVED, RESERVE_WAITING, TRANSFERRED
and PROCESSING) in the confirmation hash, but circ/circulation.pl
promotes them to the impossible hash before rendering the template.
This means no override button appears, and checkout of the held item
is refused outright with a clear message explaining which patron the
hold is for.

Matching entries have been added to the IMPOSSIBLE block of the
circulation template so the blocked-checkout message is meaningful.

This sits alongside the existing Allow*Override family
(AllowTooManyOverride, AllowNotForLoanOverride, AllowHoldPolicyOverride
and AllowRenewalOnHoldOverride) and uses the same staff-only semantics.

Test plan:
1. Set AllowHoldCheckoutOverride=1 (default). Place a waiting hold
   for patron A on an item. At /cgi-bin/koha/circ/circulation.pl
   attempt to check the item out to patron B: a confirmation dialog
   appears with "Check out anyway" override buttons.
2. Set AllowHoldCheckoutOverride=0. Repeat: checkout is refused with
   a message stating the item has been waiting for patron A. No
   override button is offered.
3. Repeat with pending holds (reserves.found IS NULL), items in
   transit and items being processed: each is refused when the
   preference is 0 and offered as an overridable confirmation when
   the preference is 1.

Signed-off-by: Kristi Krueger <kkrueger@cuyahogalibrary.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 11:04:20 -06:00
Martin Renvoize b80dafc210
Bug 23909: Apply hold policy to OPAC REST self-checkout
The OPAC REST self-checkout path (guarded by OpacTrustedCheckout) is
functionally SCO-over-API, so it should honour the same hold policy
as the SCO web interface. Previously _check_availability unconditionally
promoted RESERVED, RESERVE_WAITING, TRANSFERRED and PROCESSING from
the confirmation hash to the blockers hash for any public caller. This
meant the OPAC REST endpoint always refused held items regardless of
the librarian's AllowItemsOnHoldCheckoutSCO configuration.

With this patch the promotion is gated on AllowItemsOnHoldCheckoutSCO:

* 0 - Promote all four hold confirmations to blockers (previous
      behaviour for pending holds)
* 1 - Promote only waiting/transit/processing; silently allow
      checkout of items with a pending hold
* 2 - Promote none; silently allow checkout of items with any hold

Remaining hold confirmations are stripped from the response so the
caller does not have to resolve a confirmation token for a policy the
admin has already granted.

Test plan:
1. prove t/db_dependent/api/v1/checkouts.t
2. Enable OpacTrustedCheckout. POST an item with a pending hold for
   another patron to /api/v1/public/patrons/X/checkouts:
   - AllowItemsOnHoldCheckoutSCO=0: 403 Checkout not authorized
   - AllowItemsOnHoldCheckoutSCO=1: 201 Created
3. Place a waiting hold for another patron on an item. Repeat:
   - AllowItemsOnHoldCheckoutSCO=1: 403
   - AllowItemsOnHoldCheckoutSCO=2: 201

Signed-off-by: Kristi Krueger <kkrueger@cuyahogalibrary.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 11:04:20 -06:00
Martin Renvoize cd3b0770c5
Bug 23909: Extend SIP to distinguish pending vs waiting holds
This patch extends the SIP checkout handler to honour the new
three-valued AllowItemsOnHoldCheckoutSIP preference:

* 0 - Don't allow checkout of any held item
* 1 - Allow checkout when the hold is still pending (not attached)
* 2 - Also allow checkout when the hold is waiting, in transit or
      being processed for another patron

Previously the preference was a simple YesNo toggle that, when
enabled, allowed the checkout regardless of whether the hold was
pending or already attached to an item on the hold shelf. Libraries
could not block the more disruptive case (checking out a waiting
hold destined for another patron) without also blocking pending
holds.

The existing post-confirmation filter is reworked into a single
branch per confirmation type that consults the preference value.

Test plan:
1. prove t/db_dependent/SIP/Transaction.t
2. With AllowItemsOnHoldCheckoutSIP=0, place a pending hold for
   patron A on an item, attempt SIP checkout to patron B: refused.
3. Repeat with the hold marked waiting: refused.
4. Set AllowItemsOnHoldCheckoutSIP=1, pending hold: allowed;
   waiting hold: refused.
5. Set AllowItemsOnHoldCheckoutSIP=2, waiting hold: allowed.

Signed-off-by: Kristi Krueger <kkrueger@cuyahogalibrary.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 11:04:19 -06:00
Martin Renvoize b336cf88d5
Bug 23909: Block waiting holds at SCO checkout
AllowItemsOnHoldCheckoutSCO was previously passed through to
CanBookBeIssued as the ignore_reserves flag, which skipped the entire
holds branch in C4::Circulation and silently allowed checkout of
items already waiting on the hold shelf (reserves.found = 'W') or in
transit/processing for another patron's hold.

With the pref now a 3-value Choice:
  0 = don't allow checkout of any held items
  1 = allow checkout of items with a pending hold only
  2 = allow checkout of items with any hold state

sco-main.pl now always calls CanBookBeIssued with ignore_reserves=0
and filters the needsconfirmation keys according to the pref value,
mirroring the pattern SIP already uses. RESERVE_WAITING, TRANSFERRED
and PROCESSING are added to the error-matching list and to the SCO
template so patrons see a specific "on the hold shelf" / "in transit"
/ "being processed" message instead of falling through to the generic
"please see a member of the library staff" line.

Test plan:
- Enable WebBasedSelfCheck; log in to /cgi-bin/koha/sco/sco-main.pl.
- Create an item with a pending hold for another patron
  (reserves.found IS NULL).
- With AllowItemsOnHoldCheckoutSCO = 0, scan the barcode: checkout is
  refused with "This item is on hold for another patron".
- Switch to 1 ("Allow pending holds only"): checkout succeeds.
- Set the hold to waiting (reserves.found = 'W') and try again with
  the pref still at 1: checkout is refused with "This item is waiting
  on the hold shelf for another patron".
- Switch to 2 ("Allow pending and waiting holds"): checkout succeeds.
- Repeat with found = 'T' and 'P' at pref = 1 -> refused with the
  appropriate transit/processing message; at pref = 2 -> allowed.

Signed-off-by: Kristi Krueger <kkrueger@cuyahogalibrary.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 11:04:19 -06:00
Martin Renvoize 83155b8ffd
Bug 23909: Add syspref values and atomicupdate
AllowItemsOnHoldCheckoutSCO and AllowItemsOnHoldCheckoutSIP become
Choice (0|1|2) so libraries can distinguish pending from waiting holds:

  0 = Don't allow (unchanged)
  1 = Allow pending holds only (new semantics; previously "Allow")
  2 = Allow pending and waiting holds (preserves old permissive behaviour)

A new AllowHoldCheckoutOverride YesNo preference (default 1) follows
the existing Allow*Override family and lets libraries block the
"check out anyway" override in the staff interface when an item is on
hold for another patron.

Note: installations that previously had either Allow*CheckoutSCO/SIP
set to 1 will migrate to "Allow pending only". Libraries that relied
on the previous behaviour (silently allowing checkout of items already
waiting on the hold shelf) need to manually switch to 2.

Test plan:
- Apply the patch and restart Koha.
- Run the web installer / updatedatabase.pl.
- Confirm no errors and the log mentions the three sysprefs.
- Run it a second time; confirm the "already migrated" / "already
  exists" skip messages appear.
- In Administration > System preferences, search for
  AllowItemsOnHoldCheckoutSCO and AllowItemsOnHoldCheckoutSIP and
  confirm the dropdown now has three options.
- Confirm AllowHoldCheckoutOverride appears in the same section with
  the Allow/Don't allow choices and defaults to Allow.

Signed-off-by: Kristi Krueger <kkrueger@cuyahogalibrary.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 11:04:11 -06:00
Lucas Gass dc866af68b
Bug 26355: (RM follow-up) Fix script attribute order
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 09:30:08 -06:00
Lucas Gass dc2c4014d0
Bug 26355: (RM follow-up) Fix atomic update
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 09:30:07 -06:00